@opencode/browse 0.0.6-dev.19 → 0.0.6-dev.21
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.
- package/dist/{cli-22fe7etn.mjs → cli-qcdv8aqh.mjs} +85 -84
- package/dist/cli.mjs +14 -9
- package/dist/{helpers-rta4d2as.mjs → helpers-d74qp3s1.mjs} +10 -9
- package/dist/{main-yd50d95g.mjs → main-0m569gdn.mjs} +165 -127
- package/dist/{main-2wrztakb.mjs → main-33kf3cfr.mjs} +12 -4
- package/dist/main-3gthmkzw.mjs +112 -0
- package/dist/{main-xatp12cr.mjs → main-6bjnpchg.mjs} +4 -4
- package/dist/{main-75rdz6bc.mjs → main-7qs0mwvc.mjs} +1 -1
- package/dist/main-ek76h9k1.mjs +4 -0
- package/dist/{main-8a245rm1.mjs → main-qm2keaeb.mjs} +199 -186
- package/dist/{main-knahxbey.mjs → main-ybvatjk0.mjs} +5 -5
- package/dist/{main-90enpn5q.mjs → main-yq58hnwq.mjs} +10 -10
- package/dist/{mcp-p0mcjvq9.mjs → mcp-qmq1yerr.mjs} +38 -39
- package/dist/{relay-tfvn856e.mjs → relay-446cn6cs.mjs} +4073 -3894
- package/dist/{runtime-update-pxbj49tf.mjs → runtime-update-fj4s270v.mjs} +6 -5
- package/dist/{setup-y8pd11b8.mjs → setup-atsf76r6.mjs} +3844 -3132
- package/dist/skill-text-v35020jn.mjs +1007 -0
- package/dist/supervisor-bvnx7xgg.mjs +154 -0
- package/dist/{update-5fx8t344.mjs → update-38wp6pt4.mjs} +4 -2
- package/dist/{version-cjhq7cm9.mjs → version-x7djen82.mjs} +1 -1
- package/package.json +3 -2
- package/skills/browse/SKILL.md +29 -29
- package/dist/main-ts9jjqs8.mjs +0 -4
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import"./main-ek76h9k1.mjs";
|
|
2
|
+
import {
|
|
3
|
+
versionEntry,
|
|
4
|
+
readCurrent,
|
|
5
|
+
npmRuntime
|
|
6
|
+
} from "./main-0m569gdn.mjs";
|
|
7
|
+
|
|
8
|
+
// src/mcp/supervisor.ts
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { createInterface } from "node:readline";
|
|
12
|
+
var directEnvironmentVariable = "OPENCODE_BROWSER_MCP_DIRECT";
|
|
13
|
+
async function runBinaryMcp(args) {
|
|
14
|
+
const runtime = npmRuntime();
|
|
15
|
+
if (!runtime) {
|
|
16
|
+
process.env[directEnvironmentVariable] = "1";
|
|
17
|
+
const { runCli } = await import("./cli-qcdv8aqh.mjs");
|
|
18
|
+
return runCli();
|
|
19
|
+
}
|
|
20
|
+
const { layout } = runtime;
|
|
21
|
+
const REPLAY = "opencode-browser-shim-initialize";
|
|
22
|
+
const relayPort = Number(process.env.OPENCODE_BROWSER_PORT) || 19988;
|
|
23
|
+
const current = () => readCurrent(layout)?.version ?? runtime.version;
|
|
24
|
+
const command = (version) => {
|
|
25
|
+
const entry = versionEntry(layout, version, true);
|
|
26
|
+
return existsSync(entry) ? entry : runtime.current.binary ?? process.execPath;
|
|
27
|
+
};
|
|
28
|
+
process.env.OPENCODE_BROWSER_MCP_SESSION ??= "mcp-" + Math.random().toString(16).slice(2, 10).padEnd(8, "0");
|
|
29
|
+
process.env.OPENCODE_BROWSER_CLIENT_PID = String(process.pid);
|
|
30
|
+
let version = current();
|
|
31
|
+
let child;
|
|
32
|
+
let initialize;
|
|
33
|
+
let initialized;
|
|
34
|
+
let swapping = false;
|
|
35
|
+
let listChanged = false;
|
|
36
|
+
const inflight = new Set;
|
|
37
|
+
const queue = [];
|
|
38
|
+
const toClient = (line) => process.stdout.write(line + `
|
|
39
|
+
`);
|
|
40
|
+
const toChild = (line) => child.stdin.write(line + `
|
|
41
|
+
`);
|
|
42
|
+
const parse = (line) => {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(line);
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const start = (replay) => {
|
|
50
|
+
const next = spawn(command(version), ["mcp", ...args], { stdio: ["pipe", "pipe", "inherit"], env: { ...process.env, [directEnvironmentVariable]: "1" } });
|
|
51
|
+
next.stdin.on("error", () => {});
|
|
52
|
+
child = next;
|
|
53
|
+
next.on("exit", (code, signal) => {
|
|
54
|
+
if (next !== child)
|
|
55
|
+
return;
|
|
56
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
57
|
+
});
|
|
58
|
+
createInterface({ input: next.stdout }).on("line", (line) => {
|
|
59
|
+
if (next !== child)
|
|
60
|
+
return;
|
|
61
|
+
const message = parse(line);
|
|
62
|
+
if (message && message.id === REPLAY) {
|
|
63
|
+
if (initialized)
|
|
64
|
+
toChild(initialized);
|
|
65
|
+
swapping = false;
|
|
66
|
+
if (listChanged)
|
|
67
|
+
toClient(JSON.stringify({ jsonrpc: "2.0", method: "notifications/tools/list_changed" }));
|
|
68
|
+
for (const queued of queue.splice(0))
|
|
69
|
+
fromClient(queued);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (message && message.id !== undefined && message.method === undefined) {
|
|
73
|
+
inflight.delete(message.id);
|
|
74
|
+
if (message.result?.capabilities?.tools)
|
|
75
|
+
listChanged = message.result.capabilities.tools.listChanged === true;
|
|
76
|
+
}
|
|
77
|
+
toClient(line);
|
|
78
|
+
maybeSwap();
|
|
79
|
+
});
|
|
80
|
+
if (replay && initialize) {
|
|
81
|
+
swapping = true;
|
|
82
|
+
toChild(JSON.stringify({ ...initialize, id: REPLAY }));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
let heldSince;
|
|
86
|
+
const moved = () => current() !== version;
|
|
87
|
+
const fromClient = (line) => {
|
|
88
|
+
if (swapping)
|
|
89
|
+
return void queue.push(line);
|
|
90
|
+
if (moved()) {
|
|
91
|
+
heldSince ??= Date.now();
|
|
92
|
+
queue.push(line);
|
|
93
|
+
return void maybeSwap();
|
|
94
|
+
}
|
|
95
|
+
const message = parse(line);
|
|
96
|
+
if (message?.method === "initialize")
|
|
97
|
+
initialize = message;
|
|
98
|
+
if (message?.method === "notifications/initialized")
|
|
99
|
+
initialized = line;
|
|
100
|
+
if (message && message.id !== undefined && message.method !== undefined)
|
|
101
|
+
inflight.add(message.id);
|
|
102
|
+
toChild(line);
|
|
103
|
+
};
|
|
104
|
+
let checking = false;
|
|
105
|
+
let unreachable = 0;
|
|
106
|
+
const relayVersion = () => fetch(`http://127.0.0.1:${relayPort}/version`, { signal: AbortSignal.timeout(2000) }).then((response) => response.ok ? response.json() : undefined).then((body) => body && typeof body.version === "string" ? body.version : undefined, () => {
|
|
107
|
+
return;
|
|
108
|
+
});
|
|
109
|
+
const maybeSwap = async () => {
|
|
110
|
+
if (swapping || checking || inflight.size > 0)
|
|
111
|
+
return;
|
|
112
|
+
const next = current();
|
|
113
|
+
if (next === version) {
|
|
114
|
+
unreachable = 0;
|
|
115
|
+
heldSince = undefined;
|
|
116
|
+
for (const queued of queue.splice(0))
|
|
117
|
+
fromClient(queued);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
checking = true;
|
|
121
|
+
const running = await relayVersion();
|
|
122
|
+
checking = false;
|
|
123
|
+
unreachable = running === undefined ? unreachable + 1 : 0;
|
|
124
|
+
if (running !== next && unreachable < 3) {
|
|
125
|
+
if (heldSince !== undefined && Date.now() - heldSince > 15000) {
|
|
126
|
+
heldSince = undefined;
|
|
127
|
+
for (const line of queue.splice(0)) {
|
|
128
|
+
const message = parse(line);
|
|
129
|
+
if (message && message.id !== undefined && message.method !== undefined)
|
|
130
|
+
inflight.add(message.id);
|
|
131
|
+
toChild(line);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (swapping || inflight.size > 0 || current() !== next)
|
|
137
|
+
return;
|
|
138
|
+
const old = child;
|
|
139
|
+
version = next;
|
|
140
|
+
heldSince = undefined;
|
|
141
|
+
start(true);
|
|
142
|
+
old.stdin.end();
|
|
143
|
+
setTimeout(() => old.kill(), 5000).unref();
|
|
144
|
+
};
|
|
145
|
+
start(false);
|
|
146
|
+
createInterface({ input: process.stdin }).on("line", fromClient).on("close", () => {
|
|
147
|
+
child.stdin.end();
|
|
148
|
+
setTimeout(() => process.exit(0), 5000).unref();
|
|
149
|
+
});
|
|
150
|
+
setInterval(() => void maybeSwap(), 1000).unref();
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
runBinaryMcp
|
|
154
|
+
};
|
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
updateNotice2,
|
|
5
5
|
createUpdateWatcher2,
|
|
6
6
|
runUpdateCommand2
|
|
7
|
-
} from "./main-
|
|
8
|
-
import"./main-
|
|
7
|
+
} from "./main-33kf3cfr.mjs";
|
|
8
|
+
import"./main-0m569gdn.mjs";
|
|
9
|
+
import"./main-ek76h9k1.mjs";
|
|
10
|
+
import"./main-3gthmkzw.mjs";
|
|
9
11
|
export {
|
|
10
12
|
createUpdateWatcher2 as createUpdateWatcher,
|
|
11
13
|
formatUpdateNotice2 as formatUpdateNotice,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencode/browse",
|
|
3
|
-
"version": "0.0.6-dev.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.6-dev.21",
|
|
4
|
+
"description": "browse: connect the extension to opencode, and drive your browser from any agent (CLI, MCP, Playwright).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@clack/core": "1.5.1",
|
|
41
|
+
"@clack/prompts": "1.8.1",
|
|
41
42
|
"@types/bun": "1.4.0",
|
|
42
43
|
"@types/chrome": "0.3.4",
|
|
43
44
|
"@types/node": "26.6.4",
|
package/skills/browse/SKILL.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: browse
|
|
3
|
-
description: Use the user's existing Chromium-family browser through
|
|
3
|
+
description: Use the user's existing Chromium-family browser through browse, with deterministic Playwright. Use when asked to inspect, automate, test, or interact with a visible browser tab; continue an authenticated browser workflow; handle 2FA, passkeys, CAPTCHAs, or payment confirmation; record browser behavior; or capture an authenticated network flow.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# browse
|
|
7
7
|
|
|
8
8
|
The `browse` tools (MCP server `browse`; the same commands as the `opencode-browser`
|
|
9
|
-
CLI) drive the user's browser through the
|
|
10
|
-
|
|
11
|
-
do;
|
|
9
|
+
CLI) drive the user's browser through the browse extension.
|
|
10
|
+
browse is a **driver**, not an agent. The calling agent decides what to
|
|
11
|
+
do; browse runs deterministic Playwright code in the user's visible
|
|
12
12
|
browser.
|
|
13
13
|
|
|
14
14
|
Use one loop throughout: **inspect, act, verify**. Inspect the real page before
|
|
@@ -55,7 +55,7 @@ Completion: one execute returns a page result and a readable session id, or
|
|
|
55
55
|
### 2. Choose The Page Deliberately
|
|
56
56
|
|
|
57
57
|
DevTools-attached tabs
|
|
58
|
-
are excluded from
|
|
58
|
+
are excluded from browse's inventory. `page.title()` reads time out
|
|
59
59
|
after five seconds if the page execution context remains unavailable; the read
|
|
60
60
|
timeout does not close or replace the tab.
|
|
61
61
|
|
|
@@ -119,7 +119,7 @@ exact Playwright accessible name.
|
|
|
119
119
|
`targetUrl` and `targetIndex` select existing attached pages; they never
|
|
120
120
|
navigate. A URL selector must match exactly one page, and URL and index selectors
|
|
121
121
|
cannot be combined. Adoption makes that tab the session default, closes the
|
|
122
|
-
session's previous relay-created page, and is exclusive to one
|
|
122
|
+
session's previous relay-created page, and is exclusive to one browse
|
|
123
123
|
session. Reset or delete releases an adopted user tab without closing it.
|
|
124
124
|
|
|
125
125
|
Adoption binds ownership and exact target identity before initializing automation.
|
|
@@ -187,7 +187,7 @@ not merely evidence that an action was attempted.
|
|
|
187
187
|
### 4. Continue Or Finish Cleanly
|
|
188
188
|
|
|
189
189
|
Named sessions preserve their default page across short-lived CLI and MCP
|
|
190
|
-
processes. They also survive relay restarts:
|
|
190
|
+
processes. They also survive relay restarts: browse restores the id,
|
|
191
191
|
read-only mode, and exact default target. JavaScript `state` and snapshot refs
|
|
192
192
|
are process-local and reset after a relay restart with an explicit warning.
|
|
193
193
|
|
|
@@ -228,7 +228,7 @@ work.
|
|
|
228
228
|
|
|
229
229
|
## Canonical Authenticated Flow
|
|
230
230
|
|
|
231
|
-
The distinguishing
|
|
231
|
+
The distinguishing browse workflow is an authenticated tab plus a
|
|
232
232
|
human-only prompt:
|
|
233
233
|
|
|
234
234
|
Attach and adopt the existing tab, inspect its real UI, fill ordinary fields,
|
|
@@ -237,7 +237,7 @@ After the user completes it, verify the authenticated destination. The same
|
|
|
237
237
|
session can continue after an MCP process or relay restart.
|
|
238
238
|
|
|
239
239
|
When the prompt-triggering action may itself block, put only that action in
|
|
240
|
-
`start`.
|
|
240
|
+
`start`. browse presents and acknowledges WAIT before invoking it:
|
|
241
241
|
|
|
242
242
|
```js
|
|
243
243
|
await handoff("Complete the security-key prompt, then continue", {
|
|
@@ -252,7 +252,7 @@ await page.getByRole("heading", { name: /account|dashboard/i }).waitFor()
|
|
|
252
252
|
return { authenticatedUrl: page.url(), title: await page.title() }
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
After a resolved handoff,
|
|
255
|
+
After a resolved handoff, browse waits through transient destination
|
|
256
256
|
context replacement before returning, so this verification can remain in the
|
|
257
257
|
same execute.
|
|
258
258
|
|
|
@@ -287,7 +287,7 @@ code; a demonstration records actions, not proof that the workflow succeeded.
|
|
|
287
287
|
|
|
288
288
|
### Bot Challenges And Blocks
|
|
289
289
|
|
|
290
|
-
After an execute call navigates or fails,
|
|
290
|
+
After an execute call navigates or fails, browse looks at the session
|
|
291
291
|
page for a bot challenge or block page (Cloudflare, Turnstile, Akamai, DataDome,
|
|
292
292
|
PerimeterX/HUMAN, Imperva, AWS WAF, reCAPTCHA or hCaptcha walls, generic
|
|
293
293
|
"access denied" and "verify you are human" pages) and reports it as
|
|
@@ -399,7 +399,7 @@ diffs include visible page content: inspect for private information before shari
|
|
|
399
399
|
Execute code can use `page`, `context`, `browser`, persistent `state`, the
|
|
400
400
|
`files` helper, compute-only Node modules through `modules` and the aliases
|
|
401
401
|
`path`, `url`, `util`, `crypto`, `buffer`, `zlib`, `events` and `stream`, plus
|
|
402
|
-
the
|
|
402
|
+
the browse helpers documented here. Timers, `URL`, `TextEncoder`,
|
|
403
403
|
`Buffer`, `structuredClone` and `crypto.randomUUID()` are available. Use
|
|
404
404
|
`page.evaluate` for `window`, `document`, storage, and same-origin `fetch`
|
|
405
405
|
with page cookies. `page.evaluate` runs in an isolated world, whose `fetch`
|
|
@@ -503,12 +503,12 @@ return { tools, result }
|
|
|
503
503
|
```
|
|
504
504
|
|
|
505
505
|
Discovery covers all frames. If the same name appears in multiple frames, pass
|
|
506
|
-
the exact reported frame label as `{ frame }`.
|
|
506
|
+
the exact reported frame label as `{ frame }`. browse re-discovers the
|
|
507
507
|
tool immediately before invoking it, so stale registrations fail directly.
|
|
508
508
|
|
|
509
509
|
## Safety
|
|
510
510
|
|
|
511
|
-
|
|
511
|
+
browse blocks CDP commands that would destroy shared browser state,
|
|
512
512
|
including browser close and cookie/cache clearing. Never work around those
|
|
513
513
|
guardrails. Cookies and site storage (IndexedDB, Cache Storage, local storage,
|
|
514
514
|
quota) are limited to the hosts of your session's pages, and navigations to
|
|
@@ -546,7 +546,7 @@ match; hostnames are resolved, and the document's real remote IP is checked, so
|
|
|
546
546
|
a name pointing at a private address is caught too.
|
|
547
547
|
|
|
548
548
|
With an allowlist, `page.goto()` and `context.newPage()` to another host fail
|
|
549
|
-
with "
|
|
549
|
+
with "browse blocked Page.navigate: session ... has a domain
|
|
550
550
|
allowlist". Links, redirects, scripts and form posts that leave the list are
|
|
551
551
|
stopped, or the tab is sent back to `about:blank`; popups that open elsewhere
|
|
552
552
|
are closed. Each one adds a "Domain allowlist:" warning to the execute result.
|
|
@@ -568,7 +568,7 @@ await secrets.type(page.getByLabel("Password"), "example") // or a selector st
|
|
|
568
568
|
await page.getByRole("button", { name: "Sign in" }).click()
|
|
569
569
|
```
|
|
570
570
|
|
|
571
|
-
|
|
571
|
+
browse checks the field's frame URL (from the browser's frame tree,
|
|
572
572
|
not page script) against the secret's allowed origins, and the session's
|
|
573
573
|
allowlist when it has one, right before it inserts the value. It returns
|
|
574
574
|
`{ typed: true, name, origin, kind }`, never the value, and redacts the value
|
|
@@ -582,7 +582,7 @@ elsewhere.
|
|
|
582
582
|
|
|
583
583
|
### Signing in with Deadbolt
|
|
584
584
|
|
|
585
|
-
When the user connected
|
|
585
|
+
When the user connected browse to Deadbolt (`opencode-browser deadbolt
|
|
586
586
|
connect`), you can sign in with the logins they granted to agents, without
|
|
587
587
|
seeing any value. `secrets_list` shows them for the session's allowed sites:
|
|
588
588
|
title, username, `granted`, which `fields` (username, password, totp) and
|
|
@@ -808,7 +808,7 @@ values fail instead of silently clamping. The start result reports the chosen ra
|
|
|
808
808
|
Tab capture can include audio; CDP requires `ffmpeg` and has no audio. Use the
|
|
809
809
|
command's `--help` for format options.
|
|
810
810
|
|
|
811
|
-
Recordings are clean by default: nothing from
|
|
811
|
+
Recordings are clean by default: nothing from browse is drawn in the
|
|
812
812
|
recorded page. The ghost cursor stays off, `screenshotWithLabels` draws its
|
|
813
813
|
labels onto the returned image instead of the page, and a handoff on a tab being
|
|
814
814
|
recorded shows no in-page card: the user gets a system notification and the
|
|
@@ -918,12 +918,12 @@ Common diagnoses:
|
|
|
918
918
|
`page.url()` read can still work; retry the page read after the page settles.
|
|
919
919
|
Ordinary missing-locator timeouts do not receive this diagnostic.
|
|
920
920
|
- `connected:false`: check `extension.pairingState` and follow `extension.nextStep`.
|
|
921
|
-
`unpaired` or `refused` means
|
|
922
|
-
`paired-offline`: reopen the paired browser profile and enable
|
|
923
|
-
`not-seen`: the relay hasn't heard from a browser. If
|
|
921
|
+
`unpaired` or `refused` means browse is installed: re-run install to pair it, **not** the store.
|
|
922
|
+
`paired-offline`: reopen the paired browser profile and enable browse; its worker wakes every 30 seconds.
|
|
923
|
+
`not-seen`: the relay hasn't heard from a browser. If browse is already added, open that browser and
|
|
924
924
|
rerun install. Otherwise follow install's store or Load unpacked steps.
|
|
925
925
|
- Incompatible extension protocol: follow `extension.nextStep`. For an older unpacked extension after setup,
|
|
926
|
-
ask the user: “Reload
|
|
926
|
+
ask the user: “Reload browse on your browser's extensions page (↻).” Running setup again does not reload older workers.
|
|
927
927
|
- Competing browser/profile connections: the active browser is preserved and
|
|
928
928
|
additional connections are rejected. Use one browser/profile per relay;
|
|
929
929
|
repeatedly creating sessions or resetting tabs does not switch browsers.
|
|
@@ -946,7 +946,7 @@ Common diagnoses:
|
|
|
946
946
|
- Extension installed but not paired: re-run `npx @opencode/browse install` to open its pairing link
|
|
947
947
|
(from a checkout, `bun run setup`). To pair manually, run `npx @opencode/browse pair`
|
|
948
948
|
(from the checkout root, `node cli/dist/cli.mjs pair`). This starts the relay. Have the user enter
|
|
949
|
-
the code in
|
|
949
|
+
the code in browse's Options and click Connect. The terminal confirms which browser paired.
|
|
950
950
|
- HTTP 401 from the relay: clients need the relay token from the data root
|
|
951
951
|
(`relay-token`); the CLI and MCP server send it, other CDP clients must add
|
|
952
952
|
`Authorization: Bearer <token>` or `?opencodeBrowserToken=<token>`.
|
|
@@ -954,10 +954,10 @@ Common diagnoses:
|
|
|
954
954
|
and snapshot refs reset. Continue after the warning.
|
|
955
955
|
- Reset/delete after an extension update may wait briefly for target
|
|
956
956
|
re-announcement; if the old relay-owned target is absent from the completed
|
|
957
|
-
inventory,
|
|
957
|
+
inventory, browse forgets the dead identity without closing a
|
|
958
958
|
guessed tab.
|
|
959
|
-
- Repeated execution-context errors: run one short follow-up so
|
|
960
|
-
can health-check the page. A live page is kept:
|
|
959
|
+
- Repeated execution-context errors: run one short follow-up so browse
|
|
960
|
+
can health-check the page. A live page is kept: browse reconnects and
|
|
961
961
|
re-resolves the same tab once, then fails with a `session-page/*-unresponsive`
|
|
962
962
|
diagnosis if the page still does not answer. Blank or unknown URLs are preserved:
|
|
963
963
|
they can contain unsaved content. Only a crashed or
|
|
@@ -967,7 +967,7 @@ Common diagnoses:
|
|
|
967
967
|
stall the main world for automation while rendering normally for the human),
|
|
968
968
|
open a fresh tab with `context.newPage()` or hand the tab to the user.
|
|
969
969
|
- Handoff ends with "page execution context did not become available": the user
|
|
970
|
-
finished; only
|
|
970
|
+
finished; only browse's view of the tab is stale. Run a short
|
|
971
971
|
follow-up execute so the page is re-checked instead of assuming it was lost.
|
|
972
972
|
- Fill timeout on login fields: inspect first, then try `fillInput` after
|
|
973
973
|
confirming the selector or locator resolves. String selectors search open
|
|
@@ -992,7 +992,7 @@ For deeper relay diagnosis, restart with `OPENCODE_BROWSER_DEBUG=1`. Debug trace
|
|
|
992
992
|
must never include expressions, arguments, results, headers, cookies, or form
|
|
993
993
|
values.
|
|
994
994
|
|
|
995
|
-
Whenever
|
|
995
|
+
Whenever browse fails, wedges, replaces a page/session, or behaves
|
|
996
996
|
unexpectedly, create or update a `opencode-browser` project todo with the Browser
|
|
997
997
|
Control version, safe session/page context, exact error, deterministic
|
|
998
998
|
reproduction, expected versus actual behavior, and recovery attempted. Never
|
package/dist/main-ts9jjqs8.mjs
DELETED