pi-agent-browser-native 0.6.10 → 0.6.11

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 (45) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +49 -6
  3. package/dist/extensions/agent-browser/index.js +416 -430
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +1 -1
  5. package/dist/extensions/agent-browser/lib/command-policy.js +41 -2
  6. package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
  7. package/dist/extensions/agent-browser/lib/input-modes/script.js +3 -2
  8. package/dist/extensions/agent-browser/lib/managed-session-restore.js +40 -10
  9. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +3 -0
  10. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +8 -2
  11. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +1 -0
  12. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +3 -2
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +25 -14
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +56 -10
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +161 -0
  16. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +3 -3
  17. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +2 -4
  18. package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +68 -0
  19. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -6
  20. package/dist/extensions/agent-browser/lib/page-target-validation.js +9 -5
  21. package/dist/extensions/agent-browser/lib/playbook.js +10 -9
  22. package/dist/extensions/agent-browser/lib/process-environment.js +26 -8
  23. package/dist/extensions/agent-browser/lib/process.js +8 -5
  24. package/dist/extensions/agent-browser/lib/read-confirmation.js +59 -0
  25. package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -1
  26. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +8 -0
  27. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -5
  28. package/dist/extensions/agent-browser/lib/results/categories.js +4 -2
  29. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +60 -29
  30. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +19 -8
  31. package/dist/extensions/agent-browser/lib/results/presentation/common.js +0 -20
  32. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +40 -38
  33. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +1 -0
  34. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +3 -3
  35. package/dist/extensions/agent-browser/lib/results/presentation.js +38 -9
  36. package/dist/extensions/agent-browser/lib/results/recording.js +50 -0
  37. package/dist/extensions/agent-browser/lib/runtime.js +72 -20
  38. package/dist/extensions/agent-browser/lib/session-page-state.js +23 -7
  39. package/dist/extensions/agent-browser/lib/temp.js +4 -0
  40. package/docs/ARCHITECTURE.md +26 -10
  41. package/docs/COMMAND_REFERENCE.md +35 -15
  42. package/docs/SUPPORT_MATRIX.md +7 -3
  43. package/docs/TOOL_CONTRACT.md +72 -20
  44. package/package.json +1 -1
  45. package/scripts/prepare.mjs +2 -4
@@ -3,6 +3,7 @@ import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNa
3
3
  import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
4
4
  import { isCloseAllCommand, isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWebMcpPageMutationCommand, isWindowOrDiffPageTransitionCommand } from "./command-taxonomy.js";
5
5
  import { isRecord } from "./parsing.js";
6
+ import { findReadConfirmation as findPendingReadConfirmation, parseReadConfirmation } from "./read-confirmation.js";
6
7
  import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
7
8
  import { enrichSnapshotRefEntries, getSnapshotRefEntries } from "./results/snapshot-refs.js";
8
9
  import { parseSnapshotLines } from "./results/snapshot-segments.js";
@@ -335,6 +336,7 @@ export function getSessionPageStateKey(sessionName, namespace) {
335
336
  return sessionName ? getAgentBrowserSessionIdentityKey(sessionName, namespace) : undefined;
336
337
  }
337
338
  export class SessionPageState {
339
+ readConfirmations = new Map();
338
340
  refSnapshotInvalidations = new Map();
339
341
  refSnapshots = new Map();
340
342
  tabPinningReasons = new Map();
@@ -364,20 +366,20 @@ export class SessionPageState {
364
366
  const closeAllApplied = details.closeAllApplied === true
365
367
  || (message.isError !== true && isCloseAllCommand(commandTokens))
366
368
  || batchHasSuccessfulCloseAll(details.batchSteps);
369
+ const lifecycleReset = (isCloseCommand(command) && message.isError !== true) || batchCloseLifecycle !== undefined;
367
370
  if (closeAllApplied) {
368
371
  restoredOrder += 1;
369
372
  state.clearNamespace(namespace);
370
- if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
371
- continue;
372
373
  }
373
- if (!sessionKey)
374
- continue;
375
- if (!closeAllApplied && ((isCloseCommand(command) && message.isError !== true) || batchCloseLifecycle)) {
374
+ else if (sessionKey && lifecycleReset) {
376
375
  restoredOrder += 1;
377
376
  state.clearSession(sessionKey);
378
- if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
379
- continue;
380
377
  }
378
+ const readConfirmation = parseReadConfirmation(details.readConfirmation);
379
+ if (readConfirmation)
380
+ state.applyReadConfirmation(readConfirmation, ++restoredOrder);
381
+ if (!sessionKey || ((closeAllApplied || lifecycleReset) && (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)))
382
+ continue;
381
383
  const tabTarget = getRestoredSessionTabTarget(details, command, subcommand);
382
384
  const tabTargetUnknown = details.sessionTabTargetUnknown === true;
383
385
  const reopenPending = typeof details.sessionTabReopenPending === "boolean" ? details.sessionTabReopenPending : undefined;
@@ -421,6 +423,7 @@ export class SessionPageState {
421
423
  return this.updateOrder;
422
424
  }
423
425
  reset() {
426
+ this.readConfirmations.clear();
424
427
  this.refSnapshotInvalidations = new Map();
425
428
  this.refSnapshots = new Map();
426
429
  this.tabPinningReasons = new Map();
@@ -440,6 +443,17 @@ export class SessionPageState {
440
443
  tabTarget: this.tabTargets.get(sessionName)?.target,
441
444
  };
442
445
  }
446
+ findReadConfirmation(args, namespace) {
447
+ return findPendingReadConfirmation(args, [...this.readConfirmations.values()].map(entry => entry.value), namespace);
448
+ }
449
+ getReadConfirmation(sessionKey) {
450
+ return this.readConfirmations.get(sessionKey)?.value;
451
+ }
452
+ applyReadConfirmation(value, update) {
453
+ const key = getAgentBrowserSessionIdentityKey(value.sessionName, value.namespace);
454
+ if (update >= (this.readConfirmations.get(key)?.order ?? 0))
455
+ this.readConfirmations.set(key, { value, order: update });
456
+ }
443
457
  applyTabTarget(options) {
444
458
  const current = this.tabTargets.get(options.sessionName);
445
459
  if (!shouldApplyTabTargetUpdate(current, this.tabTargetUnknownOrders.get(options.sessionName), options.update)) {
@@ -492,6 +506,7 @@ export class SessionPageState {
492
506
  return { ...this.get(options.sessionName), applied: true };
493
507
  }
494
508
  clearSession(sessionName) {
509
+ this.readConfirmations.delete(sessionName);
495
510
  this.refSnapshotInvalidations.delete(sessionName);
496
511
  this.refSnapshots.delete(sessionName);
497
512
  this.tabPinningReasons.delete(sessionName);
@@ -500,6 +515,7 @@ export class SessionPageState {
500
515
  }
501
516
  clearNamespace(namespace) {
502
517
  const sessionKeys = new Set([
518
+ ...this.readConfirmations.keys(),
503
519
  ...this.refSnapshotInvalidations.keys(),
504
520
  ...this.refSnapshots.keys(),
505
521
  ...this.tabPinningReasons.keys(),
@@ -307,6 +307,8 @@ export function getSecureTempRootMaxBytes(env = process.env) {
307
307
  return parsePositiveInteger(env[TEMP_ROOT_MAX_BYTES_ENV]) ?? DEFAULT_TEMP_ROOT_MAX_BYTES;
308
308
  }
309
309
  export function getPersistentSessionArtifactMaxBytes(env = process.env) {
310
+ if (env[SESSION_ARTIFACT_MAX_BYTES_ENV]?.trim() === "0")
311
+ return 0;
310
312
  return parsePositiveInteger(env[SESSION_ARTIFACT_MAX_BYTES_ENV]) ?? DEFAULT_SESSION_ARTIFACT_MAX_BYTES;
311
313
  }
312
314
  async function assertSecureTempRootBudget(tempRoot, additionalBytes) {
@@ -373,6 +375,8 @@ async function prunePersistentSessionArtifactsToBudget(sessionArtifactDir, addit
373
375
  if (additionalBytes <= 0)
374
376
  return [];
375
377
  const maxBytes = getPersistentSessionArtifactMaxBytes();
378
+ if (maxBytes === 0)
379
+ return [];
376
380
  let files = await listArtifactFiles(sessionArtifactDir);
377
381
  let totalBytes = files.reduce((total, file) => total + file.size, 0);
378
382
  if (totalBytes + additionalBytes <= maxBytes) {
@@ -40,7 +40,7 @@ The extension should:
40
40
  - inject `--json`
41
41
  - complete each upstream invocation when the direct `agent-browser` child exits even if Node delays `"close"`: piped stdio can stay referenced by longer-lived descendant processes, so `runAgentBrowserProcess` watches `exit` and `close` together, leaves stdio intact during a short post-`exit` grace so normal `close` can still win, destroys streams only when the post-`exit` fallback fires, and prefers `close` codes then wrapper timeout (`124`) over signal-shaped `exit` codes (`watchSpawnedChildCompletion` / `resolveSpawnedChildExitCode` in `extensions/agent-browser/lib/process.ts`) so the tool cannot hang after the CLI process has already terminated
42
42
  - support optional stdin only for `eval --stdin`, `batch`, `auth save --password-stdin`, and wrapper-generated `batch` stdin from top-level `job`, `qa`, `sourceLookup`, or `networkSourceLookup`, rejecting other command/stdin combinations before launch; top-level `electron` never accepts caller `stdin` (see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#electron))
43
- - support optional top-level `outputPath` for successful browser results by writing `details.data` (or model-facing text when no structured data exists) to a caller-requested local file and reporting `details.outputFile`, without changing upstream argv semantics or overwriting a browser artifact when both destinations resolve to the same file. If presentation compacted direct data, a batch result row, or a whole batch, apply the relevant command-specific redactor before spilling and rehydrate each full pre-compaction value only from its matching live wrapper-manifest spill; fail without writing compact metadata when any required spill is unavailable or untrusted
43
+ - support optional top-level `outputPath` for successful browser results and failed/pending/recovered recording receipts. Recording exports retain success/error, original attempt, native data, artifacts and recovery provenance; they do not treat a written receipt as a successful video. Other successful results write `details.data` (or model-facing text when no structured data exists) to a caller-requested local file and reporting `details.outputFile`, without changing upstream argv semantics or overwriting a browser artifact when both destinations resolve to the same file. If presentation compacted direct data, a batch result row, or a whole batch, apply the relevant command-specific redactor before spilling and rehydrate each full pre-compaction value only from its matching live wrapper-manifest spill; fail without writing compact metadata when any required spill is unavailable or untrusted
44
44
  - support optional top-level `timeoutMs` as a per-call subprocess watchdog override for browser CLI input modes while keeping Electron-specific timeouts inside the `electron` object
45
45
  - accept an optional top-level `script` string as a mutually exclusive one-shot orchestration mode for loops, conditional page branches, and multi-page aggregation. Source runs in a separate permissioned Node child with a constrained VM context; only null-prototype `browser({ args, stdin?, timeoutMs? })` and `emit(value)` task functions cross a bounded JSON-lines IPC bridge. The parent serializes at most one inner call at a time through the same full ordinary tool executor, clears ambient upstream launch/proxy controls across helpers and cleanup, caps calls/source/post-redaction output/time, injects one unique restore-disabled wrapper-owned session, writes a strict Pi custom-entry cleanup lease before first launch, closes in `finally`, aborts and awaits active-script cleanup on branch change/shutdown, and recovers exact non-closed active-branch leases afterward. Script requires Pi session persistence and exposes no profile/attachment/session-control, host API, named recipe, import, or persistent workflow-state surface (see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#script)).
46
46
  - accept an optional native `semanticAction` object as a mutually exclusive alternative to `args` on a single tool call (and to `script`, `job`, `qa`, `sourceLookup`, `networkSourceLookup`, and `electron` on the same call), compile locator actions into upstream `find` argv, direct selector/ref click/check/fill into upstream command argv, and native dropdown selection into upstream `select <selector> <value...>` argv (with optional `semanticAction.session` expanding to a leading `--session <name>` before the compiled command when targeting a named upstream browser instead of the managed default), and echo the compiled shape in `details.compiledSemanticAction` for observability (see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#semanticaction))
@@ -54,7 +54,7 @@ The extension should:
54
54
 
55
55
  ### One-shot script isolation
56
56
 
57
- `script` is orchestration around the native tool, not a second browser runtime. Its custom Pi call renderer keeps the approval boundary inspectable with a bounded terminal-safe preview whose line breaks render as `↵` and full terminal-safe source when expanded; JavaScript line terminators stay visible as newlines and removed controls become visible markers. The child never imports this extension or invokes `agent-browser`; it only emits bounded JSON call requests. The parent validates each request against script-specific policy, injects `--namespace "" --session piab-script-<uuid>`, and recursively uses the registered tool's ordinary executor. This preserves the same argv parsing, page-target validation, process lifecycle, presentation/redaction, spill/artifact verification, result categories, and timeout behavior instead of creating a weaker bare-process shortcut. The one deliberate process difference is stricter: an async-local isolation scope filters ambient `AGENT_BROWSER_*` and standard proxy variables during planning and spawn, then the wrapper-owned namespace, timeout, and compatibility values are applied.
57
+ `script` is orchestration around the native tool, not a second browser runtime. Its custom Pi call renderer keeps the approval boundary inspectable with a bounded terminal-safe preview whose line breaks render as `↵` and full terminal-safe source when expanded; JavaScript line terminators stay visible as newlines and removed controls become visible markers. The child never imports this extension or invokes `agent-browser`; it only emits bounded JSON call requests. The parent validates each request against script-specific policy, injects `--namespace "" --session piab-script-<uuid>`, and recursively uses the registered tool's ordinary executor. This preserves the same argv parsing, page-target validation, process lifecycle, presentation/redaction, spill/artifact verification, result categories, and timeout behavior instead of creating a weaker bare-process shortcut. The process isolation scope filters ambient `AGENT_BROWSER_*` and standard proxy variables during planning and spawn and selects an empty private temporary native config. This also bypasses HOME/project profile defaults during helpers and later lease cleanup. Inner `--config` is rejected before dispatch so it cannot override that empty config; ordinary top-level `args` still accepts native `--config`. The wrapper-owned namespace, timeout, and compatibility values are then applied.
58
58
 
59
59
  Isolation is layered:
60
60
 
@@ -67,6 +67,8 @@ Isolation is layered:
67
67
 
68
68
  Browser isolation is separate from language isolation. A pre-spawn Pi custom entry records only the generated session name, exact generated close argv, launch marker, and cleanup state. That entry is durable but model-invisible. The generated identity has no restore key, cannot be selected by script source, never updates the implicit managed-session pointer, and is always closed. Exact pending records on the active transcript branch are retried after restart; malformed names, altered close argv, or missing launch markers are ignored. If Pi persistence is disabled, script fails before child/browser launch because crash recovery cannot be guaranteed.
69
69
 
70
+ Explicit URL reads remain available inside script; parent-injected isolated identities still constrain their confirmation routing. The browserless confirmation exemption requires both explicit-read provenance and native `capabilities.readRequiresConfirmation: true`, never page text or an ID from another session. It does not add child host APIs or weaken script launch/config restrictions.
71
+
70
72
  ### Agent-first UX
71
73
 
72
74
  Artifact directory preparation has one filesystem-error boundary inside the existing cleanup/finally path: direct, stdin and raw-argv failures return structured validation and the attempted directory without launching the requested command. Raw batch strings and argv-over-stdin precedence stay native; absolute artifact paths avoid differing daemon/Pi working directories. Artifact metadata retains known requested and reported/resolved paths without a new canonicalization pass. A bounded 16-byte regular-file header read recognizes the existing PNG/JPEG/GIF/WebP formats; inline screenshots use the same classifier under their existing byte limit, and unknown MIME types are omitted. Recording page warnings use the shared transition predicate and confirmed CLI/reached-row evidence through the existing prose/JSON warning path, independently of conservative ref-state invalidation after an uncertain batch.
@@ -131,17 +133,31 @@ Why:
131
133
 
132
134
  The published package should exclude agent-only and internal planning materials such as `AGENTS.md`.
133
135
 
136
+ ## Host execution hook
137
+
138
+ The default extension factory optionally accepts `{ beforeExecute }`. `index.ts` awaits this host callback after input resolution and before non-script dispatch, forwarding the original outer Pi tool-call ID through recursive script calls and supplying each dispatch's signal. Configured hosts use Pi's native sequential tool scheduling; inner script calls retain the existing serial queue. No separate controller, persistence store, retry policy, or timeout is introduced. Ordinary installation, internal helper probes, cleanup, batch-row execution, and web search are unchanged. See [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#host-execution-hook) for cancellation and error behavior; `test/agent-browser.pi-pipeline.test.ts` exercises the registered factory through real Pi with a fake upstream executable.
139
+
134
140
  ## Session model
135
141
 
136
142
  ### Default
137
143
 
138
- If the caller does not provide `--session`, the extension should default to `sessionMode: "auto"` and use an implicit session name derived from the current `pi` session id plus a hash of the absolute cwd.
144
+ An explicit native `session` default (user/project JSON or `AGENT_BROWSER_SESSION`) selects a caller-owned browser across Pi sessions. Per-call session/namespace flags win. `lib/orchestration/native-session-defaults.ts` reads only session/namespace presence from native config paths and uses sessionless native `--config <path> --json session` to validate each contributing file and resolve its session. Native config continues to own launch/profile/storage settings; the adapter does not duplicate its schema. Explicit config and per-call idle settings are scoped to helpers through `lib/process-environment.ts`. No startup probe, persistent config cache, or new lifecycle service is added.
145
+
146
+ When no native session is configured or passed, `sessionMode: "auto"` uses the existing implicit name derived from the current `pi` session id plus a hash of the absolute cwd. A configured session has the same precedence over `fresh` as a literal `--session`; script and Electron launch keep their separate wrapper-owned lifecycles. Electron launch suppresses the native session environment default only while allocating its generated session; ordinary browser follow-ups still honor the configured native default, so use the returned Electron session explicitly.
139
147
 
140
148
  Why:
141
149
  - works out of the box
142
150
  - gives continuity across calls
143
151
  - avoids forcing the agent to invent session names for basic browsing
144
152
 
153
+ ### Native status and browser-independent reads
154
+
155
+ Browser-independent calls to an already-owned session reuse its idle timeout, namespace, restore identity and retained autosave settings through the existing owned-session context. They retain launch metadata without reapplying browser launch arguments, acquiring the daemon-policy lock, changing sticky restore policy or consuming a pending page reopen. Locally reconstructed restore identities still require the existing checkout and storage checks; caller-selected config, restore and environment values remain authoritative.
156
+
157
+ An explicit native `session info` call is the preflight: daemon activity/PID is separate from `runtime.browser` liveness, Chrome PID, exact profile, tabs and launched/attached ownership. Pi adds its own cleanup ownership from existing ownership records. Unknown native fields stay unknown; the wrapper does not infer the browser from daemon/config state or add a host process scan. Native `runtime.recording.current` / `last` receipts and protocol capabilities remain visible. Full fields require companion native support; receipts retained by the native daemon are not durable after it exits.
158
+
159
+ `lib/read-confirmation.ts` recognizes only native structured control responses for actual explicit URL reads. Ordered pending/cleared `readConfirmation` markers live in the existing `SessionPageState` and tool-result transcript, including resume/branch replay. They preserve actual session/default routing even for legacy prompts. Only native `capabilities.readRequiresConfirmation: true`, which promises exact ID validation, allows matching confirm/deny to skip page helpers and managed replacement. Explicit caller identities win, DOM/unknown-origin confirmations keep their prior checks, and response content cannot manufacture provenance. There is no separate confirmation store or wrapper authorization system.
160
+
145
161
  ### Explicit upstream sessions and fresh launches
146
162
 
147
163
  If the caller provides `--session`, `--profile`, `--cdp`, or similar upstream flags, the extension should respect them with minimal interference.
@@ -159,13 +175,13 @@ V1 ownership rule:
159
175
  Practical policy:
160
176
  - preserve the current branch-visible extension-managed session across `/reload`, exact-session relaunch, `/resume`, and Pi 0.84.0+ `session_tree` branch transitions so persisted sessions can keep following the live browser after lifecycle changes
161
177
  - close the active extension-managed session when the originating `pi` process quits, while leaving explicit caller-provided sessions alone
162
- - after branch restore, use the existing locked daemon inspection to distinguish a confirmed inactive wrapper-owned daemon from a live, unknown, or unavailable one. For compatible automatic managed restore only, keep the pending reopen in ordered session page state and persist it as `sessionTabReopenPending`. Non-page calls such as `tab list` and explicit HTTP reads can start a daemon without fulfilling it, including across branch/reload replay. Before the first current-page operation (`get url`, history commands and relative `pushstate` included), reopen the complete recorded URL with native `open`, invalidate old refs with the existing `page-transition` state, and verify the actual tab. Native `open` resets frame scope. Consume the obligation on that attempt or an executed explicit context/navigation/close command, not an unreached batch row; a failed open does not permit repeated navigation of a now-live browser. After the reopen CLI starts, cancellation returns a structured `aborted` result through the ordinary result path with the exact namespace/session, consumed marker and ref invalidation; it does not throw away replay state or run later browser helpers. Cancellation before the CLI starts does not consume the pending reopen. Internal URLs retain their fragments while tab/ref comparisons remain fragment-insensitive and presentation keeps normal redaction. Older transcripts cannot recover a fragment they did not record. Caller-owned/attached browsers and restore-disabled sessions do not take this path; live wrong-tab recovery still only selects an existing target. Reopening reloads the URL, not unsaved forms, JavaScript memory, or history. There is no second restore store or lifecycle lock.
163
- - set an idle timeout on extension-managed sessions as a backstop for abnormal exits or cleanup failures, and apply that same `AGENT_BROWSER_IDLE_TIMEOUT_MS` value to every upstream subprocess (including wrapper helper snapshots, tab lists, and navigation-summary reads) because changing the launch environment between calls can make upstream restart the background browser, discard the active tab, and invalidate fresh refs
178
+ - after branch restore, use the existing locked daemon inspection to distinguish a confirmed inactive wrapper-owned daemon from a live, unknown, or unavailable one. For compatible automatic managed restore only, keep the pending reopen in ordered session page state and persist it as `sessionTabReopenPending`. Non-page calls such as `tab list` can start a daemon without fulfilling it; explicit HTTP reads leave the managed browser and pending reopen untouched, including across branch/reload replay. Before the first current-page operation (`get url`, history commands and relative `pushstate` included), reopen the complete recorded URL with native `open`, invalidate old refs with the existing `page-transition` state, and verify the actual tab. Explicit URL reads and all-read batches skip managed ownership changes and browser helpers entirely; native owns their HTTP fetch and validation. Native `open` resets frame scope. Consume the obligation on that attempt or an executed explicit context/navigation/close command, not an unreached batch row; a failed open does not permit repeated navigation of a now-live browser. After the reopen CLI starts, cancellation returns a structured `aborted` result through the ordinary result path with the exact namespace/session, consumed marker and ref invalidation; it does not throw away replay state or run later browser helpers. Cancellation before the CLI starts does not consume the pending reopen. Internal URLs retain their fragments while tab/ref comparisons remain fragment-insensitive and presentation keeps normal redaction. Older transcripts cannot recover a fragment they did not record. Caller-owned/attached browsers and restore-disabled sessions do not take this path; live wrong-tab recovery still only selects an existing target. Reopening reloads the URL, not unsaved forms, JavaScript memory, or history. There is no second restore store or lifecycle lock.
179
+ - set an idle timeout on extension-managed sessions as a backstop for abnormal exits or cleanup failures, and apply that same `AGENT_BROWSER_IDLE_TIMEOUT_MS` value to every subprocess targeting that owned session (including wrapper helper snapshots, tab lists, and navigation-summary reads); caller-owned sessions instead retain native config/environment idle policy because changing the launch environment between calls can make upstream restart the background browser, discard the active tab, and invalidate fresh refs
164
180
  - for wrapper-owned implicit sessions only, set a transcript- and checkout-scoped `AGENT_BROWSER_RESTORE` key on compatible calls so cookies and web storage can survive idle shutdown, reload, and resume. Explicit caller sessions, restore/state choices, profiles, upstream config, file access, launch arguments, environment variables, local pages, output paths, and close arguments remain upstream-owned and pass through unchanged. `piab-*` names are not reserved; session/state lists and restore identifiers are not filtered or redacted. The wrapper validates only its automatic restore checkout/storage identity and coordinates same-daemon reuse so its own restore pools cannot mix. Ambiguous page-target transitions still require live `get url` verification before content calls. The current v3 ticket-claim lock is the only managed-daemon coordination protocol; no earlier lock bridge or compatibility path remains.
165
- - redact snapshot spill payloads before writing them, clean up process-private temp spill artifacts on shutdown, and keep persisted-session spill files in a private session-scoped artifact directory with a bounded per-session budget so `details.fullOutputPath` stays usable after reload/resume without unbounded growth
181
+ - redact snapshot spill payloads before writing them, clean up process-private temp spill artifacts on shutdown, and keep persisted-session spill files in a private session-scoped artifact directory for `details.fullOutputPath` after reload/resume, with a 32 MiB per-session budget by default; `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES=0` skips persistent-spill enumeration and eviction without changing temporary spill cleanup
166
182
  - keep explicit screenshots, downloads, PDFs, traces, HAR captures, and recordings written to caller-chosen paths on disk after a successful upstream close command (`close`, `quit`, or `exit`); before artifact-producing commands run, create missing parent directories for requested host paths, and for simple loopback HTML anchor downloads with resolvable HTTP(S) hrefs the wrapper may save directly to the requested path before upstream fallback. When the bounded `details.artifactManifest` has entries, successful close commands also surface `details.artifactCleanup` and a compact `Artifact lifecycle` note pointing to structured explicit paths so operators remove files with normal host tools—the native tool does not delete arbitrary user paths (`extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts`, `getArtifactCleanupGuidance`); contract in [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details), checklist `RQ-0079` in [`SUPPORT_MATRIX.md`](SUPPORT_MATRIX.md)
167
183
  - reconstruct the current branch-visible extension-managed session, every transcript-proven still-active wrapper-owned managed identity, page-scoped refs, newest-revision aggregate artifact manifest, unbounded active-recording reservation events, and Electron launch records from the active transcript branch on `session_start` and `session_tree` so later default and explicit off-current calls keep following owned browsers after resume/reload or branch switching; restore also honors successful explicit `--session <wrapper-owned> close` rows, terminal nested-batch close outcomes even when aggregate artifact verification failed, and `electron.cleanup` managed-session steps so closed wrapper-owned sessions are not resurrected; a nested close invalidates the pre-close page target so a lifecycle-proven relaunch at `about:blank` is not treated as stale focus drift; explicit lifecycle evidence that a later diagnostic did not launch a browser preserves the terminal close, while any later row—including a failed row—whose lifecycle reports a browser launch keeps active/attached provenance; failed-step presentation persists only that bounded launch boolean so transcript replay reaches the same decision, missing lifecycle evidence remains conservatively active even on the first managed call, successful closes clear wrapper trace/profiler ownership before ordered later successful rows can rebuild it, namespace-scoped `close --all` clears all matching managed/attached/page/ref/route/trace/recording ownership, and recording starts after close are rejected before spawn
168
- - keep active recording destination reservations separate from the bounded metadata-only artifact manifest. The process-wide map is keyed by canonical namespace/session identity, rebuilt from append-only branch events, and retained for still-live process-owned recordings across branch switches. Shutdown/reload appends both terminal tombstones and still-live reservations onto the current branch so restart cannot resurrect a cross-branch close or lose a live-daemon reservation. One artifact lifecycle/output queue makes global destination preflight and reservation updates atomic across otherwise-concurrent caller-owned session queues. Every successful direct, ordered nested-batch, managed replacement, script, Electron, or shutdown close retires its exact identity at that lifecycle point; only the newest pending recording path remains authoritative across current transition replay (including same-timestamp restart rows), and recording starts after a nested close are rejected because upstream can falsely report success. Existing and dangling symlink ancestry, hardlink inode identity, full Unicode/platform case folding, and same-call `outputPath` comparison prevent alias reuse. One shared command-token projection mirrors upstream's full-argv global cleanup before artifact, recording, and presentation parsing; wait-download detection removes only the first timeout pair, follows upstream long/short mode precedence, and accepts both `--download` and `-d` wherever download mode wins; screenshot destinations use upstream's exact-flag, selector-prefix, case-sensitive extension, slash-path, and second-positional rules, while retaining the wrapper's intentional slash-bearing hidden-workspace path normalization. Recording path/URL consumers share a command-local reader that skips complete numeric `--fps` pairs without rewriting argv or treating them as outer globals; native owns rate, format and extra-argument validation. Current recording transitions are replayed directly; artifact manifests are not treated as reservation events
184
+ - keep active recording destination reservations separate from the bounded metadata-only artifact manifest. The same journal retains native recording IDs and start windows; native receipts, including failures, flow through existing artifact metadata and verification. A stop timeout/no-recording result gets one two-second native `session info` query matching namespace/session, ID/path or effective batch start window. Recovery needs terminal native encoder success plus a matching verified file; filesystem presence alone is insufficient, old same-path receipts cannot verify a new take, and freshness starts at capture rather than status-query time. Original attempts and unrelated batch failures remain visible. No second ledger, recorder, sidecar or runtime ffprobe is added. The process-wide map is keyed by canonical namespace/session identity, rebuilt from append-only branch events, and retained for still-live process-owned recordings across branch switches. Shutdown/reload appends both terminal tombstones and still-live reservations onto the current branch so restart cannot resurrect a cross-branch close or lose a live-daemon reservation. One artifact lifecycle/output queue makes global destination preflight and reservation updates atomic across otherwise-concurrent caller-owned session queues. Every successful direct, ordered nested-batch, managed replacement, script, Electron, or shutdown close retires its exact identity at that lifecycle point; only the newest pending recording path remains authoritative across current transition replay (including same-timestamp restart rows), and recording starts after a nested close are rejected because upstream can falsely report success. Existing and dangling symlink ancestry, hardlink inode identity, full Unicode/platform case folding, and same-call `outputPath` comparison prevent alias reuse. One shared command-token projection mirrors upstream's full-argv global cleanup before artifact, recording, and presentation parsing; wait-download detection removes only the first timeout pair, follows upstream long/short mode precedence, and accepts both `--download` and `-d` wherever download mode wins; screenshot destinations use upstream's exact-flag, selector-prefix, case-sensitive extension, slash-path, and second-positional rules, while retaining the wrapper's intentional slash-bearing hidden-workspace path normalization. Recording path/URL consumers share a command-local reader that skips complete numeric `--fps` pairs without rewriting argv or treating them as outer globals; native owns rate, format and extra-argument validation. Current recording transitions are replayed directly; artifact manifests are not treated as reservation events
169
185
  - keep process-owned cleanup registries for extension-managed sessions and wrapper-launched Electron records separate from the current branch-visible view; `session_tree` restore and wrapper-owned browser commands are serialized with managed-session work, while caller-owned explicit-session commands are serialized by process-local queues keyed to effective canonical namespace/session across prepare helpers (explicit namespace argv overrides inherited `AGENT_BROWSER_NAMESPACE`, including an explicit empty default) and main execution. macOS and Windows additionally normalize and case-fold namespace and session components to match case-insensitive daemon identity. Different caller-owned identities remain concurrent, except namespace-scoped `close --all` drains and exclusively barriers managed plus matching caller-owned work before clearing global namespace state; nested helpers never re-enter the outer queue, policy/route/artifact deltas merge across unrelated managed-state commits, and a separate branch-restore generation guard prevents stale completions from overwriting newer branch-visible state; aggregate artifact results use monotonic revisions so transcript replay cannot lose a concurrently completed entry. Branch switches still must not drop resources the current Pi process owns and must keep fresh-session allocation monotonic
170
186
  - record successful `connect`, `--cdp`, enabled `--auto-connect`, environment-configured CDP/auto-connect, and wrapper Electron attachment identities in branch-visible state. First-use and later content-bearing calls live-check `get url` because attached targets can drift outside Pi. Caller config, file access, launch arguments, and environment pass through unchanged; only wrapper-injected compatibility launch arguments are omitted on active attachments. A terminal successful close removes the marker; a close followed by a later step whose lifecycle reports a browser launch preserves it, while a non-launching diagnostic such as `stream status` leaves the close terminal
171
187
  - when a successful close targets the current extension-managed session, including an explicit `--session <current> close` or an `electron.cleanup` managed-session step, clear page/ref state, mark that session inactive, untrack cleanup ownership, and rotate the next default auto call to a fresh wrapper-generated session name rather than reusing the closed name
@@ -173,7 +189,7 @@ Practical policy:
173
189
  - expose still-owned off-branch Electron launch records to `electron.status { launchId }`, `electron.status { all: true }`, `electron.probe { launchId }`, and `electron.cleanup`, while leaving default `electron.probe` scoped to the current managed session
174
190
  - if an unnamed fresh launch replaces an active extension-managed session, best-effort close the old managed session after the switch succeeds; `managedSessionOutcome.replacedSessionClosed` records whether that cleanup succeeded, and a failed close keeps the older identity wrapper-owned across transcript resume for explicit follow-up or cleanup
175
191
  - expose `details.browserWindow` and one visible login handoff only when a successful first/fresh local wrapper-managed headed result, including `batch`, is not an attachment and has upstream `lifecycle.effectiveLaunch.browserLaunched: true` and a `created`/`replaced` managed-session outcome. Keep `visibility: "unverified"`: this is launch evidence, never a claim about the user's OS desktop
176
- - leave explicit caller-provided `--session` choices alone unless the caller closes them explicitly, but before any content-bearing read or interaction against a caller-owned explicit session, live-probe that session with `get url` instead of trusting missing or stale transcript page state; hold the effective canonical namespace/session queue from that probe through semantic snapshot resolution and the main command so another same-instance call cannot change tabs in between. Non-bail batch analysis retains every possible page left by a failed transition up to a fixed bound and blocks later content only when the target is unverified; exceeding the bound also fails closed to exact `batch --bail` guidance. Nested `batch` steps remain unsupported, and raw batch command strings mirror upstream's ASCII-space tokenizer, including quote/backslash handling, rather than splitting on other Unicode whitespace.
192
+ - leave explicit caller-provided `--session` choices alone unless the caller closes them explicitly, but before any DOM/content-bearing read or interaction against a caller-owned explicit session, live-probe that session with `get url` instead of trusting missing or stale transcript page state; hold the effective canonical namespace/session queue from that probe through semantic snapshot resolution and the main command so another same-instance call cannot change tabs in between. Non-bail batch analysis retains every possible page left by a failed transition up to a fixed bound and blocks later content only when the target is unverified; exceeding the bound also fails closed to exact `batch --bail` guidance. Nested `batch` steps remain unsupported, and raw batch command strings mirror upstream's ASCII-space tokenizer, including quote/backslash handling, rather than splitting on other Unicode whitespace.
177
193
  - after profiled `open` / `goto` / `navigate` calls, verify the active tab still matches the returned page URL and best-effort switch back when restored profile tabs steal focus
178
194
  - once the wrapper observes tab-drift risk for a session (profile restore correction, overlapping stale opens, or restored session state), later active-tab commands verify the intended tab under the existing session queue before semantic/ref helpers and user commands. Native selection runs only when the intended tab is not already active, because upstream selection clears refs and frame scope even on same-tab reselection. Missing targets, failed selection, and post-selection target mismatches fail before user commands. Caller argv/stdin and native `--pin-tab` / `--no-pin-tab` preferences remain unchanged. Local commands, live `get url`, explicit HTTP `read <url>` (including its flags), URL `a11y`/`vitals`/`web-vitals`, `diff url`, `window new`, URL-bearing recording commands, and explicit tab/navigation/`connect`/`state load` recovery do not require the old target; history back/forward/reload, `pushstate`, and page-content operations still do. The same classifier scans effective batch rows past non-page prefixes until a page dependency or explicit context change, without rewriting user rows or changing bail behavior. For `window new` and `diff url`, observe the resulting URL instead of retaining the old target or treating the requested second URL as redirect evidence. Fold only reached native batch rows when available, discard observations and refs from before those transitions, and let later successful snapshots rebuild refs even when another batch row fails. Retain an observed blank destination after either command instead of recovering the old page; if no final target is observed, use the existing unknown-target state. Caller batch arguments, stdin and bail behavior stay unchanged. Routine same-session commands avoid `tab list` preflights
179
195
  - for sessions with observed tab-drift risk, after a successful command on a known tab target, the wrapper may best-effort restore that same target again if restored/background tabs steal focus after the command returns; routine same-session commands skip this post-command `tab list` probe
@@ -185,7 +201,7 @@ Practical policy:
185
201
  - derive narrow prompt guards only for concrete evidence invariants: explicitly requested screenshot/recording output paths block browser close until the artifact manifest verifies those paths, while bare inbound attachment paths remain inputs. The wrapper intentionally does not infer broad business/user intent from prompt text such as order/payment/post boundaries; agents must follow those instructions themselves. The artifact guard is bounded preflight policy (`details.promptGuard`, `failureCategory: "policy-blocked"`), not a reusable browser recipe layer
186
202
  - reject direct and effective batch `scrollintoview text=...` / `scrollinto text=...` before dispatch because current upstream can falsely report success without movement, while leaving help forms untouched; return only native recovery (`find text ... hover` or fresh snapshot/ref), leaving CSS, XPath, and current-ref behavior upstream-owned
187
203
  - after successful `get text` on a qualifying non-ref CSS selector, optionally issue one read-only `eval --stdin` probe per selector when multiple DOM matches or a hidden first match with visible peers could misread tabbed or off-screen content; simple id selectors and sensitive-looking literals skip this probe. Merge `details.selectorTextVisibility` / `selectorTextVisibilityAll`, visible warning lines, and `inspect-visible-text-candidates*` next actions as documented in [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) and `RQ-0074` in [`SUPPORT_MATRIX.md`](SUPPORT_MATRIX.md)
188
- - for local Unix launches, set a short private socket directory so extension-generated session names do not fail on the upstream Unix socket-path length limit; require the selected path to be absolute, owned by the current uid, mode `0700`, under checked ancestry, and free of symlink, foreign-owner, or special planted entries; reject pre-existing unsafe modes instead of repairing them, then recheck before spawn. The actual filesystem root `/` is supplied by the trusted operating environment: a directory without group/other write bits is accepted regardless of its reported owner, which can be unmapped in a Linux user namespace. Existing root-owned sticky-directory acceptance is unchanged. This boundary does not protect against whoever controls the root filesystem. Every non-root ancestor still needs trusted ownership and permissions, including the destination ancestry of root-owned aliases; a matching overflow UID is not trusted. Android/Termux uses `/data/data/<package>/piab`, treats the owner-only app-data directory as the trust anchor, permits the app's matching private uid/gid ancestry, compacts generated managed identities to one 80-bit digest so ordinary namespace plus fresh-session paths remain within the limit, and places policy-lock coordination under `os.tmpdir()` because Android `/tmp` is shell-owned and inaccessible
204
+ - caller-owned sessions honor native `AGENT_BROWSER_SOCKET_DIR` unless `PI_AGENT_BROWSER_SOCKET_DIR` overrides it, with the same socket integrity checks. For other local Unix launches, set a short private socket directory so extension-generated session names do not fail on the upstream Unix socket-path length limit; require the selected path to be absolute, owned by the current uid, mode `0700`, under checked ancestry, and free of symlink, foreign-owner, or special planted entries; reject pre-existing unsafe modes instead of repairing them, then recheck before spawn. The actual filesystem root `/` is supplied by the trusted operating environment: a directory without group/other write bits is accepted regardless of its reported owner, which can be unmapped in a Linux user namespace. Existing root-owned sticky-directory acceptance is unchanged. This boundary does not protect against whoever controls the root filesystem. Every non-root ancestor still needs trusted ownership and permissions, including the destination ancestry of root-owned aliases; a matching overflow UID is not trusted. Android/Termux uses `/data/data/<package>/piab`, treats the owner-only app-data directory as the trust anchor, permits the app's matching private uid/gid ancestry, compacts generated managed identities to one 80-bit digest so ordinary namespace plus fresh-session paths remain within the limit, and places policy-lock coordination under `os.tmpdir()` because Android `/tmp` is shell-owned and inaccessible
189
205
  - keep wrapper-spawned upstream CLI calls bounded by clamping `AGENT_BROWSER_DEFAULT_TIMEOUT` to the upstream documented 25-second default while deriving a longer subprocess watchdog for explicit long `wait <ms>` / `wait --timeout <ms>`, read, and WebMCP calls from the effective direct or raw-argument-else-stdin batch steps; dialog commands, likely dialog-trigger clicks/taps/finds, and `eval --stdin` snippets that look like alert/confirm/prompt/dialog triggers use shorter wrapper subprocess budgets so blocking JavaScript prompts surface recovery actions before the full default watchdog. Timeout recovery removes standalone snapshots when the target is unknown and emits one executable session-scoped `batch --bail` (`get url`, then `snapshot -i`); blocking-dialog status/accept/dismiss remains allowed under the same unknown-target guard
190
206
 
191
207
  This is primarily about ownership clarity and avoiding surprise, not adding a heavy safety wrapper. If the extension invented the session, the extension should own its lifecycle without breaking reload, resume, or branch-tree semantics. If the caller explicitly chose the upstream session model, the extension should stay out of the way.
@@ -213,7 +229,7 @@ Implementation detail lives in `extensions/agent-browser/lib/launch-scoped-flags
213
229
  - **`--headed`:** Treated as launch-scoped for both enabled and explicit `false` values so a visible-window choice cannot be silently ignored by an already-running managed session.
214
230
  - **`--allowed-domains`:** Treated as launch-scoped so it cannot silently reuse an active implicit browser. Upstream 0.32.0 owns request, worker, popup, and WebRTC containment plus incompatible-mode rejection; the wrapper passes the setting and result through unchanged.
215
231
 
216
- **Sessionless inspection and local commands:** Plain-text help/version probes and upstream commands that do not require a page skip implicit managed-session injection. This includes read-only skills, local auth/profile/setup commands, `session list`, and syntactically local state lifecycle operations. State/session rows, restore identifiers, wrapper-prefixed session targets, caller-selected paths, upstream config, file access, launch arguments, and environment variables pass through unchanged. Browser-backed or context-dependent commands receive normal managed-session injection only when the caller did not choose an explicit session. `extensions/agent-browser/lib/page-target-validation.ts` owns only page-target correctness: after an ambiguous tab, attachment, history, script, or state-load transition, content reads require a live `get url` or explicit navigation so the wrapper cannot silently act on the wrong page. Command-shape allowlisting lives in `extensions/agent-browser/lib/command-policy.ts` (`needsManagedSession`), while `extensions/agent-browser/lib/runtime.ts` (`isPlainTextInspectionArgs`, `buildExecutionPlan`) applies that decision to execution planning.
232
+ **Sessionless inspection and local commands:** Plain-text help/version probes and upstream commands that do not require a page skip implicit managed-session injection. This includes read-only skills, local auth/profile/setup commands, `session list`, syntactically local state lifecycle operations, explicit URL reads and all-read batches. The read classifier follows native option consumption; invalid read syntax remains native validation rather than triggering page work. State/session rows, restore identifiers, wrapper-prefixed session targets, caller-selected paths, upstream config, file access, launch arguments, and environment variables pass through unchanged. Browser-backed or context-dependent commands receive normal managed-session injection only when the caller did not choose an explicit session. `extensions/agent-browser/lib/page-target-validation.ts` owns only page-target correctness: after an ambiguous tab, attachment, history, script, or state-load transition, content reads require a live `get url` or explicit navigation so the wrapper cannot silently act on the wrong page. Command-shape allowlisting lives in `extensions/agent-browser/lib/command-policy.ts` (`needsManagedSession`), while `extensions/agent-browser/lib/runtime.ts` (`isPlainTextInspectionArgs`, `buildExecutionPlan`) applies that decision to execution planning.
217
233
 
218
234
  A successful unnamed `sessionMode: "fresh"` launch should become the new extension-managed session so later default calls follow that browser instead of silently snapping back to the older managed session.
219
235
 
@@ -16,6 +16,8 @@ This project intentionally blocks normal `agent-browser` bash usage in most agen
16
16
 
17
17
  After updating `pi-agent-browser-native`, fully quit and restart Pi before using the updated tools. `/reload` can retain previously loaded compiled JavaScript even after `dist/` is rebuilt, so it is not a reliable way to pick up package updates.
18
18
 
19
+ SDK hosts can supply an awaited [`beforeExecute` callback](TOOL_CONTRACT.md#host-execution-hook) to save host state before ordinary or script-inner browser dispatch. This is a factory option, not a tool argument; normal installations do not need it.
20
+
19
21
  ## Upstream baseline
20
22
 
21
23
  <!-- agent-browser-capability-baseline:start upstream-baseline -->
@@ -213,10 +215,11 @@ Tool parameters (use exactly one of `script`, `args`, `semanticAction`, `job`, `
213
215
  - `networkSourceLookup`: **EXPERIMENTAL — candidates only** for failed request-to-source hints; compiles to generated `batch`, reports `details.compiledNetworkSourceLookup` and `details.networkSourceLookup`, and never assigns blame or edits files.
214
216
  - `electron`: optional Electron desktop-app shorthand. `list`, `status`, `cleanup`, and `probe` are wrapper-owned host/session helpers; `launch` starts a wrapper-owned isolated Electron profile and attaches through upstream `connect`.
215
217
  - `stdin`: top-level stdin is only for `batch`, `eval --stdin`, and `auth save --password-stdin`; other combinations are rejected before `agent-browser` is launched. `script` puts inner stdin on `browser({ stdin })`; `job`, `qa`, `sourceLookup`, `networkSourceLookup`, and `electron` generate or manage their own input.
216
- - `outputPath`: optional wrapper-owned local file sink for successful results. Use it for durable `eval`, `get`, `snapshot`, or diagnostic outputs, not as the destination for screenshots, downloads, recordings, or other browser artifacts; if the paths resolve to the same file, the browser artifact is preserved and the result-data write fails validation. If presentation compacted a large direct result, a result row, or the whole `batch`, the writer copies each full command-redacted pre-compaction value only from its matching live wrapper-manifest spill; if any required spill is unavailable or untrusted, it fails without writing compact metadata. `details.outputFile` reports the saved path and byte count. If caller argv includes upstream `--json`, the visible JSON content stays parseable and the save notice is only in `details.outputFile`.
218
+ - `outputPath`: optional wrapper-owned local file sink for successful results and recording receipts, including failed, pending, timed-out or recovered stops. Recording exports preserve an envelope with original attempt status and native receipt/verification (`details.outputFile.source: "recording-receipt"`); unrelated failed extractions remain unwritten. Use it for durable `eval`, `get`, `snapshot`, or diagnostic outputs, not as the destination for screenshots, downloads, recordings, or other browser artifacts; if the paths resolve to the same file, the browser artifact is preserved and the result-data write fails validation. If presentation compacted a large direct result, a result row, or the whole `batch`, the writer copies each full command-redacted pre-compaction value only from its matching live wrapper-manifest spill; if any required spill is unavailable or untrusted, it fails without writing compact metadata. `details.outputFile` reports the saved path and byte count. If caller argv includes upstream `--json`, the visible JSON content stays parseable and the save notice is only in `details.outputFile`.
217
219
  - `timeoutMs`: optional per-call wrapper subprocess watchdog override in milliseconds for the requested browser CLI process. Managed-session policy inspection can independently consume up to 35 seconds before that process; this preflight is intentionally not shortened by `timeoutMs` because a busy but valid daemon must remain distinguishable from an unverifiable one.
218
220
  - `sessionMode`:
219
- - `"auto"` reuses the extension-managed session when possible.
221
+ - Native configured `session` / `AGENT_BROWSER_SESSION` defaults select a caller-owned shared browser for ordinary calls, just like explicit `--session`, without requiring repeated flags. Such a selection wins over both modes and is not closed on Pi quit. See [shared browser defaults](../README.md#shared-browser-defaults).
222
+ - `"auto"` reuses the extension-managed session when no native session is selected.
220
223
  - `"fresh"` rotates that managed session to a fresh upstream launch so launch-scoped flags (`--allowed-domains`, `--auto-connect`, `--args`, `--ca-cert`, `--no-ca-cert`, `--cdp`, `--enable`, `--executable-path`, `--webgpu`, `--init-script`, `--idle-timeout`, `--user-agent`, `--headed`, `--device`, `--namespace`, `--profile`, `--provider`, `-p`, `--restore`, `--restore-save`, `--restore-check-url`, `--restore-check-text`, `--restore-check-fn`, `--session-name`, `--state`) apply.
221
224
  - If a fresh launch fails or times out, read `details.managedSessionOutcome` for `preserved` vs `abandoned` (and related fields). A model-visible `Managed session outcome: …` line is appended for failing calls that used `sessionMode: "fresh"` and when automatic close of a replaced session fails; `"auto"` failures can still populate the struct without that extra line. If you explicitly close the current wrapper-managed session with `--session <name> close`, later default auto calls rotate to a new wrapper-generated session instead of reusing the closed name; repeated closes and branch restores keep those generated names monotonic.
222
225
 
@@ -232,7 +235,7 @@ Use `script` when a loop, an optional page branch, or multi-page aggregation wou
232
235
 
233
236
  The wrapper serializes inner calls, caps them at 25, caps source/final JSON at 64 KiB, defaults the whole script to 120 seconds, and rejects more than 300 seconds. Final data is redacted, compact-serialized, and byte-checked again before presentation so nesting cannot amplify small JSON into unbounded prose. Inner summaries/text are bounded, complete envelopes are checked against the IPC cap, and script-visible browser `nextActions` retain only policy-compatible calls after the isolated identity prefix is removed. It launches a separate permissioned Node child with no imports, process, filesystem, network, timers, dynamic code generation, or host object/function references. `Promise.all` is allowed for local orchestration but does not make browser calls concurrent. Pi approval covers the one visible top-level input and may therefore authorize all 25 inner calls. The collapsed Pi tool row shows a bounded terminal-safe source preview with line breaks marked as `↵`; expand the row to inspect the full terminal-safe source before approval. JavaScript CR/U+2028/U+2029 line terminators remain visible newlines, and removed terminal/directional/zero-width controls become visible markers.
234
237
 
235
- A script invocation uses a unique `piab-script-<uuid>` browser identity in an empty namespace with managed restore disabled. It cannot name or attach to sessions, use profiles/providers/state/restore/raw launch mutation, issue lifecycle/sessionless/local commands, or nest `batch` or another top-level input mode. Every inner helper and cleanup process also clears ambient `AGENT_BROWSER_*` and standard proxy variables before the wrapper reapplies its own safe config, namespace, timeout, and compatibility values. It never replaces the implicit conversation browser and always closes its isolated session. Use ordinary `args` with a requested profile/attachment for authenticated state.
238
+ A script invocation uses a unique `piab-script-<uuid>` browser identity in an empty namespace with managed restore disabled. It cannot name or attach to sessions, pass `--config`, use profiles/providers/state/restore/raw launch mutation, issue lifecycle/sessionless/local commands, or nest `batch` or another top-level input mode. Every inner helper and cleanup process also clears ambient `AGENT_BROWSER_*` and standard proxy variables and uses an empty temporary native config to bypass HOME/project profile defaults before the wrapper reapplies its namespace, timeout, and compatibility values. It never replaces the implicit conversation browser and always closes its isolated session. Use ordinary `args` with a requested profile/attachment for authenticated state.
236
239
 
237
240
  Pi persistence is required because the extension appends a strict model-invisible cleanup lease before the first inner browser launch. Pi branch changes, quit, and reload abort the script and await normal isolated-session cleanup before state restoration continues. A failed close is retried on the active branch after restart and returns `failureCategory: "cleanup-failed"` plus exact `details.scriptSession.closeCommandArgs` / `close-script-session-after-cleanup-failure`. Any rejected inner policy/validation call fails the top-level result even if source consumes its envelope. See [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#script) for the full schema, limits, result fields, rejected controls, and recovery semantics.
238
241
 
@@ -361,7 +364,7 @@ Successful `snapshot -i` results can also surface `Possible overlay blockers` wh
361
364
  { "args": ["eval", "--stdin"], "stdin": "document.title" }
362
365
  ```
363
366
 
364
- Use `read [url]` for documentation and other unstructured text. `read <url> --raw` preserves the response body, `read <url> --require-md` requires `text/markdown`, `read <url> --llms <index|full>` reads the nearest ancestor llms index/full file, `read <url> --outline` emits headings, `read <url> --filter <text>` narrows matching sections/headings/links, and `read <url> --timeout <ms>` changes the request timeout. Explicit URL reads prefer markdown, try a `.md` path and nearby `llms.txt` links, then fall back to readable HTML without requiring a Chrome page. The wrapper still starts the CLI under its managed identity. A visible `Read execution` line reports the fetch source, CLI start, managed browser lifecycle, and managed-session outcome; the same facts remain in `details.readSource`, `details.lifecycle.effectiveLaunch.browserLaunched`, `details.agentBrowserStarted`, and `details.managedSessionOutcome`. The lifecycle boolean can be `false` before any browser launch or `true` when reusing an active browser. Omit the URL to read rendered active-tab DOM, including current browser auth and client-side state; `--llms` / `--require-md` without a URL instead fetch from the active tab URL. The wrapper renders `data.content` first, retains source/content-type/status/final-URL metadata in `details.data`, keeps fetched URLs from replacing the active browser tab target, and extends its subprocess watchdog for explicit long read timeouts.
367
+ Use `read [url]` for documentation and other unstructured text. `read <url> --raw` preserves the response body, `read <url> --require-md` requires `text/markdown`, `read <url> --llms <index|full>` reads the nearest ancestor llms index/full file, `read <url> --outline` emits headings, `read <url> --filter <text>` narrows matching sections/headings/links, and `read <url> --timeout <ms>` changes the request timeout. Explicit URL reads prefer markdown, try a `.md` path and nearby `llms.txt` links, then fall back to readable HTML without requiring a Chrome page. The wrapper does not allocate or replace a managed browser for an explicit URL read or all-read batch. It skips page verification, tab/ref changes and timeout page probes; malformed read arguments go to native validation without falling back to a DOM preflight. Caller config and flags remain native-owned. `Read execution` reports source, CLI start and native launch evidence without treating the HTTP read as proof of shared-browser liveness. Use `session info` for that. Native no-browser-effects behavior requires the companion upstream fix; older supported binaries do not guarantee it merely because the wrapper skips helpers. Omit the URL to read rendered active-tab DOM, including current browser auth and client-side state; `--llms` / `--require-md` without a URL instead fetch from the active tab URL. The wrapper renders `data.content` first, retains source/content-type/status/final-URL metadata in `details.data`, keeps fetched URLs from replacing the active browser tab target, and extends its subprocess watchdog for explicit long read timeouts.
365
368
 
366
369
  When you already know several visible refs or selectors, extract them in one `batch` call instead of many serial getter calls. When a prior snapshot and session are available and the same-page freshness checks apply, ref-consuming calls add one extra `snapshot -i` preflight per top-level call or batch. Batching shares that probe across rows; it does not remove it:
367
370
 
@@ -551,8 +554,8 @@ For evidence-only screenshots, QA captures, or audit artifacts, save to an expli
551
554
  Wrapper result rendering is metadata-first for saved files. Image MIME types come from a bounded header read for PNG, JPEG, GIF and WebP, never from a filename suffix; missing, unreadable, unknown or truncated headers omit `mediaType`. This identifies a format, not full image validity. Inline screenshots use the same byte check and existing size limit, so a PNG saved as `.webm` still attaches as `image/png`; other artifact kinds are not auto-inlined. An artifact-producing command fails as `artifact-missing` with artifact `status: "stale"` when the reported path's `mtimeMs` falls outside the command's bounded start/end window (with two seconds of filesystem precision tolerance), including a previous recording that `record restart` claims to finalize; clearly old or future-dated evidence is never accepted as a fresh capture. A batch, whether supplied through stdin arrays or argument command strings, must use distinct explicit artifact destinations; preflight canonicalizes existing path ancestry, compares existing file identities to catch hardlinks, and applies full Unicode plus platform case folding on macOS/Windows so aliases cannot satisfy another step's verification. The same preflight prevents `outputPath` from aliasing a same-call browser artifact, follows upstream's forward option consumption and final effective `-o` / `--output` for `diff screenshot`, and treats the optional path on `network har stop` as an artifact destination; upstream ignores positional paths on `network har start`. Outer CLI artifact parsing removes upstream global flags, so direct forms such as `record --json start <path>` and `pdf --quiet <path>` retain their native destinations. Native batch rows do not run that cleanup: `pdf --quick ignored.pdf` writes to the literal path `--quick`, and `download #link --quiet ignored.bin` writes to `--quiet`. Preflight, directory preparation, presentation, and timeout evidence use those same operands, not the ignored trailing tokens. Screenshot destination parsing mirrors upstream's exact flag matching and `[selector] [path]` positional order: `--` is positional, `true` / `false` after screenshot-only `--full` / `-f` remain positional, extra positionals are ignored after the path slot, selector-prefixed (`.`, `#`, `@`) or uppercase-extension single arguments remain selectors, and lowercase image extensions or slash-bearing arguments are paths. The wrapper deliberately keeps its existing slash-bearing hidden-workspace path normalization (for example `.dogfood/run/foo.png`) before launch. `wait --download` is observational and may verify a download that completed just before the wait began, so it is exempt from the command-window mtime gate; an explicit wait destination, in long `--download <path>` or short `-d <path>` form (including `wait --download --timeout 30000 capture.csv`), still participates in active-recording reservation preflight; the path is the next retained operand after the first timeout pair is removed; unsupported `--download=<path>` fails with split-argument guidance:
552
555
  - screenshots return a saved-path summary, visible artifact metadata, structured `details.artifacts` metadata, and an inline image attachment when safe; the visible block includes artifact type, requested path, absolute path, existence, size, cwd, session, and repair/copy status when applicable
553
556
  - downloads, PDFs, `wait --download` files, `state save` state files, diff screenshot output images, traces, CPU profiles, completed video recordings from `record stop`, and path-bearing HAR captures return concise saved-path summaries plus structured `details.artifacts` metadata without inlining large files
554
- - `record start <path>` and `record restart <path>` report `successCategory: "artifact-pending"` and that output will be written on `record stop`; dispatched `record start` and URL-bearing `record restart` attempts append one `Page state:` warning on success or failure, describing conservative ref invalidation rather than an observed page change; explicit `--json` puts that warning in `warnings`. Only reached batch rows qualify, not preflight failures, missing binaries, help calls or unconfirmed planned rows — the wrapper invalidates the session’s prior ref snapshot (direct calls and batch steps alike, and even when the start fails with `Recording already active`, to protect older supported natives that can swap the page before that check), so old `@e…` refs fail as `stale-ref` until a fresh `snapshot -i` succeeds; `record restart <path> <url>` navigates the current page and invalidates refs the same way, while a restart without a URL, including FPS-only options, keeps the current page and refs; `details.artifacts` / `details.artifactVerification` mark that future file as `pending` with `recordingState: "openRecording"` and `willExistOnStop: true`, and `details.nextActions` includes exact `stop-pending-recording` args. When `record restart` finalizes a previous wrapper-known recording, that file must exist and fall within the command mtime window before the result includes `Previous recording saved: …`; a missing or stale prior file fails as `artifact-missing` while the new recording remains visible as pending and the prior manifest row is retired. Within one Pi extension process, an unbounded transcript-backed index reserves active recording destinations independently of the bounded artifact manifest. Artifact lifecycle calls and result `outputPath` writes serialize around that global check; reservations use canonical namespace/session identity, survive manifest eviction and branch replay, and retire after direct, ordered nested-batch, fresh-replacement, script, Electron, or shutdown close; the newest pending row per identity is authoritative. Legacy batch replay retires a pending manifest only when the ordered close lifecycle leaves recording closed; a later successful browser reactivation plus `record start` keeps the new pending reservation. Lexical, hardlink, existing/dangling symlink, full Unicode-fold, and macOS/Windows case aliases are rejected, so `record restart` must use a distinct new path. Do not place `record start` or `record restart` after `close` / `quit` / `exit` in one batch: wrapper preflight rejects it because upstream can report success without starting a recording; split the close and recording into separate calls. A definitive `No recording in progress` stop failure, whether direct or inside a batch, retires stale reservation state at that ordered step; a later successful batch recording row opens its new pending path normally. Any success or failure result that still contains pending recording output includes `stop-pending-recording`. The target remains unverified until recording stops. Native 0.37 checks `ffmpeg` before starting; older supported natives may defer failure. If a successful start/restart reports pending output without `ffmpeg`, the wrapper appends `Recording dependency warning: ffmpeg not found on PATH` and `details.recordingDependencyWarning`; stop, check the result, then install the dependency before starting a new recording.
555
- - `batch` keeps each step's artifacts in `details.batchSteps[].artifacts`; top-level `details.artifacts` and `details.artifactManifest` coalesce an earlier pending recording into the later saved, missing, or stale terminal result for the same namespace/session identity; a successful later close marks an unfinalized pending recording `missing` / `close-abandoned`, removes its stop action, and resets earlier ref/page/network-route batch state; a later successful `record stop` replaces that intermediate abandoned row with its verified saved artifact, and later rows—including failed rows—whose lifecycle reports a browser launch may rebuild state without triggering stale pre-close `about:blank` recovery; failed-step `batchSteps[]` retains only the bounded `lifecycle.effectiveLaunch.browserLaunched` boolean for replay, explicitly non-launching diagnostics leave the close terminal, missing lifecycle evidence remains conservatively active even on the first managed call, every successful close clears wrapper trace/profiler ownership before ordered later successful rows can rebuild it, namespace-scoped `close --all` clears all matching managed/attached/page/ref/route/trace/recording ownership, and any later same-session failure before recording stops keeps exact `stop-pending-recording` args alongside its normal recovery
557
+ - `record start <path>` and `record restart <path>` report `successCategory: "artifact-pending"` and that output will be written on `record stop`; dispatched `record start` and URL-bearing `record restart` attempts append one `Page state:` warning on success or failure, describing conservative ref invalidation rather than an observed page change; explicit `--json` puts that warning in `warnings`. Only reached batch rows qualify, not preflight failures, missing binaries, help calls or unconfirmed planned rows — the wrapper invalidates the session’s prior ref snapshot (direct calls and batch steps alike, and even when the start fails with `Recording already active`, to protect older supported natives that can swap the page before that check), so old `@e…` refs fail as `stale-ref` until a fresh `snapshot -i` succeeds; `record restart <path> <url>` navigates the current page and invalidates refs the same way, while a restart without a URL, including FPS-only options, keeps the current page and refs; `details.artifacts` / `details.artifactVerification` mark that future file as `pending` with `recordingState: "openRecording"` and `willExistOnStop: true`, and `details.nextActions` includes exact `stop-pending-recording` args. When `record restart` returns a native `previousRecording`, that receipt's outcome and capture window control the previous artifact's verification; a legacy file without a terminal native receipt remains unverified, not saved; a missing or stale prior file fails as `artifact-missing` while the new recording remains visible as pending and the prior manifest row is retired. Within one Pi extension process, an unbounded transcript-backed index reserves active recording destinations independently of the bounded artifact manifest. Artifact lifecycle calls and result `outputPath` writes serialize around that global check; reservations use canonical namespace/session identity, survive manifest eviction and branch replay, and retire after direct, ordered nested-batch, fresh-replacement, script, Electron, or shutdown close; the newest pending row per identity is authoritative. Legacy batch replay retires a pending manifest only when the ordered close lifecycle leaves recording closed; a later successful browser reactivation plus `record start` keeps the new pending reservation. Lexical, hardlink, existing/dangling symlink, full Unicode-fold, and macOS/Windows case aliases are rejected, so `record restart` must use a distinct new path. Do not place `record start` or `record restart` after `close` / `quit` / `exit` in one batch: wrapper preflight rejects it because upstream can report success without starting a recording; split the close and recording into separate calls. A `No recording in progress` stop failure checks the matching native receipt once and preserves checked file metadata instead of assuming the path is missing; a later successful batch recording row opens its new pending path normally. Recovery offers an exact status query, and a stop only when the matching take is still current and pending. The target remains unverified until recording stops. Native 0.37 checks `ffmpeg` before starting; older supported natives may defer failure. If a successful start/restart reports pending output without `ffmpeg`, the wrapper appends `Recording dependency warning: ffmpeg not found on PATH` and `details.recordingDependencyWarning`; stop, check the result, then install the dependency before starting a new recording.
558
+ - `batch` keeps each step's artifacts in `details.batchSteps[].artifacts`; top-level `details.artifacts` and `details.artifactManifest` coalesce an earlier pending recording into the later saved, missing, or stale terminal result for the same namespace/session identity; a successful later close retires an unfinalized recording as `close-abandoned`, uses `missing` only after a filesystem check proves absence (otherwise unverified), removes its stop action, and resets earlier ref/page/network-route batch state; a later successful `record stop` replaces that intermediate abandoned row with its verified saved artifact, and later rows—including failed rows—whose lifecycle reports a browser launch may rebuild state without triggering stale pre-close `about:blank` recovery; failed-step `batchSteps[]` retains only the bounded `lifecycle.effectiveLaunch.browserLaunched` boolean for replay, explicitly non-launching diagnostics leave the close terminal, missing lifecycle evidence remains conservatively active even on the first managed call, every successful close clears wrapper trace/profiler ownership before ordered later successful rows can rebuild it, namespace-scoped `close --all` clears all matching managed/attached/page/ref/route/trace/recording ownership, and any later same-session failure before recording stops keeps exact `stop-pending-recording` args alongside its normal recovery
556
559
 
557
560
  `diff screenshot` follows the file-artifact path above for the **diff** image: model-visible text and `details.artifacts` focus on that output, while baseline paths stay out of the artifact summary block, and Pi does **not** auto-inline the diff the way it inlines trusted `screenshot` captures. `state load` may print the loaded path in prose but does not add a saved-file artifact entry the way `state save` does.
558
561
 
@@ -564,6 +567,19 @@ For annotated screenshots in `batch`, put `--annotate` in top-level args instead
564
567
  { "args": ["--annotate", "batch"], "stdin": "[[\"screenshot\",\"/tmp/page.png\"]]" }
565
568
  ```
566
569
 
570
+ #### Recording quality and receipts
571
+
572
+ ```json
573
+ { "args": ["record", "start", "captures/demo.webm", "--fps", "30"] }
574
+ { "args": ["record", "stop"], "outputPath": "captures/demo-receipt.json" }
575
+ ```
576
+
577
+ Inspect `details.artifacts[].recording`: native capture start/end and first/last frame timestamps, wall duration, captured-frame rate, received frames, encoded/written/held/dropped/skipped counts, and separate output duration/FPS. Received frames are not pixel-unique. Repeated/static or late/final-only frames cannot establish smoothness. Missing metrics stay unknown; nominal FPS and `frames / fps` are not wall-clock capture evidence.
578
+
579
+ A stop timeout or `No recording in progress` result triggers one two-second native `session info` query, not another stop. `details.recordingRecovery` keeps the original failed attempt and requires matching session/namespace, recording ID/path (or an effective planned start window), terminal native encoder success and a verified file before recovering success. Same-path older receipts cannot verify a newer take. A timed-out batch may yield a verified recording while other steps remain unproven; unrelated failed-step repair actions remain available. Failed or unverified receipts still export safely to a distinct `outputPath`, with error/attempt provenance and parseable JSON. Follow the returned status/stop actions, not blind mutation retries or longer timeouts.
580
+
581
+ Detailed receipt/live-browser fields and browser-independent native read/confirm handling require companion upstream support not yet present in the current recommended release. Older supported versions remain usable with unknown metrics. Native receipt lookup lasts only while that daemon retains its memory; transcript metadata is not post-exit native recovery. See [the full receipt contract](TOOL_CONTRACT.md#recording-receipts-and-recovery).
582
+
567
583
  #### Artifact retention and dogfood-heavy QA runs
568
584
 
569
585
  The wrapper keeps a bounded, metadata-only `details.artifactManifest` of recent artifacts so long sessions do not grow unbounded. The default recent window is 100 entries and can be raised for screenshot/video-heavy QA sessions with `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES=<count>`.
@@ -572,7 +588,7 @@ This manifest cap controls what appears in `details.artifactManifest` and in sum
572
588
 
573
589
  Browser close commands (`close`, `quit`, or `exit`) are also not file cleanup. If `details.artifactManifest` is present with a non-empty `entries` list, a successful close command appends a compact `Artifact lifecycle` note and reports `details.artifactCleanup` with the current retention summary and the same host-owned cleanup `note` as the contract (`extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts`, `getArtifactCleanupGuidance`). Up to ten distinct user-chosen paths that still exist on disk appear in `explicitArtifactPaths` when matching `explicit-path` manifest rows exist in the recent window; deleted/stale paths are skipped. Otherwise that array is empty and the visible text stays compact while the structured detail still reminds you that close commands do not delete saved files. Delete any paths you care about with host file tools after inspection; the native browser tool intentionally does not remove arbitrary user-chosen filesystem paths.
574
590
 
575
- Oversized snapshots and oversized generic outputs are different: when a persisted pi session is available, their wrapper-managed spill files are stored under the private session artifact directory and are governed by the byte budget `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES` (default 32 MiB). Raise that byte budget as well for long QA sessions that need many full redacted snapshots or large text spills to survive reload/resume.
591
+ Oversized snapshots and oversized generic outputs are different: when a persisted pi session is available, their wrapper-managed spill files are stored under the private session artifact directory and are governed by the byte budget `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES` (default 32 MiB). Raise that byte budget for longer retention, or set `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES=0` to disable automatic eviction of persistent spill files. Zero leaves existing files in place as new spills are written; it does not recover files already evicted or change temporary subprocess spill cleanup.
576
592
 
577
593
  ### Switch from an already-active implicit session to a fresh profiled or alternate-browser launch
578
594
 
@@ -617,7 +633,9 @@ If the result says `Pending confirmation id: c_8f3a1234`, choose one follow-up:
617
633
  { "args": ["deny", "c_8f3a1234"] }
618
634
  ```
619
635
 
620
- Confirmation context may be redacted when it contains credentials, tokens, cookies, or auth-bearing URLs. URL scrubbing covers SAMLRequest, SAMLResponse, RelayState, and auth-context `state` / `nonce` while retaining ordinary non-auth state URLs; persisted snapshot spills receive the same redaction, while exact internal page-target URLs remain available to browser state logic. Use the id exactly as printed.
636
+ For a policy-required explicit URL read, use the returned actions: they name the actual native namespace/session, including `default`, and this routing survives resume/branch replay. The wrapper skips page helpers on matching confirm/deny only when the native read response also advertises `capabilities.readRequiresConfirmation: true`, which includes strict native ID matching. Legacy read prompts retain correct routing but normal page checks; DOM or page-content-shaped prompts never gain the exemption. Explicit caller identities and script isolation still win.
637
+
638
+ Confirmation context may be redacted when it contains credentials, tokens, cookies, or auth-bearing URLs. Replacements are marked `[REDACTED]` (URL-encoded in parsed URLs); ordinary technical phrases such as `bearer token` and `bearer authentication` stay intact outside credential fields and headers. URL scrubbing covers `code`, SAMLRequest, SAMLResponse, RelayState, `authorization_session_id`, and auth-context `state` / `nonce` while retaining ordinary non-auth query values and the spelling of URLs needing no redaction. Visible text, structured details, persisted spills, and `outputPath` exports use the same redaction; exact internal page-target URLs remain available to browser state logic. Use the id exactly as printed.
621
639
 
622
640
  ### Use stateful browser-context commands safely
623
641
 
@@ -735,7 +753,7 @@ Comboboxes vary by app. For native `<select>` controls, prefer raw `select <sele
735
753
  | `state rename <old-name> <new-name>` | Rename a saved state file. |
736
754
  | `state clear [session-name] [--all]` | Clear saved states for one name or all names; `state clear -a` is the upstream short alias for clearing all names. |
737
755
  | `session id --scope worktree --prefix <name>` | Generate a stable session id for agent/worktree-scoped browser state. |
738
- | `session info --json` | Inspect daemon, launch, and restore status for a session. |
756
+ | `session info --json` | One read-only preflight: daemon activity/PID versus native browser liveness, Chrome PID, exact profile, tabs and launched/attached ownership, plus separate Pi cleanup ownership. Missing native fields remain unknown; no browser launch or tab changes. |
739
757
  | `state clean --older-than <days>` | Delete expired saved-state files. |
740
758
  | `frame <selector|main>` | Switch iframe context by selector/ref/name/URL, or return to the main frame. |
741
759
  | `dialog accept [text]` | Accept an alert, confirm, or prompt dialog, optionally supplying prompt text. |
@@ -850,7 +868,7 @@ Current upstream still does not parse `wait <selector> --state hidden` / `wait <
850
868
  | `trace start`, `trace stop [path]` | Record a Chrome DevTools trace. |
851
869
  | `profiler start|stop [path]` | Record a Chrome DevTools profile. |
852
870
  | `record start <path> [url]` | Record the active page; an optional URL navigates first. Use `.webm` or `.mp4` and optional `--fps <n>` (1–60, default 30); native validates startup and requires `ffmpeg` on `PATH`. Verify output after `record stop`. |
853
- | `record stop` | Stop and save video. If this fails with `ffmpeg not found`, install `ffmpeg` / `ffmpeg-full` and rerun the recording. |
871
+ | `record stop` | Finalize video and inspect its native receipt plus wrapper file verification. A failed or recovered stop retains original attempt evidence; use a distinct top-level `outputPath` to save its receipt. |
854
872
  | `record restart <path> [url]` | Stop any current recording and start a new video. Supports the same formats and `--fps` option; without a URL it keeps the page and refs. |
855
873
  | `console [--clear]` | View or clear console logs. |
856
874
  | `errors [--clear]` | View or clear page errors. |
@@ -1006,7 +1024,7 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
1006
1024
  `agent-browser` 0.35.0 and newer require separate argv tokens for global flag values (for example, `--args <args>` and `--user-agent <ua>`). The explicit exception is `--restore=<key>`, which is supported when an optional restore key could otherwise be confused with a command word. The wrapper rejects other global `--flag=value` forms before normal command dispatch, including when they trail the command. Plain `--help`, `-h`, `--version`, and `-V` inspection preserves exact caller argv because upstream accepts those top-level inspection shapes. Global flags for `batch` belong before `batch` in top-level `args`; row-local equals forms are rejected without the help/version or `--restore=<key>` exceptions.
1007
1025
 
1008
1026
  - `--profile <name|path>`: reuse Chrome profile login state by directory name from `profiles`, or use a persistent custom profile/profile-directory path when upstream accepts it. Environment: `AGENT_BROWSER_PROFILE`.
1009
- - `--session <name>`: use an isolated session. Environment: `AGENT_BROWSER_SESSION`.
1027
+ - `--session <name>`: use an isolated session. Environment: `AGENT_BROWSER_SESSION`. Native session names may begin with a hyphen; they remain values, not extra flags, including when selected through config or environment.
1010
1028
  - `--restore [name]`: auto-save/restore cookies, local storage, and session storage; bare `--restore` uses `--session` as the key. Environment: `AGENT_BROWSER_RESTORE`. Wrapper-owned implicit sessions set a transcript- and checkout-scoped restore key automatically unless disabled with `PI_AGENT_BROWSER_MANAGED_SESSION_RESTORE=0` or suppressed by incompatible caller launch choices. Explicit restore/state/session/config choices pass through unchanged and remain visible in results. Automatic restore validates only its own checkout/storage identity and coordinates same-daemon reuse so wrapper restore pools cannot mix.
1011
1029
  - `--restore-save <policy>` (`auto`, `always`, or `never`): restore auto-save policy. Environment: `AGENT_BROWSER_RESTORE_SAVE`. Restore-enabled sessions also save periodically while open; `AGENT_BROWSER_AUTOSAVE_INTERVAL_MS` sets the minimum interval in milliseconds (`30000` by default, `0` disables periodic saves but not save-on-close).
1012
1030
  - `--restore-check-url <glob>`, `--restore-check-text <txt>`, `--restore-check-fn <js>`: validate restored state before auto-save. Environments: `AGENT_BROWSER_RESTORE_CHECK_URL`, `AGENT_BROWSER_RESTORE_CHECK_TEXT`, `AGENT_BROWSER_RESTORE_CHECK_FN`.
@@ -1043,7 +1061,7 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
1043
1061
  On Android/Termux, follow the README setup to install the packaged Linux-musl arm64 upstream binary, install Termux's `which`, and expose its launcher as `$PREFIX/bin/chromium`. Prefer that upstream system-browser discovery over ambient `AGENT_BROWSER_EXECUTABLE_PATH`: it survives isolated `HOME` values, works for ordinary calls and top-level `script`, and preserves the script security boundary that clears ambient launch controls and rejects inner `--executable-path` flags. Wrapper-generated Android managed identities use a compact 80-bit digest so ordinary namespaces and fresh rotations fit upstream's Unix socket path.
1044
1062
 
1045
1063
  - `--no-auto-dialog`: disable automatic dismissal of alert/beforeunload dialogs. Environment: `AGENT_BROWSER_NO_AUTO_DIALOG`.
1046
- - `--idle-timeout <ms>`: launch-scoped background browser lifecycle setting. The wrapper already sets one stable `AGENT_BROWSER_IDLE_TIMEOUT_MS` for top-level and helper subprocesses. A per-call value must equal that configured value; otherwise the tool rejects it before launch and tells you to restart Pi with `PI_AGENT_BROWSER_IMPLICIT_SESSION_IDLE_TIMEOUT_MS=<ms>`. This prevents upstream from restarting the browser and discarding tabs/refs when later helper calls use a different launch environment.
1064
+ - `--idle-timeout <ms>`: native background browser lifecycle setting (also accepts `10s`, `3m`, `1h`). Caller-owned sessions retain native idle policy; an explicit flag is carried to every helper in that call. Keep it consistent between calls to avoid a native daemon restart. Only wrapper-owned sessions receive the implicit timeout and numeric mismatch check against `PI_AGENT_BROWSER_IMPLICIT_SESSION_IDLE_TIMEOUT_MS`.
1047
1065
 
1048
1066
  ### Output, provider, policy, and AI flags
1049
1067
 
@@ -1077,6 +1095,8 @@ Standalone `agent-browser` looks for `agent-browser.json` in these locations, fr
1077
1095
  3. Environment variables, including `AGENT_BROWSER_CONFIG`.
1078
1096
  4. CLI flags.
1079
1097
 
1098
+ Native `session` and `namespace` defaults are honored by ordinary tool calls before implicit-session generation; `sessionName` is only a legacy restore key. Per-call flags override environment, which overrides project/user JSON. `--config` or `AGENT_BROWSER_CONFIG` selects one file instead of merging the discovered files; per-call config also reaches helper subprocesses. Caller-owned native `AGENT_BROWSER_SOCKET_DIR` is honored unless the wrapper-specific socket override is set. Script alone bypasses these defaults with an empty temporary config.
1099
+
1080
1100
  Use separated `--config <path>` to load a specific upstream config; upstream 0.33.2 does not recognize `--config=<path>` as the global selector. Browser-backed and sessionless native calls preserve `--config`, `AGENT_BROWSER_CONFIG`, passive project/user config, and other upstream environment exactly as supplied. The Pi-scoped package config under `.pi/config/pi-agent-browser-native/` remains separate. Boolean flags accept optional `true` or `false` values, such as `--headed false`, `--webgpu false`, or `--no-webmcp false`, to override config. Browser extensions from user and project configs are merged rather than replaced.
1081
1101
 
1082
1102
  Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGENT_BROWSER_AUTOSAVE_INTERVAL_MS`, `AGENT_BROWSER_STREAM_PORT`, `AGENT_BROWSER_STREAM_QUALITY`, `AGENT_BROWSER_STREAM_MAX_WIDTH`, `AGENT_BROWSER_STREAM_MAX_HEIGHT`, `AGENT_BROWSER_IDLE_TIMEOUT_MS`, `AGENT_BROWSER_ENCRYPTION_KEY`, `AGENT_BROWSER_STATE_EXPIRE_DAYS`, `AGENT_BROWSER_IOS_DEVICE`, `AGENT_BROWSER_IOS_UDID`, `AI_GATEWAY_URL`, `AI_GATEWAY_API_KEY`, provider credential names, and AWS credential names when using AgentCore. The upstream child receives the parent environment plus wrapper overrides such as the managed socket directory, clamped default operation timeout, canonical owned-session namespace (including empty default), and Pi-transcript- plus Git-checkout-generation-scoped `AGENT_BROWSER_RESTORE` for wrapper-owned managed sessions (`buildAgentBrowserProcessEnv` in `extensions/agent-browser/lib/process.ts`, ownership carried by the wrapper's typed process options and call-scoped managed-session context). Model-facing output still redacts recognized secret values.
@@ -1089,14 +1109,14 @@ Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGE
1089
1109
  <!-- agent-browser-playbook:start wrapper-tab-recovery -->
1090
1110
  <!-- Generated from extensions/agent-browser/lib/playbook.ts. Run `npm run docs -- playbook write` to update. -->
1091
1111
  - After open/goto/navigate calls with --profile, --restore, --session-name, or --state, agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch or reconnect.
1092
- - After confirmed shutdown of an automatically restored managed session, the wrapper retains its complete recorded URL, including the fragment, until the first current-page operation (including get url and reload). Non-page calls such as tab list or read <url> may start a daemon without fulfilling that reopen. The wrapper uses native open once, verifies the observed tab, and discards old refs/frame scope; it does not restore unsaved forms, JavaScript memory, or history. Explicit navigation, caller-owned/attached sessions, and restore-disabled sessions are not auto-reopened.
1112
+ - After confirmed shutdown of an automatically restored managed session, the wrapper retains its complete recorded URL, including the fragment, until the first current-page operation (including get url and reload). Non-page calls such as tab list may start a daemon without fulfilling that reopen; explicit URL reads leave the managed browser and pending reopen untouched. The wrapper uses native open once, verifies the observed tab, and discards old refs/frame scope; it does not restore unsaved forms, JavaScript memory, or history. Explicit navigation, caller-owned/attached sessions, and restore-disabled sessions are not auto-reopened.
1093
1113
  - For a still-live browser after tab drift or resume, the wrapper verifies/selects the intended tab before ref/semantic helpers and page commands; failed selection stops the call without navigating. Local commands, read <url>, URL a11y/vitals, diff url, window new, and explicit tab/navigation/connection/state recovery do not require the prior tab. Batch checks follow effective rows past non-page prefixes and stop at explicit context changes, preserving caller argv/stdin and continue-on-error behavior. Same-tab reselection is avoided because it clears refs. Use exact batch --bail for fail-fast, not --bail=<value>. Routine same-session calls skip tab-list preflights.
1094
1114
  - For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.
1095
1115
  - If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.
1096
1116
  - If upstream reports tab_gone, the pinned bound tab is gone; use details.nextActions (tab list / tab new) instead of assuming another tab is yours.
1097
1117
  <!-- agent-browser-playbook:end wrapper-tab-recovery -->
1098
- - Wrapper-spawned commands clamp `AGENT_BROWSER_DEFAULT_TIMEOUT` to the upstream documented 25-second default and use a 35-second child-process watchdog (`PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS` overrides the default 35s budget; top-level `timeoutMs` overrides it per browser CLI call). Explicit `wait <ms>`, `wait --timeout <ms>`, and WebMCP `invoke` / `result --timeout <ms>` calls can exceed that default; when top-level `timeoutMs` is omitted, the wrapper derives a subprocess watchdog from the requested command duration plus a small grace window. Batch budgeting and timeout recovery read the same effective source as upstream: raw command strings when present, otherwise stdin rows. Dialog commands are additionally bounded to 5 seconds (`PI_AGENT_BROWSER_DIALOG_PROCESS_TIMEOUT_MS`), and click/tap/find refs or tokens plus `eval --stdin` snippets that look like alert/confirm/prompt/dialog triggers are bounded to 8 seconds (`PI_AGENT_BROWSER_DIALOG_TRIGGER_PROCESS_TIMEOUT_MS`). When any watchdog fires, `details.timeoutPartialProgress` may include a planned step list with per-step status (including `generatedFrom` labels for wrapper-inserted rows such as `open.loadState`) and a `retry-timeout-step` next action with a one-row native batch (`args: ["batch"]` plus `stdin`) only when the first incomplete step is read-only or idempotent, or `inspect-current-page-after-timeout` when the target is already verified but the incomplete step may be mutating and should not be blindly retried. If the target is unknown, standalone snapshots are removed and visible failure text plus `details.nextActions` show `verify-page-target-after-timeout`, including its session-scoped `batch --bail` args and short stdin for fail-fast `get url` then `snapshot -i`; dialog status/accept/dismiss actions remain allowed for blocking-dialog recovery. It also includes current page URL from best-effort session `get url`, followed by `get title` only after a URL is recovered (or a planned URL inferred from the step list when the session cannot answer), an `openedButPostOpenTimedOut` classification only when a live page URL was recovered before a later step hung, and declared artifact paths such as `screenshot`, `pdf`, `download`, or `wait --download` outputs with existence/state checks; the same evidence is appended under `Timeout partial progress` in visible text with URL/path redaction.
1099
- - Oversized snapshots and oversized generic outputs may be compacted in tool content, with the full redacted output written to a spill file path shown directly in the tool result. Recent artifact metadata is bounded by `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES` (default 100); persisted spill files are separately bounded by `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES` (default 32 MiB).
1118
+ - Wrapper-spawned commands clamp `AGENT_BROWSER_DEFAULT_TIMEOUT` to the upstream documented 25-second default and use a 35-second child-process watchdog (`PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS` overrides the default 35s budget; top-level `timeoutMs` overrides it per browser CLI call). Explicit `wait <ms>`, `wait --timeout <ms>`, and WebMCP `invoke` / `result --timeout <ms>` calls can exceed that default; when top-level `timeoutMs` is omitted, the wrapper derives a subprocess watchdog from the requested command duration plus a small grace window. Batch budgeting and timeout recovery read the same effective source as upstream: raw command strings when present, otherwise stdin rows. Dialog commands are additionally bounded to 5 seconds (`PI_AGENT_BROWSER_DIALOG_PROCESS_TIMEOUT_MS`), and click/tap/find refs or tokens plus `eval --stdin` snippets that look like alert/confirm/prompt/dialog triggers are bounded to 8 seconds (`PI_AGENT_BROWSER_DIALOG_TRIGGER_PROCESS_TIMEOUT_MS`). A `session info` timeout returns only an exact-session `retry-session-info` status action, without page probes, liveness claims, or changes to existing page/ref state. When a browser-operation watchdog fires, `details.timeoutPartialProgress` may include a planned step list with per-step status (including `generatedFrom` labels for wrapper-inserted rows such as `open.loadState`) and a `retry-timeout-step` next action with a one-row native batch (`args: ["batch"]` plus `stdin`) only when the first incomplete step is read-only or idempotent, or `inspect-current-page-after-timeout` when the target is already verified but the incomplete step may be mutating and should not be blindly retried. If the target is unknown, standalone snapshots are removed and visible failure text plus `details.nextActions` show `verify-page-target-after-timeout`, including its session-scoped `batch --bail` args and short stdin for fail-fast `get url` then `snapshot -i`; dialog status/accept/dismiss actions remain allowed for blocking-dialog recovery. It also includes current page URL from best-effort session `get url`, followed by `get title` only after a URL is recovered (or a planned URL inferred from the step list when the session cannot answer), an `openedButPostOpenTimedOut` classification only when a live page URL was recovered before a later step hung, and declared artifact paths such as `screenshot`, `pdf`, `download`, or `wait --download` outputs with existence/state checks; the same evidence is appended under `Timeout partial progress` in visible text with URL/path redaction.
1119
+ - Oversized snapshots and oversized generic outputs may be compacted in tool content, with the full redacted output written to a spill file path shown directly in the tool result. Recent artifact metadata is bounded by `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES` (default 100); persisted spill files have a separate `PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES` budget (default 32 MiB; `0` disables automatic eviction).
1100
1120
  - The wrapper keeps `--help` and `--version` stateless so they do not consume the implicit managed-session slot.
1101
1121
 
1102
1122
  ## Generated capability baseline