@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,163 @@
1
+ # Legacy HTTP API
2
+
3
+ Use these endpoints for compatibility with older clients. For new multi-step work, use the runtime and action API.
4
+
5
+ ## HTTP API Reference
6
+
7
+ The HTTP API below is for code, tests, custom tools, and low-level debugging.
8
+ For interactive agent work, prefer the CLI workflow above.
9
+
10
+ Errors are structured across HTTP, CLI `--json`, and MCP tool errors:
11
+
12
+ ```
13
+ { "ok": false, "code": "invalid_request", "error": "url is required", "message": "url is required", "status": 400, "retryable": false }
14
+ ```
15
+
16
+ Agents should branch on `code` rather than matching localized/free-form error
17
+ text. `retryable: true` means a reconnect/retry is reasonable.
18
+
19
+ ### 1. browser_tabs
20
+ List all attached browser tabs.
21
+ ```
22
+ GET http://127.0.0.1:18795/api/tabs
23
+ ```
24
+ Returns: `{ ok: true, tabs: [{ id, title, url }] }`
25
+
26
+ ### 2. browser_navigate
27
+ Navigate a tab to a URL.
28
+ ```
29
+ POST http://127.0.0.1:18795/api/navigate
30
+ Header: Content-Type: application/json
31
+ Body: { "url": "https://example.com", "tabId?": "optional-tab-id" }
32
+ ```
33
+
34
+ ### 2b. browser_console
35
+ Read captured console, page error, and browser log entries.
36
+ ```
37
+ GET http://127.0.0.1:18795/api/console?tabId=<id>&limit=100&level=error&clear=false
38
+ POST http://127.0.0.1:18795/api/console/clear
39
+ Body: { "tabId?": "...", "level?": "error" }
40
+ ```
41
+ Use this after actions that may trigger frontend errors or warnings.
42
+
43
+ ### 2c. browser_network
44
+ Read captured request/response/finished/failed network events. Sensitive
45
+ headers such as `Authorization`, `Cookie`, `Proxy-Authorization`, and
46
+ `Set-Cookie` are redacted; request bodies are not captured.
47
+ ```
48
+ GET http://127.0.0.1:18795/api/network?tabId=<id>&type=response&status=500&limit=100&clear=false
49
+ POST http://127.0.0.1:18795/api/network/clear
50
+ Body: { "tabId?": "...", "type?": "request|response|finished|failed", "method?": "GET", "status?": 500, "requestId?": "...", "url?": "substring" }
51
+ ```
52
+ Use this after actions that fail silently, after form submits, or when console
53
+ errors imply an API request failed.
54
+
55
+ ### 3. browser_snapshot
56
+ Get a text representation of the current page (interactive elements annotated).
57
+ ```
58
+ GET http://127.0.0.1:18795/api/snapshot?tabId=<id>&format=text&maxLength=100000
59
+ ```
60
+ Format can be `"text"` (annotated DOM) or `"html"` (raw HTML).
61
+
62
+ ### 3b. browser_wait
63
+ Wait for a CSS selector to be attached to the DOM or become visible. Prefer
64
+ this over fixed sleeps after navigation or actions.
65
+ ```
66
+ POST http://127.0.0.1:18795/api/wait
67
+ Body: {
68
+ "selector": "button.submit",
69
+ "state?": "attached|visible",
70
+ "timeoutMs?": 5000,
71
+ "pollMs?": 100,
72
+ "tabId?": "..."
73
+ }
74
+ ```
75
+ `state` defaults to `visible`. `timeoutMs` accepts 1–20000 and `pollMs`
76
+ accepts 50–1000. A timeout returns `code: "wait_timeout"` with
77
+ `retryable: true`; a tab closing or the extension disconnecting fails
78
+ immediately instead of waiting for the timeout.
79
+
80
+ ### 4. browser_click
81
+ Click an element by CSS selector. Scrolls into view first, uses real mouse events.
82
+ ```
83
+ POST http://127.0.0.1:18795/api/click
84
+ Body: { "selector": "button.submit", "tabId?": "...", "doubleClick?": false }
85
+ ```
86
+
87
+ ### 5. browser_type
88
+ Type text into an input field. Optionally clear and submit.
89
+ ```
90
+ POST http://127.0.0.1:18795/api/type
91
+ Body: {
92
+ "text": "hello world",
93
+ "selector?": "input[name='q']",
94
+ "clear?": true,
95
+ "submit?": true,
96
+ "tabId?": "..."
97
+ }
98
+ ```
99
+
100
+ ### 6. browser_scroll
101
+ Scroll the page.
102
+ ```
103
+ POST http://127.0.0.1:18795/api/scroll
104
+ Body: { "direction": "down|up|top|bottom", "amount?": 800, "tabId?": "..." }
105
+ ```
106
+
107
+ ### 7. browser_key
108
+ Press a key or keyboard shortcut using real keyboard events.
109
+ ```
110
+ POST http://127.0.0.1:18795/api/key
111
+ Body: { "key?": "Enter", "combo?": "Control+L", "tabId?": "..." }
112
+ ```
113
+
114
+ Use `combo` for shortcuts (`Control+L`, `Meta+K`, `Shift+Tab`) and `key`
115
+ for single keys (`Enter`, `Escape`, `ArrowDown`, `a`).
116
+
117
+ ### 8. browser_screenshot
118
+ Capture a PNG screenshot (base64).
119
+ ```
120
+ POST/GET http://127.0.0.1:18795/api/screenshot?tabId=<id>&fullPage=true
121
+ ```
122
+ Full-page captures use layout metrics plus a clipped screenshot when possible,
123
+ then fall back to Chrome's `captureBeyondViewport` path. Returns:
124
+ `{ ok: true, data: "base64...", format: "png", fullPage, strategy, width?, height?, bytes }`
125
+
126
+ ### 9. browser_eval
127
+ Evaluate arbitrary JavaScript in the page. The escape hatch.
128
+ ```
129
+ POST http://127.0.0.1:18795/api/eval
130
+ Body: { "expression": "document.querySelector('h1').innerText", "tabId?": "..." }
131
+ ```
132
+
133
+ ### 10. browser_download
134
+ Get the URL of an image/media/link element.
135
+ ```
136
+ POST http://127.0.0.1:18795/api/download
137
+ Body: { "selector": "img.profile-pic", "tabId?": "..." }
138
+ ```
139
+
140
+ ### 10. browser_download_start
141
+ Start a real Chrome download from a URL using the user's browser profile.
142
+ ```
143
+ POST http://127.0.0.1:18795/api/download/start
144
+ Body: {
145
+ "url": "https://example.com/file.pdf",
146
+ "filename?": "files/file.pdf",
147
+ "saveAs?": false,
148
+ "conflictAction?": "uniquify|overwrite|prompt"
149
+ }
150
+ ```
151
+ Returns: `{ ok: true, downloadId, id, options }`
152
+
153
+ ### 11. browser_downloads
154
+ List Chrome downloads plus recent Browser Relay download events.
155
+ ```
156
+ GET http://127.0.0.1:18795/api/downloads?limit=20&state=complete
157
+ POST http://127.0.0.1:18795/api/downloads/clear
158
+ ```
159
+ Use this after `browser_download_start` to verify completion or diagnose interruptions.
160
+
161
+ Real downloads require the extension's `downloads` permission. If Browser Relay
162
+ was already loaded in Chrome before this capability was installed, reload the
163
+ unpacked extension in `chrome://extensions`.
@@ -0,0 +1,240 @@
1
+ # JavaScript runtime and browser actions
2
+
3
+ Use MCP `browser_exec({code, sessionId?, timeoutMs?})`. Each named session retains
4
+ top-level variables, `await`, and browser handles. Default timeout is 30 seconds,
5
+ maximum 120 seconds; a timeout kills that runtime and requests cancellation of
6
+ its pending browser tasks. `browser_exec_reset` discards one named session.
7
+ This is trusted local execution, not an OS security sandbox or page `eval`.
8
+
9
+ ```js
10
+ var tabs = await browser.tabs();
11
+ print(tabs);
12
+ // Select the actual returned id for the task's URL/title.
13
+ var tab = browser.tab(tabs.find(t => t.url === 'https://example.com/app').id);
14
+ print(await tab.snapshot({diff:false}));
15
+ ```
16
+
17
+ Keep the same variable bindings in later calls. All snippets must use targets
18
+ observed in the actual page; names below only illustrate the interface.
19
+
20
+ ```js
21
+ var result = await tab.act([
22
+ {type:'fill', target:{role:'textbox',name:'Search'}, text:'invoice'},
23
+ {type:'select', target:{role:'combobox',name:'Owner'}, value:'alice'},
24
+ {type:'check', target:{role:'checkbox',name:'Active only'}, checked:true},
25
+ {type:'click', target:{role:'button',name:'Search records'}},
26
+ {type:'wait', target:{selector:'[data-ready]'}, state:'visible'}
27
+ ]);
28
+ print(result.observation.snapshot);
29
+ ```
30
+
31
+ ## SDK
32
+
33
+ Application code can use `import {createBrowser} from '@xnng/browser-relay/sdk'`.
34
+ `createBrowser()` uses `BROWSER_RELAY_URL` or the remote device/host environment
35
+ variables. Inject `request(method,path,body,{signal,timeoutMs}?)` for another transport;
36
+ preserve abort handling when implementing a custom transport.
37
+
38
+ | API | Result / purpose |
39
+ | --- | --- |
40
+ | `browser.sessionId` / `.claims()` / `.heartbeat()` / `.dispose()` | Session identity, tab owners, lease renewal and explicit stop (tabs remain open) |
41
+ | `browser.tabs()` | Current `{id,title,url}` entries |
42
+ | `browser.tab(id)` | Handle for an existing discovered tab |
43
+ | `browser.open(url)` | New background tab handle |
44
+ | `browser.capabilities()` | Negotiated executor capabilities |
45
+ | `tab.read({target?,cursor?,maxLength?,diff?})` | Main content, complete links and text; nextCursor continues the captured observation |
46
+ | `tab.observe({mode:'snapshot'|'read'|'both',target?,cursor?})` | Full controls or combined text/image; both returns screenshot separately |
47
+ | `tab.claim({label?})` / `.release()` / `.handoff(toSessionId)` / `.focus()` | Ownership, explicit transfer and foreground |
48
+ | `tab.snapshot({diff?,includeNodes?,maxLength?})` | AX text, URL, title, frames, viewport; default diff=true |
49
+ | `tab.screenshot({fullPage?,clip?})` | PNG base64, dimensions, screenshotId, imageToViewport |
50
+ | `tab.act(actions,{observe?,async?,timeoutMs?,diff?,signal?,requestTimeoutMs?})` | Task with partial results, final observation and request cancellation |
51
+ | `tab.ref(ref)` / `tab.locator(css,{frameId?})` / `tab.getByRole(role,{name,frameId?,exact?})` | Locator handle |
52
+ | Locator `.click()` / `.doubleClick()` / `.fill(text)` / `.type(text)` | Action + resulting snapshot |
53
+ | Locator `.select(value)` / `.check(bool)` / `.hover()` / `.waitFor({state,timeoutMs})` | Action + resulting snapshot |
54
+ | Locator `.read({maxLength?,cursor?})` | Read only the observed locator subtree |
55
+ | Locator `.getByRole(role,{name,exact?})` | Descendant semantic locator scoped to this parent |
56
+ | `tab.clickAt(x,y,{screenshotId?,allowFocus?})` | Visual click |
57
+ | `tab.drag({x,y},{x,y},{screenshotId?,allowFocus?})` | Press, movement path, release |
58
+ | `tab.key('Control+A')` / `tab.scroll(deltaY,{x,y})` | Keyboard / wheel |
59
+ | `tab.goto(url,{waitUntil?,waitFor?,timeoutMs?})` / `tab.close()` | Navigation to document readiness (or commit), optional target wait / close |
60
+ | `tab.eval(expression)` | Page-context JavaScript; no access to SDK objects |
61
+ | `browser.tasks.get(id)` / `.wait(id)` / `.cancel(id)` | Inspect / await / cancel a job |
62
+
63
+ For efficiency, prefer `act` over multiple locator calls when several operations
64
+ are already determined. An action group is serialized as a whole at the
65
+ extension, so remote links do not carry each low-level CDP command separately.
66
+ `observe:'read'` returns main content and `observe:'both'` also returns a screenshot. `observe:'none'` suppresses the final snapshot only when another reliable check
67
+ is supplied by the workflow. `observe:'screenshot'` returns visual evidence.
68
+
69
+ ## Action schema
70
+
71
+ Each action has `type`. A target is `{ref}`, `{selector,frameId?}`, or
72
+ `{role,name?,exact?,frameId?,scope?}`. Matching must be unique. Defaults are exact names.
73
+ `scope` is another observed target identifying an ancestor in the same document;
74
+ nesting is bounded. A snapshot's `within` points to its named container ref;
75
+ `includeNodes` also supplies `parentRef`. For example:
76
+
77
+ ```js
78
+ await tab.getByRole('group',{name:'South'})
79
+ .getByRole('button',{name:'Save'}).click({timeoutMs:1500});
80
+ // Equivalent batch target:
81
+ // {role:'button',name:'Save',scope:{role:'group',name:'South'}}
82
+ ```
83
+
84
+ Complete links are returned as `url`. Text snapshots retain semantic hierarchy. `truncated:true` supplies nextCursor; continue it with the same tab/session. Cursors are bounded cached observations and expire on navigation or eviction; `stale_observation` requires a new read. Large individual text nodes may span pages.
85
+
86
+ Cross-origin iframe and shadow DOM refs are resolved by Chrome's AX/CDP APIs.
87
+ Unavailable frames are listed in snapshot `warnings`, never silently guessed.
88
+ Main-content reading includes frames embedded inside that main in document order.
89
+ Without a top-level main it reads the whole document, even if a child frame has its own main.
90
+
91
+ - `click`, `double_click`, `hover`: `target`, or numeric `x,y`; optional `button`
92
+ (`left`, `middle`, `right`), `screenshotId`, `allowFocus`.
93
+ - `move`: numeric `x,y`, optional screenshotId/allowFocus.
94
+ - `drag`: starting `x,y`, `to:{x,y}` or `path:[{x,y},...]`; optional screenshotId.
95
+ - `fill`, `type`: `text`, optional `target`; fill requires a target. `clear:true`
96
+ selects existing text. `submit:true` presses Enter after typing.
97
+ - `select`: `target`, `value` string or array of option values. Disabled options
98
+ and disabled optgroups are rejected without changing the selection.
99
+ - `check`: `target`, boolean `checked`; native inputs and ARIA checkbox/radio/switch.
100
+ Already-correct state is a no-op. Foreground changes dispatch mouse input and
101
+ verify checked state; background DOM fallback reports `strategy:'dom'` and
102
+ does not imply a trusted mouse event. A radio cannot be directly unchecked.
103
+ - `key`: `key`, e.g. `Enter`, `Escape`, `Control+A`, `Meta+A`, `Shift+Tab`.
104
+ - `scroll`: `target` or numeric `x,y`, plus `deltaX` and/or `deltaY` in pixels. Background scrolling uses DOM scrolling without activating the tab, including when an older caller supplies `allowFocus:true`. Coordinate scrolling selects the scrollable container under the point; for an iframe, use an explicit target with frameId. Results identify strategy:'dom' or 'wheel'. `waitForChange:true` waits up to timeoutMs (default 1500) and reports contentChanged. Hidden pages may defer rendering; no observed change is not permission to focus. This compares rendered text, not unique record identities. viewportMoved reports page scroll movement, not nested element scroll movement.
105
+ - `focus`: Bring the current tab/window to the foreground only when the user explicitly requests it. The same requirement applies to `allowFocus:true` on visual actions.
106
+ - `wait`: `target`, state `attached|visible|hidden|detached|enabled`, timeoutMs 1–20000.
107
+ - `navigate`: HTTP(S) `url`, or `about:blank`. Default waitUntil:'interactive' waits for document readiness; waitUntil:'commit' only starts navigation. Neither guarantees site-specific async data is loaded. Add a wait action for the actual result.
108
+
109
+ Target actions accept optional `timeoutMs` (1–20000) for readiness checks:
110
+ appearance, visibility, enabled state, occlusion, and pointer target stability.
111
+ Without it, readiness errors fail immediately. This is distinct from the task
112
+ timeout below. Waiting is cancellable; ambiguous targets, invalid selectors, and
113
+ stale refs are not silently retargeted. Once input has been dispatched, it is
114
+ never replayed. Parent iframe hit-testing checks the actual action point.
115
+ Locator `select(value, options)`, `check(checked, options)`, and `hover(options)`
116
+ accept the same action options.
117
+
118
+ A group contains 1–100 operations. Default task timeout is 20 seconds; maximum
119
+ 120 seconds. Use async tasks when execution may exceed the local relay's
120
+ 30-second command transport timeout. Task IDs and runtime bindings are separate
121
+ from tab IDs. Task history is bounded and does not survive extension restart.
122
+
123
+ ```js
124
+ var job = await tab.act([{type:'wait',target:{selector:'.report-ready'},timeoutMs:20000}], {async:true});
125
+ print(job.id);
126
+ // Work on an independent tab, then:
127
+ print(await browser.tasks.wait(job.id));
128
+ ```
129
+
130
+ ## Session ownership and stopping
131
+
132
+ `browser.start(label)` names the task group; `browser.open(url)` automatically groups
133
+ new tabs in the session. `browser.complete()` closes only tracked task-created tabs,
134
+ then ends the session. Release result tabs first to retain them outside the group.
135
+ `browser.claims()` also returns `taskGroups` with session, group, window and tab IDs.
136
+ `browser.dispose()` and runtime exit preserve pages. For multiple CLI scripts use
137
+ `exec --session <same-task-id>` and finish with `session complete --session <id>`.
138
+ Completion remains available after lease expiry/stop; it preserves pages now owned
139
+ by another session or moved out of the task group by the user.
140
+
141
+
142
+ SDK browsers use a unique sessionId by default; createBrowser({sessionId:'research'})
143
+ uses an explicit identity. Modern reading/actions automatically claim the tab.
144
+ Heartbeat interval is 40 seconds and the lease expires after 120 seconds without
145
+ renewal. browser.dispose() stops pending jobs and releases claims; it preserves tabs.
146
+ A stopped, disconnected or expired identity cannot restart implicitly.
147
+
148
+ `tab.handoff(receiver)` requires the current owner and no pending work; it preserves
149
+ whether the tab was task-created. `release()` also requires pending work to finish.
150
+ `close()` refuses busy tabs. Hand off or release before changing to a legacy/CDP
151
+ client; those calls cannot operate on a claimed tab. Leases coordinate trusted
152
+ clients; they are not a security boundary against other local software.
153
+
154
+ CLI `--session` names survive separate invocations until expiry/stop. MCP has a
155
+ per-process default and optional named sessions. Runtime sessions retain browser
156
+ objects; use browser.sessionId when coordinating an explicit handoff.
157
+
158
+ GET /api/tasks/:id takes sessionId in the query; cancellation takes it in the JSON
159
+ body. Failed jobs return completed results plus interruptedAction when an input
160
+ may have partly run. Cancellation does not undo already dispatched input. Runtime
161
+ errors retain structured details and return without waiting for the execution timeout.
162
+
163
+ The built-in transport combines the abort signal with its request timeout. Aborting
164
+ or timing out an in-flight browser task sends a separate cancellation request. The
165
+ error includes taskId/sessionId and, when cancellation reaches the executor, task
166
+ progress. request_cancelled and task request timeouts are not retryable. A failed
167
+ cancellation delivery is reported as cancellationError; query the task before any
168
+ recovery. This cannot undo already dispatched input or a page's own timers/requests.
169
+ MCP notifications/cancelled reports request_cancelled for ordinary and runtime calls.
170
+
171
+ Runtime object output preserves full strings, arrays and nested values. Its total
172
+ output budget is separate from an observation's pagination budget: runtimeOutput
173
+ reports any clipped output with nextCursor and readWith. In the same persistent
174
+ session, execute `readOutput("<returned cursor>")` to read that cached output,
175
+ including any clipped browser-page cursor. This does not read the browser again
176
+ or change its diff baseline. Continue until runtimeOutput is absent, then follow
177
+ any browser nextCursor from the recovered observation. The runtime caches three
178
+ outputs of up to four million characters each; eviction/reset returns stale_output,
179
+ and exceeding the cache returns output_cache_limit. Preserve very large results
180
+ in variables and print selected parts. One-shot CLI exec drains runtime output
181
+ pages before closing; MCP/browser_exec and CLI repl retain their session.
182
+
183
+ ## Images and coordinates
184
+
185
+ ```js
186
+ var shot = await tab.screenshot();
187
+ display(shot);
188
+ // After inspecting the image, use its actual coordinates:
189
+ await tab.clickAt(420,180,{screenshotId:shot.screenshotId});
190
+ ```
191
+
192
+ Image coordinates map to CSS viewport coordinates as
193
+ `x * scaleX + offsetX`, `y * scaleY + offsetY`. The executor validates viewport,
194
+ scroll position and URL before applying a screenshot-based coordinate. Screenshot
195
+ identity cannot detect every DOM animation; capture again if the UI changed.
196
+ `clip` is a document-CSS rectangle `{x,y,width,height}`. Full-page images can be
197
+ larger than the visible viewport; scroll to offscreen elements first.
198
+
199
+ ## CLI / MCP / HTTP
200
+
201
+ CLI `exec --file workflow.js --json` returns mixed text/image content. Use
202
+ `--output screenshot.png` for a script that emits one image to a file. CLI `repl`
203
+ reads one JSON object per line (`{"code":"...","sessionId":"work"}`) and emits
204
+ one JSON result per line. Separate `exec` processes do not share JS variables.
205
+
206
+ Modern HTTP endpoints (also forwarded by the remote hub):
207
+
208
+ | Method | Path | Input |
209
+ | --- | --- | --- |
210
+ | GET | `/api/capabilities` | — |
211
+ | POST | `/api/observe` or `/api/read` | tabId, mode, diff, sessionId, maxLength, includeNodes, target, cursor |
212
+ | GET/POST | `/api/sessions` | List claims and taskGroups / sessionId + action: start, heartbeat, stop or complete; optional label |
213
+ | POST | `/api/tabs/claim`, `/release`, `/handoff`, `/focus` | tabId, sessionId; handoff also toSessionId |
214
+ | POST | `/api/evaluate` | Managed and serialized page JS: tabId, sessionId, expression |
215
+ | POST | `/api/actions` | tabId, actions, observe, async, timeoutMs, sessionId |
216
+ | GET | `/api/tasks/:id` | sessionId query |
217
+ | POST | `/api/tasks/:id/cancel` | sessionId |
218
+ | POST | `/api/tabs/create` | url |
219
+ | POST | `/api/tabs/close` | tabId |
220
+
221
+ MCP exposes browser_read, browser_observe, browser_actions, browser_tab, browser_session, browser_task, browser_exec and
222
+ browser_exec_reset, plus the original tools. Screenshot results are image blocks.
223
+ Both NDJSON MCP stdio and legacy Content-Length framing are accepted.
224
+ There is deliberately no HTTP endpoint for executing local OS JavaScript.
225
+
226
+ ## Playwright interoperability
227
+
228
+ ```js
229
+ const browser = await chromium.connectOverCDP('http://127.0.0.1:18795');
230
+ const context = browser.contexts()[0];
231
+ // Discover/select a page from context.pages(); reuse the user's default context.
232
+ ```
233
+
234
+ Release any tab claimed by a managed session before handing it to this compatibility client.
235
+
236
+ The local `/cdp` bridge gives each client virtual sessions and preserves the
237
+ extension attachment when clients disconnect. Browser profiles, creating
238
+ incognito contexts and window resizing are outside this bridge's contract.
239
+ Native Chrome downloads retain the user's configured download behavior.
240
+ This CDP endpoint is local-only; remote workflows use SDK/HTTP actions.