pi-agent-browser-native 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +24 -15
  3. package/dist/extensions/agent-browser/index.js +496 -343
  4. package/dist/extensions/agent-browser/lib/browser-journal.js +439 -0
  5. package/dist/extensions/agent-browser/lib/browser-session-conversion.js +381 -0
  6. package/dist/extensions/agent-browser/lib/browser-transcript.js +119 -19
  7. package/dist/extensions/agent-browser/lib/execution-cwd.js +0 -16
  8. package/dist/extensions/agent-browser/lib/input-modes/job.js +49 -2
  9. package/dist/extensions/agent-browser/lib/input-modes/params.js +32 -35
  10. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +25 -5
  11. package/dist/extensions/agent-browser/lib/managed-session-restore.js +20 -5
  12. package/dist/extensions/agent-browser/lib/native-output.js +46 -0
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +97 -78
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +23 -2
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +52 -17
  16. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +1 -1
  17. package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +13 -8
  18. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +30 -16
  19. package/dist/extensions/agent-browser/lib/playbook.js +9 -2
  20. package/dist/extensions/agent-browser/lib/prompt-policy.js +16 -26
  21. package/dist/extensions/agent-browser/lib/results/presentation/content.js +4 -0
  22. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +17 -9
  23. package/dist/extensions/agent-browser/lib/session-page-state.js +95 -157
  24. package/dist/extensions/agent-browser/lib/temp.js +14 -6
  25. package/dist/extensions/agent-browser/lib/tool-surface.js +50 -33
  26. package/dist/extensions/agent-browser/lib/web-search.js +85 -72
  27. package/docs/ARCHITECTURE.md +25 -25
  28. package/docs/COMMAND_REFERENCE.md +13 -9
  29. package/docs/RELEASE.md +25 -19
  30. package/docs/REQUIREMENTS.md +6 -6
  31. package/docs/SESSION_STORAGE.md +60 -0
  32. package/docs/SUPPORT_MATRIX.md +16 -11
  33. package/docs/TOOL_CONTRACT.md +26 -19
  34. package/package.json +9 -5
  35. package/scripts/build.mjs +4 -3
  36. package/scripts/convert-browser-session.mjs +22 -0
  37. package/scripts/doctor.mjs +2 -2
@@ -12,9 +12,13 @@ Related docs:
12
12
 
13
13
  Entry tools are `agent_browser` (native argv), `agent_browser_code` (fresh JavaScript over a persistent browser), and `agent_browser_tools` (advanced capability discovery/activation). Optional credential-backed `agent_browser_web_search` remains independent of browser state and joins the entry surface only when registered.
14
14
 
15
- On hosts supporting extension-owned discovery, these registrations belong to the `browser` group: `discover_tools` enables the entry tools, and `agent_browser_tools` still enables the five advanced tools below. Tool selection, exclusions, and restored activation remain host-owned. No user-maintained group inventory is required. Discovery metadata is host-only, not a tool parameter or provider request field. Official Pi ignores the optional metadata and retains ordinary entry-tool exposure. The extension prepares its complete `agent_browser` prompt section even for a generic user prompt when the host identifies that section as deferred; the host reveals it on activation in the same run, before browser execution. Hosts without that section ownership retain keyword-triggered prompt guidance.
15
+ The extension synchronously registers a `browser` instruction group through the public `pi:instruction-groups` event bus. One full instruction function supplies project/config guidance, shared browser rules, and all specialized tool guidance. When the collector's dynamic `isManaged()` is true, Pi owns delivery through `discover_tools` and compaction repair; the extension does not inject a duplicate eager section. Otherwise `before_agent_start` supplies the identical full instructions eagerly, including on generic prompts and stock Pi. There is no legacy discovery metadata or prompt-keyword gate.
16
16
 
17
- Pi 0.87.0 or newer is required; official and fork qualification is independent. Five specialized tools are registered but initially inactive: `agent_browser_action`, `agent_browser_qa`, `agent_browser_electron`, `agent_browser_source`, and `agent_browser_network_source`. Explicit CLI allowlists, filtered SDK catalogs, and restored Pi tool history are respected. Omit CLI `--tools` for lazy activation, or include every desired advanced tool in its allowlist; the loader reports excluded tools as unavailable rather than bypassing host selection. Pi 0.87 does not distinguish an SDK explicitly selecting the complete browser catalog from its default catalog; SDK hosts choosing activation for that complete catalog can call `session.setActiveToolsByName(...)` after `bindExtensions()`.
17
+ Group enablement reveals selected declarations and instructions, not callable permissions. `agent_browser_tools` remains the separate advanced-tool activation path. Existing host selections, exclusions, namespaces, and restored tool history are unchanged.
18
+
19
+ Pi 1.0.0 or newer is required; official and fork qualification is independent. Five specialized direct tools declare native `defaultActive: false`: `agent_browser_action`, `agent_browser_qa`, `agent_browser_electron`, `agent_browser_source`, and `agent_browser_network_source`. They require activation unless selected by the host; they are not deferred tools. Explicit CLI allowlists, SDK selections/defaultTools, loader activation, restored tool history, tree navigation and reload are respected. Omit CLI `--tools` for lazy activation, or include every desired advanced tool in its allowlist; the loader reports excluded tools as unavailable rather than bypassing host selection.
20
+
21
+ Official Pi 1.0's initial SDK resume supplies an initial tool selection that bypasses native transcript restoration. The extension retains a narrow additive resume fallback for available advanced tools; it never deactivates host-selected tools or bypasses an explicit allowlist/filtered catalog. Remove that fallback only when the minimum supported official host restores this state natively.
18
22
 
19
23
  ## Why this tool shape
20
24
 
@@ -29,7 +33,7 @@ It also keeps the main UX where it belongs: the agent invokes the tool directly
29
33
 
30
34
  The tool guidance should be written for task discovery first, not wrapper implementation first. That means the description should emphasize browser use cases like reading live pages, clicking, filling, screenshots, extraction, and authenticated/profile-based workflows. Live/current external facts belong on `agent_browser_web_search` when that companion tool is registered. Low-level wrapper details like `stdin` and exact CLI args belong in the schema and guidelines, not the lead description.
31
35
 
32
- The tool also needs an operating playbook, not just a capability list. The model should not have to rediscover basics each session, but always-on guidance must stay concise. The canonical agent-facing playbook lives in `extensions/agent-browser/lib/playbook.ts`; it provides compact runtime rules plus absolute installed-package paths to `README.md`, `docs/COMMAND_REFERENCE.md`, and this contract so agents with file tools can read targeted guidance on demand instead of receiving the full docs in prompt context. Generated Markdown fragments are updated by `npm run docs -- playbook write`, and `npm run docs -- playbook check` fails when checked-in documentation drifts.
36
+ The tool also needs an operating playbook, not just a capability list. The model receives the full operating guidance before browser use. The canonical agent-facing playbook lives in `extensions/agent-browser/lib/playbook.ts`; it provides the shared full instruction source plus absolute installed-package paths to `README.md`, `docs/COMMAND_REFERENCE.md`, and this contract so agents with file tools can read targeted guidance on demand instead of receiving the full docs in prompt context. Generated Markdown fragments are updated by `npm run docs -- playbook write`, and `npm run docs -- playbook check` fails when checked-in documentation drifts.
33
37
 
34
38
  The native command reference in `docs/COMMAND_REFERENCE.md` is driven by the same pattern: `scripts/agent-browser-target.mjs` owns the runtime version and `scripts/agent-browser-capability-baseline.mjs` imports it alongside help/doc inventory; selected regions are generated into the Markdown by `npm run docs -- command-reference write`, and `npm run docs` plus `npm run verify -- command-reference` catch drift (the latter also samples the installed `agent-browser` on `PATH`). Maintainer workflow details live in `AGENTS.md` under upstream capability baseline.
35
39
 
@@ -53,7 +57,7 @@ Supplying the callback registers `agent_browser` with Pi's native `executionMode
53
57
 
54
58
  ## Optional companion web search
55
59
 
56
- `agent_browser_web_search` is a separate custom tool, not an `agent_browser` input mode. It is available when the extension can see at least one configured/resolvable Exa or Brave credential source from `~/.pi/config/pi-agent-browser-native/config.json`, `.pi/config/pi-agent-browser-native/config.json`, `PI_AGENT_BROWSER_CONFIG`, or the `EXA_API_KEY` / `BRAVE_API_KEY` environment fallbacks, and runtime execution still checks that the final available merged config has not set `webSearch.enabled` to `false`. Config layers merge global → project → `PI_AGENT_BROWSER_CONFIG` override; under Pi 0.84.0+, globally installed and CLI-loaded copies read `.pi/config/...` when Pi trust allows that project layer, and they skip the project layer when Pi reports the project is untrusted or when launched with `--no-approve`. Disable scope is explicit: a global disable is a normal user default, a project disable applies to one repo, and an override file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Credential sources may be plaintext, `$ENV_VAR` / `${ENV_VAR}` interpolation, escaped literals, or command sources such as `"!op read 'op://Private/Exa/API Key'"` from any loaded config layer; they make the tool available without exposing the value in status text, and command values resolve when the tool executes. Browser profile/executable config uses the same paths and emits prompt guidance from the highest-priority loaded layer, including project config when that layer is loaded.
60
+ `agent_browser_web_search` is a separate custom tool, not an `agent_browser` input mode. It is available when the extension can see at least one configured/resolvable Exa or Brave credential source from `~/.pi/config/pi-agent-browser-native/config.json`, `.pi/config/pi-agent-browser-native/config.json`, `PI_AGENT_BROWSER_CONFIG`, or the `EXA_API_KEY` / `BRAVE_API_KEY` environment fallbacks, and runtime execution still checks that the final available merged config has not set `webSearch.enabled` to `false`. Config layers merge global → project → `PI_AGENT_BROWSER_CONFIG` override; globally installed and CLI-loaded copies read `.pi/config/...` when Pi trust allows that project layer, and they skip the project layer when Pi reports the project is untrusted or when launched with `--no-approve`. Disable scope is explicit: a global disable is a normal user default, a project disable applies to one repo, and an override file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Credential sources may be plaintext, `$ENV_VAR` / `${ENV_VAR}` interpolation, escaped literals, or command sources such as `"!op read 'op://Private/Exa/API Key'"` from any loaded config layer; they make the tool available without exposing the value in status text, and command values resolve when the tool executes. Browser profile/executable config uses the same paths and emits prompt guidance from the highest-priority loaded layer, including project config when that layer is loaded.
57
61
 
58
62
  Prefer it for live/current external web facts, current docs/news, and candidate URLs. Prefer it over browser-driving public search-engine forms such as Google: headless typing flows may be redirected to anti-bot or CAPTCHA pages, and agents should use search API results, then `agent_browser` on a target URL, instead of attempting CAPTCHA bypass. Use `agent_browser` when the task needs browser interaction, screenshots, authenticated/profile content, page inspection, or DOM work. The search tool is namespaced to avoid colliding with generic `web_search`, chooses Exa or Brave automatically from available credentials, defaults to Exa when both are available (unless `webSearch.preferredProvider` is set), and must not expose resolved API keys in content, details, errors, status output, docs examples, logs, or PR artifacts.
59
63
 
@@ -130,7 +134,7 @@ Result details:
130
134
  }
131
135
  ```
132
136
 
133
- For Exa, `details.searchType` is the effective requested type even when the provider response does not echo it. `requestId` is included when Exa returns one. Brave omits both fields. `pageDate` comes from Exa `publishedDate` (an estimated page creation date) or Brave `page_age`; Brave may separately return `age`. These provider fields are not crawl/retrieval age or proof that a result matches a requested version. For version-sensitive work, inspect the result, constrain one follow-up to the primary domain (`includeDomains` for Exa or `site:` in a Brave query), and read the primary page.
137
+ For Exa, `details.searchType` is the effective requested type even when the provider response does not echo it. `requestId` is included when Exa returns one. Brave omits both fields. `pageDate` comes from Exa `publishedDate` (an estimated page creation date) or Brave `page_age`; Brave may separately return `age`. These provider fields are not crawl/retrieval age or proof that a result matches a requested version. For version-sensitive work, inspect the result, constrain one follow-up to the primary domain (`includeDomains` for Exa or `site:` in a Brave query), and read the primary page. Failures return structured results: local config/validation problems (invalid config, disabled tool, missing credential, blank query, Exa-only filters resolved to Brave) are `validation-error`; provider/network failures are `upstream-error`; both keep `isError` and redacted messages.
134
138
 
135
139
  ## Input mode chooser
136
140
 
@@ -323,9 +327,9 @@ Source runs in a fresh permissioned Node child with an empty child environment,
323
327
 
324
328
  `codeRun` reports `callCount`, `emitCount`, `failedCallCount`, `rejectedCallCount`, and timeout/abort flags. `codeSteps` retains bounded audit rows. Handled browser failures may coexist with successful source completion, but remain model-visible in `failures` with exact recovery evidence and in the failure count. A successful code result therefore does not assert that every inner command succeeded. Invalid inner input shapes make the outer result fail even if source handles their observations. Uncaught source exceptions use `script-error`; deadline and cancellation use `timeout` and `aborted`; output/bridge validation retains its specific failure.
325
329
 
326
- Pi persistence is required: `--no-session` fails before code execution. Before each accepted browser dispatch, the parent appends an ordered `agent-browser-transition` intent; completion appends reducer-consumed state from the same executor as direct calls. This includes target/ref invalidation, attachment, close/relaunch, and existing ownership evidence. Browser state uses existing replay reducers; there is no persistent JavaScript registry. An interrupted intent leaves the target unknown until inspection. If completion persistence fails after a mutation, the failure says the command may already have run; do not retry blindly.
330
+ Direct browser use in a disabled/no-file Pi session (`--no-session`) records current in-memory state without claiming persistence. Code requires an actually published journal, and a named but not-yet-published persistent conversation refuses browser dispatch. Sessionless local inspections and browser-independent reads remain available without journal publication, including complete redacted private spills and selected output files; observation-only artifact receipts are optional until publication. Direct, code and Electron host operations use one canonical journal event/reducer. Begin/finish records correlate operation ID, outer tool-call ID and inner command index, preserving native effects before presentation/export. Complete snapshots are defined once; ordinary finishes explicitly reuse, replace, invalidate or keep refs unknown. An unfinished begin requires inspection and a new capture, without repeating the mutation. Failed finish publication reports `browserStatePersistence: "finish-unconfirmed"` and stops dependent code calls; failed begin reports `"begin-unconfirmed"` and prevents dispatch. These acknowledgments do not promise power-loss-atomic browser actions. See [session storage and conversion](SESSION_STORAGE.md).
327
331
 
328
- Abort, timeout, branch change, reload, and shutdown stop/reap the child and settle the active inner call before releasing coordination. They do not roll back browser actions or automatically close the selected browser. Existing ownership still controls later shutdown cleanup. The only retained pre-0.7 script machinery reads exact old isolated-session cleanup leases during upgrade; it does not accept old public script execution.
332
+ Abort, timeout, branch change, reload, and shutdown stop/reap the child, settle the active inner call and join outer observation finalization before releasing lifecycle coordination. A result from a prior branch retains its complete caller observation in a process-private spill without changing the newly selected branch's artifact state. They do not roll back browser actions or automatically close the selected browser. Existing ownership still controls later shutdown cleanup. The offline converter normalizes exact pre-0.7 isolated-session cleanup leases into owner-qualified canonical events; runtime only reads those canonical cleanup facts, without old public script execution.
329
333
 
330
334
  ### `agent_browser_action`
331
335
 
@@ -357,7 +361,7 @@ Locator click/check/fill compiles to native `find`; direct targets compile to th
357
361
  Enable `qa`. Flat input is either `{ url, ...checks }` or `{ attached: true, ...checks }`; these forms are exclusive. Checks are optional `expectedText` (string or string array), `expectedSelector`, `screenshotPath`, `checkNetwork`, `checkConsole`, `checkErrors`, and `loadState` (`domcontentloaded` default, `load`, or `networkidle`). `outputPath`, `timeoutMs`, and `sessionMode` are supported; attached QA only accepts `sessionMode: "auto"`.
358
362
 
359
363
  ```json
360
- { "url": "https://example.com", "expectedText": "Example Domain", "screenshotPath": "/tmp/qa.png" }
364
+ { "url": "https://example.com", "expectedText": "Learn more", "screenshotPath": "/tmp/qa.png" }
361
365
  { "attached": true, "expectedText": "Explorer", "checkErrors": true }
362
366
  ```
363
367
 
@@ -365,7 +369,7 @@ QA compiles a fixed `batch --bail` diagnostic plan. URL QA clears enabled networ
365
369
 
366
370
  Attached QA checks the current browser without navigation or buffer clears. It first requires a live nonempty URL, including file/custom app URLs. Failed target verification returns exact tab/URL recovery instead of running the batch. Diagnostic reads default off because preserved buffers may predate this check; URL diagnostics default on. Explicit `checkErrors: false` skips the error-baseline check.
367
371
 
368
- The verdict is `qaPreset: { passed, failedChecks, warnings, summary }`. Console errors, definitely new page errors, ambiguous matching post-clear residue, actionable failed requests, failed batch steps, or failed visible assertions cause `qa-failure`. Matching a nonempty error baseline is an **unverified check**, not proof of a new application error. A clean final buffer can pass. Benign low-impact icon misses may be warnings when the failed row and resource metadata satisfy `classifyNetworkRequestFailure`; ordinary document/script/API failures remain actionable. Native diagnostic-read success alone never constitutes a QA pass.
372
+ The verdict is `qaPreset: { passed, failedChecks, notRunChecks, warnings, summary }`. When a reported fail-fast step stops the batch, `notRunChecks` lists later readiness, text/selector, diagnostic, and screenshot checks rather than falsely marking them failed; reset and settle rows are omitted. Incomplete receipts without a reported failure remain unverified, not inferred as unexecuted. Failed QA leads with the redacted causal error when available, then failed/not-run checks and reported execution coverage; a timeout without step receipts shows an unknown executed count, not zero. Console errors, definitely new page errors, ambiguous matching post-clear residue, actionable failed requests, failed batch steps, or failed visible assertions cause `qa-failure`. Matching a nonempty error baseline is an **unverified check**, not proof of a new application error. A clean final buffer can pass. Benign low-impact icon misses may be warnings when the failed row and resource metadata satisfy `classifyNetworkRequestFailure`; ordinary document/script/API failures remain actionable. Native diagnostic-read success alone never constitutes a QA pass.
369
373
 
370
374
  `details.compiledQaPreset` retains the generated plan and checks, including `diagnosticsResetAtStart`; `compiledJob` is internal QA batch-plan metadata, not a public job DSL. Use native batch/code for custom checks.
371
375
 
@@ -526,7 +530,9 @@ These calls return plain text and stay stateless: the extension does not inject
526
530
 
527
531
  ### Canonical observation
528
532
 
529
- Direct and advanced results expose the same decision-relevant observation in model-visible JSON or prose; code receives it as data without rendering/reparsing prose. The base is `{ success, resultCategory, data?, error?, summary?, successCategory?, failureCategory? }`. Optional fields include exact `nextActions`, warnings, session/namespace, artifact verification, image observations, page-change evidence, timeout partial progress, and QA/source/diagnostic results. Batch observations retain per-step outcomes. Native help/version remains plain text.
533
+ Direct and advanced results expose the same decision-relevant observation in model-visible JSON or prose; code receives it as data without rendering/reparsing prose. The base is `{ success, resultCategory, data?, error?, summary?, successCategory?, failureCategory? }`; `summary` is omitted when it is byte-identical to `error` so large failure text is not counted twice. Optional fields include exact `nextActions`, warnings, session/namespace, artifact verification, image observations, page-change evidence, timeout partial progress, and QA/source/diagnostic results. Batch observations retain per-step outcomes. Native help/version remains plain text.
534
+
535
+ Tool declarations share native Browser namespace metadata and an `outputSchema`. Returned `structuredContent` exposes the same bounded redacted projection, including failed/preparation/cancelled results, rather than raw internal `details`. Oversized observations use the existing compact object and complete redacted spill path. The loader returns `{ inventory, added }`; optional search returns its normalized provider data in the observation's `data`. This supports host-selected native codemode without auto-enabling it, changing advanced activation, or replacing the Browser code VM. Structured image paths/handles are metadata, not native `image()` input; retain verified Pi image blocks and current-cell `emitImage` semantics.
530
536
 
531
537
  `details` retains audit, process, ownership, and replay metadata; it is not the sole channel for actionable failures. JSON remains parseable, including early preparation failures and filters. Prose retains useful command output and appends canonical observation metadata. Final visible text is bounded to 16,000 characters. If necessary, the complete redacted observation is spilled and the result names `observationPath` plus retrieval instructions. Exact recovery objects are either included whole or remain in that complete observation; truncated argv/stdin is never presented as executable recovery. An unavailable spill is reported honestly.
532
538
 
@@ -598,7 +604,7 @@ These categories are intentionally bounded and stable so agents can branch on th
598
604
 
599
605
  For argv-supplied `--allowed-domains`, the wrapper treats domain containment as launch-scoped. Upstream owns request, worker, popup, and WebRTC containment plus incompatible-mode rejection; the wrapper passes the setting and upstream result through unchanged.
600
606
 
601
- Real Pi custom tools only mark a tool result failed when the tool throws during `execute`; returned `isError` fields are not authoritative. The extension therefore also registers a `tool_result` handler for the direct, code, and advanced browser tools that treats a result with `details.resultCategory: "failure"` as a real Pi tool error. For normal prose output, it appends `Result category: failure; failureCategory: …; Pi tool isError: true.` to model-visible text. For caller-requested `--json` output, it only patches `isError` and preserves visible parseable JSON content unchanged. The TUI renderer also repeats that category line at the top of failed rendered results so collapsed failed rows keep the outcome visible. The hook treats `--json` as requested when echoed `details.args` or the original tool `input.args` includes that flag; it skips appending the prose notice when any non-empty text content item is parseable JSON, even if other text items are not parseable. Invalid or non-JSON text still gets the visible prose notice. Implementation: `buildAgentBrowserToolResultPatch` in `extensions/agent-browser/lib/pi-tool-rendering.ts`; `extensions/agent-browser/index.ts` registers the handler. This keeps Pi transcript semantics aligned with the machine-readable result contract, including wrapper-side reclassifications such as `qa-failure` after an upstream-successful batch and `artifact-missing` after an upstream-successful artifact command whose requested file is absent.
607
+ Final public results return canonical `isError: true` when `resultCategory` is `failure`; Pi 1.0 honors that return without a `tool_result` repair hook. For normal prose, finalization appends `Result category: failure; failureCategory: …; Pi tool isError: true.` to model-visible text. Explicit parseable JSON remains unchanged, including code JSON and preparation failures; invalid/non-JSON text retains the notice. The TUI also shows the category at the top of failed rendered results so collapsed rows keep the outcome visible. Shared finalization lives in `lib/native-output.ts` and `finalizeAgentBrowserFailure` in `lib/pi-tool-rendering.ts`. This keeps transcript, UI and structured outcomes aligned after wrapper-side reclassification such as `qa-failure` or `artifact-missing`.
602
608
 
603
609
  For `batch`, top-level `details` still carries `resultCategory` plus `successCategory` or `failureCategory` for the **aggregate** tool outcome: if any step fails, the overall result is a failure (`resultCategory: "failure"`) even when later steps succeed—inspect `batchSteps[]` for per-step outcomes. Each `batchSteps[]` entry includes its own `resultCategory` and either `successCategory` or `failureCategory` for that step. When upstream reports it, successful and failed rows also expose a dedicated `lifecycle` field containing only the bounded `effectiveLaunch.browserLaunched` boolean; on failed rows this preserves launch evidence even though the ordinary result payload is omitted. Live state and transcript replay use that evidence to distinguish a terminal nested close from a post-close browser launch. `batchFailure.failedStep` duplicates the first failing step’s details, including its `failureCategory`, bounded lifecycle evidence, and any `nextActions`.
604
610
 
@@ -701,7 +707,7 @@ Additional structured fields can appear when relevant:
701
707
  - `compiledSourceLookup` when the call used `agent_browser_source`: `{ args: ["batch"], stdin, steps, query }` with the generated local-evidence plan and original query fields (`selector?`, `reactFiberId?`, `componentName?`, `includeDomHints?`, `maxWorkspaceFiles?`).
702
708
  - `sourceLookup` when the call used `agent_browser_source`: `{ status, candidates, limitations, summary, workspaceRoot?, electronContext? }`; wrapper-tracked packaged Electron no-candidate diagnostics may carry `workspaceRoot` plus `electronContext` and live Electron nextActions without marking the successful batch as a tool failure.
703
709
  - `compiledNetworkSourceLookup` / `networkSourceLookup` when the call used `agent_browser_network_source`: the generated batch plan plus bounded failed-request/candidate evidence as described above.
704
- - `qaPreset` when the call used `agent_browser_qa`: `{ passed, failedChecks, warnings, summary }`. `failedChecks` includes “page-error check could not be verified” when final errors match a nonempty post-clear baseline; this sets `passed: false` without claiming those matched rows are new application errors. Definitely new rows are counted separately as page errors. Network rows inside the `network requests` batch step use `summarizeNetworkFailures` / `classifyNetworkRequestFailure` in `network.ts`: actionable failures appear in `failedChecks` (and fail the tool when the upstream batch still succeeded); benign icon-classified failures appear only in `warnings` and in `summary` as `QA preset passed with warnings: …` when nothing else failed.
710
+ - `qaPreset` when the call used `agent_browser_qa`: `{ passed, failedChecks, notRunChecks, warnings, summary }`. `notRunChecks` is empty on a complete result or an unknown timeout; after a reported fail-fast failure it lists only significant checks whose later compiled steps have no result. `failedChecks` includes “page-error check could not be verified” when final errors match a nonempty post-clear baseline; this sets `passed: false` without claiming those matched rows are new application errors. Definitely new rows are counted separately as page errors. Network rows inside the `network requests` batch step use `summarizeNetworkFailures` / `classifyNetworkRequestFailure` in `network.ts`: actionable failures appear in `failedChecks` (and fail the tool when the upstream batch still succeeded); benign icon-classified failures appear only in `warnings` and in `summary` as `QA preset passed with warnings: …` when nothing else failed.
705
711
  - `networkRouteDiagnostics` after successful `network requests` when the wrapper has observed active `network route` patterns for that session and a matching request row is pending/no-status or carries CORS/preflight-looking error text. Each row includes `reason` (`"pending-routed-request"` or `"cors-likely-routed-request"`), `routePattern`, `mode`, optional `requestId`, optional `requestUrl`, and `summary`; visible text starts with `Network route diagnostics`, and `details.nextActions` prepends executable route-mock inspection/HAR follow-ups before generic request follow-ups.
706
712
  - `compiledElectron` when the call used `agent_browser_electron`: redacted action plan for `list`, `launch`, `status`, `cleanup`, or `probe`.
707
713
  - `electron` when the call used `agent_browser_electron`: action-specific lifecycle, discovery, probe, and cleanup data; see the `agent_browser_electron` contract above.
@@ -712,6 +718,7 @@ Additional structured fields can appear when relevant:
712
718
  - `promptGuard` when the requested-artifact-before-close guard blocks browser close before required prompt artifact paths are verified; implementation lives in `extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts`
713
719
  - `overlayBlockers` for conservative overlay/banner/dialog blocker candidates when a successful snapshot itself contains strong modal evidence, or after a qualifying top-level `@e…` / `ref=` click stays on the same URL, no `clickDispatch` diagnostic fired, and a fresh snapshot provides evidence (`candidates`, `summary`, and `snapshot` per `OverlayBlockerDiagnostic` in `extensions/agent-browser/index.ts`). CSS selector clicks do not run this overlay probe.
714
720
  - `visibleRefFallback` after a raw `find` or compiled `agent_browser_action` fails with `selector-not-found` and a fresh snapshot finds exact role/name `@ref` matches. Shape follows `VisibleRefFallbackDiagnostic` in `extensions/agent-browser/lib/results/selector-recovery.ts`: `{ candidates, snapshot, summary, target }`, where each candidate has `ref`, `role`, `name`, optional direct ref `args`, and `reason`; visible text appends `Current snapshot ref fallback`. Non-fill candidates with direct args add `try-current-visible-ref` or numbered `try-current-visible-ref-N` actions. Fill candidates omit direct args and target text so recovery details do not repeat potentially sensitive fill text.
721
+ - `browserEventVersion: 1` marks current tool observations; replay authority is the canonical custom journal event, not presentation details. Ordinary commands omit unchanged `refSnapshot`; dedicated snapshot captures retain full membership, accessible metadata, target, snapshot ID and verified native generation when available. A changed or unverifiable generation requires a new complete capture even at the same URL.
715
722
  - `refSnapshotInvalidation` after a confirmed cold managed-session shutdown (`reason: "page-transition"`, including when reopening fails), a session `snapshot` fails with `No active page`, any upstream-executed `record start` attempt or URL-bearing `record restart` conservatively invalidates refs (including failures, for older-native protection rather than proof of a page change), a direct or reached batch `window new` / `diff url` attempt changes the page, or a failed non-batch transition command (`eval`, `back`, `forward`, `reload`, `connect`, `state load`, `tab` selection) whose live URL re-verification probe observed the page (a failed transition can still have mutated the document, so the verified URL is kept but the prior refs are not). Shape follows `SessionRefSnapshotInvalidation` in `extensions/agent-browser/lib/session-page-state.ts`: `{ reason: "no-active-page" | "page-transition", summary }`; replay preserves the persisted summary. The wrapper deletes prior refs for that session, persists the invalidation for resume, and blocks mutation-prone `@e…` preflight with `failureCategory: "stale-ref"` until a successful fresh `snapshot -i` records refs again.
716
723
  - `snapshotFilter` after wrapper-side `snapshot -i --search <text>` or `snapshot -i --filter role=<role>`. Shape: `{ cleanArgs, search?, role?, matchedRefs, totalRefs, visibleLines, totalLines, renderedTextMatches?, renderedTextTotalMatches?, renderedTextTruncated? }`. Search runs one bounded read-only rendered-DOM probe across the full document; each visible match carries bounded `text`, `tagName`, `kind` (`text` or prioritized `validation`), `offscreen`, optional `role`/accessible `name`, and a unique mapped `ref` when the full snapshot supports it. Hidden elements are excluded. The filtered accessibility snapshot remains separate, while `details.refSnapshot` still records the full upstream ref map for later stale-ref checks.
717
724
  - `snapshotViewport` after wrapper-side `snapshot --viewport` (with or without `-i`, `--search`, or `--filter`). Shape matches the scroll-position probe: viewport scroll offsets, inner/document dimensions, sampled scrollable-container count, and bounded container offsets. The wrapper strips `--viewport` before upstream spawn and gathers this with a read-only `eval --stdin` call.
@@ -737,10 +744,10 @@ Additional structured fields can appear when relevant:
737
744
 
738
745
  Recording destinations are reserved within one Pi process, not across processes. Use unique paths for concurrent Pi processes: different explicit sessions can overwrite one file even when both `record stop` results are verified. Upstream’s same-session `record start` guard does not reserve the filename across other sessions.
739
746
  - `savedFilePath` / `savedFile` for direct `download`, `pdf`, and `wait --download` / `wait -d` saved-file workflows when a host file path is reported or wrapper-verified. Batch results preserve the same fields on the relevant `batchSteps` entry. These fields are metadata only until `artifactVerification` verifies the file. Native `download <selector> <path>` always owns the click and download, including loopback links, generated Blob exports, and redirects; the wrapper does not substitute a fetch of an anchor's `href`.
740
- - `batchSteps[].artifacts` for per-step artifacts in `batch` output; top-level `artifacts` and `artifactManifest` coalesce an earlier pending recording into the later saved, missing, or stale terminal result for the same namespace/session identity. `record restart` includes both the previous recording it finalized (or an explicit missing/stale failure) and the new pending recording; missing/stale terminal rows retire the prior pending manifest row. A successful later `close` / `quit` / `exit` retires an earlier unfinalized pending recording as `subcommand: "close-abandoned"`, clears its stop action, and leaves its file unverified. Batch presentation checks the path: only a confirmed absent file becomes `status: "missing"`; a present or inaccessible file stays unverified. It updates aggregate verification/manifest state consistently; a later successful `record stop` replaces that intermediate abandoned row with its saved artifact. Close also resets ref/page/network-route state produced by earlier rows; later lifecycle-proven browser launches, including `record stop`, can rebuild that state without triggering stale pre-close `about:blank` recovery, explicitly non-launching diagnostics cannot, and unknown later rows stay conservatively active. Per-step history remains unchanged. When any later call on the same namespace/session fails while a recording remains pending, `nextActions` combines its normal recovery with exact `stop-pending-recording` args and visible cleanup guidance; the same applies at top level when a later batch step fails. After reload in a non-Git checkout or with managed restore disabled, a live daemon without current-instance provenance cannot accept a stop. A tracked Electron attachment can rebuild that proof through the live debug-endpoint check described above; generic restore-disabled sessions cannot. That policy refusal includes `managedSessionCleanupOnlyReason: "restore-disabled-daemon-without-provenance"` plus the exact `sessionName`/`namespace`, including on implicit calls. It replaces the impossible stop with `close-pending-recording`, an exact close without `sessionMode: "fresh"`. Close retires the recording as `close-abandoned`; any file it leaves is unverified. Same-instance recordings and supported durable-Git reloads still use stop and normal WebM verification.
747
+ - `batchSteps[].artifacts` for per-step artifacts in `batch` output; top-level `artifacts` and `artifactManifest` coalesce an earlier pending recording into the later saved, missing, or stale terminal result for the same namespace/session identity. `record restart` includes both the previous recording it finalized (or an explicit missing/stale failure) and the new pending recording; missing/stale terminal rows retire the prior pending manifest row. A successful later `close` / `quit` / `exit` retires an earlier unfinalized pending recording as `subcommand: "close-abandoned"`, clears its stop action, and leaves its file unverified. Batch presentation checks the path: only a confirmed absent file becomes `status: "missing"`; a present or inaccessible file stays unverified. It updates aggregate verification/manifest state consistently; a later successful `record stop` replaces that intermediate abandoned row with its saved artifact. Close also resets ref/page/network-route state produced by earlier rows; later lifecycle-proven browser launches, including `record stop`, can rebuild that state without triggering stale pre-close `about:blank` recovery, explicitly non-launching diagnostics cannot, and unknown later rows stay conservatively active. Per-step history remains unchanged. When any later call on the same namespace/session fails while a recording remains pending, `nextActions` combines its normal recovery with exact `stop-pending-recording` args and visible cleanup guidance; the same applies at top level when a later batch step fails. After reload in a non-Git checkout or with managed restore disabled, a live daemon without current-instance provenance or a matching same-owner native daemon receipt cannot accept a stop. A tracked Electron attachment can rebuild that proof through the live debug-endpoint check described above; unproven restore-disabled sessions cannot rebuild provenance from a name or URL. That policy refusal includes `managedSessionCleanupOnlyReason: "restore-disabled-daemon-without-provenance"` plus the exact `sessionName`/`namespace`, including on implicit calls. It replaces the impossible stop with `close-pending-recording`, an exact close without `sessionMode: "fresh"`. Close retires the recording as `close-abandoned`; any file it leaves is unverified. Same-instance recordings and supported durable-Git reloads still use stop and normal WebM verification.
741
748
  - `artifactVerification` for a normalized verification summary on the unified result and on each successful `batchSteps[]` row and on failed recording rows whose receipt identifies an artifact. Top-level `batch` verification rolls up all step file artifacts; each step’s summary reflects that step’s nested tool presentation (including its spill paths and manifest slice). It reports `verified`, `verifiedCount`, `missingCount`, `pendingCount`, `unverifiedCount`, and `artifacts[]` entries with `path`, optional `absolutePath`, optional `requestedPath`, `kind` (a normal file artifact kind or `"spill"` for manifest-backed rows), optional `mediaType`, optional `exists`, optional `sizeBytes`, optional `updatedAtMs`, optional `status`, optional `retentionState` / `storageScope` on manifest-derived rows, `state` (`verified`, `missing`, `pending`, or `unverified`), and optional `limitation` (human-readable lifecycle or retention context, for example pending `record start` / `record restart`, missing, stale, or otherwise unverified files, ephemeral spill files, or evicted persisted spills). The summary `verified` boolean is true only when every entry is `verified`. `record start` / `record restart` are `pending` until `record stop`; `state load` may mention a path in command output but is not a saved artifact row.
742
749
  - `fullOutputPath` / `fullOutputPaths` when parse-valid large snapshot output or other oversized tool output is compacted and spilled to a private file; persisted sessions keep that path under a private session-scoped artifact directory for reload/resume, with a per-session byte budget by default; `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES=0` disables automatic eviction. Malformed oversized upstream output is discarded after parsing, is omitted from `details.stdout`, and reports `fullOutputUnavailable` instead of creating a parse-failure spill.
743
- - `artifactManifest` for a bounded, metadata-only inventory of recent session artifacts. Entries include path metadata, optional recording receipt/start-window metadata, canonical `namespace` plus `session` lifecycle identity, artifact `kind`, source `command`/`subcommand` when safe, `storageScope` (`persistent-session`, `process-temp`, or `explicit-path`), and `retentionState` (`live`, `ephemeral`, `missing`, or `evicted`). The default recent window is 100 entries and can be configured with `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES`. A successful session close retires only that exact namespace/session identity's pending recording rows; the separate active reservation index remains authoritative even if this bounded display inventory evicts them. Only the newest pending recording row per namespace/session identity remains live in the manifest. The manifest must not store command args, output contents, headers, DOM snapshots, or downloaded file contents.
750
+ - `artifactManifest` for changed or referenced metadata-only receipts in this invocation, without unchanged session history. Entries include path metadata, optional recording receipt/start-window metadata, canonical `namespace` plus `session` lifecycle identity, artifact `kind`, source `command`/`subcommand` when safe, `storageScope` (`persistent-session`, `process-temp`, or `explicit-path`), and `retentionState` (`live`, `ephemeral`, `missing`, or `evicted`). The default recent window is 100 entries and can be configured with `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES`. A successful session close retires only that exact namespace/session identity's pending recording rows; the separate active reservation index remains authoritative even if this bounded display inventory evicts them. Only the newest pending recording row per namespace/session identity remains live in the manifest. The manifest must not store command args, output contents, headers, DOM snapshots, or downloaded file contents.
744
751
  - `artifactRetentionSummary` with a concise count of live, evicted, ephemeral, and missing artifacts from the current manifest; results append this summary to model-facing text only when retention state affects recovery, such as spill files, ephemeral files, or evictions. Routine explicit saved files keep the summary in details to avoid noisy browsing transcripts.
745
752
  - `artifactCleanup` after a successful close command (`close`, `quit`, or `exit`) only when `artifactManifest` contains at least one existing explicit artifact path. Fields: `owner: "host-file-tools"`, `summary` (same retention summary string as `artifactRetentionSummary` for that manifest), `note` explaining that browser close commands do not delete explicit screenshots/downloads/PDFs/traces/HAR/recordings, and `explicitArtifactPaths`: up to ten **distinct existing** paths taken from manifest rows with `storageScope: "explicit-path"` in encounter order (de-duplicated after checking the filesystem); deleted/stale explicit paths are skipped. When the recent window has only spill/ephemeral inventory or explicit paths already deleted, the field and visible cleanup guidance are omitted. The visible close text stays compact and points operators to `details.artifactCleanup.explicitArtifactPaths` instead of listing paths inline. The native browser tool intentionally does not expose a delete operation for arbitrary user-chosen artifact paths; agents should inspect `artifactVerification` / manifest metadata, then remove files with normal host file tools when cleanup is required.
746
753
  - compact **snapshot** metadata on successful presentation when `details.data.compacted` is true (oversized trees): `previewMode` (`"structured"` vs outline `"outline"`), `structuredPreviewUsed`, `previewRefIds`, `previewSections` (per-section `linesShown` / `omittedLines` / root `role` / `title`), `additionalSectionsOmitted`, counts such as `refCount`, `snapshotLineCount`, and `roleCounts`, optional `highValueControlRefIds` aligned with the visible bounded `Omitted high-value controls` lines, and optional `spillError` when the wrapper could not write the redacted spill file; the model text still ends with `Full redacted snapshot path:` or an explicit unavailable reason plus `details.fullOutputPath` when a path exists
@@ -754,7 +761,7 @@ When the tool echoes `args` or `effectiveArgs` back into Pi, sensitive values su
754
761
 
755
762
  URL redaction covers `code`, SAMLRequest, SAMLResponse, RelayState, and `authorization_session_id` (including common separator/case variants); `state` and `nonce` are redacted only when the same URL token has an auth/login/OAuth/OIDC/SAML/SSO context or another known sensitive query name, so ordinary application state URLs remain useful. URLs needing no redaction keep their original spelling. Exact internal page-target URLs stay unredacted for browser correctness; model-facing content, structured details, persisted spills, and explicit `outputPath` exports receive the same redacted copies.
756
763
 
757
- For parse-valid oversized snapshots and other oversized tool outputs, details should switch to a compact metadata object and include `fullOutputPath` pointing at a private spill file with the full redacted upstream payload. When the caller supplied `outputPath`, only matching live wrapper-manifest spills may provide pre-compaction payloads; direct compacted data, compacted result rows, and command-redacted whole-batch data are rehydrated in place, while any unavailable required spill fails without writing compact metadata. Malformed oversized output is not safe to redact structurally, so its temporary subprocess spill is deleted and no durable `fullOutputPath` is returned. The model-facing tool text should print the actual spill-file path when one exists instead of only saying to inspect a details key. Oversized batch/QA failures include bounded failed-step context inline before the preview so agents can see the failed assertion/error and failure category without opening the spill file. Persisted sessions should keep that spill file under a private session-scoped artifact directory so the path remains usable after reload/restart. The oldest persisted spill files are evicted as needed to stay within `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES` (default 32 MiB), and those evictions are reported as `artifactManifest.entries[].retentionState: "evicted"` instead of silently disappearing from the session inventory. Set `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES=0` to retain existing and new persistent spill files without automatic byte-budget eviction; unset or invalid values use the default, and positive integer limits retain oldest-first eviction. This does not recover previously evicted files or change temporary subprocess spill cleanup. This persisted-spill byte budget is separate from the recent metadata window controlled by `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES`.
764
+ For parse-valid oversized snapshots and other oversized tool outputs, details should switch to a compact metadata object and include `fullOutputPath` pointing at a private spill file with the full redacted upstream payload. When the caller supplied `outputPath`, only matching live wrapper-manifest spills may provide pre-compaction payloads; direct compacted data, compacted result rows, and command-redacted whole-batch data are rehydrated in place, while any unavailable required spill fails without writing compact metadata. Malformed oversized output is not safe to redact structurally, so its temporary subprocess spill is deleted and no durable `fullOutputPath` is returned. The model-facing tool text should print the actual spill-file path when one exists instead of only saying to inspect a details key. Oversized batch/QA failures include bounded failed-step context inline before the preview so agents can see the failed assertion/error and failure category without opening the spill file. Persisted sessions should keep that spill file under a private session-scoped artifact directory so the path remains usable after reload/restart. The oldest persisted spill files are evicted as needed to stay within `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES` (default 32 MiB), and changed eviction receipts use `artifactManifest.entries[].retentionState: "evicted"` while the internal recent view is rebuilt from journal-row changes. Set `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES=0` to retain existing and new persistent spill files without automatic byte-budget eviction; unset or invalid values use the default, and positive integer limits retain oldest-first eviction. This does not recover previously evicted files or change temporary subprocess spill cleanup. This persisted-spill byte budget is separate from the recent metadata window controlled by `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES`.
758
765
 
759
766
  ## Recording receipts and recovery
760
767
 
@@ -791,7 +798,7 @@ The TUI renderer is user-facing only. It may compact or colorize what the human
791
798
 
792
799
  Worth doing in v1:
793
800
  - screenshots → saved-path summary, visible artifact metadata, `details.artifacts` metadata, and inline image attachment when safe; screenshot paths that upstream would treat ambiguously, such as `.dogfood/run/foo.png`, are normalized to absolute paths before launch and repaired from upstream temp output when possible
794
- - file artifacts such as PDFs, downloads, `wait --download` / `wait -d` files, `state save` state files, diff screenshot output images, traces, CPU profiles, completed video recordings, and path-bearing HAR captures → concise saved-path summaries plus metadata in `details.artifacts` and bounded recent metadata in `details.artifactManifest`; `record start` / `record restart` report recording lifecycle state and the future output path without adding a missing manifest entry, and `record restart` can also report the previous wrapper-known recording that was finalized by the restart; native 0.37 checks `ffmpeg` before starting video capture, while older supported natives may defer failure; successful start/restart calls without ffmpeg expose `details.recordingDependencyWarning` and leave output unverified until checked after stop; direct saved-file workflows also expose `details.savedFilePath` / `details.savedFile`; large or binary artifacts are not inlined into model context; the recent manifest cap can age out explicit-file metadata but does not remove explicit saved files from disk
801
+ - file artifacts such as PDFs, downloads, `wait --download` / `wait -d` files, `state save` state files, diff screenshot output images, traces, CPU profiles, completed video recordings, and path-bearing HAR captures → concise saved-path summaries plus metadata in `details.artifacts` and changed/referenced invocation receipts in `details.artifactManifest`; `record start` / `record restart` report recording lifecycle state and the future output path without adding a missing manifest entry, and `record restart` can also report the previous wrapper-known recording that was finalized by the restart; native 0.37 checks `ffmpeg` before starting video capture, while older supported natives may defer failure; successful start/restart calls without ffmpeg expose `details.recordingDependencyWarning` and leave output unverified until checked after stop; direct saved-file workflows also expose `details.savedFilePath` / `details.savedFile`; large or binary artifacts are not inlined into model context; the recent manifest cap can age out explicit-file metadata but does not remove explicit saved files from disk
795
802
  - `diff screenshot` → same file-artifact pattern as above for the **diff** image path only (summary text uses “Saved diff image” only when the diff output exists; missing output says “Diff image reported; file not verified” and fails as `artifact-missing`); baseline paths and other fields stay in the structured payload but are not echoed as separate saved artifacts in the visible artifact block, and there is no Pi inline image attachment for the diff output
796
803
  - `state load` → completion text may mention the loaded path, but the wrapper does **not** treat that path as a new saved artifact (`artifacts` / `artifactManifest` stay unset) the way `state save` does
797
804
  - auth, cookies, storage, clipboard, dialog, frame, state, network, debug, diff, stream, dashboard, chat, and other structured results → concise summaries that avoid expanding secret-bearing payloads; `state show` exposes metadata only in visible text and redacts every cookie/localStorage/sessionStorage `value` in structured details; credential-like keys, values, URLs, body snippets, bearer/basic credentials, clipboard write text, cookie values, and likely secret storage values are redacted before model-facing output and `details.data`, while benign primitive storage values may remain visible for local QA
@@ -825,8 +832,8 @@ If `agent-browser` is not on `PATH`, fail with a message that:
825
832
  - close the active extension-managed session when the originating `pi` process quits, while leaving explicit caller-provided sessions alone
826
833
  - set one idle timeout on extension-managed sessions as a backstop for abnormal exits or cleanup failures, and pass that same `AGENT_BROWSER_IDLE_TIMEOUT_MS` to top-level commands plus every helper targeting that owned session; caller-owned sessions retain native idle policy so upstream does not restart the background browser, reset the active tab, or discard current refs between a snapshot and action
827
834
  - clean up process-private temp spill artifacts on shutdown, while keeping persisted-session snapshot spill files in a private session-scoped artifact directory so `details.fullOutputPath` survives reload/restart and the oldest spill files are evicted if the per-session artifact budget is exceeded
828
- - reconstruct the current branch-visible extension-managed session, every transcript-proven still-active wrapper-owned identity, latest page-scoped refs, newest-revision aggregate `artifactManifest`, and wrapper-tracked Electron launch records from the active transcript branch on `session_start` and Pi `session_tree` so later default and explicit off-current calls keep following owned managed browsers and can continue reporting artifact retention state; successful explicit wrapper-owned close rows and `electron.cleanup` managed-session steps are restore-visible close events
829
- - keep runtime cleanup ownership separate from branch-visible state: `session_tree` restore and wrapper-owned browser commands are serialized with managed-session work; caller-owned explicit-session commands use local queues plus a cooperating-process execution lock keyed by actual socket context and effective canonical namespace/session (explicit namespace argv wins over inherited `AGENT_BROWSER_NAMESPACE`, including an explicit empty default), so the live URL probe, preparation helpers, semantic snapshot, main command, and state commit for one identity cannot interleave while different identities remain concurrent. Namespace-scoped `close --all` is the exception: it drains and exclusively barriers managed plus matching caller-owned work before clearing global namespace state. Namespace and session identity components are additionally Unicode-normalized and case-folded on macOS and Windows to match their case-insensitive daemon paths. Only the outer tool execution acquires that key; nested helpers run under it without re-entry. Policy, route, and artifact deltas survive unrelated managed-state commits, while a separate branch-restore generation guard prevents stale completions from overwriting a newer branch. Concurrent artifact-producing results carry a monotonic aggregate manifest revision so transcript replay selects the complete bounded manifest rather than whichever call happened to occupy the last row. Extension-managed sessions and wrapper-launched Electron records owned by the current process remain eligible for quit/cleanup, and fresh-session allocation stays monotonic across branch restores, including auto rows and close rows that reference wrapper-generated fresh names
835
+ - reconstruct the current branch-visible extension-managed session, every transcript-proven still-active wrapper-owned identity, latest page-scoped refs, journal-ordered artifact-row changes, and wrapper-tracked Electron launch records from the active transcript branch on `session_start` and Pi `session_tree` so later default and explicit off-current calls keep following owned managed browsers and can continue reporting artifact retention state; successful explicit wrapper-owned close rows and `electron.cleanup` managed-session steps are restore-visible close events
836
+ - keep runtime cleanup ownership separate from branch-visible state: `session_tree` restore and wrapper-owned browser commands are serialized with managed-session work; caller-owned explicit-session commands use local queues plus a cooperating-process execution lock keyed by actual socket context and effective canonical namespace/session (explicit namespace argv wins over inherited `AGENT_BROWSER_NAMESPACE`, including an explicit empty default), so the live URL probe, preparation helpers, semantic snapshot, main command, and state commit for one identity cannot interleave while different identities remain concurrent. Namespace-scoped `close --all` is the exception: it drains and exclusively barriers managed plus matching caller-owned work before clearing global namespace state. Namespace and session identity components are additionally Unicode-normalized and case-folded on macOS and Windows to match their case-insensitive daemon paths. Only the outer tool execution acquires that key; nested helpers run under it without re-entry. Policy, route, and artifact deltas survive unrelated managed-state commits, while a separate branch-restore generation guard prevents stale completions from overwriting a newer branch. Concurrent artifact-producing operations merge under the existing artifact queue and persist ordered row changes; replay derives the same bounded recent view from journal order. Extension-managed sessions and wrapper-launched Electron records owned by the current process remain eligible for quit/cleanup, and fresh-session allocation stays monotonic across branch restores, including auto rows and close rows that reference wrapper-generated fresh names
830
837
  - when a close command or `electron.cleanup` successfully closes the current wrapper-managed session, clear live page/ref state, reserve the next generated fresh-session ordinal, and rotate the next default auto call to a fresh wrapper-generated session name rather than reusing the closed name
831
838
  - when `/reload` shuts down an extension instance, close off-branch owned managed sessions and off-branch owned Electron launches before clearing process-local ownership; retain attached-browser context for those still-owned off-branch resources so cleanup cannot resend local-launch defaults. Preserve only the current branch-visible active managed session and active Electron launch plus its isolated `userDataDir` for reload continuity, and also persistently protect `userDataDir` paths when partial cleanup intentionally skips or fails profile removal so later temp cleanup, process exit, and stale temp-root pruning after restart do not violate Electron cleanup's safety decision; rebuild active branch state from the active branch on the next `session_start`
832
839
  - when an unnamed `sessionMode: "fresh"` launch succeeds, make it the new extension-managed session so later default calls keep using it
@@ -892,7 +899,7 @@ For exact delayed-input behavior, build native rows instead of relying on unveri
892
899
  { "args": ["batch", "--bail"], "stdin": "[[\"focus\",\"#prompt\"],[\"keyboard\",\"type\",\"h\"],[\"wait\",\"20\"],[\"keyboard\",\"type\",\"i\"]]" }
893
900
  ```
894
901
 
895
- Fixed waits are elapsed time, not postconditions. Preserve explicit URL/text waits, same-snapshot fill ordering, fresh refs after mutations, and partial-progress inspection. There is no legacy public script/job runtime. Exact isolated-script cleanup leases are read only to retire pre-upgrade resources. Saved auth and Pi transcripts are not migrated or deleted; [rollback](RELEASE.md#07-upgrade-and-rollback) changes the package version, not browser side effects.
902
+ Fixed waits are elapsed time, not postconditions. Preserve explicit URL/text waits, same-snapshot fill ordering, fresh refs after mutations, and partial-progress inspection. There is no legacy public script/job runtime. Exact isolated-script cleanup leases are normalized only by offline conversion so canonical replay can retire their recorded pre-upgrade resources. Saved auth and original Pi transcripts are not rewritten or deleted; [rollback](RELEASE.md#07-upgrade-and-rollback) changes the package version, not browser side effects.
896
903
 
897
904
  ## Non-goals
898
905
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-agent-browser-native",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "pi extension that exposes agent-browser as a native tool for browser automation",
5
5
  "type": "module",
6
6
  "author": "Mitch Fultz (https://github.com/fitchmultz)",
@@ -28,12 +28,14 @@
28
28
  },
29
29
  "bin": {
30
30
  "pi-agent-browser-config": "scripts/config.mjs",
31
+ "pi-agent-browser-convert": "scripts/convert-browser-session.mjs",
31
32
  "pi-agent-browser-doctor": "scripts/doctor.mjs"
32
33
  },
33
34
  "files": [
34
35
  "dist",
35
36
  "platform-smoke.config.mjs",
36
37
  "scripts/config.mjs",
38
+ "scripts/convert-browser-session.mjs",
37
39
  "scripts/doctor.mjs",
38
40
  "scripts/prepare.mjs",
39
41
  "scripts/build.mjs",
@@ -48,6 +50,7 @@
48
50
  "docs/COMMAND_REFERENCE.md",
49
51
  "docs/ELECTRON.md",
50
52
  "docs/RELEASE.md",
53
+ "docs/SESSION_STORAGE.md",
51
54
  "docs/platform-smoke.md",
52
55
  "docs/REQUIREMENTS.md",
53
56
  "docs/SUPPORT_MATRIX.md",
@@ -65,14 +68,14 @@
65
68
  "typebox": "*"
66
69
  },
67
70
  "devDependencies": {
68
- "@earendil-works/pi-ai": "0.87.1",
69
- "@earendil-works/pi-coding-agent": "0.87.1",
70
- "@earendil-works/pi-tui": "0.87.1",
71
+ "@earendil-works/pi-ai": "1.0.0",
72
+ "@earendil-works/pi-coding-agent": "1.0.0",
73
+ "@earendil-works/pi-tui": "1.0.0",
71
74
  "@types/cross-spawn": "6.0.6",
72
75
  "@types/node": "24.19.0",
73
76
  "@types/which": "2.0.2",
74
77
  "tsx": "4.23.15",
75
- "typebox": "1.3.34",
78
+ "typebox": "1.3.27",
76
79
  "typescript": "7.0.2"
77
80
  },
78
81
  "scripts": {
@@ -113,6 +116,7 @@
113
116
  "dependencies": {
114
117
  "cross-spawn": "7.0.6",
115
118
  "path-key": "3.1.1",
119
+ "stream-json": "3.7.0",
116
120
  "which": "2.0.2"
117
121
  }
118
122
  }
package/scripts/build.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Purpose: Produce the compiled runtime files that the published Pi package loads.
4
- * Responsibilities: Remove stale dist output, run TypeScript emit through the local compiler, and fail with clear build output.
4
+ * Responsibilities: Remove stale dist output, run TypeScript emit, and fail with clear build output.
5
5
  * Scope: Maintainer/package build only; runtime behavior remains in extensions/agent-browser TypeScript sources.
6
6
  * Usage: `npm run build` before package verification, lifecycle validation, and npm pack/publish.
7
- * Invariants/Assumptions: `node_modules` is installed and provides `typescript`; deleting `dist/` is safe because it is generated output.
7
+ * Invariants/Assumptions: `node_modules` provides `typescript`; Termux supplies Android-native `tsgo` on PATH. `dist/` is generated output.
8
8
  */
9
9
 
10
10
  import { execFile as execFileCallback } from "node:child_process";
@@ -15,7 +15,8 @@ import { promisify } from "node:util";
15
15
 
16
16
  const execFile = promisify(execFileCallback);
17
17
  const binSuffix = process.platform === "win32" ? ".cmd" : "";
18
- const tscPath = join(process.cwd(), "node_modules", ".bin", `tsc${binSuffix}`);
18
+ // TypeScript's npm binaries do not support Android; Termux supplies native tsgo.
19
+ const tscPath = process.platform === "android" ? "tsgo" : join(process.cwd(), "node_modules", ".bin", `tsc${binSuffix}`);
19
20
 
20
21
  async function main() {
21
22
  await rm(join(process.cwd(), "dist"), { force: true, maxRetries: 5, recursive: true, retryDelay: 100 });
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from "node:fs/promises";
3
+ import { parseArgs } from "node:util";
4
+
5
+ const { values } = parseArgs({ options: {
6
+ source: { type: "string" }, output: { type: "string" }, receipt: { type: "string" },
7
+ "confirm-stopped": { type: "boolean" }, help: { type: "boolean", short: "h" },
8
+ } });
9
+ if (values.help) {
10
+ console.log("Usage: pi-agent-browser-convert --source <retained.jsonl> --output <private-archive/copy.jsonl> --confirm-stopped [--receipt <new.json>]\nStop/quiesce the exact writer first. This offline command never starts Pi, a provider or a browser, never edits the source, and publishes only a separate private copy. Keep the destination outside automatic session discovery until explicitly selected. The receipt and each converted record identify the retained source checksum/byte range.");
11
+ } else {
12
+ try {
13
+ if (!values.source || !values.output) throw new Error("--source and --output are required; use --help for the stopped-writer workflow.");
14
+ const { convertBrowserSession } = await import("../dist/extensions/agent-browser/lib/browser-session-conversion.js");
15
+ const receipt = await convertBrowserSession({ source: values.source, destination: values.output, confirmedStopped: values["confirm-stopped"] === true });
16
+ console.log(JSON.stringify(receipt, null, 2));
17
+ if (values.receipt) await writeFile(values.receipt, `${JSON.stringify(receipt, null, 2)}\n`, { flag: "wx", mode: 0o600 });
18
+ } catch (error) {
19
+ console.error(error instanceof Error ? error.message : String(error));
20
+ process.exitCode = 1;
21
+ }
22
+ }
@@ -26,7 +26,7 @@ const EXTENSION_ENTRYPOINTS = Object.freeze([
26
26
  "dist/extensions/agent-browser/index.js",
27
27
  ]);
28
28
  const RECOMMENDED_VERSION = TARGET_AGENT_BROWSER_VERSION;
29
- export const MINIMUM_PI_VERSION = "0.87.0";
29
+ export const MINIMUM_PI_VERSION = "1.0.0";
30
30
  const DEFAULT_AGENT_DIR = resolve(homedir(), ".pi/agent");
31
31
  const THIS_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
32
32
 
@@ -44,7 +44,7 @@ function parseVersionParts(version) {
44
44
  return match.slice(1).map((part) => Number.parseInt(part, 10));
45
45
  }
46
46
 
47
- function versionAtLeast(actual, minimum) {
47
+ export function versionAtLeast(actual, minimum) {
48
48
  const actualParts = parseVersionParts(actual);
49
49
  const minimumParts = parseVersionParts(minimum);
50
50
  if (!actualParts || !minimumParts) return undefined;