pi-agent-browser-native 0.2.71 → 0.2.74

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 (52) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +14 -12
  3. package/dist/extensions/agent-browser/index.js +104 -16
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +124 -0
  5. package/dist/extensions/agent-browser/lib/command-taxonomy.js +12 -1
  6. package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
  7. package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
  8. package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
  9. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
  10. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
  11. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
  12. package/dist/extensions/agent-browser/lib/managed-session-restore.js +367 -0
  13. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
  14. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
  15. package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
  16. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  24. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  25. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  26. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +625 -429
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +136 -56
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +28 -40
  29. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
  30. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
  31. package/dist/extensions/agent-browser/lib/playbook.js +10 -9
  32. package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
  33. package/dist/extensions/agent-browser/lib/process.js +270 -34
  34. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
  35. package/dist/extensions/agent-browser/lib/results/categories.js +21 -2
  36. package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
  37. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +80 -12
  38. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
  39. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
  40. package/dist/extensions/agent-browser/lib/runtime.js +85 -85
  41. package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
  42. package/dist/extensions/agent-browser/lib/temp.js +13 -25
  43. package/docs/ARCHITECTURE.md +9 -8
  44. package/docs/COMMAND_REFERENCE.md +97 -32
  45. package/docs/ELECTRON.md +10 -10
  46. package/docs/RELEASE.md +3 -2
  47. package/docs/SUPPORT_MATRIX.md +22 -19
  48. package/docs/TOOL_CONTRACT.md +31 -28
  49. package/docs/platform-smoke.md +5 -5
  50. package/package.json +1 -1
  51. package/platform-smoke.config.mjs +3 -1
  52. package/scripts/agent-browser-capability-baseline.mjs +45 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.74 - 2026-08-03
4
+
5
+ ### Changed
6
+
7
+ - Rebaselined the command/help inventory and package docs to `agent-browser 0.33.2` / vercel-labs/agent-browser@93cdda5709e8861c0c26b0b955d8d746e9fda0d7 (0.33.1 daemon idle timeout + tab recovery; 0.33.2 stream quality/size envs and latest-wins streaming).
8
+ - Wrapper-owned managed sessions now set a Git-checkout-generation-stable `AGENT_BROWSER_RESTORE` key so SSO cookies/localStorage/sessionStorage survive browser relaunches across Pi chats in the same checkout generation. The wrapper combines checkout-root and Git-admin filesystem identities with a UUID in the Git admin directory, keeps the key across checkout renames, changes it on copied/replaced checkouts, and fails closed outside Git instead of adopting older cwd-only keys. Restore is ownership-gated, and `piab-*` live-session names are reserved for the extension instance that owns them, sticky-disabled after incompatible launches/config (profiles, CDP/providers, extensions/init scripts/raw args/plugins, and related browser mutation), and opt-out with `PI_AGENT_BROWSER_MANAGED_SESSION_RESTORE=0`. Any upstream config discovered while planning blocks browser-backed native calls without being read by the Pi host, while accepted browser-backed subprocesses, including wrapper-owned closes, pin a process-private empty config (`0400` on POSIX) in the marked secure-temp lifecycle to prevent later config creation from changing the receiving browser while preserving PID/start-identity abnormal-exit stale cleanup on POSIX and native Windows while treating legacy Windows identity formats conservatively; ownership marker schema v2 makes older readers ignore new Windows identity records. A user-private immutable ticket-claim lock with PID/start-identity dead-claim/artifact recovery and a fail-closed pre-update v2 bridge serializes cross-process daemon inspection through spawn (its post-v0.2.74 removal is tracked in [#93](https://github.com/fitchmultz/pi-agent-browser-native/issues/93)), failed fresh starts are probed and retained for shutdown cleanup when live or uninspectable, and the wrapper canonicalizes and pins namespace identity (including default-namespace closes and replayable Electron probe state), canonicalizes wrapper-owned close argv so caller config/restore globals cannot redirect saved auth, keeps close from injecting a replacement checkout's restore key into a live daemon while recording returned old-generation snapshots against the observed wrapper key, rejects nested batch attachment, inspects live same-name daemons before incompatible reuse, prevents already-aborted calls from spawning, canonicalizes and pins trusted home roots after caller env merging, rejects writable/unowned POSIX ancestry without silently chmod-tightening it, rejects symlinks through the POSIX state path and `.tmp` write area, enforces owner-only mode `0700`, persists close-proven snapshot ownership as atomic per-key records across Pi restarts, converges concurrent close records without a blocking pruning lock, self-heals malformed regular records, expires wrapper-created snapshots older than 30 days while retaining two fallbacks, and caps young churn at 256 records per restore key, redacts `state show` cookie/storage values, and fails closed when storage is unsafe or an encryption key is malformed; Windows requires a 64-character hex `AGENT_BROWSER_ENCRYPTION_KEY`. An already-live daemon using an older cwd-only restore key must be closed before reuse; the wrapper now refuses to attach when the live same-name daemon's key does not match the checkout generation.
9
+ - Closed final review gaps by re-inspecting every same-identity daemon under the policy lock and requiring current-process provenance before reusing a restore-disabled daemon, cleaning Electron processes/profiles after any post-launch prepare failure, using a strict native-Windows command-first global scanner that preserves valued `--restore` semantics through `--restore=<name>`, exact lowercase optional booleans, and invalid-input failure behavior, falling back from `/bin/ps` to `/usr/bin/ps`, repeating checkout/storage/state-access validation after all async setup immediately before spawn, splitting real-upstream verification into force-exiting fail-fast phases, keeping npm's local dependency bin directory from shadowing the host Pi in lifecycle verification, applying managed restore policy to every Electron status/probe subprocess, retaining restore-disabled daemon provenance across same-process branch changes, and making filtered state-list summaries count only caller-visible rows.
10
+ - Final merge review also made Electron host launch cancellation no-spawn/cleanup-safe, decoupled managed daemon inspection from shorter caller watchdog overrides, recorded null daemon policy for owned restore-disabled helper starts, classified all-failed Electron probes as upstream errors, reserved managed session names case-insensitively, split daemon policy and managed-list filtering into focused modules, and hardened local boundaries. POSIX daemon socket storage now rejects rather than repairs pre-existing unsafe modes, validates trusted ancestry and planted entries, and uses the canonical macOS temp path. Browser access to `.agent-browser` state is blocked through command-specific input/output operands (including dash-prefixed global and positional paths), every path-bearing upstream environment mirror (state/profile/config, executable/extension/init-script, action-policy, artifact, skills, and socket paths), encoded/nested-file-scheme/Windows-aliased/symlinked paths (including nonexistent descendants), protected top-level `outputPath`, content-returning local URLs, local-page follow-ups, recursively inspected raw batch command strings, and persisted unverified top-level or batch tab/attachment/script/state-load transitions; Electron snapshot/tabs handoff, probes, and later capture share the boundary; handoff/probes verify the live URL before tab/title/content helpers, and handoff failure or cancellation cleans the managed session plus host process/profile. Raw artifact destinations are checked before directory creation with the same screenshot-path parser used by preparation. Enabled file-access argv/env and file-access-enabling or protected-path raw Chrome values are rejected, while every upstream spawn clears raw-args env, strips caller file-access occurrences, and adds canonical `--args "" --allow-file-access false` defaults so project/user config cannot re-enable local access. Post-transition navigation summaries, including forced live probes after arbitrary `eval`, and timeout diagnostics verify `get url` before reading title and fail when an implicit transition lands on a local file page. Failed or unexecuted navigation stays unverified, stale concurrent completions serialize authoritative state only, and replay gives unknown state precedence over inconsistent stale fields. While a target is unverified, `tab list` and non-content `tab <id>` selection remain available, but page reads still require `get url` to verify the selected target.
11
+ - Final security review now live-verifies the active URL before content-bearing calls against caller-owned explicit sessions, including sessions restored from stale transcript page state, and fails closed when that probe cannot prove a safe target. Protected-path detection treats Windows drive-relative forms such as `C:.agent-browser\\state\\...` as filesystem paths, nested `batch` steps are rejected instead of being interpreted recursively, and raw batch command strings mirror upstream's ASCII-space tokenizer, including its quote/backslash handling, rather than splitting on other Unicode whitespace.
12
+ - Final reviewer remediation models continued execution after failed non-bail batch navigation and blocks later content when any retained page could be local or unverified; exact `batch --bail` and already-safe diagnostic continuation remain available. Non-bail state exploration is capped and fails closed to `--bail` guidance instead of growing without bound. Caller-owned explicit-session calls are serialized per effective canonical namespace/session inside one extension instance, including CLI/environment namespace aliases from live URL verification through semantic snapshot resolution and the main command; macOS and Windows identity keys also case-fold namespace and session components to match case-insensitive daemon paths. Different identities remain concurrent, with policy, route, and artifact deltas merged across unrelated managed-state commits while branch restores still discard stale work. Concurrent artifact results carry the aggregate manifest with monotonic revisions so transcript restore retains all bounded entries. Semantic-action snapshots now run only after the live URL gate succeeds, and cancellation during the live probe propagates instead of becoming a page-verification error.
13
+ - Managed `piab-r2-*` restore capabilities, legacy `piab-r-*` capabilities, and capability-bearing paths are now redacted from model-visible text, structured details, JSON-mode content, and persisted tool results. `session list` and `state list` omit wrapper-managed rows; malformed oversized upstream output is discarded instead of being persisted as a secret-bearing parse-failure spill; foreign managed `--restore` / `--state` / `state show` / `state load` references, broad `state clear`, `state clean`, and managed save/rename targets fail before spawn. Retention removes stale ownership-proven snapshots and empty manifests from superseded restore-key generations after 30 days only when a private lineage record proves the same canonical checkout path, while preserving independent checkouts, unrecorded files, and the current checkout key.
14
+
15
+ ### Validation
16
+
17
+ - Passed `npm run verify -- pre-pr` (690 tests passed, 2 opt-in skips; 125 packed files), real-upstream contract, dogfood, packaged Pi, startup-profile, configured-source lifecycle, isolated Pi explicit-session smoke, and local platform-target verification. The remote Crabbox macOS/Ubuntu/native-Windows matrix was unavailable and explicitly waived for this release.
18
+
19
+ ## 0.2.73 - 2026-08-02
20
+
21
+ ### Changed
22
+
23
+ - Shrunk the model-facing `agent_browser` parameter schema by trimming redundant field descriptions while keeping every input mode (`args`, `semanticAction`, `job`, `qa`, `sourceLookup`, `networkSourceLookup`, `electron`) and the same validation constraints.
24
+ - Updated schema/extension validation coverage, including a compact schema size budget check.
25
+
26
+ ### Validation
27
+
28
+ - Passed `npm run verify` (591 tests passed, 2 opt-in skips) and live command-reference verification against `agent-browser 0.33.0`. Platform/cloud release smoke was not run for this GitHub-only prep.
29
+
30
+ ## 0.2.72 - 2026-07-23
31
+
32
+ ### Changed
33
+
34
+ - Rebaselined the command/help inventory, source evidence, prompt guidance, and package docs to `agent-browser 0.33.0` / vercel-labs/agent-browser@1ed371f3af472cc0d6cd8fdaea75d1a085ff7534 (includes 0.32.3–0.32.4 HAR/`find`/`derive-client` surfaces).
35
+ - Documented HAR response-body capture modes (`network har start --content text|all|none`), `skills get derive-client`, and the new `a11y [url]` axe-core accessibility audit (`--tags`, `--selector`).
36
+ - Documented upstream 0.32.4 `find role` implicit ARIA / accessible-name matching, locator-detail miss text, and the aligned `find` action list (`click, fill, check, hover, text`).
37
+ - Added compact model-facing presentation for `a11y` violation/incomplete summaries.
38
+
39
+ ### Fixed
40
+
41
+ - Classified upstream 0.32.4+ locator-detail misses (`Names seen:`, `No element found: getByRole(...)`, `Element not found: … Verify the selector, role, or name`) as `failureCategory: "selector-not-found"` so snapshot-ref recovery still runs, without treating bare accessible-name text containing `timeout` or `Confirmation required` as unrelated categories.
42
+ - Treated command-scoped `--content` and `--tags` as value-taking flags during argv planning so `network har start --content all` and `a11y --tags wcag2a,wcag2aa` keep mode/tag tokens with their flags.
43
+
44
+ ### Validation
45
+
46
+ - Passed `npm run verify` (590 tests passed, 2 opt-in skips), live command-reference verification, and `npm run verify -- real-upstream` (2/2 tests) against installed `agent-browser 0.33.0`.
47
+ - Passed `npm run verify -- release`, including configured-source lifecycle, packaged Pi smoke, and macOS/Ubuntu/native-Windows Crabbox `platform-build` plus `browser-dogfood-smoke` on `agent-browser 0.33.0` (Windows snapshot `crabbox-ready-ab-0.33.0`, Ubuntu image `node24-agent-browser0.33.0`).
48
+
3
49
  ## 0.2.71 - 2026-07-18
4
50
 
5
51
  ### Fixed
package/README.md CHANGED
@@ -75,7 +75,7 @@ The result is optimized for agent work:
75
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` |
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
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` and redacts sensitive args, URLs, stdout/stderr, details, and parse-failure spills | `test/agent-browser.extension-security-redaction.test.ts` |
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` |
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` |
@@ -85,7 +85,7 @@ The result is optimized for agent work:
85
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
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` |
87
87
  | 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`, `session id`, `session info`, `plugin add/list/show/run`, `mcp --help`, and targeted/all saved-state maintenance (`state clear --all`, `state clear -a`, named clear, or `state clean --older-than <days>`) as sessionless reads/actions: no implicit managed `--session` under default `sessionMode: "auto"` (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` |
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
89
 
90
90
  ## Fastest way to try it
91
91
 
@@ -275,7 +275,7 @@ Render a WebGPU page by enabling upstream's WebGPU launch preset on a fresh loca
275
275
  { "args": ["screenshot", "/tmp/webgpu.png"] }
276
276
  ```
277
277
 
278
- `--webgpu` is also available as `AGENT_BROWSER_WEBGPU` or `"webgpu": true` in upstream `agent-browser.json`; `--webgpu false` overrides an enabled default. 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`).
278
+ `--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`).
279
279
 
280
280
  Restrict browser and `read` traffic with upstream's domain containment on a fresh local Chrome context:
281
281
 
@@ -297,9 +297,11 @@ Click a visible ref, then refresh refs after navigation or a DOM update:
297
297
  Run a multi-step flow in one tool call:
298
298
 
299
299
  ```json
300
- { "args": ["batch"], "stdin": "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }
300
+ { "args": ["batch", "--bail"], "stdin": "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }
301
301
  ```
302
302
 
303
+ 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.
304
+
303
305
  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).
304
306
 
305
307
  Read documentation or other unstructured text without launching Chrome, or omit the URL to read the rendered DOM of the current tab:
@@ -367,11 +369,11 @@ Typical pitfalls:
367
369
  - 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.
368
370
  - 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.
369
371
  - 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.
370
- - 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, including `file://` fixtures, do not qualify without Electron launch provenance. Prefer `snapshot -i`, a current `@ref`, or a narrower panel selector.
372
+ - 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.
371
373
 
372
374
  ### Constrained browser jobs
373
375
 
374
- 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 title/URL, 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.
376
+ 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.
375
377
 
376
378
  **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.
377
379
 
@@ -432,7 +434,7 @@ For an app you launched yourself with remote debugging enabled, use raw upstream
432
434
  { "args": ["snapshot", "-i"] }
433
435
  ```
434
436
 
435
- `connect` success means the debug endpoint accepted the session, not that an active page is ready. 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.
437
+ `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.
436
438
 
437
439
  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.
438
440
 
@@ -450,7 +452,7 @@ For a quick smoke/QA pass, use top-level `qa`. It compiles to the same batch pat
450
452
  }
451
453
  ```
452
454
 
453
- Use custom `job` or raw `batch` when you need a different check sequence. `job` defaults to `batch --bail` (`failFast: true`) so later mutating steps do not run after an earlier required step fails; set `failFast: false` only when collecting later diagnostic artifacts is more important than stopping immediately. `qa` always uses fail-fast assertion behavior; omit expected text/selector when you want load-plus-diagnostics only.
455
+ Use custom `job` or raw `batch` when you need a different check sequence. `job` defaults to `batch --bail` (`failFast: true`) so later mutating steps do not run after an earlier required step fails; set `failFast: false` only when later diagnostics remain safe if an earlier navigation fails; navigation-dependent content may require fail-fast behavior or split calls. `qa` always uses fail-fast assertion behavior; omit expected text/selector when you want load-plus-diagnostics only.
454
456
 
455
457
  ### Experimental source lookup
456
458
 
@@ -509,11 +511,12 @@ Use these rules:
509
511
  - 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.
510
512
  - 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`.
511
513
  - Use `--session` when you want to manage a live upstream session name yourself.
512
- - Do not treat `--session` alone as persisted auth or tab restore after `close`, `quit`, or `exit`; use `--session <id> --restore`, `--profile`, or `--state` for persistence. Upstream 0.31.2 periodically saves restore-enabled cookies/localStorage while the browser is open, including idle page-driven changes; `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.
514
+ - 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 so project/user config cannot silently preserve local-page filesystem access; 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.
515
+ - 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.
513
516
  - Prefer page actions and storage checks over cookie dumps. `cookies get` can expose real profile cookies.
514
517
  - 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).
515
518
  - 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.
516
- - Treat `cookies get`, `storage local|session`, and `auth show` output as sensitive. 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.
519
+ - 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.
517
520
  - 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.
518
521
 
519
522
  Safe stateful examples:
@@ -670,8 +673,7 @@ The upstream browser engine remains [`agent-browser`](https://agent-browser.dev/
670
673
  - Targets the current locally installed upstream `agent-browser` version only.
671
674
  - Does not bundle `agent-browser`; users install it separately.
672
675
  - 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.
673
- - 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 a host-reachable address when available or a `file://` URL for static fixtures, then verify with `snapshot -i` or an explicit screenshot.
674
- - `file://` pages are useful as a static fallback, but they can behave differently from HTTP pages for MIME types, CORS, storage, and script/debugger behavior. If `eval --stdin` returns `null` or otherwise cannot verify a `file://` page, treat that as inconclusive and use screenshot/snapshot evidence or move the fixture to reachable HTTP.
676
+ - 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.
675
677
  - 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.
676
678
  - Real authenticated profile use is powerful but sensitive. Treat profile and cookie access as user-approved, task-specific behavior.
677
679
  - Wrapper tab/session recovery is best effort around observed upstream behavior, not a replacement for explicit profile/session design.
@@ -11,7 +11,9 @@ import { fileURLToPath } from "node:url";
11
11
  import { Text } from "@earendil-works/pi-tui";
12
12
  import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
13
13
  import { SessionPageState } from "./lib/session-page-state.js";
14
- import { createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, extractExplicitNamespace, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, extractExplicitSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
14
+ import { createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
15
+ import { extractExplicitNamespace, extractExplicitSessionName, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
16
+ import { cleanupManagedSessionRestoreConfig, ManagedSessionRestoreState } from "./lib/managed-session-restore.js";
15
17
  import { isRecord } from "./lib/parsing.js";
16
18
  import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
17
19
  import { isCloseCommand } from "./lib/command-taxonomy.js";
@@ -23,8 +25,8 @@ import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./
23
25
  import { parseBatchStdinJsonArray } from "./lib/orchestration/batch-stdin.js";
24
26
  import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
25
27
  import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/orchestration/input-plan.js";
26
- import { applyAgentBrowserOutputPath } from "./lib/orchestration/output-file.js";
27
- import { isSessionArtifactManifest } from "./lib/results/artifact-manifest.js";
28
+ import { applyAgentBrowserOutputPath, getAgentBrowserOutputPathValidationError } from "./lib/orchestration/output-file.js";
29
+ import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isSessionArtifactManifest, mergeSessionArtifactManifest } from "./lib/results/artifact-manifest.js";
28
30
  import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
29
31
  import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
30
32
  import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
@@ -67,7 +69,7 @@ function restoreArtifactManifestFromBranch(branch) {
67
69
  if (!message || message.toolName !== "agent_browser")
68
70
  continue;
69
71
  const details = isRecord(message.details) ? message.details : undefined;
70
- if (isSessionArtifactManifest(details?.artifactManifest)) {
72
+ if (isSessionArtifactManifest(details?.artifactManifest) && (!restoredManifest || details.artifactManifest.updatedAtMs >= restoredManifest.updatedAtMs)) {
71
73
  restoredManifest = details.artifactManifest;
72
74
  }
73
75
  }
@@ -395,18 +397,18 @@ function syncElectronCleanupManagedSessions(sessions, cleanupResults) {
395
397
  untrackOwnedManagedSession(sessions, sessionName);
396
398
  }
397
399
  }
398
- async function closeOwnedManagedSessionsExcept(sessions, keepSessionName, timeoutMs, keepNamespace) {
400
+ async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessionName, timeoutMs, keepNamespace) {
399
401
  const keepKey = getSessionContextKey(keepSessionName, keepNamespace);
400
402
  for (const [key, owner] of [...sessions]) {
401
403
  if (key === keepKey)
402
404
  continue;
403
- const error = await closeManagedSession({ cwd: owner.cwd, namespace: owner.namespace, sessionName: owner.sessionName, timeoutMs });
405
+ const error = await closeManagedSession({ cwd: owner.cwd, namespace: owner.namespace, restoreState, sessionName: owner.sessionName, timeoutMs });
404
406
  if (!error)
405
407
  sessions.delete(key);
406
408
  }
407
409
  }
408
- async function closeOwnedManagedSessions(sessions, timeoutMs) {
409
- await closeOwnedManagedSessionsExcept(sessions, undefined, timeoutMs);
410
+ async function closeOwnedManagedSessions(sessions, restoreState, timeoutMs) {
411
+ await closeOwnedManagedSessionsExcept(sessions, restoreState, undefined, timeoutMs);
410
412
  }
411
413
  function getOffBranchOwnedElectronLaunchRecords(ownedRecords, branchRecords) {
412
414
  const activeBranchLaunchIds = new Set(getActiveElectronRecords(branchRecords).map((record) => record.launchId));
@@ -446,6 +448,49 @@ class AsyncExecutionQueue {
446
448
  })();
447
449
  }
448
450
  }
451
+ class KeyedAsyncExecutionQueue {
452
+ entries = new Map();
453
+ async run(key, work) {
454
+ const entry = this.entries.get(key) ?? { queue: new AsyncExecutionQueue(), users: 0 };
455
+ entry.users += 1;
456
+ this.entries.set(key, entry);
457
+ try {
458
+ return await entry.queue.run(work);
459
+ }
460
+ finally {
461
+ entry.users -= 1;
462
+ if (entry.users === 0 && this.entries.get(key) === entry)
463
+ this.entries.delete(key);
464
+ }
465
+ }
466
+ }
467
+ function mergeBrowserRunMap(current, initial, updated) {
468
+ if (updated === initial)
469
+ return current;
470
+ const merged = new Map(current);
471
+ for (const [key, value] of updated) {
472
+ if (!initial.has(key) || initial.get(key) !== value)
473
+ merged.set(key, value);
474
+ }
475
+ for (const key of initial.keys()) {
476
+ if (!updated.has(key))
477
+ merged.delete(key);
478
+ }
479
+ return merged;
480
+ }
481
+ function mergeBrowserRunArtifactManifest(current, initial, updated) {
482
+ if (!updated || updated === initial)
483
+ return current;
484
+ const initialEntries = new Map((initial?.entries ?? []).map((entry) => [getSessionArtifactManifestEntryKey(entry), entry]));
485
+ const changedEntries = updated.entries.filter((entry) => initialEntries.get(getSessionArtifactManifestEntryKey(entry)) !== entry);
486
+ return changedEntries.length === 0
487
+ ? current
488
+ : mergeSessionArtifactManifest({
489
+ base: current,
490
+ entries: changedEntries,
491
+ nowMs: Math.max(Date.now(), (current?.updatedAtMs ?? 0) + 1, updated.updatedAtMs),
492
+ });
493
+ }
449
494
  function findPackageRoot(startDir) {
450
495
  let currentDir = startDir;
451
496
  while (true) {
@@ -508,16 +553,22 @@ export default function agentBrowserExtension(pi) {
508
553
  let ownedElectronLaunchRecords = new Map();
509
554
  let branchOwnedElectronLaunchIds = new Set();
510
555
  let electronChildProcesses = new Map();
556
+ const managedSessionRestoreState = new ManagedSessionRestoreState();
511
557
  const ownedManagedSessions = new Map();
512
558
  const managedSessionExecutionQueue = new AsyncExecutionQueue();
559
+ const callerOwnedSessionExecutionQueues = new KeyedAsyncExecutionQueue();
560
+ let branchRestoreGeneration = 0;
513
561
  let branchStateGeneration = 0;
514
562
  const clearSessionScopedBrowserState = (sessionName, namespace) => {
515
563
  const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
564
+ allowedDomainsBySession = new Map(allowedDomainsBySession);
516
565
  allowedDomainsBySession.delete(key);
566
+ networkRoutesBySession = new Map(networkRoutesBySession);
517
567
  networkRoutesBySession.delete(key);
518
568
  sessionPageState.clearSession(key);
519
569
  };
520
570
  const restoreBranchBackedState = (ctx, options) => {
571
+ branchRestoreGeneration += 1;
521
572
  branchStateGeneration += 1;
522
573
  const previousManagedSessionActive = managedSessionActive;
523
574
  const previousManagedSessionName = managedSessionName;
@@ -526,6 +577,9 @@ export default function agentBrowserExtension(pi) {
526
577
  const branch = ctx.sessionManager.getBranch();
527
578
  const branchResourceEvents = collectBranchManagedResourceEvents(branch);
528
579
  const restoredState = restoreManagedSessionStateFromBranch(branch, managedSessionBaseName);
580
+ managedSessionRestoreState.replace(restoredState.managedSessionRestoreDisabledIdentities, {
581
+ preserveDaemonRestoreKeys: !options.resetRuntimeOwnership,
582
+ });
529
583
  managedSessionActive = restoredState.active;
530
584
  const restoredFreshSessionOrdinal = options.resetRuntimeOwnership
531
585
  ? restoredState.freshSessionOrdinal
@@ -600,6 +654,8 @@ export default function agentBrowserExtension(pi) {
600
654
  });
601
655
  });
602
656
  pi.on("session_shutdown", async (event, ctx) => {
657
+ branchRestoreGeneration += 1;
658
+ branchStateGeneration += 1;
603
659
  let preservedElectronProfileDirs = [];
604
660
  await managedSessionExecutionQueue.run(async () => {
605
661
  const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
@@ -614,6 +670,7 @@ export default function agentBrowserExtension(pi) {
614
670
  cwd: shutdownCwd,
615
671
  electronChildProcesses,
616
672
  electronLaunchRecords: electronRecordsToCleanup,
673
+ managedSessionRestoreState,
617
674
  timeoutMs: implicitSessionCloseTimeoutMs,
618
675
  });
619
676
  preservedElectronProfileDirs = [...new Set([
@@ -622,10 +679,10 @@ export default function agentBrowserExtension(pi) {
622
679
  ])];
623
680
  syncElectronCleanupManagedSessions(ownedManagedSessions, electronCleanupResults);
624
681
  if (quitting) {
625
- await closeOwnedManagedSessions(ownedManagedSessions, implicitSessionCloseTimeoutMs);
682
+ await closeOwnedManagedSessions(ownedManagedSessions, managedSessionRestoreState, implicitSessionCloseTimeoutMs);
626
683
  }
627
684
  else {
628
- await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, managedSessionActive ? managedSessionNamespace : undefined);
685
+ await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, managedSessionActive ? managedSessionNamespace : undefined);
629
686
  }
630
687
  });
631
688
  managedSessionActive = false;
@@ -640,6 +697,7 @@ export default function agentBrowserExtension(pi) {
640
697
  branchOwnedElectronLaunchIds = new Set();
641
698
  electronChildProcesses = new Map();
642
699
  ownedManagedSessions.clear();
700
+ cleanupManagedSessionRestoreConfig();
643
701
  await cleanupSecureTempArtifacts({ preservePaths: preservedElectronProfileDirs });
644
702
  });
645
703
  pi.on("before_agent_start", async (event, ctx) => {
@@ -709,6 +767,10 @@ export default function agentBrowserExtension(pi) {
709
767
  if (resolvedInput.status === "invalid") {
710
768
  return buildValidationFailureResult(resolvedInput);
711
769
  }
770
+ const outputPathValidationError = getAgentBrowserOutputPathValidationError(outputPath, ctx.cwd);
771
+ if (outputPathValidationError) {
772
+ return buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: outputPathValidationError });
773
+ }
712
774
  const { toolArgs } = resolvedInput;
713
775
  const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
714
776
  const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
@@ -726,6 +788,8 @@ export default function agentBrowserExtension(pi) {
726
788
  implicitSessionCloseTimeoutMs,
727
789
  managedSessionActive,
728
790
  managedSessionName,
791
+ managedSessionNamespace,
792
+ managedSessionRestoreState,
729
793
  redactedCompiledElectron,
730
794
  sessionPageState,
731
795
  signal,
@@ -775,7 +839,11 @@ export default function agentBrowserExtension(pi) {
775
839
  ownedElectronLaunchRecords,
776
840
  ownedManagedSessions,
777
841
  });
842
+ const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
843
+ ? getSessionContextKey(explicitSessionName, resolveAgentBrowserNamespace(toolArgs, process.env.AGENT_BROWSER_NAMESPACE)) ?? explicitSessionName
844
+ : undefined;
778
845
  const runBrowserCommand = async () => {
846
+ const branchRestoreGenerationAtStart = branchRestoreGeneration;
779
847
  const generationAtStart = branchStateGeneration;
780
848
  const sessionPageStateUpdate = sessionPageState.beginUpdate();
781
849
  const browserRunState = {
@@ -791,11 +859,16 @@ export default function agentBrowserExtension(pi) {
791
859
  managedSessionCwd,
792
860
  managedSessionName,
793
861
  managedSessionNamespace,
862
+ managedSessionRestoreState,
794
863
  networkRoutesBySession,
864
+ ownedManagedSessions,
795
865
  sessionPageState,
796
866
  traceOwners,
797
867
  };
798
- const result = await runAgentBrowserTool({
868
+ const initialAllowedDomainsBySession = browserRunState.allowedDomainsBySession;
869
+ const initialArtifactManifest = browserRunState.artifactManifest;
870
+ const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
871
+ let result = await runAgentBrowserTool({
799
872
  ctx,
800
873
  cwd: ctx.cwd,
801
874
  electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
@@ -810,11 +883,24 @@ export default function agentBrowserExtension(pi) {
810
883
  signal,
811
884
  state: browserRunState,
812
885
  });
886
+ const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
887
+ if (branchRestoreStillCurrent) {
888
+ allowedDomainsBySession = mergeBrowserRunMap(allowedDomainsBySession, initialAllowedDomainsBySession, browserRunState.allowedDomainsBySession);
889
+ networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
890
+ artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
891
+ if (artifactManifest) {
892
+ result = {
893
+ ...result,
894
+ details: {
895
+ ...(isRecord(result.details) ? result.details : {}),
896
+ artifactManifest,
897
+ artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
898
+ },
899
+ };
900
+ }
901
+ }
813
902
  const branchStateStillCurrent = generationAtStart === branchStateGeneration;
814
903
  if (serializeBrowserCommand || branchStateStillCurrent) {
815
- allowedDomainsBySession = browserRunState.allowedDomainsBySession;
816
- networkRoutesBySession = browserRunState.networkRoutesBySession;
817
- artifactManifest = browserRunState.artifactManifest;
818
904
  freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
819
905
  managedSessionActive = browserRunState.managedSessionActive;
820
906
  managedSessionCwd = browserRunState.managedSessionCwd;
@@ -835,8 +921,10 @@ export default function agentBrowserExtension(pi) {
835
921
  }
836
922
  return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result });
837
923
  };
838
- return serializeBrowserCommand
839
- ? managedSessionExecutionQueue.run(runBrowserCommand)
924
+ if (serializeBrowserCommand)
925
+ return managedSessionExecutionQueue.run(runBrowserCommand);
926
+ return callerOwnedSessionQueueKey
927
+ ? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, runBrowserCommand)
840
928
  : runBrowserCommand();
841
929
  },
842
930
  });