Text Generation
Transformers
Safetensors
English
canopy
browser-use
web-agent
recurrent-moe
edge-llm
lightpanda
obscura
multi-agent
robotics-web
conversational
custom_code
Instructions to use psikosen/canopy-258m-r3 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use psikosen/canopy-258m-r3 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="psikosen/canopy-258m-r3", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("psikosen/canopy-258m-r3", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use psikosen/canopy-258m-r3 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "psikosen/canopy-258m-r3" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "psikosen/canopy-258m-r3", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/psikosen/canopy-258m-r3
- SGLang
How to use psikosen/canopy-258m-r3 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "psikosen/canopy-258m-r3" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "psikosen/canopy-258m-r3", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "psikosen/canopy-258m-r3" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "psikosen/canopy-258m-r3", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use psikosen/canopy-258m-r3 with Docker Model Runner:
docker model run hf.co/psikosen/canopy-258m-r3
Update to v3: Hybrid Input Protocol, 2x browser acceleration, and visual logging
Browse files- .gitattributes +1 -0
- README.md +20 -8
- miniswardbower/artifacts/complex_chains/chain1_ecommerce_receipt.png +0 -0
- miniswardbower/artifacts/complex_chains/chain2_etl_dispatched.png +0 -0
- miniswardbower/artifacts/complex_chains/chain3_spatial_approved.png +0 -0
- miniswardbower/artifacts/complex_chains_report.json +35 -0
- miniswardbower/artifacts/extended_browser_suite_report.json +110 -110
- miniswardbower/artifacts/frontier_experiments_report.json +30 -0
- miniswardbower/artifacts/live_web_report.json +14 -0
- miniswardbower/artifacts/live_web_screenshots/live_hackernews_audit.png +3 -0
- miniswardbower/artifacts/trajectories/20260905_201655/index.html +207 -0
- miniswardbower/artifacts/trajectories/20260905_201655/step_01_01_goto.png +0 -0
- miniswardbower/artifacts/trajectories/20260905_201655/step_02_01_type.png +0 -0
- miniswardbower/artifacts/trajectories/20260905_201655/step_02_02_type.png +0 -0
- miniswardbower/artifacts/trajectories/20260905_201655/step_02_03_type.png +0 -0
- miniswardbower/artifacts/trajectories/20260905_201655/step_02_04_click.png +0 -0
- miniswardbower/artifacts/v3_capabilities_report.json +5 -5
- miniswardbower/artifacts/v3_screenshots/step_01_open_modal.png +0 -0
- miniswardbower/evals/experiment_next_frontier.py +230 -0
- miniswardbower/evals/test_chained_complex_actions.py +305 -0
- miniswardbower/evals/test_live_web.py +161 -0
- miniswardbower/miniswardbower/browser/action_executor.py +32 -0
- miniswardbower/miniswardbower/browser/controller.py +65 -0
- miniswardbower/miniswardbower/evals/mock_web_env.py +271 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
miniswardbower/artifacts/live_web_screenshots/live_hackernews_audit.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -51,14 +51,26 @@ v3 introduces the **Hybrid Input Protocol** and **Event-Driven DOM Synchronizati
|
|
| 51 |
|
| 52 |
| Test Scenario | Modality | v2 Latency | v3 Latency | Latency Reduction | Speedup |
|
| 53 |
| :--- | :--- | :---: | :---: | :---: | :---: |
|
| 54 |
-
| **1. Dropdowns & Multi-Select** | 3 Selects + 1 Button Click | 434.6 ms | **
|
| 55 |
-
| **2. Modal Dialog Overlays** | Open Modal + Email Entry + Save | 1,893.8 ms | **
|
| 56 |
-
| **3. Paginated Data Tables** | Next Page + Row Approve Action | 499.6 ms | **
|
| 57 |
-
| **4. Infinite Scroll Hydration** | Wheel Scroll + Checkpoint Ack | 1,484.0 ms | **
|
| 58 |
-
| **5. Radio & Checkbox Toggles** | Radio Sel + Webhook Check + Save | 935.7 ms | **
|
| 59 |
-
| **6. Autocomplete & Typeahead** | Stream Search + Suggestion Click | 715.6 ms | **465.
|
| 60 |
-
| **7. Multi-Step Form Wizard** | 2-Stage Multi-Field Flow + Finish | 2,381.4 ms | **
|
| 61 |
-
| **Cumulative Battery Total** | **7 Scenarios / 20 Actions** | **8,344.6 ms** | **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
---
|
| 64 |
|
|
|
|
| 51 |
|
| 52 |
| Test Scenario | Modality | v2 Latency | v3 Latency | Latency Reduction | Speedup |
|
| 53 |
| :--- | :--- | :---: | :---: | :---: | :---: |
|
| 54 |
+
| **1. Dropdowns & Multi-Select** | 3 Selects + 1 Button Click | 434.6 ms | **375.0 ms** | -59.6 ms | **1.16x** |
|
| 55 |
+
| **2. Modal Dialog Overlays** | Open Modal + Email Entry + Save | 1,893.8 ms | **497.1 ms** | -1,396.7 ms | **3.81x** |
|
| 56 |
+
| **3. Paginated Data Tables** | Next Page + Row Approve Action | 499.6 ms | **433.1 ms** | -66.5 ms | **1.15x** |
|
| 57 |
+
| **4. Infinite Scroll Hydration** | Wheel Scroll + Checkpoint Ack | 1,484.0 ms | **984.1 ms** | -499.9 ms | **1.51x** |
|
| 58 |
+
| **5. Radio & Checkbox Toggles** | Radio Sel + Webhook Check + Save | 935.7 ms | **749.9 ms** | -185.8 ms | **1.25x** |
|
| 59 |
+
| **6. Autocomplete & Typeahead** | Stream Search + Suggestion Click | 715.6 ms | **465.6 ms** | -250.0 ms | **1.54x** |
|
| 60 |
+
| **7. Multi-Step Form Wizard** | 2-Stage Multi-Field Flow + Finish | 2,381.4 ms | **433.3 ms** | -1,948.1 ms | **5.50x (550%+)** |
|
| 61 |
+
| **Cumulative Battery Total** | **7 Scenarios / 20 Actions** | **8,344.6 ms** | **3,938.1 ms** | **-4,406.5 ms** | **2.12x FASTER** |
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## Complex Chained Actions Benchmark (Multi-Stage Orchestration)
|
| 66 |
+
|
| 67 |
+
| Chained Complex Action Scenario | Total Actions | Execution Latency | Verdict |
|
| 68 |
+
| :--- | :---: | :---: | :---: |
|
| 69 |
+
| **Chain 1: E-Commerce Multi-Stage Cart & Checkout** | 12 actions across 3 stages | **1,774.4 ms** | **✓ PASS** |
|
| 70 |
+
| **Chain 2: ETL Bulk Filter & Modal Dispatch** | 7 actions | **1,313.8 ms** | **✓ PASS** |
|
| 71 |
+
| **Chain 3: Spatial Grounding & Dynamic Extraction** | 4 actions | **453.0 ms** | **✓ PASS** |
|
| 72 |
+
| **Live Public Web Navigation (Hacker News)** | Real Network | **577.7 ms** | **✓ PASS** |
|
| 73 |
+
| **Speculative Action Chunking Speedup** | Multi-Field Form | **1.50x Faster (224 ms vs 336 ms)** | **✓ PASS** |
|
| 74 |
|
| 75 |
---
|
| 76 |
|
miniswardbower/artifacts/complex_chains/chain1_ecommerce_receipt.png
ADDED
|
miniswardbower/artifacts/complex_chains/chain2_etl_dispatched.png
ADDED
|
miniswardbower/artifacts/complex_chains/chain3_spatial_approved.png
ADDED
|
miniswardbower/artifacts/complex_chains_report.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"name": "Chain 1: E-Commerce Multi-Stage Cart & Checkout",
|
| 4 |
+
"passed": true,
|
| 5 |
+
"total_ms": 1774.41,
|
| 6 |
+
"stages": [
|
| 7 |
+
[
|
| 8 |
+
"Stage 1: Configure & Add to Cart",
|
| 9 |
+
383.6338601540774
|
| 10 |
+
],
|
| 11 |
+
[
|
| 12 |
+
"Stage 2: Voucher & Checkout Open",
|
| 13 |
+
676.8952731508762
|
| 14 |
+
],
|
| 15 |
+
[
|
| 16 |
+
"Stage 3: Pipelined Form Submission",
|
| 17 |
+
544.9664250481874
|
| 18 |
+
]
|
| 19 |
+
],
|
| 20 |
+
"order_id": "CANOPY-ORD-508829",
|
| 21 |
+
"screenshot": "/home/psikosen/canopy/miniswardbower/artifacts/complex_chains/chain1_ecommerce_receipt.png"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"name": "Chain 2: ETL Bulk Filter & Modal Dispatch",
|
| 25 |
+
"passed": true,
|
| 26 |
+
"total_ms": 1313.81,
|
| 27 |
+
"screenshot": "/home/psikosen/canopy/miniswardbower/artifacts/complex_chains/chain2_etl_dispatched.png"
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"name": "Chain 3: Spatial Grounding & Table Extraction",
|
| 31 |
+
"passed": true,
|
| 32 |
+
"total_ms": 452.98,
|
| 33 |
+
"screenshot": "/home/psikosen/canopy/miniswardbower/artifacts/complex_chains/chain3_spatial_approved.png"
|
| 34 |
+
}
|
| 35 |
+
]
|
miniswardbower/artifacts/extended_browser_suite_report.json
CHANGED
|
@@ -1,56 +1,56 @@
|
|
| 1 |
{
|
| 2 |
-
"timestamp": "2026-09-05
|
| 3 |
"total_scenarios": 7,
|
| 4 |
"passed_scenarios": 7,
|
| 5 |
-
"cumulative_battery_time_ms":
|
| 6 |
"scenarios": [
|
| 7 |
{
|
| 8 |
"scenario": "1. Dynamic Dropdowns & Selects",
|
| 9 |
"passed": true,
|
| 10 |
-
"total_time_ms":
|
| 11 |
-
"exec_ms":
|
| 12 |
-
"distill_ms":
|
| 13 |
-
"verify_ms":
|
| 14 |
"bottleneck": "Execution (Kinematics)",
|
| 15 |
"step_count": 4,
|
| 16 |
"steps": [
|
| 17 |
{
|
| 18 |
"op": "select",
|
| 19 |
"target": "@e1",
|
| 20 |
-
"total_ms":
|
| 21 |
-
"exec_ms":
|
| 22 |
-
"distill_ms": 2.
|
| 23 |
-
"verify_ms":
|
| 24 |
"status": "success",
|
| 25 |
"summary": "Executed action select"
|
| 26 |
},
|
| 27 |
{
|
| 28 |
"op": "select",
|
| 29 |
"target": "@e2",
|
| 30 |
-
"total_ms":
|
| 31 |
-
"exec_ms":
|
| 32 |
-
"distill_ms":
|
| 33 |
-
"verify_ms":
|
| 34 |
"status": "success",
|
| 35 |
"summary": "Executed action select"
|
| 36 |
},
|
| 37 |
{
|
| 38 |
"op": "select",
|
| 39 |
"target": "@e3",
|
| 40 |
-
"total_ms":
|
| 41 |
-
"exec_ms":
|
| 42 |
-
"distill_ms":
|
| 43 |
-
"verify_ms":
|
| 44 |
"status": "success",
|
| 45 |
"summary": "Executed action select"
|
| 46 |
},
|
| 47 |
{
|
| 48 |
"op": "click",
|
| 49 |
"target": "@e4",
|
| 50 |
-
"total_ms":
|
| 51 |
-
"exec_ms":
|
| 52 |
-
"distill_ms":
|
| 53 |
-
"verify_ms":
|
| 54 |
"status": "success",
|
| 55 |
"summary": "Executed click on @e4; URL changed: False"
|
| 56 |
}
|
|
@@ -59,40 +59,40 @@
|
|
| 59 |
{
|
| 60 |
"scenario": "2. Modal Dialogs & Focus Trapping",
|
| 61 |
"passed": true,
|
| 62 |
-
"total_time_ms":
|
| 63 |
-
"exec_ms":
|
| 64 |
-
"distill_ms":
|
| 65 |
-
"verify_ms":
|
| 66 |
"bottleneck": "Execution (Kinematics)",
|
| 67 |
"step_count": 3,
|
| 68 |
"steps": [
|
| 69 |
{
|
| 70 |
"op": "click",
|
| 71 |
"target": "@e1",
|
| 72 |
-
"total_ms": 223.
|
| 73 |
-
"exec_ms":
|
| 74 |
-
"distill_ms":
|
| 75 |
-
"verify_ms":
|
| 76 |
"status": "success",
|
| 77 |
"summary": "Executed click on @e1; URL changed: False"
|
| 78 |
},
|
| 79 |
{
|
| 80 |
"op": "type",
|
| 81 |
"target": "@e2",
|
| 82 |
-
"total_ms":
|
| 83 |
-
"exec_ms":
|
| 84 |
-
"distill_ms":
|
| 85 |
-
"verify_ms":
|
| 86 |
"status": "success",
|
| 87 |
"summary": "Typed text into @e2"
|
| 88 |
},
|
| 89 |
{
|
| 90 |
"op": "click",
|
| 91 |
"target": "@e4",
|
| 92 |
-
"total_ms":
|
| 93 |
-
"exec_ms":
|
| 94 |
-
"distill_ms": 1.
|
| 95 |
-
"verify_ms":
|
| 96 |
"status": "success",
|
| 97 |
"summary": "Executed click on @e4; URL changed: False"
|
| 98 |
}
|
|
@@ -101,30 +101,30 @@
|
|
| 101 |
{
|
| 102 |
"scenario": "3. Paginated Data Tables & Actions",
|
| 103 |
"passed": true,
|
| 104 |
-
"total_time_ms":
|
| 105 |
-
"exec_ms":
|
| 106 |
-
"distill_ms":
|
| 107 |
-
"verify_ms":
|
| 108 |
"bottleneck": "Execution (Kinematics)",
|
| 109 |
"step_count": 2,
|
| 110 |
"steps": [
|
| 111 |
{
|
| 112 |
"op": "click",
|
| 113 |
"target": "@e3",
|
| 114 |
-
"total_ms":
|
| 115 |
-
"exec_ms":
|
| 116 |
-
"distill_ms":
|
| 117 |
-
"verify_ms":
|
| 118 |
"status": "success",
|
| 119 |
"summary": "Executed click on @e3; URL changed: False"
|
| 120 |
},
|
| 121 |
{
|
| 122 |
"op": "click",
|
| 123 |
"target": "@e1",
|
| 124 |
-
"total_ms":
|
| 125 |
-
"exec_ms":
|
| 126 |
-
"distill_ms":
|
| 127 |
-
"verify_ms":
|
| 128 |
"status": "success",
|
| 129 |
"summary": "Executed click on @e1; URL changed: False"
|
| 130 |
}
|
|
@@ -133,30 +133,30 @@
|
|
| 133 |
{
|
| 134 |
"scenario": "4. Infinite Scroll & Dynamic Hydration",
|
| 135 |
"passed": true,
|
| 136 |
-
"total_time_ms":
|
| 137 |
-
"exec_ms":
|
| 138 |
-
"distill_ms":
|
| 139 |
-
"verify_ms":
|
| 140 |
"bottleneck": "Execution (Kinematics)",
|
| 141 |
"step_count": 2,
|
| 142 |
"steps": [
|
| 143 |
{
|
| 144 |
"op": "scroll",
|
| 145 |
"target": "down",
|
| 146 |
-
"total_ms":
|
| 147 |
-
"exec_ms":
|
| 148 |
-
"distill_ms": 2.
|
| 149 |
-
"verify_ms":
|
| 150 |
"status": "success",
|
| 151 |
"summary": "Executed action scroll"
|
| 152 |
},
|
| 153 |
{
|
| 154 |
"op": "click",
|
| 155 |
"target": "@e1",
|
| 156 |
-
"total_ms":
|
| 157 |
-
"exec_ms":
|
| 158 |
-
"distill_ms": 1.
|
| 159 |
-
"verify_ms":
|
| 160 |
"status": "success",
|
| 161 |
"summary": "Executed click on @e1; URL changed: False"
|
| 162 |
}
|
|
@@ -165,40 +165,40 @@
|
|
| 165 |
{
|
| 166 |
"scenario": "5. Radio Groups & Checkbox Toggles",
|
| 167 |
"passed": true,
|
| 168 |
-
"total_time_ms":
|
| 169 |
-
"exec_ms":
|
| 170 |
-
"distill_ms":
|
| 171 |
-
"verify_ms":
|
| 172 |
"bottleneck": "Execution (Kinematics)",
|
| 173 |
"step_count": 3,
|
| 174 |
"steps": [
|
| 175 |
{
|
| 176 |
"op": "click",
|
| 177 |
"target": "@e2",
|
| 178 |
-
"total_ms":
|
| 179 |
-
"exec_ms":
|
| 180 |
-
"distill_ms":
|
| 181 |
-
"verify_ms":
|
| 182 |
"status": "success",
|
| 183 |
"summary": "Executed click on @e2; URL changed: False"
|
| 184 |
},
|
| 185 |
{
|
| 186 |
"op": "click",
|
| 187 |
"target": "@e5",
|
| 188 |
-
"total_ms": 199.
|
| 189 |
-
"exec_ms":
|
| 190 |
-
"distill_ms":
|
| 191 |
-
"verify_ms":
|
| 192 |
"status": "success",
|
| 193 |
"summary": "Executed click on @e5; URL changed: False"
|
| 194 |
},
|
| 195 |
{
|
| 196 |
"op": "click",
|
| 197 |
"target": "@e7",
|
| 198 |
-
"total_ms":
|
| 199 |
-
"exec_ms":
|
| 200 |
-
"distill_ms":
|
| 201 |
-
"verify_ms":
|
| 202 |
"status": "success",
|
| 203 |
"summary": "Executed click on @e7; URL changed: False"
|
| 204 |
}
|
|
@@ -207,30 +207,30 @@
|
|
| 207 |
{
|
| 208 |
"scenario": "6. Autocomplete & Typeahead Search",
|
| 209 |
"passed": true,
|
| 210 |
-
"total_time_ms": 465.
|
| 211 |
-
"exec_ms":
|
| 212 |
-
"distill_ms":
|
| 213 |
-
"verify_ms":
|
| 214 |
"bottleneck": "Execution (Kinematics)",
|
| 215 |
"step_count": 2,
|
| 216 |
"steps": [
|
| 217 |
{
|
| 218 |
"op": "type",
|
| 219 |
"target": "@e1",
|
| 220 |
-
"total_ms":
|
| 221 |
-
"exec_ms":
|
| 222 |
-
"distill_ms":
|
| 223 |
-
"verify_ms":
|
| 224 |
"status": "success",
|
| 225 |
"summary": "Typed text into @e1"
|
| 226 |
},
|
| 227 |
{
|
| 228 |
"op": "click",
|
| 229 |
"target": "@e2",
|
| 230 |
-
"total_ms":
|
| 231 |
-
"exec_ms":
|
| 232 |
-
"distill_ms": 1.
|
| 233 |
-
"verify_ms":
|
| 234 |
"status": "success",
|
| 235 |
"summary": "Executed click on @e2; URL changed: False"
|
| 236 |
}
|
|
@@ -239,50 +239,50 @@
|
|
| 239 |
{
|
| 240 |
"scenario": "7. Multi-Step Onboarding Wizard",
|
| 241 |
"passed": true,
|
| 242 |
-
"total_time_ms":
|
| 243 |
-
"exec_ms":
|
| 244 |
-
"distill_ms":
|
| 245 |
-
"verify_ms":
|
| 246 |
"bottleneck": "Execution (Kinematics)",
|
| 247 |
"step_count": 4,
|
| 248 |
"steps": [
|
| 249 |
{
|
| 250 |
"op": "type",
|
| 251 |
"target": "@e1",
|
| 252 |
-
"total_ms":
|
| 253 |
-
"exec_ms":
|
| 254 |
-
"distill_ms":
|
| 255 |
-
"verify_ms":
|
| 256 |
"status": "success",
|
| 257 |
"summary": "Typed text into @e1"
|
| 258 |
},
|
| 259 |
{
|
| 260 |
"op": "click",
|
| 261 |
"target": "@e2",
|
| 262 |
-
"total_ms":
|
| 263 |
-
"exec_ms":
|
| 264 |
-
"distill_ms":
|
| 265 |
-
"verify_ms":
|
| 266 |
"status": "success",
|
| 267 |
"summary": "Executed click on @e2; URL changed: False"
|
| 268 |
},
|
| 269 |
{
|
| 270 |
"op": "type",
|
| 271 |
"target": "@e1",
|
| 272 |
-
"total_ms":
|
| 273 |
-
"exec_ms":
|
| 274 |
-
"distill_ms":
|
| 275 |
-
"verify_ms":
|
| 276 |
"status": "success",
|
| 277 |
"summary": "Typed text into @e1"
|
| 278 |
},
|
| 279 |
{
|
| 280 |
"op": "click",
|
| 281 |
"target": "@e2",
|
| 282 |
-
"total_ms":
|
| 283 |
-
"exec_ms":
|
| 284 |
-
"distill_ms": 1.
|
| 285 |
-
"verify_ms":
|
| 286 |
"status": "success",
|
| 287 |
"summary": "Executed click on @e2; URL changed: False"
|
| 288 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"timestamp": "2026-09-05 20:16:47",
|
| 3 |
"total_scenarios": 7,
|
| 4 |
"passed_scenarios": 7,
|
| 5 |
+
"cumulative_battery_time_ms": 3938.09,
|
| 6 |
"scenarios": [
|
| 7 |
{
|
| 8 |
"scenario": "1. Dynamic Dropdowns & Selects",
|
| 9 |
"passed": true,
|
| 10 |
+
"total_time_ms": 375.0,
|
| 11 |
+
"exec_ms": 316.12,
|
| 12 |
+
"distill_ms": 8.79,
|
| 13 |
+
"verify_ms": 18.9,
|
| 14 |
"bottleneck": "Execution (Kinematics)",
|
| 15 |
"step_count": 4,
|
| 16 |
"steps": [
|
| 17 |
{
|
| 18 |
"op": "select",
|
| 19 |
"target": "@e1",
|
| 20 |
+
"total_ms": 37.57,
|
| 21 |
+
"exec_ms": 28.79,
|
| 22 |
+
"distill_ms": 2.5,
|
| 23 |
+
"verify_ms": 6.27,
|
| 24 |
"status": "success",
|
| 25 |
"summary": "Executed action select"
|
| 26 |
},
|
| 27 |
{
|
| 28 |
"op": "select",
|
| 29 |
"target": "@e2",
|
| 30 |
+
"total_ms": 12.48,
|
| 31 |
+
"exec_ms": 3.17,
|
| 32 |
+
"distill_ms": 2.36,
|
| 33 |
+
"verify_ms": 6.95,
|
| 34 |
"status": "success",
|
| 35 |
"summary": "Executed action select"
|
| 36 |
},
|
| 37 |
{
|
| 38 |
"op": "select",
|
| 39 |
"target": "@e3",
|
| 40 |
+
"total_ms": 14.03,
|
| 41 |
+
"exec_ms": 8.41,
|
| 42 |
+
"distill_ms": 2.35,
|
| 43 |
+
"verify_ms": 3.26,
|
| 44 |
"status": "success",
|
| 45 |
"summary": "Executed action select"
|
| 46 |
},
|
| 47 |
{
|
| 48 |
"op": "click",
|
| 49 |
"target": "@e4",
|
| 50 |
+
"total_ms": 279.76,
|
| 51 |
+
"exec_ms": 275.75,
|
| 52 |
+
"distill_ms": 1.58,
|
| 53 |
+
"verify_ms": 2.42,
|
| 54 |
"status": "success",
|
| 55 |
"summary": "Executed click on @e4; URL changed: False"
|
| 56 |
}
|
|
|
|
| 59 |
{
|
| 60 |
"scenario": "2. Modal Dialogs & Focus Trapping",
|
| 61 |
"passed": true,
|
| 62 |
+
"total_time_ms": 497.15,
|
| 63 |
+
"exec_ms": 475.36,
|
| 64 |
+
"distill_ms": 4.27,
|
| 65 |
+
"verify_ms": 8.23,
|
| 66 |
"bottleneck": "Execution (Kinematics)",
|
| 67 |
"step_count": 3,
|
| 68 |
"steps": [
|
| 69 |
{
|
| 70 |
"op": "click",
|
| 71 |
"target": "@e1",
|
| 72 |
+
"total_ms": 223.02,
|
| 73 |
+
"exec_ms": 218.49,
|
| 74 |
+
"distill_ms": 1.65,
|
| 75 |
+
"verify_ms": 2.88,
|
| 76 |
"status": "success",
|
| 77 |
"summary": "Executed click on @e1; URL changed: False"
|
| 78 |
},
|
| 79 |
{
|
| 80 |
"op": "type",
|
| 81 |
"target": "@e2",
|
| 82 |
+
"total_ms": 12.09,
|
| 83 |
+
"exec_ms": 7.74,
|
| 84 |
+
"distill_ms": 1.26,
|
| 85 |
+
"verify_ms": 3.09,
|
| 86 |
"status": "success",
|
| 87 |
"summary": "Typed text into @e2"
|
| 88 |
},
|
| 89 |
{
|
| 90 |
"op": "click",
|
| 91 |
"target": "@e4",
|
| 92 |
+
"total_ms": 252.75,
|
| 93 |
+
"exec_ms": 249.13,
|
| 94 |
+
"distill_ms": 1.36,
|
| 95 |
+
"verify_ms": 2.26,
|
| 96 |
"status": "success",
|
| 97 |
"summary": "Executed click on @e4; URL changed: False"
|
| 98 |
}
|
|
|
|
| 101 |
{
|
| 102 |
"scenario": "3. Paginated Data Tables & Actions",
|
| 103 |
"passed": true,
|
| 104 |
+
"total_time_ms": 433.09,
|
| 105 |
+
"exec_ms": 415.12,
|
| 106 |
+
"distill_ms": 2.98,
|
| 107 |
+
"verify_ms": 4.1,
|
| 108 |
"bottleneck": "Execution (Kinematics)",
|
| 109 |
"step_count": 2,
|
| 110 |
"steps": [
|
| 111 |
{
|
| 112 |
"op": "click",
|
| 113 |
"target": "@e3",
|
| 114 |
+
"total_ms": 222.24,
|
| 115 |
+
"exec_ms": 218.83,
|
| 116 |
+
"distill_ms": 1.34,
|
| 117 |
+
"verify_ms": 2.07,
|
| 118 |
"status": "success",
|
| 119 |
"summary": "Executed click on @e3; URL changed: False"
|
| 120 |
},
|
| 121 |
{
|
| 122 |
"op": "click",
|
| 123 |
"target": "@e1",
|
| 124 |
+
"total_ms": 199.96,
|
| 125 |
+
"exec_ms": 196.29,
|
| 126 |
+
"distill_ms": 1.64,
|
| 127 |
+
"verify_ms": 2.03,
|
| 128 |
"status": "success",
|
| 129 |
"summary": "Executed click on @e1; URL changed: False"
|
| 130 |
}
|
|
|
|
| 133 |
{
|
| 134 |
"scenario": "4. Infinite Scroll & Dynamic Hydration",
|
| 135 |
"passed": true,
|
| 136 |
+
"total_time_ms": 984.12,
|
| 137 |
+
"exec_ms": 452.22,
|
| 138 |
+
"distill_ms": 3.79,
|
| 139 |
+
"verify_ms": 12.36,
|
| 140 |
"bottleneck": "Execution (Kinematics)",
|
| 141 |
"step_count": 2,
|
| 142 |
"steps": [
|
| 143 |
{
|
| 144 |
"op": "scroll",
|
| 145 |
"target": "down",
|
| 146 |
+
"total_ms": 130.38,
|
| 147 |
+
"exec_ms": 118.06,
|
| 148 |
+
"distill_ms": 2.32,
|
| 149 |
+
"verify_ms": 10.0,
|
| 150 |
"status": "success",
|
| 151 |
"summary": "Executed action scroll"
|
| 152 |
},
|
| 153 |
{
|
| 154 |
"op": "click",
|
| 155 |
"target": "@e1",
|
| 156 |
+
"total_ms": 338.0,
|
| 157 |
+
"exec_ms": 334.16,
|
| 158 |
+
"distill_ms": 1.47,
|
| 159 |
+
"verify_ms": 2.36,
|
| 160 |
"status": "success",
|
| 161 |
"summary": "Executed click on @e1; URL changed: False"
|
| 162 |
}
|
|
|
|
| 165 |
{
|
| 166 |
"scenario": "5. Radio Groups & Checkbox Toggles",
|
| 167 |
"passed": true,
|
| 168 |
+
"total_time_ms": 749.88,
|
| 169 |
+
"exec_ms": 728.32,
|
| 170 |
+
"distill_ms": 5.35,
|
| 171 |
+
"verify_ms": 6.28,
|
| 172 |
"bottleneck": "Execution (Kinematics)",
|
| 173 |
"step_count": 3,
|
| 174 |
"steps": [
|
| 175 |
{
|
| 176 |
"op": "click",
|
| 177 |
"target": "@e2",
|
| 178 |
+
"total_ms": 339.32,
|
| 179 |
+
"exec_ms": 335.54,
|
| 180 |
+
"distill_ms": 1.8,
|
| 181 |
+
"verify_ms": 1.98,
|
| 182 |
"status": "success",
|
| 183 |
"summary": "Executed click on @e2; URL changed: False"
|
| 184 |
},
|
| 185 |
{
|
| 186 |
"op": "click",
|
| 187 |
"target": "@e5",
|
| 188 |
+
"total_ms": 199.36,
|
| 189 |
+
"exec_ms": 195.85,
|
| 190 |
+
"distill_ms": 1.63,
|
| 191 |
+
"verify_ms": 1.88,
|
| 192 |
"status": "success",
|
| 193 |
"summary": "Executed click on @e5; URL changed: False"
|
| 194 |
},
|
| 195 |
{
|
| 196 |
"op": "click",
|
| 197 |
"target": "@e7",
|
| 198 |
+
"total_ms": 201.28,
|
| 199 |
+
"exec_ms": 196.93,
|
| 200 |
+
"distill_ms": 1.92,
|
| 201 |
+
"verify_ms": 2.42,
|
| 202 |
"status": "success",
|
| 203 |
"summary": "Executed click on @e7; URL changed: False"
|
| 204 |
}
|
|
|
|
| 207 |
{
|
| 208 |
"scenario": "6. Autocomplete & Typeahead Search",
|
| 209 |
"passed": true,
|
| 210 |
+
"total_time_ms": 465.55,
|
| 211 |
+
"exec_ms": 243.84,
|
| 212 |
+
"distill_ms": 2.52,
|
| 213 |
+
"verify_ms": 4.6,
|
| 214 |
"bottleneck": "Execution (Kinematics)",
|
| 215 |
"step_count": 2,
|
| 216 |
"steps": [
|
| 217 |
{
|
| 218 |
"op": "type",
|
| 219 |
"target": "@e1",
|
| 220 |
+
"total_ms": 24.32,
|
| 221 |
+
"exec_ms": 20.23,
|
| 222 |
+
"distill_ms": 1.35,
|
| 223 |
+
"verify_ms": 2.74,
|
| 224 |
"status": "success",
|
| 225 |
"summary": "Typed text into @e1"
|
| 226 |
},
|
| 227 |
{
|
| 228 |
"op": "click",
|
| 229 |
"target": "@e2",
|
| 230 |
+
"total_ms": 226.65,
|
| 231 |
+
"exec_ms": 223.61,
|
| 232 |
+
"distill_ms": 1.17,
|
| 233 |
+
"verify_ms": 1.86,
|
| 234 |
"status": "success",
|
| 235 |
"summary": "Executed click on @e2; URL changed: False"
|
| 236 |
}
|
|
|
|
| 239 |
{
|
| 240 |
"scenario": "7. Multi-Step Onboarding Wizard",
|
| 241 |
"passed": true,
|
| 242 |
+
"total_time_ms": 433.3,
|
| 243 |
+
"exec_ms": 411.47,
|
| 244 |
+
"distill_ms": 4.51,
|
| 245 |
+
"verify_ms": 8.41,
|
| 246 |
"bottleneck": "Execution (Kinematics)",
|
| 247 |
"step_count": 4,
|
| 248 |
"steps": [
|
| 249 |
{
|
| 250 |
"op": "type",
|
| 251 |
"target": "@e1",
|
| 252 |
+
"total_ms": 23.98,
|
| 253 |
+
"exec_ms": 20.41,
|
| 254 |
+
"distill_ms": 1.15,
|
| 255 |
+
"verify_ms": 2.42,
|
| 256 |
"status": "success",
|
| 257 |
"summary": "Typed text into @e1"
|
| 258 |
},
|
| 259 |
{
|
| 260 |
"op": "click",
|
| 261 |
"target": "@e2",
|
| 262 |
+
"total_ms": 200.23,
|
| 263 |
+
"exec_ms": 197.34,
|
| 264 |
+
"distill_ms": 1.23,
|
| 265 |
+
"verify_ms": 1.66,
|
| 266 |
"status": "success",
|
| 267 |
"summary": "Executed click on @e2; URL changed: False"
|
| 268 |
},
|
| 269 |
{
|
| 270 |
"op": "type",
|
| 271 |
"target": "@e1",
|
| 272 |
+
"total_ms": 7.38,
|
| 273 |
+
"exec_ms": 4.47,
|
| 274 |
+
"distill_ms": 0.96,
|
| 275 |
+
"verify_ms": 1.96,
|
| 276 |
"status": "success",
|
| 277 |
"summary": "Typed text into @e1"
|
| 278 |
},
|
| 279 |
{
|
| 280 |
"op": "click",
|
| 281 |
"target": "@e2",
|
| 282 |
+
"total_ms": 192.79,
|
| 283 |
+
"exec_ms": 189.25,
|
| 284 |
+
"distill_ms": 1.17,
|
| 285 |
+
"verify_ms": 2.37,
|
| 286 |
"status": "success",
|
| 287 |
"summary": "Executed click on @e2; URL changed: False"
|
| 288 |
}
|
miniswardbower/artifacts/frontier_experiments_report.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"experiment": "Speculative Action Chunking",
|
| 4 |
+
"step_by_step_ms": 336.12,
|
| 5 |
+
"chunked_ms": 224.45,
|
| 6 |
+
"speedup": 1.5,
|
| 7 |
+
"success": true
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"experiment": "Stealth Fingerprint Profile",
|
| 11 |
+
"fingerprint": {
|
| 12 |
+
"webdriver": null,
|
| 13 |
+
"plugins_count": 3,
|
| 14 |
+
"languages": [
|
| 15 |
+
"en-US",
|
| 16 |
+
"en"
|
| 17 |
+
],
|
| 18 |
+
"has_chrome": true,
|
| 19 |
+
"device_memory": 8,
|
| 20 |
+
"hardware_concurrency": 24
|
| 21 |
+
},
|
| 22 |
+
"is_stealthy": true
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"experiment": "Adaptive Recurrent Inference",
|
| 26 |
+
"baseline_tok_per_sec": 50.8,
|
| 27 |
+
"adaptive_tok_per_sec_projected": 63.3,
|
| 28 |
+
"potential_speedup": 1.25
|
| 29 |
+
}
|
| 30 |
+
]
|
miniswardbower/artifacts/live_web_report.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"network_headers": {
|
| 3 |
+
"passed": true,
|
| 4 |
+
"rtt_ms": 340.35,
|
| 5 |
+
"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
|
| 6 |
+
"accept_language": "en-US"
|
| 7 |
+
},
|
| 8 |
+
"live_scraping": {
|
| 9 |
+
"passed": true,
|
| 10 |
+
"stories_count": 5,
|
| 11 |
+
"sample_title": "Private German rocket makes history, reaches orbit from European soil",
|
| 12 |
+
"screenshot": "/home/psikosen/canopy/miniswardbower/artifacts/live_web_screenshots/live_hackernews_audit.png"
|
| 13 |
+
}
|
| 14 |
+
}
|
miniswardbower/artifacts/live_web_screenshots/live_hackernews_audit.png
ADDED
|
Git LFS Details
|
miniswardbower/artifacts/trajectories/20260905_201655/index.html
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Visual Audit Trail - miniswardbower</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root {
|
| 9 |
+
--bg-color: #0f172a;
|
| 10 |
+
--card-bg: #1e293b;
|
| 11 |
+
--text-main: #f8fafc;
|
| 12 |
+
--text-dim: #94a3b8;
|
| 13 |
+
--border-color: #334155;
|
| 14 |
+
--accent-cyan: #38bdf8;
|
| 15 |
+
--accent-emerald: #10b981;
|
| 16 |
+
}
|
| 17 |
+
body {
|
| 18 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
| 19 |
+
background: var(--bg-color);
|
| 20 |
+
color: var(--text-main);
|
| 21 |
+
margin: 0;
|
| 22 |
+
padding: 30px 20px;
|
| 23 |
+
}
|
| 24 |
+
.container { max-width: 1200px; margin: 0 auto; }
|
| 25 |
+
header {
|
| 26 |
+
background: var(--card-bg);
|
| 27 |
+
border: 1px solid var(--border-color);
|
| 28 |
+
border-radius: 12px;
|
| 29 |
+
padding: 24px 30px;
|
| 30 |
+
margin-bottom: 30px;
|
| 31 |
+
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.5);
|
| 32 |
+
}
|
| 33 |
+
.status-pill {
|
| 34 |
+
display: inline-block;
|
| 35 |
+
background: #10b98125;
|
| 36 |
+
color: #10b981;
|
| 37 |
+
border: 1px solid #10b981;
|
| 38 |
+
font-weight: 700;
|
| 39 |
+
font-size: 13px;
|
| 40 |
+
padding: 4px 12px;
|
| 41 |
+
border-radius: 9999px;
|
| 42 |
+
text-transform: uppercase;
|
| 43 |
+
letter-spacing: 0.05em;
|
| 44 |
+
}
|
| 45 |
+
h1 { margin: 12px 0 8px 0; font-size: 26px; }
|
| 46 |
+
.objective-text { font-size: 16px; color: var(--text-dim); margin-bottom: 16px; }
|
| 47 |
+
.metrics-bar {
|
| 48 |
+
display: flex;
|
| 49 |
+
gap: 24px;
|
| 50 |
+
border-top: 1px solid var(--border-color);
|
| 51 |
+
padding-top: 16px;
|
| 52 |
+
font-size: 14px;
|
| 53 |
+
}
|
| 54 |
+
.metric-item span { color: var(--accent-cyan); font-weight: 700; }
|
| 55 |
+
.gallery-grid {
|
| 56 |
+
display: grid;
|
| 57 |
+
grid-template-columns: repeat(auto-fit, minmax(540px, 1fr));
|
| 58 |
+
gap: 24px;
|
| 59 |
+
}
|
| 60 |
+
.step-card {
|
| 61 |
+
background: var(--card-bg);
|
| 62 |
+
border: 1px solid var(--border-color);
|
| 63 |
+
border-radius: 10px;
|
| 64 |
+
overflow: hidden;
|
| 65 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
| 66 |
+
display: flex;
|
| 67 |
+
flex-direction: column;
|
| 68 |
+
}
|
| 69 |
+
.card-header {
|
| 70 |
+
display: flex;
|
| 71 |
+
align-items: center;
|
| 72 |
+
gap: 12px;
|
| 73 |
+
padding: 12px 18px;
|
| 74 |
+
background: #182234;
|
| 75 |
+
border-bottom: 1px solid var(--border-color);
|
| 76 |
+
}
|
| 77 |
+
.step-badge {
|
| 78 |
+
background: var(--accent-cyan);
|
| 79 |
+
color: #0f172a;
|
| 80 |
+
font-weight: 800;
|
| 81 |
+
font-size: 12px;
|
| 82 |
+
padding: 2px 8px;
|
| 83 |
+
border-radius: 6px;
|
| 84 |
+
}
|
| 85 |
+
.action-title { font-weight: 600; font-size: 14px; flex-grow: 1; }
|
| 86 |
+
.coords-badge { font-size: 12px; color: var(--text-dim); }
|
| 87 |
+
.img-container {
|
| 88 |
+
position: relative;
|
| 89 |
+
background: #000;
|
| 90 |
+
width: 100%;
|
| 91 |
+
overflow: hidden;
|
| 92 |
+
}
|
| 93 |
+
.img-container img {
|
| 94 |
+
width: 100%;
|
| 95 |
+
height: auto;
|
| 96 |
+
display: block;
|
| 97 |
+
transition: transform 0.2s ease;
|
| 98 |
+
}
|
| 99 |
+
.card-footer {
|
| 100 |
+
padding: 14px 18px;
|
| 101 |
+
font-size: 13px;
|
| 102 |
+
color: var(--text-dim);
|
| 103 |
+
background: #182234;
|
| 104 |
+
border-top: 1px solid var(--border-color);
|
| 105 |
+
}
|
| 106 |
+
.outcome-text { margin: 0; }
|
| 107 |
+
pre.extracted-box {
|
| 108 |
+
background: #090d16;
|
| 109 |
+
padding: 14px;
|
| 110 |
+
border-radius: 8px;
|
| 111 |
+
border: 1px solid var(--border-color);
|
| 112 |
+
color: #38bdf8;
|
| 113 |
+
overflow-x: auto;
|
| 114 |
+
font-size: 12px;
|
| 115 |
+
}
|
| 116 |
+
</style>
|
| 117 |
+
</head>
|
| 118 |
+
<body>
|
| 119 |
+
<div class="container">
|
| 120 |
+
<header>
|
| 121 |
+
<span class="status-pill">VERIFIED SUCCESS</span>
|
| 122 |
+
<h1>🌐 miniswardbower Visual Audit Replay</h1>
|
| 123 |
+
<p class="objective-text"><strong>Objective:</strong> Navigate to http://127.0.0.1:8994/form and fill the authorized representative form with client info and submit</p>
|
| 124 |
+
<div class="metrics-bar">
|
| 125 |
+
<div class="metric-item">Total Time: <span>12.14s</span></div>
|
| 126 |
+
<div class="metric-item">Avg Step Latency: <span>3522.0ms</span></div>
|
| 127 |
+
<div class="metric-item">Tokens Saved: <span>3,514</span></div>
|
| 128 |
+
<div class="metric-item">Steps: <span>5</span></div>
|
| 129 |
+
</div>
|
| 130 |
+
</header>
|
| 131 |
+
|
| 132 |
+
<main class="gallery-grid">
|
| 133 |
+
|
| 134 |
+
<div class="step-card">
|
| 135 |
+
<div class="card-header">
|
| 136 |
+
<span class="step-badge">Step 1</span>
|
| 137 |
+
<span class="action-title">goto( )</span>
|
| 138 |
+
<span class="coords-badge">Targeted Selector</span>
|
| 139 |
+
</div>
|
| 140 |
+
<div class="img-container">
|
| 141 |
+
<img src="step_01_01_goto.png" alt="Step 1" loading="lazy"/>
|
| 142 |
+
</div>
|
| 143 |
+
<div class="card-footer">
|
| 144 |
+
<p class="outcome-text"><strong>Outcome:</strong> Navigated successfully to http://127.0.0.1:8994/form</p>
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
|
| 148 |
+
<div class="step-card">
|
| 149 |
+
<div class="card-header">
|
| 150 |
+
<span class="step-badge">Step 2</span>
|
| 151 |
+
<span class="action-title">type(@e1 Jane Doe)</span>
|
| 152 |
+
<span class="coords-badge">Targeted Selector</span>
|
| 153 |
+
</div>
|
| 154 |
+
<div class="img-container">
|
| 155 |
+
<img src="step_02_01_type.png" alt="Step 2" loading="lazy"/>
|
| 156 |
+
</div>
|
| 157 |
+
<div class="card-footer">
|
| 158 |
+
<p class="outcome-text"><strong>Outcome:</strong> Typed text into @e1</p>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
|
| 162 |
+
<div class="step-card">
|
| 163 |
+
<div class="card-header">
|
| 164 |
+
<span class="step-badge">Step 2</span>
|
| 165 |
+
<span class="action-title">type(@e2 Sample Text)</span>
|
| 166 |
+
<span class="coords-badge">Targeted Selector</span>
|
| 167 |
+
</div>
|
| 168 |
+
<div class="img-container">
|
| 169 |
+
<img src="step_02_02_type.png" alt="Step 2" loading="lazy"/>
|
| 170 |
+
</div>
|
| 171 |
+
<div class="card-footer">
|
| 172 |
+
<p class="outcome-text"><strong>Outcome:</strong> Typed text into @e2</p>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
|
| 176 |
+
<div class="step-card">
|
| 177 |
+
<div class="card-header">
|
| 178 |
+
<span class="step-badge">Step 2</span>
|
| 179 |
+
<span class="action-title">type(@e3 jane.doe@example.com)</span>
|
| 180 |
+
<span class="coords-badge">Targeted Selector</span>
|
| 181 |
+
</div>
|
| 182 |
+
<div class="img-container">
|
| 183 |
+
<img src="step_02_03_type.png" alt="Step 2" loading="lazy"/>
|
| 184 |
+
</div>
|
| 185 |
+
<div class="card-footer">
|
| 186 |
+
<p class="outcome-text"><strong>Outcome:</strong> Typed text into @e3</p>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
|
| 190 |
+
<div class="step-card">
|
| 191 |
+
<div class="card-header">
|
| 192 |
+
<span class="step-badge">Step 2</span>
|
| 193 |
+
<span class="action-title">click(@e4 )</span>
|
| 194 |
+
<span class="coords-badge">Click Target: (411.7, 452.2)</span>
|
| 195 |
+
</div>
|
| 196 |
+
<div class="img-container">
|
| 197 |
+
<img src="step_02_04_click.png" alt="Step 2" loading="lazy"/>
|
| 198 |
+
</div>
|
| 199 |
+
<div class="card-footer">
|
| 200 |
+
<p class="outcome-text"><strong>Outcome:</strong> Executed click on @e4; URL changed: False</p>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
</main>
|
| 205 |
+
</div>
|
| 206 |
+
</body>
|
| 207 |
+
</html>
|
miniswardbower/artifacts/trajectories/20260905_201655/step_01_01_goto.png
ADDED
|
miniswardbower/artifacts/trajectories/20260905_201655/step_02_01_type.png
ADDED
|
miniswardbower/artifacts/trajectories/20260905_201655/step_02_02_type.png
ADDED
|
miniswardbower/artifacts/trajectories/20260905_201655/step_02_03_type.png
ADDED
|
miniswardbower/artifacts/trajectories/20260905_201655/step_02_04_click.png
ADDED
|
miniswardbower/artifacts/v3_capabilities_report.json
CHANGED
|
@@ -2,16 +2,16 @@
|
|
| 2 |
{
|
| 3 |
"name": "Hybrid Input Protocol",
|
| 4 |
"passed": true,
|
| 5 |
-
"static_ms":
|
| 6 |
-
"stream_ms":
|
| 7 |
"stream_reactive_success": true
|
| 8 |
},
|
| 9 |
{
|
| 10 |
"name": "Click Locations & Coordinate Grounding",
|
| 11 |
"passed": true,
|
| 12 |
"mark_click": [
|
| 13 |
-
|
| 14 |
-
|
| 15 |
],
|
| 16 |
"coord_click": [
|
| 17 |
412.9765625,
|
|
@@ -39,6 +39,6 @@
|
|
| 39 |
{
|
| 40 |
"name": "Multi-Step Instruction Following",
|
| 41 |
"passed": true,
|
| 42 |
-
"total_ms":
|
| 43 |
}
|
| 44 |
]
|
|
|
|
| 2 |
{
|
| 3 |
"name": "Hybrid Input Protocol",
|
| 4 |
"passed": true,
|
| 5 |
+
"static_ms": 35.19,
|
| 6 |
+
"stream_ms": 132.88,
|
| 7 |
"stream_reactive_success": true
|
| 8 |
},
|
| 9 |
{
|
| 10 |
"name": "Click Locations & Coordinate Grounding",
|
| 11 |
"passed": true,
|
| 12 |
"mark_click": [
|
| 13 |
+
364.8726939352632,
|
| 14 |
+
386.64688392732313
|
| 15 |
],
|
| 16 |
"coord_click": [
|
| 17 |
412.9765625,
|
|
|
|
| 39 |
{
|
| 40 |
"name": "Multi-Step Instruction Following",
|
| 41 |
"passed": true,
|
| 42 |
+
"total_ms": 233.5
|
| 43 |
}
|
| 44 |
]
|
miniswardbower/artifacts/v3_screenshots/step_01_open_modal.png
CHANGED
|
|
miniswardbower/evals/experiment_next_frontier.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Canopy & MiniswardBower: Frontier Exploration & Experimentation Suite.
|
| 4 |
+
|
| 5 |
+
Explores beyond v3 to answer: "Have we hit a ceiling, or can we get even better?"
|
| 6 |
+
Runs 3 concrete experiments:
|
| 7 |
+
1. Speculative Action Chunking (Pipelined multi-action burst vs step-by-step)
|
| 8 |
+
2. Anti-Bot Telemetry & Stealth Context Profile (Browser fingerprint analysis)
|
| 9 |
+
3. Adaptive Recurrent Inference Early Exit (Profiling Canopy-258M token generation speedup)
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import asyncio
|
| 15 |
+
import json
|
| 16 |
+
import math
|
| 17 |
+
import os
|
| 18 |
+
import sys
|
| 19 |
+
import time
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
from typing import Any, Dict, List
|
| 22 |
+
|
| 23 |
+
CANOPY_ROOT = Path("/home/psikosen/canopy")
|
| 24 |
+
MINISWARD_ROOT = CANOPY_ROOT / "miniswardbower"
|
| 25 |
+
sys.path.insert(0, str(CANOPY_ROOT))
|
| 26 |
+
sys.path.insert(0, str(MINISWARD_ROOT))
|
| 27 |
+
|
| 28 |
+
import torch
|
| 29 |
+
from miniswardbower.browser.controller import BrowserController
|
| 30 |
+
from miniswardbower.core.config import BrowserConfig
|
| 31 |
+
from miniswardbower.core.schemas import ActionBatch, BrowserAction, BrowserActionType
|
| 32 |
+
from miniswardbower.evals.mock_web_env import MockWebEnvironment
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
async def experiment_speculative_action_chunking(mock_env: MockWebEnvironment, controller: BrowserController) -> Dict[str, Any]:
|
| 36 |
+
"""Experiment 1: Measures latency of Speculative Action Chunking vs step-by-step execution."""
|
| 37 |
+
print("\n" + "=" * 75)
|
| 38 |
+
print("🔬 EXPERIMENT 1: Speculative Action Chunking (AsyncWebRL 2026)")
|
| 39 |
+
print("=" * 75)
|
| 40 |
+
|
| 41 |
+
# Multi-Step Form Benchmark: Contact Info & Submission
|
| 42 |
+
await controller.goto(mock_env.form_url)
|
| 43 |
+
tree = await controller.get_pruned_tree()
|
| 44 |
+
|
| 45 |
+
name_elem = next(e for e in tree.elements if "client_name" in e.selector)
|
| 46 |
+
id_elem = next(e for e in tree.elements if "client_id" in e.selector)
|
| 47 |
+
email_elem = next(e for e in tree.elements if "email" in e.selector)
|
| 48 |
+
submit_btn = next(e for e in tree.elements if "submit" in e.selector or e.tag == "button")
|
| 49 |
+
|
| 50 |
+
# Mode A: Step-by-Step with intermediate tree extraction (v3 Baseline)
|
| 51 |
+
t0 = time.perf_counter()
|
| 52 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.TYPE, target=name_elem.id, text="Bruce Wayne", stream_input=False), tree)
|
| 53 |
+
t_tree1 = await controller.get_pruned_tree()
|
| 54 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.TYPE, target=id_elem.id, text="881920", stream_input=False), t_tree1)
|
| 55 |
+
t_tree2 = await controller.get_pruned_tree()
|
| 56 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.TYPE, target=email_elem.id, text="bruce@wayne-enterprises.com", stream_input=False), t_tree2)
|
| 57 |
+
t_tree3 = await controller.get_pruned_tree()
|
| 58 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.CLICK, target=submit_btn.id), t_tree3)
|
| 59 |
+
step_by_step_ms = (time.perf_counter() - t0) * 1000
|
| 60 |
+
|
| 61 |
+
# Reset page for Mode B
|
| 62 |
+
await controller.goto(mock_env.form_url)
|
| 63 |
+
tree_b = await controller.get_pruned_tree()
|
| 64 |
+
|
| 65 |
+
# Mode B: Pipelined Speculative Action Chunk (Executed in a single atomic pipelined burst)
|
| 66 |
+
chunk = [
|
| 67 |
+
BrowserAction(op=BrowserActionType.TYPE, target="input#client_name", text="Bruce Wayne", stream_input=False),
|
| 68 |
+
BrowserAction(op=BrowserActionType.TYPE, target="input#client_id", text="881920", stream_input=False),
|
| 69 |
+
BrowserAction(op=BrowserActionType.TYPE, target="input#email", text="bruce@wayne-enterprises.com", stream_input=False),
|
| 70 |
+
BrowserAction(op=BrowserActionType.CLICK, target="button#submit-btn"),
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
t1 = time.perf_counter()
|
| 74 |
+
# Pipelined execution without artificial inter-step sleep or redundant DOM re-scans
|
| 75 |
+
for act in chunk:
|
| 76 |
+
await controller.execute_action(act)
|
| 77 |
+
chunked_ms = (time.perf_counter() - t1) * 1000
|
| 78 |
+
|
| 79 |
+
page_text = await controller.page.inner_text("body")
|
| 80 |
+
success = "Confirmation Received!" in page_text and "Bruce Wayne" in page_text
|
| 81 |
+
|
| 82 |
+
speedup = step_by_step_ms / (chunked_ms + 1e-6)
|
| 83 |
+
print(f" Step-by-Step Roundtrip Latency : {step_by_step_ms:6.1f} ms")
|
| 84 |
+
print(f" Speculative Action Chunk Latency: {chunked_ms:6.1f} ms")
|
| 85 |
+
print(f" Chunking Speedup Factor : {speedup:6.2f}x ({round((1 - chunked_ms / step_by_step_ms) * 100, 1)}% faster)")
|
| 86 |
+
print(f" Task Success Verified : {success}")
|
| 87 |
+
|
| 88 |
+
return {
|
| 89 |
+
"experiment": "Speculative Action Chunking",
|
| 90 |
+
"step_by_step_ms": round(step_by_step_ms, 2),
|
| 91 |
+
"chunked_ms": round(chunked_ms, 2),
|
| 92 |
+
"speedup": round(speedup, 2),
|
| 93 |
+
"success": success,
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
async def experiment_stealth_fingerprinting(controller: BrowserController) -> Dict[str, Any]:
|
| 98 |
+
"""Experiment 2: Tests browser fingerprint against common bot detection criteria."""
|
| 99 |
+
print("\n" + "=" * 75)
|
| 100 |
+
print("🔬 EXPERIMENT 2: Anti-Bot Telemetry & Stealth Fingerprinting Profile")
|
| 101 |
+
print("=" * 75)
|
| 102 |
+
|
| 103 |
+
# Inject CDP stealth scripts before evaluation
|
| 104 |
+
await controller.page.add_init_script("""
|
| 105 |
+
// 1. Remove navigator.webdriver flag
|
| 106 |
+
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
| 107 |
+
|
| 108 |
+
// 2. Mock realistic plugins & mime types
|
| 109 |
+
Object.defineProperty(navigator, 'plugins', {
|
| 110 |
+
get: () => [
|
| 111 |
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer' },
|
| 112 |
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' },
|
| 113 |
+
{ name: 'Native Client', filename: 'internal-nacl-plugin' }
|
| 114 |
+
]
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
// 3. Mock languages
|
| 118 |
+
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
| 119 |
+
""")
|
| 120 |
+
|
| 121 |
+
await controller.goto("about:blank")
|
| 122 |
+
fp = await controller.page.evaluate("""() => {
|
| 123 |
+
return {
|
| 124 |
+
webdriver: navigator.webdriver,
|
| 125 |
+
plugins_count: navigator.plugins.length,
|
| 126 |
+
languages: navigator.languages,
|
| 127 |
+
has_chrome: !!window.chrome,
|
| 128 |
+
device_memory: navigator.deviceMemory || 8,
|
| 129 |
+
hardware_concurrency: navigator.hardwareConcurrency || 16,
|
| 130 |
+
};
|
| 131 |
+
}""")
|
| 132 |
+
|
| 133 |
+
print(f" navigator.webdriver (should be None) : {fp.get('webdriver')}")
|
| 134 |
+
print(f" navigator.plugins count (should be >0): {fp.get('plugins_count')}")
|
| 135 |
+
print(f" navigator.languages : {fp.get('languages')}")
|
| 136 |
+
print(f" window.chrome available : {fp.get('has_chrome')}")
|
| 137 |
+
|
| 138 |
+
is_stealthy = (fp.get("webdriver") is None) and (fp.get("plugins_count") > 0)
|
| 139 |
+
print(f" Stealth Profile Score : {'✅ RESISTANT (Stealthy)' if is_stealthy else '⚠️ BOT DETECTED'}")
|
| 140 |
+
|
| 141 |
+
return {
|
| 142 |
+
"experiment": "Stealth Fingerprint Profile",
|
| 143 |
+
"fingerprint": fp,
|
| 144 |
+
"is_stealthy": is_stealthy,
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def experiment_adaptive_recurrent_inference() -> Dict[str, Any]:
|
| 149 |
+
"""Experiment 3: Tests Adaptive Recurrence Early-Exit for Canopy-258M token throughput."""
|
| 150 |
+
print("\n" + "=" * 75)
|
| 151 |
+
print("🔬 EXPERIMENT 3: Canopy-258M Adaptive Recurrent Early-Exit")
|
| 152 |
+
print("=" * 75)
|
| 153 |
+
|
| 154 |
+
from canopy_r3.config import CanopyConfig
|
| 155 |
+
from canopy_r3.model import CanopyForCausalLM
|
| 156 |
+
from transformers import AutoTokenizer
|
| 157 |
+
|
| 158 |
+
model_path = CANOPY_ROOT / "checkpoints" / "canopy_258m_stage13_unified.pt"
|
| 159 |
+
print(" Loading Canopy-258M-R3 weights onto GPU...")
|
| 160 |
+
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/cosmo2-tokenizer")
|
| 161 |
+
cfg = CanopyConfig()
|
| 162 |
+
model = CanopyForCausalLM(cfg).to(device="cuda", dtype=torch.bfloat16)
|
| 163 |
+
ckpt = torch.load(model_path, map_location="cuda")
|
| 164 |
+
state_dict = ckpt.get("model", ckpt)
|
| 165 |
+
model.load_state_dict(state_dict, strict=False)
|
| 166 |
+
model.eval()
|
| 167 |
+
|
| 168 |
+
prompt = "<|im_start|>user\nWrite a quick Python function to check if a string is a palindrome.<|im_end|>\n<|im_start|>assistant\n"
|
| 169 |
+
inputs = tokenizer(prompt, return_tensors="pt")["input_ids"].to("cuda")
|
| 170 |
+
|
| 171 |
+
# Baseline: Default 2 visits on every token
|
| 172 |
+
t0 = time.perf_counter()
|
| 173 |
+
with torch.no_grad():
|
| 174 |
+
out_baseline = model.generate(inputs, max_new_tokens=64, temperature=0.2)
|
| 175 |
+
torch.cuda.synchronize()
|
| 176 |
+
time_baseline = time.perf_counter() - t0
|
| 177 |
+
tok_baseline = 64 / time_baseline
|
| 178 |
+
|
| 179 |
+
print(f" Baseline Throughput (Fixed 2 Visits): {tok_baseline:6.1f} tokens/sec ({time_baseline*1000/64:5.2f} ms/tok)")
|
| 180 |
+
|
| 181 |
+
# Simulated Adaptive Early-Exit (Visit 1 only on simple tokens with low entropy)
|
| 182 |
+
# On 258M MoE, bypassing 1 visit across 6 layers saves ~33% of FLOPs on those tokens
|
| 183 |
+
# In practice: ~60% of code tokens are low-entropy syntax (def, return, :, spaces, indent)
|
| 184 |
+
estimated_speedup = 1.0 / (0.40 * 1.0 + 0.60 * 0.67) # ~1.25x speedup
|
| 185 |
+
print(f" Projected Adaptive Early-Exit Speedup : {estimated_speedup:6.2f}x ({tok_baseline * estimated_speedup:6.1f} tokens/sec)")
|
| 186 |
+
print(f" Sample Generation Output Preview:\n {tokenizer.decode(out_baseline[0], skip_special_tokens=True)[len(prompt):][:120].strip()}...")
|
| 187 |
+
|
| 188 |
+
return {
|
| 189 |
+
"experiment": "Adaptive Recurrent Inference",
|
| 190 |
+
"baseline_tok_per_sec": round(tok_baseline, 1),
|
| 191 |
+
"adaptive_tok_per_sec_projected": round(tok_baseline * estimated_speedup, 1),
|
| 192 |
+
"potential_speedup": round(estimated_speedup, 2),
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
async def main():
|
| 197 |
+
print("=" * 80)
|
| 198 |
+
print("🌌 FRONTIER EXPERIMENTATION: HAVE WE HIT A CEILING OR CAN WE GO FASTER?")
|
| 199 |
+
print("=" * 80)
|
| 200 |
+
|
| 201 |
+
mock_env = MockWebEnvironment(port=0)
|
| 202 |
+
mock_env.start()
|
| 203 |
+
|
| 204 |
+
cfg = BrowserConfig(headless=True)
|
| 205 |
+
controller = BrowserController(cfg)
|
| 206 |
+
await controller.start()
|
| 207 |
+
|
| 208 |
+
exp1 = await experiment_speculative_action_chunking(mock_env, controller)
|
| 209 |
+
exp2 = await experiment_stealth_fingerprinting(controller)
|
| 210 |
+
await controller.stop()
|
| 211 |
+
mock_env.stop()
|
| 212 |
+
|
| 213 |
+
exp3 = experiment_adaptive_recurrent_inference()
|
| 214 |
+
|
| 215 |
+
print("\n" + "=" * 80)
|
| 216 |
+
print("📊 FRONTIER EXPERIMENTATION SUMMARY")
|
| 217 |
+
print("=" * 80)
|
| 218 |
+
print(f" 1. Speculative Action Chunking : {exp1['speedup']}x faster ({exp1['chunked_ms']} ms vs {exp1['step_by_step_ms']} ms)")
|
| 219 |
+
print(f" 2. Anti-Bot Stealth Profile : {'PASS (Masked)' if exp2['is_stealthy'] else 'FAIL'}")
|
| 220 |
+
print(f" 3. Adaptive Recurrent Scaling : {exp3['potential_speedup']}x throughput gain ({exp3['adaptive_tok_per_sec_projected']} tok/s)")
|
| 221 |
+
print("=" * 80)
|
| 222 |
+
|
| 223 |
+
report_path = CANOPY_ROOT / "miniswardbower" / "artifacts" / "frontier_experiments_report.json"
|
| 224 |
+
report_path.parent.mkdir(parents=True, exist_ok=True)
|
| 225 |
+
report_path.write_text(json.dumps([exp1, exp2, exp3], indent=2))
|
| 226 |
+
print(f"[✓] Saved Frontier Experiments Report to: {report_path}")
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
if __name__ == "__main__":
|
| 230 |
+
asyncio.run(main())
|
miniswardbower/evals/test_chained_complex_actions.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Complex Chained Browser Actions & Multi-Stage Orchestration Test Battery.
|
| 4 |
+
|
| 5 |
+
Evaluates high-complexity chained workflows:
|
| 6 |
+
Chain 1: E-Commerce Multi-Stage Cart, Variant Configuration, Voucher Application & Checkout
|
| 7 |
+
Chain 2: Data Pipeline ETL Multi-Row Filter, Bulk Selection, Modal Confirmation & Async Dispatch
|
| 8 |
+
Chain 3: Cross-Modal Spatial Grounding & Dynamic Extraction Chain
|
| 9 |
+
|
| 10 |
+
Measures:
|
| 11 |
+
- End-to-end chain completion latency
|
| 12 |
+
- State verification receipt accuracy at each stage
|
| 13 |
+
- Speculative chunking execution inside complex chains
|
| 14 |
+
- Failure diagnostics & self-healing remediation
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import asyncio
|
| 20 |
+
import json
|
| 21 |
+
import os
|
| 22 |
+
import sys
|
| 23 |
+
import time
|
| 24 |
+
from pathlib import Path
|
| 25 |
+
from typing import Any, Dict, List
|
| 26 |
+
|
| 27 |
+
# Ensure paths
|
| 28 |
+
CANOPY_ROOT = Path("/home/psikosen/canopy")
|
| 29 |
+
MINISWARD_ROOT = CANOPY_ROOT / "miniswardbower"
|
| 30 |
+
sys.path.insert(0, str(CANOPY_ROOT))
|
| 31 |
+
sys.path.insert(0, str(MINISWARD_ROOT))
|
| 32 |
+
|
| 33 |
+
from miniswardbower.agents.verifier import ProcessVerifier
|
| 34 |
+
from miniswardbower.browser.controller import BrowserController
|
| 35 |
+
from miniswardbower.core.config import BrowserConfig
|
| 36 |
+
from miniswardbower.core.schemas import BrowserAction, BrowserActionType
|
| 37 |
+
from miniswardbower.evals.mock_web_env import MockWebEnvironment
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
async def test_chain_1_ecommerce_provisioning(
|
| 41 |
+
mock_env: MockWebEnvironment, controller: BrowserController, verifier: ProcessVerifier
|
| 42 |
+
) -> Dict[str, Any]:
|
| 43 |
+
"""
|
| 44 |
+
Chain 1: E-Commerce Multi-Stage Cart, Variant Configuration, Voucher Application & Checkout.
|
| 45 |
+
12 sequential actions testing filters, variants, cart drawer, live voucher discount,
|
| 46 |
+
and checkout form entry with speculative chunking.
|
| 47 |
+
"""
|
| 48 |
+
print("\n" + "=" * 80)
|
| 49 |
+
print("🛒 CHAIN 1: Enterprise Multi-Stage Hardware Provisioning & Checkout")
|
| 50 |
+
print("=" * 80)
|
| 51 |
+
|
| 52 |
+
t_start = time.perf_counter()
|
| 53 |
+
await controller.goto(mock_env.ecommerce_url)
|
| 54 |
+
tree1 = await controller.get_pruned_tree()
|
| 55 |
+
|
| 56 |
+
stage_timings = []
|
| 57 |
+
|
| 58 |
+
# Stage 1: Category Filter & Variant Selection
|
| 59 |
+
t0 = time.perf_counter()
|
| 60 |
+
cat_sel = next(e for e in tree1.elements if "category-filter" in e.selector)
|
| 61 |
+
var_sel = next(e for e in tree1.elements if "variant-select" in e.selector)
|
| 62 |
+
add_btn = next(e for e in tree1.elements if "add-to-cart-btn" in e.selector)
|
| 63 |
+
|
| 64 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.SELECT, target=cat_sel.id, value="b200"), tree1)
|
| 65 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.SELECT, target=var_sel.id, value="extreme"), tree1)
|
| 66 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.CLICK, target=add_btn.id), tree1)
|
| 67 |
+
stage_timings.append(("Stage 1: Configure & Add to Cart", (time.perf_counter() - t0) * 1000))
|
| 68 |
+
|
| 69 |
+
# Stage 2: Cart Drawer & Voucher Application
|
| 70 |
+
t1 = time.perf_counter()
|
| 71 |
+
tree2 = await controller.get_pruned_tree()
|
| 72 |
+
vouch_inp = next(e for e in tree2.elements if "voucher-code" in e.selector)
|
| 73 |
+
vouch_btn = next(e for e in tree2.elements if "apply-voucher-btn" in e.selector)
|
| 74 |
+
chkout_btn = next(e for e in tree2.elements if "proceed-checkout-btn" in e.selector)
|
| 75 |
+
|
| 76 |
+
# Speculative chunk: apply discount and proceed
|
| 77 |
+
chunk_voucher = [
|
| 78 |
+
BrowserAction(op=BrowserActionType.TYPE, target=vouch_inp.id, text="VIP20", stream_input=False),
|
| 79 |
+
BrowserAction(op=BrowserActionType.CLICK, target=vouch_btn.id),
|
| 80 |
+
]
|
| 81 |
+
await controller.execute_chunk(chunk_voucher, tree2)
|
| 82 |
+
await asyncio.sleep(0.05) # allow live discount calculation
|
| 83 |
+
|
| 84 |
+
# Verify discount applied
|
| 85 |
+
cart_total_text = await controller.page.inner_text("#cart-total")
|
| 86 |
+
discount_applied = "2399.20" in cart_total_text
|
| 87 |
+
print(f" ✓ Live Voucher Discount Verified: {cart_total_text} (20% applied: {discount_applied})")
|
| 88 |
+
|
| 89 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.CLICK, target=chkout_btn.id), tree2)
|
| 90 |
+
stage_timings.append(("Stage 2: Voucher & Checkout Open", (time.perf_counter() - t1) * 1000))
|
| 91 |
+
|
| 92 |
+
# Stage 3: Checkout Form Entry (Speculative Chunking)
|
| 93 |
+
t2 = time.perf_counter()
|
| 94 |
+
tree3 = await controller.get_pruned_tree()
|
| 95 |
+
cname_inp = next(e for e in tree3.elements if "cluster-name" in e.selector)
|
| 96 |
+
email_inp = next(e for e in tree3.elements if "contact-email" in e.selector)
|
| 97 |
+
terms_chk = next(e for e in tree3.elements if "terms-agree" in e.selector)
|
| 98 |
+
place_btn = next(e for e in tree3.elements if "confirm-order-btn" in e.selector)
|
| 99 |
+
|
| 100 |
+
# Pipelined chunk for entire checkout submission
|
| 101 |
+
chunk_checkout = [
|
| 102 |
+
BrowserAction(op=BrowserActionType.TYPE, target=cname_inp.id, text="canopy-quantum-cluster-01", stream_input=False),
|
| 103 |
+
BrowserAction(op=BrowserActionType.TYPE, target=email_inp.id, text="devops-lead@canopy.ai", stream_input=False),
|
| 104 |
+
BrowserAction(op=BrowserActionType.CLICK, target=terms_chk.id),
|
| 105 |
+
BrowserAction(op=BrowserActionType.CLICK, target=place_btn.id),
|
| 106 |
+
]
|
| 107 |
+
await controller.execute_chunk(chunk_checkout, tree3)
|
| 108 |
+
stage_timings.append(("Stage 3: Pipelined Form Submission", (time.perf_counter() - t2) * 1000))
|
| 109 |
+
|
| 110 |
+
# Stage 4: Verify Order Receipt & Capture Visual Proof
|
| 111 |
+
await asyncio.sleep(0.05)
|
| 112 |
+
page_text = await controller.page.inner_text("body")
|
| 113 |
+
receipt_text = await controller.page.inner_text("#receipt-id")
|
| 114 |
+
chain_success = "Provisioning Finalized!" in page_text and "CANOPY-ORD-" in receipt_text
|
| 115 |
+
|
| 116 |
+
screenshot_path = CANOPY_ROOT / "miniswardbower" / "artifacts" / "complex_chains" / "chain1_ecommerce_receipt.png"
|
| 117 |
+
screenshot_path.parent.mkdir(parents=True, exist_ok=True)
|
| 118 |
+
await controller.capture_annotated_step_screenshot(
|
| 119 |
+
output_path=screenshot_path,
|
| 120 |
+
step_num=12,
|
| 121 |
+
action_label=f"order_confirmed: {receipt_text}",
|
| 122 |
+
click_coords=(425.0, 480.0),
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
total_chain_ms = (time.perf_counter() - t_start) * 1000
|
| 126 |
+
|
| 127 |
+
print(f" ✓ Order Reference Generated : {receipt_text}")
|
| 128 |
+
print(f" ✓ Total Chain Execution Latency: {total_chain_ms:6.1f} ms (12 actions across 3 stages)")
|
| 129 |
+
for name, ms in stage_timings:
|
| 130 |
+
print(f" - {name:<35}: {ms:6.1f} ms")
|
| 131 |
+
|
| 132 |
+
return {
|
| 133 |
+
"name": "Chain 1: E-Commerce Multi-Stage Cart & Checkout",
|
| 134 |
+
"passed": chain_success and discount_applied,
|
| 135 |
+
"total_ms": round(total_chain_ms, 2),
|
| 136 |
+
"stages": stage_timings,
|
| 137 |
+
"order_id": receipt_text,
|
| 138 |
+
"screenshot": str(screenshot_path),
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
async def test_chain_2_etl_batch_operations(
|
| 143 |
+
mock_env: MockWebEnvironment, controller: BrowserController, verifier: ProcessVerifier
|
| 144 |
+
) -> Dict[str, Any]:
|
| 145 |
+
"""
|
| 146 |
+
Chain 2: Data Pipeline ETL Multi-Row Filter, Bulk Selection, Modal Confirmation & Async Dispatch.
|
| 147 |
+
Tests table filtering, mass checkbox selection, modal priority setting, and asynchronous receipt verification.
|
| 148 |
+
"""
|
| 149 |
+
print("\n" + "=" * 80)
|
| 150 |
+
print("📊 CHAIN 2: Data Pipeline ETL Filter, Bulk Selection & Modal Dispatch")
|
| 151 |
+
print("=" * 80)
|
| 152 |
+
|
| 153 |
+
t_start = time.perf_counter()
|
| 154 |
+
await controller.goto(mock_env.etl_url)
|
| 155 |
+
tree1 = await controller.get_pruned_tree()
|
| 156 |
+
|
| 157 |
+
# Stage 1: Filter to Failed Jobs & Select All Filtered
|
| 158 |
+
filter_sel = next(e for e in tree1.elements if "status-filter" in e.selector)
|
| 159 |
+
select_all_btn = next(e for e in tree1.elements if "select-all-btn" in e.selector)
|
| 160 |
+
batch_act_sel = next(e for e in tree1.elements if "batch-action-select" in e.selector)
|
| 161 |
+
dispatch_btn = next(e for e in tree1.elements if "dispatch-batch-btn" in e.selector)
|
| 162 |
+
|
| 163 |
+
# Execute Filter & Selection
|
| 164 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.SELECT, target=filter_sel.id, value="failed"), tree1)
|
| 165 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.CLICK, target=select_all_btn.id), tree1)
|
| 166 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.SELECT, target=batch_act_sel.id, value="rerun"), tree1)
|
| 167 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.CLICK, target=dispatch_btn.id), tree1)
|
| 168 |
+
|
| 169 |
+
# Stage 2: Modal Focus Trapping & Confirmation Dispatch
|
| 170 |
+
tree2 = await controller.get_pruned_tree()
|
| 171 |
+
priority_sel = next(e for e in tree2.elements if "batch-priority" in e.selector)
|
| 172 |
+
confirm_btn = next(e for e in tree2.elements if "confirm-batch-btn" in e.selector)
|
| 173 |
+
|
| 174 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.SELECT, target=priority_sel.id, value="high"), tree2)
|
| 175 |
+
await controller.execute_action(BrowserAction(op=BrowserActionType.CLICK, target=confirm_btn.id), tree2)
|
| 176 |
+
|
| 177 |
+
# Stage 3: Await & Scrape Final Progress Status
|
| 178 |
+
await asyncio.sleep(0.1)
|
| 179 |
+
page_text = await controller.page.inner_text("body")
|
| 180 |
+
chain_success = "100% Complete - Batch Dispatched" in page_text and "ETL-BATCH-8891" in page_text
|
| 181 |
+
|
| 182 |
+
screenshot_path = CANOPY_ROOT / "miniswardbower" / "artifacts" / "complex_chains" / "chain2_etl_dispatched.png"
|
| 183 |
+
await controller.capture_annotated_step_screenshot(
|
| 184 |
+
output_path=screenshot_path,
|
| 185 |
+
step_num=7,
|
| 186 |
+
action_label="batch_dispatched: ETL-BATCH-8891",
|
| 187 |
+
click_coords=(520.0, 390.0),
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
total_chain_ms = (time.perf_counter() - t_start) * 1000
|
| 191 |
+
print(f" ✓ Batch Pipeline Dispatched Successfully in {total_chain_ms:6.1f} ms")
|
| 192 |
+
|
| 193 |
+
return {
|
| 194 |
+
"name": "Chain 2: ETL Bulk Filter & Modal Dispatch",
|
| 195 |
+
"passed": chain_success,
|
| 196 |
+
"total_ms": round(total_chain_ms, 2),
|
| 197 |
+
"screenshot": str(screenshot_path),
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
async def test_chain_3_spatial_coordinate_extraction(
|
| 202 |
+
mock_env: MockWebEnvironment, controller: BrowserController, verifier: ProcessVerifier
|
| 203 |
+
) -> Dict[str, Any]:
|
| 204 |
+
"""
|
| 205 |
+
Chain 3: Spatial Coordinate Grounding, Bézier Navigation & Dynamic Extraction.
|
| 206 |
+
Combines visual coordinates targeting, table matrix scraping, and receipt verification.
|
| 207 |
+
"""
|
| 208 |
+
print("\n" + "=" * 80)
|
| 209 |
+
print("🎯 CHAIN 3: Spatial Grounding & Dynamic Table Extraction")
|
| 210 |
+
print("=" * 80)
|
| 211 |
+
|
| 212 |
+
t_start = time.perf_counter()
|
| 213 |
+
await controller.goto(mock_env.table_url)
|
| 214 |
+
tree = await controller.get_pruned_tree()
|
| 215 |
+
|
| 216 |
+
# 1. Scrape original table state
|
| 217 |
+
table_matrix = await controller.scrape_table("#ledger-table")
|
| 218 |
+
|
| 219 |
+
# 2. Find row button and extract exact center coordinates
|
| 220 |
+
row_btn = next(e for e in tree.elements if "approve-inv-101" in e.selector)
|
| 221 |
+
bx, by, bw, bh = row_btn.bbox
|
| 222 |
+
cx = bx + bw / 2.0
|
| 223 |
+
cy = by + bh / 2.0
|
| 224 |
+
|
| 225 |
+
# 3. Coordinate Click with Cubic Bézier Kinematics
|
| 226 |
+
act_coord = BrowserAction(op=BrowserActionType.CLICK, coords=(cx, cy))
|
| 227 |
+
res_coord = await controller.execute_action(act_coord, tree)
|
| 228 |
+
|
| 229 |
+
# 4. Verify page text state update
|
| 230 |
+
page_text = await controller.page.inner_text("body")
|
| 231 |
+
chain_success = "Invoice #101 Approved!" in page_text or "Approved Invoice 101" in await controller.page.title()
|
| 232 |
+
|
| 233 |
+
screenshot_path = CANOPY_ROOT / "miniswardbower" / "artifacts" / "complex_chains" / "chain3_spatial_approved.png"
|
| 234 |
+
await controller.capture_annotated_step_screenshot(
|
| 235 |
+
output_path=screenshot_path,
|
| 236 |
+
step_num=3,
|
| 237 |
+
action_label="approved_row: INV-101",
|
| 238 |
+
bbox=row_btn.bbox,
|
| 239 |
+
click_coords=(cx, cy),
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
total_chain_ms = (time.perf_counter() - t_start) * 1000
|
| 243 |
+
print(f" ✓ Spatial Click Target Grounded at ({cx:.1f}, {cy:.1f}) in {total_chain_ms:6.1f} ms")
|
| 244 |
+
|
| 245 |
+
return {
|
| 246 |
+
"name": "Chain 3: Spatial Grounding & Table Extraction",
|
| 247 |
+
"passed": chain_success,
|
| 248 |
+
"total_ms": round(total_chain_ms, 2),
|
| 249 |
+
"screenshot": str(screenshot_path),
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
async def main():
|
| 254 |
+
print("=" * 80)
|
| 255 |
+
print("🌟 COMPLEX CHAINED ACTIONS & MULTI-STAGE ORCHESTRATION BATTERY")
|
| 256 |
+
print("=" * 80)
|
| 257 |
+
|
| 258 |
+
mock_env = MockWebEnvironment(port=0)
|
| 259 |
+
mock_env.start()
|
| 260 |
+
print(f" [Mock Server Active at http://127.0.0.1:{mock_env.port}]")
|
| 261 |
+
|
| 262 |
+
cfg = BrowserConfig(headless=True)
|
| 263 |
+
controller = BrowserController(cfg)
|
| 264 |
+
verifier = ProcessVerifier()
|
| 265 |
+
await controller.start()
|
| 266 |
+
|
| 267 |
+
chain_tests = [
|
| 268 |
+
test_chain_1_ecommerce_provisioning,
|
| 269 |
+
test_chain_2_etl_batch_operations,
|
| 270 |
+
test_chain_3_spatial_coordinate_extraction,
|
| 271 |
+
]
|
| 272 |
+
|
| 273 |
+
reports = []
|
| 274 |
+
try:
|
| 275 |
+
for t in chain_tests:
|
| 276 |
+
res = await t(mock_env, controller, verifier)
|
| 277 |
+
reports.append(res)
|
| 278 |
+
finally:
|
| 279 |
+
await controller.stop()
|
| 280 |
+
mock_env.stop()
|
| 281 |
+
|
| 282 |
+
print("\n" + "=" * 80)
|
| 283 |
+
print("📊 COMPLEX CHAINED ACTIONS SCORECARD")
|
| 284 |
+
print("=" * 80)
|
| 285 |
+
all_passed = True
|
| 286 |
+
total_time = sum(r["total_ms"] for r in reports)
|
| 287 |
+
for r in reports:
|
| 288 |
+
status = "✓ PASS" if r["passed"] else "✗ FAIL"
|
| 289 |
+
if not r["passed"]:
|
| 290 |
+
all_passed = False
|
| 291 |
+
print(f" {r['name']:<48} : {status} ({r['total_ms']:6.1f} ms)")
|
| 292 |
+
|
| 293 |
+
print("-" * 80)
|
| 294 |
+
print(f" Cumulative Execution Time : {total_time:6.1f} ms ({total_time/1000:.2f}s)")
|
| 295 |
+
print(f" Overall Chained Battery : {'✅ ALL CHAINS PASSED' if all_passed else '❌ FAILURES DETECTED'}")
|
| 296 |
+
print("=" * 80)
|
| 297 |
+
|
| 298 |
+
report_path = CANOPY_ROOT / "miniswardbower" / "artifacts" / "complex_chains_report.json"
|
| 299 |
+
report_path.parent.mkdir(parents=True, exist_ok=True)
|
| 300 |
+
report_path.write_text(json.dumps(reports, indent=2))
|
| 301 |
+
print(f"\n[✓] Saved Complex Chains Report to: {report_path}")
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
if __name__ == "__main__":
|
| 305 |
+
asyncio.run(main())
|
miniswardbower/evals/test_live_web.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Live Web Environment Integration & Validation Test.
|
| 4 |
+
Performs real-world testing on public live websites:
|
| 5 |
+
1. Validates authentic browser headers and stealth fingerprint over real network
|
| 6 |
+
2. Live navigation to public web service (Hacker News)
|
| 7 |
+
3. Structured scraping of real live news titles, ranks, and metadata
|
| 8 |
+
4. Live interaction: targeted search query fill & execution
|
| 9 |
+
5. Visual screenshot auditing of live page state
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import asyncio
|
| 15 |
+
import json
|
| 16 |
+
import os
|
| 17 |
+
import sys
|
| 18 |
+
import time
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from typing import Any, Dict
|
| 21 |
+
|
| 22 |
+
CANOPY_ROOT = Path("/home/psikosen/canopy")
|
| 23 |
+
MINISWARD_ROOT = CANOPY_ROOT / "miniswardbower"
|
| 24 |
+
sys.path.insert(0, str(CANOPY_ROOT))
|
| 25 |
+
sys.path.insert(0, str(MINISWARD_ROOT))
|
| 26 |
+
|
| 27 |
+
from miniswardbower.browser.controller import BrowserController
|
| 28 |
+
from miniswardbower.core.config import BrowserConfig
|
| 29 |
+
from miniswardbower.core.schemas import BrowserAction, BrowserActionType
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
async def test_live_network_and_scraping():
|
| 33 |
+
print("=" * 80)
|
| 34 |
+
print("🌐 LIVE REAL-WORLD WEB NAVIGATION, STEALTH & SCRAPING TEST")
|
| 35 |
+
print("=" * 80)
|
| 36 |
+
|
| 37 |
+
cfg = BrowserConfig(headless=True)
|
| 38 |
+
controller = BrowserController(cfg)
|
| 39 |
+
await controller.start()
|
| 40 |
+
|
| 41 |
+
artifact_dir = CANOPY_ROOT / "miniswardbower" / "artifacts" / "live_web_screenshots"
|
| 42 |
+
artifact_dir.mkdir(parents=True, exist_ok=True)
|
| 43 |
+
|
| 44 |
+
results: Dict[str, Any] = {}
|
| 45 |
+
|
| 46 |
+
try:
|
| 47 |
+
# 1. Live Navigation & Stealth Header Validation
|
| 48 |
+
print("\n[LIVE 1] Checking network headers & fingerprint on httpbin.org...")
|
| 49 |
+
t0 = time.perf_counter()
|
| 50 |
+
await controller.goto("https://httpbin.org/headers")
|
| 51 |
+
try:
|
| 52 |
+
# Read pre-formatted JSON directly from page before distillation overlays
|
| 53 |
+
pre_text = await controller.page.inner_text("pre")
|
| 54 |
+
raw_h = json.loads(pre_text).get("headers", {})
|
| 55 |
+
parsed_headers = {k.lower(): v for k, v in raw_h.items()}
|
| 56 |
+
except Exception:
|
| 57 |
+
try:
|
| 58 |
+
body_text = await controller.page.inner_text("body")
|
| 59 |
+
raw_h = json.loads(body_text).get("headers", {})
|
| 60 |
+
parsed_headers = {k.lower(): v for k, v in raw_h.items()}
|
| 61 |
+
except Exception:
|
| 62 |
+
parsed_headers = {}
|
| 63 |
+
|
| 64 |
+
header_time = (time.perf_counter() - t0) * 1000
|
| 65 |
+
has_ua = "user-agent" in parsed_headers
|
| 66 |
+
has_lang = "accept-language" in parsed_headers
|
| 67 |
+
ua_val = parsed_headers.get("user-agent", "")
|
| 68 |
+
lang_val = parsed_headers.get("accept-language", "")
|
| 69 |
+
|
| 70 |
+
print(f" ✓ Live Network RTT: {header_time:6.1f} ms")
|
| 71 |
+
print(f" ✓ User-Agent: {ua_val[:65]}...")
|
| 72 |
+
print(f" ✓ Accept-Language: {lang_val}")
|
| 73 |
+
|
| 74 |
+
results["network_headers"] = {
|
| 75 |
+
"passed": has_ua and has_lang,
|
| 76 |
+
"rtt_ms": round(header_time, 2),
|
| 77 |
+
"user_agent": ua_val,
|
| 78 |
+
"accept_language": lang_val,
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
# 2. Live Public Content Navigation (Hacker News)
|
| 82 |
+
print("\n[LIVE 2] Navigating to https://news.ycombinator.com & Scraping Live Items...")
|
| 83 |
+
t1 = time.perf_counter()
|
| 84 |
+
await controller.goto("https://news.ycombinator.com")
|
| 85 |
+
nav_time = (time.perf_counter() - t1) * 1000
|
| 86 |
+
|
| 87 |
+
# Scrape live headline items from table
|
| 88 |
+
live_stories = await controller.page.evaluate("""() => {
|
| 89 |
+
const rows = Array.from(document.querySelectorAll('tr.athing'));
|
| 90 |
+
return rows.slice(0, 5).map(r => {
|
| 91 |
+
const titleLink = r.querySelector('span.titleline > a');
|
| 92 |
+
const rank = r.querySelector('span.rank');
|
| 93 |
+
return {
|
| 94 |
+
rank: rank ? rank.innerText : '',
|
| 95 |
+
title: titleLink ? titleLink.innerText : '',
|
| 96 |
+
href: titleLink ? titleLink.href : ''
|
| 97 |
+
};
|
| 98 |
+
});
|
| 99 |
+
}""")
|
| 100 |
+
|
| 101 |
+
print(f" ✓ Page Loaded in {nav_time:6.1f} ms")
|
| 102 |
+
print(f" ✓ Live Stories Scraped: {len(live_stories)} top posts extracted:")
|
| 103 |
+
for s in live_stories[:3]:
|
| 104 |
+
print(f" [{s.get('rank')}] {s.get('title')[:60]}...")
|
| 105 |
+
|
| 106 |
+
# 3. Live Interaction: Search Query Fill with Hybrid Input
|
| 107 |
+
print("\n[LIVE 3] Executing Live Search Query Interaction...")
|
| 108 |
+
tree = await controller.get_pruned_tree()
|
| 109 |
+
search_inputs = [e for e in tree.elements if e.tag == "input"]
|
| 110 |
+
if search_inputs:
|
| 111 |
+
target_search = search_inputs[0]
|
| 112 |
+
await controller.execute_action(
|
| 113 |
+
BrowserAction(
|
| 114 |
+
op=BrowserActionType.TYPE,
|
| 115 |
+
target=target_search.id,
|
| 116 |
+
text="Canopy AI edge",
|
| 117 |
+
stream_input=False, # High speed atomic fill
|
| 118 |
+
),
|
| 119 |
+
tree,
|
| 120 |
+
)
|
| 121 |
+
print(f" ✓ Search query entered into target {target_search.id}")
|
| 122 |
+
|
| 123 |
+
# 4. Capture Live Annotated Visual Screenshot
|
| 124 |
+
print("\n[LIVE 4] Capturing Live Annotated Screenshot Audit...")
|
| 125 |
+
screenshot_path = artifact_dir / "live_hackernews_audit.png"
|
| 126 |
+
await controller.capture_annotated_step_screenshot(
|
| 127 |
+
output_path=screenshot_path,
|
| 128 |
+
step_num=1,
|
| 129 |
+
action_label="live_nav: news.ycombinator.com",
|
| 130 |
+
click_coords=(200.0, 30.0),
|
| 131 |
+
)
|
| 132 |
+
print(f" ✓ Visual Audit Screenshot Saved: {screenshot_path.name} ({screenshot_path.stat().st_size / 1024:.1f} KB)")
|
| 133 |
+
|
| 134 |
+
results["live_scraping"] = {
|
| 135 |
+
"passed": len(live_stories) >= 3,
|
| 136 |
+
"stories_count": len(live_stories),
|
| 137 |
+
"sample_title": live_stories[0].get("title") if live_stories else None,
|
| 138 |
+
"screenshot": str(screenshot_path),
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
finally:
|
| 142 |
+
await controller.stop()
|
| 143 |
+
|
| 144 |
+
print("\n" + "=" * 80)
|
| 145 |
+
print("📋 LIVE WEB TEST SUMMARY")
|
| 146 |
+
print("=" * 80)
|
| 147 |
+
all_passed = all(r.get("passed", False) for r in results.values())
|
| 148 |
+
for k, v in results.items():
|
| 149 |
+
status = "✓ PASS" if v.get("passed") else "✗ FAIL"
|
| 150 |
+
print(f" {k:<30} : {status}")
|
| 151 |
+
print("=" * 80)
|
| 152 |
+
print(f" Overall Verdict: {'✅ ALL LIVE TESTS PASSED' if all_passed else '❌ ISSUES DETECTED'}")
|
| 153 |
+
print("=" * 80)
|
| 154 |
+
|
| 155 |
+
report_file = CANOPY_ROOT / "miniswardbower" / "artifacts" / "live_web_report.json"
|
| 156 |
+
report_file.write_text(json.dumps(results, indent=2))
|
| 157 |
+
print(f"[✓] Saved Live Web Report to: {report_file}")
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
if __name__ == "__main__":
|
| 161 |
+
asyncio.run(test_live_network_and_scraping())
|
miniswardbower/miniswardbower/browser/action_executor.py
CHANGED
|
@@ -388,3 +388,35 @@ class ActionExecutor:
|
|
| 388 |
pause = random.uniform(0.12, 0.22) if self.enable_natural_dynamics else 0.08
|
| 389 |
await asyncio.sleep(pause)
|
| 390 |
return results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
pause = random.uniform(0.12, 0.22) if self.enable_natural_dynamics else 0.08
|
| 389 |
await asyncio.sleep(pause)
|
| 390 |
return results
|
| 391 |
+
|
| 392 |
+
async def execute_chunk(
|
| 393 |
+
self,
|
| 394 |
+
page: Page,
|
| 395 |
+
actions: List[BrowserAction],
|
| 396 |
+
tree: Optional[PrunedAXTree] = None,
|
| 397 |
+
early_stop_on_navigation: bool = True,
|
| 398 |
+
) -> List[Dict[str, Any]]:
|
| 399 |
+
"""
|
| 400 |
+
Executes an atomic action chunk (AsyncWebRL 2026 / Action-Chunking).
|
| 401 |
+
Pipelined execution across contiguous targets without intermediate re-scans.
|
| 402 |
+
Halts early if navigation occurs or an action encounters an error.
|
| 403 |
+
"""
|
| 404 |
+
results = []
|
| 405 |
+
pre_url = page.url
|
| 406 |
+
for idx, action in enumerate(actions):
|
| 407 |
+
res = await self.execute_action(page, action, tree)
|
| 408 |
+
results.append(res)
|
| 409 |
+
|
| 410 |
+
# Check for URL navigation or state change
|
| 411 |
+
if early_stop_on_navigation and idx < len(actions) - 1:
|
| 412 |
+
try:
|
| 413 |
+
if page.url != pre_url:
|
| 414 |
+
res["early_stopped_due_to_navigation"] = True
|
| 415 |
+
break
|
| 416 |
+
except Exception:
|
| 417 |
+
pass
|
| 418 |
+
|
| 419 |
+
# Brisk human pipelining pause (15-30ms) between chunk actions
|
| 420 |
+
pause = random.uniform(0.015, 0.035) if self.enable_natural_dynamics else 0.005
|
| 421 |
+
await asyncio.sleep(pause)
|
| 422 |
+
return results
|
miniswardbower/miniswardbower/browser/controller.py
CHANGED
|
@@ -43,6 +43,8 @@ class BrowserController:
|
|
| 43 |
"--disable-blink-features=AutomationControlled",
|
| 44 |
"--no-sandbox",
|
| 45 |
"--disable-setuid-sandbox",
|
|
|
|
|
|
|
| 46 |
],
|
| 47 |
)
|
| 48 |
self._context = await self._browser.new_context(
|
|
@@ -50,7 +52,59 @@ class BrowserController:
|
|
| 50 |
user_agent=self.config.user_agent,
|
| 51 |
device_scale_factor=1,
|
| 52 |
accept_downloads=True,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
self._page = await self._context.new_page()
|
| 55 |
|
| 56 |
async def stop(self) -> None:
|
|
@@ -149,6 +203,17 @@ class BrowserController:
|
|
| 149 |
"""Executes a speculative batch of actions."""
|
| 150 |
return await self.executor.execute_batch(self.page, batch, tree)
|
| 151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
async def scrape_page(self) -> Dict[str, Any]:
|
| 153 |
"""Scrapes structured page data (metadata, tables, card elements, text)."""
|
| 154 |
action = BrowserAction(op=BrowserActionType.EXTRACT)
|
|
|
|
| 43 |
"--disable-blink-features=AutomationControlled",
|
| 44 |
"--no-sandbox",
|
| 45 |
"--disable-setuid-sandbox",
|
| 46 |
+
"--disable-infobars",
|
| 47 |
+
"--window-size=1280,800",
|
| 48 |
],
|
| 49 |
)
|
| 50 |
self._context = await self._browser.new_context(
|
|
|
|
| 52 |
user_agent=self.config.user_agent,
|
| 53 |
device_scale_factor=1,
|
| 54 |
accept_downloads=True,
|
| 55 |
+
locale="en-US",
|
| 56 |
+
timezone_id="America/New_York",
|
| 57 |
+
permissions=["geolocation", "notifications"],
|
| 58 |
+
extra_http_headers={
|
| 59 |
+
"Accept-Language": "en-US,en;q=0.9",
|
| 60 |
+
"Sec-Ch-Ua": '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"',
|
| 61 |
+
"Sec-Ch-Ua-Mobile": "?0",
|
| 62 |
+
"Sec-Ch-Ua-Platform": '"Linux"',
|
| 63 |
+
},
|
| 64 |
)
|
| 65 |
+
|
| 66 |
+
# Stealth CDP Profile: Mask automation signatures & emulate authentic physical browser
|
| 67 |
+
await self._context.add_init_script("""
|
| 68 |
+
// 1. Mask navigator.webdriver
|
| 69 |
+
try {
|
| 70 |
+
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
| 71 |
+
} catch (e) {}
|
| 72 |
+
|
| 73 |
+
// 2. Realistic window.chrome object
|
| 74 |
+
if (!window.chrome) {
|
| 75 |
+
window.chrome = {
|
| 76 |
+
app: { isInstalled: false },
|
| 77 |
+
runtime: { OnInstalledReason: {}, OnRestartRequiredReason: {} }
|
| 78 |
+
};
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
// 3. Realistic plugins list
|
| 82 |
+
try {
|
| 83 |
+
Object.defineProperty(navigator, 'plugins', {
|
| 84 |
+
get: () => [
|
| 85 |
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
| 86 |
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
| 87 |
+
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
|
| 88 |
+
]
|
| 89 |
+
});
|
| 90 |
+
} catch (e) {}
|
| 91 |
+
|
| 92 |
+
// 4. Genuine languages
|
| 93 |
+
try {
|
| 94 |
+
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
| 95 |
+
} catch (e) {}
|
| 96 |
+
|
| 97 |
+
// 5. Hardware-accelerated WebGL vendor profile (NVIDIA)
|
| 98 |
+
try {
|
| 99 |
+
const origGetParam = WebGLRenderingContext.prototype.getParameter;
|
| 100 |
+
WebGLRenderingContext.prototype.getParameter = function(param) {
|
| 101 |
+
if (param === 37445) return 'Google Inc. (NVIDIA)';
|
| 102 |
+
if (param === 37446) return 'ANGLE (NVIDIA, NVIDIA GeForce RTX 5090 Direct3D11 vs_5_0 ps_5_0, D3D11)';
|
| 103 |
+
return origGetParam.apply(this, arguments);
|
| 104 |
+
};
|
| 105 |
+
} catch (e) {}
|
| 106 |
+
""")
|
| 107 |
+
|
| 108 |
self._page = await self._context.new_page()
|
| 109 |
|
| 110 |
async def stop(self) -> None:
|
|
|
|
| 203 |
"""Executes a speculative batch of actions."""
|
| 204 |
return await self.executor.execute_batch(self.page, batch, tree)
|
| 205 |
|
| 206 |
+
async def execute_chunk(
|
| 207 |
+
self,
|
| 208 |
+
actions: List[BrowserAction],
|
| 209 |
+
tree: Optional[PrunedAXTree] = None,
|
| 210 |
+
early_stop_on_navigation: bool = True,
|
| 211 |
+
) -> List[Dict[str, Any]]:
|
| 212 |
+
"""Executes a pipelined speculative action chunk."""
|
| 213 |
+
return await self.executor.execute_chunk(
|
| 214 |
+
self.page, actions, tree, early_stop_on_navigation=early_stop_on_navigation
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
async def scrape_page(self) -> Dict[str, Any]:
|
| 218 |
"""Scrapes structured page data (metadata, tables, card elements, text)."""
|
| 219 |
action = BrowserAction(op=BrowserActionType.EXTRACT)
|
miniswardbower/miniswardbower/evals/mock_web_env.py
CHANGED
|
@@ -464,6 +464,265 @@ HTML_WIZARD_PAGE = """<!DOCTYPE html>
|
|
| 464 |
"""
|
| 465 |
|
| 466 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
class MockServerHandler(BaseHTTPRequestHandler):
|
| 468 |
def do_GET(self):
|
| 469 |
path = self.path.split("?")[0]
|
|
@@ -483,6 +742,10 @@ class MockServerHandler(BaseHTTPRequestHandler):
|
|
| 483 |
content = HTML_AUTOCOMPLETE_PAGE.encode("utf-8")
|
| 484 |
elif path == "/wizard":
|
| 485 |
content = HTML_WIZARD_PAGE.encode("utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
else:
|
| 487 |
content = HTML_FORM_PAGE.encode("utf-8")
|
| 488 |
|
|
@@ -556,3 +819,11 @@ class MockWebEnvironment:
|
|
| 556 |
@property
|
| 557 |
def wizard_url(self) -> str:
|
| 558 |
return f"http://127.0.0.1:{self.port}/wizard"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
"""
|
| 465 |
|
| 466 |
|
| 467 |
+
HTML_ECOMMERCE_PAGE = """<!DOCTYPE html>
|
| 468 |
+
<html>
|
| 469 |
+
<head>
|
| 470 |
+
<title>Canopy Cloud Hardware Emporium</title>
|
| 471 |
+
<style>
|
| 472 |
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 850px; margin: 30px auto; padding: 20px; background: #0b0f19; color: #f1f5f9; }
|
| 473 |
+
.filter-bar { display: flex; gap: 15px; margin-bottom: 25px; align-items: center; background: #1e293b; padding: 12px 18px; border-radius: 8px; }
|
| 474 |
+
select, input { padding: 8px 12px; background: #0f172a; color: #f8fafc; border: 1px solid #334155; border-radius: 6px; }
|
| 475 |
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
| 476 |
+
.card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 20px; position: relative; }
|
| 477 |
+
.price { font-size: 20px; font-weight: bold; color: #38bdf8; margin: 10px 0; }
|
| 478 |
+
button { padding: 10px 18px; background: #2563eb; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; }
|
| 479 |
+
button:hover { background: #1d4ed8; }
|
| 480 |
+
#cart-drawer { display: none; margin-top: 25px; padding: 20px; background: #1e293b; border: 2px solid #3b82f6; border-radius: 8px; }
|
| 481 |
+
#checkout-form { display: none; margin-top: 20px; background: #0f172a; padding: 20px; border-radius: 8px; border: 1px solid #334155; }
|
| 482 |
+
.form-row { margin-bottom: 12px; }
|
| 483 |
+
.receipt { display: none; margin-top: 20px; padding: 20px; background: #064e3b; border: 1px solid #059669; border-radius: 8px; color: #a7f3d0; }
|
| 484 |
+
</style>
|
| 485 |
+
</head>
|
| 486 |
+
<body>
|
| 487 |
+
<h1>Enterprise Accelerators & Compute Nodes</h1>
|
| 488 |
+
<div class="filter-bar">
|
| 489 |
+
<label>Category:</label>
|
| 490 |
+
<select id="category-filter" onchange="filterCategory()">
|
| 491 |
+
<option value="all">All Accelerators</option>
|
| 492 |
+
<option value="b200">Blackwell B200 SXM</option>
|
| 493 |
+
<option value="h100">Hopper H100 NVL</option>
|
| 494 |
+
</select>
|
| 495 |
+
<span id="stock-badge" style="color:#10b981;font-weight:600;">● In Stock (Verified)</span>
|
| 496 |
+
</div>
|
| 497 |
+
<div class="grid">
|
| 498 |
+
<div class="card" id="card-b200">
|
| 499 |
+
<h3>NVIDIA B200 Tensor Core Node</h3>
|
| 500 |
+
<p>192GB HBM3e Memory, 8TB/s Bandwidth</p>
|
| 501 |
+
<div class="price" id="base-price">$2,499.00 / mo</div>
|
| 502 |
+
<label>Memory Spec: </label>
|
| 503 |
+
<select id="variant-select" onchange="updateVariantPrice()">
|
| 504 |
+
<option value="standard">192GB Standard</option>
|
| 505 |
+
<option value="extreme">288GB Ultra (+ $500)</option>
|
| 506 |
+
</select>
|
| 507 |
+
<div style="margin-top: 15px;">
|
| 508 |
+
<button id="add-to-cart-btn" onclick="addToCart()">Add Accelerator to Cluster</button>
|
| 509 |
+
</div>
|
| 510 |
+
</div>
|
| 511 |
+
</div>
|
| 512 |
+
<div id="cart-drawer">
|
| 513 |
+
<h2>Provisioning Cart Summary</h2>
|
| 514 |
+
<p>Configured: <span id="cart-item-name">NVIDIA B200 Tensor Core Node</span> (<span id="cart-variant">192GB Standard</span>)</p>
|
| 515 |
+
<p>Subtotal: <span id="cart-subtotal" style="font-weight:bold;color:#38bdf8;">$2,499.00</span></p>
|
| 516 |
+
<div style="margin: 15px 0;">
|
| 517 |
+
<input type="text" id="voucher-code" placeholder="Enter promo voucher (e.g. VIP20)">
|
| 518 |
+
<button id="apply-voucher-btn" onclick="applyVoucher()" style="background:#059669;">Apply Voucher</button>
|
| 519 |
+
<span id="voucher-status" style="margin-left:10px;font-weight:600;"></span>
|
| 520 |
+
</div>
|
| 521 |
+
<p>Final Price: <span id="cart-total" style="font-size:22px;font-weight:bold;color:#10b981;">$2,499.00</span></p>
|
| 522 |
+
<button id="proceed-checkout-btn" onclick="openCheckout()">Proceed to Authorization & Checkout ></button>
|
| 523 |
+
</div>
|
| 524 |
+
<div id="checkout-form">
|
| 525 |
+
<h3>Cluster Assignment & Billing Authorization</h3>
|
| 526 |
+
<div class="form-row">
|
| 527 |
+
<label style="display:block;margin-bottom:4px;">Cluster Name:</label>
|
| 528 |
+
<input type="text" id="cluster-name" placeholder="production-inference-pool" style="width:100%;box-sizing:border-box;">
|
| 529 |
+
</div>
|
| 530 |
+
<div class="form-row">
|
| 531 |
+
<label style="display:block;margin-bottom:4px;">Technical Contact Email:</label>
|
| 532 |
+
<input type="email" id="contact-email" placeholder="devops@canopy.corp" style="width:100%;box-sizing:border-box;">
|
| 533 |
+
</div>
|
| 534 |
+
<div class="form-row">
|
| 535 |
+
<label><input type="checkbox" id="terms-agree"> I authorize immediate resource provisioning and telemetry audit.</label>
|
| 536 |
+
</div>
|
| 537 |
+
<button id="confirm-order-btn" onclick="placeOrder()" style="background:#16a34a;font-size:16px;padding:12px 24px;">Confirm & Provision Cluster</button>
|
| 538 |
+
</div>
|
| 539 |
+
<div id="order-receipt" class="receipt">
|
| 540 |
+
<h2>🎉 Provisioning Finalized!</h2>
|
| 541 |
+
<p>Order Reference: <span id="receipt-id" style="font-family:monospace;font-weight:bold;color:#fff;"></span></p>
|
| 542 |
+
<p>Cluster: <span id="receipt-cluster"></span> | Total Allocated: <span id="receipt-amount"></span></p>
|
| 543 |
+
</div>
|
| 544 |
+
<script>
|
| 545 |
+
let price = 2499.00;
|
| 546 |
+
let discount = 0.0;
|
| 547 |
+
function updateVariantPrice() {
|
| 548 |
+
const v = document.getElementById('variant-select').value;
|
| 549 |
+
price = (v === 'extreme') ? 2999.00 : 2499.00;
|
| 550 |
+
document.getElementById('base-price').innerText = '$' + price.toFixed(2) + ' / mo';
|
| 551 |
+
}
|
| 552 |
+
function addToCart() {
|
| 553 |
+
document.getElementById('cart-drawer').style.display = 'block';
|
| 554 |
+
document.getElementById('cart-variant').innerText = document.getElementById('variant-select').value;
|
| 555 |
+
document.getElementById('cart-subtotal').innerText = '$' + price.toFixed(2);
|
| 556 |
+
document.getElementById('cart-total').innerText = '$' + (price * (1 - discount)).toFixed(2);
|
| 557 |
+
document.title = "Cart: 1 Item Ready";
|
| 558 |
+
}
|
| 559 |
+
function applyVoucher() {
|
| 560 |
+
const code = document.getElementById('voucher-code').value.trim().toUpperCase();
|
| 561 |
+
if (code === 'VIP20') {
|
| 562 |
+
discount = 0.20;
|
| 563 |
+
document.getElementById('voucher-status').innerText = '✓ 20% Discount Applied!';
|
| 564 |
+
document.getElementById('voucher-status').style.color = '#10b981';
|
| 565 |
+
} else {
|
| 566 |
+
document.getElementById('voucher-status').innerText = '✗ Invalid Code';
|
| 567 |
+
document.getElementById('voucher-status').style.color = '#ef4444';
|
| 568 |
+
}
|
| 569 |
+
document.getElementById('cart-total').innerText = '$' + (price * (1 - discount)).toFixed(2);
|
| 570 |
+
}
|
| 571 |
+
function openCheckout() {
|
| 572 |
+
document.getElementById('checkout-form').style.display = 'block';
|
| 573 |
+
document.title = "Checkout: Authorization";
|
| 574 |
+
}
|
| 575 |
+
function placeOrder() {
|
| 576 |
+
if (!document.getElementById('terms-agree').checked) {
|
| 577 |
+
alert('Please accept terms');
|
| 578 |
+
return;
|
| 579 |
+
}
|
| 580 |
+
const cName = document.getElementById('cluster-name').value;
|
| 581 |
+
const finalAmt = document.getElementById('cart-total').innerText;
|
| 582 |
+
const orderId = 'CANOPY-ORD-' + Math.floor(100000 + Math.random() * 900000);
|
| 583 |
+
document.getElementById('cart-drawer').style.display = 'none';
|
| 584 |
+
document.getElementById('checkout-form').style.display = 'none';
|
| 585 |
+
document.getElementById('receipt-id').innerText = orderId;
|
| 586 |
+
document.getElementById('receipt-cluster').innerText = cName;
|
| 587 |
+
document.getElementById('receipt-amount').innerText = finalAmt;
|
| 588 |
+
document.getElementById('order-receipt').style.display = 'block';
|
| 589 |
+
document.title = "Order Confirmed: " + orderId;
|
| 590 |
+
}
|
| 591 |
+
</script>
|
| 592 |
+
</body>
|
| 593 |
+
</html>
|
| 594 |
+
"""
|
| 595 |
+
|
| 596 |
+
HTML_ETL_PAGE = """<!DOCTYPE html>
|
| 597 |
+
<html>
|
| 598 |
+
<head>
|
| 599 |
+
<title>Enterprise ETL & Data Pipeline Control</title>
|
| 600 |
+
<style>
|
| 601 |
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 900px; margin: 30px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
|
| 602 |
+
.toolbar { display: flex; gap: 12px; margin-bottom: 20px; align-items: center; background: #1e293b; padding: 12px; border-radius: 8px; }
|
| 603 |
+
select, input { padding: 8px 12px; background: #0f172a; color: #f8fafc; border: 1px solid #334155; border-radius: 6px; }
|
| 604 |
+
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
|
| 605 |
+
th, td { border: 1px solid #334155; padding: 10px 14px; text-align: left; }
|
| 606 |
+
th { background: #1e293b; }
|
| 607 |
+
button { padding: 8px 16px; background: #0284c7; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; }
|
| 608 |
+
.status-failed { color: #f87171; font-weight: bold; }
|
| 609 |
+
.status-ok { color: #4ade80; font-weight: bold; }
|
| 610 |
+
#modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); }
|
| 611 |
+
.modal-box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1e293b; padding: 25px; border-radius: 8px; border: 1px solid #475569; width: 420px; }
|
| 612 |
+
#dispatch-progress { display: none; margin-top: 20px; padding: 15px; background: #1e293b; border-radius: 8px; border: 1px solid #0284c7; }
|
| 613 |
+
.progress-bar { width: 100%; height: 12px; background: #0f172a; border-radius: 6px; overflow: hidden; margin-top: 8px; }
|
| 614 |
+
.progress-fill { width: 0%; height: 100%; background: #38bdf8; transition: width 0.3s; }
|
| 615 |
+
</style>
|
| 616 |
+
</head>
|
| 617 |
+
<body>
|
| 618 |
+
<h1>Data Pipeline Execution Ledger</h1>
|
| 619 |
+
<div class="toolbar">
|
| 620 |
+
<label>Filter Status:</label>
|
| 621 |
+
<select id="status-filter" onchange="filterTable()">
|
| 622 |
+
<option value="all">All Jobs</option>
|
| 623 |
+
<option value="failed">Failed Jobs Only</option>
|
| 624 |
+
<option value="success">Healthy Jobs Only</option>
|
| 625 |
+
</select>
|
| 626 |
+
<button id="select-all-btn" onclick="toggleSelectAll()">Select All Filtered</button>
|
| 627 |
+
<label style="margin-left:auto;">Action: </label>
|
| 628 |
+
<select id="batch-action-select">
|
| 629 |
+
<option value="rerun">Batch Re-run & Reseed</option>
|
| 630 |
+
<option value="archive">Archive Cold Storage</option>
|
| 631 |
+
</select>
|
| 632 |
+
<button id="dispatch-batch-btn" onclick="openBatchModal()" style="background:#16a34a;">Execute Batch</button>
|
| 633 |
+
</div>
|
| 634 |
+
<table id="etl-table">
|
| 635 |
+
<thead>
|
| 636 |
+
<tr>
|
| 637 |
+
<th><input type="checkbox" id="th-checkbox" onchange="toggleCheckbox(this)"></th>
|
| 638 |
+
<th>Job ID</th>
|
| 639 |
+
<th>Dataset</th>
|
| 640 |
+
<th>Status</th>
|
| 641 |
+
<th>Records</th>
|
| 642 |
+
</tr>
|
| 643 |
+
</thead>
|
| 644 |
+
<tbody id="etl-tbody">
|
| 645 |
+
<tr class="row-job" data-status="failed">
|
| 646 |
+
<td><input type="checkbox" class="job-chk" value="JOB-101"></td>
|
| 647 |
+
<td>JOB-101</td><td>Parquet Cosmopedia Partition 4</td><td class="status-failed">Failed (OOM)</td><td>1.2M</td>
|
| 648 |
+
</tr>
|
| 649 |
+
<tr class="row-job" data-status="failed">
|
| 650 |
+
<td><input type="checkbox" class="job-chk" value="JOB-102"></td>
|
| 651 |
+
<td>JOB-102</td><td>FineWeb-Edu Shard 18</td><td class="status-failed">Failed (Timeout)</td><td>3.4M</td>
|
| 652 |
+
</tr>
|
| 653 |
+
<tr class="row-job" data-status="success">
|
| 654 |
+
<td><input type="checkbox" class="job-chk" value="JOB-103"></td>
|
| 655 |
+
<td>JOB-103</td><td>OpenMathInstruct V2 Subsample</td><td class="status-ok">Success</td><td>800K</td>
|
| 656 |
+
</tr>
|
| 657 |
+
</tbody>
|
| 658 |
+
</table>
|
| 659 |
+
<div id="modal-overlay">
|
| 660 |
+
<div class="modal-box">
|
| 661 |
+
<h3>⚠️ Confirm Batch Action</h3>
|
| 662 |
+
<p>You are about to dispatch batch execution on <span id="modal-selected-count" style="font-weight:bold;color:#38bdf8;">0</span> jobs.</p>
|
| 663 |
+
<label>Priority: </label>
|
| 664 |
+
<select id="batch-priority">
|
| 665 |
+
<option value="high">High (Preempt Worker)</option>
|
| 666 |
+
<option value="standard">Standard</option>
|
| 667 |
+
</select>
|
| 668 |
+
<div style="margin-top:20px;display:flex;gap:10px;justify-content:flex-end;">
|
| 669 |
+
<button onclick="closeModal()" style="background:#475569;">Cancel</button>
|
| 670 |
+
<button id="confirm-batch-btn" onclick="startDispatch()" style="background:#16a34a;">Confirm & Dispatch</button>
|
| 671 |
+
</div>
|
| 672 |
+
</div>
|
| 673 |
+
</div>
|
| 674 |
+
<div id="dispatch-progress">
|
| 675 |
+
<h3>🚀 Batch Execution Progress</h3>
|
| 676 |
+
<p id="progress-status">Initiating worker cluster nodes...</p>
|
| 677 |
+
<div class="progress-bar"><div class="progress-fill" id="progress-fill"></div></div>
|
| 678 |
+
<div id="final-receipt" style="display:none;margin-top:12px;color:#4ade80;font-weight:bold;">
|
| 679 |
+
Batch #ETL-BATCH-8891 Finalized Successfully. All selected jobs active.
|
| 680 |
+
</div>
|
| 681 |
+
</div>
|
| 682 |
+
<script>
|
| 683 |
+
function filterTable() {
|
| 684 |
+
const val = document.getElementById('status-filter').value;
|
| 685 |
+
document.querySelectorAll('.row-job').forEach(r => {
|
| 686 |
+
if (val === 'all' || r.getAttribute('data-status') === val) {
|
| 687 |
+
r.style.display = '';
|
| 688 |
+
} else {
|
| 689 |
+
r.style.display = 'none';
|
| 690 |
+
}
|
| 691 |
+
});
|
| 692 |
+
}
|
| 693 |
+
function toggleSelectAll() {
|
| 694 |
+
document.querySelectorAll('.row-job').forEach(r => {
|
| 695 |
+
if (r.style.display !== 'none') {
|
| 696 |
+
r.querySelector('.job-chk').checked = true;
|
| 697 |
+
}
|
| 698 |
+
});
|
| 699 |
+
}
|
| 700 |
+
function toggleCheckbox(master) {
|
| 701 |
+
document.querySelectorAll('.job-chk').forEach(c => c.checked = master.checked);
|
| 702 |
+
}
|
| 703 |
+
function openBatchModal() {
|
| 704 |
+
const count = Array.from(document.querySelectorAll('.job-chk:checked')).length;
|
| 705 |
+
document.getElementById('modal-selected-count').innerText = count;
|
| 706 |
+
document.getElementById('modal-overlay').style.display = 'block';
|
| 707 |
+
document.title = "Modal: Confirm Batch (" + count + " selected)";
|
| 708 |
+
}
|
| 709 |
+
function closeModal() {
|
| 710 |
+
document.getElementById('modal-overlay').style.display = 'none';
|
| 711 |
+
}
|
| 712 |
+
function startDispatch() {
|
| 713 |
+
closeModal();
|
| 714 |
+
document.getElementById('dispatch-progress').style.display = 'block';
|
| 715 |
+
document.getElementById('progress-fill').style.width = '100%';
|
| 716 |
+
document.getElementById('progress-status').innerText = '100% Complete - Batch Dispatched to 8 GPU workers.';
|
| 717 |
+
document.getElementById('final-receipt').style.display = 'block';
|
| 718 |
+
document.title = "Batch Pipeline Dispatched";
|
| 719 |
+
}
|
| 720 |
+
</script>
|
| 721 |
+
</body>
|
| 722 |
+
</html>
|
| 723 |
+
"""
|
| 724 |
+
|
| 725 |
+
|
| 726 |
class MockServerHandler(BaseHTTPRequestHandler):
|
| 727 |
def do_GET(self):
|
| 728 |
path = self.path.split("?")[0]
|
|
|
|
| 742 |
content = HTML_AUTOCOMPLETE_PAGE.encode("utf-8")
|
| 743 |
elif path == "/wizard":
|
| 744 |
content = HTML_WIZARD_PAGE.encode("utf-8")
|
| 745 |
+
elif path == "/ecommerce":
|
| 746 |
+
content = HTML_ECOMMERCE_PAGE.encode("utf-8")
|
| 747 |
+
elif path == "/etl":
|
| 748 |
+
content = HTML_ETL_PAGE.encode("utf-8")
|
| 749 |
else:
|
| 750 |
content = HTML_FORM_PAGE.encode("utf-8")
|
| 751 |
|
|
|
|
| 819 |
@property
|
| 820 |
def wizard_url(self) -> str:
|
| 821 |
return f"http://127.0.0.1:{self.port}/wizard"
|
| 822 |
+
|
| 823 |
+
@property
|
| 824 |
+
def ecommerce_url(self) -> str:
|
| 825 |
+
return f"http://127.0.0.1:{self.port}/ecommerce"
|
| 826 |
+
|
| 827 |
+
@property
|
| 828 |
+
def etl_url(self) -> str:
|
| 829 |
+
return f"http://127.0.0.1:{self.port}/etl"
|