pi-agent-browser-native 0.2.77 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +8 -17
  3. package/dist/extensions/agent-browser/index.js +127 -19
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +0 -5
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +4 -5
  6. package/dist/extensions/agent-browser/lib/command-policy.js +0 -5
  7. package/dist/extensions/agent-browser/lib/command-taxonomy.js +0 -6
  8. package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
  9. package/dist/extensions/agent-browser/lib/config.js +0 -6
  10. package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
  11. package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
  12. package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
  13. package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
  14. package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
  15. package/dist/extensions/agent-browser/lib/input-modes/job.js +0 -5
  16. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +0 -5
  17. package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -6
  18. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +0 -5
  19. package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
  20. package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
  21. package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
  22. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +4 -5
  23. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
  24. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +0 -3
  25. package/dist/extensions/agent-browser/lib/managed-session-restore.js +30 -13
  26. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +0 -5
  27. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +1 -7
  28. package/dist/extensions/agent-browser/lib/managed-session-storage.js +0 -5
  29. package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +1 -1
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +41 -30
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +17 -11
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +22 -5
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +35 -7
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +17 -1
  40. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +35 -18
  41. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +4 -1
  42. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
  43. package/dist/extensions/agent-browser/lib/parsing.js +0 -7
  44. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +4 -1
  45. package/dist/extensions/agent-browser/lib/playbook.js +3 -10
  46. package/dist/extensions/agent-browser/lib/process-identity.js +0 -5
  47. package/dist/extensions/agent-browser/lib/process.js +0 -7
  48. package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
  49. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +1 -8
  50. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -7
  51. package/dist/extensions/agent-browser/lib/results/categories.js +1 -8
  52. package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
  53. package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
  54. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
  55. package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
  56. package/dist/extensions/agent-browser/lib/results/network.js +0 -7
  57. package/dist/extensions/agent-browser/lib/results/next-actions.js +0 -30
  58. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +1 -7
  59. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
  60. package/dist/extensions/agent-browser/lib/results/presentation/common.js +0 -5
  61. package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
  62. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +0 -5
  63. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
  64. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
  65. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +0 -5
  66. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +0 -5
  67. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
  68. package/dist/extensions/agent-browser/lib/results/presentation.js +0 -5
  69. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +0 -7
  70. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
  71. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +0 -7
  72. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +0 -7
  73. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
  74. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
  75. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
  76. package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
  77. package/dist/extensions/agent-browser/lib/results/text.js +0 -7
  78. package/dist/extensions/agent-browser/lib/runtime.js +4 -14
  79. package/dist/extensions/agent-browser/lib/session-page-state.js +0 -7
  80. package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
  81. package/dist/extensions/agent-browser/lib/temp.js +0 -7
  82. package/dist/extensions/agent-browser/lib/web-search.js +0 -5
  83. package/docs/ARCHITECTURE.md +16 -16
  84. package/docs/COMMAND_REFERENCE.md +8 -7
  85. package/docs/ELECTRON.md +3 -19
  86. package/docs/RELEASE.md +5 -17
  87. package/docs/REQUIREMENTS.md +4 -4
  88. package/docs/SUPPORT_MATRIX.md +17 -17
  89. package/docs/TOOL_CONTRACT.md +18 -16
  90. package/docs/platform-smoke.md +1 -1
  91. package/package.json +5 -6
  92. package/scripts/doctor.mjs +1 -1
  93. package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
  94. package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
  95. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
  96. package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
  97. package/dist/extensions/agent-browser/lib/results.js +0 -14
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.3.0 - 2026-08-06
6
+
7
+ ### Changed
8
+
9
+ - Raised the minimum supported Pi runtime to 0.84.0 with no compatibility shims for older Pi releases, bumped the package from 0.2.x to 0.3.0 for the breaking support-floor change, pinned direct Pi development dependencies and the fleet marker to 0.84.0, and retained optional wildcard Pi peer dependencies per Pi package guidance.
10
+ - Audited the extension factory, native tool registration, schemas, Pi `tool_result` patching, TUI rendering, SDK/package harnesses, browser/session/profile lifecycle, artifacts, lookups, Electron paths, build/package scripts, docs, fixtures, and tests against all Pi 0.84.0 breaking changes. The package does not consume the renamed model transform, RPC delta accumulator, provider header/refresh/auth APIs, pi-agent-core harness repositories or custom filesystem, or remote-session summary APIs; the existing coding-agent `ModelRuntime`, `createAgentSession`, `SessionManager`, extension, and tool contracts remain valid on 0.84.0.
11
+ - Added Pi 0.84.0's `scrollbarThumb` background color to the complete test theme fixture and refreshed the lockfile against the released 0.84.0 packages and TypeBox 1.3.7.
12
+ - Updated the `protobufjs` safety override to 7.6.5, clearing the advisory carried by the previous 7.6.4 pin.
13
+
14
+ ### Fixed
15
+
16
+ - Result `outputPath` writes now fail validation instead of overwriting a screenshot, download, recording, or other browser artifact when both destinations resolve to the same file; the browser artifact and its verified metadata remain intact, including through filesystem aliases such as hard links.
17
+
18
+ - Headed wrapper-managed launches now disable upstream periodic restore autosave by default and retain that launch environment across every follow-up subprocess, including still-owned off-current sessions, transcript-restored sessions whose replacement cleanup failed, and Electron cleanup closes, preventing agent-browser 0.33.2's multi-origin storage collector from flashing temporary tabs, blocking daemon policy probes, or triggering daemon-configuration mismatches; native close still saves, while direct window close can lose newer state because headed browsers are exempt from idle shutdown. The effective launch-time interval, including an explicit `AGENT_BROWSER_AUTOSAVE_INTERVAL_MS`, is persisted across transcript resume; changing it in either direction on a running wrapper-owned headed session is rejected until close plus a fresh launch. Slow valid daemon inspections now receive the full 35-second policy budget instead of failing after five seconds.
19
+ - `--headed` and `--headed false` are now enforced as launch-scoped choices instead of being silently sent to an already-running managed session.
20
+ - Bare, review-only, fenced-reference, conditional, permissive/uncertain, directly negated, and Pi clipboard/attachment image/video paths no longer become requested output artifacts that block browser close; output enforcement now requires a direct artifact-creation phrase with a destination, carries that intent across contiguous plain or Markdown path-list lines, handles delimited paths, preserves subordinate requirements such as “do not close until you save,” scopes availability-qualified recordings before, within, or after their list, leaves explicitly optional artifacts unenforced, applies recording availability per path clause, makes required duplicate paths take precedence, handles Markdown-link destinations, scans single-line and multiline path lists once instead of once per path, and avoids pathological backtracking on slash-heavy non-path text.
21
+
22
+ ## 0.2.78 - 2026-08-04
23
+
24
+ ### Changed
25
+
26
+ - Removed zero-behavior maintainer bloat: Purpose/Responsibilities file banners, barrel-only `lib/results.ts` and `lib/input-modes.ts` facades, the synthetic efficiency benchmark script/tests/verify mode, the completed Electron plan ADR, and the `AgentBrowserNextActionCollector` class (plain array helpers remain).
27
+ - Folded tiny one-liner modules (`session-artifacts`, `artifact-state`, `electron/text`) into neighboring owners; pending-recording predicates live in `artifact-manifest.ts`.
28
+ - Trimmed docs that only existed to index or advertise the removed surfaces (`AGENTS.md`, `RELEASE.md`, `SUPPORT_MATRIX.md`, `ARCHITECTURE.md`, `ELECTRON.md`, `TOOL_CONTRACT.md`, `COMMAND_REFERENCE.md`, README).
29
+
3
30
  ## 0.2.77 - 2026-08-04
4
31
 
5
32
  ### Fixed
package/README.md CHANGED
@@ -89,7 +89,7 @@ The result is optimized for agent work:
89
89
 
90
90
  ## Fastest way to try it
91
91
 
92
- Use Pi 0.80.6 or newer. This package keeps Pi core imports as wildcard `peerDependencies` because Pi package docs require the host Pi install to provide those packages, and `pi-agent-browser-doctor` fails setup when `pi --version` is below the enforced runtime floor. The current source tree's Pi compatibility is audited and validated against the Pi 0.80.9 extension/package baseline, including Project Trust; the runtime floor remains 0.80.6 because this refresh requires no newer runtime API.
92
+ Use Pi 0.84.0 or newer. This package keeps optional Pi core imports as wildcard `peerDependencies` because Pi package docs require the host Pi install to provide those packages, pins its direct Pi validation dependencies to 0.84.0, and makes older hosts a setup failure through `pi-agent-browser-doctor`. Version 0.3.0 intentionally provides no compatibility shims for older Pi releases.
93
93
 
94
94
  Install upstream `agent-browser` first and make sure it is on `PATH`:
95
95
 
@@ -125,7 +125,7 @@ For a one-off trial that does not touch your configured Pi extensions:
125
125
  pi --no-extensions -e npm:pi-agent-browser-native
126
126
  ```
127
127
 
128
- Pi 0.79+ may ask whether to trust the current project before loading project-local instructions, settings, or resources. This extension treats its own project-local package config as developer-trusted by default; use `--no-approve` when you intentionally want Pi and this extension to ignore project-local inputs for that run.
128
+ Pi 0.84.0+ may ask whether to trust the current project before loading project-local instructions, settings, or resources. This extension treats its own project-local package config as developer-trusted by default; use `--no-approve` when you intentionally want Pi and this extension to ignore project-local inputs for that run.
129
129
 
130
130
  For a specific published version:
131
131
 
@@ -234,7 +234,7 @@ printf '%s' "$EXA_API_KEY" | npm exec --yes --package pi-agent-browser-native@la
234
234
  npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-command "op read 'op://Private/Brave Search/API Key'" --provider brave --global
235
235
  ```
236
236
 
237
- Config merges in this order: global → project → `PI_AGENT_BROWSER_CONFIG` override. Under Pi 0.79+, the globally installed or CLI-loaded extension still loads project-local `.pi/config/pi-agent-browser-native/config.json` when Pi trust allows that project layer; it skips that project layer when Pi reports the project is untrusted or when Pi is launched with `--no-approve`. `webSearch.enabled` is evaluated after the loaded layers merge. Use `web-search disable --global` for a user default, `web-search disable --project` for one repo, and a `PI_AGENT_BROWSER_CONFIG` override with `{ "webSearch": { "enabled": false } }` when web search must stay off even if project config exists. Loaded config may use plaintext, custom environment aliases, interpolation literals, malformed-or-late-bound `$` values, and `!command` credential sources; the resolved secret is passed to the provider request while tool content, details, status output, and docs examples stay redacted. `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`.
237
+ Config merges in this order: global → project → `PI_AGENT_BROWSER_CONFIG` override. Under Pi 0.84.0+, the globally installed or CLI-loaded extension still loads project-local `.pi/config/pi-agent-browser-native/config.json` when Pi trust allows that project layer; it skips that project layer when Pi reports the project is untrusted or when Pi is launched with `--no-approve`. `webSearch.enabled` is evaluated after the loaded layers merge. Use `web-search disable --global` for a user default, `web-search disable --project` for one repo, and a `PI_AGENT_BROWSER_CONFIG` override with `{ "webSearch": { "enabled": false } }` when web search must stay off even if project config exists. Loaded config may use plaintext, custom environment aliases, interpolation literals, malformed-or-late-bound `$` values, and `!command` credential sources; the resolved secret is passed to the provider request while tool content, details, status output, and docs examples stay redacted. `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`.
238
238
 
239
239
  For Exa, the tool defaults to `searchType: "auto"` with `contents.highlights: true`. Agents may pass `searchType` (`fast`, `instant`, `deep-lite`, `deep`, or `deep-reasoning`) only when the task needs that latency/depth tradeoff; structured output schemas are intentionally not exposed yet.
240
240
 
@@ -268,6 +268,8 @@ Watch a browser window during a demo or QA run by adding upstream's global `--he
268
268
  { "args": ["screenshot", "/tmp/agent-browser-headed-check.png"] }
269
269
  ```
270
270
 
271
+ For wrapper-owned headed launches, the extension disables upstream 0.33.2 periodic restore autosave by default because its multi-origin collector opens visible temporary tabs and can delay daemon policy inspection. The extension records the effective launch-time interval and reapplies it to every helper and follow-up subprocess, including still-owned off-current sessions (also after failed replacement cleanup), Electron cleanup closes, and reload/resume, so the receiving daemon does not see changing configuration. Native `close` still saves, but upstream exempts headed browsers from idle shutdown, so closing the window by hand can lose newer state. Set `AGENT_BROWSER_AUTOSAVE_INTERVAL_MS` before launch when periodic preservation matters; changing it on a running wrapper-owned headed daemon is rejected until you close that session and launch fresh.
272
+
271
273
  Render a WebGPU page by enabling upstream's WebGPU launch preset on a fresh local browser:
272
274
 
273
275
  ```json
@@ -321,7 +323,7 @@ Evaluate page JavaScript through stdin. Put the script in the top-level `stdin`
321
323
  { "args": ["eval", "--stdin"], "stdin": "({ title: document.title, url: location.href })", "outputPath": "logs/page-state.json" }
322
324
  ```
323
325
 
324
- Use `outputPath` when `eval`, `get`, `snapshot`, or another extraction should be saved as a durable workspace file. The wrapper writes `details.data` when present, otherwise the model-facing text content, and returns `details.outputFile` with the saved path and byte count. Explicit upstream `--json` content stays parseable; in that case the save notice lives only in `details.outputFile`.
326
+ Use `outputPath` when `eval`, `get`, `snapshot`, or another extraction should be saved as a durable workspace file. Keep it distinct from screenshot, download, recording, and other browser artifact destinations; if the paths resolve to the same file, the wrapper preserves the browser artifact and rejects the result-data write. The wrapper writes `details.data` when present, otherwise the model-facing text content, and returns `details.outputFile` with the saved path and byte count. Explicit upstream `--json` content stays parseable; in that case the save notice lives only in `details.outputFile`.
325
327
 
326
328
  Extract several known refs or selectors in one `batch` call instead of many serial getter calls:
327
329
 
@@ -440,7 +442,7 @@ After either path, use `qa: { "attached": true, ... }` for a current-session smo
440
442
 
441
443
  ### Lightweight QA preset
442
444
 
443
- For a quick smoke/QA pass, use top-level `qa`. It compiles to the same batch path as `job` and uses `batch --bail` so failed readiness/text/selector assertions stop before slower diagnostics can burn the wrapper watchdog. The URL form clears enabled network/console/page-error buffers before opening the target URL, waits for page readiness, checks optional expected text or selector, inspects fresh network requests, console messages, and page errors when preceding assertions pass, and can capture an evidence screenshot. Successful reset rows are labeled as reset-scoped output and ignored by QA failure analysis so stale pre-target errors do not fail an otherwise healthy target page; real post-open diagnostic rows still fail or warn according to the normal QA rules. Expected text is checked with bounded visible-text `wait --fn … --timeout 5000` predicates after the requested load state so dense pages can pass on visible headings/copy and missing text becomes crisp QA evidence. The attached form (`qa: { "attached": true }`) runs checks against the current managed session, such as an attached Electron app, rejects `url`, and deliberately preserves existing diagnostics instead of clearing evidence; its diagnostic reads default off so stale buffers do not fail a current-page smoke unless `checkNetwork`, `checkConsole`, or `checkErrors` is explicitly `true`. `loadState` defaults to `"domcontentloaded"`; set it to `"load"` or `"networkidle"` only when the stricter state is useful and the site is not expected to keep background requests alive. For URL-opening QA, `checkNetwork`, `checkConsole`, and `checkErrors` default to true; set one to `false` to skip that diagnostic read. Network failures are classified by likely impact and failed rows are listed first in network previews: actionable document/script/API-style failures still fail QA, while some low-impact browser icon asset misses (for example certain `favicon` or `apple-touch-icon` paths when upstream marks the row failed and resource metadata looks image-like) surface only as warnings instead of failing an otherwise healthy smoke check (`details.qaPreset.warnings`, with human-readable `details.qaPreset.summary` when the preset still passes). Exact predicates live in [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#qa) and `classifyNetworkRequestFailure` in `extensions/agent-browser/lib/results/network.ts` (re-exported from the compatibility barrel).
445
+ For a quick smoke/QA pass, use top-level `qa`. It compiles to the same batch path as `job` and uses `batch --bail` so failed readiness/text/selector assertions stop before slower diagnostics can burn the wrapper watchdog. The URL form clears enabled network/console/page-error buffers before opening the target URL, waits for page readiness, checks optional expected text or selector, inspects fresh network requests, console messages, and page errors when preceding assertions pass, and can capture an evidence screenshot. Successful reset rows are labeled as reset-scoped output and ignored by QA failure analysis so stale pre-target errors do not fail an otherwise healthy target page; real post-open diagnostic rows still fail or warn according to the normal QA rules. Expected text is checked with bounded visible-text `wait --fn … --timeout 5000` predicates after the requested load state so dense pages can pass on visible headings/copy and missing text becomes crisp QA evidence. The attached form (`qa: { "attached": true }`) runs checks against the current managed session, such as an attached Electron app, rejects `url`, and deliberately preserves existing diagnostics instead of clearing evidence; its diagnostic reads default off so stale buffers do not fail a current-page smoke unless `checkNetwork`, `checkConsole`, or `checkErrors` is explicitly `true`. `loadState` defaults to `"domcontentloaded"`; set it to `"load"` or `"networkidle"` only when the stricter state is useful and the site is not expected to keep background requests alive. For URL-opening QA, `checkNetwork`, `checkConsole`, and `checkErrors` default to true; set one to `false` to skip that diagnostic read. Network failures are classified by likely impact and failed rows are listed first in network previews: actionable document/script/API-style failures still fail QA, while some low-impact browser icon asset misses (for example certain `favicon` or `apple-touch-icon` paths when upstream marks the row failed and resource metadata looks image-like) surface only as warnings instead of failing an otherwise healthy smoke check (`details.qaPreset.warnings`, with human-readable `details.qaPreset.summary` when the preset still passes). Exact predicates live in [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#qa) and `classifyNetworkRequestFailure` in `extensions/agent-browser/lib/results/network.ts`.
444
446
 
445
447
  ```json
446
448
  {
@@ -606,17 +608,6 @@ npm run verify -- pre-pr
606
608
 
607
609
  That mode composes the full default gate with `npm run verify -- package`, so package contents and forbidden repo-only files are checked without launching Pi lifecycle, Crabbox, or live dogfood flows. Package modes build through npm `prepare`; lifecycle and startup-profile build in their focused scripts; default and platform-target build before consuming `dist/`, so clean checkouts do not validate stale or missing compiled output. The same `prepare` script owns GitHub/source installs; when Pi installs with `npm install --omit=dev`, it installs the source-build dev dependencies with lifecycle scripts disabled before building the ignored `dist/` entrypoint that Pi loads.
608
610
 
609
- The deterministic agent-efficiency benchmark’s **standalone JSON/Markdown accounting run** is not part of default or pre-PR `npm run verify` (only `npm run verify -- benchmark` or `npm run benchmark:agent-browser` invokes the script). The full unit suite still exercises `test/agent-browser.efficiency-benchmark.test.ts`. Use the script before and after agent-facing abstractions to prove call-count, output-size, stale-ref, artifact, failure-category coverage, success-rate, and elapsed-time effects before changing the wrapper UX:
610
-
611
- ```bash
612
- npm run benchmark:agent-browser
613
- npm run verify -- benchmark
614
- ```
615
-
616
- Save a JSON baseline (for example before changing playbook or wrapper behavior), then compare later runs: `npm run benchmark:agent-browser -- --json > /tmp/agent-browser-benchmark.json` and `npm run benchmark:agent-browser -- --compare /tmp/agent-browser-benchmark.json`.
617
-
618
- It does not launch a browser or mutate local profiles; it models representative raw workflows and provides a stable baseline for later comparisons.
619
-
620
611
  The opt-in startup profiler measures only the package extension entrypoint import plus factory registration in fresh Node processes. It intentionally does **not** launch Pi, tmux, mise, npm, browsers, or `agent-browser`; full Pi TUI ready-prompt profiling proved too invasive for routine verification on the operator machine. Run it after package entrypoint, generated runtime, or top-level import changes:
621
612
 
622
613
  ```bash
@@ -704,7 +695,7 @@ Configured-source lifecycle validation:
704
695
  npm run verify -- lifecycle
705
696
  ```
706
697
 
707
- The harness defaults to Pi model `zai/glm-5.2` and **180000 ms** per-step tmux waits; pass `--model <id>` and/or `--timeout-ms <ms>` after `lifecycle` when you need different settings (see [Configured-source lifecycle validation](docs/RELEASE.md#configured-source-lifecycle-validation) in `docs/RELEASE.md`). It launches Pi 0.79 with `--approve` and a deterministic `--session-id`, drives `/reload`, closes Pi, relaunches the exact same session, asserts the JSONL header id, and checks managed-session continuity, compiled-entrypoint pickup after process restart, persisted spill reachability, and real Pi `tool_result` failure-patch behavior.
698
+ The harness defaults to Pi model `zai/glm-5.2` and **180000 ms** per-step tmux waits; pass `--model <id>` and/or `--timeout-ms <ms>` after `lifecycle` when you need different settings (see [Configured-source lifecycle validation](docs/RELEASE.md#configured-source-lifecycle-validation) in `docs/RELEASE.md`). It launches the supported Pi runtime with `--approve` and a deterministic `--session-id`, drives `/reload`, closes Pi, relaunches the exact same session, asserts the JSONL header id, and checks managed-session continuity, compiled-entrypoint pickup after process restart, persisted spill reachability, and real Pi `tool_result` failure-patch behavior.
708
699
 
709
700
  Use lifecycle validation when testing `/reload`, exact-session relaunch, `/resume`, managed-session continuity, or persisted artifact behavior. Branch-backed state and `session_tree` cleanup ownership are covered by focused extension harness tests. Maintainers must run the lifecycle harness before every publish; see [Pre-release checks](docs/RELEASE.md#pre-release-checks).
710
701
 
@@ -1,25 +1,18 @@
1
- /**
2
- * Purpose: Register the native agent_browser tool for pi so agents can invoke agent-browser without going through bash.
3
- * Responsibilities: Define the tool schema, inject thin wrapper behavior around the upstream CLI, manage extension-owned browser session convenience, and return pi-friendly content/details.
4
- * Scope: Native tool registration and orchestration only; the wrapper intentionally stays close to the upstream agent-browser CLI.
5
- * Usage: Loaded by pi through the package manifest in this package, or explicitly via `pi --no-extensions -e .` during local checkout development.
6
- * Invariants/Assumptions: agent-browser is installed separately on PATH, the wrapper targets the current locally installed upstream version only, and no backward-compatibility shims are provided.
7
- */
8
1
  import { existsSync, readFileSync } from "node:fs";
9
2
  import { dirname, join } from "node:path";
10
3
  import { fileURLToPath } from "node:url";
11
4
  import { Text } from "@earendil-works/pi-tui";
12
5
  import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
13
6
  import { SessionPageState } from "./lib/session-page-state.js";
14
- import { canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
15
- import { extractExplicitNamespace, extractExplicitSessionName, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
7
+ import { canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
8
+ import { extractExplicitNamespace, extractExplicitSessionName, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
16
9
  import { cleanupManagedSessionRestoreConfig, ManagedSessionRestoreState } from "./lib/managed-session-restore.js";
17
10
  import { isRecord } from "./lib/parsing.js";
18
11
  import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
19
12
  import { isCloseCommand } from "./lib/command-taxonomy.js";
20
13
  import { hasLaunchScopedFlagToken } from "./lib/launch-scoped-flags.js";
21
14
  import { cleanupSecureTempArtifacts } from "./lib/temp.js";
22
- import { AGENT_BROWSER_PARAMS, } from "./lib/input-modes.js";
15
+ import { AGENT_BROWSER_PARAMS } from "./lib/input-modes/params.js";
23
16
  import { parseAllowedDomainsPolicyFromArgs } from "./lib/navigation-policy.js";
24
17
  import { closeManagedSession, getSessionContextKey, runAgentBrowserTool } from "./lib/orchestration/browser-run/index.js";
25
18
  import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./lib/orchestration/browser-run/session-state.js";
@@ -112,6 +105,57 @@ function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionN
112
105
  }
113
106
  return restored;
114
107
  }
108
+ function restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, sessionName, namespace) {
109
+ let restored = false;
110
+ const targetKey = getSessionContextKey(sessionName, namespace);
111
+ for (const entry of branch) {
112
+ if (!isRecord(entry) || entry.type !== "message")
113
+ continue;
114
+ const message = isRecord(entry.message) ? entry.message : undefined;
115
+ if (!message || message.toolName !== "agent_browser")
116
+ continue;
117
+ const details = isRecord(message.details) ? message.details : undefined;
118
+ if (!details)
119
+ continue;
120
+ if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
121
+ continue;
122
+ const outcome = getManagedSessionOutcome(details);
123
+ const activeAfterFailure = outcome?.activeAfter === true
124
+ && typeof outcome.currentSessionName === "string"
125
+ && getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
126
+ if ((getSuccessfulToolResult(details, message) || activeAfterFailure) && typeof details.managedSessionHeadedAutosaveDisabled === "boolean") {
127
+ restored = details.managedSessionHeadedAutosaveDisabled;
128
+ }
129
+ }
130
+ return restored;
131
+ }
132
+ function restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, sessionName, namespace) {
133
+ let restored;
134
+ const targetKey = getSessionContextKey(sessionName, namespace);
135
+ for (const entry of branch) {
136
+ if (!isRecord(entry) || entry.type !== "message")
137
+ continue;
138
+ const message = isRecord(entry.message) ? entry.message : undefined;
139
+ if (!message || message.toolName !== "agent_browser")
140
+ continue;
141
+ const details = isRecord(message.details) ? message.details : undefined;
142
+ if (!details)
143
+ continue;
144
+ if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
145
+ continue;
146
+ const outcome = getManagedSessionOutcome(details);
147
+ const activeAfterFailure = outcome?.activeAfter === true
148
+ && typeof outcome.currentSessionName === "string"
149
+ && getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
150
+ if (!getSuccessfulToolResult(details, message) && !activeAfterFailure)
151
+ continue;
152
+ if (typeof details.managedSessionHeadedAutosaveInterval === "string")
153
+ restored = details.managedSessionHeadedAutosaveInterval;
154
+ else if (details.managedSessionHeadedAutosaveDisabled === true)
155
+ restored = "0";
156
+ }
157
+ return restored;
158
+ }
115
159
  function getToolResultArgs(details) {
116
160
  if (Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string"))
117
161
  return details.args;
@@ -125,7 +169,7 @@ function isAttachedBrowserInvocation(args, env = process.env) {
125
169
  || hasLaunchScopedFlagToken(args, "--cdp")
126
170
  || hasLaunchScopedFlagToken(args, "--auto-connect")
127
171
  || env.AGENT_BROWSER_CDP !== undefined
128
- || (autoConnectEnv !== undefined && !["", "0", "false", "no"].includes(autoConnectEnv.toLowerCase()));
172
+ || isUpstreamEnvFlagEnabled(autoConnectEnv);
129
173
  }
130
174
  function restoreAttachedSessionKeysFromBranch(branch) {
131
175
  const attachedSessionKeys = new Set();
@@ -216,7 +260,9 @@ function trackOwnedManagedSession(sessions, sessionName, cwd, options = {}) {
216
260
  const key = getSessionContextKey(sessionName, options.namespace) ?? sessionName;
217
261
  const existing = sessions.get(key);
218
262
  const branchOwned = existing && !existing.branchOwned ? false : options.branchOwned === true;
219
- sessions.set(key, { branchOwned, cwd, namespace: options.namespace, sessionName });
263
+ const headedManagedAutosaveDisabled = options.headedManagedAutosaveDisabled ?? existing?.headedManagedAutosaveDisabled;
264
+ const headedManagedAutosaveInterval = options.headedManagedAutosaveInterval ?? existing?.headedManagedAutosaveInterval;
265
+ sessions.set(key, { branchOwned, cwd, headedManagedAutosaveDisabled, headedManagedAutosaveInterval, namespace: options.namespace, sessionName });
220
266
  }
221
267
  function untrackOwnedManagedSession(sessions, sessionName, namespace) {
222
268
  if (!sessionName)
@@ -247,7 +293,11 @@ function syncOwnedManagedSessionsFromResult(sessions, result, cwd) {
247
293
  const attemptedSessionName = typeof outcome.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
248
294
  if (outcome.activeAfter === true && (status === "created" || status === "replaced" || status === "unchanged")) {
249
295
  const namespace = isRecord(details) && typeof details.namespace === "string" ? details.namespace : undefined;
250
- trackOwnedManagedSession(sessions, currentSessionName, cwd, { namespace });
296
+ trackOwnedManagedSession(sessions, currentSessionName, cwd, {
297
+ headedManagedAutosaveDisabled: details?.managedSessionHeadedAutosaveDisabled === true,
298
+ headedManagedAutosaveInterval: typeof details?.managedSessionHeadedAutosaveInterval === "string" ? details.managedSessionHeadedAutosaveInterval : undefined,
299
+ namespace,
300
+ });
251
301
  }
252
302
  if (succeeded && status === "closed") {
253
303
  untrackOwnedManagedSession(sessions, attemptedSessionName ?? currentSessionName);
@@ -382,10 +432,18 @@ function setBranchRankForString(map, value, rank) {
382
432
  if (typeof value === "string" && value.length > 0)
383
433
  map.set(value, rank);
384
434
  }
435
+ function setBranchManagedSessionActive(events, sessionName, namespace, rank) {
436
+ if (typeof sessionName !== "string" || sessionName.length === 0)
437
+ return;
438
+ const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
439
+ events.managedSessionActiveIdentities.set(key, { namespace, sessionName });
440
+ events.managedSessionActiveRanks.set(key, rank);
441
+ }
385
442
  function collectBranchManagedResourceEvents(branch) {
386
443
  const events = {
387
444
  electronLaunchActiveRanks: new Map(),
388
445
  electronLaunchCleanupRanks: new Map(),
446
+ managedSessionActiveIdentities: new Map(),
389
447
  managedSessionActiveRanks: new Map(),
390
448
  managedSessionCloseRanks: new Map(),
391
449
  };
@@ -414,17 +472,17 @@ function collectBranchManagedResourceEvents(branch) {
414
472
  const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
415
473
  const outcomeAttemptedSessionName = typeof outcome?.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
416
474
  if (outcomeSucceeded && outcome.activeAfter === true && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged")) {
417
- setBranchRankForString(events.managedSessionActiveRanks, getSessionContextKey(outcomeCurrentSessionName, namespace), eventRank);
475
+ setBranchManagedSessionActive(events, outcomeCurrentSessionName, namespace, eventRank);
418
476
  }
419
477
  if (outcomeSucceeded && outcomeStatus === "closed") {
420
478
  setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(outcomeAttemptedSessionName ?? outcomeCurrentSessionName ?? sessionName, namespace), eventRank);
421
479
  }
422
- if (outcomeSucceeded && outcomeStatus === "replaced") {
480
+ if (outcome && outcomeStatus === "replaced" && outcome.replacedSessionClosed !== false) {
423
481
  const replacedSessionNamespace = typeof outcome.replacedSessionNamespace === "string" ? outcome.replacedSessionNamespace : namespace;
424
482
  setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(typeof outcome.replacedSessionName === "string" ? outcome.replacedSessionName : undefined, replacedSessionNamespace), eventRank);
425
483
  }
426
- if (succeeded && !isCloseCommand(command) && sessionName && (usedImplicitSession || sessionMode === "fresh")) {
427
- events.managedSessionActiveRanks.set(getSessionContextKey(sessionName, namespace) ?? sessionName, eventRank);
484
+ if (succeeded && !isCloseCommand(command) && sessionName && (usedImplicitSession || sessionMode === "fresh" || details.managedSessionHeadedAutosaveDisabled === true || typeof details.managedSessionHeadedAutosaveInterval === "string")) {
485
+ setBranchManagedSessionActive(events, sessionName, namespace, eventRank);
428
486
  }
429
487
  if (succeeded && isCloseCommand(command)) {
430
488
  setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName, namespace), eventRank);
@@ -475,7 +533,7 @@ async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessi
475
533
  for (const [key, owner] of [...sessions]) {
476
534
  if (key === keepKey)
477
535
  continue;
478
- const error = await closeManagedSession({ cwd: owner.cwd, namespace: owner.namespace, preserveAttachedBrowserSession: attachedSessionKeys.has(key), restoreState, sessionName: owner.sessionName, timeoutMs });
536
+ const error = await closeManagedSession({ cwd: owner.cwd, headedManagedAutosaveInterval: owner.headedManagedAutosaveInterval, namespace: owner.namespace, preserveAttachedBrowserSession: attachedSessionKeys.has(key), restoreState, sessionName: owner.sessionName, timeoutMs });
479
537
  if (!error)
480
538
  sessions.delete(key);
481
539
  }
@@ -614,6 +672,8 @@ export default function agentBrowserExtension(pi) {
614
672
  let managedSessionActive = false;
615
673
  let managedSessionBaseName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
616
674
  let managedSessionCompatibilityWorkaround;
675
+ let managedSessionHeadedAutosaveDisabled = false;
676
+ let managedSessionHeadedAutosaveInterval;
617
677
  let managedSessionName = managedSessionBaseName;
618
678
  let managedSessionCwd = process.cwd();
619
679
  let managedSessionNamespace;
@@ -680,6 +740,11 @@ export default function agentBrowserExtension(pi) {
680
740
  managedSessionCompatibilityWorkaround = managedSessionActive
681
741
  ? restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, managedSessionName, managedSessionNamespace)
682
742
  : undefined;
743
+ managedSessionHeadedAutosaveDisabled = managedSessionActive
744
+ && restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, managedSessionName, managedSessionNamespace);
745
+ managedSessionHeadedAutosaveInterval = managedSessionActive
746
+ ? restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, managedSessionName, managedSessionNamespace)
747
+ : undefined;
683
748
  managedSessionCwd = ctx.cwd;
684
749
  freshSessionOrdinal = nextFreshSessionOrdinal;
685
750
  sessionPageState = SessionPageState.fromBranch(branch);
@@ -704,8 +769,41 @@ export default function agentBrowserExtension(pi) {
704
769
  }
705
770
  removeInactiveOwnedElectronLaunchRecords(ownedElectronLaunchRecords, branchOwnedElectronLaunchIds, electronLaunchRecords, branchResourceEvents.electronLaunchActiveRanks, branchResourceEvents.electronLaunchCleanupRanks);
706
771
  }
772
+ for (const [sessionKey, identity] of branchResourceEvents.managedSessionActiveIdentities) {
773
+ const activeRank = branchResourceEvents.managedSessionActiveRanks.get(sessionKey);
774
+ const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
775
+ if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
776
+ continue;
777
+ if (!isRestorableManagedSessionName(identity.sessionName, managedSessionBaseName))
778
+ continue;
779
+ trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, ctx.cwd, {
780
+ branchOwned: true,
781
+ headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, identity.sessionName, identity.namespace),
782
+ headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, identity.sessionName, identity.namespace),
783
+ namespace: identity.namespace,
784
+ });
785
+ }
707
786
  if (restoredState.active) {
708
- trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, { branchOwned: true, namespace: restoredState.namespace });
787
+ trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, {
788
+ branchOwned: true,
789
+ headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
790
+ headedManagedAutosaveInterval: managedSessionHeadedAutosaveInterval,
791
+ namespace: restoredState.namespace,
792
+ });
793
+ }
794
+ for (const record of getActiveElectronRecords(electronLaunchRecords)) {
795
+ if (!record.sessionName || !isRestorableManagedSessionName(record.sessionName, managedSessionBaseName))
796
+ continue;
797
+ const sessionKey = getSessionContextKey(record.sessionName) ?? record.sessionName;
798
+ const activeRank = branchResourceEvents.managedSessionActiveRanks.get(sessionKey);
799
+ const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
800
+ if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
801
+ continue;
802
+ trackOwnedManagedSession(ownedManagedSessions, record.sessionName, ctx.cwd, {
803
+ branchOwned: true,
804
+ headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, record.sessionName),
805
+ headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, record.sessionName),
806
+ });
709
807
  }
710
808
  mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
711
809
  branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
@@ -768,6 +866,7 @@ export default function agentBrowserExtension(pi) {
768
866
  electronChildProcesses,
769
867
  electronLaunchRecords: electronRecordsToCleanup,
770
868
  managedSessionRestoreState,
869
+ ownedManagedSessions,
771
870
  timeoutMs: implicitSessionCloseTimeoutMs,
772
871
  });
773
872
  preservedElectronProfileDirs = [...new Set([
@@ -784,6 +883,8 @@ export default function agentBrowserExtension(pi) {
784
883
  });
785
884
  managedSessionActive = false;
786
885
  managedSessionCompatibilityWorkaround = undefined;
886
+ managedSessionHeadedAutosaveDisabled = false;
887
+ managedSessionHeadedAutosaveInterval = undefined;
787
888
  managedSessionNamespace = undefined;
788
889
  sessionPageState.reset();
789
890
  traceOwners = new Map();
@@ -890,6 +991,7 @@ export default function agentBrowserExtension(pi) {
890
991
  managedSessionName,
891
992
  managedSessionNamespace,
892
993
  managedSessionRestoreState,
994
+ ownedManagedSessions,
893
995
  redactedCompiledElectron,
894
996
  sessionPageState,
895
997
  signal,
@@ -917,6 +1019,8 @@ export default function agentBrowserExtension(pi) {
917
1019
  if (closedSessionName === managedSessionName) {
918
1020
  managedSessionActive = false;
919
1021
  managedSessionCompatibilityWorkaround = undefined;
1022
+ managedSessionHeadedAutosaveDisabled = false;
1023
+ managedSessionHeadedAutosaveInterval = undefined;
920
1024
  managedSessionNamespace = undefined;
921
1025
  freshSessionOrdinal += 1;
922
1026
  managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
@@ -959,6 +1063,8 @@ export default function agentBrowserExtension(pi) {
959
1063
  managedSessionActive,
960
1064
  managedSessionBaseName,
961
1065
  managedSessionCompatibilityWorkaround,
1066
+ managedSessionHeadedAutosaveDisabled,
1067
+ managedSessionHeadedAutosaveInterval,
962
1068
  managedSessionCwd,
963
1069
  managedSessionName,
964
1070
  managedSessionNamespace,
@@ -1037,6 +1143,8 @@ export default function agentBrowserExtension(pi) {
1037
1143
  freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
1038
1144
  managedSessionActive = browserRunState.managedSessionActive;
1039
1145
  managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
1146
+ managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
1147
+ managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
1040
1148
  managedSessionCwd = browserRunState.managedSessionCwd;
1041
1149
  managedSessionName = browserRunState.managedSessionName;
1042
1150
  managedSessionNamespace = browserRunState.managedSessionNamespace;
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Parse raw agent-browser argv once into a stable command descriptor for planners and policy.
3
- * Responsibilities: Own command-token extraction, command/subcommand identification, and descriptor construction.
4
- * Scope: Pure argv parsing; runtime planning and session policy consume descriptors instead of re-parsing tokens.
5
- */
6
1
  import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, optionalGlobalValueFlagConsumesNext } from "./argv-grammar.js";
7
2
  import { isOpenNavigationCommand } from "./command-taxonomy.js";
8
3
  function isBooleanLiteral(token) {
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Shared argv flag-shape metadata and helpers for command discovery and sessionless policy checks.
3
- * Responsibilities: Own global/command value-flag sets and boolean/value-flag validation used during argv parsing.
4
- * Scope: Pure token grammar; command semantics and subprocess execution live elsewhere.
5
- */
6
1
  import { isKnownCommandToken } from "./command-taxonomy.js";
7
2
  export const GLOBAL_VALUE_FLAGS = [
8
3
  "--session",
@@ -125,6 +120,10 @@ export function getBooleanFlagValue(args, flag) {
125
120
  export function isBooleanFlagEnabled(args, flag) {
126
121
  return getBooleanFlagValue(args, flag) ?? false;
127
122
  }
123
+ /** Match upstream env_var_is_truthy exactly: lowercase only, without trimming or accepting "off". */
124
+ export function isUpstreamEnvFlagEnabled(value) {
125
+ return value !== undefined && !["", "0", "false", "no"].includes(value.toLowerCase());
126
+ }
128
127
  /** Mirror upstream sanitize_session_component for namespace/socket/state identity. */
129
128
  export function canonicalizeAgentBrowserNamespace(value) {
130
129
  if (value === undefined)
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Own upstream command-shape policies that decide whether the wrapper should allocate a managed browser session.
3
- * Responsibilities: Keep local/sessionless command grammar out of the runtime execution planner while preserving exact upstream shapes.
4
- * Scope: Pure argv-token policy; command discovery, subprocess execution, and presentation live in focused modules.
5
- */
6
1
  import { hasOnlyBooleanFlags, hasOnlyOptionFlags, isNonFlagToken, stripSessionlessShapeGlobalFlags } from "./argv-grammar.js";
7
2
  const SESSIONLESS_AUTH_SUBCOMMANDS = new Set(["save", "list", "show", "delete", "remove"]);
8
3
  const PLUGIN_SESSIONLESS_SUBCOMMANDS = new Set(["list", "show", "add", "run"]);
@@ -1,9 +1,3 @@
1
- /**
2
- * Purpose: Centralize upstream agent-browser command capabilities that wrapper behavior depends on.
3
- * Responsibilities: Normalize command aliases once and expose capability predicates for runtime planning,
4
- * session/ref guards, result recommendations, and presentation summaries without coupling unrelated behaviors.
5
- * Scope: Static command capability taxonomy only; command-shape parsing, spawning, and formatting live elsewhere.
6
- */
7
1
  const ADDITIONAL_COMMAND_TOKENS = [
8
2
  "a11y", "auth", "chat", "clipboard", "confirm", "connect", "dashboard", "deny", "device", "dialog", "diff", "doctor", "errors", "eval", "find", "frame", "get", "highlight", "inspect", "install", "is", "mcp", "plugin", "plugins", "profiles", "profiler", "react", "record", "removeinitscript", "session", "set", "skills", "snapshot", "state", "stream", "trace", "upgrade", "vitals", "wait", "web-vitals", "window",
9
3
  ];
@@ -1,9 +1,4 @@
1
1
  // @ts-check
2
- /**
3
- * Purpose: Canonical pi-agent-browser-native config policy shared by runtime and setup CLI.
4
- * Responsibilities: Own config paths, provider descriptors, credential source parsing, layer validation/merge, status projection, and redacted summaries.
5
- * Scope: Pure configuration policy plus synchronous status loading; secret command execution and browser/web-search runtime calls live elsewhere.
6
- */
7
2
  import { existsSync, readFileSync } from "node:fs";
8
3
  import { homedir } from "node:os";
9
4
  import { join, resolve } from "node:path";
@@ -1,9 +1,3 @@
1
- /**
2
- * Purpose: Load pi-agent-browser-native package configuration from Pi-scoped global, project, or explicit paths.
3
- * Responsibilities: Resolve config layers, resolve secrets without exposing values, and provide redacted status for tools/CLIs.
4
- * Scope: Package-owned configuration only; canonical config policy lives in config-policy.js, browser command execution and web-search API calls live in focused modules.
5
- * Invariants/Assumptions: Credential sources from loaded config are passed through to the runtime; command credentials are resolved lazily at execution time and displayed values stay redacted.
6
- */
7
1
  import { exec as execCallback } from "node:child_process";
8
2
  import { readFile } from "node:fs/promises";
9
3
  import { promisify } from "node:util";
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Parse and fetch Chrome DevTools Protocol metadata for wrapper-owned Electron launches.
3
- * Responsibilities: Normalize CDP version/target JSON and perform bounded localhost CDP JSON fetches.
4
- * Scope: Tiny Electron CDP boundary helpers only; launch, status, cleanup, and target selection stay in their owning modules.
5
- * Usage: Imported by Electron launch and cleanup paths when polling `/json/version` and `/json/list`.
6
- * Invariants/Assumptions: Malformed or unavailable CDP endpoints return undefined/empty metadata rather than throwing, matching prior caller behavior.
7
- */
8
1
  import { isRecord } from "../parsing.js";
9
2
  const ELECTRON_CDP_FETCH_TIMEOUT_MS = 1_000;
10
3
  function asString(value) {
@@ -49,3 +42,9 @@ export async function fetchCdpJson(url, signal) {
49
42
  clearTimeout(timeout);
50
43
  }
51
44
  }
45
+ export function boundElectronProbeString(value, maxLength = 240) {
46
+ const trimmed = value?.trim();
47
+ if (!trimmed)
48
+ return undefined;
49
+ return trimmed.length > maxLength ? `${trimmed.slice(0, Math.max(0, maxLength - 3))}...` : trimmed;
50
+ }
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Inspect and clean wrapper-owned Electron launch resources.
3
- * Responsibilities: Report CDP liveness/targets without mutation and remove only tracked process/profile resources during explicit or shutdown cleanup.
4
- * Scope: Host-side Electron status and resource cleanup only; upstream managed-session close remains in the extension entrypoint.
5
- * Usage: Called by electron.status, electron.cleanup, and session_shutdown handling.
6
- * Invariants/Assumptions: Cleanup operates only on launch records produced by this wrapper and prefers partial cleanup reporting over killing or deleting untracked resources.
7
- */
8
1
  import { execFile } from "node:child_process";
9
2
  import { rm } from "node:fs/promises";
10
3
  import { promisify } from "node:util";
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Discover installed Electron desktop applications without invoking upstream agent-browser.
3
- * Responsibilities: Scan bounded macOS app bundles and Linux .desktop launchers, apply Electron framework evidence gates, and return small platform-tagged app metadata.
4
- * Scope: Discovery only; launch, cleanup, status, and CDP attachment live in later Electron lifecycle work items.
5
- * Usage: Called by the agent_browser top-level electron.list shorthand and directly by tests through parameterized scan locations.
6
- * Invariants/Assumptions: Discovery is best-effort, missing scan roots are ignored, malformed .desktop files are skipped, and results are capped before they reach model-visible output.
7
- */
8
1
  import { constants as fsConstants } from "node:fs";
9
2
  import { access, readdir, readFile, realpath, stat } from "node:fs/promises";
10
3
  import { homedir } from "node:os";
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Launch wrapper-owned Electron applications and discover their CDP endpoint.
3
- * Responsibilities: Resolve Electron targets, enforce caller-owned allow/deny policy, create isolated userDataDir profiles, launch with remote debugging on an OS-chosen port, poll DevToolsActivePort, and read bounded CDP version/target metadata.
4
- * Scope: Host-side Electron lifecycle setup only; upstream agent-browser attach/presentation stays in the extension entrypoint.
5
- * Usage: Called by the agent_browser electron.launch shorthand before routing through upstream `connect`.
6
- * Invariants/Assumptions: The wrapper only launches targets with Electron framework evidence, always uses an isolated temp profile, never accepts a caller-supplied remote debugging port, and cleans any spawned process/profile when cancellation interrupts readiness.
7
- */
8
1
  import { spawn } from "node:child_process";
9
2
  import { randomUUID } from "node:crypto";
10
3
  import { readFile, rm } from "node:fs/promises";
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Compile top-level Electron wrapper inputs into validated Electron actions.
3
- * Responsibilities: Enforce action-specific fields, launch-target rules, and wrapper-owned flag safety.
4
- * Scope: Electron input-mode validation only; launch/probe/cleanup execution stays in the extension entrypoint.
5
- */
6
1
  import { isRecord } from "../parsing.js";
7
2
  import { AGENT_BROWSER_ELECTRON_ACTIONS, AGENT_BROWSER_ELECTRON_HANDOFFS, AGENT_BROWSER_ELECTRON_LIST_FIELDS, AGENT_BROWSER_ELECTRON_PROBE_FIELDS, AGENT_BROWSER_ELECTRON_RESERVED_APP_ARGS, AGENT_BROWSER_ELECTRON_TARGET_TYPES, } from "./types.js";
8
3
  function validateOptionalNonEmptyString(input, fieldName) {
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Compile constrained job and lightweight QA wrapper inputs to upstream batch commands.
3
- * Responsibilities: Validate job/QA fields, produce argv/stdin, and summarize QA diagnostic results.
4
- * Scope: Job and QA modes only.
5
- */
6
1
  import { isRecord } from "../parsing.js";
7
2
  import { summarizeNetworkFailures } from "../results/network.js";
8
3
  import { getBatchResultItems, getCommandNameFromBatchItem, getSelectValues } from "./shared.js";
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Compile and analyze experimental DOM/source and network source lookup input modes.
3
- * Responsibilities: Build lookup batches, collect DOM/React/workspace candidates, and redact network lookup surfaces.
4
- * Scope: Source lookup and network source lookup only.
5
- */
6
1
  import { readFile, readdir } from "node:fs/promises";
7
2
  import { extname, join } from "node:path";
8
3
  import { isRecord } from "../parsing.js";
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Define the Pi tool input schema for the native agent_browser wrapper.
3
- * Responsibilities: Keep TypeBox schema construction separate from runtime execution and input-mode compilers.
4
- * Scope: Schema-only; behavioral validation lives in the mode compilers.
5
- */
6
1
  import { JsonSchema } from "../json-schema.js";
7
2
  import { StringEnum as localStringEnum } from "../string-enum-schema.js";
8
3
  import { ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS, ELECTRON_DISCOVERY_MAX_RESULTS, } from "../electron/discovery.js";
@@ -128,7 +123,7 @@ export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = l
128
123
  }, { additionalProperties: false }), { minItems: 1 }),
129
124
  }, { additionalProperties: false, description: "Constrained multi-step batch." })),
130
125
  stdin: Type.Optional(Type.String({ description: "Raw stdin for batch, eval --stdin, or auth save --password-stdin; unavailable with structured modes and electron." })),
131
- outputPath: Type.Optional(Type.String({ description: "Workspace-relative or absolute result path.", minLength: 1 })),
126
+ outputPath: Type.Optional(Type.String({ description: "Workspace-relative or absolute result-data path; keep it distinct from screenshot, download, recording, and other browser artifact destinations.", minLength: 1 })),
132
127
  timeoutMs: Type.Optional(Type.Integer({ description: "Wrapper timeout in ms; exceed explicit waits. Electron uses electron.timeoutMs.", minimum: 1 })),
133
128
  sessionMode: Type.Optional(StringEnum(["auto", "fresh"], {
134
129
  description: "auto reuses the managed session; fresh starts one for launch-only flags, then makes it the managed session.",
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Compile semanticAction shorthand inputs into upstream agent-browser commands.
3
- * Responsibilities: Validate shorthand locator/action fields and expose compiled-command helpers.
4
- * Scope: semanticAction mode only.
5
- */
6
1
  import { isRecord } from "../parsing.js";
7
2
  import { getSelectValues } from "./shared.js";
8
3
  import { AGENT_BROWSER_SEMANTIC_ACTIONS, AGENT_BROWSER_SEMANTIC_LOCATORS, } from "./types.js";