pi-agent-browser-native 0.3.0 → 0.6.5

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 (89) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/README.md +130 -54
  3. package/dist/extensions/agent-browser/index.js +781 -169
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +50 -2
  6. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +5 -8
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +53 -12
  9. package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
  10. package/dist/extensions/agent-browser/lib/config.js +1 -1
  11. package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -13
  12. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
  13. package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -24
  14. package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
  15. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
  16. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +26 -4
  17. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +6 -139
  18. package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -116
  19. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
  20. package/dist/extensions/agent-browser/lib/managed-session-storage.js +54 -25
  21. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
  24. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +54 -48
  25. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +71 -5
  26. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +6 -7
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
  29. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +152 -64
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +244 -102
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +63 -37
  33. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +20 -21
  34. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
  35. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
  36. package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
  37. package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
  38. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
  39. package/dist/extensions/agent-browser/lib/playbook.js +29 -25
  40. package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
  41. package/dist/extensions/agent-browser/lib/process-identity.js +5 -12
  42. package/dist/extensions/agent-browser/lib/process.js +130 -104
  43. package/dist/extensions/agent-browser/lib/recording-reservations.js +116 -0
  44. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -6
  45. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
  46. package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
  47. package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
  48. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
  49. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +86 -18
  50. package/dist/extensions/agent-browser/lib/results/presentation/common.js +38 -2
  51. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +18 -17
  52. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
  53. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +38 -20
  54. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +60 -15
  55. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
  56. package/dist/extensions/agent-browser/lib/results/presentation.js +36 -6
  57. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +3 -1
  58. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
  59. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +54 -11
  60. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
  61. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
  62. package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
  63. package/dist/extensions/agent-browser/lib/runtime.js +186 -108
  64. package/dist/extensions/agent-browser/lib/session-page-state.js +71 -10
  65. package/dist/extensions/agent-browser/lib/temp.js +1 -2
  66. package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
  67. package/dist/extensions/agent-browser/lib/web-search.js +108 -24
  68. package/dist/extensions/agent-browser/script-worker.js +169 -0
  69. package/dist/scripts/agent-browser-target.mjs +21 -0
  70. package/docs/ARCHITECTURE.md +57 -34
  71. package/docs/COMMAND_REFERENCE.md +255 -68
  72. package/docs/ELECTRON.md +2 -2
  73. package/docs/RELEASE.md +12 -10
  74. package/docs/REQUIREMENTS.md +11 -8
  75. package/docs/SUPPORT_MATRIX.md +36 -24
  76. package/docs/TOOL_CONTRACT.md +169 -95
  77. package/package.json +3 -1
  78. package/platform-smoke.config.mjs +2 -2
  79. package/scripts/agent-browser-capability-baseline.mjs +87 -9
  80. package/scripts/agent-browser-target.mjs +21 -0
  81. package/scripts/build.mjs +41 -0
  82. package/scripts/config.mjs +1 -0
  83. package/scripts/doctor.mjs +16 -9
  84. package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
  85. package/scripts/platform-smoke/targets.mjs +12 -6
  86. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
  87. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -583
  88. package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
  89. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
package/README.md CHANGED
@@ -43,7 +43,7 @@ The result is optimized for agent work:
43
43
  - interactive `@eN` refs for follow-up clicks and form fills
44
44
  - screenshots and downloaded files surfaced as Pi artifacts
45
45
  - structured details for titles, URLs, saved files, sessions, and errors
46
- - spill files for oversized raw output instead of dumping pages into context
46
+ - spill files for full redacted output instead of dumping oversized pages into context
47
47
  - compact, colorized Pi TUI rows that can be expanded without changing what the agent receives
48
48
  - recovery hints when a tab, selector, stale `@ref`, or launch mode needs a different next step
49
49
 
@@ -71,25 +71,26 @@ The result is optimized for agent work:
71
71
 
72
72
  | Pain | Native wrapper capability | Proof surface |
73
73
  |---|---|---|
74
- | Agents build fragile shell commands | Exposes `agent_browser` with exact `args`, an optional `semanticAction` shorthand for common `find` flows and native `select`, constrained `job` / `qa` presets, experimental `sourceLookup` / `networkSourceLookup` that compile short workflows to `batch`, top-level `electron` for desktop lifecycle, plus controlled `stdin` and `sessionMode` | `extensions/agent-browser/index.ts`, `extensions/agent-browser/lib/input-modes/`, [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md) |
75
- | Page snapshots are too large or viewport-blind | Shows compact, main-content-first summaries, surfaces an `Omitted high-value controls` section (plus `details.data.highValueControlRefIds`) when dense pages or desktop host screens hide editables, named surfaces/tabs, primary action buttons, and high-signal named links such as repository results from the trimmed ref lists, supports wrapper-side `snapshot -i --search <text>` / `--filter role=<role>` to trim dense pages while preserving full `details.refSnapshot`, supports `snapshot --viewport` for scroll/viewport metadata, supports `snapshot --diff` for quick ref-map deltas versus the prior tracked snapshot, and stores full raw output in spill files when needed | `extensions/agent-browser/lib/results/snapshot.ts`, `extensions/agent-browser/lib/orchestration/browser-run/prepare.ts`, `test/agent-browser.presentation.test.ts`, `test/agent-browser.extension-validation.test.ts` |
74
+ | Agents build fragile shell commands or repeat browser calls for loops and branches | Exposes `agent_browser` with one-shot sandboxed `script` orchestration, exact `args`, an optional `semanticAction` shorthand for common `find` flows and native `select`, constrained `job` / `qa` presets, experimental `sourceLookup` / `networkSourceLookup` that compile short workflows to `batch`, top-level `electron` for desktop lifecycle, plus controlled `stdin` and `sessionMode` | `extensions/agent-browser/index.ts`, `extensions/agent-browser/lib/input-modes/`, [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md) |
75
+ | Page snapshots are too large or viewport-blind | Shows compact, main-content-first summaries, surfaces an `Omitted high-value controls` section (plus `details.data.highValueControlRefIds`) when dense pages or desktop host screens hide editables, named surfaces/tabs, primary action buttons, and high-signal named links such as repository results from the trimmed ref lists, supports wrapper-side `snapshot -i --search <text>` / `--filter role=<role>` to trim dense pages while preserving full `details.refSnapshot`, supports `snapshot --viewport` for scroll/viewport metadata, supports `snapshot --diff` for quick ref-map deltas versus the prior tracked snapshot, and stores full redacted output in spill files when needed | `extensions/agent-browser/lib/results/snapshot.ts`, `extensions/agent-browser/lib/orchestration/browser-run/prepare.ts`, `test/agent-browser.presentation.test.ts`, `test/agent-browser.extension-validation.test.ts` |
76
76
  | Screenshots/downloads get lost in text | Normalizes artifact paths, creates missing parent directories, saves simple loopback anchor downloads to the requested path when possible, and reports existence, size, cwd, session, and repair status | [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#download-screenshot-and-pdf-files) |
77
- | Profile restores and tab drift confuse agents | Tracks managed sessions, keeps every upstream helper probe on the same idle-timeout launch configuration so the background browser is not restarted between a snapshot and action, re-selects target tabs after observed drift, refreshes the active target after `tab close`, rehydrates branch-backed session state on Pi session-tree changes, and pins later commands only for sessions with drift/restored-session risk | generated tab-recovery notes below; `test/agent-browser.extension-tab-recovery.test.ts` (drift and about:blank recovery), `test/agent-browser.extension-tabs.test.ts` (post-close target), `test/agent-browser.extension-ref-guards.test.ts` (snapshot/action environment and session-tree rehydration), `test/agent-browser.resume-state.test.ts` (persisted session / resume planning) |
78
- | Auth/profile workflows can leak secrets | Supports `auth save --password-stdin`, redacts sensitive args, URLs, stdout/stderr, and details, and discards malformed oversized stdout instead of persisting a parse-failure spill | `test/agent-browser.extension-security-redaction.test.ts` |
77
+ | Profile restores and tab drift confuse agents | Tracks managed sessions, keeps every upstream helper probe on the same idle-timeout launch configuration so the background browser is not restarted between a snapshot and action, re-selects target tabs after observed drift, live-verifies the active target and refreshes its title after successful tab selection or `tab close` (while retaining deliberate `about:blank` selections and post-close blank targets), rehydrates branch-backed session state on Pi session-tree changes, and pins later commands only for sessions with drift/restored-session risk | generated tab-recovery notes below; `test/agent-browser.extension-tab-recovery.test.ts` (drift and about:blank recovery), `test/agent-browser.extension-tabs.test.ts` (post-close target), `test/agent-browser.extension-ref-guards.test.ts` (snapshot/action environment and session-tree rehydration), `test/agent-browser.resume-state.test.ts` (persisted session / resume planning) |
78
+ | Auth/profile workflows can leak secrets | Supports `auth save --password-stdin`, redacts sensitive args, SAML/OAuth-bearing URLs, stdout/stderr, details, and snapshot spills, and discards malformed oversized stdout instead of persisting a parse-failure spill | `test/agent-browser.extension-security-redaction.test.ts` |
79
79
  | Stateful cookies/storage/auth output bloats or leaks context | Presentation layer redacts `details.data` for cookies and credential-like storage values while keeping low-risk local QA values such as `theme: dark` readable; recursively scrubs other structured upstream JSON (network, diff, trace/profiler, stream, dashboard, chat, auth, dialog, frame, state, and similar) using sensitive key names plus string heuristics; masks sensitive argv flags and positionals; scrubs secrets from failed batch step errors; and exposes a compact redacted `batch` matrix on top-level `details.data` | `extensions/agent-browser/lib/results/presentation.ts`, `extensions/agent-browser/lib/results/presentation/diagnostics.ts`, `extensions/agent-browser/lib/runtime.ts`, `test/agent-browser.presentation-diagnostics.test.ts` |
80
80
  | Stale `@eN` refs fail mysteriously | Records per-session `details.refSnapshot`, rejects mismatched URLs / unknown refs / unsafe `batch` stdin ordering before spawn, adds recovery guidance to rerun `snapshot -i` or use stable `find` locators | `extensions/agent-browser/index.ts`, `extensions/agent-browser/lib/session-page-state.ts`, `test/agent-browser.session-page-state.test.ts`, `test/agent-browser.results.test.ts`, `test/agent-browser.extension-ref-guards.test.ts`, `test/agent-browser.extension-semantic-recovery.test.ts` |
81
81
  | Agents need stable success/failure buckets | Exposes bounded `resultCategory`, `successCategory`, and `failureCategory` on tool `details` for branching without parsing prose; a `tool_result` hook also aligns real Pi `isError` semantics, naming `Pi tool isError: true` in prose output while preserving parseable caller-requested `--json` output | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), `extensions/agent-browser/lib/results/categories.ts`, `extensions/agent-browser/index.ts`, `extensions/agent-browser/lib/pi-tool-rendering.ts`, `test/agent-browser.results.test.ts`, `test/agent-browser.extension-validation.test.ts`, `test/agent-browser.pi-pipeline.test.ts` |
82
- | Clicks can report success without the page receiving the event | Top-level non-Electron direct `click` calls on `xpath=` targets or role-gated current `@e…` refs (`button`, `checkbox`, `menuitem`, `radio`, `switch`, `tab`) install a bounded target-specific DOM-event probe; eligible `@e…` refs use the latest snapshot role/name metadata, and duplicate-name refs use snapshot-order `duplicateIndex` rather than requiring a unique name. If upstream reports success but no trusted event reaches the resolved target, the wrapper fails the tool, exposes `details.clickDispatch`, and suggests explicit retry/inspect next actions (no in-page replay), including a nested-scroll `scrollintoview` action when the probe sees the target outside a scroll container or viewport. Unresolved locator clicks such as raw `find … click` are left upstream-owned to avoid false failures for frame-scoped targets. Other click results still expose `details.pageChangeSummary`, and unchanged-URL clicks can surface evidence-backed `details.overlayBlockers` candidates. | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), `extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts`, `extensions/agent-browser/lib/results/presentation/navigation.ts`, `test/agent-browser.presentation.test.ts`, `test/agent-browser.extension-click-dispatch.test.ts` |
83
- | Dashboard scroll commands can look successful while nothing moves | Handles standard `scroll <dir> [px]` against the document first (including pages whose smooth-scroll CSS defeats upstream wheel timing), falls back upstream when the document cannot move, and samples viewport/containers around the fallback; unchanged positions produce `details.scrollNoop`, visible recovery guidance, and exact snapshot/screenshot checks | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `test/agent-browser.extension-validation.test.ts` |
84
- | Dropdown/combobox clicks can focus or hit native option box-model errors | Adds first-class `select <selector> <value...>` paths through raw `args`, `semanticAction`, and `job`; for custom combobox clicks, detects focused controls with explicit `aria-expanded` state but no visible options and returns `details.comboboxFocus` plus exact recovery `nextActions` | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `extensions/agent-browser/lib/input-modes/semantic-action.ts`, `test/agent-browser.extension-input-modes.test.ts`, `test/agent-browser.extension-validation.test.ts` |
85
- | Recording workflows fail late when `ffmpeg` is missing | After successful `record start` / `record restart`, warns when `ffmpeg` is not on `PATH` so agents can install or fix PATH before `record stop` | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#diff-debug-and-streaming), `test/agent-browser.extension-validation.test.ts` |
86
- | Direct binary help may be blocked in agent sessions | Publishes a repo-readable command reference and verifies it against the target upstream version | `npm run verify` |
82
+ | Clicks can report success without the page receiving the event | Top-level non-Electron direct `click` calls on `xpath=` targets or role-gated current `@e…` refs (`button`, `checkbox`, `menuitem`, `radio`, `switch`, `tab`) install a bounded target-specific DOM-event probe; eligible `@e…` refs use the latest snapshot role/name metadata, and duplicate-name refs use snapshot-order `duplicateIndex` rather than requiring a unique name. If upstream reports success but no trusted event reaches the resolved target, the wrapper fails the tool, exposes `details.clickDispatch`, and suggests explicit retry/inspect next actions (no in-page replay), including a nested-scroll `scrollintoview` action when the probe sees the target outside a scroll container or viewport. Unresolved locator clicks such as raw `find … click` are left upstream-owned to avoid false failures for frame-scoped targets. Other click results still expose `details.pageChangeSummary`; `observed: false` explicitly marks dispatch-only mutation summaries and adds a visible `Action dispatched; application change unverified` warning. Unchanged-URL clicks can surface evidence-backed `details.overlayBlockers` candidates. | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), `extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts`, `extensions/agent-browser/lib/results/presentation/navigation.ts`, `test/agent-browser.presentation.test.ts`, `test/agent-browser.extension-click-dispatch.test.ts` |
83
+ | Dashboard scroll commands can look successful while nothing moves | Handles standard `scroll <dir> [px]` against the document first (including pages whose smooth-scroll CSS defeats upstream wheel timing), falls back upstream when the document cannot move, and samples viewport/containers around the fallback; unchanged positions fail as `upstream-error` with `details.scrollNoop`, visible recovery guidance, and exact snapshot/screenshot checks. Unsupported `scrollintoview text=...` fails before dispatch, including inside effective batch rows, and shows exact native `find text ... hover` and snapshot/ref recovery payloads; help remains native pass-through. | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `test/agent-browser.extension-validation.test.ts` |
84
+ | Dropdown/combobox clicks can focus or hit native option box-model errors | Adds first-class `select <selector> <value...>` paths through raw `args`, `job`, and `semanticAction`; semantic role/name or label select resolves exactly one current visible combobox/listbox ref before action. Custom combobox clicks still detect focused controls with explicit `aria-expanded` state but no visible options and return `details.comboboxFocus` plus exact recovery `nextActions` | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `extensions/agent-browser/lib/input-modes/semantic-action.ts`, `test/agent-browser.extension-input-modes.test.ts`, `test/agent-browser.extension-validation.test.ts` |
85
+ | Recording workflows fail late when `ffmpeg` is missing or report stale lifecycle state | After successful `record start` / `record restart`, reports `successCategory: "artifact-pending"`, returns an exact `stop-pending-recording` action, warns when `ffmpeg` is unavailable, and tells agents that `record start` switches to a fresh active page whose in-page state does not carry over while invalidating prior page-scoped `@e…` refs on every executed start attempt (even a failed already-active one) and on URL-bearing `record restart` (stale-ref until a fresh snapshot); an unbounded transcript-backed namespace/session index reserves active destinations across aliases, serializes artifact lifecycle and explicit wait/output writes, persists cross-branch close tombstones, retires every successful close path (including every matching namespace owner for `close --all`), rejects missing/stale restart output, coalesces terminal batch state, keeps only the newest pending path per identity, rejects recording starts after a nested close, folds Unicode path aliases, and retains exact cleanup actions with visible guidance on any later same-session failure | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#diff-debug-and-streaming), `test/agent-browser.extension-validation.test.ts`, `test/agent-browser.presentation-artifacts-batch.test.ts` |
86
+ | Upstream CLI drift can silently invalidate wrapper behavior | Publishes a repo-readable command reference, verifies it against the recommended 0.36.0 target, and probes browser-backed calls once per cwd/PATH so stable versions below the 0.35.0 floor fail before browser launch with installed/expected version evidence | `npm run verify` |
87
+ | Pages can expose structured workflows through experimental WebMCP | Passes through `webmcp list`, `invoke`, detached `result` / `cancel`, params/frame/timeout options, and the bundled `webmcp-gen` skill; treats `--no-webmcp` as launch-scoped, keeps pending or unsuccessfully settled targets unverified with an actionable `get url` follow-up, invalidates stale refs after page tools run, and budgets effective raw or stdin batch timeouts | [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#webmcp-page-tools), `test/agent-browser.extension-ref-guards.test.ts`, `test/agent-browser.wait-timeouts.test.ts`, `test/agent-browser.real-upstream-contract.test.ts` |
87
88
  | Desktop Electron apps need discovery, CDP attach, and safe teardown | Top-level `electron` runs host `list` / isolated `launch` (temp profile, OS-chosen debug port) / `status` / `probe` / `cleanup`, merges `launchId` plus managed `sessionName`, supports `handoff` `snapshot` / `tabs` / `connect`, and surfaces mismatch and post-command health guidance; wrapper cleanup applies only to launches it created | `extensions/agent-browser/lib/electron/discovery.ts`, `launch.ts`, `cleanup.ts`, [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#electron), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#electron-desktop-apps) |
88
- | Agents need bundled `skills` text and local setup/status commands without touching the live session | Treats `skills list`, `skills get …`, `skills path …`, local auth profile management (`auth save/list/show/delete/remove`), `profiles`, `dashboard`, `device list`, `doctor`, `install`, `upgrade`, `session list` (with wrapper-managed rows hidden), `session id`, `session info`, `plugin add/list/show/run`, `mcp --help`, and caller-owned saved-state inspection/targeted maintenance (`state list/show/rename` or named clear) as sessionless reads/actions: no implicit managed `--session` under default `sessionMode: "auto"`; broad clear/clean and managed-state targets are rejected before spawn (same session-ownership goal as plain-text `--help` / `--version`), while bare `mcp` server calls are blocked and provider/browser-backed workflows stay thin passthroughs that require upstream setup and credentials | [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#built-in-skills), `extensions/agent-browser/lib/command-policy.ts`, `extensions/agent-browser/lib/runtime.ts` |
89
+ | Agents need bundled `skills` text and local setup/status commands without touching the live session | Treats `skills list/get/path`, local auth/profile/setup commands, `session list`, and local state lifecycle commands as sessionless reads/actions when upstream does not need a live page. Session/state rows and targets remain visible, and supported upstream state/config/path operations pass through unchanged. Browser-backed workflows still receive an implicit session only when the caller did not choose one. | [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#built-in-skills), `extensions/agent-browser/lib/command-policy.ts`, `extensions/agent-browser/lib/runtime.ts` |
89
90
 
90
91
  ## Fastest way to try it
91
92
 
92
- Use Pi 0.84.0 or newer. This package keeps optional Pi core imports as wildcard `peerDependencies` because Pi package docs require the host Pi install to provide those packages, pins its direct Pi validation dependencies to 0.84.0, and makes older hosts a setup failure through `pi-agent-browser-doctor`. Version 0.3.0 intentionally provides no compatibility shims for older Pi releases.
93
+ Use Pi 0.84.0 or newer. This package keeps optional Pi core imports as wildcard `peerDependencies` because Pi package docs require the host Pi install to provide those packages, pins its direct Pi validation dependencies to 0.84.0, and makes older hosts a setup failure through `pi-agent-browser-doctor`. There are no compatibility shims for older Pi releases.
93
94
 
94
95
  Install upstream `agent-browser` first and make sure it is on `PATH`:
95
96
 
@@ -103,9 +104,28 @@ Optional external tools unlock the full command surface:
103
104
  | `agent-browser` | All browser automation through this extension | See upstream install docs |
104
105
  | `ffmpeg` | `record stop` WebM encoding after `record start` / `record restart` | `brew install ffmpeg` or `brew install ffmpeg-full` |
105
106
 
106
- Keep both binaries on `PATH`. `record start` can begin without a file on disk, but `record stop` needs `ffmpeg` to encode the WebM.
107
+ Keep both binaries on `PATH`. This package recommends `agent-browser 0.36.0` and accepts stable versions at or above the 0.35.0 floor; browser-backed calls fail fast below that floor while local inspection/setup commands remain available for diagnosis. `record start` can begin without a file on disk, but `record stop` needs `ffmpeg` to encode the WebM.
107
108
 
108
- The native tool also gives agents absolute installed-package doc paths in its compact runtime guidance. Raw `args` are the 1:1 upstream CLI coverage path for the targeted `agent-browser` release; typed modes such as `semanticAction`, `job`, `qa`, source lookups, and Electron lifecycle helpers are reliability shorthands layered on top. Agents should read `README.md` for setup/dependencies, `docs/COMMAND_REFERENCE.md` for targeted command workflows, and `docs/TOOL_CONTRACT.md` for result/detail contracts only when deeper guidance is needed.
109
+ ### Android / Termux
110
+
111
+ Android support currently uses Termux's system Chromium rather than Chrome for Testing. Upstream issue [vercel-labs/agent-browser#1587](https://github.com/vercel-labs/agent-browser/issues/1587) tracks native Android packaging; until upstream ships an Android launcher, install the packaged Linux-musl arm64 binary without lifecycle scripts and point the global command at it:
112
+
113
+ ```bash
114
+ pkg install tur-repo x11-repo
115
+ pkg install chromium ffmpeg which
116
+ npm install -g --ignore-scripts agent-browser@0.36.0
117
+ ln -sfn "$(npm root -g)/agent-browser/bin/agent-browser-linux-musl-arm64" \
118
+ "$(npm prefix -g)/bin/agent-browser"
119
+ ln -sfn "$PREFIX/lib/chromium/chromium-launcher.sh" "$PREFIX/bin/chromium"
120
+ agent-browser --version
121
+ which chromium
122
+ ```
123
+
124
+ The `which` package and launcher symlink satisfy upstream's existing Linux system-browser discovery even when tests or Pi sessions isolate `HOME`. This also lets isolated `script` calls launch without forbidden `--executable-path` overrides.
125
+
126
+ Reapply the musl command symlink after reinstalling or upgrading upstream until #1587 is resolved. The wrapper uses Termux-private socket/policy storage, compact 80-bit managed identities so ordinary namespaces and fresh rotations fit the Unix socket-path limit, Termux's `ps`, and Android app-sandbox trust rules automatically. Headless browser flows, managed restore, namespaced sessions, `script`, `qa`, `job`, screenshots, and recording are locally validated; Electron desktop discovery/lifecycle is not applicable to Android apps. Android remains outside the release-blocking Crabbox macOS/Ubuntu/native-Windows matrix until a repeatable Android provider target is added.
127
+
128
+ The native tool also gives agents absolute installed-package doc paths in its compact runtime guidance. Raw `args` are the 1:1 upstream CLI coverage path for the targeted `agent-browser` release; `script` adds bounded one-shot orchestration, while typed modes such as `semanticAction`, `job`, `qa`, source lookups, and Electron lifecycle helpers are reliability shorthands layered on top. Agents should read `README.md` for setup/dependencies, `docs/COMMAND_REFERENCE.md` for targeted command workflows, and `docs/TOOL_CONTRACT.md` for result/detail contracts only when deeper guidance is needed.
109
129
 
110
130
  Then install this Pi package:
111
131
 
@@ -119,13 +139,15 @@ Start Pi and ask for a browser action:
119
139
  Use the agent_browser tool to open https://example.com and then take an interactive snapshot.
120
140
  ```
121
141
 
122
- For a one-off trial that does not touch your configured Pi extensions:
142
+ For a one-off trial without adding the package to your Pi settings:
123
143
 
124
144
  ```bash
125
145
  pi --no-extensions -e npm:pi-agent-browser-native
126
146
  ```
127
147
 
128
- Pi 0.84.0+ may ask whether to trust the current project before loading project-local instructions, settings, or resources. This extension treats its own project-local package config as developer-trusted by default; use `--no-approve` when you intentionally want Pi and this extension to ignore project-local inputs for that run.
148
+ `--no-extensions` disables automatic extension loading, not Pi settings, configured package resolution, skills, prompts, themes, or context files.
149
+
150
+ Pi 0.84.0+ may ask whether to trust projects with trust-gated settings or resources. This extension follows Pi's trust decision when loading its project-local config. `--no-approve` skips that config and Pi's trust-gated project resources; context files such as `AGENTS.md` still load unless context loading is separately disabled.
129
151
 
130
152
  For a specific published version:
131
153
 
@@ -139,7 +161,7 @@ To install directly from source instead of npm:
139
161
  pi install https://github.com/fitchmultz/pi-agent-browser-native
140
162
  ```
141
163
 
142
- For a temporary source trial, keep it isolated from your normal package sources:
164
+ For a source trial without adding the package to your Pi settings:
143
165
 
144
166
  ```bash
145
167
  pi --no-extensions -e https://github.com/fitchmultz/pi-agent-browser-native
@@ -160,12 +182,14 @@ npm run doctor
160
182
  The doctor checks:
161
183
 
162
184
  - upstream `agent-browser` exists on `PATH`
163
- - the installed upstream version matches this wrapper's command-reference baseline
185
+ - the installed upstream is a stable version at or above the supported 0.35.0 floor; 0.36.0 remains the recommended baseline
164
186
  - `pi --version` meets the minimum Pi runtime floor for this release; older Pi versions are setup failures
165
187
  - Pi settings do not point at multiple active `pi-agent-browser-native` sources
166
188
 
167
189
  It does **not** edit Pi settings and does **not** run upstream `agent-browser doctor --fix`.
168
190
 
191
+ Pi hosts that run as uid 0 should set `PI_AGENT_BROWSER_SOCKET_DIR` to a short absolute directory under private root-owned ancestry, create it with mode `0700`, and keep it owned by the Pi user. The extension validates that directory and forwards it as upstream `AGENT_BROWSER_SOCKET_DIR`; ambient upstream socket overrides remain ignored.
192
+
169
193
  ## Optional package config and web search
170
194
 
171
195
  `pi-agent-browser-native` also reads package-owned config under Pi-scoped paths:
@@ -183,9 +207,9 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
183
207
  npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config show
184
208
  ```
185
209
 
186
- The optional `agent_browser_web_search` companion tool is available when a usable Exa or Brave credential source is configured or resolvable from startup config or trusted session config. It is not an `agent_browser` input mode and does not launch a browser; agents may use it whenever current/live external web information helps, then use `agent_browser` when they need page interaction, screenshots, authenticated/profile content, or DOM inspection. Prefer it over automating public search-engine forms such as Google in headless browser jobs: those flows may be redirected to anti-bot or CAPTCHA pages, and this wrapper does not provide or recommend CAPTCHA bypass. If both keys are available, the default provider is Exa because its `/search` endpoint returns agent-friendly highlights and search modes; set `webSearch.preferredProvider` to `"brave"` when you prefer Brave Search.
210
+ The optional `agent_browser_web_search` companion tool is available when a usable Exa or Brave credential source is configured or resolvable from startup config or trusted session config. It is not an `agent_browser` input mode and does not launch a browser; prefer it for current/live external web facts and URL discovery, then use `agent_browser` when the page itself needs interaction, screenshots, authenticated/profile content, or DOM inspection. Prefer it over automating public search-engine forms such as Google in headless browser jobs: those flows may be redirected to anti-bot or CAPTCHA pages, and this wrapper does not provide or recommend CAPTCHA bypass. If both keys are available, the default provider is Exa because its `/search` endpoint returns agent-friendly highlights and search modes; set `webSearch.preferredProvider` to `"brave"` when you prefer Brave Search.
187
211
 
188
- Get an Exa API key from the [Exa dashboard](https://dashboard.exa.ai/api-keys) or a Brave Search API key from the [Brave Search API dashboard](https://api-dashboard.search.brave.com/). Most users can simply export `EXA_API_KEY` or `BRAVE_API_KEY` in the environment that launches `pi`; config is only needed when you want Pi-scoped secret references, a preferred provider, or to disable this built-in search tool.
212
+ Get an Exa API key from the [Exa dashboard](https://dashboard.exa.ai/api-keys) or a Brave Search API key from the [Brave Search API dashboard](https://api-dashboard.search.brave.com/). Most users can simply export `EXA_API_KEY` or `BRAVE_API_KEY` in the environment that launches `pi`; config is only needed when you want Pi-scoped secret references, a preferred provider, a default Exa search type, or to disable this built-in search tool.
189
213
 
190
214
  Most config users should store env-var references in the Pi-scoped config:
191
215
 
@@ -197,6 +221,7 @@ cat > ~/.pi/config/pi-agent-browser-native/config.json <<'JSON'
197
221
  "webSearch": {
198
222
  "enabled": true,
199
223
  "preferredProvider": "exa",
224
+ "defaultSearchType": "deep-lite",
200
225
  "exaApiKey": "$EXA_API_KEY",
201
226
  "braveApiKey": "$BRAVE_API_KEY"
202
227
  }
@@ -236,7 +261,28 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
236
261
 
237
262
  Config merges in this order: global → project → `PI_AGENT_BROWSER_CONFIG` override. Under Pi 0.84.0+, the globally installed or CLI-loaded extension still loads project-local `.pi/config/pi-agent-browser-native/config.json` when Pi trust allows that project layer; it skips that project layer when Pi reports the project is untrusted or when Pi is launched with `--no-approve`. `webSearch.enabled` is evaluated after the loaded layers merge. Use `web-search disable --global` for a user default, `web-search disable --project` for one repo, and a `PI_AGENT_BROWSER_CONFIG` override with `{ "webSearch": { "enabled": false } }` when web search must stay off even if project config exists. Loaded config may use plaintext, custom environment aliases, interpolation literals, malformed-or-late-bound `$` values, and `!command` credential sources; the resolved secret is passed to the provider request while tool content, details, status output, and docs examples stay redacted. `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`.
238
263
 
239
- For Exa, the tool defaults to `searchType: "auto"` with `contents.highlights: true`. Agents may pass `searchType` (`fast`, `instant`, `deep-lite`, `deep`, or `deep-reasoning`) only when the task needs that latency/depth tradeoff; structured output schemas are intentionally not exposed yet.
264
+ For Exa, the effective mode is the per-call `searchType`, then `webSearch.defaultSearchType`, then `auto`. A research-heavy coding workflow should set the config default to `deep-lite`; callers can still override it per search. Users who do not opt in keep the existing `auto` latency.
265
+
266
+ | Exa `searchType` | Typical latency | Use |
267
+ | --- | --- | --- |
268
+ | `instant` | ~250 ms | Trivial lookups only |
269
+ | `fast` | ~450 ms | Low-latency relevance |
270
+ | `auto` | ~1 s | Everyday fact lookup |
271
+ | `deep-lite` | ~4 s | Preferred research-before-implementation mode |
272
+ | `deep` | 4–15 s | Hard multi-source research and comparisons |
273
+ | `deep-reasoning` | 12–40 s | Exhaustive or hardest multi-hop research only |
274
+
275
+ ```json
276
+ {
277
+ "query": "pi-agent-browser-native agent_browser_web_search searchType defaults",
278
+ "searchType": "deep-lite",
279
+ "count": 5
280
+ }
281
+ ```
282
+
283
+ Exa calls may also use up to 20 `includeDomains` or `excludeDomains`, a typed `category`, up to 10 deep-mode `additionalQueries`, and the `highlightsDynamic` research preview. `company` and `people` categories cannot combine with `freshness` or `excludeDomains`. These explicit Exa-only options fail clearly when Brave is selected; the existing `searchType` field remains ignored by Brave. Regular `contents.highlights: true` stays the default, and structured output schemas remain out of scope.
284
+
285
+ Every Exa request asks the provider to prefer primary official sources, honor requested versions/dates, and avoid equivalent results. After provider normalization, both adapters remove later results with the same exact normalized URL while preserving first-result order; they do not guess that distinct paths or query URLs are aliases and do not overfetch to replace removed rows. `details.duplicatesRemoved` reports any shrinkage. Exa `publishedDate` and Brave `page_age` appear as `pageDate`; Brave can also return a separate result `age`. These are provider-supplied page clues, not crawl age or proof of a version match. For version-sensitive work, inspect those clues, constrain one follow-up to the primary domain (`includeDomains` for Exa or `site:` in a Brave query), then read the primary page.
240
286
 
241
287
  The same config file can record conservative browser defaults such as a profile hint or a Chromium-compatible executable path:
242
288
 
@@ -261,7 +307,7 @@ Open a page and inspect it (first-call recipe: open → snapshot -i → interact
261
307
  { "args": ["snapshot", "-i"] }
262
308
  ```
263
309
 
264
- Watch a browser window during a demo or QA run by adding upstream's global `--headed` flag on the first launch. Use `sessionMode: "fresh"` if a managed session may already exist, because headed/headless state is launch-scoped. A successful tool call means upstream opened a browser context; it does **not** prove the OS window is visible on the user's display, especially under remote, container, or virtual-display setups.
310
+ Watch a browser window during a demo, QA run, or user-completed login by adding upstream's global `--headed` flag on the first launch. Use `sessionMode: "fresh"` if a managed session may already exist, because headed/headless state is launch-scoped. A successful first/fresh local wrapper-managed headed launch, including a launch inside `batch`, returns `details.browserWindow = { mode: "headed", ownership: "wrapper-managed", sessionName, visibility: "unverified" }` and one visible handoff sentence; CDP, auto-connect, provider, and Electron attachments do not. This proves that the wrapper requested and upstream launched headed mode, not that the OS window is visible on the user's display; remote, container, or virtual-display setups can still hide it. After the user finishes in the window, continue with `sessionMode: "auto"`.
265
311
 
266
312
  ```json
267
313
  { "args": ["--headed", "open", "https://example.com"], "sessionMode": "fresh" }
@@ -277,7 +323,15 @@ Render a WebGPU page by enabling upstream's WebGPU launch preset on a fresh loca
277
323
  { "args": ["screenshot", "/tmp/webgpu.png"] }
278
324
  ```
279
325
 
280
- `--webgpu` is also available as `AGENT_BROWSER_WEBGPU`; `--webgpu false` overrides an enabled environment default. Standalone upstream supports `"webgpu": true` in `agent-browser.json`, but browser-backed native calls reject upstream config files as described below. It cannot be combined while enabled with `--cdp`, `--auto-connect`, or provider launches. Run `{ "args": ["doctor", "--webgpu"] }` to pixel-check rendering and capture. macOS supports headless WebGPU screenshots; upstream requires a logged-in headed desktop on Windows and `--headed` plus Vulkan loader/Mesa packages on Linux (automatic Xvfb unless `AGENT_BROWSER_NO_XVFB=1`).
326
+ `--webgpu` is also available as `AGENT_BROWSER_WEBGPU`; `--webgpu false` overrides an enabled environment default. Standalone upstream also supports `"webgpu": true` in `agent-browser.json`; native calls preserve upstream config. It cannot be combined while enabled with `--cdp`, `--auto-connect`, or provider launches. Run `{ "args": ["doctor", "--webgpu"] }` to pixel-check rendering and capture. macOS supports headless WebGPU screenshots; upstream requires a logged-in headed desktop on Windows and `--headed` plus Vulkan loader/Mesa packages on Linux (automatic Xvfb unless `AGENT_BROWSER_NO_XVFB=1`).
327
+
328
+ On `agent-browser 0.35.0`, trust a private interception-proxy CA for locally launched Linux Chromium with a fresh, restore-disabled session:
329
+
330
+ ```json
331
+ { "args": ["--proxy", "http://proxy.example:8080", "--ca-cert", "/path/to/proxy-ca.pem", "open", "https://example.com"], "sessionMode": "fresh" }
332
+ ```
333
+
334
+ `--ca-cert <path>` also has `AGENT_BROWSER_CA_CERT`; `--no-ca-cert` / `AGENT_BROWSER_CLEAR_CA_CERT` clears retained trust. Upstream accepts PEM bundles or DER certificates, uses an isolated NSS store, preserves normal hostname/validity checks, and requires Linux Chromium plus `certutil`. It rejects profiles, CDP/auto-connect, providers, Lightpanda, `--ignore-https-errors`, and non-Linux hosts. Because a trusted interception CA can observe authenticated traffic, this wrapper disables automatic managed restore for CA-enabled sessions.
281
335
 
282
336
  Restrict browser and `read` traffic with upstream's domain containment on a fresh local Chrome context:
283
337
 
@@ -285,7 +339,7 @@ Restrict browser and `read` traffic with upstream's domain containment on a fres
285
339
  { "args": ["--allowed-domains", "example.com,*.example.org", "open", "https://example.com"], "sessionMode": "fresh" }
286
340
  ```
287
341
 
288
- In `agent-browser 0.32.0`, the allowlist also covers workers and popups and disables Chromium `RTCPeerConnection` while active. Upstream rejects `--allowed-domains` with CDP/auto-connect, profiles, restore/state replay, direct-page providers, iOS/Safari, and startup/profile Chrome args because those paths cannot guarantee containment. The wrapper's final-URL check remains defense in depth and reports escapes as `failureCategory: "policy-blocked"`.
342
+ In `agent-browser 0.32.0`, the allowlist also covers workers and popups and disables Chromium `RTCPeerConnection` while active. Upstream owns containment and rejects incompatible CDP/auto-connect, profile, restore/state, provider, iOS/Safari, and startup-argument combinations; the wrapper passes the setting and upstream result through unchanged.
289
343
 
290
344
  On `https://example.com/`, the main link label is **Learn more**—use exact visible text from your snapshot, not guessed copy such as `More information...`.
291
345
 
@@ -302,18 +356,18 @@ Run a multi-step flow in one tool call:
302
356
  { "args": ["batch", "--bail"], "stdin": "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }
303
357
  ```
304
358
 
305
- Use exact `batch --bail` when a later content step assumes an earlier navigation succeeded. Without fail-fast behavior, a failed navigation can leave the prior page active; the wrapper rejects the batch when that retained page could be local or unverified. Non-bail continuation remains available when every possible retained page is already verified safe. Splitting navigation and content into separate calls is the other safe option.
359
+ Use exact `batch --bail` when a later content step assumes an earlier navigation succeeded. Without fail-fast behavior, a failed navigation can leave an unverified prior page active; the wrapper rejects that shape before the content step. Non-bail continuation remains available when every possible retained target is verified. Splitting navigation and content into separate calls is the other safe option.
306
360
 
307
361
  If the same `batch` stdin later uses `@e…` on interaction commands after a step that can navigate or mutate the page (`open`, non-form `click`, `reload`, and similar), insert a `snapshot` step whose first argv token is `snapshot` (for example `["snapshot","-i"]`) between those phases. Multiple same-snapshot `fill @e…` steps and native form-control steps (`check`/`uncheck` on checkbox or radio refs, checkbox/radio `click`/`tap` refs, and `select` on combobox refs) may be batched before a final click/submit step. Dynamic or autosubmit forms should still use stable locators or split with a fresh snapshot. The wrapper rejects unsafe ordering with `failureCategory: "stale-ref"` before upstream runs; full rules are under `refSnapshot` in [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details).
308
362
 
309
- Read documentation or other unstructured text without launching Chrome, or omit the URL to read the rendered DOM of the current tab:
363
+ Read documentation or other unstructured text without requiring a Chrome page, or omit the URL to read the rendered DOM of the current tab:
310
364
 
311
365
  ```json
312
366
  { "args": ["read", "https://example.com/docs", "--filter", "authentication"] }
313
367
  { "args": ["read"] }
314
368
  ```
315
369
 
316
- Explicit URL reads prefer `text/markdown`, then try a `.md` path and nearby `llms.txt` links before falling back to readable HTML text. Use `--outline`, `--llms index|full`, `--require-md`, `--raw`, or `--timeout <ms>` when needed. The wrapper renders upstream `data.content` first, preserves metadata in `details.data`, keeps fetched URLs from replacing the active browser tab target, and budgets explicit long read timeouts across upstream's `.md` and ancestor-`llms.txt` request fallbacks.
370
+ Explicit URL reads prefer `text/markdown`, then try a `.md` path and nearby `llms.txt` links before falling back to readable HTML text. Use `--outline`, `--llms index|full`, `--require-md`, `--raw`, or `--timeout <ms>` when needed. The wrapper still starts the upstream CLI under the managed session identity. A concise visible `Read execution` line reports the fetch source, CLI start, managed browser lifecycle, and managed-session outcome; the same facts remain in `details.readSource`, `details.lifecycle.effectiveLaunch.browserLaunched`, `details.agentBrowserStarted`, and `details.managedSessionOutcome`. The lifecycle boolean can be `false` before any browser launch or `true` when the read reuses an already-active browser session. It renders upstream `data.content` first, preserves metadata in `details.data`, keeps fetched URLs from replacing the active browser tab target, and budgets explicit long read timeouts across upstream's `.md` and ancestor-`llms.txt` request fallbacks.
317
371
 
318
372
  Evaluate page JavaScript through stdin. Put the script in the top-level `stdin` field, not as an extra `args` token after `--stdin`. Return the value you want as an expression; `eval --stdin` may warn with `details.evalStdinHint` when a function-shaped snippet serializes to `{}` instead of being invoked:
319
373
 
@@ -323,7 +377,7 @@ Evaluate page JavaScript through stdin. Put the script in the top-level `stdin`
323
377
  { "args": ["eval", "--stdin"], "stdin": "({ title: document.title, url: location.href })", "outputPath": "logs/page-state.json" }
324
378
  ```
325
379
 
326
- Use `outputPath` when `eval`, `get`, `snapshot`, or another extraction should be saved as a durable workspace file. Keep it distinct from screenshot, download, recording, and other browser artifact destinations; if the paths resolve to the same file, the wrapper preserves the browser artifact and rejects the result-data write. The wrapper writes `details.data` when present, otherwise the model-facing text content, and returns `details.outputFile` with the saved path and byte count. Explicit upstream `--json` content stays parseable; in that case the save notice lives only in `details.outputFile`.
380
+ Use `outputPath` when `eval`, `get`, `snapshot`, or another extraction should be saved as a durable workspace file. Keep it distinct from screenshot, download, recording, and other browser artifact destinations; preflight rejects known same-call aliases before browser activity, and the result writer preserves the browser artifact if an alias becomes apparent only afterward. The wrapper writes `details.data` when present, otherwise the model-facing text content. When presentation compacted a large direct result, a result row, or the whole `batch`, it instead reads the full command-redacted pre-compaction payload only from the corresponding live wrapper-managed spill recorded in `details.artifactManifest`; if any required spill is unavailable or untrusted, the call fails without writing compact metadata to the requested path. `details.outputFile` reports the saved path and byte count. Explicit upstream `--json` content stays parseable; in that case the save notice lives only in `details.outputFile`.
327
381
 
328
382
  Extract several known refs or selectors in one `batch` call instead of many serial getter calls:
329
383
 
@@ -343,6 +397,25 @@ Download a file from a known link or control:
343
397
  { "args": ["download", "@e5", "/tmp/report.pdf"] }
344
398
  ```
345
399
 
400
+ ### One-shot code mode (`script`)
401
+
402
+ Use top-level `script` when the browser work needs a loop, a conditional page branch, or multi-page aggregation that would otherwise require several `agent_browser` calls. The source is an async JavaScript body with only two task-specific globals:
403
+
404
+ - `await browser({ args, stdin?, timeoutMs? })` runs one ordinary native browser call through the same validation, policy, redaction, presentation, artifact, and timeout pipeline as top-level `args`. It resolves to `{ ok, data, details?, error?, failureCategory?, nextActions?, resultCategory, successCategory?, summary, text }`; check `ok` before consuming `data`. Script-visible browser `nextActions` keep only policy-compatible calls with the wrapper-owned isolated identity prefix removed, so their `params` can be passed back to `browser()`.
405
+ - `emit(value)` adds a JSON-compatible output value. One emission is returned directly; multiple emissions are returned as an array. With no emission, the async body’s return value is used; when it returns nothing, `details.data` is omitted.
406
+
407
+ ```json
408
+ {
409
+ "script": "const rows = [];\nfor (const page of [1, 2]) {\n const opened = await browser({ args: [\"open\", `https://news.ycombinator.com/news?p=${page}`] });\n if (!opened.ok) throw new Error(opened.error);\n const extracted = await browser({ args: [\"eval\", \"--stdin\"], stdin: \"({ hasBanner: Boolean(document.querySelector('[role=dialog]')), rows: [...document.querySelectorAll('tr.athing')].slice(0, 30).map(row => ({ id: row.id, title: row.querySelector('.titleline > a')?.textContent ?? '' })) })\" });\n if (!extracted.ok) throw new Error(extracted.error);\n if (extracted.data.result.hasBanner) {\n const dismissed = await browser({ args: [\"click\", \"[role=dialog] button\"] });\n if (!dismissed.ok) throw new Error(dismissed.error);\n }\n rows.push(...extracted.data.result.rows);\n}\nemit(rows);"
410
+ }
411
+ ```
412
+
413
+ This mode is intentionally one-shot, not a reusable recipe runtime. Each invocation gets a unique non-profile browser session, never touches the implicit conversation session, serializes inner calls, and closes the isolated session in `finally`. It rejects caller `--session` / `--namespace`, browser lifecycle and attachment commands, persistent launch/profile/restore controls, nested `batch`, local/sessionless commands, and every other top-level input mode. Every script-owned helper and cleanup subprocess also clears ambient `AGENT_BROWSER_*` and standard proxy variables before the wrapper reapplies its own isolated-session controls, so shell defaults cannot attach, restore, or select a profile behind the script’s back. The sandbox has no imports, `require`, process, filesystem, network, timers, dynamic code generation, or host object/function references.
414
+
415
+ Limits are fixed: 25 attempted `browser()` calls, 64 KiB source, 64 KiB final emitted JSON, a 120-second default timeout, and a 300-second hard timeout ceiling. Final data is redacted, serialized compactly, and checked again before presentation; unsafe depth or post-redaction growth becomes a structured validation failure rather than unbounded prose. One approved top-level `agent_browser` call can authorize all 25 inner calls, so inspect the visible script source before approving it: the collapsed Pi tool row shows a bounded terminal-safe preview with source line breaks marked as `↵`, and expanding that row shows the full terminal-safe source with JavaScript line terminators preserved as visible newlines and removed controls marked visibly. The extension rehydrates only wrapper-verified parse-valid compact-result spills before returning inner `data`; ordinary result redaction still applies. Inner `summary` and `text` are bounded, and a complete envelope that still exceeds the IPC message cap becomes a handleable `upstream-error` browser result instead of breaking the sandbox bridge. Pi session persistence is required so the wrapper can append a model-invisible cleanup lease before the first browser launch and retry a failed close after restart. A rejected inner policy/validation call fails the top-level result even when source handles its returned envelope; an uncaught source exception returns `failureCategory: "script-error"`; a failed cleanup overrides any script outcome with `failureCategory: "cleanup-failed"`, `details.scriptSession.closeCommandArgs`, and an exact `close-script-session-after-cleanup-failure` next action. Compact prose confirms a successful isolated-session close after browser-bearing runs. Pi branch changes, quit, and reload abort active scripts, wait for isolated-session cleanup, and reap the sandbox child before restoring branch-visible state.
416
+
417
+ Use normal `args`, `job`, or `qa` for linear work. Use ordinary profile/attached flows for authenticated browser state. Do not store code mode source under a name or treat it as shared workflow configuration.
418
+
346
419
  ### Locator shorthand (`semanticAction`)
347
420
 
348
421
  For supported upstream `find` flows, direct selector/ref `click` / `check` / `fill`, and native dropdown selection you can omit hand-built `args` and pass a top-level `semanticAction` object instead. The wrapper compiles locator actions to the same `find` argv upstream already understands, direct selector/ref actions to matching upstream commands, or `action: "select"` to upstream `select <selector> <value...>`; compiled argv is echoed as `details.compiledSemanticAction` when the unified result includes that field. Full field rules live in [`docs/TOOL_CONTRACT.md#semanticaction`](docs/TOOL_CONTRACT.md#semanticaction).
@@ -359,23 +432,24 @@ For supported upstream `find` flows, direct selector/ref `click` / `check` / `fi
359
432
 
360
433
  Typical pitfalls:
361
434
 
362
- - Supply **exactly one** of `args`, `semanticAction`, `job`, `qa`, `sourceLookup`, `networkSourceLookup`, or `electron` per call (not more, not none). Prefer `args` for routine browse; `semanticAction` for stable locators; `job`/`qa` for multi-step checks; `electron` for desktop apps; treat `sourceLookup` / `networkSourceLookup` as experimental candidates-only.
435
+ - Supply **exactly one** of `script`, `args`, `semanticAction`, `job`, `qa`, `sourceLookup`, `networkSourceLookup`, or `electron` per call (not more, not none). Prefer `script` only for one-shot loops/branches/aggregation, `args` for routine browse; `semanticAction` for stable locators; `job`/`qa` for multi-step checks; `electron` for desktop apps; treat `sourceLookup` / `networkSourceLookup` as experimental candidates-only.
363
436
  - Do not pass `--json` in `args`; the wrapper injects it automatically.
364
437
  - `semanticAction` and `job` are **not** valid inside `batch` stdin; batch steps stay upstream argv string arrays (spell a `find` step as tokens there if you need it in a batch).
365
438
  - Commands or locators outside the supported shorthand still require explicit `args`. Common page getters are grouped under `get`: use `get title`, `get url`, or `get text <selector>` rather than shortcut commands such as `title` or `url`; unknown getter shortcuts can return read-only `details.nextActions` like `use-get-title`.
366
439
  - For `locator: "role"`, pass either `value: "button"` or `role: "button"`; if both are present they must match.
367
440
  - Use `semanticAction.session` to target a named upstream browser session; the wrapper prepends `--session <name>` before the compiled `find` or `select` argv and keeps that prefix on retry/candidate actions. In active sessions, role/name click/check/fill shorthands may resolve through the current `snapshot -i` refs before execution so hidden duplicate matches do not steal the action; fill only resolves when the current snapshot has one exact editable ref match. `details.effectiveArgs` shows the exact executed argv.
368
- - Do not reuse `@e…` refs across navigation. The wrapper records the latest snapshot refs per session and fails mutation-prone stale/recycled refs before upstream can silently hit a different current-page element; use the session-aware `refresh-interactive-refs` next action.
441
+ - Do not reuse `@e…` refs across navigation or in-place rerenders. The wrapper records the latest snapshot refs per session and fails stale/recycled getter and mutation refs, including batched getters, before upstream can silently read or hit a different current-page element; use the session-aware `refresh-interactive-refs` next action.
369
442
  - If upstream classifies the failure as `stale-ref` and `details.compiledSemanticAction` is present for a compiled `find` action, `details.nextActions` may list `retry-semantic-action-after-stale-ref` after `refresh-interactive-refs`, carrying the same compiled `find` argv so you can retry the locator-stable target once it is safe to do so. `select` calls that used stale `@refs` only get refresh guidance; use a fresh snapshot or stable selector before retrying (contract in [`docs/TOOL_CONTRACT.md#semanticaction`](docs/TOOL_CONTRACT.md#semanticaction)).
370
- - If the failure is `selector-not-found`, the wrapper may take one fresh snapshot and add `Current snapshot ref fallback` when that snapshot has exact visible role/name matches for the failed `find` / `semanticAction` target. Non-fill targets can include direct `try-current-visible-ref*` next actions, and semantic click misses can still add bounded `Agent-browser candidate fallbacks` such as `button`/`link` role retries for `text` clicks. `semanticAction` does not expose `uncheck` while upstream `find ... uncheck` is not runtime-supported; use raw `args: ["uncheck", <selector-or-ref>]` after a stable selector or fresh snapshot ref. For semantic `fill` misses on desktop or host-controlled rich inputs, prefer `details.richInputRecovery`: refresh refs, choose the current editable `@ref`, focus or click it, then use `keyboard inserttext` or `keyboard type` with the intended text. Direct contenteditable fills are verified with `get text` when snapshot metadata proves the target is contenteditable; if replacement did not happen, `details.fillVerification` warns before any submit step. Those recovery nextActions do not copy the fill text and do not press `Enter` or submit; only submit when the user flow explicitly calls for it (same contract link).
443
+ - If the failure is `selector-not-found`, the wrapper may take one fresh snapshot and add `Current snapshot ref fallback` when that snapshot has exact visible role/name matches for the failed `find` / `semanticAction` target. Non-fill targets can include direct `try-current-visible-ref*` next actions, and semantic click misses can still add bounded `Agent-browser candidate fallbacks` such as `button`/`link` role retries for `text` clicks. `semanticAction` does not expose `uncheck` while upstream `find ... uncheck` is not runtime-supported; use raw `args: ["uncheck", <selector-or-ref>]` after a stable selector or fresh snapshot ref. For semantic `fill` misses on desktop or host-controlled rich inputs, prefer `details.richInputRecovery`: refresh refs, choose the current editable `@ref`, focus or click it, then use `keyboard type` for framework-controlled editors that require real key events. `keyboard inserttext` is paste-like and can change a DOM value without updating application state, so use it only with separate application-state verification. Direct contenteditable fills are verified with `get text` when snapshot metadata proves the target is contenteditable; if replacement did not happen, `details.fillVerification` warns before any submit step. Those recovery nextActions do not copy the fill text and do not press `Enter` or submit; only submit when the user flow explicitly calls for it (same contract link).
371
444
  - A successful upstream `click` is not proof that the web app handled the event or changed state. For top-level non-Electron direct clicks on `xpath=` targets and eligible current `@e…` refs, the wrapper may fail the tool with `details.clickDispatch` and a `Click dispatch diagnostic` line when upstream reported success but no trusted DOM event reached the resolved target. Raw `find … click` locator calls are not probed because the wrapper has no concrete element before upstream resolves the locator, and document-level probes can falsely fail frame-scoped clicks. `@e…` ref click probes are limited to current snapshot refs with accessible role `button`, `checkbox`, `menuitem`, `radio`, `switch`, or `tab`, using duplicate-name snapshot order when needed. Use the suggested `inspect-click-dispatch-miss` / `retry-click-after-dispatch-miss` next actions instead of assuming the click mutated the page; when `details.clickDispatch.scrollContainer` is present, use `scroll-target-into-view-after-dispatch-miss` first. When the task depends on a mutation, follow `inspect-after-mutation` / `pageChangeSummary` evidence with a wait, URL/text check, or fresh snapshot before trusting the result; if the target still did not change, retry with a current visible ref or stable selector and report the workflow issue instead of silently continuing. For static local fixtures where the user only needs to exercise app code, an explicit `eval --stdin` programmatic click such as `document.querySelector("#demo").click()` can be a diagnostic workaround, but treat it as an untrusted scripted activation rather than proof a real user click works, and never use it to bypass user instructions. Respect explicit user stop boundaries yourself: if the user says to stop before order/post/purchase/submit, gather evidence on that page and do not click the final action. The wrapper does not parse broad prompt text into business-intent action blocks; `details.promptGuard` is reserved for concrete artifact-before-close checks.
445
+ - A successful upstream `click` can deliver no input at all on some sites. Reproduced against `https://www.saucedemo.com/` with upstream `agent-browser` 0.34.0 and no wrapper involved: when every CLI invocation in the flow is spaced ~0.6s apart, the React add-to-cart click reports success while **zero** DOM events reach a capture-phase listener and the cart badge never updates (0/5 runs succeeded, versus 4/5 with no spacing). A single idle gap is harmless (5/5); only sustained spacing across the flow triggers it. In that state a scripted `document.querySelector(...).click()` still works and updates the badge, so the page and its handlers are fine and the input never arrives; retries, `scrollintoview`, headed mode, and re-navigation do not recover it. This looks site-specific — React TodoMVC and react.dev were unaffected under the same pacing and command count. Wrapper calls do more per-command work than raw CLI calls, so they sit in the slow regime more often. Running the interaction steps as one `batch` keeps them inside a single upstream process and succeeded 5/5; prefer `batch` for consecutive real-app click flows, and treat a click whose `pageChangeSummary`/`navigationSummary` shows no change as unproven. When a `wait --url` or `assertUrl` times out after a suspected missed click, use the `fresh-session-after-url-wait-timeout` next action (`sessionMode: "fresh"` + `open about:blank`): replace about:blank with the target URL and replay the flow as one batch in a fresh session instead of retrying the wait.
372
446
  - A successful `snapshot -i` can surface `Possible overlay blockers` immediately when refs already contain strong dialog/alertdialog evidence plus close/dismiss controls. If a **top-level** `@e…`/`ref=` click succeeds (unified command `click`, not a `batch` step), upstream reports `data.clicked`, and `details.navigationSummary.url` stays on the same tab URL under the same normalization as ref preflight (fragment-insensitive), the wrapper may take one extra `snapshot -i` and add `Possible overlay blockers` with `details.overlayBlockers` (`candidates`, `summary`, optional `snapshot` refresh for refs) plus session-aware `inspect-overlay-state` / bounded `try-overlay-blocker-candidate-*` next actions when that snapshot shows strong modal context (`dialog` / `alertdialog`) and close/dismiss-like controls. Page-wide words like privacy, sign in, or banner alone do not trigger this diagnostic. The unchanged-URL check compares the prior pinned tab target with `details.navigationSummary.url`; CSS selector clicks do not run this overlay probe. Also skipped when tab correction or about-blank recovery already ran on that result.
373
447
  - If `get text <selector>` reads a non-ref, non-simple-id CSS selector with multiple matches or a hidden first match while visible matches exist, including successful `batch` steps, the wrapper may add `Selector text visibility warning`, `details.selectorTextVisibility` (plus `selectorTextVisibilityAll` for multiple batched warnings), and `inspect-visible-text-candidates` next actions; the warning names the matching `details.nextActions` id. Prefer a visible `@ref`, a scoped selector, or a targeted `eval --stdin` over hidden tab content.
374
- - In wrapper-tracked attached Electron sessions, broad selectors such as `body`, `html`, `main`, or `[role=application]` may read the whole app shell. The wrapper may add `Broad Electron get text selector warning`, `details.electronGetTextScopeWarning`, and `snapshot-for-electron-text-scope`; ordinary browser pages do not qualify without Electron launch provenance, and local `file://` page follow-ups are blocked before this diagnostic. Prefer `snapshot -i`, a current `@ref`, or a narrower panel selector.
448
+ - In wrapper-tracked attached Electron sessions, broad selectors such as `body`, `html`, `main`, or `[role=application]` may read the whole app shell. The wrapper may add `Broad Electron get text selector warning`, `details.electronGetTextScopeWarning`, and `snapshot-for-electron-text-scope`; ordinary browser pages do not qualify without Electron launch provenance. Prefer `snapshot -i`, a current `@ref`, or a narrower panel selector.
375
449
 
376
450
  ### Constrained browser jobs
377
451
 
378
- For short repeatable workflows, pass a top-level `job` instead of hand-writing `batch` stdin. Keep dynamic app jobs short around navigation, click, and rerender boundaries; avoid packing a whole checkout into one job. The wrapper only supports constrained steps (`open`, `click`, `fill`, `type`, `select`, `wait`, `assertText`, `assertUrl`, `waitForDownload`, `snapshot`, and `screenshot`), compiles them to existing upstream `batch` commands, and echoes the compiled commands as `details.compiledJob` for auditability. `open` steps can include `loadState` (`domcontentloaded`, `load`, or `networkidle`) to insert a readiness wait before the next step. `click` and `fill` steps can use either CSS `selector` or semantic locator fields (`locator`, `role`/`value`, optional `name`) so a job can express flows like role/name search without brittle selectors. `type` can use `selector`, `text`, optional `delayMs` for per-character pacing, and optional `press` for a final key such as `Enter`; paced type compiles to existing `focus`, `keyboard type`, `wait`, and `press` batch rows, is capped at 200 characters per delayed step, and compacts model-visible batch text while full rows remain in `details.batchSteps`. The same compile path backs top-level `qa`, so long `qa` runs surface the same timeout evidence shape. If a long `job`, `qa`, or `batch` hits the wrapper watchdog, `details.timeoutPartialProgress` may recover per-step status (`completed`, `failed`, `pending`, or `unknown`), current page URL plus a title only after a non-file URL is verified, declared artifact paths that already exist on disk, and either a `retry-timeout-step` next action for the first incomplete read-only or idempotent step or `inspect-current-page-after-timeout` when the first incomplete step may be mutating and needs state inspection before a shorter follow-up flow (see [`docs/TOOL_CONTRACT.md#details`](docs/TOOL_CONTRACT.md#details)). There is no separate catalog of reusable named browser recipes above `job`, `qa`, and raw `batch`; see [`docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet`](docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet) for the closed `RQ-0068` decision and when to revisit it.
452
+ For short repeatable workflows, pass a top-level `job` instead of hand-writing `batch` stdin. Keep dynamic app jobs short around navigation, click, and rerender boundaries; avoid packing a whole checkout into one job. The wrapper only supports constrained steps (`open`, `click`, `fill`, `type`, `select`, `wait`, `assertText`, `assertUrl`, `waitForDownload`, `snapshot`, and `screenshot`), compiles them to existing upstream `batch` commands, and echoes the compiled commands as `details.compiledJob` for auditability. `open` steps can include `loadState` (`domcontentloaded`, `load`, or `networkidle`) to insert a readiness wait before the next step. `click` and `fill` steps can use either CSS `selector` or semantic locator fields (`locator`, `role`/`value`, optional `name`) so a job can express flows like role/name search without brittle selectors. `type` can use `selector`, `text`, optional `delayMs` for per-character pacing, and optional `press` for a final key such as `Enter`; paced type compiles to existing `focus`, `keyboard type`, `wait`, and `press` batch rows, is capped at 200 characters per delayed step, and compacts model-visible batch text while full rows remain in `details.batchSteps`. The same compile path backs top-level `qa`, so long `qa` runs surface the same timeout evidence shape. If a long `job`, `qa`, or `batch` hits the wrapper watchdog, `details.timeoutPartialProgress` may recover per-step status (`completed`, `failed`, `pending`, or `unknown`), current page URL plus a title after the URL is verified, declared artifact paths that already exist on disk, and either a `retry-timeout-step` next action for the first incomplete read-only or idempotent step, `inspect-current-page-after-timeout` when the target is already verified, or a fail-fast `verify-page-target-after-timeout` batch that runs `get url` before `snapshot -i` when the target is unknown (see [`docs/TOOL_CONTRACT.md#details`](docs/TOOL_CONTRACT.md#details)). There is no separate catalog of reusable named browser recipes above one-shot ad hoc `script`, `job`, `qa`, and raw `batch`; `script` has no names, registry, or persistent workflow state; see [`docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet`](docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet) for the closed `RQ-0068` decision and when to revisit it.
379
453
 
380
454
  **Navigation inside `job` is explicit.** A successful `click` does not prove the next page loaded; add `assertUrl` and/or `assertText` after navigation-prone clicks (forms, checkout, tabs, submit buttons) before screenshots or steps that assume the new page. `assertUrl` accepts exact URLs and `*` / `**` glob-style patterns and now compiles directly to upstream `wait --url` for both forms.
381
455
 
@@ -408,7 +482,7 @@ For short repeatable workflows, pass a top-level `job` instead of hand-writing `
408
482
 
409
483
  On app pages that expose a native dropdown, add a `select` step such as `{ "action": "select", "selector": "#flavor", "value": "chocolate" }` before the assertion that depends on it. On locator-friendly pages, use semantic job steps such as `{ "action": "fill", "locator": "role", "role": "searchbox", "name": "Search", "text": "agent browser" }` and `{ "action": "click", "locator": "role", "role": "button", "name": "Search" }`.
410
484
 
411
- Use raw `args`/`stdin` when you need full upstream `batch` power, custom flags, or commands outside the constrained job schema. Do not pass `stdin` with `job`, `qa`, `sourceLookup`, `networkSourceLookup`, or `electron`; those modes generate or manage their own input.
485
+ Use raw `args`/`stdin` when you need full upstream `batch` power, custom flags, or commands outside the constrained job schema. Do not pass top-level `stdin` with `script`, `job`, `qa`, `sourceLookup`, `networkSourceLookup`, or `electron`; script puts inner stdin on `browser({ stdin })`, while the other modes generate or manage their own input.
412
486
 
413
487
  ### Electron desktop apps
414
488
 
@@ -438,11 +512,11 @@ For an app you launched yourself with remote debugging enabled, use raw upstream
438
512
 
439
513
  `connect` success means the debug endpoint accepted the session, not that an active page is ready. Use the returned `verify-connected-session-url` (`get url`) action before page-content reads, then inspect/select a stable tab and verify its URL. If a snapshot says `No active page`, the wrapper clears prior refs for that session; choose a stable `t<N>` tab and retry a condition wait or fresh `snapshot -i` before using `@e…` refs. Close commands (`close`, `quit`, or `exit`) only close the browser/CDP session; manually launched apps, their profiles, and explicit screenshots/downloads/HARs/traces/recordings remain host-owned.
440
514
 
441
- After either path, use `qa: { "attached": true, ... }` for a current-session smoke check without opening a URL. Attached QA preserves existing network/console/page-error buffers instead of clearing them, so it can catch errors raised before the check started; visible output and `details.compiledQaPreset.checks.diagnosticsResetAtStart` identify that scope. Prefer condition waits (`wait --text`, `wait --url`, `wait --fn`, `wait --load <state>`, `wait --download`), `qa.attached`, `electron.probe` / `electron.status`, `tab list` → `tab t<N>`, fresh snapshots, or screenshots over blind sleeps. Fixed waits are a last resort: use explicit `--timeout` or top-level `timeoutMs` for legitimately slow waits, and treat a result like `"waited":"timeout"` as elapsed time only.
515
+ After either path, use `qa: { "attached": true, ... }` for a current-session smoke check without opening a URL. Attached QA preserves existing network/console/page-error buffers instead of clearing them, so it can catch errors raised before the check started; visible output and `details.compiledQaPreset.checks.diagnosticsResetAtStart` identify that scope. Prefer condition waits (`wait --text`, `wait --url`, `wait --fn`, `wait --load <state>`, `wait --download`), `qa.attached`, `electron.probe` / `electron.status`, `tab list` → `tab t<N>`, fresh snapshots, or screenshots over blind sleeps. Fixed waits are a last resort: use explicit `--timeout` or top-level `timeoutMs` for legitimately slow waits, and treat a result like `"waited":"timeout"` as elapsed time only. Batch output promotes dispatch-only mutation evidence and states that fixed waits are not postconditions.
442
516
 
443
517
  ### Lightweight QA preset
444
518
 
445
- For a quick smoke/QA pass, use top-level `qa`. It compiles to the same batch path as `job` and uses `batch --bail` so failed readiness/text/selector assertions stop before slower diagnostics can burn the wrapper watchdog. The URL form clears enabled network/console/page-error buffers before opening the target URL, waits for page readiness, checks optional expected text or selector, inspects fresh network requests, console messages, and page errors when preceding assertions pass, and can capture an evidence screenshot. Successful reset rows are labeled as reset-scoped output and ignored by QA failure analysis so stale pre-target errors do not fail an otherwise healthy target page; real post-open diagnostic rows still fail or warn according to the normal QA rules. Expected text is checked with bounded visible-text `wait --fn … --timeout 5000` predicates after the requested load state so dense pages can pass on visible headings/copy and missing text becomes crisp QA evidence. The attached form (`qa: { "attached": true }`) runs checks against the current managed session, such as an attached Electron app, rejects `url`, and deliberately preserves existing diagnostics instead of clearing evidence; its diagnostic reads default off so stale buffers do not fail a current-page smoke unless `checkNetwork`, `checkConsole`, or `checkErrors` is explicitly `true`. `loadState` defaults to `"domcontentloaded"`; set it to `"load"` or `"networkidle"` only when the stricter state is useful and the site is not expected to keep background requests alive. For URL-opening QA, `checkNetwork`, `checkConsole`, and `checkErrors` default to true; set one to `false` to skip that diagnostic read. Network failures are classified by likely impact and failed rows are listed first in network previews: actionable document/script/API-style failures still fail QA, while some low-impact browser icon asset misses (for example certain `favicon` or `apple-touch-icon` paths when upstream marks the row failed and resource metadata looks image-like) surface only as warnings instead of failing an otherwise healthy smoke check (`details.qaPreset.warnings`, with human-readable `details.qaPreset.summary` when the preset still passes). Exact predicates live in [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#qa) and `classifyNetworkRequestFailure` in `extensions/agent-browser/lib/results/network.ts`.
519
+ For a quick smoke/QA pass, use top-level `qa`. It compiles to the same batch path as `job` and uses `batch --bail` so failed readiness/text/selector assertions stop before slower diagnostics can burn the wrapper watchdog. The URL form clears enabled network/console buffers and snapshots any page-error residue after the unreliable upstream clear, then opens the target URL and gives immediate post-load console/page-error callbacks a bounded 150 ms settle, waits for page readiness, checks optional expected text or selector, inspects fresh network requests, console messages, and page errors when preceding assertions pass, and can capture an evidence screenshot. Successful reset rows are labeled as reset-scoped output. Only page-error residue still present after the clear can be ignored when it remains unchanged; a matching error that reappears after a successful clear still fails the target page. Expected text is checked with bounded visible-text `wait --fn … --timeout 5000` predicates after the requested load state so dense pages can pass on visible headings/copy and missing text becomes crisp QA evidence. The attached form (`qa: { "attached": true }`) runs checks against the current managed session, such as an attached Electron app, rejects `url`, and deliberately preserves existing diagnostics instead of clearing evidence; its diagnostic reads default off so stale buffers do not fail a current-page smoke unless `checkNetwork`, `checkConsole`, or `checkErrors` is explicitly `true`. `loadState` defaults to `"domcontentloaded"`; set it to `"load"` or `"networkidle"` only when the stricter state is useful and the site is not expected to keep background requests alive. For URL-opening QA, `checkNetwork`, `checkConsole`, and `checkErrors` default to true; set one to `false` to skip that diagnostic read. Network failures are classified by likely impact and failed rows are listed first in network previews: actionable document/script/API-style failures still fail QA, while some low-impact browser icon asset misses (for example certain `favicon` or `apple-touch-icon` paths when upstream marks the row failed and resource metadata looks image-like) surface only as warnings instead of failing an otherwise healthy smoke check (`details.qaPreset.warnings`, with human-readable `details.qaPreset.summary` when the preset still passes). Exact predicates live in [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#qa) and `classifyNetworkRequestFailure` in `extensions/agent-browser/lib/results/network.ts`.
446
520
 
447
521
  ```json
448
522
  {
@@ -466,22 +540,22 @@ For local app debugging, `sourceLookup` can gather candidate component/file loca
466
540
 
467
541
  This is an experiment, not a guarantee. React hints require a session opened with `--enable react-devtools`, and many builds do not expose useful sourcemap/source metadata; `status: "no-candidates"` is common when nothing matched, and `status: "unsupported"` only when no candidates were found **and** a compiled `react` batch step failed (if DOM or workspace search still produced candidates, you get `candidates-found` instead). For wrapper-tracked packaged Electron apps, a no-candidate result includes `details.sourceLookup.workspaceRoot`, optional `details.sourceLookup.electronContext`, limitations explaining that the scan is limited to the Pi cwd and does not unpack app bundles/`app.asar`, plus Electron snapshot/probe/tab next actions when a launch is known.
468
542
 
469
- `networkSourceLookup` is the matching failed-request experiment. It runs `network request <id>` when `requestId` is present and/or `network requests --filter …` when `filter` or `url` is present (`url` supplies the filter pattern when `filter` is omitted); add `namespace` / `session` when the generated batch should target an explicit upstream namespace/session. It merges failed-request rows from the batch JSON with initiator-style hints and a bounded workspace literal scan (`maxWorkspaceFiles` defaults to 2000, cap 5000), surfaces everything under `details.networkSourceLookup`, and avoids automatic blame or edits. Compact `network requests` results with safe request IDs also add `details.nextActions` for request details, bounded `networkSourceLookup` on actionable failures, path filtering, diagnostic-buffer clearing before a repro, or HAR capture so agents can branch without guessing request-id syntax. For noisy aggregate buffers, wrapper-side `network requests --current-page` / `--current-origin` keeps only rows matching the active page origin, while `--current-url` keeps exact active-document URL rows and reports counts in `details.networkRequestsPageFilter`. When the wrapper has seen `network route` in the same session, pending fetch/XHR rows or CORS-looking errors that match the route surface `details.networkRouteDiagnostics` plus executable follow-ups to inspect the request or start HAR capture; same-origin/CORS-correct fixture retry guidance stays in prose. Network diagnostics are read-only for wrapper page state: request URLs in `network request` or generated `networkSourceLookup` batches do not replace the session’s active page target or invalidate page-scoped refs from the app page.
543
+ `networkSourceLookup` is the matching failed-request experiment. It runs `network request <id>` when `requestId` is present and/or `network requests --filter …` when `filter` or `url` is present (`url` supplies the filter pattern when `filter` is omitted); add `namespace` / `session` when the generated batch should target an explicit upstream namespace/session (`namespace: ""` explicitly selects the default namespace and overrides an ambient namespace). It merges failed-request rows from the batch JSON with initiator-style hints and a bounded workspace literal scan (`maxWorkspaceFiles` defaults to 2000, cap 5000), surfaces everything under `details.networkSourceLookup`, and avoids automatic blame or edits. Compact `network requests` results with safe request IDs also add `details.nextActions` for request details, bounded `networkSourceLookup` on actionable failures, path filtering, diagnostic-buffer clearing before a repro, or HAR capture so agents can branch without guessing request-id syntax. For noisy aggregate buffers, wrapper-side `network requests --current-page` / `--current-origin` keeps only rows matching the active page origin, while `--current-url` keeps exact active-document URL rows and reports counts in `details.networkRequestsPageFilter`. When the wrapper has seen `network route` in the same session, pending fetch/XHR rows or CORS-looking errors that match the route surface `details.networkRouteDiagnostics` plus executable follow-ups to inspect the request or start HAR capture; same-origin/CORS-correct fixture retry guidance stays in prose. Network diagnostics are read-only for wrapper page state: request URLs in `network request` or generated `networkSourceLookup` batches do not replace the session’s active page target or invalidate page-scoped refs from the app page.
470
544
 
471
545
  ```json
472
546
  { "networkSourceLookup": { "requestId": "req-1", "url": "/api/fail" } }
473
547
  ```
474
548
 
475
- For asynchronous exports, click first and then wait for the download:
549
+ For asynchronous exports, use the export control's current snapshot ref (for example `@e5`), then wait for the download:
476
550
 
477
551
  ```json
478
- { "args": ["click", "@export"] }
552
+ { "args": ["click", "@e5"] }
479
553
  { "args": ["wait", "--download", "/tmp/report.csv"] }
480
554
  ```
481
555
 
482
556
  When a user gives exact artifact paths for screenshots, recordings, downloads, PDFs, traces, or HAR files, use those paths or explicitly report why the artifact was unavailable; do not silently substitute a different path in the final report. The wrapper creates missing parent directories for direct artifact paths such as `state save`, screenshots, PDFs, downloads, and `wait --download`. For simple loopback `download <selector> <path>` anchor links with HTTP(S) `href`, it can save the in-page response directly to the requested path before falling back to upstream click/download behavior; non-loopback/profile downloads stay upstream-owned. With current upstream `agent-browser`, treat `details.savedFilePath` as upstream-reported metadata and confirm `details.artifacts[].exists` / `details.artifactVerification.verified` before relying on the requested `wait --download <path>` file being present on disk; non-file download payloads such as `data:` URLs are not verified local artifacts.
483
557
 
484
- For evidence-only screenshots or QA captures, branch on `details.artifactVerification` and `details.artifacts` before reporting PASS/FAIL; inline image attachments are optional when size limits allow—do not require vision review unless the user asked for visual inspection. If the latest prompt names exact required artifact paths, browser close can be blocked with `details.promptGuard` until those artifacts are saved and verified.
558
+ For evidence-only screenshots or QA captures, branch on `details.artifactVerification` and `details.artifacts` before reporting PASS/FAIL; a pre-existing path from an artifact-producing command that was not updated during the command fails as `status: "stale"` instead of being accepted as fresh evidence (`wait --download` remains observational). Inline image attachments are optional when size limits allow—do not require vision review unless the user asked for visual inspection. If the latest prompt names exact required artifact paths, browser close can be blocked with `details.promptGuard` until those artifacts are saved and verified.
485
559
 
486
560
  Artifact cleanup is host-owned, not a browser command. Close commands (`close`, `quit`, or `exit`) shut down the browser session but do **not** delete explicit screenshots, downloads, PDFs, traces, HAR files, or recordings saved to paths you chose. When the session’s non-empty `details.artifactManifest` is in scope, a successful close command appends a compact `Artifact lifecycle` note and sets `details.artifactCleanup` with the same retention summary as `details.artifactRetentionSummary`, a fixed `note` about host-owned cleanup, and `explicitArtifactPaths`: up to ten distinct paths from manifest rows whose `storageScope` is `explicit-path` (this list can be empty if the recent window only holds spills or other non-explicit inventory). Remove any listed paths with normal file tools after inspection.
487
561
 
@@ -509,15 +583,15 @@ The wrapper does not clone profiles or hide what upstream Chrome/Chromium profil
509
583
  Use these rules:
510
584
 
511
585
  - Use public/temp profiles for tests and examples.
512
- - Do not assume `--profile Default` is correct. Ask the agent to run `profiles` to list Chrome profile directory names, then `doctor` if profile/user-data-dir resolution still fails.
586
+ - Do not assume `--profile Default` is correct. Ask the agent to run `profiles` to list Chrome profile directory names, then `doctor` if profile/user-data-dir resolution still fails. On macOS, a copied Chrome profile may omit Keychain-encrypted cookies, so profile selection is not proof that the target page is authenticated; verify the page and use a user-approved headed login once when needed.
513
587
  - For non-Chrome Chromium browsers such as Brave, Edge, Arc, or Vivaldi, use `--executable-path <path>` when upstream can launch that executable. If you need that browser's existing login state, use the browser's real profile/user-data directory path when upstream accepts it, or attach with `--auto-connect` / `connect` to a debug-enabled running browser when appropriate.
514
588
  - Use `sessionMode: "fresh"` when switching from public browsing to `--allowed-domains`, `--profile`, `--executable-path`, `--webgpu`, `--restore`, `--restore-save`, restore check flags, `--namespace`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, `--enable`, `-p` / `--provider`, or iOS `--device`.
515
589
  - Use `--session` when you want to manage a live upstream session name yourself. For CDP, connect once, verify with `get url`, keep using that session without repeating `--cdp`, and close it explicitly when done. The wrapper preserves the established attachment across follow-ups instead of resending local-launch defaults, and live-checks the URL before later page reads or interactions because an attached browser can change tabs outside Pi.
516
- - Do not treat an arbitrary `--session` name alone as persisted auth after `close`, `quit`, or `exit`. Wrapper-owned managed sessions automatically set a Git-checkout-generation-stable `AGENT_BROWSER_RESTORE` key so cookies/localStorage/sessionStorage survive browser relaunches across Pi chats in the same checkout; the key follows a renamed checkout but changes when that path is replaced or copied, and automatic restore fails closed outside a Git checkout. The wrapper combines the checkout-root and Git-admin filesystem identities with a generation UUID in the Git admin directory and never adopts older cwd-only keys; a bare caller `--session` name does not get that injection, and the wrapper reserves `piab-*` names case-insensitively so another Pi process cannot attach to a managed authenticated browser through a case alias. Disable with `PI_AGENT_BROWSER_MANAGED_SESSION_RESTORE=0`. For explicit non-managed sessions use `--session <id> --restore`, `--profile`, or `--state`. SSO/2FA such as Okta Touch ID may still need one human approval (often `--headed` the first time); after that, managed restore should keep the session without a manual `state save` dance. Any upstream `agent-browser.json` / `AGENT_BROWSER_CONFIG` / `--config` discovered while planning blocks browser-backed native calls without reading it, while accepted browser-backed spawns pin a process-private empty config to close config-creation races. This is separate from this package's trusted Pi-scoped config; sessionless local/setup commands retain upstream config behavior. Raw batch argv, batch stdin containing nested `connect`/`batch`, browser mutation flag, or matching launch-mutation env disables automatic managed restore rather than risking restored auth in a caller-customized or attached browser. Every accepted browser-backed subprocess, including wrapper-owned close, pins `AGENT_BROWSER_CONFIG` to that process-private empty config (`0400` on POSIX) in the marked secure-temp lifecycle so a project or user config created between planning and spawn cannot change the browser. A user-private immutable ticket-claim lock serializes each same-identity daemon inspection through the receiving spawn and bridges the pre-update v2 lock path; every lock winner re-inspects the live daemon, and abandoned v2 locks fail closed rather than being reclaimed unsafely. POSIX process identity probes use absolute `/bin/ps` then `/usr/bin/ps` paths. Before an incompatible call, the wrapper inspects the actual same-identity daemon and blocks when it retains any restore key, cannot be inspected, or reports restore-disabled policy without current-process provenance, including daemons missing from transcript state and sessions launched with explicit restore keys. Same-process `session_tree` transitions retain recorded provenance; extension reload, restart, and `/resume` deliberately do not trust transcript-only provenance for a still-live restore-disabled daemon, so close it first, omit the explicit session and use `sessionMode: "fresh"`, or choose a distinct explicit session. If inspection instead proves the old daemon inactive, the next owned no-restore spawn records that null policy so subsequent follow-ups remain usable. Wrapper-owned subprocesses pin the canonical namespace, including an explicit empty default, so a parent `AGENT_BROWSER_NAMESPACE` cannot redirect close or helper calls; Electron status target reads and current-managed probes also acquire the same daemon-policy lock, verify the live URL before title/content reads, and apply the same restore decision to every underlying read. A probe whose reads all fail is an `upstream-error`, not a successful empty partial result. Current-managed probe results persist their namespace and ref state for Pi reload/branch replay. Upstream restore files live under `~/.agent-browser/` and are plaintext unless you set `AGENT_BROWSER_ENCRYPTION_KEY`; on POSIX the wrapper canonicalizes and pins `HOME` after caller env merging, requires owner-trusted non-writable ancestry, requires stable device/inode/birth-time metadata for both checkout and Git-admin directories, enforces mode `0700` without silently tightening unsafe existing directories, and rejects symlinks/non-directories along the exact restore `sessions` path and its `.tmp` write area before automatic managed restore. Windows automatic managed restore requires an absolute `USERPROFILE` and the documented 64-character hex `AGENT_BROWSER_ENCRYPTION_KEY` because POSIX mode checks cannot verify profile ACLs. Wrapper-owned close commands discard caller config/restore globals, preserve the live daemon's existing restore key instead of injecting one derived from a possibly replaced checkout, and record a returned old-generation snapshot against that observed wrapper key. If a fresh command starts agent-browser but then fails, the wrapper probes that exact identity and retains a live or uninspectable daemon for shutdown cleanup instead of abandoning it. After a wrapper-owned managed session closes successfully, the wrapper persists the returned state path as an atomic record in a lockless convergent per-key ownership directory (`0700`, with `0600` records, on POSIX), keeps the two newest proven snapshots for its exact restore key across Pi restarts, self-heals malformed regular records, removes additional proven snapshots older than 30 days, expires ownership-proven snapshots and empty manifests from older restore-key generations only when a private lineage record proves the same canonical checkout path, after 30 days, and caps young close churn at 256 records per restore key; unrecorded matching files and the current checkout key remain untouched. Managed restore keys and key-bearing paths are redacted from tool output and transcripts. `session list` and `state list` hide wrapper-managed rows; cross-checkout managed `--restore` / `--state` / state-file access, broad `state clear`, `state clean`, and managed save/rename targets are rejected before spawn. Browser access to `.agent-browser` storage is blocked through command-specific file operands (including dash-prefixed values), every path-bearing upstream environment mirror (including state/profile/config, executable/extension/init-script, action-policy, artifact, skills, and socket paths), encoded, nested-file-scheme, Windows-aliased, or symlinked targets (including not-yet-created descendants of symlinked directories), content-returning local-URL commands, protected artifact destinations and top-level `outputPath`, local-page follow-ups, and persisted unverified top-level or batch tab/attachment/script/state-load transitions. Raw batch command strings are split on literal ASCII spaces exactly like upstream and inspected recursively just like batch stdin arrays; Electron launch handoffs, probes, and later capture share the same boundary: snapshot/tabs handoff and probes verify the live URL before tab/title/content helpers, and cancellation during handoff closes the managed session plus process/profile. The wrapper rejects enabled `--allow-file-access` argv/env plus file-access-enabling or protected-path `--args` / `AGENT_BROWSER_ARGS` values, removes caller file-access occurrences, clears raw-args env, and adds canonical `--args "" --allow-file-access false` defaults on local-browser spawns so project/user config cannot silently preserve local-page filesystem access; attached-session follow-ups omit those launch-only flags; an explicit safe CLI `--args` value may still override the empty default. Post-transition summaries, including after arbitrary `eval`, verify the live URL before title and fail implicit transitions to local file pages; failed navigation attempts remain unverified, and stale concurrent completions cannot overwrite newer unknown page state. `get url`, `tab list`, non-content `tab <id>` selection, explicit safe navigation away, and session/tab close remain available for recovery; tab selection stays unverified until `get url` succeeds. The wrapper repeats checkout, storage, environment, managed-session ownership, and managed-state access validation after async config/socket setup immediately before spawn. On POSIX the selected daemon socket directory must be absolute, current-user-owned, mode `0700`, under trusted ancestry, and free of symlink, foreign-owner, or special planted entries. Pre-existing unsafe modes are rejected rather than repaired, and the check is repeated immediately before spawn. On native Windows, command-first launcher reordering moves only syntactically valid leading globals, rewrites a valued `--restore <name>` as `--restore=<name>` to preserve upstream optional-value semantics, and leaves invalid or command-scoped leading tokens untouched. Upstream periodically saves restore-enabled cookies/localStorage while the browser is open; `AGENT_BROWSER_AUTOSAVE_INTERVAL_MS` defaults to `30000`, `0` disables periodic saves but keeps save-on-close, and the `never` value for `--restore-save` disables automatic saves for that restore session.
517
- - Caller-owned explicit sessions are live-checked with `get url` before content-bearing reads or interactions. Missing or stale transcript page state is not treated as proof of a safe target; if the live URL cannot be verified, the requested content command does not run. Calls to the same effective canonical namespace/session are serialized inside one extension instance; explicit namespace argv overrides `AGENT_BROWSER_NAMESPACE`, including an explicit empty default from that probe through any semantic-action snapshot and the requested command, while different caller-owned sessions remain independent. Raw non-bail batches are rejected when a failed navigation could expose prior local or unverified page content; use exact `batch --bail` or split navigation from content. Protected Windows paths include drive-relative forms such as `C:.agent-browser\\state\\...`. Nested `batch` steps are rejected, and raw batch command strings mirror upstream's ASCII-space tokenizer, including its single/double-quote and backslash handling, without splitting on other Unicode whitespace.
590
+ - Do not treat an arbitrary `--session` name alone as persisted auth after `close`, `quit`, or `exit`. Wrapper-owned implicit sessions automatically use a Pi-transcript- and Git-checkout-generation-scoped `AGENT_BROWSER_RESTORE` key so cookies and web storage can survive relaunch, reload, and `/resume`; disable that convenience with `PI_AGENT_BROWSER_MANAGED_SESSION_RESTORE=0`. Caller-selected sessions, restore/state paths, profiles, upstream config, file access, launch arguments, environment variables, and close arguments pass through unchanged. `session list` and `state list` keep all upstream rows and restore identifiers visible. The wrapper does not reserve `piab-*` names or reject cross-checkout/local paths. Automatic restore still validates its own checkout/storage identity and coordinates same-daemon reuse so it cannot mix the wrapper's restore pools.
591
+ - Caller-owned explicit sessions are live-checked with `get url` before content-bearing reads or interactions. Missing or stale transcript page state is not treated as proof of a target; if the live URL cannot be verified, the requested content command does not run. Calls to the same effective canonical namespace/session are serialized inside one extension instance; explicit namespace argv overrides `AGENT_BROWSER_NAMESPACE`, including an explicit empty default from that probe through any semantic-action snapshot and the requested command, while different caller-owned sessions remain independent. Raw non-bail batches are rejected when a failed navigation could leave an unverified target before later content; use exact `batch --bail` or split navigation from content. Nested `batch` steps are rejected, and raw batch command strings mirror upstream's ASCII-space tokenizer, including its single/double-quote and backslash handling, without splitting on other Unicode whitespace.
518
592
  - Prefer page actions and storage checks over cookie dumps. `cookies get` can expose real profile cookies.
519
593
  - Prefer `auth save --password-stdin` over putting passwords in `args`; the wrapper only accepts caller `stdin` for `batch`, `eval --stdin`, and `auth save --password-stdin` (top-level `job` and `qa` compile to `batch` and supply their own stdin).
520
- - Use `state save <path>` / `state load <path>` for portable test state. `state save` is reported as a file artifact with verification metadata; if an upstream-successful artifact command reports a non-pending file path that the wrapper cannot find on disk, the tool fails with `failureCategory: "artifact-missing"` instead of treating the path as durable. `state load` may mention a path but is not treated as a newly saved artifact.
594
+ - Use `state save <path>` / `state load <path>` for portable test state. `state save` is reported as a file artifact with verification metadata; if an upstream-successful artifact command reports a non-pending file path that the wrapper cannot find or did not update during this command, the tool fails with `failureCategory: "artifact-missing"` instead of treating missing/stale evidence as durable. `state load` may mention a path but is not treated as a newly saved artifact.
521
595
  - Treat `cookies get`, `storage local|session`, `state show`, and `auth show` output as sensitive. `state show` is presented as saved-state metadata only, and cookie/localStorage/sessionStorage values are redacted from structured details. The native presentation summarizes and redacts credential-like values while allowing benign primitive storage values to aid local QA, but avoid requesting broad dumps unless the task needs them.
522
596
  - Use `dialog status`, `dialog accept [text]`, `dialog dismiss`, and `frame <selector|main>` through native `args`; dialog commands use a shorter wrapper timeout and timed-out interactions add `inspect-dialog-after-timeout` / `dismiss-dialog-after-timeout` / fresh-session recovery actions so a blocking alert/prompt does not burn the full default watchdog. Use exact `confirm <id>` / `deny <id>` next actions for guarded-action confirmations.
523
597
 
@@ -623,7 +697,7 @@ The opt-in real-upstream suite is separate because it drives a real browser inst
623
697
  npm run verify -- real-upstream
624
698
  ```
625
699
 
626
- That mode sets `PI_AGENT_BROWSER_REAL_UPSTREAM=1` and runs `test/agent-browser.real-upstream-contract.test.ts` against the real `agent-browser` on `PATH` (version must match the capability baseline). It covers inspection, skills, a broad core interaction and navigation matrix on localhost fixtures (including off-viewport click, frame-scoped selector/wait/click behavior, form command fixes, `batch` stdin, and `pushstate`), plus `vitals`, network route/requests/HAR, diff snapshot/screenshot/url, trace/profiler, console/errors/highlight, stream enable/status/disable, `cookies set --curl`, a `react tree` missing-renderer path, and `wait --download` with the on-disk caveat documented in release notes. The harness uses a throwaway temp `HOME` and dedicated socket/screenshot directories so the run does not touch your normal browser profile paths. Browser-opening or credential-dependent families such as `inspect`, `dashboard`, `chat`, provider clouds, and OS clipboard flows stay in fake-upstream or manual validation unless a safe deterministic fixture is added. For prerequisites, isolation details, and troubleshooting, see [`docs/RELEASE.md`](docs/RELEASE.md#real-upstream-contract-validation).
700
+ That mode sets `PI_AGENT_BROWSER_REAL_UPSTREAM=1` and runs `test/agent-browser.real-upstream-contract.test.ts` against the real `agent-browser` on `PATH` (the stable version must meet the 0.35.0 floor; current command-reference validation targets the recommended 0.36.0 capability baseline). It covers inspection, skills, experimental WebMCP list/invoke/result/cancel plus `--no-webmcp`, and a broad core interaction and navigation matrix on localhost fixtures (including off-viewport click, frame-scoped selector/wait/click behavior, form command fixes, `batch` stdin, and `pushstate`), plus `vitals`, network route/requests/HAR, diff snapshot/screenshot/url, trace/profiler, console/errors/highlight, stream enable/status/disable, `cookies set --curl`, a `react tree` missing-renderer path, and `wait --download` with the on-disk caveat documented in release notes. The harness uses a throwaway temp `HOME` and dedicated socket/screenshot directories so the run does not touch your normal browser profile paths. Browser-opening or credential-dependent families such as `inspect`, `dashboard`, `chat`, provider clouds, and OS clipboard flows stay in fake-upstream or manual validation unless a safe deterministic fixture is added. For prerequisites, isolation details, and troubleshooting, see [`docs/RELEASE.md`](docs/RELEASE.md#real-upstream-contract-validation).
627
701
 
628
702
  A deterministic host-only live-browser wrapper smoke is available without an LLM choosing tool calls:
629
703
 
@@ -631,7 +705,7 @@ A deterministic host-only live-browser wrapper smoke is available without an LLM
631
705
  npm run verify -- dogfood
632
706
  ```
633
707
 
634
- That mode drives the native wrapper through top-level `qa`, `semanticAction`, constrained `job`, screenshot artifact verification, and session close against a deterministic local fixture. It complements, but does not replace, the interactive Pi/tmux release dogfood in [`docs/RELEASE.md`](docs/RELEASE.md#pre-release-checks).
708
+ That mode clean-builds the package, then drives the native wrapper through top-level `script` branching/aggregation, `qa`, `semanticAction`, constrained `job`, screenshot artifact verification, and session close against a deterministic local fixture. It complements, but does not replace, the interactive Pi/tmux release dogfood in [`docs/RELEASE.md`](docs/RELEASE.md#pre-release-checks).
635
709
 
636
710
  Cross-platform release coverage uses Crabbox to run macOS, Ubuntu Linux, and native Windows target suites; see [`docs/platform-smoke.md`](docs/platform-smoke.md) for the required matrix, standalone coverage (`npm run smoke:platform:all` and per-target `smoke:platform:macos` / `:ubuntu` / `:windows-native`), and artifact/lease inspection. The release gate is:
637
711
 
@@ -650,21 +724,21 @@ npm run verify -- release
650
724
  `pi-agent-browser-native` is intentionally thin:
651
725
 
652
726
  1. Pi loads the compiled `dist/extensions/agent-browser/index.js` entrypoint from the package manifest; TypeScript under `extensions/` remains the source of truth and `npm run build` regenerates `dist/` before packing.
653
- 2. The extension registers one native tool named `agent_browser`.
727
+ 2. The extension registers `agent_browser` and, when enabled with a usable credential source, the optional `agent_browser_web_search` companion.
654
728
  3. Tool calls are translated into upstream `agent-browser` CLI invocations with controlled args, stdin, environment, timeout, and session planning.
655
729
  4. Upstream JSON/plain-text output is parsed into model-friendly content and structured details.
656
730
  5. Screenshots, downloads, recordings, traces, profiles, and spill files are normalized as Pi-visible artifacts where possible.
657
731
  6. Generated playbook text in docs and tool metadata stays aligned with `extensions/agent-browser/lib/playbook.ts`.
658
732
 
659
- The upstream browser engine remains [`agent-browser`](https://agent-browser.dev/). This package does not bundle it and does not maintain compatibility shims for old upstream versions.
733
+ The upstream browser engine remains [`agent-browser`](https://agent-browser.dev/). This package does not bundle it. The recommended baseline is 0.36.0 and the stable runtime floor is 0.35.0; newer stable versions are accepted without version-specific compatibility shims.
660
734
 
661
735
  ## Current limits
662
736
 
663
737
  - Published pre-1.0 package.
664
- - Targets the current locally installed upstream `agent-browser` version only.
738
+ - Recommends upstream `agent-browser` 0.36.0 and accepts stable runtimes at or above 0.35.0.
665
739
  - Does not bundle `agent-browser`; users install it separately.
666
740
  - Does not provide a human browser UI inside Pi; the primary UX is agent-invoked tool calls. `--headed` asks upstream to show a browser window, but the wrapper cannot yet prove that the window is visible on the user's desktop.
667
- - Localhost means the browser host's loopback, not necessarily the shell/Pi host. If `http://localhost:<port>` or `http://127.0.0.1:<port>` fails with errors such as `ERR_EMPTY_RESPONSE`, use an environment-specific host-reachable HTTP(S) address. Do not switch the native wrapper to a `file://` fixture: follow-up inspection and interaction on local file pages is blocked to protect authenticated `.agent-browser` state.
741
+ - Localhost means the browser host's loopback, not necessarily the shell/Pi host. If `http://localhost:<port>` or `http://127.0.0.1:<port>` fails with errors such as `ERR_EMPTY_RESPONSE`, use an environment-specific host-reachable HTTP(S) address. A `file://` fixture is supported when upstream browser launch settings allow it; use HTTP(S) only when the browser environment cannot reach the local file.
668
742
  - A successful upstream `click` is not proof that the app handled the event. For state-changing flows, verify with a fresh snapshot, text/URL assertion, screenshot, or `pageChangeSummary` before reporting success.
669
743
  - Real authenticated profile use is powerful but sensitive. Treat profile and cookie access as user-approved, task-specific behavior.
670
744
  - Wrapper tab/session recovery is best effort around observed upstream behavior, not a replacement for explicit profile/session design.
@@ -677,15 +751,15 @@ Install upstream `agent-browser`, then install dependencies:
677
751
  npm install
678
752
  ```
679
753
 
680
- Use the npm version declared in `package.json` `packageManager` when refreshing `package-lock.json` (for example `npx -y npm@11.14.0 install`) so optional-platform lockfile metadata does not drift. Align the global `pi` CLI with this repo’s `pi-coding-agent` devDependency range before lifecycle or interactive browser smokes. See [Environment and automation pitfalls](docs/RELEASE.md#environment-and-automation-pitfalls) in `docs/RELEASE.md`.
754
+ Use the npm version declared in `package.json` `packageManager` when refreshing `package-lock.json` (for example `npx -y npm@11.14.0 install`) so optional-platform lockfile metadata does not drift. Use Pi 0.84.0 or newer for lifecycle and interactive browser smokes; the pinned Pi devDependencies are validation fixtures, not an exact-version requirement for the host CLI. See [Environment and automation pitfalls](docs/RELEASE.md#environment-and-automation-pitfalls) in `docs/RELEASE.md`.
681
755
 
682
- Quick isolated checkout smoke test:
756
+ Checkout-only extension smoke test:
683
757
 
684
758
  ```bash
685
759
  pi --approve --no-extensions -e .
686
760
  ```
687
761
 
688
- This bypasses Pi settings and configured extensions while explicitly trusting this checkout's project-local inputs for the run. Omit `--approve` when you want to exercise Pi's interactive Project Trust prompt instead. After editing extension code, restart that Pi process to test the new checkout.
762
+ This selects the checkout extension and disables automatic extension loading; Pi settings and configured package resolution remain active. Use temporary `HOME` and `PI_CODING_AGENT_DIR` directories for isolated test settings, and `PI_OFFLINE=1` to disable automatic startup network/update operations. `--approve` trusts this checkout's project-local inputs; omit it when testing the Project Trust prompt. After editing extension code, restart Pi to test the new checkout.
689
763
 
690
764
  For a concrete expanded native-tool smoke matrix (version/help/skills through dashboard/chat families), see [Local development validation](docs/RELEASE.md#local-development-validation) in `docs/RELEASE.md`. For bounded release smokes that should validate this extension rather than skill routing, use the [Sauce Demo smoke prompt](docs/RELEASE.md#public-sauce-demo-checkout-smoke-prompt), which adds `--no-skills`. When changes affect dense dashboards, diagnostics, artifacts, recording, scroll, or combobox behavior, use the public [Grafana stress checklist](docs/RELEASE.md#public-grafana-stress-checklist) for repeatable release dogfood without bundling private skills or recipes.
691
765
 
@@ -726,6 +800,7 @@ These calls return plain text and stay stateless: the extension does not inject
726
800
  - After the wrapper observes tab-drift risk for a session (for example open correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab or ref snapshot is known.
727
801
  - For sessions with observed tab-drift risk, 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. Routine same-session commands skip this post-command tab-list probe.
728
802
  - If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.
803
+ - If upstream reports tab_gone, the pinned bound tab is gone; use details.nextActions (tab list / tab new) instead of assuming another tab is yours.
729
804
  <!-- agent-browser-playbook:end wrapper-tab-recovery -->
730
805
 
731
806
  ## Project map
@@ -736,7 +811,8 @@ These calls return plain text and stay stateless: the extension does not inject
736
811
  | `extensions/agent-browser/lib/runtime.ts` | Argv parsing, session planning, redaction, and execution-plan helpers (pure planning; subprocess wiring lives beside the entrypoint) |
737
812
  | `extensions/agent-browser/lib/results/` | Model-facing result rendering and error guidance |
738
813
  | `extensions/agent-browser/lib/playbook.ts` | Canonical generated agent/browser guidance |
739
- | `scripts/agent-browser-capability-baseline.mjs` | Target upstream version, help samples, and doc/token inventory for drift checks |
814
+ | `scripts/agent-browser-target.mjs` | Canonical recommended target and minimum supported stable version shared by runtime and build-time checks |
815
+ | `scripts/agent-browser-capability-baseline.mjs` | Help samples and doc/token inventory for drift checks; imports the canonical target version |
740
816
  | `scripts/check-command-reference-baseline.mjs` | Regenerates or verifies HTML-bounded baseline blocks in `docs/COMMAND_REFERENCE.md` (via `npm run docs -- command-reference …`) |
741
817
  | `docs/COMMAND_REFERENCE.md` | Repo-readable native command reference |
742
818
  | `docs/TOOL_CONTRACT.md` | Tool parameters, result shape, and behavior contract |