pi-agent-browser-native 0.2.45 → 0.2.47

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@
4
4
 
5
5
  No changes yet.
6
6
 
7
+ ## 0.2.47 - 2026-06-08
8
+
9
+ ### Changed
10
+
11
+ - Updated the local Pi development baseline and release guidance to Pi 0.79.0, including Project Trust-aware lifecycle/package/platform validation commands.
12
+ - Kept this extension risk-on by loading project-local package config by default while honoring explicit Pi `--no-approve` / `-na` opt-out runs.
13
+ - Updated the `pi-extension-development` skill guidance for Pi 0.79.0 and for explicit user consultation before behavioral changes.
14
+
15
+ ### Fixed
16
+
17
+ - Made platform smoke package install/list checks use Pi 0.79 approval flags so clean target-local package validation is deterministic.
18
+ - Ensured project-local package config can be explicitly skipped without losing global, override, or environment-backed web-search configuration.
19
+
20
+ ### Validation
21
+
22
+ - Ran docs, typecheck, unit/default verify, package smoke, lifecycle, deterministic dogfood, doctor, platform doctor, full release/Crabbox gates, and interactive tmux native-tool smoke before publish.
23
+
24
+ ## 0.2.46 - 2026-06-08
25
+
26
+ ### Changed
27
+
28
+ - Reduced native extension startup cost by replacing heavy top-level Pi and TypeBox runtime imports with lightweight local schema and event helpers while preserving the public tool schemas.
29
+ - Kept custom browser tool rendering compact without importing Pi's full coding-agent runtime during extension load.
30
+
31
+ ### Fixed
32
+
33
+ - Fixed issue #84 by cutting local cold extension import plus factory registration from roughly 1.1 seconds to roughly 76 milliseconds in checkout measurements.
34
+ - Stabilized timeout partial-progress diagnostics so post-navigation timeouts are recognized from later completed-step evidence even when live URL recovery is unavailable under load.
35
+
36
+ ### Validation
37
+
38
+ - Added a cold-start budget test for the real extension entrypoint, schema parity coverage against the canonical TypeBox/StringEnum builders, rendering coverage for JSON highlighting plus collapsed-output expand hints, and stabilized deterministic dogfood smoke by avoiding a rapid Windows browser close/relaunch race.
39
+
7
40
  ## 0.2.45 - 2026-06-06
8
41
 
9
42
  ### Added
package/README.md CHANGED
@@ -74,7 +74,7 @@ The result is optimized for agent work:
74
74
 
75
75
  ## Fastest way to try it
76
76
 
77
- Use Pi 0.78.1 or newer when possible. This package does not hard-pin Pi 0.78.1 as a runtime requirement, but the current release is audited and validated against that extension/package baseline.
77
+ Use Pi 0.79.0 or newer when possible. This package does not hard-pin Pi 0.79.0 as a runtime requirement, but the current release is audited and validated against that extension/package baseline, including Project Trust.
78
78
 
79
79
  Install upstream `agent-browser` first and make sure it is on `PATH`:
80
80
 
@@ -110,6 +110,8 @@ For a one-off trial that does not touch your configured Pi extensions:
110
110
  pi --no-extensions -e npm:pi-agent-browser-native
111
111
  ```
112
112
 
113
+ 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.
114
+
113
115
  For a specific published version:
114
116
 
115
117
  ```bash
@@ -217,7 +219,7 @@ printf '%s' "$EXA_API_KEY" | npm exec --yes --package pi-agent-browser-native@la
217
219
  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
218
220
  ```
219
221
 
220
- Config merges in this order: global → project → `PI_AGENT_BROWSER_CONFIG` override. `webSearch.enabled` is evaluated after that 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. Project-local plaintext, custom env aliases, interpolation-literal, malformed, and command-backed web-search keys are refused; project config may only use the matching provider env refs (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa and `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave). `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`. The tool content, details, status output, and docs examples must not expose resolved keys.
222
+ 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` by default because installed extensions are developer-trusted code; it skips that project layer only 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. Project-local plaintext, custom env aliases, interpolation-literal, malformed, and command-backed web-search keys are refused; project config may only use the matching provider env refs (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa and `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave). `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`. The tool content, details, status output, and docs examples must not expose resolved keys.
221
223
 
222
224
  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.
223
225
 
@@ -587,7 +589,7 @@ npm run smoke:platform:doctor
587
589
  npm run smoke:platform:all
588
590
  ```
589
591
 
590
- The required matrix is documented in [`docs/platform-smoke.md`](docs/platform-smoke.md). It runs `platform-build` (fast target-local verify, pack, clean packed Pi install, `pi list`) and `browser-dogfood-smoke` (real `agent-browser`/browser wrapper smoke) on every target. Inspect `.artifacts/platform-smoke/` and check `crabbox list --provider local-container` plus `crabbox list --provider parallels` after release runs so cleanup proof is not chat-only.
592
+ The required matrix is documented in [`docs/platform-smoke.md`](docs/platform-smoke.md). It runs `platform-build` (fast target-local verify, pack, clean packed Pi install with `--approve`, `pi list --approve`) and `browser-dogfood-smoke` (real `agent-browser`/browser wrapper smoke) on every target. Inspect `.artifacts/platform-smoke/` and check `crabbox list --provider local-container` plus `crabbox list --provider parallels` after release runs so cleanup proof is not chat-only.
591
593
 
592
594
  For package release confidence, follow [`docs/RELEASE.md`](docs/RELEASE.md). The release gate is:
593
595
 
@@ -639,10 +641,10 @@ Use the npm version declared in `package.json` `packageManager` when refreshing
639
641
  Quick isolated checkout smoke test:
640
642
 
641
643
  ```bash
642
- pi --no-extensions -e .
644
+ pi --approve --no-extensions -e .
643
645
  ```
644
646
 
645
- This bypasses Pi settings and configured extensions. After editing extension code, restart that Pi process to test the new checkout.
647
+ This bypasses Pi settings and configured extensions while explicitly trusting this checkout's project-local inputs for the run. Omit `--approve` when you want to exercise Pi's interactive Project Trust prompt instead. After editing extension code, restart that Pi process to test the new checkout.
646
648
 
647
649
  For a concrete expanded native-tool smoke matrix (version/help/skills through dashboard/chat families), see [Local development validation](docs/RELEASE.md#local-development-validation) in `docs/RELEASE.md`. For bounded release smokes that should validate this extension rather than skill routing, use the [Sauce Demo smoke prompt](docs/RELEASE.md#public-sauce-demo-checkout-smoke-prompt), which adds `--no-skills`. When changes affect dense dashboards, diagnostics, artifacts, recording, scroll, or combobox behavior, use the public [Grafana stress checklist](docs/RELEASE.md#public-grafana-stress-checklist) for repeatable release dogfood without bundling private skills or recipes.
648
650
 
@@ -652,7 +654,7 @@ Configured-source lifecycle validation:
652
654
  npm run verify -- lifecycle
653
655
  ```
654
656
 
655
- The harness defaults to Pi model `zai/glm-5.1` 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.78 with a deterministic `--session-id`, drives `/reload`, closes Pi, relaunches the exact same session, asserts the JSONL header id, and checks managed-session continuity, persisted spill reachability, and real Pi `tool_result` failure-patch behavior.
657
+ The harness defaults to Pi model `zai/glm-5.1` 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, persisted spill reachability, and real Pi `tool_result` failure-patch behavior.
656
658
 
657
659
  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).
658
660
 
@@ -68,9 +68,9 @@ Pi docs use `settings.json` for package/resource loading and filtering, not arbi
68
68
  - project-local: `.pi/config/pi-agent-browser-native/config.json`
69
69
  - explicit override: `PI_AGENT_BROWSER_CONFIG=/path/to/config.json`
70
70
 
71
- Config layers merge in that order: global, project, override. The shared policy module (`extensions/agent-browser/lib/config-policy.js`) owns provider descriptors, environment variable names, config keys, project-local credential safety, layer validation/merge, redacted status projection, and credential summaries for both runtime config loading and the package config helper. The config reader accepts v1 fields for `webSearch.enabled`, `webSearch.preferredProvider`, `webSearch.exaApiKey`, `webSearch.braveApiKey`, and conservative browser defaults such as `browser.defaultProfile` and `browser.executablePath`. Web-search key fields follow Pi model/provider-style value resolution for trusted global/override config: literal values, `$ENV_VAR` / `${ENV_VAR}` interpolation, escapes (`$$`, `$!`), and leading `!command` resolved at request time. Project-local plaintext, interpolation-literal, malformed, and command-backed web-search keys are rejected because project config can be copied, committed, or supplied by a repository; project config may use only the matching provider env ref (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa, `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave), so a repository cannot redirect search credentials to arbitrary host env vars. `EXA_API_KEY` and `BRAVE_API_KEY` remain environment fallbacks when no config credential source exists for that provider. Browser default values keep their source scope; profile/executable prompt guidance is emitted only from trusted global or explicit override config, not from project-local config that could steer host profile or executable choices.
71
+ Config layers merge in that order: global, project, override. The shared policy module (`extensions/agent-browser/lib/config-policy.js`) owns provider descriptors, environment variable names, config keys, project-local credential safety, developer-trusted project layer inclusion, layer validation/merge, redacted status projection, and credential summaries for both runtime config loading and the package config helper. Under Pi 0.79+, globally installed or CLI-loaded extensions are developer-trusted code, so this extension reads `.pi/config/pi-agent-browser-native/config.json` by default and skips that project layer only when Pi is launched with `--no-approve`. Global config and explicit `PI_AGENT_BROWSER_CONFIG` overrides remain available either way. The config reader accepts v1 fields for `webSearch.enabled`, `webSearch.preferredProvider`, `webSearch.exaApiKey`, `webSearch.braveApiKey`, and conservative browser defaults such as `browser.defaultProfile` and `browser.executablePath`. Web-search key fields follow Pi model/provider-style value resolution for trusted global/override config: literal values, `$ENV_VAR` / `${ENV_VAR}` interpolation, escapes (`$$`, `$!`), and leading `!command` resolved at request time. Project-local plaintext, interpolation-literal, malformed, and command-backed web-search keys are rejected because project config can be copied, committed, or supplied by a repository; project config may use only the matching provider env ref (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa, `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave), so a repository cannot redirect search credentials to arbitrary host env vars. `EXA_API_KEY` and `BRAVE_API_KEY` remain environment fallbacks when no config credential source exists for that provider. Browser default values keep their source scope; profile/executable prompt guidance is emitted only from trusted global or explicit override config, not from project-local config that could steer host profile or executable choices.
72
72
 
73
- `agent_browser_web_search` registration is conditional. `webSearch.enabled: false` disables registration even when environment keys are present, but it is evaluated after config merge. A global disable is the normal user default and can still be overridden by project config or `PI_AGENT_BROWSER_CONFIG`; a project disable applies to one repo; an explicit `PI_AGENT_BROWSER_CONFIG` file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Literal and env-backed sources must resolve at startup; command-backed sources are considered configured without running the command until tool execution, so secret managers do not slow startup or prompt unexpectedly. The tool resolves the selected key lazily, chooses Exa or Brave from available credentials (preferring Exa by default unless `webSearch.preferredProvider` says otherwise), then follows one provider-agnostic execution path through provider adapters for request building, HTTP JSON fetch, response normalization, and provider-specific detail fields. It calls Exa `/search` with highlights or Brave Search and returns compact result details without exposing keys.
73
+ `agent_browser_web_search` registration is conditional. `webSearch.enabled: false` disables registration even when environment keys are present, but it is evaluated after the available config layers merge. A global disable is the normal user default and can still be overridden by project config or `PI_AGENT_BROWSER_CONFIG`; a project disable applies to one repo; an explicit `PI_AGENT_BROWSER_CONFIG` file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Literal and env-backed sources must resolve at startup; command-backed sources are considered configured without running the command until tool execution, so secret managers do not slow startup or prompt unexpectedly. The tool resolves the selected key lazily, chooses Exa or Brave from available credentials (preferring Exa by default unless `webSearch.preferredProvider` says otherwise), then follows one provider-agnostic execution path through provider adapters for request building, HTTP JSON fetch, response normalization, and provider-specific detail fields. It calls Exa `/search` with highlights or Brave Search and returns compact result details without exposing keys.
74
74
 
75
75
  Browser default config is intentionally conservative. It can add prompt guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables, but current releases do not auto-inject `--profile` or `--executable-path` into every launch. Project-local browser config is status-only for launch guidance unless the profile policy is `explicit-only`; executable guidance must come from global or explicit override config. Automatic launch-default mutation would affect privacy, browser state, and host executable choice, so it needs a separate explicit design and test pass.
76
76
 
@@ -98,8 +98,8 @@ The published package should load from the `pi` manifest in `package.json`.
98
98
 
99
99
  Local checkout validation has two intentional modes:
100
100
 
101
- - **Quick isolated mode:** use explicit CLI loading such as `pi --no-extensions -e .` from the repository root. This bypasses Pi settings and extension discovery, avoids duplicate `agent_browser` registrations when another source is installed globally, and is the right mode for checkout smoke tests.
102
- - **Configured-source lifecycle mode:** configure exactly one active checkout or package source in Pi settings and launch plain `pi`. This is the right mode for validating `/reload` and exact-session relaunch because those lifecycle checks exercise discovered/configured resources. Focused extension harness tests validate branch-backed `session_tree` rehydration and cleanup ownership. Before shipping, maintainers also run `npm run verify -- lifecycle` (same semantics under automation, using Pi 0.78 `--session-id` to reopen the exact JSONL session) plus the live-site checks in [`RELEASE.md`](RELEASE.md#pre-release-checks); `npm publish` enforces `npm run verify -- release` via `prepublishOnly` unless scripts are skipped.
101
+ - **Quick isolated mode:** use explicit CLI loading such as `pi --approve --no-extensions -e .` from the repository root when this checkout is intentionally trusted. This bypasses Pi settings and extension discovery, avoids duplicate `agent_browser` registrations when another source is installed globally, and is the right mode for checkout smoke tests; omit `--approve` only when deliberately testing Pi's Project Trust prompt.
102
+ - **Configured-source lifecycle mode:** configure exactly one active checkout or package source in Pi settings and launch plain `pi` for manual validation, or run the automated harness that launches with `--approve`. This is the right mode for validating `/reload` and exact-session relaunch because those lifecycle checks exercise discovered/configured resources. Focused extension harness tests validate branch-backed `session_tree` rehydration and cleanup ownership. Before shipping, maintainers also run `npm run verify -- lifecycle` (same semantics under automation, using Pi 0.79 `--approve --session-id` to reopen the exact JSONL session) plus the live-site checks in [`RELEASE.md`](RELEASE.md#pre-release-checks); `npm publish` enforces `npm run verify -- release` via `prepublishOnly` unless scripts are skipped.
103
103
 
104
104
  The repo should not add a repo-local `.pi/extensions/` autoload shim as the documented checkout path.
105
105
 
@@ -137,7 +137,7 @@ V1 ownership rule:
137
137
  - extension-managed sessions should be reusable during an active `pi` session and across `/reload`, exact-session relaunch, `/resume`, and Pi branch-tree transitions, while still being cleaned up predictably
138
138
 
139
139
  Practical policy:
140
- - preserve the current branch-visible extension-managed session across `/reload`, exact-session relaunch, `/resume`, and Pi 0.78 `session_tree` branch transitions so persisted sessions can keep following the live browser after lifecycle changes
140
+ - preserve the current branch-visible extension-managed session across `/reload`, exact-session relaunch, `/resume`, and Pi 0.79 `session_tree` branch transitions so persisted sessions can keep following the live browser after lifecycle changes
141
141
  - close the active extension-managed session when the originating `pi` process quits, while leaving explicit caller-provided sessions alone
142
142
  - set an idle timeout on extension-managed sessions as a backstop for abnormal exits or cleanup failures
143
143
  - clean up process-private temp spill artifacts on shutdown, but keep persisted-session snapshot 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
@@ -701,7 +701,7 @@ When these commands are invoked through the native `agent_browser` tool, structu
701
701
  - project-local: `.pi/config/pi-agent-browser-native/config.json`
702
702
  - explicit override: `PI_AGENT_BROWSER_CONFIG=/path/to/config.json`
703
703
 
704
- Get an Exa API key from the [Exa dashboard](https://dashboard.exa.ai/api-keys) or a Brave Search API key from the [Brave Search API dashboard](https://api-dashboard.search.brave.com/). If both keys are available, `agent_browser_web_search` prefers Exa by default because its `/search` endpoint returns token-efficient highlights and agent-oriented search modes; set `webSearch.preferredProvider` to `"brave"` when Brave Search is preferred. You can also disable this package's search tool with `webSearch.enabled: false` when another search tool should win. Config merges global → project → `PI_AGENT_BROWSER_CONFIG` override, so `enabled` is read from the final merged config: a global disable can be re-enabled by project or override config, while an override file with `enabled: false` is the highest-priority hard disable for that run.
704
+ Get an Exa API key from the [Exa dashboard](https://dashboard.exa.ai/api-keys) or a Brave Search API key from the [Brave Search API dashboard](https://api-dashboard.search.brave.com/). If both keys are available, `agent_browser_web_search` prefers Exa by default because its `/search` endpoint returns token-efficient highlights and agent-oriented search modes; set `webSearch.preferredProvider` to `"brave"` when Brave Search is preferred. You can also disable this package's search tool with `webSearch.enabled: false` when another search tool should win. Config merges global → project → `PI_AGENT_BROWSER_CONFIG` override, so `enabled` is read from the final loaded config: a global disable can be re-enabled by project or override config, while an override file with `enabled: false` is the highest-priority hard disable for that run. Under Pi 0.79+, globally installed or CLI-loaded extensions are developer-trusted code, so this extension reads project-local config under `.pi/config/...` by default and skips that project layer only when Pi is launched with `--no-approve`.
705
705
 
706
706
  `pi install npm:pi-agent-browser-native` loads the extension, but it does **not** usually put the package helper on your shell `PATH`. The clearest setup is to write the config file directly and keep actual keys in the environment that launches `pi`:
707
707
 
@@ -737,7 +737,7 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
737
737
  npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable set "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
738
738
  ```
739
739
 
740
- The optional `agent_browser_web_search` tool is registered only when Exa or Brave credentials are available and the final merged config has not set `webSearch.enabled` to `false`. It is a separate custom tool, not an `agent_browser` input mode, and does not launch a browser. Use it when current/live external web information would help; use `agent_browser` for browser interaction, screenshots, authenticated/profile pages, and DOM inspection. Disable scope is explicit: `web-search disable --global` sets the normal user default, `web-search disable --project` disables it for one repo, and a `PI_AGENT_BROWSER_CONFIG` override containing `{ "version": 1, "webSearch": { "enabled": false } }` wins over both for a hard per-run disable. Project-local plaintext, custom env aliases, interpolation-literal, malformed, and command-backed web-search keys are refused; project config may only use the matching provider env refs (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa and `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave). `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`. For Exa, the tool defaults to `searchType: "auto"` with `contents.highlights: true`; use `fast`, `instant`, `deep-lite`, `deep`, or `deep-reasoning` only when the task needs that latency/depth tradeoff.
740
+ The optional `agent_browser_web_search` tool is registered only when Exa or Brave credentials are available and the final available config has not set `webSearch.enabled` to `false`. It is a separate custom tool, not an `agent_browser` input mode, and does not launch a browser. Use it when current/live external web information would help; use `agent_browser` for browser interaction, screenshots, authenticated/profile pages, and DOM inspection. Disable scope is explicit: `web-search disable --global` sets the normal user default, `web-search disable --project` disables it for one repo, and a `PI_AGENT_BROWSER_CONFIG` override containing `{ "version": 1, "webSearch": { "enabled": false } }` wins over both for a hard per-run disable. Project-local plaintext, custom env aliases, interpolation-literal, malformed, and command-backed web-search keys are refused; project config may only use the matching provider env refs (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa and `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave). `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`. For Exa, the tool defaults to `searchType: "auto"` with `contents.highlights: true`; use `fast`, `instant`, `deep-lite`, `deep`, or `deep-reasoning` only when the task needs that latency/depth tradeoff.
741
741
 
742
742
  Example config:
743
743
 
@@ -760,7 +760,7 @@ Example config:
760
760
  }
761
761
  ```
762
762
 
763
- Browser default config is conservative: it adds agent guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables; current releases do not auto-inject `--profile` or `--executable-path` for every launch. Configure profile/executable guidance globally or through `PI_AGENT_BROWSER_CONFIG`; project-local browser config is not trusted to steer host executable/profile prompt guidance. Ask the agent to run `profiles` and `doctor` when profile resolution fails, then use the reported Chrome profile directory name, a full profile/user-data directory path if upstream accepts one, or the configured `browser.executablePath` with top-level `sessionMode: "fresh"`.
763
+ Browser default config is conservative: it adds agent guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables; current releases do not auto-inject `--profile` or `--executable-path` for every launch. Configure profile/executable guidance globally or through `PI_AGENT_BROWSER_CONFIG`; project-local browser config is loaded by default but is never trusted to steer host executable/profile prompt guidance. Ask the agent to run `profiles` and `doctor` when profile resolution fails, then use the reported Chrome profile directory name, a full profile/user-data directory path if upstream accepts one, or the configured `browser.executablePath` with top-level `sessionMode: "fresh"`.
764
764
 
765
765
  ## Important global flags, config, and environment
766
766
 
package/docs/RELEASE.md CHANGED
@@ -64,11 +64,11 @@ The Crabbox gate is only green when suite assertions and artifact manifests unde
64
64
 
65
65
  The deterministic dogfood mode uses the extension harness and the real `agent-browser` on `PATH` against a deterministic local file fixture, then verifies top-level `qa`, `semanticAction`, constrained `job`, screenshot artifact verification, and session close. Use `npm run verify -- dogfood --keep-artifacts` or `--artifact-dir <path>` only while debugging, then delete retained screenshots. This smoke complements, but does not replace, human-readable interactive transcript evidence.
66
66
 
67
- Every release also requires interactive `tmux`-driven Pi dogfood with the native `agent_browser` tool against real sites. For extension-focused release smokes, use `pi --no-extensions --no-skills -e .` from the checkout before publish so auto-loaded dogfood/QA skills cannot replace the bounded smoke workflow; run separate skill-enabled dogfood only when validating skill routing or report-generation behavior. Drive prompts with `tmux send-keys`, exercise at least one simple static site and one real documentation/product site, include the higher-level `qa` or `job`/`batch` surfaces when they changed, close every opened browser session, remove screenshots/temp artifacts, and record the outcome in the release notes or support-matrix evidence. Automated localhost, fake-upstream, and deterministic dogfood gates do not replace this human-readable live-site transcript evidence. When `agent_browser_web_search` or package config changed, add one key-free smoke proving the optional tool is absent without config, one fake/unit-backed smoke in the default suite, and one opt-in live Exa or Brave Search check with a real key while confirming the key does not appear in transcripts, stdout/stderr, config status, PR text, or artifacts. When `electron.*` surfaces, attached-session diagnostics, or `qa.attached` changed, add a local Electron pass: `electron.list` → `electron.launch` (expect isolated profile behavior) → `snapshot -i` or `electron.probe` / `qa.attached` → `electron.cleanup` with the returned `launchId`, verifying status/mismatch guidance if you simulate a dead renderer or stale refs. For dense-dashboard stress coverage, use the [public Grafana stress checklist](#public-grafana-stress-checklist) below; it is a maintainer workflow, not bundled product skill or recipe runtime.
67
+ Every release also requires interactive `tmux`-driven Pi dogfood with the native `agent_browser` tool against real sites. For extension-focused release smokes, use `pi --approve --no-extensions --no-skills -e .` from the trusted checkout before publish so auto-loaded dogfood/QA skills cannot replace the bounded smoke workflow; omit `--approve` only when the smoke is explicitly testing Pi's Project Trust prompt. Run separate skill-enabled dogfood only when validating skill routing or report-generation behavior. Drive prompts with `tmux send-keys`, exercise at least one simple static site and one real documentation/product site, include the higher-level `qa` or `job`/`batch` surfaces when they changed, close every opened browser session, remove screenshots/temp artifacts, and record the outcome in the release notes or support-matrix evidence. Automated localhost, fake-upstream, and deterministic dogfood gates do not replace this human-readable live-site transcript evidence. When `agent_browser_web_search` or package config changed, add one key-free smoke proving the optional tool is absent without config, one fake/unit-backed smoke in the default suite, and one opt-in live Exa or Brave Search check with a real key while confirming the key does not appear in transcripts, stdout/stderr, config status, PR text, or artifacts. When `electron.*` surfaces, attached-session diagnostics, or `qa.attached` changed, add a local Electron pass: `electron.list` → `electron.launch` (expect isolated profile behavior) → `snapshot -i` or `electron.probe` / `qa.attached` → `electron.cleanup` with the returned `launchId`, verifying status/mismatch guidance if you simulate a dead renderer or stale refs. For dense-dashboard stress coverage, use the [public Grafana stress checklist](#public-grafana-stress-checklist) below; it is a maintainer workflow, not bundled product skill or recipe runtime.
68
68
 
69
69
  When reviewing saved session JSONL after a failed smoke or a `qa` preset that reclassified an upstream-successful batch, expect `agent_browser` tool rows to carry `isError: true` whenever `details.resultCategory` is `failure`. For normal prose output, model-visible text should end with a `Pi tool isError: true` category line; for caller-requested `--json` output, the hook preserves parseable JSON and only patches `isError`. The extension applies that patch on the `tool_result` path so Pi’s transcript matches the wrapper contract ([`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details)). Preserve a normal Pi session directory for those checks; avoiding `--no-session` keeps this evidence intact ([`AGENTS.md`](../AGENTS.md) preferred validation workflow).
70
70
 
71
- The configured-source lifecycle regression harness is required before release because it launches an interactive `pi` process under `tmux` and validates `/reload`, full relaunch with the same exact Pi 0.78 `--session-id`, managed-session continuity, persisted artifacts, and Pi failure-patch behavior. Branch-backed `session_tree` rehydration and cleanup ownership are validated by focused extension harness tests:
71
+ The configured-source lifecycle regression harness is required before release because it launches an interactive `pi` process under `tmux` with `--approve` and validates `/reload`, full relaunch with the same exact Pi 0.79 `--session-id`, managed-session continuity, persisted artifacts, and Pi failure-patch behavior. Branch-backed `session_tree` rehydration and cleanup ownership are validated by focused extension harness tests:
72
72
 
73
73
  ```bash
74
74
  npm run verify -- lifecycle
@@ -106,7 +106,7 @@ Use this validation prompt after changing click enrichment, tab pinning, ref pre
106
106
  Run it in an isolated checkout session with skills disabled so the run validates the extension browser workflow instead of external dogfood/QA skill routing. It is fine to restrict active tools at launch so the checkout extension is the only browser surface, but keep those launch details out of the user prompt:
107
107
 
108
108
  ```bash
109
- pi --no-extensions --no-skills -e . --model openai-codex/gpt-5.5:minimal --tools agent_browser --session-dir "$SESSION_DIR"
109
+ pi --approve --no-extensions --no-skills -e . --model openai-codex/gpt-5.5:minimal --tools agent_browser --session-dir "$SESSION_DIR"
110
110
  ```
111
111
 
112
112
  Repeat with `--model openai-codex/gpt-5.5:medium` when validating instruction-following robustness. Use unique temp paths for each run and delete them afterward. Run separate skill-enabled dogfood sessions only when the thing under test is skill integration, not this bounded release smoke.
@@ -193,7 +193,7 @@ Before publishing, validate both local-checkout modes without mixing their assum
193
193
  ### Quick isolated checkout smoke test
194
194
 
195
195
  1. Install `agent-browser` separately.
196
- 2. Launch `pi --no-extensions -e .` from this repository root.
196
+ 2. Launch `pi --approve --no-extensions -e .` from this trusted repository root. Omit `--approve` only when testing Pi's Project Trust prompt.
197
197
  3. Confirm the checkout extension loads from `extensions/agent-browser/index.ts`.
198
198
  4. Run a smoke prompt that exercises `agent_browser`.
199
199
  5. Restart the `pi` process after extension edits; Pi settings and `/reload` are not the validation target in this isolated mode.
@@ -212,7 +212,7 @@ Run the automated harness for deterministic configured-source lifecycle regressi
212
212
  npm run verify -- lifecycle
213
213
  ```
214
214
 
215
- The harness creates an isolated `PI_CODING_AGENT_DIR`, writes settings with exactly one temporary configured package source, runs `pi` in `tmux` with default model **`zai/glm-5.1`** and a deterministic `--session-id`, puts a deterministic fake `agent-browser` first on `PATH`, drives `/reload`, closes Pi, and relaunches with the same exact session id instead of typing `/resume`. It also asserts the JSONL session header id, same-page managed-session continuity, persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification. Per-step tmux waits default to **180000 ms** (three minutes) in [`scripts/verify-lifecycle.mjs`](../scripts/verify-lifecycle.mjs) (`DEFAULT_TIMEOUT_MS`); override with `--timeout-ms <ms>` when slower models or cold starts need more headroom. Override the model when needed:
215
+ The harness creates an isolated `PI_CODING_AGENT_DIR`, writes settings with exactly one temporary configured package source, runs `pi` in `tmux` with `--approve`, default model **`zai/glm-5.1`**, and a deterministic `--session-id`, puts a deterministic fake `agent-browser` first on `PATH`, drives `/reload`, closes Pi, and relaunches with the same exact session id instead of typing `/resume`. It also asserts the JSONL session header id, same-page managed-session continuity, persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification. Per-step tmux waits default to **180000 ms** (three minutes) in [`scripts/verify-lifecycle.mjs`](../scripts/verify-lifecycle.mjs) (`DEFAULT_TIMEOUT_MS`); override with `--timeout-ms <ms>` when slower models or cold starts need more headroom. Override the model when needed:
216
216
 
217
217
  ```bash
218
218
  npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal
@@ -327,8 +327,8 @@ Before publishing:
327
327
  - run `npm run verify -- command-reference` if the installed upstream `agent-browser` version or help surface changed
328
328
  - run `npm run doctor` and confirm any duplicate-source remediation matches the active package/checkout setup
329
329
  - run `npm run verify -- real-upstream` for upstream runtime, result-presentation, or managed-session changes
330
- - confirm both local-checkout modes still work for pre-release validation: isolated `pi --no-extensions -e .` smoke testing for general checkout loading (add `--no-skills` for extension-focused bounded smokes) and configured-source lifecycle validation
331
- - complete interactive `tmux` live-site extension smoke with `pi --no-extensions --no-skills -e .` and the native `agent_browser` tool (at least one simple static site and one real documentation/product site; include `qa` or `job`/`batch` when those surfaces changed; use the [public Grafana stress checklist](#public-grafana-stress-checklist) when dashboard/diagnostic/artifact behavior changed; close sessions and remove screenshots/temp artifacts; record evidence). Run separate skill-enabled dogfood only when validating skill routing/report-generation behavior—see [Pre-release checks](#pre-release-checks); automated gates are not a substitute
330
+ - confirm both local-checkout modes still work for pre-release validation: isolated `pi --approve --no-extensions -e .` smoke testing for general trusted checkout loading (add `--no-skills` for extension-focused bounded smokes; omit `--approve` only to test the trust prompt) and configured-source lifecycle validation
331
+ - complete interactive `tmux` live-site extension smoke with `pi --approve --no-extensions --no-skills -e .` and the native `agent_browser` tool (at least one simple static site and one real documentation/product site; include `qa` or `job`/`batch` when those surfaces changed; use the [public Grafana stress checklist](#public-grafana-stress-checklist) when dashboard/diagnostic/artifact behavior changed; close sessions and remove screenshots/temp artifacts; record evidence). Run separate skill-enabled dogfood only when validating skill routing/report-generation behavior—see [Pre-release checks](#pre-release-checks); automated gates are not a substitute
332
332
  - rerun `npm run verify -- release` and confirm the embedded Crabbox `platform-build` plus `browser-dogfood-smoke` matrix passed on `macos`, `ubuntu`, and `windows-native` with artifacts under `.artifacts/platform-smoke/`
333
333
  - run `npm run verify -- lifecycle` for configured-source `/reload`, exact `--session-id` relaunch, managed-session continuity, persisted-spill, and Pi failure-patch regression coverage (required before publish; see [Pre-release checks](#pre-release-checks))
334
334
  - confirm [`SUPPORT_MATRIX.md`](SUPPORT_MATRIX.md) still maps every current baseline inventory section to docs, runtime handling, tests, and validation status
@@ -47,12 +47,12 @@ Define the product requirements and constraints for `pi-agent-browser-native`.
47
47
  ### Install priority
48
48
 
49
49
  - Prioritize the package install path first.
50
- - User-facing install docs should lead with `pi install npm:pi-agent-browser-native`; ephemeral package trials and validation must use `pi --no-extensions -e npm:pi-agent-browser-native[@<version>]` so configured checkout or global sources cannot duplicate `agent_browser`.
50
+ - User-facing install docs should lead with `pi install npm:pi-agent-browser-native`; ephemeral package trials and validation should use `pi --no-extensions -e npm:pi-agent-browser-native[@<version>]` so configured checkout or global sources cannot duplicate `agent_browser`, adding `--approve` in Pi 0.79+ automation when the current project is intentionally trusted.
51
51
  - User-facing install docs should also include the GitHub source path `pi install https://github.com/fitchmultz/pi-agent-browser-native`.
52
52
  - Provide a read-only package-level doctor command that checks upstream `agent-browser` PATH/version and duplicate Pi package/checkout sources before first use. It must not mutate Pi settings and must remain distinct from upstream `agent-browser doctor`.
53
53
  - Keep the current local-checkout path documented as the practical pre-release and development flow.
54
54
  - Most users will install this extension globally rather than as a project-local extension.
55
- - Local checkout smoke testing should use explicit CLI loading such as `pi --no-extensions -e .` or `pi --no-extensions -e /absolute/path/to/pi-agent-browser-native`; Pi settings are bypassed in this mode and code edits require a process restart for validation.
55
+ - Local trusted-checkout smoke testing should use explicit CLI loading such as `pi --approve --no-extensions -e .` or `pi --approve --no-extensions -e /absolute/path/to/pi-agent-browser-native`; Pi settings are bypassed in this mode and code edits require a process restart for validation. Omit `--approve` only when the test is meant to cover Pi's Project Trust prompt.
56
56
  - Local checkout hot-reload and exact-session relaunch validation should use configured-source lifecycle mode: exactly one active checkout/package source in Pi settings, launched with plain `pi` (or the lifecycle harness' exact `--session-id` relaunch path), so `/reload` and relaunch events exercise discovered/configured resources. Focused extension harness tests validate Pi `session_tree` branch rehydration and cleanup ownership.
57
57
  - Do **not** rely on repo-local `.pi/extensions/` auto-discovery for this package, because it conflicts with the global installed-package path.
58
58
 
@@ -86,7 +86,7 @@ Define the product requirements and constraints for `pi-agent-browser-native`.
86
86
  ### Testing guidance
87
87
 
88
88
  - The primary confidence path is a real `pi` session driven in `tmux`.
89
- - For quick local checkout smoke validation, launch `pi --no-extensions -e .` from the repository root so only the checkout copy loads; do not rely on Pi settings or `/reload` semantics in this isolated mode.
89
+ - For quick local checkout smoke validation, launch `pi --approve --no-extensions -e .` from the repository root so only the checkout copy loads; do not rely on Pi settings or `/reload` semantics in this isolated mode.
90
90
  - For hot-reload validation, configure exactly one active source for this extension in Pi settings and launch plain `pi`; validate `/reload` there because it exercises auto-discovered/configured resources.
91
91
  - Maintain a tmux-driven configured-source lifecycle harness (`npm run verify -- lifecycle`; required before release per `docs/RELEASE.md`) that isolates Pi settings, uses exactly one configured source, exercises `/reload`, full restart plus exact `--session-id` relaunch, and asserts managed-session continuity, persisted artifact survival, and real Pi `tool_result` failure-patch semantics. It remains outside the default `npm run verify` sequence, but it is embedded in `npm run verify -- release` so `prepublishOnly` enforces it before publish unless scripts are intentionally skipped. The harness defaults Pi to model `zai/glm-5.1` (`scripts/verify-lifecycle.mjs`); pass `--model <id>` after `lifecycle` when a different model is required. Keep `docs/RELEASE.md` accurate about the harness behavior, cleanup, transcript retention, and limitations.
92
92
  - Validate a full `pi` restart with exact `--session-id` relaunch or `/resume` when changes touch managed-session continuity, reload behavior, or persisted artifact paths. Validate branch-backed state changes with the focused `session_tree` harness tests.
@@ -71,7 +71,7 @@ Re-run the gates below before each release; this table records what the closure
71
71
  | Crabbox platform smoke | `npm run check:platform-smoke` syntax-checks the harness and cheap invariants. `npm run smoke:platform:ubuntu-image` builds the project-owned Linux image, `npm run smoke:platform:doctor` checks Crabbox 0.26.0+ and local target readiness, and `npm run smoke:platform:all` runs doctor first, then fast target-local `platform-build` (`npm run verify -- platform-target`, pack, clean Pi install) plus `browser-dogfood-smoke` on Crabbox `macos`, `ubuntu`, and `windows-native`; see [`platform-smoke.md`](platform-smoke.md). Target artifacts include Crabbox/provider/work-root metadata, and release review also checks provider-specific `crabbox list` commands for leftover leases/clones. | Pass on 2026-06-03 (`npm run check:platform-smoke`, `npm run smoke:platform:ubuntu-image`, and `npm run verify -- release`, whose platform slice ran the macOS/Ubuntu/native-Windows Crabbox matrix; artifacts cleaned after evidence capture). |
72
72
  | `verify -- release` / `prepublishOnly` | `npm run verify -- release` chains the default gate with the configured-source lifecycle harness, packaged Pi smoke, and the release-blocking Crabbox platform matrix (`verifySteps` `release` in [`scripts/project.mjs`](../scripts/project.mjs)). `package.json` `prepublishOnly` runs that compose before `npm pack --dry-run` during `npm publish`. It intentionally omits standalone real-upstream, host-only dogfood, and benchmark modes—see [`RELEASE.md`](RELEASE.md#pre-release-checks). | Pass on 2026-06-03 (`npm run verify -- release`, including macOS/Ubuntu/native-Windows Crabbox matrix). |
73
73
  | Configured-source lifecycle | `npm run verify -- lifecycle` (`scripts/verify-lifecycle.mjs`) drives `/reload`, closes and relaunches Pi with the same exact `--session-id`, checks the JSONL session header id, session continuity, slash-command sentinel tokens (`v1` then `v2` after rewriting the packaged extension to simulate pickup), persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification with a fake upstream on `PATH`. Default Pi model is `zai/glm-5.1`; default per-step wait is **180000 ms** (`DEFAULT_TIMEOUT_MS`); override model with `--model <id>` and waits with `--timeout-ms <ms>`. Passthrough flags in [`scripts/project.mjs`](../scripts/project.mjs): `--keep-artifacts`, `--model`, `--verbose`, and `--timeout-ms` plus a value (for example `npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal --keep-artifacts --verbose --timeout-ms 600000`). | Pass on 2026-06-03 (`npm run verify -- lifecycle`). Treat any future unexplained red lifecycle gate as a release blocker. |
74
- | Quick isolated Pi smoke | `pi --no-extensions --no-skills -e . --tools agent_browser` from repo root; native `agent_browser` only. | Last interactive tmux checkout smoke pass on 2026-05-29 (`agent-browser 0.27.0` at the time). The 2026-06-03 Crabbox matrix now covers clean packed Pi install plus deterministic wrapper dogfood on all required platforms for `agent-browser 0.27.1`; run a new manual tmux smoke before publish when human-readable transcript evidence is required. Broader historical coverage also includes version/help/skills, open/snapshot/click, eval stdin, batch stdin, screenshot, explicit session, `sessionMode: "fresh"`, network requests, console/errors, diff snapshot, stream status/disable, dashboard start/stop, and chat credential-failure pass-through during RQ-0055. |
74
+ | Quick isolated Pi smoke | `pi --approve --no-extensions --no-skills -e . --tools agent_browser` from trusted repo root; native `agent_browser` only. | Last interactive tmux checkout smoke pass on 2026-05-29 (`agent-browser 0.27.0` at the time). The 2026-06-03 Crabbox matrix now covers clean packed Pi install plus deterministic wrapper dogfood on all required platforms for `agent-browser 0.27.1`; run a new manual tmux smoke before publish when human-readable transcript evidence is required. Broader historical coverage also includes version/help/skills, open/snapshot/click, eval stdin, batch stdin, screenshot, explicit session, `sessionMode: "fresh"`, network requests, console/errors, diff snapshot, stream status/disable, dashboard start/stop, and chat credential-failure pass-through during RQ-0055. |
75
75
 
76
76
  ## Baseline checklist by inventory section
77
77
 
@@ -88,7 +88,7 @@ Re-run the gates below before each release; this table records what the closure
88
88
 
89
89
  Native `job`, `qa`, experimental `sourceLookup`, experimental `networkSourceLookup`, first-class Electron lifecycle/probe support, and optional Exa/Brave-backed companion web search are shipped.
90
90
 
91
- `RQ-0121` adds Pi-scoped package config plus optional Exa/Brave web search without turning search into an `agent_browser` input mode. Config lives at `~/.pi/config/pi-agent-browser-native/config.json`, `.pi/config/pi-agent-browser-native/config.json`, or an explicit `PI_AGENT_BROWSER_CONFIG` override, with global → project → override merge order and `EXA_API_KEY` / `BRAVE_API_KEY` as fallbacks only when no config credential source exists for that provider. `webSearch.exaApiKey` and `webSearch.braveApiKey` support Pi model/provider-style literal, `$ENV_VAR` / `${ENV_VAR}`, escape, and `!command` values in trusted global/override config; project-local config rejects plaintext, custom env aliases, interpolation-literal, malformed, and command-backed keys and allows only matching provider env refs. `webSearch.enabled: false` disables the tool even when environment keys exist after the final config merge: global disable is a user default, project disable is repo-scoped, and `PI_AGENT_BROWSER_CONFIG` disable wins for a hard per-run off switch. `webSearch.preferredProvider` chooses the default provider when both credentials resolve; Exa is the default because `/search` with highlights is the more agent-oriented result shape. `agent_browser_web_search` registers only when a usable credential source is available, resolves command secrets lazily at execution, calls Exa `/search` with highlights or Brave Search, returns compact normalized result details, and never exposes the key. Browser default profile/executable config records conservative prompt guidance only from trusted global or explicit override config; project-local browser config is not trusted to steer host profile/executable prompt guidance, and no config auto-injects launch args. Contract: [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#optional-companion-web-search); human workflow: README optional package config and [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#optional-package-config-and-companion-web-search); implementation: `extensions/agent-browser/lib/config.ts`, `extensions/agent-browser/lib/web-search.ts`, `scripts/config.mjs`, and conditional registration in `extensions/agent-browser/index.ts`; fake coverage: `test/agent-browser.config.test.ts`, `test/agent-browser.web-search.test.ts`, and `test/agent-browser.config-cli.test.ts`.
91
+ `RQ-0121` adds Pi-scoped package config plus optional Exa/Brave web search without turning search into an `agent_browser` input mode. Config lives at `~/.pi/config/pi-agent-browser-native/config.json`, `.pi/config/pi-agent-browser-native/config.json`, or an explicit `PI_AGENT_BROWSER_CONFIG` override, with global → project → override merge order and `EXA_API_KEY` / `BRAVE_API_KEY` as fallbacks only when no config credential source exists for that provider. Under Pi 0.79+, the extension reads project-local config by default because extensions are developer-trusted code and skips the project layer only when Pi is launched with `--no-approve`. `webSearch.exaApiKey` and `webSearch.braveApiKey` support Pi model/provider-style literal, `$ENV_VAR` / `${ENV_VAR}`, escape, and `!command` values in trusted global/override config; project-local config rejects plaintext, custom env aliases, interpolation-literal, malformed, and command-backed keys when loaded, and allows only matching provider env refs. `webSearch.enabled: false` disables the tool even when environment keys exist after the final config merge: global disable is a user default, project disable is repo-scoped, and `PI_AGENT_BROWSER_CONFIG` disable wins for a hard per-run off switch. `webSearch.preferredProvider` chooses the default provider when both credentials resolve; Exa is the default because `/search` with highlights is the more agent-oriented result shape. `agent_browser_web_search` registers only when a usable credential source is available, resolves command secrets lazily at execution, calls Exa `/search` with highlights or Brave Search, returns compact normalized result details, and never exposes the key. Browser default profile/executable config records conservative prompt guidance only from trusted global or explicit override config; project-local browser config is not trusted to steer host profile/executable prompt guidance, and no config auto-injects launch args. Contract: [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#optional-companion-web-search); human workflow: README optional package config and [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#optional-package-config-and-companion-web-search); implementation: `extensions/agent-browser/lib/config.ts`, `extensions/agent-browser/lib/web-search.ts`, `scripts/config.mjs`, and conditional registration in `extensions/agent-browser/index.ts`; fake coverage: `test/agent-browser.config.test.ts`, `test/agent-browser.web-search.test.ts`, and `test/agent-browser.config-cli.test.ts`.
92
92
 
93
93
  `RQ-0122` tracks the private shared-session finding reported by @deepakness: agents should not fan out web searches until provider rate limits are hit, and browser/profile config failures should lead to diagnostics plus user-facing setup recommendations instead of repeated failing opens. The wrapper now serializes companion web-search calls with a small request gate, adds agent-visible 429 guidance, rejects `--session-mode` inside native `args` in favor of top-level `sessionMode`, surfaces profile/user-data-dir failures with `inspect-browser-profiles` and `run-agent-browser-doctor` next actions, treats `--executable-path` as launch-scoped for active implicit sessions, and adds conservative config/prompt/docs support for non-default Chromium-compatible browser executables. Contract: [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) and [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#sessionmode); human workflow: README authenticated/profile workflows and [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#switch-from-an-already-active-implicit-session-to-a-fresh-profiled-or-alternate-browser-launch); implementation: `extensions/agent-browser/lib/web-search.ts`, `extensions/agent-browser/lib/config-policy.js`, `extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts`, `extensions/agent-browser/lib/results/presentation/errors.ts`, `extensions/agent-browser/lib/launch-scoped-flags.ts`, `extensions/agent-browser/lib/runtime.ts`, `extensions/agent-browser/lib/config.ts`, `scripts/config.mjs`, and `extensions/agent-browser/lib/playbook.ts`; fake coverage: `test/agent-browser.web-search.test.ts`, `test/agent-browser.presentation-skills-recovery.test.ts`, `test/agent-browser.results.test.ts`, `test/agent-browser.runtime.test.ts`, `test/agent-browser.config.test.ts`, `test/agent-browser.config-cli.test.ts`, and prompt-guidance assertions in `test/agent-browser.extension-validation.test.ts`.
94
94
 
@@ -128,7 +128,7 @@ Native `job`, `qa`, experimental `sourceLookup`, experimental `networkSourceLook
128
128
 
129
129
  `RQ-0088` adds current-snapshot ref fallback for selector misses: when raw `find` or compiled `semanticAction` fails with `failureCategory: "selector-not-found"`, `extensions/agent-browser/index.ts` may take one fresh session-scoped `snapshot -i`, then `extensions/agent-browser/lib/results/selector-recovery.ts` looks for exact normalized role/name matches for the failed target and emits `details.visibleRefFallback` plus visible `Current snapshot ref fallback`. Non-fill matches append bounded direct-ref next actions (`try-current-visible-ref` / `try-current-visible-ref-N`); fill matches omit direct args/text and feed the RQ-0099 rich-input recovery path when the ref is editable. The matcher is intentionally narrow: role locators require `--name`; text-click maps only to exact-name `button`/`link` refs; label/placeholder fill maps only to exact-name textbox/searchbox-style refs; prefixes/fuzzy matches are ignored, and duplicate exact matches carry ambiguity safety copy. Contract: [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) (`visibleRefFallback`, nextActions); human workflow: [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md) selector strategy and README pitfalls; fake coverage: `agentBrowserExtension suggests current snapshot refs when raw find role locators miss` in [`test/agent-browser.extension-validation.test.ts`](../test/agent-browser.extension-validation.test.ts).
130
130
 
131
- `RQ-0072` guards page-scoped `@e…` refs against silent recycling: successful `snapshot` (or the last `snapshot` step inside a successful `batch`) records `details.refSnapshot` with ref ids and the snapshot page URL; `extensions/agent-browser/lib/session-page-state.ts` replays per-session snapshots and `refSnapshotInvalidation` markers from the active transcript branch on `session_start` and Pi 0.78 `session_tree` branch changes, clears them on successful close commands (`close`, `quit`, or `exit`), invalidates prior refs when a session `snapshot` fails with `No active page`, rejects mutation-prone ref argv before spawn when the tab URL diverges, a ref id is missing from the latest snapshot, or the session refs are invalidated, blocks `batch` stdin that uses `@e…` on a guarded command after an earlier step that can navigate or mutate until a `snapshot` step appears later in the same stdin array (pre-spawn latch reset only), and prefixes `refresh-interactive-refs` with `--session` when the call names a session (including upstream-classified `stale-ref` outcomes). The entrypoint also serializes `session_tree` restore and wrapper-owned browser commands with managed-session work, guards independent caller-owned explicit-session completions with a branch-state generation check, keeps process-owned cleanup registries for managed sessions and wrapper-launched Electron records separate from the branch-visible view, treats explicit wrapper-owned close rows and Electron cleanup managed-session steps as restore-visible close events, closes off-branch owned managed sessions and Electron launches on non-quit reload shutdown, preserves current branch-visible active managed/Electron sessions and active Electron temp profiles for reload continuity, and preserves fresh-session allocation monotonicity across branch restores. Contract: [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) (`refSnapshot`, `refSnapshotInvalidation`, `stale-ref`); human workflow: [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md) snapshot/ref notes and README pitfalls; fake coverage: `agentBrowserExtension recommends tab recovery after No active page snapshot failures` and `agentBrowserExtension invalidates refs after No active page snapshot failures inside batch` in [`test/agent-browser.extension-validation.test.ts`](../test/agent-browser.extension-validation.test.ts), plus `agentBrowserExtension blocks page-scoped ref reuse…`, `…rehydrates page-scoped refs from the current tree branch`, `…rehydrates managed browser session state from the current tree branch`, `…rehydrates artifact manifest state from the current tree branch`, `…keeps Electron cleanup ownership after session_tree switches away from the launch branch`, `…blocks stale refs after page-changing steps inside a batch`, `…allows same-snapshot form fills before a batch click`, `…allows same-snapshot form control batches before a hard invalidating click`, `…allows batch stdin ref steps after snapshot following an invalidating step`, `…records snapshot refs returned inside a successful batch`, and `…rejects refs absent from the latest same-page snapshot` in [`test/agent-browser.extension-ref-guards.test.ts`](../test/agent-browser.extension-ref-guards.test.ts); managed-session reload cleanup, explicit close untracking/state rotation/restore, generated fresh-name reservation after repeated explicit closes, explicit-session command versus `session_tree` generation-guard coverage, explicit close versus in-flight implicit command serialization, and fresh-ordinal coverage lives in [`test/agent-browser.resume-state.test.ts`](../test/agent-browser.resume-state.test.ts).
131
+ `RQ-0072` guards page-scoped `@e…` refs against silent recycling: successful `snapshot` (or the last `snapshot` step inside a successful `batch`) records `details.refSnapshot` with ref ids and the snapshot page URL; `extensions/agent-browser/lib/session-page-state.ts` replays per-session snapshots and `refSnapshotInvalidation` markers from the active transcript branch on `session_start` and Pi 0.79 `session_tree` branch changes, clears them on successful close commands (`close`, `quit`, or `exit`), invalidates prior refs when a session `snapshot` fails with `No active page`, rejects mutation-prone ref argv before spawn when the tab URL diverges, a ref id is missing from the latest snapshot, or the session refs are invalidated, blocks `batch` stdin that uses `@e…` on a guarded command after an earlier step that can navigate or mutate until a `snapshot` step appears later in the same stdin array (pre-spawn latch reset only), and prefixes `refresh-interactive-refs` with `--session` when the call names a session (including upstream-classified `stale-ref` outcomes). The entrypoint also serializes `session_tree` restore and wrapper-owned browser commands with managed-session work, guards independent caller-owned explicit-session completions with a branch-state generation check, keeps process-owned cleanup registries for managed sessions and wrapper-launched Electron records separate from the branch-visible view, treats explicit wrapper-owned close rows and Electron cleanup managed-session steps as restore-visible close events, closes off-branch owned managed sessions and Electron launches on non-quit reload shutdown, preserves current branch-visible active managed/Electron sessions and active Electron temp profiles for reload continuity, and preserves fresh-session allocation monotonicity across branch restores. Contract: [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) (`refSnapshot`, `refSnapshotInvalidation`, `stale-ref`); human workflow: [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md) snapshot/ref notes and README pitfalls; fake coverage: `agentBrowserExtension recommends tab recovery after No active page snapshot failures` and `agentBrowserExtension invalidates refs after No active page snapshot failures inside batch` in [`test/agent-browser.extension-validation.test.ts`](../test/agent-browser.extension-validation.test.ts), plus `agentBrowserExtension blocks page-scoped ref reuse…`, `…rehydrates page-scoped refs from the current tree branch`, `…rehydrates managed browser session state from the current tree branch`, `…rehydrates artifact manifest state from the current tree branch`, `…keeps Electron cleanup ownership after session_tree switches away from the launch branch`, `…blocks stale refs after page-changing steps inside a batch`, `…allows same-snapshot form fills before a batch click`, `…allows same-snapshot form control batches before a hard invalidating click`, `…allows batch stdin ref steps after snapshot following an invalidating step`, `…records snapshot refs returned inside a successful batch`, and `…rejects refs absent from the latest same-page snapshot` in [`test/agent-browser.extension-ref-guards.test.ts`](../test/agent-browser.extension-ref-guards.test.ts); managed-session reload cleanup, explicit close untracking/state rotation/restore, generated fresh-name reservation after repeated explicit closes, explicit-session command versus `session_tree` generation-guard coverage, explicit close versus in-flight implicit command serialization, and fresh-ordinal coverage lives in [`test/agent-browser.resume-state.test.ts`](../test/agent-browser.resume-state.test.ts).
132
132
 
133
133
  `RQ-0087` keeps the RQ-0072 guard but removes safe same-snapshot form work from the batch invalidation latch: `fill @e…` rows and role-checked native form-control rows (`check`/`uncheck` or direct `click`/`tap` on checkbox or radio refs, and `select` on combobox refs) remain guarded against stale/missing refs, yet can run before the first hard click/submit/navigation step in one upstream `batch`. A later guarded ref after `open`, `reload`, non-form `click`/`tap`, or other invalidating rows still fails before spawn unless the batch includes a fresh `snapshot` step first; checkbox/radio clicks are only allowed when every ref in the step has latest-snapshot checkbox/radio role evidence. This improves login/checkout/static-form efficiency without permitting likely post-navigation ref reuse. Contract: [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) (`Batch stdin ordering`); human workflow: README and [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md) ref notes; fake coverage: `agentBrowserExtension allows same-snapshot form fills before a batch click` and `agentBrowserExtension allows same-snapshot form control batches before a hard invalidating click` in [`test/agent-browser.extension-ref-guards.test.ts`](../test/agent-browser.extension-ref-guards.test.ts).
134
134
 
@@ -13,7 +13,7 @@ Related docs:
13
13
  V1 exposes one primary native browser tool and one optional companion search tool:
14
14
 
15
15
  - `agent_browser`
16
- - `agent_browser_web_search` when an Exa or Brave Search credential source is configured or resolvable and `webSearch.enabled` is not false
16
+ - `agent_browser_web_search` when an Exa or Brave Search credential source is configured or resolvable from available config and `webSearch.enabled` is not false
17
17
 
18
18
  ## Why this tool shape
19
19
 
@@ -36,7 +36,7 @@ Agent-facing efficiency claims are measured with `npm run benchmark:agent-browse
36
36
 
37
37
  ## Optional companion web search
38
38
 
39
- `agent_browser_web_search` is a separate custom tool, not an `agent_browser` input mode. It is registered only when the extension can see at least one configured/resolvable Exa or Brave credential source from `~/.pi/config/pi-agent-browser-native/config.json`, `.pi/config/pi-agent-browser-native/config.json`, `PI_AGENT_BROWSER_CONFIG`, or the `EXA_API_KEY` / `BRAVE_API_KEY` environment fallbacks, and only when the final merged config does not set `webSearch.enabled` to `false`. Config layers merge global → project → `PI_AGENT_BROWSER_CONFIG` override, so disable scope is explicit: a global disable is a normal user default, a project disable applies to one repo, and an override file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Command credential sources such as `"!op read 'op://Private/Exa/API Key'"` are allowed only from trusted global or explicit-override config; they make the tool available without running the command at startup, and the key is resolved when the tool executes. Project-local config may use only matching provider env refs (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa and `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave); custom env aliases, interpolation literals, and malformed `$` values are rejected. Browser profile/executable config uses the same paths but only trusted global or explicit override values are emitted as host launch prompt guidance; project-local browser config is not trusted to steer local profiles or executable paths.
39
+ `agent_browser_web_search` is a separate custom tool, not an `agent_browser` input mode. It is registered only when the extension can see at least one configured/resolvable Exa or Brave credential source from `~/.pi/config/pi-agent-browser-native/config.json`, `.pi/config/pi-agent-browser-native/config.json`, `PI_AGENT_BROWSER_CONFIG`, or the `EXA_API_KEY` / `BRAVE_API_KEY` environment fallbacks, and only when the final available merged config does not set `webSearch.enabled` to `false`. Config layers merge global → project → `PI_AGENT_BROWSER_CONFIG` override; under Pi 0.79+, globally installed and CLI-loaded copies read `.pi/config/...` by default because extensions are developer-trusted code, and they skip the project layer only when Pi is launched with `--no-approve`. Disable scope is explicit: a global disable is a normal user default, a project disable applies to one repo, and an override file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Command credential sources such as `"!op read 'op://Private/Exa/API Key'"` are allowed only from trusted global or explicit-override config; they make the tool available without running the command at startup, and the key is resolved when the tool executes. Project-local config may use only matching provider env refs (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa and `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave); custom env aliases, interpolation literals, and malformed `$` values are rejected. Browser profile/executable config uses the same paths but only trusted global or explicit override values are emitted as host launch prompt guidance; project-local browser config is loaded by default but is not trusted to steer local profiles or executable paths.
40
40
 
41
41
  Use it when live/current external web information would help answer a task, find current docs/news, or discover candidate URLs. Use `agent_browser` when the task needs browser interaction, screenshots, authenticated/profile content, page inspection, or DOM work. The search tool is namespaced to avoid colliding with generic `web_search`, chooses Exa or Brave automatically from available credentials, defaults to Exa when both are available (unless `webSearch.preferredProvider` is set), and must not expose resolved API keys in content, details, errors, status output, docs examples, logs, or PR artifacts.
42
42
 
@@ -98,8 +98,8 @@ Each required target runs `platform-build` and `browser-dogfood-smoke` on one Cr
98
98
  4. Run `npm pack`.
99
99
  5. Create a clean target-local Pi project.
100
100
  6. Install the packed tarball with `npm install --no-save`.
101
- 7. Run `pi install -l ./node_modules/pi-agent-browser-native` from the clean project.
102
- 8. Run `pi list` and assert the package is registered from the packed install.
101
+ 7. Run `pi install -l --approve ./node_modules/pi-agent-browser-native` from the clean project so Pi 0.79+ trusts the generated project-local settings for that command.
102
+ 8. Run `pi list --approve` and assert the package is registered under project packages from the packed install.
103
103
  9. Assert the release proof did not use `pi -e .` or `pi --extension .`.
104
104
 
105
105
  ### `browser-dogfood-smoke`
@@ -10,11 +10,10 @@ import type { ChildProcess } from "node:child_process";
10
10
  import { dirname, join, resolve } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
 
13
- import {
14
- isToolCallEventType,
15
- type AgentToolResult,
16
- type ExtensionAPI,
17
- type ExtensionContext,
13
+ import type {
14
+ AgentToolResult,
15
+ ExtensionAPI,
16
+ ExtensionContext,
18
17
  } from "@earendil-works/pi-coding-agent";
19
18
  import { Text } from "@earendil-works/pi-tui";
20
19
  import {
@@ -91,7 +90,7 @@ import {
91
90
  restoreElectronLaunchRecordsFromBranch,
92
91
  type ElectronLaunchRecord,
93
92
  } from "./lib/orchestration/electron-host/index.js";
94
- import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/orchestration/input-plan.js";
93
+ import { buildValidationFailureResult, resolveAgentBrowserInput, type AgentBrowserExecuteParams } from "./lib/orchestration/input-plan.js";
95
94
  import { applyAgentBrowserOutputPath } from "./lib/orchestration/output-file.js";
96
95
  import type { NetworkRouteRecord } from "./lib/results/contracts.js";
97
96
  import type { SessionArtifactManifest } from "./lib/results/contracts.js";
@@ -131,6 +130,16 @@ import {
131
130
 
132
131
  const DEFAULT_SESSION_MODE = "auto" as const;
133
132
 
133
+ type BashToolCallLike = {
134
+ input: { command: string };
135
+ toolName: "bash";
136
+ };
137
+
138
+ function isBashToolCallEvent(event: unknown): event is BashToolCallLike {
139
+ if (!isRecord(event) || event.toolName !== "bash" || !isRecord(event.input)) return false;
140
+ return typeof event.input.command === "string";
141
+ }
142
+
134
143
  type OwnedManagedSession = {
135
144
  branchOwned: boolean;
136
145
  cwd: string;
@@ -580,11 +589,20 @@ function getInstalledDocsPaths(): { readmePath: string; commandReferencePath: st
580
589
  };
581
590
  }
582
591
 
592
+ function hasArgvFlag(argv: readonly string[], longFlag: string, shortFlag: string): boolean {
593
+ return argv.includes(longFlag) || argv.includes(shortFlag);
594
+ }
583
595
 
596
+ function shouldIncludeProjectConfig(_cwd: string, argv: readonly string[] = process.argv): boolean {
597
+ return !hasArgvFlag(argv, "--no-approve", "-na");
598
+ }
584
599
 
585
600
  export default function agentBrowserExtension(pi: ExtensionAPI) {
586
601
  const ephemeralSessionSeed = createEphemeralSessionSeed();
587
- const agentBrowserConfig = loadAgentBrowserConfigSync({ cwd: process.cwd() });
602
+ const agentBrowserConfig = loadAgentBrowserConfigSync({
603
+ cwd: process.cwd(),
604
+ includeProjectConfig: shouldIncludeProjectConfig(process.cwd()),
605
+ });
588
606
  const webSearchToolAvailable = canRegisterWebSearchTool(agentBrowserConfig);
589
607
  const toolPromptGuidelines = buildToolPromptGuidelines({
590
608
  browserDefaultProfile: agentBrowserConfig.trustedBrowserDefaultProfile,
@@ -753,7 +771,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
753
771
  pi.on("tool_call", async (event, ctx) => {
754
772
  const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
755
773
  if (
756
- isToolCallEventType("bash", event) &&
774
+ isBashToolCallEvent(event) &&
757
775
  !promptPolicy.allowLegacyAgentBrowserBash &&
758
776
  looksLikeDirectAgentBrowserBash(event.input.command) &&
759
777
  !isHarmlessAgentBrowserInspectionCommand(event.input.command) &&
@@ -789,7 +807,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
789
807
  component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
790
808
  return component;
791
809
  },
792
- async execute(_toolCallId, params, signal, onUpdate, ctx) {
810
+ async execute(_toolCallId, params: AgentBrowserExecuteParams, signal, onUpdate, ctx) {
793
811
  const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
794
812
  const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
795
813
  const resolvedInput = resolveAgentBrowserInput({
@@ -23,7 +23,8 @@ import { join, resolve } from "node:path";
23
23
  /** @typedef {{ config: AgentBrowserConfig; path: string; scope: ConfigLayerScope }} ConfigLayer */
24
24
  /** @typedef {{ kind: CredentialSourceKind; provider?: WebSearchProvider; rawValue: string; scope: AgentBrowserConfigScope }} CredentialSource */
25
25
  /** @typedef {{ global: string; project: string; override?: string }} AgentBrowserConfigPaths */
26
- /** @typedef {{ browserDefaultProfile?: Required<BrowserDefaultProfileConfig>; browserDefaultProfileScope?: ConfigLayerScope; browserExecutablePath?: string; browserExecutablePathScope?: ConfigLayerScope; trustedBrowserDefaultProfile?: Required<BrowserDefaultProfileConfig>; trustedBrowserDefaultProfileScope?: Exclude<ConfigLayerScope, "project">; trustedBrowserExecutablePath?: string; trustedBrowserExecutablePathScope?: Exclude<ConfigLayerScope, "project">; config: AgentBrowserConfig; webSearchCredentialSources: Partial<Record<WebSearchProvider, CredentialSource>>; webSearchEnabled: boolean; webSearchPreferredProvider: WebSearchProvider; errors: string[]; layers: ConfigLayer[]; paths: AgentBrowserConfigPaths; warnings: string[] }} AgentBrowserConfigState */
26
+ /** @typedef {{ cwd?: string; env?: NodeJS.ProcessEnv; includeProjectConfig?: boolean }} AgentBrowserConfigLoadOptions */
27
+ /** @typedef {{ browserDefaultProfile?: Required<BrowserDefaultProfileConfig>; browserDefaultProfileScope?: ConfigLayerScope; browserExecutablePath?: string; browserExecutablePathScope?: ConfigLayerScope; trustedBrowserDefaultProfile?: Required<BrowserDefaultProfileConfig>; trustedBrowserDefaultProfileScope?: Exclude<ConfigLayerScope, "project">; trustedBrowserExecutablePath?: string; trustedBrowserExecutablePathScope?: Exclude<ConfigLayerScope, "project">; config: AgentBrowserConfig; webSearchCredentialSources: Partial<Record<WebSearchProvider, CredentialSource>>; webSearchEnabled: boolean; webSearchPreferredProvider: WebSearchProvider; errors: string[]; layers: ConfigLayer[]; paths: AgentBrowserConfigPaths; projectConfigIncluded: boolean; warnings: string[] }} AgentBrowserConfigState */
27
28
  /** @typedef {{ scope: string; path: string; exists: boolean }} ConfigFileSummary */
28
29
 
29
30
  export const AGENT_BROWSER_CONFIG_ENV = "PI_AGENT_BROWSER_CONFIG";
@@ -469,7 +470,7 @@ export function buildWebSearchCredentialSources(options) {
469
470
  }
470
471
 
471
472
  /**
472
- * @param {{ env: NodeJS.ProcessEnv; layers: ConfigLayer[]; mergedConfig: AgentBrowserConfig; paths: AgentBrowserConfigPaths; errors: string[]; warnings: string[] }} options
473
+ * @param {{ env: NodeJS.ProcessEnv; layers: ConfigLayer[]; mergedConfig: AgentBrowserConfig; paths: AgentBrowserConfigPaths; errors: string[]; warnings: string[]; projectConfigIncluded?: boolean }} options
473
474
  * @returns {AgentBrowserConfigState}
474
475
  */
475
476
  export function buildAgentBrowserConfigState(options) {
@@ -492,6 +493,7 @@ export function buildAgentBrowserConfigState(options) {
492
493
  errors: options.errors,
493
494
  layers: options.layers,
494
495
  paths: options.paths,
496
+ projectConfigIncluded: options.projectConfigIncluded ?? options.layers.some((layer) => layer.scope === "project"),
495
497
  warnings: options.warnings,
496
498
  };
497
499
  }
@@ -516,12 +518,13 @@ function readConfigLayerSync(path, scope, errors, warnings) {
516
518
  }
517
519
 
518
520
  /**
519
- * @param {{ cwd?: string; env?: NodeJS.ProcessEnv }} [options]
521
+ * @param {AgentBrowserConfigLoadOptions} [options]
520
522
  * @returns {AgentBrowserConfigState}
521
523
  */
522
524
  export function loadAgentBrowserConfigStateSync(options = {}) {
523
525
  const env = options.env ?? process.env;
524
526
  const paths = getAgentBrowserConfigPaths({ cwd: options.cwd, env });
527
+ const includeProjectConfig = options.includeProjectConfig !== false;
525
528
  /** @type {string[]} */
526
529
  const errors = [];
527
530
  /** @type {string[]} */
@@ -529,7 +532,7 @@ export function loadAgentBrowserConfigStateSync(options = {}) {
529
532
  /** @type {Array<{ path: string; scope: ConfigLayerScope }>} */
530
533
  const layerCandidates = [
531
534
  { path: paths.global, scope: "global" },
532
- { path: paths.project, scope: "project" },
535
+ ...(includeProjectConfig ? [{ path: paths.project, scope: /** @type {ConfigLayerScope} */ ("project") }] : []),
533
536
  ...(paths.override ? [{ path: paths.override, scope: /** @type {ConfigLayerScope} */ ("override") }] : []),
534
537
  ];
535
538
  /** @type {ConfigLayer[]} */
@@ -542,7 +545,15 @@ export function loadAgentBrowserConfigStateSync(options = {}) {
542
545
  layers.push(layer);
543
546
  mergedConfig = mergeAgentBrowserConfig(mergedConfig, layer.config);
544
547
  }
545
- return buildAgentBrowserConfigState({ env, errors, layers, mergedConfig, paths, warnings });
548
+ return buildAgentBrowserConfigState({
549
+ env,
550
+ errors,
551
+ layers,
552
+ mergedConfig,
553
+ paths,
554
+ projectConfigIncluded: includeProjectConfig,
555
+ warnings,
556
+ });
546
557
  }
547
558
 
548
559
  /**
@@ -22,6 +22,7 @@ import {
22
22
  } from "./config-policy.js";
23
23
  import type {
24
24
  AgentBrowserConfig,
25
+ AgentBrowserConfigLoadOptions,
25
26
  AgentBrowserConfigScope,
26
27
  AgentBrowserConfigState,
27
28
  BrowserDefaultProfileConfig,
@@ -74,6 +75,7 @@ export {
74
75
  } from "./config-policy.js";
75
76
  export type {
76
77
  AgentBrowserConfig,
78
+ AgentBrowserConfigLoadOptions,
77
79
  AgentBrowserConfigScope,
78
80
  AgentBrowserConfigState,
79
81
  BrowserDefaultProfileConfig,
@@ -106,14 +108,15 @@ async function readConfigLayer(path: string, scope: ConfigLayer["scope"], errors
106
108
  return parseAgentBrowserConfigLayer(raw, path, scope, errors, warnings);
107
109
  }
108
110
 
109
- export async function loadAgentBrowserConfig(options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}): Promise<AgentBrowserConfigState> {
111
+ export async function loadAgentBrowserConfig(options: AgentBrowserConfigLoadOptions = {}): Promise<AgentBrowserConfigState> {
110
112
  const env = options.env ?? process.env;
111
113
  const paths = getAgentBrowserConfigPaths({ cwd: options.cwd, env });
114
+ const includeProjectConfig = options.includeProjectConfig !== false;
112
115
  const errors: string[] = [];
113
116
  const warnings: string[] = [];
114
117
  const layerCandidates = [
115
118
  { path: paths.global, scope: "global" as const },
116
- { path: paths.project, scope: "project" as const },
119
+ ...(includeProjectConfig ? [{ path: paths.project, scope: "project" as const }] : []),
117
120
  ...(paths.override ? [{ path: paths.override, scope: "override" as const }] : []),
118
121
  ];
119
122
  const layers: ConfigLayer[] = [];
@@ -124,10 +127,18 @@ export async function loadAgentBrowserConfig(options: { cwd?: string; env?: Node
124
127
  layers.push(layer);
125
128
  mergedConfig = mergeAgentBrowserConfig(mergedConfig, layer.config);
126
129
  }
127
- return buildAgentBrowserConfigState({ env, errors, layers, mergedConfig, paths, warnings });
130
+ return buildAgentBrowserConfigState({
131
+ env,
132
+ errors,
133
+ layers,
134
+ mergedConfig,
135
+ paths,
136
+ projectConfigIncluded: includeProjectConfig,
137
+ warnings,
138
+ });
128
139
  }
129
140
 
130
- export function loadAgentBrowserConfigSync(options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}): AgentBrowserConfigState {
141
+ export function loadAgentBrowserConfigSync(options: AgentBrowserConfigLoadOptions = {}): AgentBrowserConfigState {
131
142
  return loadAgentBrowserConfigStateSync(options);
132
143
  }
133
144
 
@@ -4,8 +4,8 @@
4
4
  * Scope: Schema-only; behavioral validation lives in the mode compilers.
5
5
  */
6
6
 
7
- import { StringEnum } from "@earendil-works/pi-ai";
8
- import { Type } from "typebox";
7
+ import { JsonSchema, type JsonSchemaBuilder } from "../json-schema.js";
8
+ import { StringEnum as localStringEnum, type StringEnumBuilder } from "../string-enum-schema.js";
9
9
 
10
10
  import {
11
11
  ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS,
@@ -23,7 +23,11 @@ import {
23
23
  SOURCE_LOOKUP_MAX_WORKSPACE_FILES,
24
24
  } from "./types.js";
25
25
 
26
- export const AGENT_BROWSER_PARAMS = Type.Object({
26
+ export function createAgentBrowserParamsSchema(
27
+ Type: JsonSchemaBuilder = JsonSchema,
28
+ StringEnum: StringEnumBuilder = localStringEnum,
29
+ ) {
30
+ return Type.Object({
27
31
 
28
32
  args: Type.Optional(
29
33
  Type.Array(Type.String({ description: "Exact agent-browser CLI arguments, excluding the binary name. Do not pass --json; the wrapper injects it. First-call recipe: open → snapshot -i → click/fill @eN → snapshot -i." }), {
@@ -195,4 +199,7 @@ export const AGENT_BROWSER_PARAMS = Type.Object({
195
199
  default: DEFAULT_SESSION_MODE,
196
200
  }),
197
201
  ),
198
- }, { additionalProperties: false });
202
+ }, { additionalProperties: false });
203
+ }
204
+
205
+ export const AGENT_BROWSER_PARAMS = createAgentBrowserParamsSchema();
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Purpose: Build the small JSON Schema subset used by Pi tool schemas without importing TypeBox at runtime.
3
+ * Responsibilities: Preserve plain JSON Schema objects Pi consumes while keeping extension startup cheap.
4
+ * Scope: Schema construction only; runtime validation still belongs to Pi and the tool input compilers.
5
+ */
6
+
7
+ import type { TSchema, TSchemaOptions, TUnsafe } from "typebox";
8
+
9
+ const OPTIONAL_SCHEMA = Symbol("pi-agent-browser-optional-schema");
10
+
11
+ type SchemaObject = TSchema & { [OPTIONAL_SCHEMA]?: true };
12
+ type SchemaProperties = Record<string, TSchema>;
13
+
14
+ function withOptions(schema: Record<string, unknown>, options?: TSchemaOptions): TSchema {
15
+ return { ...schema, ...(options ?? {}) } as TSchema;
16
+ }
17
+
18
+ function literalType(value: unknown): "boolean" | "number" | "string" | undefined {
19
+ const valueType = typeof value;
20
+ return valueType === "string" || valueType === "number" || valueType === "boolean" ? valueType : undefined;
21
+ }
22
+
23
+ function propertySchema(schema: TSchema): TSchema {
24
+ const clone = { ...(schema as SchemaObject & Record<PropertyKey, unknown>) };
25
+ delete clone[OPTIONAL_SCHEMA];
26
+ return clone as TSchema;
27
+ }
28
+
29
+ export const JsonSchema = {
30
+ Array(items: TSchema, options?: TSchemaOptions): TSchema {
31
+ return withOptions({ type: "array", items }, options);
32
+ },
33
+ Boolean(options?: TSchemaOptions): TSchema {
34
+ return withOptions({ type: "boolean" }, options);
35
+ },
36
+ Integer(options?: TSchemaOptions): TSchema {
37
+ return withOptions({ type: "integer" }, options);
38
+ },
39
+ Literal(value: unknown, options?: TSchemaOptions): TSchema {
40
+ const type = literalType(value);
41
+ return withOptions(type ? { type, const: value } : { const: value }, options);
42
+ },
43
+ Number(options?: TSchemaOptions): TSchema {
44
+ return withOptions({ type: "number" }, options);
45
+ },
46
+ Object(properties: SchemaProperties, options?: TSchemaOptions): TSchema {
47
+ const required = globalThis.Object.entries(properties)
48
+ .filter(([, schema]) => (schema as SchemaObject)[OPTIONAL_SCHEMA] !== true)
49
+ .map(([key]) => key);
50
+ return withOptions({
51
+ type: "object",
52
+ properties: globalThis.Object.fromEntries(
53
+ globalThis.Object.entries(properties).map(([key, schema]) => [key, propertySchema(schema)]),
54
+ ),
55
+ ...(required.length > 0 ? { required } : {}),
56
+ }, options);
57
+ },
58
+ Optional(schema: TSchema): TSchema {
59
+ return { ...(schema as SchemaObject), [OPTIONAL_SCHEMA]: true } as TSchema;
60
+ },
61
+ String(options?: TSchemaOptions): TSchema {
62
+ return withOptions({ type: "string" }, options);
63
+ },
64
+ Union(types: TSchema[], options?: TSchemaOptions): TSchema {
65
+ return withOptions({ anyOf: types }, options);
66
+ },
67
+ Unsafe<Value>(schema: TSchema): TUnsafe<Value> {
68
+ return schema as TUnsafe<Value>;
69
+ },
70
+ };
71
+
72
+ export type JsonSchemaBuilder = typeof JsonSchema;
73
+ export type { TSchema, TSchemaOptions, TUnsafe };
@@ -824,6 +824,12 @@ function buildTimeoutProgressSteps(options: {
824
824
  step.reason = "Later step completion evidence indicates the batch advanced past this step before timeout.";
825
825
  }
826
826
  }
827
+ for (const step of progressSteps) {
828
+ const command = step.args[0];
829
+ if (step.status === "completed" && (isOpenNavigationCommand(command) || command === "pushstate")) {
830
+ lastCompletedNavigationIndex = Math.max(lastCompletedNavigationIndex ?? 0, step.index);
831
+ }
832
+ }
827
833
  for (const step of progressSteps) {
828
834
  if (step.status === "completed") continue;
829
835
  if (!retryStep) {
@@ -1,6 +1,5 @@
1
1
  import type { AgentToolResult, Theme, ToolResultEvent } from "@earendil-works/pi-coding-agent";
2
- import { highlightCode, keyHint } from "@earendil-works/pi-coding-agent";
3
- import { Text, truncateToWidth } from "@earendil-works/pi-tui";
2
+ import { getKeybindings, Text, truncateToWidth } from "@earendil-works/pi-tui";
4
3
 
5
4
  import {
6
5
  compileAgentBrowserElectron,
@@ -16,6 +15,7 @@ import { redactInvocationArgs } from "./runtime.js";
16
15
  const TUI_INVOCATION_PREVIEW_MAX_CHARS = 160;
17
16
  const TUI_COLLAPSED_OUTPUT_MAX_LINES = 12;
18
17
  const ANSI_CONTROL_SEQUENCE_PATTERN = /\x1B(?:\][^\x07\x1B]*(?:\x07|\x1B\\)|\[[0-?]*[ -/]*[@-~]|P[^\x1B]*(?:\x1B\\)|_[^\x1B]*(?:\x1B\\)|\^[^\x1B]*(?:\x1B\\)|[@-Z\\-_])/g;
18
+ const JSON_TOKEN_PATTERN = /"(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null|[{}\[\],:]/g;
19
19
  const UNSAFE_DISPLAY_CONTROL_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x80-\x9F]/g;
20
20
 
21
21
  function sanitizeDisplayText(value: string): string {
@@ -48,6 +48,26 @@ function isJsonDocumentText(value: string): boolean {
48
48
  }
49
49
  }
50
50
 
51
+ function colorizeJsonLine(line: string, theme: Theme): string {
52
+ let output = "";
53
+ let cursor = 0;
54
+ for (const match of line.matchAll(JSON_TOKEN_PATTERN)) {
55
+ const token = match[0];
56
+ const index = match.index ?? 0;
57
+ output += line.slice(cursor, index);
58
+ const color = token.startsWith('"')
59
+ ? /"\s*$/.test(token) && line.slice(index + token.length).trimStart().startsWith(":")
60
+ ? "syntaxVariable"
61
+ : "syntaxString"
62
+ : /^[{}\[\],:]$/.test(token)
63
+ ? "syntaxPunctuation"
64
+ : "syntaxType";
65
+ output += theme.fg(color, token);
66
+ cursor = index + token.length;
67
+ }
68
+ return output + line.slice(cursor);
69
+ }
70
+
51
71
  function getPrimaryTextContent(result: AgentToolResult<unknown>): string {
52
72
  const textContent = result.content.find((item) => item.type === "text");
53
73
  return textContent?.type === "text" ? textContent.text : "";
@@ -57,23 +77,24 @@ function colorizeToolOutputLines(outputText: string, theme: Theme, isError: bool
57
77
  const normalizedLines = trimTrailingBlankLines(replaceTabsForDisplay(sanitizeDisplayText(outputText)).split("\n"));
58
78
  const normalizedText = normalizedLines.join("\n");
59
79
  if (normalizedText.length === 0) return [];
60
- if (isJsonDocumentText(normalizedText)) {
61
- return highlightCode(normalizedText, "json");
62
- }
80
+ const isJsonDocument = !isError && isJsonDocumentText(normalizedText);
63
81
  return normalizedLines.map((line) => {
64
82
  if (line.length === 0) {
65
83
  return "";
66
84
  }
85
+ if (isJsonDocument) return colorizeJsonLine(line, theme);
67
86
  return isError ? theme.fg("error", line) : theme.fg("toolOutput", line);
68
87
  });
69
88
  }
70
89
 
71
90
  function formatExpandHint(theme: Theme): string {
72
91
  try {
73
- return keyHint("app.tools.expand", "to expand");
92
+ const [key] = getKeybindings().getKeys("app.tools.expand" as never);
93
+ if (key) return `${theme.fg("dim", key)} ${theme.fg("muted", "to expand")}`;
74
94
  } catch {
75
- return `${theme.fg("dim", "ctrl+o")} ${theme.fg("muted", "to expand")}`;
95
+ // Fall through to the built-in default key when coding-agent keybindings are unavailable.
76
96
  }
97
+ return `${theme.fg("dim", "ctrl+o")} ${theme.fg("muted", "to expand")}`;
77
98
  }
78
99
 
79
100
  function formatVisualTruncationNotice(remainingLines: number, totalLines: number, theme: Theme, width: number): string {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Purpose: Build compact JSON-schema string enums without importing pi runtime helpers.
3
+ * Responsibilities: Mirror pi-ai StringEnum's `{ type: "string", enum: [...] }` shape while keeping extension startup imports light.
4
+ * Scope: Schema construction only.
5
+ */
6
+
7
+ import { JsonSchema, type TSchemaOptions, type TUnsafe } from "./json-schema.js";
8
+
9
+ export type StringEnumBuilder = typeof StringEnum;
10
+
11
+ export function StringEnum<const Values extends readonly string[]>(
12
+ values: Values,
13
+ options?: TSchemaOptions,
14
+ ): TUnsafe<Values[number]> {
15
+ return JsonSchema.Unsafe<Values[number]>({
16
+ type: "string",
17
+ enum: [...values],
18
+ ...options,
19
+ });
20
+ }
@@ -4,9 +4,8 @@
4
4
  * Scope: Live web search only; browser automation remains in the `agent_browser` tool.
5
5
  */
6
6
 
7
- import { StringEnum } from "@earendil-works/pi-ai";
8
- import { defineTool } from "@earendil-works/pi-coding-agent";
9
- import { Type } from "typebox";
7
+ import { JsonSchema, type JsonSchemaBuilder } from "./json-schema.js";
8
+ import { StringEnum as localStringEnum, type StringEnumBuilder } from "./string-enum-schema.js";
10
9
  import {
11
10
  DEFAULT_WEB_SEARCH_PROVIDER,
12
11
  WEB_SEARCH_PROVIDERS,
@@ -119,8 +118,12 @@ export interface WebSearchProviderAdapter<Request = unknown, Response = unknown>
119
118
  provider: WebSearchProvider;
120
119
  }
121
120
 
122
- export const AgentBrowserWebSearchParams = Type.Object(
123
- {
121
+ export function createAgentBrowserWebSearchParamsSchema(
122
+ Type: JsonSchemaBuilder = JsonSchema,
123
+ StringEnum: StringEnumBuilder = localStringEnum,
124
+ ) {
125
+ return Type.Object(
126
+ {
124
127
  query: Type.String({
125
128
  minLength: 1,
126
129
  description: "Search query to run with the configured Exa or Brave web search provider.",
@@ -172,9 +175,12 @@ export const AgentBrowserWebSearchParams = Type.Object(
172
175
  description: "Optional freshness window: pd=past day, pw=past week, pm=past month, py=past year.",
173
176
  }),
174
177
  ),
175
- },
176
- { additionalProperties: false },
177
- );
178
+ },
179
+ { additionalProperties: false },
180
+ );
181
+ }
182
+
183
+ export const AgentBrowserWebSearchParams = createAgentBrowserWebSearchParamsSchema();
178
184
 
179
185
  const HTML_ENTITY_REPLACEMENTS: Readonly<Record<string, string>> = {
180
186
  amp: "&",
@@ -644,9 +650,21 @@ function buildMissingCredentialError(provider: WebSearchProviderParam): string {
644
650
  return "No Exa or Brave web search credential resolved. Configure webSearch.exaApiKey or webSearch.braveApiKey, or load EXA_API_KEY/BRAVE_API_KEY in the runtime environment.";
645
651
  }
646
652
 
653
+ type AgentBrowserWebSearchParamsInput = {
654
+ country?: string;
655
+ count?: number;
656
+ freshness?: SearchFreshness;
657
+ offset?: number;
658
+ provider?: WebSearchProviderParam;
659
+ query: string;
660
+ safesearch?: "off" | "moderate" | "strict";
661
+ searchLang?: string;
662
+ searchType?: ExaSearchType;
663
+ };
664
+
647
665
  export function createAgentBrowserWebSearchTool(configState: AgentBrowserConfigState) {
648
666
  const requestGate = new WebSearchRequestGate();
649
- return defineTool({
667
+ return {
650
668
  name: AGENT_BROWSER_WEB_SEARCH_TOOL_NAME,
651
669
  label: "Agent Browser Web Search",
652
670
  description: `Search the web with Exa or Brave when configured. Returns up to ${MAX_SEARCH_RESULT_COUNT} concise web results.`,
@@ -659,7 +677,7 @@ export function createAgentBrowserWebSearchTool(configState: AgentBrowserConfigS
659
677
  "After using agent_browser_web_search, cite result URLs in the final answer when web evidence informed the answer.",
660
678
  ],
661
679
  parameters: AgentBrowserWebSearchParams,
662
- async execute(_toolCallId, params, signal) {
680
+ async execute(_toolCallId: string, params: AgentBrowserWebSearchParamsInput, signal?: AbortSignal) {
663
681
  if (!configState.webSearchEnabled) {
664
682
  throw new Error("agent_browser_web_search is disabled by pi-agent-browser-native config.");
665
683
  }
@@ -695,9 +713,9 @@ export function createAgentBrowserWebSearchTool(configState: AgentBrowserConfigS
695
713
  results: normalized.results,
696
714
  };
697
715
  return {
698
- content: [{ type: "text", text: formatSearchResults(adapter.provider, normalized.returnedQuery, normalized.results) }],
716
+ content: [{ type: "text" as const, text: formatSearchResults(adapter.provider, normalized.returnedQuery, normalized.results) }],
699
717
  details,
700
718
  };
701
719
  },
702
- });
720
+ };
703
721
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-agent-browser-native",
3
- "version": "0.2.45",
3
+ "version": "0.2.47",
4
4
  "description": "pi extension that exposes agent-browser as a native tool for browser automation",
5
5
  "type": "module",
6
6
  "author": "Mitch Fultz (https://github.com/fitchmultz)",
@@ -62,9 +62,9 @@
62
62
  "typebox": "*"
63
63
  },
64
64
  "devDependencies": {
65
- "@earendil-works/pi-ai": "^0.78.1",
66
- "@earendil-works/pi-coding-agent": "^0.78.1",
67
- "@earendil-works/pi-tui": "^0.78.1",
65
+ "@earendil-works/pi-ai": "^0.79.0",
66
+ "@earendil-works/pi-coding-agent": "^0.79.0",
67
+ "@earendil-works/pi-tui": "^0.79.0",
68
68
  "@types/node": "^25.6.1",
69
69
  "tsx": "^4.21.0",
70
70
  "typebox": "^1.1.38",
@@ -22,7 +22,7 @@ const PACKAGE_NAME = "pi-agent-browser-native";
22
22
  const REPO_URL_FRAGMENT = "github.com/fitchmultz/pi-agent-browser-native";
23
23
  const EXTENSION_ENTRYPOINT = "extensions/agent-browser/index.ts";
24
24
  const EXPECTED_VERSION = CAPABILITY_BASELINE.targetVersion;
25
- const RECOMMENDED_PI_VERSION = "0.78.1";
25
+ const RECOMMENDED_PI_VERSION = "0.79.0";
26
26
  const DEFAULT_AGENT_DIR = resolve(homedir(), ".pi/agent");
27
27
  const THIS_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
28
28
 
@@ -308,8 +308,8 @@ async function checkPiVersion({ runPi }) {
308
308
  status: "warn",
309
309
  title: `Pi ${RECOMMENDED_PI_VERSION} or newer is recommended; found ${version || "<empty>"}.`,
310
310
  lines: [
311
- "This package does not hard-pin Pi 0.78.1, but this release was audited against Pi 0.78.1 extension/package behavior.",
312
- "Update Pi before release validation or lifecycle debugging if you see tool routing, /reload, exact-session, or package-install differences.",
311
+ "This package does not hard-pin Pi 0.79.0, but this release was audited against Pi 0.79.0 extension/package behavior, including Project Trust.",
312
+ "Update Pi before release validation or lifecycle debugging if you see tool routing, /reload, exact-session, project trust, or package-install differences.",
313
313
  ],
314
314
  };
315
315
  }
@@ -392,7 +392,7 @@ async function checkPiSources({ cwd, agentDir, settingsPaths, readText, pathExis
392
392
  ...sources.map((source) => `- ${source.source} from ${source.location}`),
393
393
  "Keep exactly one active source:",
394
394
  "- for normal use: keep `pi install npm:pi-agent-browser-native` and remove/disable checkout paths from Pi settings",
395
- "- for temporary package or checkout trials: use `pi --no-extensions -e <source>` so configured sources are bypassed",
395
+ "- for temporary package or checkout trials: use `pi --approve --no-extensions -e <source>` when you intentionally trust the current project, or omit `--approve` to let Pi prompt in interactive mode",
396
396
  "- for configured-source lifecycle validation: keep exactly one checkout or package source, then launch plain `pi`",
397
397
  ],
398
398
  warnings,
@@ -71,7 +71,7 @@ $PiInstallStderr = Join-Path $PackDir "pi-install.stderr.txt"
71
71
  if ($PackedNodeInstallExit -eq 0) {
72
72
  Push-Location $PiProject
73
73
  $env:PI_OFFLINE = "1"
74
- & $PiCli install -l ".\node_modules\$PackageName" >$PiInstallStdout 2>$PiInstallStderr
74
+ & $PiCli install -l --approve ".\node_modules\$PackageName" >$PiInstallStdout 2>$PiInstallStderr
75
75
  $PiInstallExit = $LASTEXITCODE
76
76
  Remove-Item Env:\PI_OFFLINE -ErrorAction SilentlyContinue
77
77
  Pop-Location
@@ -87,7 +87,7 @@ $PiListStdout = Join-Path $PackDir "pi-list.stdout.txt"
87
87
  $PiListStderr = Join-Path $PackDir "pi-list.stderr.txt"
88
88
  Push-Location $PiProject
89
89
  $env:PI_OFFLINE = "1"
90
- & $PiCli list >$PiListStdout 2>$PiListStderr
90
+ & $PiCli list --approve >$PiListStdout 2>$PiListStderr
91
91
  $PiListExit = $LASTEXITCODE
92
92
  Remove-Item Env:\PI_OFFLINE -ErrorAction SilentlyContinue
93
93
  Pop-Location
@@ -93,6 +93,10 @@ function section(text, name) {
93
93
  return (endIndex === -1 ? text.slice(contentStart) : text.slice(contentStart, endIndex)).replace(/^\r?\n/, "").replace(/\r?\n$/, "");
94
94
  }
95
95
 
96
+ function escapeRegExp(text) {
97
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
98
+ }
99
+
96
100
  function marker(text, name) {
97
101
  return text.match(new RegExp(`^${name}=(.*)$`, "m"))?.[1]?.trim() ?? "";
98
102
  }
@@ -288,11 +292,11 @@ export function buildPlatformBuildCommand(targetName, packageName = "pi-agent-br
288
292
  lines.push(`echo "PLATFORM_PACKED_NODE_INSTALL_EXIT=$PACKED_NODE_INSTALL_EXIT"`);
289
293
  lines.push(`echo "--- PACKED_NODE_INSTALL_STDOUT START ---"; cat "$PACK_DIR/packed-node-install.stdout.txt" 2>/dev/null || true; echo "--- PACKED_NODE_INSTALL_STDOUT END ---"`);
290
294
  lines.push(`echo "--- PACKED_NODE_INSTALL_STDERR START ---"; cat "$PACK_DIR/packed-node-install.stderr.txt" 2>/dev/null || true; echo "--- PACKED_NODE_INSTALL_STDERR END ---"`);
291
- lines.push(`if [ "$PACKED_NODE_INSTALL_EXIT" -eq 0 ] && [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" install -l ./node_modules/${packageName} >"$PACK_DIR/pi-install.stdout.txt" 2>"$PACK_DIR/pi-install.stderr.txt"); PI_INSTALL_EXIT=$?; else echo "missing pi cli or packed install" >"$PACK_DIR/pi-install.stderr.txt"; PI_INSTALL_EXIT=1; fi`);
295
+ lines.push(`if [ "$PACKED_NODE_INSTALL_EXIT" -eq 0 ] && [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" install -l --approve ./node_modules/${packageName} >"$PACK_DIR/pi-install.stdout.txt" 2>"$PACK_DIR/pi-install.stderr.txt"); PI_INSTALL_EXIT=$?; else echo "missing pi cli or packed install" >"$PACK_DIR/pi-install.stderr.txt"; PI_INSTALL_EXIT=1; fi`);
292
296
  lines.push(`echo "PLATFORM_PI_INSTALL_EXIT=$PI_INSTALL_EXIT"`);
293
297
  lines.push(`echo "--- PI_INSTALL_STDOUT START ---"; cat "$PACK_DIR/pi-install.stdout.txt" 2>/dev/null || true; echo "--- PI_INSTALL_STDOUT END ---"`);
294
298
  lines.push(`echo "--- PI_INSTALL_STDERR START ---"; cat "$PACK_DIR/pi-install.stderr.txt" 2>/dev/null || true; echo "--- PI_INSTALL_STDERR END ---"`);
295
- lines.push(`if [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" list >"$PACK_DIR/pi-list.stdout.txt" 2>"$PACK_DIR/pi-list.stderr.txt"); PI_LIST_EXIT=$?; else echo "missing pi cli" >"$PACK_DIR/pi-list.stderr.txt"; PI_LIST_EXIT=1; fi`);
299
+ lines.push(`if [ -n "$PI_CLI" ]; then (cd "$PI_PROJECT" && PI_OFFLINE=1 "$PI_CLI" list --approve >"$PACK_DIR/pi-list.stdout.txt" 2>"$PACK_DIR/pi-list.stderr.txt"); PI_LIST_EXIT=$?; else echo "missing pi cli" >"$PACK_DIR/pi-list.stderr.txt"; PI_LIST_EXIT=1; fi`);
296
300
  lines.push(`echo "PLATFORM_PI_LIST_EXIT=$PI_LIST_EXIT"`);
297
301
  lines.push(`echo "--- PI_LIST_STDOUT START ---"; cat "$PACK_DIR/pi-list.stdout.txt" 2>/dev/null || true; echo "--- PI_LIST_STDOUT END ---"`);
298
302
  lines.push(`echo "--- PI_LIST_STDERR START ---"; cat "$PACK_DIR/pi-list.stderr.txt" 2>/dev/null || true; echo "--- PI_LIST_STDERR END ---"`);
@@ -459,7 +463,7 @@ async function runPlatformBuildSuite(config, targetName, suiteName, leaseSession
459
463
  { id: "npm-pack", fn: () => /PLATFORM_NPM_PACK_EXIT=0/.test(stdout) && marker(stdout, "PLATFORM_PACKED_TARBALL").length > 0 },
460
464
  { id: "packed-node-install", fn: () => /PLATFORM_PACKED_NODE_INSTALL_EXIT=0/.test(stdout) },
461
465
  { id: "pi-install-local-package", fn: () => /PLATFORM_PI_INSTALL_EXIT=0/.test(stdout) },
462
- { id: "pi-list-local-package", fn: () => /PLATFORM_PI_LIST_EXIT=0/.test(stdout) && listOutput.includes(config.packageName) },
466
+ { id: "pi-list-local-package", fn: () => /PLATFORM_PI_LIST_EXIT=0/.test(stdout) && new RegExp(`Project packages:[\\s\\S]*${escapeRegExp(config.packageName)}`).test(listOutput) },
463
467
  { id: "no-source-extension-shortcut", fn: () => !/\bpi\s+(?:-e|--extension)\s+\./.test(stdout) },
464
468
  { id: "no-secret-artifacts", fn: () => secretViolations.length === 0, error: secretViolations.join(", ") },
465
469
  ];
@@ -39,7 +39,7 @@ Targets:
39
39
  macos, ubuntu, windows-native
40
40
 
41
41
  Suites:
42
- platform-build npm ci, npm run verify -- platform-target, npm pack, packed pi install, pi list
42
+ platform-build npm ci, npm run verify -- platform-target, npm pack, packed pi install --approve, pi list --approve
43
43
  browser-dogfood-smoke model-free native agent_browser smoke with real agent-browser/browser
44
44
 
45
45
  Options: