@xnng/browser-relay 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +445 -0
  3. package/docs/README.zh-CN.md +421 -0
  4. package/docs/benchmarks/browser-gaps-codex-ax.json +418 -0
  5. package/docs/benchmarks/browser-gaps-relay-after.json +655 -0
  6. package/docs/benchmarks/browser-gaps-relay-baseline.json +679 -0
  7. package/docs/benchmarks/browser-readiness-cost.json +106 -0
  8. package/docs/benchmarks/browser-runtime-balanced-headed.json +412 -0
  9. package/docs/benchmarks/browser-runtime-balanced-rtt50.json +412 -0
  10. package/docs/benchmarks/browser-runtime-balanced.json +412 -0
  11. package/docs/benchmarks/browser-runtime-rtt50.json +194 -0
  12. package/docs/benchmarks/browser-runtime.json +254 -0
  13. package/docs/benchmarks/browser-use-parity.json +54 -0
  14. package/docs/benchmarks/codex-extension-audit.json +131 -0
  15. package/docs/benchmarks/codex-native-protocol.md +69 -0
  16. package/docs/benchmarks/codex-native-replay.js +116 -0
  17. package/docs/benchmarks/codex-native-status.json +81 -0
  18. package/docs/benchmarks/codex-native.json +1003 -0
  19. package/docs/benchmarks/extension-sessions.png +0 -0
  20. package/docs/benchmarks/extension-tasks.png +0 -0
  21. package/docs/benchmarks/iframe-routing-regression.json +37 -0
  22. package/docs/browser-use-comparison.md +331 -0
  23. package/docs/browser-use-gap-audit.md +141 -0
  24. package/docs/browser-use-parity.md +105 -0
  25. package/docs/demo/intranet.html +103 -0
  26. package/docs/releases/v1.5.0.md +59 -0
  27. package/docs/releases/v1.5.1.md +16 -0
  28. package/docs/releases/v1.5.2.md +42 -0
  29. package/docs/releases/v1.5.3.md +33 -0
  30. package/docs/releases/v1.5.4.md +42 -0
  31. package/docs/releases/v1.6.0.md +16 -0
  32. package/docs/remote-control-hub.md +523 -0
  33. package/extension/activity.js +328 -0
  34. package/extension/automation.js +1839 -0
  35. package/extension/background.js +1854 -0
  36. package/extension/i18n.js +149 -0
  37. package/extension/icons/icon128.png +0 -0
  38. package/extension/icons/icon16.png +0 -0
  39. package/extension/icons/icon32.png +0 -0
  40. package/extension/icons/icon48.png +0 -0
  41. package/extension/manifest.json +47 -0
  42. package/extension/observations.js +109 -0
  43. package/extension/options.html +289 -0
  44. package/extension/options.js +269 -0
  45. package/extension/popup.html +74 -0
  46. package/extension/popup.js +105 -0
  47. package/extension/protocol.js +45 -0
  48. package/extension/remote-auth.js +18 -0
  49. package/extension/sessions.js +134 -0
  50. package/extension/snapshot.js +161 -0
  51. package/extension/task-groups.js +108 -0
  52. package/extension/tasks.js +186 -0
  53. package/extension/wait.js +89 -0
  54. package/hub/README.md +42 -0
  55. package/hub/package-lock.json +1544 -0
  56. package/hub/package.json +13 -0
  57. package/hub/src/rpc.js +41 -0
  58. package/hub/src/worker.js +322 -0
  59. package/hub/wrangler.example.toml +19 -0
  60. package/package.json +83 -0
  61. package/server/cdp-bridge.js +200 -0
  62. package/server/cli.js +1798 -0
  63. package/server/hub-server.js +258 -0
  64. package/server/install.js +250 -0
  65. package/server/mcp-server.js +504 -0
  66. package/server/npx-runner.js +96 -0
  67. package/server/relay-server.js +1356 -0
  68. package/server/remote-protocol.js +76 -0
  69. package/server/runtime-worker.js +166 -0
  70. package/server/script-runtime.js +189 -0
  71. package/server/sdk.js +307 -0
  72. package/server/service-state.js +103 -0
  73. package/server/snapshot.js +161 -0
  74. package/server/uninstall.js +61 -0
  75. package/server/windows-service-entry.js +58 -0
  76. package/server/windows-service.js +360 -0
  77. package/skills/browser-relay/SKILL.md +192 -0
  78. package/skills/browser-relay/references/legacy-api.md +163 -0
  79. package/skills/browser-relay/references/runtime.md +240 -0
@@ -0,0 +1,254 @@
1
+ {
2
+ "measuredAt": "2026-09-08T11:15:19.408Z",
3
+ "injectedExtensionRttMs": 0,
4
+ "platform": "darwin",
5
+ "node": "v22.23.1",
6
+ "chromium": "153.0.8010.12",
7
+ "task": "Search invoice / owner Alice / active only; await visible result",
8
+ "scope": "Deterministic browser/tool execution; no model inference or token accounting. Playwright over Relay is not Codex Browser Use.",
9
+ "summary": {
10
+ "legacy": {
11
+ "runs": 7,
12
+ "successes": 7,
13
+ "elapsedMs": 273.5,
14
+ "httpCalls": 10,
15
+ "extensionRoundTrips": 39,
16
+ "responseBytes": 3521,
17
+ "snapshotBytes": 2301
18
+ },
19
+ "actions": {
20
+ "runs": 7,
21
+ "successes": 7,
22
+ "elapsedMs": 278.7,
23
+ "httpCalls": 2,
24
+ "extensionRoundTrips": 2,
25
+ "responseBytes": 3919,
26
+ "snapshotBytes": 2202
27
+ },
28
+ "playwright-cdp": {
29
+ "runs": 7,
30
+ "successes": 7,
31
+ "elapsedMs": 334.8,
32
+ "httpCalls": 0,
33
+ "extensionRoundTrips": 73,
34
+ "responseBytes": null,
35
+ "snapshotBytes": 1425
36
+ }
37
+ },
38
+ "delta": {
39
+ "fullSnapshotBytes": 1880,
40
+ "changedSnapshotBytes": 160
41
+ },
42
+ "samples": [
43
+ {
44
+ "variant": "legacy",
45
+ "iteration": 0,
46
+ "success": true,
47
+ "elapsedMs": 292.4,
48
+ "httpCalls": 10,
49
+ "extensionRoundTrips": 39,
50
+ "responseBytes": 3517,
51
+ "snapshotBytes": 2301
52
+ },
53
+ {
54
+ "variant": "actions",
55
+ "iteration": 0,
56
+ "success": true,
57
+ "elapsedMs": 278.7,
58
+ "httpCalls": 2,
59
+ "extensionRoundTrips": 2,
60
+ "responseBytes": 3923,
61
+ "snapshotBytes": 2202
62
+ },
63
+ {
64
+ "variant": "playwright-cdp",
65
+ "iteration": 0,
66
+ "success": true,
67
+ "elapsedMs": 293,
68
+ "httpCalls": 0,
69
+ "extensionRoundTrips": 73,
70
+ "responseBytes": null,
71
+ "snapshotBytes": 1425
72
+ },
73
+ {
74
+ "variant": "legacy",
75
+ "iteration": 1,
76
+ "success": true,
77
+ "elapsedMs": 274.7,
78
+ "httpCalls": 10,
79
+ "extensionRoundTrips": 39,
80
+ "responseBytes": 3517,
81
+ "snapshotBytes": 2301
82
+ },
83
+ {
84
+ "variant": "actions",
85
+ "iteration": 1,
86
+ "success": true,
87
+ "elapsedMs": 284.1,
88
+ "httpCalls": 2,
89
+ "extensionRoundTrips": 2,
90
+ "responseBytes": 3926,
91
+ "snapshotBytes": 2202
92
+ },
93
+ {
94
+ "variant": "playwright-cdp",
95
+ "iteration": 1,
96
+ "success": true,
97
+ "elapsedMs": 329.3,
98
+ "httpCalls": 0,
99
+ "extensionRoundTrips": 73,
100
+ "responseBytes": null,
101
+ "snapshotBytes": 1425
102
+ },
103
+ {
104
+ "variant": "legacy",
105
+ "iteration": 2,
106
+ "success": true,
107
+ "elapsedMs": 291.8,
108
+ "httpCalls": 10,
109
+ "extensionRoundTrips": 39,
110
+ "responseBytes": 3521,
111
+ "snapshotBytes": 2301
112
+ },
113
+ {
114
+ "variant": "actions",
115
+ "iteration": 2,
116
+ "success": true,
117
+ "elapsedMs": 265.9,
118
+ "httpCalls": 2,
119
+ "extensionRoundTrips": 2,
120
+ "responseBytes": 3918,
121
+ "snapshotBytes": 2202
122
+ },
123
+ {
124
+ "variant": "playwright-cdp",
125
+ "iteration": 2,
126
+ "success": true,
127
+ "elapsedMs": 338.5,
128
+ "httpCalls": 0,
129
+ "extensionRoundTrips": 73,
130
+ "responseBytes": null,
131
+ "snapshotBytes": 1425
132
+ },
133
+ {
134
+ "variant": "legacy",
135
+ "iteration": 3,
136
+ "success": true,
137
+ "elapsedMs": 270.2,
138
+ "httpCalls": 10,
139
+ "extensionRoundTrips": 39,
140
+ "responseBytes": 3521,
141
+ "snapshotBytes": 2301
142
+ },
143
+ {
144
+ "variant": "actions",
145
+ "iteration": 3,
146
+ "success": true,
147
+ "elapsedMs": 288.1,
148
+ "httpCalls": 2,
149
+ "extensionRoundTrips": 2,
150
+ "responseBytes": 3919,
151
+ "snapshotBytes": 2202
152
+ },
153
+ {
154
+ "variant": "playwright-cdp",
155
+ "iteration": 3,
156
+ "success": true,
157
+ "elapsedMs": 323.5,
158
+ "httpCalls": 0,
159
+ "extensionRoundTrips": 73,
160
+ "responseBytes": null,
161
+ "snapshotBytes": 1425
162
+ },
163
+ {
164
+ "variant": "legacy",
165
+ "iteration": 4,
166
+ "success": true,
167
+ "elapsedMs": 273.5,
168
+ "httpCalls": 10,
169
+ "extensionRoundTrips": 39,
170
+ "responseBytes": 3521,
171
+ "snapshotBytes": 2301
172
+ },
173
+ {
174
+ "variant": "actions",
175
+ "iteration": 4,
176
+ "success": true,
177
+ "elapsedMs": 269.7,
178
+ "httpCalls": 2,
179
+ "extensionRoundTrips": 2,
180
+ "responseBytes": 3918,
181
+ "snapshotBytes": 2202
182
+ },
183
+ {
184
+ "variant": "playwright-cdp",
185
+ "iteration": 4,
186
+ "success": true,
187
+ "elapsedMs": 338.4,
188
+ "httpCalls": 0,
189
+ "extensionRoundTrips": 73,
190
+ "responseBytes": null,
191
+ "snapshotBytes": 1425
192
+ },
193
+ {
194
+ "variant": "legacy",
195
+ "iteration": 5,
196
+ "success": true,
197
+ "elapsedMs": 269.7,
198
+ "httpCalls": 10,
199
+ "extensionRoundTrips": 39,
200
+ "responseBytes": 3521,
201
+ "snapshotBytes": 2301
202
+ },
203
+ {
204
+ "variant": "actions",
205
+ "iteration": 5,
206
+ "success": true,
207
+ "elapsedMs": 278.3,
208
+ "httpCalls": 2,
209
+ "extensionRoundTrips": 2,
210
+ "responseBytes": 3918,
211
+ "snapshotBytes": 2202
212
+ },
213
+ {
214
+ "variant": "playwright-cdp",
215
+ "iteration": 5,
216
+ "success": true,
217
+ "elapsedMs": 334.8,
218
+ "httpCalls": 0,
219
+ "extensionRoundTrips": 73,
220
+ "responseBytes": null,
221
+ "snapshotBytes": 1425
222
+ },
223
+ {
224
+ "variant": "legacy",
225
+ "iteration": 6,
226
+ "success": true,
227
+ "elapsedMs": 261.4,
228
+ "httpCalls": 10,
229
+ "extensionRoundTrips": 39,
230
+ "responseBytes": 3521,
231
+ "snapshotBytes": 2301
232
+ },
233
+ {
234
+ "variant": "actions",
235
+ "iteration": 6,
236
+ "success": true,
237
+ "elapsedMs": 280.1,
238
+ "httpCalls": 2,
239
+ "extensionRoundTrips": 2,
240
+ "responseBytes": 3924,
241
+ "snapshotBytes": 2202
242
+ },
243
+ {
244
+ "variant": "playwright-cdp",
245
+ "iteration": 6,
246
+ "success": true,
247
+ "elapsedMs": 342.5,
248
+ "httpCalls": 0,
249
+ "extensionRoundTrips": 73,
250
+ "responseBytes": null,
251
+ "snapshotBytes": 1425
252
+ }
253
+ ]
254
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "url": "https://zh.omarchy.org/",
3
+ "capability": {
4
+ "ok": true,
5
+ "protocolVersion": 2,
6
+ "features": [
7
+ "observe",
8
+ "read",
9
+ "ax",
10
+ "refs",
11
+ "frames",
12
+ "shadow-dom",
13
+ "diff",
14
+ "actions",
15
+ "tasks",
16
+ "coordinates",
17
+ "drag",
18
+ "hover",
19
+ "screenshot-mapping",
20
+ "scoped-targets",
21
+ "action-readiness-wait",
22
+ "aria-check",
23
+ "tabs",
24
+ "observation-pages",
25
+ "full-links",
26
+ "sessions",
27
+ "handoff",
28
+ "focus"
29
+ ],
30
+ "maxActions": 100,
31
+ "extensionVersion": "1.5.0-dev.1",
32
+ "runtimeId": "0fbd0483-b48f-4ff2-98b0-8589240d99d1"
33
+ },
34
+ "elapsedMs": 3792,
35
+ "pages": 4,
36
+ "readBytes": 66862,
37
+ "legacyBytes": 37409,
38
+ "checks": {
39
+ "main": true,
40
+ "legacy": true,
41
+ "fullLinks": true
42
+ },
43
+ "checksRun": {
44
+ "unit": {
45
+ "passed": 105,
46
+ "skipped": 7
47
+ },
48
+ "browser": {
49
+ "passed": 28,
50
+ "failed": 0
51
+ }
52
+ },
53
+ "scope": "isolated-profile public-page reading verification; not X speed comparison"
54
+ }
@@ -0,0 +1,131 @@
1
+ {
2
+ "auditedAt": "2026-09-09",
3
+ "chromeExtensionVersion": "1.26.901.11451",
4
+ "desktopPluginVersion": "26.901.51231",
5
+ "sourceMapsFound": 0,
6
+ "scope": "Installed published JavaScript bundles and packaged workflow docs, not the complete upstream development source. Compiled/native parts and live server-side deployment are not fully visible. No vendor code copied into project.",
7
+ "formatting": "Read-only Prettier 3.6.2 copies under /tmp/browser-relay-codex-audit",
8
+ "sourceFiles": [
9
+ {
10
+ "package": "chrome-extension",
11
+ "path": "manifest.json",
12
+ "bytes": 2739,
13
+ "sha256": "43c407d46765d3f85754a516b4500b7f491c067548456ba211fa82fca5bd209b"
14
+ },
15
+ {
16
+ "package": "chrome-extension",
17
+ "path": "background.js",
18
+ "bytes": 355753,
19
+ "sha256": "83b7b7708ab82ecc9aa2ccad20a2167941a706b38a9467dee0bb124d13f0033e"
20
+ },
21
+ {
22
+ "package": "chrome-extension",
23
+ "path": "content-scripts/codex.js",
24
+ "bytes": 35143,
25
+ "sha256": "d01bc3fd8c98fb5c7a42cf6f465a8e27e24d436b2d4304df2ef38c027f54cc70"
26
+ },
27
+ {
28
+ "package": "chrome-extension",
29
+ "path": "content-scripts/foreign-frame-monitor.js",
30
+ "bytes": 5158,
31
+ "sha256": "337ad4ff3ec7846134e5d39528936b1cbb68107de3c4da0e0641c712bda66129"
32
+ },
33
+ {
34
+ "package": "desktop-browser-plugin",
35
+ "path": "scripts/browser-service.mjs",
36
+ "bytes": 1308334,
37
+ "sha256": "953706b76083dd4067958dee7668c4b88beb26122d6262fa60cb79ff7e6abbb1"
38
+ },
39
+ {
40
+ "package": "desktop-browser-plugin",
41
+ "path": "scripts/browser-client.mjs",
42
+ "bytes": 150611,
43
+ "sha256": "b9b9bc2319d5ee6aa0b1e481d63bb2130d28102fc7c9080803ab5552185d9037"
44
+ },
45
+ {
46
+ "package": "desktop-browser-plugin",
47
+ "path": "docs/accessibility.md",
48
+ "bytes": 4685,
49
+ "sha256": "331ebe1105d1c1aa31e54f1e94ab9644032d6e68371b02459dd31bc44618edcb"
50
+ },
51
+ {
52
+ "package": "desktop-browser-plugin",
53
+ "path": "docs/api-use-behavior.md",
54
+ "bytes": 3344,
55
+ "sha256": "9c648a2cbd94ada50bd855c5e9cb1ff2f33f6a2eff5d450d4b310fde336ec256"
56
+ },
57
+ {
58
+ "package": "desktop-browser-plugin",
59
+ "path": "docs/tab-claiming-chrome.md",
60
+ "bytes": 2071,
61
+ "sha256": "58bdb8d36284eb72e7ed8b1099e83acc01880df4cecebbc8a5c34c7f567fcc33"
62
+ }
63
+ ],
64
+ "findings": [
65
+ {
66
+ "topic": "tab ownership",
67
+ "file": "background.js",
68
+ "symbols": [
69
+ "claimTab",
70
+ "getOwningSessionId",
71
+ "isClaimedBySession",
72
+ "setTabMark"
73
+ ],
74
+ "evidence": "Claims bind tabId to sessionId, turnId and extension instance. Another active session is rejected; handoff/turn marks are tracked."
75
+ },
76
+ {
77
+ "topic": "request lifecycle",
78
+ "file": "background.js",
79
+ "symbols": [
80
+ "startRequest",
81
+ "finishRequest",
82
+ "stopSession",
83
+ "AbortController"
84
+ ],
85
+ "evidence": "Tracks active requests, abort signals, running session state and per-tab cursors."
86
+ },
87
+ {
88
+ "topic": "native transport recovery",
89
+ "file": "background.js",
90
+ "symbols": [
91
+ "requestHost",
92
+ "connectNative",
93
+ "rejectPendingHostRequests",
94
+ "scheduleReconnect"
95
+ ],
96
+ "evidence": "JSON-RPC request IDs, pending deadlines and explicit rejection on native-port loss; reconnect status is exposed."
97
+ },
98
+ {
99
+ "topic": "heartbeat cleanup",
100
+ "file": "background.js",
101
+ "symbols": [
102
+ "client-heartbeat-alarm",
103
+ "stopActiveSessions"
104
+ ],
105
+ "evidence": "Alarm periodically checks clients and stops sessions/ detaches debuggers if none remain connected."
106
+ },
107
+ {
108
+ "topic": "context and prompts",
109
+ "file": "docs/accessibility.md / docs/api-use-behavior.md",
110
+ "symbols": [
111
+ "AX state",
112
+ "diff",
113
+ "current indices",
114
+ "authoritative signal"
115
+ ],
116
+ "evidence": "Accessibility-first workflow, short known action groups, fresh indices, cheapest relevant observation and one meaningful success signal."
117
+ },
118
+ {
119
+ "topic": "native internal branches differ",
120
+ "file": "scripts/browser-service.mjs",
121
+ "symbols": [
122
+ "clickLocator",
123
+ "performPointerAction",
124
+ "resolvePointerActionTarget",
125
+ "focusLocator",
126
+ "readCheckedState"
127
+ ],
128
+ "evidence": "Published service contains separate locator and accessibility paths. Do not assume those routes have identical control/event/occlusion semantics; main results use live CUA AX traces."
129
+ }
130
+ ]
131
+ }
@@ -0,0 +1,69 @@
1
+ # Codex 原生同题测试协议
2
+
3
+ 日期:2026-09-09。已完成原生 AX 单步、AX 批量、原生 Playwright 各 5 次,同题全部成功。
4
+ 原始数据见 `codex-native.json`,连接状态与历史诊断见 `codex-native-status.json`。
5
+ 本文件保留协议与后续复跑约束;实际执行的范围及偏差见下文。
6
+
7
+ ## 本次执行范围
8
+
9
+ 先进行交互式流程探索,再做确定性回放。正式回放中的“单步观察”表示每个动作后读取状态,
10
+ 不表示每步都有一次新的模型决策。AX 两种路径成对交替,各 5 次;原生 Playwright 后续单独跑 5 次。
11
+ 开始每题都重载页面并重新取得 AX 索引。表单任务的业务等待由 AX 自带的状态稳定等待或
12
+ Playwright 的结果可见等待满足,没有插入额外固定 sleep。
13
+
14
+ 原生视口 1466 × 925;Relay 已补充同视口的可见 Chromium 对照。原生 Chrome 版本读取被
15
+ URL 策略阻止,记为 unknown,没有绕过。两个浏览器配置与后端不完全相同。
16
+ 探索轮的经过时间与回放轮的内层工具时间分开保存;计时闭包问题修正后才开始正式回放。
17
+ 15 次所有状态以去重字符串字典保存,每条操作引用对应 stateId,保留逐操作毫秒计时。
18
+
19
+ [CUA 回放代码](codex-native-replay.js)保存了本次执行方法,整理为统一入口。先启动
20
+ `npm run bench:serve`,在 CUA 用 `cua.createBrowserTab('chrome', fixtureUrl, {sessionName:'🧪 原生同题对比'})`
21
+ 得到 `nativeBench`,读取初始状态,再将回放代码放入持久 CUA REPL。它不是普通 Node 脚本,
22
+ 也没有导入替代的浏览器驱动。每一对 AX 流程在一个外层工具调用中执行,原始观察在 `samples` 中保留。
23
+
24
+ ## 连接与安装
25
+
26
+ 1. 通过 CUA 的 Chrome 原生应用窗口访问官方扩展商店,安装 ChatGPT 扩展。
27
+ 2. 按[官方说明](https://learn.chatgpt.com/docs/chrome-extension),桌面应用“设置 → Computer use”中启用对应浏览器,直到显示 Manage。
28
+ 3. 用 `cua.getState()` 确认浏览器可用,再用返回的浏览器 ID 新建专用测试标签。
29
+ 4. 如果 CUA 拒绝操作 Codex 自身设置,由用户完成设置部分。不能通过脚本替代被禁止的操作。
30
+ 5. 原生 `getApp('com.google.Chrome')` 与扩展支持的 Browser Tab 是两条不同通路。即使前者成功,也不能将其结果标为后者的性能。
31
+
32
+ ## 同题与验证
33
+
34
+ 启动 `npm run bench:serve`,使用其打印的本机 URL。服务不进行外部业务操作。
35
+
36
+ 统一任务文本:
37
+
38
+ > 在当前测试页把 Search 填为 invoice,Owner 选为 Alice,勾选 Active only,点击 Search records。等待页面显示 Found: invoice / alice / active,然后停止。使用当前页面状态定位元素。
39
+
40
+ 本次工具比较可使用已知任务,但不能包装成未知网页的模型盲测。若评测模型能力,应另开干净会话,不向模型提供源码、预先写好的定位器或成功轨迹。
41
+
42
+ ## 两种原生流程
43
+
44
+ - 单步观察:初始 AX;每个会影响下一步选择的动作后读取 AX;看到成功状态后结束。
45
+ - 短批处理:初始 AX;在已知目标和状态允许时把动作放在一个 `js` 调用;遇到下拉菜单等新 UI 时先观察并重新取得索引;最后读取 AX 核验结果。
46
+
47
+ 禁止跨 reload 复用 AX 索引。下拉框不能假定支持 `setValue`;应按实际暴露的能力操作。不加没有依据的固定 sleep,也不以 DOM 写入直接伪造业务结果。首轮发现的限制和恢复步骤必须保留,不能只统计成功重试。
48
+
49
+ ## 计时口径
50
+
51
+ | 指标 | 定义 |
52
+ | --- | --- |
53
+ | 准备时间 | 创建浏览器、安装/连接、打开测试页、页面重置;单独记录 |
54
+ | 工具执行时间 | 每次 CUA `js` 内用 `Date.now()` 记录 API 调用开始、结束,累加;含该工具内部状态稳定等待 |
55
+ | 会话经过时间 | 初始观察开始到成功状态返回,包含模型决策及调用间调度;不能称为纯工具延迟 |
56
+ | Agent 工具调用数 | 外层 `js` 调用数量 |
57
+ | 浏览器 API 次数 | click、setValue、pressKey、getAXState 等实际数量;与外层调用数分列 |
58
+ | 观察量 | 每次 AX 状态 UTF-8 字节数之和;截图单独记录编码、尺寸及实际可获取的字节数 |
59
+ | 结果 | 精确成功文本、是否纠错、失败阶段、用户接管、超时;失败样本保留 |
60
+
61
+ 至少交替运行两种流程各 5 次。浏览器版本、模式(headed/headless)、视口、设备缩放、模型和推理档位、网络条件必须记录;无法从环境确认的值写 unknown,不猜测。
62
+
63
+ Relay 已有结果来自 headless Chromium;实际 Chrome 原生通常是 headed,两者环境并不完全一致。公平的性能结论需要补跑相同可见浏览器条件下的 Relay 对照。原生接口没有暴露的 CDP 命令数量记 null,不能写 0,也不能拿 Relay 逻辑命令数与原生外层工具次数比较。
64
+
65
+ 不能给整个 Codex 原生链路套用“Relay→扩展 50 ms”数字:这是 Relay 专属注入点。原生网络链路没有等效注入时,只报告原生默认环境数据。
66
+
67
+ ## 报告要求
68
+
69
+ 保留每次原始观察与计时记录,只归档该测试页内容;不保存用户其他标签或账号信息。分别报告工具时间、会话经过时间、调用数、字节数、成功/失败情况。用中位数和范围呈现小样本,不外推综合模型成功率、token 成本或生产网站胜率。
@@ -0,0 +1,116 @@
1
+ // Paste into mcp__cua_repl after creating the fixture tab as `nativeBench`.
2
+ // This is a CUA REPL snippet, not a standalone Node/Playwright runner.
3
+ // State is kept in one object: do not rebind a log captured by an older cell.
4
+ var nativeReplay = { samples: [], current: null };
5
+
6
+ nativeReplay.measure = async function (operation, fn) {
7
+ const start = Date.now();
8
+ try {
9
+ const result = await fn();
10
+ nativeReplay.current.steps.push({
11
+ operation,
12
+ elapsedMs: Date.now() - start,
13
+ success: true,
14
+ ...(typeof result === "string"
15
+ ? { state: result, stateBytes: new TextEncoder().encode(result).length }
16
+ : {}),
17
+ });
18
+ return result;
19
+ } catch (error) {
20
+ nativeReplay.current.steps.push({
21
+ operation, elapsedMs: Date.now() - start, success: false, error: String(error),
22
+ });
23
+ throw error;
24
+ }
25
+ };
26
+
27
+ nativeReplay.observe = async function (full = false) {
28
+ const state = await nativeReplay.measure("getAXState", () =>
29
+ nativeBench.getAXState({ emit: false, ...(full ? { disableDiffing: true } : {}) }),
30
+ );
31
+ const current = nativeReplay.current;
32
+ if (!state.includes("The following is a diff")) current.nodes = new Map();
33
+ for (const removed of state.matchAll(/Removed element IDs:\s*([^\n]+)/g)) {
34
+ for (const part of removed[1].split(",")) {
35
+ const [from, to = from] = part.trim().split(/[-–]/).map(Number);
36
+ for (const id of current.nodes.keys())
37
+ if (id >= from && id <= to) current.nodes.delete(id);
38
+ }
39
+ }
40
+ for (const line of state.split("\n")) {
41
+ const match = line.replace(/^[~+]\s*/, "").match(/^\s*(\d+)\s+(.+)$/);
42
+ if (match) current.nodes.set(Number(match[1]), match[2]);
43
+ }
44
+ return state;
45
+ };
46
+
47
+ nativeReplay.ref = function (pattern) {
48
+ const matches = [...nativeReplay.current.nodes].filter(([, text]) => pattern.test(text));
49
+ if (matches.length !== 1) throw new Error(`Expected one current AX target: ${pattern}`);
50
+ return matches[0][0];
51
+ };
52
+
53
+ nativeReplay.run = async function (variant, iteration, invocation) {
54
+ if (!["ax-single", "ax-batch", "native-playwright"].includes(variant))
55
+ throw new Error("Unknown variant");
56
+ await nativeBench.reload(); // Intentionally outside measured workflow.
57
+ nativeReplay.current = { steps: [], nodes: new Map() };
58
+ const start = Date.now();
59
+ let success = false, error;
60
+ try {
61
+ let finalState;
62
+ if (variant === "native-playwright") {
63
+ const pw = nativeBench.playwright;
64
+ await nativeReplay.measure("domSnapshot", () => pw.domSnapshot());
65
+ await nativeReplay.measure("fill(Search)", () =>
66
+ pw.getByRole("textbox", { name: "Search", exact: true }).fill("invoice"));
67
+ await nativeReplay.measure("selectOption(Owner)", () =>
68
+ pw.getByRole("combobox", { name: "Owner" }).selectOption({ label: "Alice" }));
69
+ await nativeReplay.measure("check(Active)", () =>
70
+ pw.getByRole("checkbox", { name: "Active only" }).check());
71
+ await nativeReplay.measure("click(Search records)", () =>
72
+ pw.getByRole("button", { name: "Search records" }).click());
73
+ await nativeReplay.measure("waitFor(result)", () =>
74
+ pw.getByText("Found: invoice / alice / active", { exact: true })
75
+ .waitFor({ state: "visible", timeoutMs: 5000 }));
76
+ finalState = await nativeReplay.measure("domSnapshot", () => pw.domSnapshot());
77
+ } else {
78
+ await nativeReplay.observe(true);
79
+ await nativeReplay.measure("setValue(Search)", () =>
80
+ nativeBench.setValue(nativeReplay.ref(/^text field .*Search/), "invoice"));
81
+ if (variant === "ax-single") await nativeReplay.observe();
82
+ await nativeReplay.measure("setValue(Owner)", () =>
83
+ nativeBench.setValue(nativeReplay.ref(/^pop up button .*Owner/), "Alice"));
84
+ if (variant === "ax-single") await nativeReplay.observe();
85
+ await nativeReplay.measure("setValue(Active)", () =>
86
+ nativeBench.setValue(nativeReplay.ref(/^checkbox .*Active only/), "1"));
87
+ if (variant === "ax-single") await nativeReplay.observe();
88
+ await nativeReplay.measure("click(Search records)", () =>
89
+ nativeBench.click(nativeReplay.ref(/^button Search records$/)));
90
+ finalState = await nativeReplay.observe();
91
+ }
92
+ success = finalState.includes("Found: invoice / alice / active");
93
+ if (!success) throw new Error("Expected success text not visible");
94
+ } catch (failure) {
95
+ error = String(failure);
96
+ }
97
+ const steps = nativeReplay.current.steps;
98
+ const sample = {
99
+ variant, iteration, invocation, success, error,
100
+ scriptElapsedMs: Date.now() - start,
101
+ toolApiMs: steps.reduce((sum, step) => sum + step.elapsedMs, 0),
102
+ stateBytes: steps.reduce((sum, step) => sum + (step.stateBytes || 0), 0),
103
+ apiCalls: steps.length,
104
+ observations: steps.filter((step) => step.state != null).length,
105
+ steps,
106
+ };
107
+ nativeReplay.samples.push(sample);
108
+ return { ...sample, steps: undefined };
109
+ };
110
+
111
+ // Run each AX pair in its own CUA call, changing i from 0 through 4:
112
+ // const order = i % 2 ? ["ax-batch", "ax-single"] : ["ax-single", "ax-batch"];
113
+ // for (const variant of order)
114
+ // nodeRepl.write(await nativeReplay.run(variant, i, `pair-${i}`));
115
+ // Then run native-playwright iterations 0–4, retaining invocation IDs.
116
+ // Read raw observations from nativeReplay.samples; export with nodeRepl.write.