pi-agent-browser-native 0.2.19 → 0.2.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/CHANGELOG.md +12 -0
- package/README.md +26 -3
- package/docs/ARCHITECTURE.md +2 -1
- package/docs/COMMAND_REFERENCE.md +62 -10
- package/docs/RELEASE.md +1 -1
- package/docs/TOOL_CONTRACT.md +7 -4
- package/extensions/agent-browser/index.ts +89 -2
- package/extensions/agent-browser/lib/playbook.ts +9 -7
- package/extensions/agent-browser/lib/process.ts +51 -5
- package/extensions/agent-browser/lib/results/envelope.ts +28 -2
- package/extensions/agent-browser/lib/runtime.ts +60 -4
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +27 -1
- package/scripts/doctor.mjs +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.21 - 2026-05-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- fixed the published `pi-agent-browser-doctor` bin entrypoint so it runs when invoked through npm's `.bin` symlink
|
|
9
|
+
|
|
10
|
+
## 0.2.20 - 2026-05-07
|
|
11
|
+
|
|
12
|
+
### Compatibility
|
|
13
|
+
- updated the extension's upstream capability baseline and command reference for `agent-browser` `0.27.0`
|
|
14
|
+
- documented and passed through the new React introspection commands (`react tree`, `react inspect`, `react renders`, `react suspense`), Web Vitals (`vitals`), SPA navigation (`pushstate`), init-script flags (`--init-script`, `--enable react-devtools`), `network route --resource-type`, and `cookies set --curl`
|
|
15
|
+
- treat `--init-script` and `--enable` as launch-scoped flags in managed-session planning so agents get the same clear `sessionMode: "fresh"` recovery path as profile/state/CDP launches
|
|
16
|
+
|
|
5
17
|
## 0.2.19 - 2026-05-03
|
|
6
18
|
|
|
7
19
|
### Fixed
|
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ The native tool exposed to the agent is named `agent_browser`.
|
|
|
120
120
|
The primary session control parameter is `sessionMode`:
|
|
121
121
|
|
|
122
122
|
- `"auto"` (default) reuses the extension-managed `pi`-scoped session when possible
|
|
123
|
-
- `"fresh"` switches that managed session to a fresh upstream launch so launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`,
|
|
123
|
+
- `"fresh"` switches that managed session to a fresh upstream launch so launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, and `--enable` apply and later auto calls follow the new browser
|
|
124
124
|
|
|
125
125
|
## Agent quick start
|
|
126
126
|
|
|
@@ -166,7 +166,7 @@ Download a file from a known link/control directly:
|
|
|
166
166
|
{ "args": ["download", "@e5", "/tmp/report.pdf"] }
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
For dashboards that start an export asynchronously after a click or navigation, click first and then wait for the download. The wrapper reports `Download completed: /tmp/report.csv` and exposes upstream-reported `details.savedFilePath` plus `details.savedFile` for the `wait` result; with upstream `agent-browser 0.
|
|
169
|
+
For dashboards that start an export asynchronously after a click or navigation, click first and then wait for the download. The wrapper reports `Download completed: /tmp/report.csv` and exposes upstream-reported `details.savedFilePath` plus `details.savedFile` for the `wait` result; with upstream `agent-browser 0.27.0`, confirm `details.artifacts[].exists` before relying on a requested `wait --download <path>` file being present on disk (tracked upstream at [vercel-labs/agent-browser#1300](https://github.com/vercel-labs/agent-browser/issues/1300)):
|
|
170
170
|
|
|
171
171
|
```json
|
|
172
172
|
{ "args": ["click", "@export"] }
|
|
@@ -187,6 +187,28 @@ Start a fresh profiled launch after you already used the implicit session:
|
|
|
187
187
|
|
|
188
188
|
After a successful unnamed fresh launch, later `sessionMode: "auto"` calls follow that new browser automatically.
|
|
189
189
|
|
|
190
|
+
React and SPA tooling added upstream in `agent-browser` v0.27.0 is passed through as native tool calls. Launch React introspection with the DevTools hook before first navigation, then use the `react` commands; `vitals` and `pushstate` work as regular command tokens:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{ "args": ["open", "--enable", "react-devtools", "https://example.com"], "sessionMode": "fresh" }
|
|
194
|
+
{ "args": ["react", "tree"] }
|
|
195
|
+
{ "args": ["react", "inspect", "<fiberId>"] }
|
|
196
|
+
{ "args": ["react", "renders", "start"] }
|
|
197
|
+
{ "args": ["react", "renders", "stop"] }
|
|
198
|
+
{ "args": ["react", "suspense", "--only-dynamic"] }
|
|
199
|
+
{ "args": ["vitals", "https://example.com", "--json"] }
|
|
200
|
+
{ "args": ["pushstate", "/dashboard"] }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
For first-navigation setup, launch a fresh blank page before staging routes, cookies, or scripts:
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{ "args": ["open"], "sessionMode": "fresh" }
|
|
207
|
+
{ "args": ["network", "route", "**/*.js", "--abort", "--resource-type", "script"] }
|
|
208
|
+
{ "args": ["cookies", "set", "--curl", "/path/to/cookies.txt", "--domain", "example.com"] }
|
|
209
|
+
{ "args": ["navigate", "https://example.com"] }
|
|
210
|
+
```
|
|
211
|
+
|
|
190
212
|
Name a new upstream session explicitly when you want to keep reusing it yourself:
|
|
191
213
|
|
|
192
214
|
```json
|
|
@@ -262,10 +284,11 @@ These calls return plain text and stay stateless: the extension does not inject
|
|
|
262
284
|
|
|
263
285
|
Current cautions:
|
|
264
286
|
- passing `--profile` is an explicit upstream choice; this extension does not add its own profile-cloning or isolation layer
|
|
265
|
-
- launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`,
|
|
287
|
+
- launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, and `--enable` are for the first command that launches a session; if the implicit session is already active, retry that call with `sessionMode: "fresh"` or provide an explicit `--session ...` for the new launch
|
|
266
288
|
- implicit `piab-*` sessions are extension-managed convenience sessions; they stay alive across `/reload` and resumable session transitions so later default calls can keep following the active managed browser on `/reload` or `/resume`, close when the originating `pi` process quits, rely on the configured idle timeout only as an abnormal-exit backstop, store persisted-session large snapshot spill files under a private session-scoped artifact directory with a bounded per-session budget so `details.fullOutputPath` and metadata-only `details.artifactManifest` survive reload/resume without unbounded growth, and still clean up process-private temp spill artifacts on shutdown
|
|
267
289
|
- `sessionMode: "fresh"` without an explicit `--session` rotates that extension-managed session to the new browser so later auto calls keep using it
|
|
268
290
|
- for local Unix launches, the wrapper uses a short private socket directory under `/tmp` so extension-generated session names do not trip upstream Unix socket-path limits in longer cwd/session-name combinations
|
|
291
|
+
- wrapper-spawned commands clamp `AGENT_BROWSER_DEFAULT_TIMEOUT` to 25 seconds and use a 28-second process watchdog so a single upstream CLI call does not cross the upstream 30-second IPC read-timeout/retry path; split intentionally long waits into shorter tool calls
|
|
269
292
|
- for direct headless local Chrome launches to `chat.com`, `chatgpt.com`, and `chat.openai.com`, the extension injects a normal Chrome user agent when the caller did not explicitly provide `--user-agent`; this keeps the default headless workflow usable without forcing `--headed` or `--auto-connect`
|
|
270
293
|
<!-- agent-browser-playbook:start wrapper-tab-recovery -->
|
|
271
294
|
<!-- Generated from extensions/agent-browser/lib/playbook.ts. Run `npm run docs -- playbook write` to update. -->
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -98,6 +98,7 @@ Practical policy:
|
|
|
98
98
|
- once the wrapper knows which tab the agent is operating on, later active-tab commands may synthesize a tiny upstream `batch` that re-selects that tab and then runs the requested command in the same upstream invocation; this stays thin while avoiding reconnect-time drift on profile-restored sessions
|
|
99
99
|
- after a successful command on a known tab target, the wrapper may best-effort restore that same target again if restored/background tabs steal focus after the command returns
|
|
100
100
|
- for local Unix launches, set a short private socket directory so extension-generated session names do not fail on the upstream Unix socket-path length limit
|
|
101
|
+
- keep wrapper-spawned upstream CLI calls inside the upstream IPC budget by clamping `AGENT_BROWSER_DEFAULT_TIMEOUT` to 25 seconds and stopping a stuck child process before the upstream 30-second read-timeout retry loop begins
|
|
101
102
|
|
|
102
103
|
This is primarily about ownership clarity and avoiding surprise, not adding a heavy safety wrapper. If the extension invented the session, the extension should own its lifecycle without breaking reload/resume semantics. If the caller explicitly chose the upstream session model, the extension should stay out of the way.
|
|
103
104
|
|
|
@@ -106,7 +107,7 @@ This is primarily about ownership clarity and avoiding surprise, not adding a he
|
|
|
106
107
|
`agent-browser` startup flags are sticky once a session is already running.
|
|
107
108
|
The extension should surface that clearly and avoid hidden restart behavior in v1.
|
|
108
109
|
|
|
109
|
-
That means explicit startup-scoping flags like `--profile`, `--session-name`, and `--
|
|
110
|
+
That means explicit startup-scoping flags like `--profile`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, and `--enable` should remain explicit upstream choices instead of being wrapped in extra hidden restart or cloning logic.
|
|
110
111
|
|
|
111
112
|
The wrapper may still apply narrow compatibility normalizations when observed behavior justifies them and the result remains thin, local, and opt-out. For example, if a specific site starts rejecting the default local headless Chrome user agent while the same flow works with a normal Chrome UA, the extension may inject a domain-specific fallback UA only when the caller did not already choose `--user-agent`, `--headed`, `--cdp`, `--auto-connect`, or a provider-backed launch.
|
|
112
113
|
|
|
@@ -16,7 +16,7 @@ This project intentionally blocks normal `agent-browser` bash usage in most agen
|
|
|
16
16
|
|
|
17
17
|
<!-- agent-browser-capability-baseline:start upstream-baseline -->
|
|
18
18
|
<!-- Generated from scripts/agent-browser-capability-baseline.mjs. Run `npm run docs -- command-reference write` to update. Do not edit manually. -->
|
|
19
|
-
This reference is baselined to the locally installed `agent-browser 0.
|
|
19
|
+
This reference is baselined to the locally installed `agent-browser 0.27.0` command/help surface. Upstream `agent-browser` remains the source of truth for command semantics; this file is the local fallback for Pi agent sessions where direct binary help is blocked or discouraged.
|
|
20
20
|
|
|
21
21
|
The lightweight drift check is `npm run verify -- command-reference`. Run it whenever the installed upstream `agent-browser` version changes or this reference is edited.
|
|
22
22
|
<!-- agent-browser-capability-baseline:end upstream-baseline -->
|
|
@@ -37,7 +37,7 @@ Tool parameters:
|
|
|
37
37
|
- `stdin`: only for `batch` and `eval --stdin`; other command/stdin combinations are rejected before `agent-browser` is launched.
|
|
38
38
|
- `sessionMode`:
|
|
39
39
|
- `"auto"` reuses the extension-managed session when possible.
|
|
40
|
-
- `"fresh"` rotates that managed session to a fresh upstream launch so launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`,
|
|
40
|
+
- `"fresh"` rotates that managed session to a fresh upstream launch so launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, or `--enable` apply.
|
|
41
41
|
|
|
42
42
|
## Recommended workflow
|
|
43
43
|
|
|
@@ -52,6 +52,35 @@ Keep routine browser work simple: open a page, inspect it with `snapshot -i`, in
|
|
|
52
52
|
{ "args": ["snapshot", "-i"] }
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### React, SPA, and Web Vitals flows
|
|
56
|
+
|
|
57
|
+
React introspection requires the React DevTools init hook to be installed before the page's first JavaScript runs. Launch or relaunch that browser session with `--enable react-devtools`; if the implicit session is already active, use `sessionMode: "fresh"`.
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "args": ["open", "--enable", "react-devtools", "https://example.com"], "sessionMode": "fresh" }
|
|
61
|
+
{ "args": ["react", "tree"] }
|
|
62
|
+
{ "args": ["react", "inspect", "<fiberId>"] }
|
|
63
|
+
{ "args": ["react", "renders", "start"] }
|
|
64
|
+
{ "args": ["react", "renders", "stop"] }
|
|
65
|
+
{ "args": ["react", "suspense", "--only-dynamic"] }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use `vitals [url]` for Core Web Vitals plus React hydration timing when available, and `pushstate <url>` for client-side SPA navigation without a full reload:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{ "args": ["vitals", "https://example.com", "--json"] }
|
|
72
|
+
{ "args": ["pushstate", "/dashboard?tab=settings"] }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For first-navigation setup, start on `about:blank`, then stage routes, cookies, or init scripts before navigating. The relevant v0.27.0 surfaces are `network route <url> [--abort|--body <json>] [--resource-type <csv>]` and `cookies set --curl <file>`:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{ "args": ["open"], "sessionMode": "fresh" }
|
|
79
|
+
{ "args": ["network", "route", "**/*.js", "--abort", "--resource-type", "script"] }
|
|
80
|
+
{ "args": ["cookies", "set", "--curl", "/path/to/cookies.txt", "--domain", "example.com"] }
|
|
81
|
+
{ "args": ["navigate", "https://example.com"] }
|
|
82
|
+
```
|
|
83
|
+
|
|
55
84
|
### Selector strategy
|
|
56
85
|
|
|
57
86
|
Prefer targets in this order:
|
|
@@ -115,7 +144,7 @@ For one-call flows, put the click and wait in `batch`; the wait step keeps the s
|
|
|
115
144
|
{ "args": ["batch"], "stdin": "[[\"click\",\"@export\"],[\"wait\",\"--download\",\"/tmp/report.csv\"]]" }
|
|
116
145
|
```
|
|
117
146
|
|
|
118
|
-
A successful wait-based download renders a readable summary such as `Download completed: /tmp/report.csv` and exposes top-level `details.savedFilePath` plus `details.savedFile` for non-batch calls. With the current upstream `agent-browser 0.
|
|
147
|
+
A successful wait-based download renders a readable summary such as `Download completed: /tmp/report.csv` and exposes top-level `details.savedFilePath` plus `details.savedFile` for non-batch calls. With the current upstream `agent-browser 0.27.0`, `wait --download <path>` may report the requested path before this environment can verify that the file was persisted there. Treat `details.savedFilePath` as upstream-reported metadata unless `details.artifacts[].exists` is true. Upstream tracking: [vercel-labs/agent-browser#1300](https://github.com/vercel-labs/agent-browser/issues/1300).
|
|
119
148
|
|
|
120
149
|
### Download, screenshot, and PDF files
|
|
121
150
|
|
|
@@ -267,8 +296,8 @@ These calls return plain text and stay stateless: the extension does not inject
|
|
|
267
296
|
| `find <locator> <value> <action> [text]` | Locator types include `role`, `text`, `label`, `placeholder`, `alt`, `title`, `testid`, `first`, `last`, and `nth`. |
|
|
268
297
|
| `mouse <action> [args]` | `move <x> <y>`, `down [btn]`, `up [btn]`, `wheel <dy> [dx]`. |
|
|
269
298
|
| `set <setting> [value]` | `viewport <w> <h>`, `device <name>`, `geo <lat> <lng>`, `offline [on|off]`, `headers <json>`, `credentials <user> <pass>`, `media [dark|light] [reduced-motion]`. |
|
|
270
|
-
| `network <action>` | `route <url> [--abort|--body <json>]`, `unroute [url]`, `requests [--clear] [--filter <pattern>]`, `request <requestId>`, `har <start|stop> [path]`. |
|
|
271
|
-
| `cookies [get|set|clear]` | Manage cookies. `set` supports `--url`, `--domain`, `--path`, `--httpOnly`, `--secure`, `--sameSite`, and `--
|
|
299
|
+
| `network <action>` | `route <url> [--abort|--body <json>] [--resource-type <csv>]`, `unroute [url]`, `requests [--clear] [--filter <pattern>]`, `request <requestId>`, `har <start|stop> [path]`. `--resource-type` filters intercepted requests by CDP resource type, such as `script`, `image`, `font`, `xhr`, or `fetch`. |
|
|
300
|
+
| `cookies [get|set|clear]` | Manage cookies. `set` supports `--url`, `--domain`, `--path`, `--httpOnly`, `--secure`, `--sameSite`, `--expires`, and `--curl <file>` for JSON, cURL, or bare Cookie-header bulk imports. |
|
|
272
301
|
| `storage <local|session>` | Manage web storage. |
|
|
273
302
|
|
|
274
303
|
### Tabs
|
|
@@ -301,13 +330,13 @@ Stable tab ids look like `t1`, `t2`, and `t3`. Optional user labels such as `doc
|
|
|
301
330
|
| Mode | Purpose |
|
|
302
331
|
| --- | --- |
|
|
303
332
|
| `wait <selector>` | Wait for an element to appear. |
|
|
304
|
-
| `wait <ms>` | Wait for a fixed number of milliseconds. |
|
|
333
|
+
| `wait <ms>` | Wait for a fixed number of milliseconds. In the native Pi wrapper, keep each fixed wait at `25000` ms or less and split longer waits into multiple tool calls. |
|
|
305
334
|
| `wait --url <pattern>` | Wait for the URL to match a pattern. |
|
|
306
335
|
| `wait --load <state>` | Wait for load state: `load`, `domcontentloaded`, or `networkidle`. |
|
|
307
336
|
| `wait --fn <expression>` | Wait for a JavaScript expression to become truthy. |
|
|
308
337
|
| `wait --text <text>` | Wait for text to appear on the page. |
|
|
309
338
|
| `wait --download [path]` | Wait for a download started by a previous action and optionally save it to `path`; successful wrapper results include upstream-reported `savedFilePath`/`savedFile`, while `details.artifacts[].exists` is the wrapper's on-disk verification signal. |
|
|
310
|
-
| `wait --download [path] --timeout <ms>` | Set download-start timeout in milliseconds. |
|
|
339
|
+
| `wait --download [path] --timeout <ms>` | Set download-start timeout in milliseconds. In the native Pi wrapper, use `25000` ms or less per call to stay under the upstream CLI IPC budget. |
|
|
311
340
|
| `wait <selector> --state hidden` | Wait for an element to become hidden. |
|
|
312
341
|
| `wait <selector> --state detached` | Wait for an element to detach. |
|
|
313
342
|
|
|
@@ -330,8 +359,16 @@ Stable tab ids look like `t1`, `t2`, and `t3`. Optional user labels such as `doc
|
|
|
330
359
|
| `stream enable [--port <n>]` | Start runtime WebSocket streaming for this session. |
|
|
331
360
|
| `stream disable` | Stop runtime WebSocket streaming. |
|
|
332
361
|
| `stream status` | Show streaming status and active port. |
|
|
362
|
+
| `react tree` | Print the full React component tree. Requires the page to have been launched with `--enable react-devtools`. |
|
|
363
|
+
| `react inspect <id>` | Inspect one React fiber's props, hooks, state, and source. |
|
|
364
|
+
| `react renders start` | Start recording React render activity. |
|
|
365
|
+
| `react renders stop [--json]` | Stop render recording and print mount/re-render counts and changed details. |
|
|
366
|
+
| `react suspense [--only-dynamic] [--json]` | Classify Suspense boundaries with grouped root-cause recommendations. |
|
|
367
|
+
| `vitals [url] [--json]` | Report Core Web Vitals: LCP, CLS, TTFB, FCP, INP, plus React hydration timing when available. |
|
|
368
|
+
| `pushstate <url>` | Perform SPA client-side navigation; detects Next.js router pushes and falls back to history navigation events. |
|
|
369
|
+
| `removeinitscript <id>` | Remove an init script registered through upstream init-script mechanisms. |
|
|
333
370
|
|
|
334
|
-
When these diagnostic commands are invoked through the native `agent_browser` tool, structured console
|
|
371
|
+
When these diagnostic commands are invoked through the native `agent_browser` tool, structured console, page-error, React, Web Vitals, and SPA outputs render as compact summaries when possible, with large outputs previewed and spilled instead of dumped into context. Large outputs are previewed with a `Full output path:` spill file instead of dumping the entire payload into context.
|
|
335
372
|
|
|
336
373
|
`trace` and `profiler` share upstream Chrome tracing machinery. Do not run them at the same time. The wrapper tracks owner state it observes in the current Pi session and blocks conflicting starts/stops with "wrapper believes ..." wording because direct upstream CLI use or browser restarts can desynchronize wrapper-local state.
|
|
337
374
|
|
|
@@ -372,6 +409,8 @@ When these commands are invoked through the native `agent_browser` tool, structu
|
|
|
372
409
|
- `--state <path>`: load saved auth state from JSON. Environment: `AGENT_BROWSER_STATE`.
|
|
373
410
|
- `--auto-connect`: connect to a running Chrome to reuse auth state. Environment: `AGENT_BROWSER_AUTO_CONNECT`.
|
|
374
411
|
- `--headers <json>`: apply HTTP headers scoped to the opened URL's origin.
|
|
412
|
+
- `--init-script <path>`: register a script before first navigation; repeatable. Environment: `AGENT_BROWSER_INIT_SCRIPTS`.
|
|
413
|
+
- `--enable <feature>`: enable built-in init scripts such as `react-devtools`; repeatable or comma-separated. Environment: `AGENT_BROWSER_ENABLE`.
|
|
375
414
|
|
|
376
415
|
### Browser launch and runtime flags
|
|
377
416
|
|
|
@@ -427,7 +466,7 @@ Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGE
|
|
|
427
466
|
## Wrapper-specific behavior worth knowing
|
|
428
467
|
|
|
429
468
|
- The extension may keep following one implicit managed session across later tool calls.
|
|
430
|
-
- If launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`,
|
|
469
|
+
- If launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, or `--enable` would be ignored because that implicit session is already active, retry with `sessionMode: "fresh"`.
|
|
431
470
|
<!-- agent-browser-playbook:start wrapper-tab-recovery -->
|
|
432
471
|
<!-- Generated from extensions/agent-browser/lib/playbook.ts. Run `npm run docs -- playbook write` to update. -->
|
|
433
472
|
- After launch-scoped open/goto/navigate calls that can restore existing tabs (for example --profile, --session-name, or --state), agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch.
|
|
@@ -435,6 +474,7 @@ Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGE
|
|
|
435
474
|
- After a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes.
|
|
436
475
|
- If a known session target unexpectedly reports about:blank, agent_browser preserves the prior intended target, best-effort re-selects it when it still exists, and reports exact recovery guidance when it cannot be re-selected.
|
|
437
476
|
<!-- agent-browser-playbook:end wrapper-tab-recovery -->
|
|
477
|
+
- Wrapper-spawned commands clamp `AGENT_BROWSER_DEFAULT_TIMEOUT` to 25 seconds and use a 28-second child-process watchdog so one upstream CLI call does not cross the upstream 30-second IPC read-timeout/retry path.
|
|
438
478
|
- Oversized snapshots and oversized generic outputs may be compacted in tool content, with the full raw output written to a spill file path shown directly in the tool result. Recent artifact metadata is bounded by `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES` (default 100); persisted spill files are separately bounded by `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES` (default 32 MiB).
|
|
439
479
|
- The wrapper keeps `--help` and `--version` stateless so they do not consume the implicit managed-session slot.
|
|
440
480
|
|
|
@@ -443,7 +483,7 @@ Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGE
|
|
|
443
483
|
<!-- agent-browser-capability-baseline:start capability-token-baseline -->
|
|
444
484
|
<!-- Generated from scripts/agent-browser-capability-baseline.mjs. Run `npm run docs -- command-reference write` to update. Do not edit manually. -->
|
|
445
485
|
<details>
|
|
446
|
-
<summary>Generated verifier capability baseline for agent-browser 0.
|
|
486
|
+
<summary>Generated verifier capability baseline for agent-browser 0.27.0</summary>
|
|
447
487
|
|
|
448
488
|
This generated block is review data for maintainers. The human-authored reference sections above remain the readable command guide.
|
|
449
489
|
|
|
@@ -476,6 +516,18 @@ This generated block is review data for maintainers. The human-authored referenc
|
|
|
476
516
|
- root help: `inspect`
|
|
477
517
|
- root help: `clipboard <op> [text]`
|
|
478
518
|
- root help: `stream enable [--port <n>]`
|
|
519
|
+
- root help: `react tree`
|
|
520
|
+
- root help: `react inspect <id>`
|
|
521
|
+
- root help: `react renders start`
|
|
522
|
+
- root help: `react renders stop [--json]`
|
|
523
|
+
- root help: `react suspense [--only-dynamic] [--json]`
|
|
524
|
+
- root help: `vitals [url] [--json]`
|
|
525
|
+
- root help: `pushstate <url>`
|
|
526
|
+
- root help: `removeinitscript <id>`
|
|
527
|
+
- root help: `--init-script <path>`
|
|
528
|
+
- root help: `--enable <feature>`
|
|
529
|
+
- root help: `--resource-type <csv>`
|
|
530
|
+
- root help: `cookies set --curl <file>`
|
|
479
531
|
- root help: `auth save <name>`
|
|
480
532
|
- root help: `confirm <id>`
|
|
481
533
|
- root help: `deny <id>`
|
package/docs/RELEASE.md
CHANGED
|
@@ -107,7 +107,7 @@ The default `npm test` and `npm run verify` paths use fast deterministic tests a
|
|
|
107
107
|
npm run verify -- real-upstream
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
This suite requires the installed `agent-browser --version` to exactly match `scripts/agent-browser-capability-baseline.mjs`. It serves fixture pages from localhost and validates real runtime output shapes for `--version`, `open`, `eval --stdin`, `snapshot -i`, `batch` stdin, `wait --download` metadata, wrapper artifact existence reporting for the requested wait-download path, and implicit managed-session reuse. The current upstream `wait --download <path>` saveAs persistence limitation is tracked at [vercel-labs/agent-browser#1300](https://github.com/vercel-labs/agent-browser/issues/1300); until it is fixed, release validation must treat `details.savedFilePath` as upstream-reported metadata and use `details.artifacts[].exists` as the filesystem truth. If the suite fails because JSON/detail keys drifted, update the wrapper behavior or refresh `test/fixtures/agent-browser-real-output-shapes.json` together with the presentation work that consumes those shapes.
|
|
110
|
+
This suite requires the installed `agent-browser --version` to exactly match `scripts/agent-browser-capability-baseline.mjs`. It serves fixture pages from localhost and validates real runtime output shapes for `--version`, `open`, `eval --stdin`, `snapshot -i`, `batch` stdin, `wait --download` metadata, wrapper artifact existence reporting for the requested wait-download path, and implicit managed-session reuse. The current upstream `agent-browser 0.27.0` `wait --download <path>` saveAs persistence limitation is tracked at [vercel-labs/agent-browser#1300](https://github.com/vercel-labs/agent-browser/issues/1300); until it is fixed, release validation must treat `details.savedFilePath` as upstream-reported metadata and use `details.artifacts[].exists` as the filesystem truth. If the suite fails because JSON/detail keys drifted, update the wrapper behavior or refresh `test/fixtures/agent-browser-real-output-shapes.json` together with the presentation work that consumes those shapes.
|
|
111
111
|
|
|
112
112
|
Example smoke prompt:
|
|
113
113
|
|
package/docs/TOOL_CONTRACT.md
CHANGED
|
@@ -34,8 +34,10 @@ The tool also needs an operating playbook, not just a capability list. The model
|
|
|
34
34
|
- Do not assume Playwright selector dialects such as text=Close or button:has-text('Close') are supported wrapper syntax unless current upstream agent-browser behavior has been verified.
|
|
35
35
|
- For authenticated or user-specific content like feeds, inboxes, dashboards, and accounts, prefer --profile Default on the first browser call and let the implicit session carry continuity. Use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser.
|
|
36
36
|
- Do not invent fixed explicit session names for routine tasks. Use the implicit session unless you truly need multiple isolated browser sessions in the same conversation.
|
|
37
|
-
- When using --profile, --session-name, --cdp, --state,
|
|
38
|
-
- If you already used the implicit session and now need launch-scoped flags like --profile, --session-name, --cdp, --state,
|
|
37
|
+
- When using --profile, --session-name, --cdp, --state, --auto-connect, --init-script, or --enable, put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.
|
|
38
|
+
- If you already used the implicit session and now need launch-scoped flags like --profile, --session-name, --cdp, --state, --auto-connect, --init-script, or --enable, retry with sessionMode set to fresh or pass an explicit --session for the new launch. After a successful unnamed fresh launch, later auto calls follow that new session.
|
|
39
|
+
- For React introspection, launch the page with --enable react-devtools before first navigation, then use react tree, react inspect <fiberId>, react renders start/stop, or react suspense; use vitals [url] for Core Web Vitals and hydration timing, and pushstate <url> for client-side SPA navigation.
|
|
40
|
+
- For first-navigation setup, use open without a URL plus network route --resource-type <csv>, cookies set --curl <file>, or --init-script/--enable before navigate/opening the target page.
|
|
39
41
|
- If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <tab-id-or-label> / snapshot -i to recover state before retrying different URLs or fallback strategies. Only use wait with an explicit argument like milliseconds, --load <state>, --url <matcher>, --fn <js>, or --text <matcher>.
|
|
40
42
|
- For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.
|
|
41
43
|
- For read-only browsing tasks, prefer extracting the answer from the current snapshot, structured ref labels, or eval --stdin on the current page before navigating away. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.
|
|
@@ -98,7 +100,7 @@ Examples:
|
|
|
98
100
|
Behavior:
|
|
99
101
|
- if `args` already include `--session`, upstream session choice wins
|
|
100
102
|
- `"auto"` prepends the current extension-managed active session when appropriate
|
|
101
|
-
- `"fresh"` rotates that managed session to a fresh upstream launch so startup-scoped flags like `--profile`, `--session-name`, or `--
|
|
103
|
+
- `"fresh"` rotates that managed session to a fresh upstream launch so startup-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, or `--enable` apply and later default calls follow the new browser
|
|
102
104
|
|
|
103
105
|
Recommended use:
|
|
104
106
|
- use `"auto"` for the common browse/snapshot/click flow inside one `pi` session
|
|
@@ -230,8 +232,9 @@ If `agent-browser` is not on `PATH`, fail with a message that:
|
|
|
230
232
|
- If a known session target unexpectedly reports about:blank, agent_browser preserves the prior intended target, best-effort re-selects it when it still exists, and reports exact recovery guidance when it cannot be re-selected.
|
|
231
233
|
<!-- agent-browser-playbook:end wrapper-tab-recovery -->
|
|
232
234
|
- on local Unix launches, set a short private socket directory for wrapper-spawned `agent-browser` processes so extension-generated session names do not fail the upstream Unix socket-path length limit in longer cwd/session-name combinations
|
|
235
|
+
- keep wrapper-spawned commands below the upstream CLI IPC read-timeout budget by clamping `AGENT_BROWSER_DEFAULT_TIMEOUT` to 25 seconds and stopping a stuck child process before the upstream 30-second retry path begins
|
|
233
236
|
- treat successful plain-text inspection commands like `--help` and `--version` as stateless: do not inject the implicit managed session and do not let those calls claim the managed-session slot
|
|
234
|
-
- if startup-scoped flags like `--profile`, `--session-name`, or `--
|
|
237
|
+
- if startup-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, or `--enable` are supplied after the implicit session is already active while `sessionMode` is `"auto"`, return a validation error with a structured recovery hint that recommends `sessionMode: "fresh"`
|
|
235
238
|
- for direct headless local Chrome launches to `chat.com` / `chatgpt.com` / `chat.openai.com`, allow a narrow compatibility fallback that injects a normal Chrome `--user-agent` only when the caller did not explicitly provide one and did not choose `--headed`, `--cdp`, `--auto-connect`, or a provider-backed launch
|
|
236
239
|
|
|
237
240
|
## Non-goals
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
PROJECT_RULE_PROMPT,
|
|
18
18
|
buildToolPromptGuidelines,
|
|
19
19
|
} from "./lib/playbook.js";
|
|
20
|
-
import { runAgentBrowserProcess } from "./lib/process.js";
|
|
20
|
+
import { SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS, runAgentBrowserProcess } from "./lib/process.js";
|
|
21
21
|
import {
|
|
22
22
|
buildToolPresentation,
|
|
23
23
|
getAgentBrowserErrorText,
|
|
@@ -77,7 +77,7 @@ const AGENT_BROWSER_PARAMS = Type.Object({
|
|
|
77
77
|
sessionMode: Type.Optional(
|
|
78
78
|
StringEnum(["auto", "fresh"] as const, {
|
|
79
79
|
description:
|
|
80
|
-
"Session handling mode. `auto` reuses the extension-managed pi-scoped session when possible. `fresh` switches that managed session to a fresh upstream launch so launch-scoped flags like --profile, --session-name, --cdp, --state,
|
|
80
|
+
"Session handling mode. `auto` reuses the extension-managed pi-scoped session when possible. `fresh` switches that managed session to a fresh upstream launch so launch-scoped flags like --profile, --session-name, --cdp, --state, --auto-connect, --init-script, or --enable apply and later auto calls follow the new browser.",
|
|
81
81
|
default: DEFAULT_SESSION_MODE,
|
|
82
82
|
}),
|
|
83
83
|
),
|
|
@@ -452,6 +452,73 @@ async function prepareBatchScreenshotPaths(args: string[], stdin: string | undef
|
|
|
452
452
|
: undefined;
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
function parseMillisecondsToken(token: string | undefined): number | undefined {
|
|
456
|
+
if (token === undefined || !/^\d+$/.test(token)) {
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
const parsed = Number(token);
|
|
460
|
+
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function findWaitTimeoutMs(commandTokens: string[]): { timeoutMs: number; source: string } | undefined {
|
|
464
|
+
if (commandTokens[0] !== "wait") {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
468
|
+
const token = commandTokens[index];
|
|
469
|
+
if (token === "--timeout") {
|
|
470
|
+
const timeoutMs = parseMillisecondsToken(commandTokens[index + 1]);
|
|
471
|
+
return timeoutMs === undefined ? undefined : { source: "wait --timeout", timeoutMs };
|
|
472
|
+
}
|
|
473
|
+
if (token.startsWith("--timeout=")) {
|
|
474
|
+
const timeoutMs = parseMillisecondsToken(token.slice("--timeout=".length));
|
|
475
|
+
return timeoutMs === undefined ? undefined : { source: "wait --timeout", timeoutMs };
|
|
476
|
+
}
|
|
477
|
+
if (!token.startsWith("-")) {
|
|
478
|
+
const timeoutMs = parseMillisecondsToken(token);
|
|
479
|
+
if (timeoutMs !== undefined) {
|
|
480
|
+
return { source: "wait", timeoutMs };
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function buildIpcUnsafeWaitError(source: string, timeoutMs: number, batchStep?: number): string {
|
|
488
|
+
const location = batchStep === undefined ? source : `batch step ${batchStep + 1} (${source})`;
|
|
489
|
+
return `${location} requests ${timeoutMs}ms, but upstream agent-browser CLI calls must stay under its 30s IPC read timeout. Use ${SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS}ms or less per wait, split long waits into multiple tool calls, or use a page-specific shorter condition.`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function validateWaitIpcTimeoutContract(commandTokens: string[], stdin: string | undefined): string | undefined {
|
|
493
|
+
const directWaitTimeout = findWaitTimeoutMs(commandTokens);
|
|
494
|
+
if (directWaitTimeout && directWaitTimeout.timeoutMs > SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS) {
|
|
495
|
+
return buildIpcUnsafeWaitError(directWaitTimeout.source, directWaitTimeout.timeoutMs);
|
|
496
|
+
}
|
|
497
|
+
if (commandTokens[0] !== "batch" || stdin === undefined) {
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
let steps: unknown;
|
|
501
|
+
try {
|
|
502
|
+
steps = JSON.parse(stdin);
|
|
503
|
+
} catch {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
if (!Array.isArray(steps)) {
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
509
|
+
for (let index = 0; index < steps.length; index += 1) {
|
|
510
|
+
const step = steps[index];
|
|
511
|
+
if (!Array.isArray(step) || !step.every((item) => typeof item === "string")) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const waitTimeout = findWaitTimeoutMs(step);
|
|
515
|
+
if (waitTimeout && waitTimeout.timeoutMs > SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS) {
|
|
516
|
+
return buildIpcUnsafeWaitError(waitTimeout.source, waitTimeout.timeoutMs, index);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
|
|
455
522
|
async function prepareAgentBrowserArgs(args: string[], stdin: string | undefined, cwd: string): Promise<PreparedAgentBrowserArgs> {
|
|
456
523
|
const preparedBatch = await prepareBatchScreenshotPaths(args, stdin, cwd);
|
|
457
524
|
if (preparedBatch) {
|
|
@@ -1520,6 +1587,22 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1520
1587
|
isError: true,
|
|
1521
1588
|
};
|
|
1522
1589
|
}
|
|
1590
|
+
const waitIpcTimeoutError = validateWaitIpcTimeoutContract(commandTokens, params.stdin);
|
|
1591
|
+
if (waitIpcTimeoutError) {
|
|
1592
|
+
return {
|
|
1593
|
+
content: [{ type: "text", text: waitIpcTimeoutError }],
|
|
1594
|
+
details: {
|
|
1595
|
+
args: redactedArgs,
|
|
1596
|
+
command: executionPlan.commandInfo.command,
|
|
1597
|
+
compatibilityWorkaround,
|
|
1598
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
1599
|
+
sessionMode,
|
|
1600
|
+
validationError: waitIpcTimeoutError,
|
|
1601
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
1602
|
+
},
|
|
1603
|
+
isError: true,
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1523
1606
|
|
|
1524
1607
|
const priorSessionTabTargetState = executionPlan.sessionName ? sessionTabTargets.get(executionPlan.sessionName) : undefined;
|
|
1525
1608
|
const priorSessionTabTarget = priorSessionTabTargetState?.target;
|
|
@@ -1853,6 +1936,8 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1853
1936
|
plainTextInspection,
|
|
1854
1937
|
spawnError: processResult.spawnError,
|
|
1855
1938
|
stderr: processResult.stderr,
|
|
1939
|
+
timedOut: processResult.timedOut,
|
|
1940
|
+
timeoutMs: processResult.timeoutMs,
|
|
1856
1941
|
wrapperRecoveryHint: buildWrapperRecoveryHint({ pinnedBatchUnwrapMode, sessionTabCorrection }),
|
|
1857
1942
|
});
|
|
1858
1943
|
|
|
@@ -1969,6 +2054,8 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1969
2054
|
? undefined
|
|
1970
2055
|
: redactSensitiveText(processResult.stdout),
|
|
1971
2056
|
summary: redactSensitiveText(presentation.summary),
|
|
2057
|
+
timedOut: processResult.timedOut || undefined,
|
|
2058
|
+
timeoutMs: processResult.timeoutMs,
|
|
1972
2059
|
},
|
|
1973
2060
|
isError: !succeeded,
|
|
1974
2061
|
};
|
|
@@ -14,10 +14,10 @@ export const TOOL_PROMPT_GUIDELINES_PREFIX = [
|
|
|
14
14
|
] as const;
|
|
15
15
|
|
|
16
16
|
export const QUICK_START_GUIDELINES = [
|
|
17
|
-
"Quick start mental model: args are the exact agent-browser CLI args after the binary; stdin is only for batch and eval --stdin, and other command/stdin combinations are rejected before launch; sessionMode=fresh switches the extension-managed pi-scoped session to a fresh upstream launch when you need new --profile, --session-name, --cdp, --state,
|
|
17
|
+
"Quick start mental model: args are the exact agent-browser CLI args after the binary; stdin is only for batch and eval --stdin, and other command/stdin combinations are rejected before launch; sessionMode=fresh switches the extension-managed pi-scoped session to a fresh upstream launch when you need new --profile, --session-name, --cdp, --state, --auto-connect, --init-script, or --enable state.",
|
|
18
18
|
"Common first calls: { args: [\"open\", \"https://example.com\"] } then { args: [\"snapshot\", \"-i\"] }; after navigation, use { args: [\"click\", \"@e2\"] } then { args: [\"snapshot\", \"-i\"] }.",
|
|
19
|
-
"Common advanced calls: { args: [\"batch\"], stdin: \"[[\\\"open\\\",\\\"https://example.com\\\"],[\\\"snapshot\\\",\\\"-i\\\"]]\" }, { args: [\"eval\", \"--stdin\"], stdin: \"document.title\" },
|
|
20
|
-
"High-value command reference: download <selector> <path> saves a file triggered by a click; get title/url/text/html/value/attr/count reads page state; screenshot [path] captures an image; pdf <path> saves a PDF; tab list and tab <tab-id-or-label> inspect or recover the active tab.",
|
|
19
|
+
"Common advanced calls: { args: [\"batch\"], stdin: \"[[\\\"open\\\",\\\"https://example.com\\\"],[\\\"snapshot\\\",\\\"-i\\\"]]\" }, { args: [\"eval\", \"--stdin\"], stdin: \"document.title\" }, { args: [\"--profile\", \"Default\", \"open\", \"https://example.com/account\"], sessionMode: \"fresh\" }, and { args: [\"open\", \"--enable\", \"react-devtools\", \"https://example.com\"], sessionMode: \"fresh\" }.",
|
|
20
|
+
"High-value command reference: download <selector> <path> saves a file triggered by a click; get title/url/text/html/value/attr/count reads page state; screenshot [path] captures an image; pdf <path> saves a PDF; tab list and tab <tab-id-or-label> inspect or recover the active tab; react tree/inspect/renders/suspense introspect React after --enable react-devtools; vitals [url] measures Core Web Vitals; pushstate <url> performs SPA navigation.",
|
|
21
21
|
"For artifact-producing commands, read the visible artifact block for requested path, absolute path, existence, size, type, cwd, and session; details.artifacts contains the same machine-readable metadata. For annotated screenshots inside batch, put --annotate in top-level args (for example { args: [\"--annotate\", \"batch\"], stdin: \"[[\\\"screenshot\\\",\\\"/tmp/page.png\\\"]]\" }) rather than inside the screenshot step.",
|
|
22
22
|
] as const;
|
|
23
23
|
|
|
@@ -30,8 +30,10 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
|
30
30
|
"Do not assume Playwright selector dialects such as text=Close or button:has-text('Close') are supported wrapper syntax unless current upstream agent-browser behavior has been verified.",
|
|
31
31
|
"For authenticated or user-specific content like feeds, inboxes, dashboards, and accounts, prefer --profile Default on the first browser call and let the implicit session carry continuity. Use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser.",
|
|
32
32
|
"Do not invent fixed explicit session names for routine tasks. Use the implicit session unless you truly need multiple isolated browser sessions in the same conversation.",
|
|
33
|
-
"When using --profile, --session-name, --cdp, --state,
|
|
34
|
-
"If you already used the implicit session and now need launch-scoped flags like --profile, --session-name, --cdp, --state,
|
|
33
|
+
"When using --profile, --session-name, --cdp, --state, --auto-connect, --init-script, or --enable, put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.",
|
|
34
|
+
"If you already used the implicit session and now need launch-scoped flags like --profile, --session-name, --cdp, --state, --auto-connect, --init-script, or --enable, retry with sessionMode set to fresh or pass an explicit --session for the new launch. After a successful unnamed fresh launch, later auto calls follow that new session.",
|
|
35
|
+
"For React introspection, launch the page with --enable react-devtools before first navigation, then use react tree, react inspect <fiberId>, react renders start/stop, or react suspense; use vitals [url] for Core Web Vitals and hydration timing, and pushstate <url> for client-side SPA navigation.",
|
|
36
|
+
"For first-navigation setup, use open without a URL plus network route --resource-type <csv>, cookies set --curl <file>, or --init-script/--enable before navigate/opening the target page.",
|
|
35
37
|
"If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <tab-id-or-label> / snapshot -i to recover state before retrying different URLs or fallback strategies. Only use wait with an explicit argument like milliseconds, --load <state>, --url <matcher>, --fn <js>, or --text <matcher>.",
|
|
36
38
|
"For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.",
|
|
37
39
|
"For read-only browsing tasks, prefer extracting the answer from the current snapshot, structured ref labels, or eval --stdin on the current page before navigating away. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.",
|
|
@@ -46,8 +48,8 @@ export const TOOL_PROMPT_GUIDELINES_SUFFIX = [
|
|
|
46
48
|
"Do not fall back to osascript, AppleScript, or generic browser-driving bash commands when agent_browser can do the job.",
|
|
47
49
|
"Pass exact agent-browser CLI arguments in args, excluding the binary name.",
|
|
48
50
|
"Use stdin only for eval --stdin and batch instead of shell heredocs; other command/stdin combinations are rejected before launch.",
|
|
49
|
-
"Let the extension-managed session handle the common path unless you explicitly need a fresh launch for upstream flags like --profile, --session-name, --cdp, --state,
|
|
50
|
-
"Use sessionMode=fresh when switching from an existing implicit session to a new profile/debug launch without inventing a fixed explicit session name; later auto calls will follow that new session.",
|
|
51
|
+
"Let the extension-managed session handle the common path unless you explicitly need a fresh launch for upstream flags like --profile, --session-name, --cdp, --state, --auto-connect, --init-script, or --enable.",
|
|
52
|
+
"Use sessionMode=fresh when switching from an existing implicit session to a new profile/debug/init-script launch without inventing a fixed explicit session name; later auto calls will follow that new session.",
|
|
51
53
|
] as const;
|
|
52
54
|
|
|
53
55
|
export const INSPECTION_TOOL_CALL_EXAMPLES = [
|
|
@@ -17,7 +17,11 @@ const MAX_BUFFERED_STDERR_CHARS = 32_000;
|
|
|
17
17
|
const MAX_BUFFERED_STDOUT_TAIL_CHARS = 32_000;
|
|
18
18
|
const PROCESS_STDOUT_SPILL_FILE_PREFIX = "process-stdout";
|
|
19
19
|
const AGENT_BROWSER_SOCKET_DIR_ENV = "AGENT_BROWSER_SOCKET_DIR";
|
|
20
|
+
const AGENT_BROWSER_DEFAULT_TIMEOUT_ENV = "AGENT_BROWSER_DEFAULT_TIMEOUT";
|
|
21
|
+
const PI_AGENT_BROWSER_PROCESS_TIMEOUT_ENV = "PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS";
|
|
20
22
|
const DEFAULT_AGENT_BROWSER_SOCKET_DIR_PREFIX = "/tmp/piab";
|
|
23
|
+
export const SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS = 25_000;
|
|
24
|
+
const DEFAULT_AGENT_BROWSER_PROCESS_TIMEOUT_MS = 28_000;
|
|
21
25
|
const httpProxyEnvName = "http_proxy";
|
|
22
26
|
const httpsProxyEnvName = "https_proxy";
|
|
23
27
|
const allProxyEnvName = "all_proxy";
|
|
@@ -92,6 +96,8 @@ export interface ProcessRunResult {
|
|
|
92
96
|
stderr: string;
|
|
93
97
|
stdout: string;
|
|
94
98
|
stdoutSpillPath?: string;
|
|
99
|
+
timedOut: boolean;
|
|
100
|
+
timeoutMs?: number;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
function appendTail(text: string, addition: string, maxChars: number): string {
|
|
@@ -99,6 +105,25 @@ function appendTail(text: string, addition: string, maxChars: number): string {
|
|
|
99
105
|
return combined.length <= maxChars ? combined : combined.slice(combined.length - maxChars);
|
|
100
106
|
}
|
|
101
107
|
|
|
108
|
+
function parsePositiveIntegerEnv(value: string | undefined): number | undefined {
|
|
109
|
+
if (value === undefined || !/^\d+$/.test(value.trim())) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const parsed = Number(value.trim());
|
|
113
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function clampUpstreamDefaultTimeout(childEnv: NodeJS.ProcessEnv): void {
|
|
117
|
+
const requestedTimeout = parsePositiveIntegerEnv(childEnv[AGENT_BROWSER_DEFAULT_TIMEOUT_ENV]);
|
|
118
|
+
if (requestedTimeout === undefined || requestedTimeout > SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS) {
|
|
119
|
+
childEnv[AGENT_BROWSER_DEFAULT_TIMEOUT_ENV] = String(SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function getAgentBrowserProcessTimeoutMs(env: NodeJS.ProcessEnv = processEnv): number {
|
|
124
|
+
return parsePositiveIntegerEnv(env[PI_AGENT_BROWSER_PROCESS_TIMEOUT_ENV]) ?? DEFAULT_AGENT_BROWSER_PROCESS_TIMEOUT_MS;
|
|
125
|
+
}
|
|
126
|
+
|
|
102
127
|
export function getAgentBrowserSocketDir(
|
|
103
128
|
platform: NodeJS.Platform = processPlatform,
|
|
104
129
|
uid: number | undefined = typeof process.getuid === "function" ? process.getuid() : undefined,
|
|
@@ -134,6 +159,7 @@ export function buildAgentBrowserProcessEnv(
|
|
|
134
159
|
}
|
|
135
160
|
|
|
136
161
|
if (!overrides) {
|
|
162
|
+
clampUpstreamDefaultTimeout(childEnv);
|
|
137
163
|
return childEnv;
|
|
138
164
|
}
|
|
139
165
|
|
|
@@ -144,6 +170,7 @@ export function buildAgentBrowserProcessEnv(
|
|
|
144
170
|
childEnv[name] = value;
|
|
145
171
|
}
|
|
146
172
|
}
|
|
173
|
+
clampUpstreamDefaultTimeout(childEnv);
|
|
147
174
|
return childEnv;
|
|
148
175
|
}
|
|
149
176
|
|
|
@@ -153,8 +180,10 @@ export async function runAgentBrowserProcess(options: {
|
|
|
153
180
|
env?: NodeJS.ProcessEnv;
|
|
154
181
|
signal?: AbortSignal;
|
|
155
182
|
stdin?: string;
|
|
183
|
+
timeoutMs?: number;
|
|
156
184
|
}): Promise<ProcessRunResult> {
|
|
157
185
|
const { args, cwd, env, signal, stdin } = options;
|
|
186
|
+
const timeoutMs = options.timeoutMs ?? getAgentBrowserProcessTimeoutMs();
|
|
158
187
|
const explicitSocketDir = env?.[AGENT_BROWSER_SOCKET_DIR_ENV];
|
|
159
188
|
let effectiveEnv = explicitSocketDir === undefined ? { ...env, [AGENT_BROWSER_SOCKET_DIR_ENV]: undefined } : env;
|
|
160
189
|
const requestedSocketDir = explicitSocketDir ?? getAgentBrowserSocketDir();
|
|
@@ -175,7 +204,9 @@ export async function runAgentBrowserProcess(options: {
|
|
|
175
204
|
let pendingStdoutWrite = Promise.resolve();
|
|
176
205
|
let stdoutSpillError: Error | undefined;
|
|
177
206
|
let killTimer: NodeJS.Timeout | undefined;
|
|
207
|
+
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
178
208
|
let abortListener: (() => void) | undefined;
|
|
209
|
+
let timedOut = false;
|
|
179
210
|
|
|
180
211
|
const queueStdoutChunk = (buffer: Buffer) => {
|
|
181
212
|
stdoutTail = appendTail(stdoutTail, buffer.toString("utf8"), MAX_BUFFERED_STDOUT_TAIL_CHARS);
|
|
@@ -224,6 +255,9 @@ export async function runAgentBrowserProcess(options: {
|
|
|
224
255
|
if (killTimer) {
|
|
225
256
|
clearTimeout(killTimer);
|
|
226
257
|
}
|
|
258
|
+
if (timeoutTimer) {
|
|
259
|
+
clearTimeout(timeoutTimer);
|
|
260
|
+
}
|
|
227
261
|
if (stdoutSpillHandle) {
|
|
228
262
|
await stdoutSpillHandle.close().catch(() => undefined);
|
|
229
263
|
}
|
|
@@ -237,6 +271,8 @@ export async function runAgentBrowserProcess(options: {
|
|
|
237
271
|
stderr,
|
|
238
272
|
stdout: stdoutSpillPath ? stdoutTail : Buffer.concat(stdoutBuffers).toString("utf8"),
|
|
239
273
|
stdoutSpillPath,
|
|
274
|
+
timedOut,
|
|
275
|
+
timeoutMs: timedOut ? timeoutMs : undefined,
|
|
240
276
|
});
|
|
241
277
|
});
|
|
242
278
|
};
|
|
@@ -247,8 +283,13 @@ export async function runAgentBrowserProcess(options: {
|
|
|
247
283
|
stdio: ["pipe", "pipe", "pipe"],
|
|
248
284
|
});
|
|
249
285
|
|
|
250
|
-
const
|
|
251
|
-
|
|
286
|
+
const terminateChild = (reason: "abort" | "timeout") => {
|
|
287
|
+
if (settled) return;
|
|
288
|
+
if (reason === "abort") {
|
|
289
|
+
aborted = true;
|
|
290
|
+
} else {
|
|
291
|
+
timedOut = true;
|
|
292
|
+
}
|
|
252
293
|
child.kill("SIGTERM");
|
|
253
294
|
killTimer = setTimeout(() => {
|
|
254
295
|
child.kill("SIGKILL");
|
|
@@ -286,7 +327,7 @@ export async function runAgentBrowserProcess(options: {
|
|
|
286
327
|
finish(127);
|
|
287
328
|
});
|
|
288
329
|
child.once("close", (code) => {
|
|
289
|
-
finish(code ?? (spawnError ? 127 : 0));
|
|
330
|
+
finish(code ?? (timedOut ? 124 : spawnError ? 127 : 0));
|
|
290
331
|
});
|
|
291
332
|
child.stdout.on("data", (chunk: Buffer | string) => {
|
|
292
333
|
queueStdoutChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -295,11 +336,16 @@ export async function runAgentBrowserProcess(options: {
|
|
|
295
336
|
stderr = appendTail(stderr, chunk.toString(), MAX_BUFFERED_STDERR_CHARS);
|
|
296
337
|
});
|
|
297
338
|
|
|
339
|
+
if (timeoutMs > 0) {
|
|
340
|
+
timeoutTimer = setTimeout(() => terminateChild("timeout"), timeoutMs);
|
|
341
|
+
timeoutTimer.unref?.();
|
|
342
|
+
}
|
|
343
|
+
|
|
298
344
|
if (signal) {
|
|
299
345
|
if (signal.aborted) {
|
|
300
|
-
|
|
346
|
+
terminateChild("abort");
|
|
301
347
|
} else {
|
|
302
|
-
abortListener =
|
|
348
|
+
abortListener = () => terminateChild("abort");
|
|
303
349
|
signal.addEventListener("abort", abortListener, { once: true });
|
|
304
350
|
}
|
|
305
351
|
}
|
|
@@ -116,6 +116,25 @@ function buildExitCodeFallback(options: { command?: string; effectiveArgs?: stri
|
|
|
116
116
|
return appendWrapperRecoveryHint(`${invocation} exited with code ${options.exitCode}.`, options.wrapperRecoveryHint);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
function buildWatchdogTimeoutMessage(options: { timeoutMs?: number }): string {
|
|
120
|
+
const timeoutText = options.timeoutMs === undefined ? "the wrapper watchdog" : `the ${options.timeoutMs}ms wrapper watchdog`;
|
|
121
|
+
return [
|
|
122
|
+
`agent-browser exceeded ${timeoutText} and was stopped before the upstream CLI entered its 30s IPC retry path.`,
|
|
123
|
+
"Keep a single agent-browser command under 30 seconds; split long waits into shorter waits or retry with sessionMode: \"fresh\" if the session state looks stale.",
|
|
124
|
+
].join(" ");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isUpstreamIpcReadTimeoutMessage(message: string): boolean {
|
|
128
|
+
return /Failed to read: Resource temporarily unavailable(?: \(os error \d+\))?.*daemon may be busy or unresponsive/i.test(message);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildUpstreamIpcReadTimeoutMessage(): string {
|
|
132
|
+
return [
|
|
133
|
+
"agent-browser hit the upstream CLI 30s IPC read timeout while waiting for the daemon response.",
|
|
134
|
+
"The daemon may still be alive; do not blindly retry a non-idempotent command. Prefer a shorter command, split long waits, or retry with sessionMode: \"fresh\" after checking tab list.",
|
|
135
|
+
].join(" ");
|
|
136
|
+
}
|
|
137
|
+
|
|
119
138
|
export function getAgentBrowserErrorText(options: {
|
|
120
139
|
aborted: boolean;
|
|
121
140
|
command?: string;
|
|
@@ -126,10 +145,13 @@ export function getAgentBrowserErrorText(options: {
|
|
|
126
145
|
plainTextInspection: boolean;
|
|
127
146
|
spawnError?: Error;
|
|
128
147
|
stderr: string;
|
|
148
|
+
timedOut?: boolean;
|
|
149
|
+
timeoutMs?: number;
|
|
129
150
|
wrapperRecoveryHint?: string;
|
|
130
151
|
}): string | undefined {
|
|
131
|
-
const { aborted, envelope, exitCode, parseError, plainTextInspection, spawnError, stderr } = options;
|
|
152
|
+
const { aborted, envelope, exitCode, parseError, plainTextInspection, spawnError, stderr, timedOut } = options;
|
|
132
153
|
if (plainTextInspection) return undefined;
|
|
154
|
+
if (timedOut) return buildWatchdogTimeoutMessage(options);
|
|
133
155
|
if (aborted) return "agent-browser was aborted.";
|
|
134
156
|
if (spawnError) return spawnError.message;
|
|
135
157
|
if (parseError) return parseError;
|
|
@@ -137,7 +159,11 @@ export function getAgentBrowserErrorText(options: {
|
|
|
137
159
|
if ((hasStructuredBatchStepFailure(envelope.data) || detectConfirmationRequired(envelope.data)) && envelope.error === undefined) {
|
|
138
160
|
return undefined;
|
|
139
161
|
}
|
|
140
|
-
|
|
162
|
+
const envelopeErrorText = extractEnvelopeErrorText(envelope.error);
|
|
163
|
+
if (envelopeErrorText && isUpstreamIpcReadTimeoutMessage(envelopeErrorText)) {
|
|
164
|
+
return buildUpstreamIpcReadTimeoutMessage();
|
|
165
|
+
}
|
|
166
|
+
return envelopeErrorText ?? (stderr.trim() || buildFailureFallback(options));
|
|
141
167
|
}
|
|
142
168
|
if (exitCode !== 0) {
|
|
143
169
|
return stderr.trim() || buildExitCodeFallback(options);
|
|
@@ -27,7 +27,8 @@ import { isRecord } from "./parsing.js";
|
|
|
27
27
|
*
|
|
28
28
|
* Other flags like `--headed`, `--engine`, `--executable-path`, `--user-agent`, and
|
|
29
29
|
* `--download-path` are first-launch-sensitive but not alternate session/auth attach
|
|
30
|
-
* mechanisms, so they are intentionally excluded
|
|
30
|
+
* mechanisms and do not inject pre-page JavaScript, so they are intentionally excluded
|
|
31
|
+
* from the full launch-scoped set.
|
|
31
32
|
*/
|
|
32
33
|
const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
|
|
33
34
|
{
|
|
@@ -38,6 +39,14 @@ const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
|
|
|
38
39
|
flag: "--cdp",
|
|
39
40
|
reason: "selects the browser/CDP endpoint used when an upstream session is launched",
|
|
40
41
|
},
|
|
42
|
+
{
|
|
43
|
+
flag: "--enable",
|
|
44
|
+
reason: "selects built-in page init scripts before the upstream browser session is launched",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
flag: "--init-script",
|
|
48
|
+
reason: "registers page init scripts before the upstream browser session is launched",
|
|
49
|
+
},
|
|
41
50
|
{
|
|
42
51
|
flag: "--profile",
|
|
43
52
|
reason: "selects Chrome profile state for the upstream launch",
|
|
@@ -77,6 +86,7 @@ const LEGACY_BASH_ALLOW_PATTERNS = [
|
|
|
77
86
|
];
|
|
78
87
|
const BROWSER_PROMPT_PATTERNS = [
|
|
79
88
|
/\b(?:agent[_ -]?browser|browser automation|eval\s+--stdin|screenshot|snapshot|tab\s+list)\b/i,
|
|
89
|
+
/\b(?:react\s+(?:tree|inspect|renders|suspense)|web\s+vitals|core\s+web\s+vitals|pushstate)\b/i,
|
|
80
90
|
/\bbrowser\b.*\b(?:automation|click|fill|navigate|open|page|screenshot|site|snapshot|tab|url|visit|web(?:site| page)?)\b/i,
|
|
81
91
|
/\b(?:browse|click|fill|login|navigate|open|visit)\b.*\b(?:https?:\/\/\S+|page|site|tab|url|web(?:site| page)?)\b/i,
|
|
82
92
|
];
|
|
@@ -98,6 +108,8 @@ const GLOBAL_FLAGS_WITH_VALUES = new Set([
|
|
|
98
108
|
"--headers",
|
|
99
109
|
"--executable-path",
|
|
100
110
|
"--extension",
|
|
111
|
+
"--init-script",
|
|
112
|
+
"--enable",
|
|
101
113
|
"--provider",
|
|
102
114
|
"-p",
|
|
103
115
|
"--engine",
|
|
@@ -328,11 +340,33 @@ function redactStandaloneBasicCredential(text: string): string {
|
|
|
328
340
|
});
|
|
329
341
|
}
|
|
330
342
|
|
|
343
|
+
function credentialTrailingPunctuation(credential: string): string {
|
|
344
|
+
return credential.match(/^(.+?)([,.]+)$/)?.[2] ?? "";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function isBearerHelpPlaceholder(label: string, credential: string, trailing: string): boolean {
|
|
348
|
+
return label.toLowerCase() === "authorization bearer" && credential.toLowerCase() === "token" && trailing === ")";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function formatRedactedCredential(label: string, credential: string, trailing = ""): string {
|
|
352
|
+
return `${label} [REDACTED]${credentialTrailingPunctuation(credential)}${trailing}`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function redactBearerCredentials(text: string): string {
|
|
356
|
+
return text
|
|
357
|
+
.replace(/\b(Authorization\s*:\s*Bearer)\s+([^\s"',)\[\]]+)([),.]?)/gi, (_match, label: string, credential: string, trailing: string) => {
|
|
358
|
+
return formatRedactedCredential(label, credential, trailing);
|
|
359
|
+
})
|
|
360
|
+
.replace(/\b((?:Authorization\s+)?Bearer)\s+([^\s"',)\[\]]+)([),.]?)/gi, (match, label: string, credential: string, trailing: string) => {
|
|
361
|
+
if (isBearerHelpPlaceholder(label, credential, trailing)) return match;
|
|
362
|
+
return formatRedactedCredential(label, credential, trailing);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
331
366
|
export function redactSensitiveText(text: string): string {
|
|
332
367
|
return redactEmbeddedStructuredText(
|
|
333
368
|
redactStandaloneBasicCredential(
|
|
334
|
-
redactLooseUrlMatches(text)
|
|
335
|
-
.replace(/\b(Bearer)\s+[^\s",]+/gi, "$1 [REDACTED]")
|
|
369
|
+
redactBearerCredentials(redactLooseUrlMatches(text))
|
|
336
370
|
.replace(/\b(Authorization\s*:\s*Basic)\s+[^\s",]+/gi, "$1 [REDACTED]")
|
|
337
371
|
.replace(/\b(Cookie|Set-Cookie)\s*:\s*[^\n\r"]+/gi, "$1: [REDACTED]"),
|
|
338
372
|
),
|
|
@@ -978,9 +1012,31 @@ export function chooseOpenResultTabCorrection(options: {
|
|
|
978
1012
|
: undefined;
|
|
979
1013
|
}
|
|
980
1014
|
|
|
1015
|
+
function getOpenCommandTarget(commandTokens: string[]): string | undefined {
|
|
1016
|
+
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
1017
|
+
const token = commandTokens[index];
|
|
1018
|
+
if (token === "--init-script" || token === "--enable") {
|
|
1019
|
+
index += 1;
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
if (token.startsWith("--init-script=") || token.startsWith("--enable=")) {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if (token.startsWith("-")) {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
return token;
|
|
1029
|
+
}
|
|
1030
|
+
return undefined;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
981
1033
|
export function parseCommandInfo(args: string[]): CommandInfo {
|
|
982
1034
|
const commandTokens = extractCommandTokens(args);
|
|
983
|
-
|
|
1035
|
+
const command = commandTokens[0];
|
|
1036
|
+
return {
|
|
1037
|
+
command,
|
|
1038
|
+
subcommand: command && OPEN_COMMANDS.has(command) ? getOpenCommandTarget(commandTokens) : commandTokens[1],
|
|
1039
|
+
};
|
|
984
1040
|
}
|
|
985
1041
|
|
|
986
1042
|
export function extractCommandTokens(args: string[]): string[] {
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ export const CAPABILITY_BASELINE_BLOCK_MARKER_PREFIX = "agent-browser-capability
|
|
|
12
12
|
export const COMMAND_REFERENCE_BASELINE_BLOCK_IDS = Object.freeze(["upstream-baseline", "capability-token-baseline"]);
|
|
13
13
|
|
|
14
14
|
export const CAPABILITY_BASELINE = Object.freeze({
|
|
15
|
-
targetVersion: "0.
|
|
15
|
+
targetVersion: "0.27.0",
|
|
16
16
|
helpCommands: Object.freeze([
|
|
17
17
|
Object.freeze({ label: "root help", args: Object.freeze(["--help"]) }),
|
|
18
18
|
Object.freeze({ label: "tab help", args: Object.freeze(["tab", "--help"]) }),
|
|
@@ -43,6 +43,20 @@ export const CAPABILITY_BASELINE = Object.freeze({
|
|
|
43
43
|
"inspect",
|
|
44
44
|
"clipboard <op> [text]",
|
|
45
45
|
"stream enable [--port <n>]",
|
|
46
|
+
"react tree",
|
|
47
|
+
"react inspect <id>",
|
|
48
|
+
"react renders start",
|
|
49
|
+
"react renders stop [--json]",
|
|
50
|
+
"react suspense [--only-dynamic] [--json]",
|
|
51
|
+
"vitals [url] [--json]",
|
|
52
|
+
"pushstate <url>",
|
|
53
|
+
"removeinitscript <id>",
|
|
54
|
+
"--init-script <path>",
|
|
55
|
+
"--enable <feature>",
|
|
56
|
+
"AGENT_BROWSER_INIT_SCRIPTS",
|
|
57
|
+
"AGENT_BROWSER_ENABLE",
|
|
58
|
+
"network route <url> [--abort|--body <json>] [--resource-type <csv>]",
|
|
59
|
+
"cookies set --curl <file>",
|
|
46
60
|
"auth save <name>",
|
|
47
61
|
"confirm <id>",
|
|
48
62
|
"deny <id>",
|
|
@@ -84,6 +98,18 @@ export const CAPABILITY_BASELINE = Object.freeze({
|
|
|
84
98
|
Object.freeze({ token: "inspect", help: "root help" }),
|
|
85
99
|
Object.freeze({ token: "clipboard <op> [text]", help: "root help" }),
|
|
86
100
|
Object.freeze({ token: "stream enable [--port <n>]", help: "root help" }),
|
|
101
|
+
Object.freeze({ token: "react tree", help: "root help" }),
|
|
102
|
+
Object.freeze({ token: "react inspect <id>", help: "root help" }),
|
|
103
|
+
Object.freeze({ token: "react renders start", help: "root help" }),
|
|
104
|
+
Object.freeze({ token: "react renders stop [--json]", help: "root help" }),
|
|
105
|
+
Object.freeze({ token: "react suspense [--only-dynamic] [--json]", help: "root help" }),
|
|
106
|
+
Object.freeze({ token: "vitals [url] [--json]", help: "root help" }),
|
|
107
|
+
Object.freeze({ token: "pushstate <url>", help: "root help" }),
|
|
108
|
+
Object.freeze({ token: "removeinitscript <id>", help: "root help" }),
|
|
109
|
+
Object.freeze({ token: "--init-script <path>", help: "root help" }),
|
|
110
|
+
Object.freeze({ token: "--enable <feature>", help: "root help" }),
|
|
111
|
+
Object.freeze({ token: "--resource-type <csv>", help: "root help" }),
|
|
112
|
+
Object.freeze({ token: "cookies set --curl <file>", help: "root help" }),
|
|
87
113
|
Object.freeze({ token: "auth save <name>", help: "root help" }),
|
|
88
114
|
Object.freeze({ token: "confirm <id>", help: "root help" }),
|
|
89
115
|
Object.freeze({ token: "deny <id>", help: "root help" }),
|
package/scripts/doctor.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { execFile as execFileCallback } from "node:child_process";
|
|
11
|
+
import { realpathSync } from "node:fs";
|
|
11
12
|
import { access, readFile } from "node:fs/promises";
|
|
12
13
|
import { homedir } from "node:os";
|
|
13
14
|
import { dirname, resolve, sep } from "node:path";
|
|
@@ -415,7 +416,16 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
415
416
|
return 0;
|
|
416
417
|
}
|
|
417
418
|
|
|
418
|
-
|
|
419
|
+
export function isDirectRun(metaUrl, argv1 = process.argv[1], resolveRealPath = realpathSync) {
|
|
420
|
+
if (!argv1) return false;
|
|
421
|
+
try {
|
|
422
|
+
return resolveRealPath(argv1) === fileURLToPath(metaUrl);
|
|
423
|
+
} catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (isDirectRun(import.meta.url)) {
|
|
419
429
|
main().then((exitCode) => {
|
|
420
430
|
process.exitCode = exitCode;
|
|
421
431
|
});
|