pi-agent-browser-native 0.2.49 → 0.2.51
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 +20 -1
- package/README.md +6 -6
- package/dist/extensions/agent-browser/index.js +38 -15
- package/dist/extensions/agent-browser/lib/config-policy.js +12 -29
- package/dist/extensions/agent-browser/lib/config.js +1 -1
- package/dist/extensions/agent-browser/lib/process.js +6 -91
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +2 -6
- package/dist/extensions/agent-browser/lib/runtime.js +48 -9
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/COMMAND_REFERENCE.md +4 -4
- package/docs/RELEASE.md +1 -1
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +1 -1
- package/docs/TOOL_CONTRACT.md +1 -1
- package/package.json +4 -3
- package/scripts/config.mjs +6 -18
- package/scripts/prepare.mjs +68 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 0.2.51 - 2026-06-11
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Made the source-package `prepare` lifecycle install dev dependencies with scripts disabled when Pi's `npm install --omit=dev` package path omits the compiler and peer type packages, so GitHub/source installs can still build `dist/` from a clean clone without changing runtime dependency policy.
|
|
10
|
+
|
|
11
|
+
### Validation
|
|
12
|
+
|
|
13
|
+
- Reproduced the `pi install -l --approve https://github.com/fitchmultz/pi-agent-browser-native@v0.2.50` source-install failure, then verified production-dependency source builds, project-local GitHub install, project-local npm install, and release gates before publish.
|
|
14
|
+
|
|
15
|
+
## 0.2.50 - 2026-06-11
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Keep visual/model-facing secret redaction and the native-tool bash guard while allowing loaded config credential sources and parent environment variables to pass through to upstream/provider runtime paths.
|
|
20
|
+
- Allow trusted project-local package config to provide web-search credential sources and browser profile/executable prompt guidance instead of limiting those capabilities to global or override config.
|
|
21
|
+
|
|
22
|
+
### Validation
|
|
23
|
+
|
|
24
|
+
- Ran focused config/web-search/process/redaction/clipboard/extension tests, `npm run typecheck`, `npm run docs`, `npm run verify -- command-reference`, the default `npm run verify` gate, and the release gate through lifecycle, package Pi, and platform smoke validation.
|
|
6
25
|
|
|
7
26
|
## 0.2.49 - 2026-06-11
|
|
8
27
|
|
package/README.md
CHANGED
|
@@ -183,7 +183,7 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
|
|
|
183
183
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config show
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
The optional `agent_browser_web_search` companion tool is
|
|
186
|
+
The optional `agent_browser_web_search` companion tool is available when a usable Exa or Brave credential source is configured or resolvable from startup config or trusted session config. It is not an `agent_browser` input mode and does not launch a browser; agents may use it whenever current/live external web information helps, then use `agent_browser` when they need page interaction, screenshots, authenticated/profile content, or DOM inspection. If both keys are available, the default provider is Exa because its `/search` endpoint returns agent-friendly highlights and search modes; set `webSearch.preferredProvider` to `"brave"` when you prefer Brave Search.
|
|
187
187
|
|
|
188
188
|
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/). Most users can simply export `EXA_API_KEY` or `BRAVE_API_KEY` in the environment that launches `pi`; config is only needed when you want Pi-scoped secret references, a preferred provider, or to disable this built-in search tool.
|
|
189
189
|
|
|
@@ -227,14 +227,14 @@ cat > /tmp/pi-agent-browser-disable-web-search.json <<'JSON'
|
|
|
227
227
|
JSON
|
|
228
228
|
PI_AGENT_BROWSER_CONFIG=/tmp/pi-agent-browser-disable-web-search.json pi
|
|
229
229
|
|
|
230
|
-
# Store a plaintext key in
|
|
230
|
+
# Store a plaintext key in Pi-scoped user config; output stays redacted.
|
|
231
231
|
printf '%s' "$EXA_API_KEY" | npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-key --provider exa --stdin
|
|
232
232
|
|
|
233
|
-
# Store a
|
|
233
|
+
# Store a secret-manager command source. Add --project when you want the repo config to own the source.
|
|
234
234
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-command "op read 'op://Private/Brave Search/API Key'" --provider brave --global
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
Config merges in this order: global → project → `PI_AGENT_BROWSER_CONFIG` override. Under Pi 0.79+, the globally installed or CLI-loaded extension still loads project-local `.pi/config/pi-agent-browser-native/config.json`
|
|
237
|
+
Config merges in this order: global → project → `PI_AGENT_BROWSER_CONFIG` override. Under Pi 0.79+, the globally installed or CLI-loaded extension still loads project-local `.pi/config/pi-agent-browser-native/config.json` when Pi trust allows that project layer; it skips that project layer when Pi reports the project is untrusted or when Pi is launched with `--no-approve`. `webSearch.enabled` is evaluated after the loaded layers merge. Use `web-search disable --global` for a user default, `web-search disable --project` for one repo, and a `PI_AGENT_BROWSER_CONFIG` override with `{ "webSearch": { "enabled": false } }` when web search must stay off even if project config exists. Loaded config may use plaintext, custom environment aliases, interpolation literals, malformed-or-late-bound `$` values, and `!command` credential sources; the resolved secret is passed to the provider request while tool content, details, status output, and docs examples stay redacted. `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`.
|
|
238
238
|
|
|
239
239
|
For Exa, the tool defaults to `searchType: "auto"` with `contents.highlights: true`. Agents may pass `searchType` (`fast`, `instant`, `deep-lite`, `deep`, or `deep-reasoning`) only when the task needs that latency/depth tradeoff; structured output schemas are intentionally not exposed yet.
|
|
240
240
|
|
|
@@ -248,7 +248,7 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
|
|
|
248
248
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable set "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
-
This adds agent guidance for signed-in/account-specific tasks; current releases do not auto-inject `--profile` or `--executable-path` for every launch. Configure profile/executable guidance globally
|
|
251
|
+
This adds agent guidance for signed-in/account-specific tasks; current releases do not auto-inject `--profile` or `--executable-path` for every launch. Configure profile/executable guidance globally, in trusted project config, or through `PI_AGENT_BROWSER_CONFIG`. Ask the agent to run `agent_browser` with `args: ["profiles"]` and `args: ["doctor"]` when profile resolution fails. The upstream `profiles` command lists Chrome profiles from Chrome's user data directory; `Default` is not canonical on every machine. Use the displayed profile directory name, a full profile/user-data directory path when upstream accepts one, or a configured `browser.executablePath` plus `sessionMode: "fresh"` for a different Chromium-compatible browser.
|
|
252
252
|
|
|
253
253
|
## Common agent calls
|
|
254
254
|
|
|
@@ -575,7 +575,7 @@ For larger local handoffs or PR-ready confidence before expensive release/lifecy
|
|
|
575
575
|
npm run verify -- pre-pr
|
|
576
576
|
```
|
|
577
577
|
|
|
578
|
-
That mode composes the full default gate with `npm run verify -- package`, so package contents and forbidden archived/repo-only files are checked without launching Pi lifecycle, Crabbox, or live dogfood flows. Package, package-pi, lifecycle, platform-target, and startup-profile modes all build `dist/` first so clean checkouts do not validate stale or missing compiled output. GitHub/source installs run the package `prepare` script
|
|
578
|
+
That mode composes the full default gate with `npm run verify -- package`, so package contents and forbidden archived/repo-only files are checked without launching Pi lifecycle, Crabbox, or live dogfood flows. Package, package-pi, lifecycle, platform-target, and startup-profile modes all build `dist/` first so clean checkouts do not validate stale or missing compiled output. GitHub/source installs run the package `prepare` script; when Pi installs with `npm install --omit=dev`, that script installs the source-build dev dependencies with lifecycle scripts disabled before building the ignored `dist/` entrypoint that Pi loads.
|
|
579
579
|
|
|
580
580
|
The deterministic agent-efficiency benchmark’s **standalone JSON/Markdown accounting run** is not part of default or pre-PR `npm run verify` (only `npm run verify -- benchmark` or `npm run benchmark:agent-browser` invokes the script). The full unit suite still exercises `test/agent-browser.efficiency-benchmark.test.ts`. Use the script before and after agent-facing abstractions to prove call-count, output-size, stale-ref, artifact, failure-category coverage, success-rate, and elapsed-time effects before changing the wrapper UX:
|
|
581
581
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { dirname, join, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { Text } from "@earendil-works/pi-tui";
|
|
11
|
-
import { PROJECT_RULE_PROMPT, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
11
|
+
import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
12
12
|
import { SessionPageState } from "./lib/session-page-state.js";
|
|
13
13
|
import { createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, extractExplicitSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
|
|
14
14
|
import { isRecord } from "./lib/parsing.js";
|
|
@@ -452,10 +452,9 @@ function shouldIncludeProjectConfig(ctx, argv = process.argv) {
|
|
|
452
452
|
}
|
|
453
453
|
export default function agentBrowserExtension(pi) {
|
|
454
454
|
const ephemeralSessionSeed = createEphemeralSessionSeed();
|
|
455
|
-
const startupProjectConfigAllowed = shouldIncludeProjectConfig(undefined);
|
|
456
455
|
const agentBrowserConfig = loadAgentBrowserConfigSync({
|
|
457
456
|
cwd: process.cwd(),
|
|
458
|
-
includeProjectConfig:
|
|
457
|
+
includeProjectConfig: false,
|
|
459
458
|
});
|
|
460
459
|
const webSearchToolAvailable = canRegisterWebSearchTool(agentBrowserConfig);
|
|
461
460
|
const toolPromptGuidelines = buildToolPromptGuidelines({
|
|
@@ -466,6 +465,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
466
465
|
});
|
|
467
466
|
const implicitSessionIdleTimeoutMs = String(getImplicitSessionIdleTimeoutMs());
|
|
468
467
|
const implicitSessionCloseTimeoutMs = getImplicitSessionCloseTimeoutMs();
|
|
468
|
+
let webSearchToolRegistered = false;
|
|
469
469
|
let managedSessionActive = false;
|
|
470
470
|
let managedSessionBaseName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
|
|
471
471
|
let managedSessionName = managedSessionBaseName;
|
|
@@ -543,9 +543,26 @@ export default function agentBrowserExtension(pi) {
|
|
|
543
543
|
markBranchOwned: true,
|
|
544
544
|
});
|
|
545
545
|
};
|
|
546
|
+
const registerWebSearchToolIfAvailable = (configState) => {
|
|
547
|
+
if (webSearchToolRegistered || !canRegisterWebSearchTool(configState))
|
|
548
|
+
return;
|
|
549
|
+
pi.registerTool(createAgentBrowserWebSearchTool(configState, {
|
|
550
|
+
loadConfigState(ctx) {
|
|
551
|
+
return loadAgentBrowserConfigSync({
|
|
552
|
+
cwd: ctx.cwd,
|
|
553
|
+
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
554
|
+
});
|
|
555
|
+
},
|
|
556
|
+
}));
|
|
557
|
+
webSearchToolRegistered = true;
|
|
558
|
+
};
|
|
546
559
|
pi.on("session_start", async (_event, ctx) => {
|
|
547
560
|
restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
548
561
|
electronChildProcesses = new Map();
|
|
562
|
+
registerWebSearchToolIfAvailable(loadAgentBrowserConfigSync({
|
|
563
|
+
cwd: ctx.cwd,
|
|
564
|
+
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
565
|
+
}));
|
|
549
566
|
});
|
|
550
567
|
pi.on("session_tree", async (_event, ctx) => {
|
|
551
568
|
await managedSessionExecutionQueue.run(async () => {
|
|
@@ -594,12 +611,27 @@ export default function agentBrowserExtension(pi) {
|
|
|
594
611
|
ownedManagedSessions.clear();
|
|
595
612
|
await cleanupSecureTempArtifacts({ preservePaths: preservedElectronProfileDirs });
|
|
596
613
|
});
|
|
597
|
-
pi.on("before_agent_start", async (event) => {
|
|
614
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
598
615
|
if (!shouldAppendBrowserSystemPrompt(event.prompt)) {
|
|
599
616
|
return undefined;
|
|
600
617
|
}
|
|
618
|
+
const runtimeConfig = loadAgentBrowserConfigSync({
|
|
619
|
+
cwd: ctx.cwd,
|
|
620
|
+
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
621
|
+
});
|
|
622
|
+
const browserGuidance = [
|
|
623
|
+
runtimeConfig.browserExecutablePathScope === "project"
|
|
624
|
+
? buildBrowserExecutablePathGuideline(runtimeConfig.browserExecutablePath)
|
|
625
|
+
: undefined,
|
|
626
|
+
runtimeConfig.browserDefaultProfileScope === "project"
|
|
627
|
+
? buildBrowserDefaultProfileGuideline(runtimeConfig.browserDefaultProfile)
|
|
628
|
+
: undefined,
|
|
629
|
+
].filter((line) => typeof line === "string" && line.length > 0);
|
|
630
|
+
const runtimeConfigPrompt = browserGuidance.length > 0
|
|
631
|
+
? `\n\nProject agent_browser config guidance:\n${browserGuidance.map((line) => `- ${line}`).join("\n")}`
|
|
632
|
+
: "";
|
|
601
633
|
return {
|
|
602
|
-
systemPrompt: `${event.systemPrompt}\n\n${PROJECT_RULE_PROMPT}`,
|
|
634
|
+
systemPrompt: `${event.systemPrompt}\n\n${PROJECT_RULE_PROMPT}${runtimeConfigPrompt}`,
|
|
603
635
|
};
|
|
604
636
|
});
|
|
605
637
|
pi.on("tool_call", async (event, ctx) => {
|
|
@@ -772,14 +804,5 @@ export default function agentBrowserExtension(pi) {
|
|
|
772
804
|
: runBrowserCommand();
|
|
773
805
|
},
|
|
774
806
|
});
|
|
775
|
-
|
|
776
|
-
pi.registerTool(createAgentBrowserWebSearchTool(agentBrowserConfig, {
|
|
777
|
-
loadConfigState(ctx) {
|
|
778
|
-
return loadAgentBrowserConfigSync({
|
|
779
|
-
cwd: ctx.cwd,
|
|
780
|
-
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
781
|
-
});
|
|
782
|
-
},
|
|
783
|
-
}));
|
|
784
|
-
}
|
|
807
|
+
registerWebSearchToolIfAvailable(agentBrowserConfig);
|
|
785
808
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
/**
|
|
3
3
|
* Purpose: Canonical pi-agent-browser-native config policy shared by runtime and setup CLI.
|
|
4
|
-
* Responsibilities: Own config paths, provider descriptors,
|
|
4
|
+
* Responsibilities: Own config paths, provider descriptors, credential source parsing, layer validation/merge, status projection, and redacted summaries.
|
|
5
5
|
* Scope: Pure configuration policy plus synchronous status loading; secret command execution and browser/web-search runtime calls live elsewhere.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -22,7 +22,7 @@ import { join, resolve } from "node:path";
|
|
|
22
22
|
/** @typedef {{ kind: CredentialSourceKind; provider?: WebSearchProvider; rawValue: string; scope: AgentBrowserConfigScope }} CredentialSource */
|
|
23
23
|
/** @typedef {{ global: string; project: string; override?: string }} AgentBrowserConfigPaths */
|
|
24
24
|
/** @typedef {{ cwd?: string; env?: NodeJS.ProcessEnv; includeProjectConfig?: boolean }} AgentBrowserConfigLoadOptions */
|
|
25
|
-
/** @typedef {{ browserDefaultProfile?: Required<BrowserDefaultProfileConfig>; browserDefaultProfileScope?: ConfigLayerScope; browserExecutablePath?: string; browserExecutablePathScope?: ConfigLayerScope; trustedBrowserDefaultProfile?: Required<BrowserDefaultProfileConfig>; trustedBrowserDefaultProfileScope?:
|
|
25
|
+
/** @typedef {{ browserDefaultProfile?: Required<BrowserDefaultProfileConfig>; browserDefaultProfileScope?: ConfigLayerScope; browserExecutablePath?: string; browserExecutablePathScope?: ConfigLayerScope; trustedBrowserDefaultProfile?: Required<BrowserDefaultProfileConfig>; trustedBrowserDefaultProfileScope?: ConfigLayerScope; trustedBrowserExecutablePath?: string; trustedBrowserExecutablePathScope?: ConfigLayerScope; config: AgentBrowserConfig; webSearchCredentialSources: Partial<Record<WebSearchProvider, CredentialSource>>; webSearchEnabled: boolean; webSearchPreferredProvider: WebSearchProvider; errors: string[]; layers: ConfigLayer[]; paths: AgentBrowserConfigPaths; projectConfigIncluded: boolean; warnings: string[] }} AgentBrowserConfigState */
|
|
26
26
|
/** @typedef {{ scope: string; path: string; exists: boolean }} ConfigFileSummary */
|
|
27
27
|
export const AGENT_BROWSER_CONFIG_ENV = "PI_AGENT_BROWSER_CONFIG";
|
|
28
28
|
export const BRAVE_API_KEY_ENV = "BRAVE_API_KEY";
|
|
@@ -237,9 +237,8 @@ export function isPlaintextCredentialValue(rawValue) {
|
|
|
237
237
|
* @param {WebSearchProvider} provider
|
|
238
238
|
*/
|
|
239
239
|
export function isProjectSafeCredentialValueForProvider(rawValue, provider) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return trimmed === `$${envName}` || trimmed === `\${${envName}}`;
|
|
240
|
+
void provider;
|
|
241
|
+
return rawValue.trim().length > 0;
|
|
243
242
|
}
|
|
244
243
|
/**
|
|
245
244
|
* @param {unknown} value
|
|
@@ -277,9 +276,6 @@ export function validateAgentBrowserConfig(value, path, scope, errors, warnings)
|
|
|
277
276
|
const apiKey = validateString(value.webSearch[descriptor.configKey], `${path}.webSearch.${descriptor.configKey}`, errors);
|
|
278
277
|
if (apiKey !== undefined) {
|
|
279
278
|
webSearch[descriptor.configKey] = apiKey;
|
|
280
|
-
if (scope === "project" && !isProjectSafeCredentialValueForProvider(apiKey, provider)) {
|
|
281
|
-
errors.push(`${path}.webSearch.${descriptor.configKey} must be exactly $${descriptor.apiKeyEnv} or \${${descriptor.apiKeyEnv}} in project-local config; plaintext, custom env aliases, interpolation literals, malformed env references, and command-backed project secrets are not allowed.`);
|
|
282
|
-
}
|
|
283
279
|
}
|
|
284
280
|
}
|
|
285
281
|
if (Object.keys(webSearch).length > 0)
|
|
@@ -295,16 +291,10 @@ export function validateAgentBrowserConfig(value, path, scope, errors, warnings)
|
|
|
295
291
|
const defaultProfile = validateBrowserDefaultProfile(value.browser.defaultProfile, `${path}.browser.defaultProfile`, errors);
|
|
296
292
|
if (defaultProfile) {
|
|
297
293
|
config.browser.defaultProfile = defaultProfile;
|
|
298
|
-
if (scope === "project" && defaultProfile.policy !== "explicit-only") {
|
|
299
|
-
warnings.push(`${path}.browser.defaultProfile is project-local; authenticated/always profile prompt guidance is emitted only from global or override config.`);
|
|
300
|
-
}
|
|
301
294
|
}
|
|
302
295
|
const executablePath = validateString(value.browser.executablePath, `${path}.browser.executablePath`, errors)?.trim();
|
|
303
296
|
if (executablePath) {
|
|
304
297
|
config.browser.executablePath = executablePath;
|
|
305
|
-
if (scope === "project") {
|
|
306
|
-
warnings.push(`${path}.browser.executablePath is project-local; executable launch prompt guidance is emitted only from global or override config.`);
|
|
307
|
-
}
|
|
308
298
|
}
|
|
309
299
|
const defaultLaunchArgs = validateStringArray(value.browser.defaultLaunchArgs, `${path}.browser.defaultLaunchArgs`, errors);
|
|
310
300
|
if (defaultLaunchArgs) {
|
|
@@ -397,31 +387,31 @@ function getBrowserExecutablePathScope(layers) {
|
|
|
397
387
|
}
|
|
398
388
|
/**
|
|
399
389
|
* @param {ConfigLayer[]} layers
|
|
400
|
-
* @returns {{ profile: Required<BrowserDefaultProfileConfig>; scope:
|
|
390
|
+
* @returns {{ profile: Required<BrowserDefaultProfileConfig>; scope: ConfigLayerScope } | undefined}
|
|
401
391
|
*/
|
|
402
392
|
function getTrustedBrowserDefaultProfile(layers) {
|
|
403
393
|
for (let index = layers.length - 1; index >= 0; index -= 1) {
|
|
404
394
|
const layer = layers[index];
|
|
405
|
-
if (!layer
|
|
395
|
+
if (!layer)
|
|
406
396
|
continue;
|
|
407
397
|
const profile = getBrowserDefaultProfile(layer.config);
|
|
408
398
|
if (profile)
|
|
409
|
-
return { profile, scope:
|
|
399
|
+
return { profile, scope: layer.scope };
|
|
410
400
|
}
|
|
411
401
|
return undefined;
|
|
412
402
|
}
|
|
413
403
|
/**
|
|
414
404
|
* @param {ConfigLayer[]} layers
|
|
415
|
-
* @returns {{ executablePath: string; scope:
|
|
405
|
+
* @returns {{ executablePath: string; scope: ConfigLayerScope } | undefined}
|
|
416
406
|
*/
|
|
417
407
|
function getTrustedBrowserExecutablePath(layers) {
|
|
418
408
|
for (let index = layers.length - 1; index >= 0; index -= 1) {
|
|
419
409
|
const layer = layers[index];
|
|
420
|
-
if (!layer
|
|
410
|
+
if (!layer)
|
|
421
411
|
continue;
|
|
422
412
|
const executablePath = getBrowserExecutablePath(layer.config);
|
|
423
413
|
if (executablePath)
|
|
424
|
-
return { executablePath, scope:
|
|
414
|
+
return { executablePath, scope: layer.scope };
|
|
425
415
|
}
|
|
426
416
|
return undefined;
|
|
427
417
|
}
|
|
@@ -655,11 +645,7 @@ export function formatBrowserProfileStatus(state) {
|
|
|
655
645
|
if (!profile)
|
|
656
646
|
return "not configured";
|
|
657
647
|
const scope = state.browserDefaultProfileScope ?? "unknown";
|
|
658
|
-
|
|
659
|
-
if (scope !== "project")
|
|
660
|
-
return base;
|
|
661
|
-
const trustedText = state.trustedBrowserDefaultProfile ? `; trusted guidance: ${state.trustedBrowserDefaultProfile.name} (${state.trustedBrowserDefaultProfileScope})` : "";
|
|
662
|
-
return `${base}; ignored for prompt guidance${trustedText}`;
|
|
648
|
+
return `${profile.name} (policy: ${profile.policy}; ${scope})`;
|
|
663
649
|
}
|
|
664
650
|
/** @param {AgentBrowserConfigState} state */
|
|
665
651
|
export function formatBrowserExecutableStatus(state) {
|
|
@@ -667,10 +653,7 @@ export function formatBrowserExecutableStatus(state) {
|
|
|
667
653
|
if (!executablePath)
|
|
668
654
|
return "not configured";
|
|
669
655
|
const scope = state.browserExecutablePathScope ?? "unknown";
|
|
670
|
-
|
|
671
|
-
return `${executablePath} (${scope})`;
|
|
672
|
-
const trustedText = state.trustedBrowserExecutablePath ? `; trusted guidance: ${state.trustedBrowserExecutablePath} (${state.trustedBrowserExecutablePathScope})` : "";
|
|
673
|
-
return `${executablePath} (${scope}; ignored for prompt guidance${trustedText})`;
|
|
656
|
+
return `${executablePath} (${scope})`;
|
|
674
657
|
}
|
|
675
658
|
/**
|
|
676
659
|
* @param {AgentBrowserConfigState} state
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Purpose: Load pi-agent-browser-native package configuration from Pi-scoped global, project, or explicit paths.
|
|
3
3
|
* Responsibilities: Resolve config layers, resolve secrets without exposing values, and provide redacted status for tools/CLIs.
|
|
4
4
|
* Scope: Package-owned configuration only; canonical config policy lives in config-policy.js, browser command execution and web-search API calls live in focused modules.
|
|
5
|
-
* Invariants/Assumptions:
|
|
5
|
+
* Invariants/Assumptions: Credential sources from loaded config are passed through to the runtime; command credentials are resolved lazily at execution time and displayed values stay redacted.
|
|
6
6
|
*/
|
|
7
7
|
import { exec as execCallback } from "node:child_process";
|
|
8
8
|
import { readFile } from "node:fs/promises";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Purpose: Execute the upstream agent-browser binary for the pi-agent-browser extension.
|
|
3
|
-
* Responsibilities: Spawn the agent-browser subprocess, forward
|
|
3
|
+
* Responsibilities: Spawn the agent-browser subprocess, forward parent environment variables plus wrapper overrides, stream optional stdin, bound in-memory output buffering, spill oversized stdout safely to a private temp file under a disk budget, and honor abort signals.
|
|
4
4
|
* Scope: Process execution only; argument planning, output formatting, and pi tool registration live elsewhere.
|
|
5
5
|
* Usage: Called by the extension tool after argument validation and session planning are complete.
|
|
6
6
|
* Invariants/Assumptions: The binary name is always `agent-browser`; Windows routes through PowerShell to invoke npm launchers with escaped argv; callers handle semantic success/error interpretation.
|
|
@@ -22,72 +22,6 @@ export const SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS = 25_000;
|
|
|
22
22
|
const DEFAULT_AGENT_BROWSER_PROCESS_TIMEOUT_MS = 35_000;
|
|
23
23
|
/** Grace period after `exit` before resolving when `close` is delayed by inherited stdio handles. */
|
|
24
24
|
const EXIT_STDIO_GRACE_MS = 100;
|
|
25
|
-
const httpProxyEnvName = "http_proxy";
|
|
26
|
-
const httpsProxyEnvName = "https_proxy";
|
|
27
|
-
const allProxyEnvName = "all_proxy";
|
|
28
|
-
const noProxyEnvName = "no_proxy";
|
|
29
|
-
const INHERITED_ENV_NAMES = new Set([
|
|
30
|
-
"ALL_PROXY",
|
|
31
|
-
"APPDATA",
|
|
32
|
-
"CI",
|
|
33
|
-
"COLORTERM",
|
|
34
|
-
"COMSPEC",
|
|
35
|
-
"DBUS_SESSION_BUS_ADDRESS",
|
|
36
|
-
"DISPLAY",
|
|
37
|
-
"FORCE_COLOR",
|
|
38
|
-
"HOME",
|
|
39
|
-
"HOMEDRIVE",
|
|
40
|
-
"HOMEPATH",
|
|
41
|
-
"HTTPS_PROXY",
|
|
42
|
-
"HTTP_PROXY",
|
|
43
|
-
"LANG",
|
|
44
|
-
"LC_ALL",
|
|
45
|
-
"LC_CTYPE",
|
|
46
|
-
"LOCALAPPDATA",
|
|
47
|
-
"LOGNAME",
|
|
48
|
-
"NO_COLOR",
|
|
49
|
-
"NO_PROXY",
|
|
50
|
-
"NODE_EXTRA_CA_CERTS",
|
|
51
|
-
"NODE_TLS_REJECT_UNAUTHORIZED",
|
|
52
|
-
"OS",
|
|
53
|
-
"PATH",
|
|
54
|
-
"PATHEXT",
|
|
55
|
-
"PWD",
|
|
56
|
-
"SHELL",
|
|
57
|
-
"SSL_CERT_DIR",
|
|
58
|
-
"SSL_CERT_FILE",
|
|
59
|
-
"SYSTEMROOT",
|
|
60
|
-
"TEMP",
|
|
61
|
-
"TERM",
|
|
62
|
-
"TMP",
|
|
63
|
-
"TMPDIR",
|
|
64
|
-
"TZ",
|
|
65
|
-
"USER",
|
|
66
|
-
"USERNAME",
|
|
67
|
-
"USERPROFILE",
|
|
68
|
-
"WAYLAND_DISPLAY",
|
|
69
|
-
"XAUTHORITY",
|
|
70
|
-
"AWS_ACCESS_KEY_ID",
|
|
71
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
72
|
-
"AWS_SESSION_TOKEN",
|
|
73
|
-
"AWS_PROFILE",
|
|
74
|
-
"AWS_REGION",
|
|
75
|
-
"AWS_DEFAULT_REGION",
|
|
76
|
-
httpProxyEnvName,
|
|
77
|
-
httpsProxyEnvName,
|
|
78
|
-
allProxyEnvName,
|
|
79
|
-
noProxyEnvName,
|
|
80
|
-
]);
|
|
81
|
-
const INHERITED_ENV_PREFIXES = [
|
|
82
|
-
"AGENT_BROWSER_",
|
|
83
|
-
"AGENTCORE_",
|
|
84
|
-
"AI_GATEWAY_",
|
|
85
|
-
"BROWSERBASE_",
|
|
86
|
-
"BROWSERLESS_",
|
|
87
|
-
"BROWSER_USE_",
|
|
88
|
-
"KERNEL_",
|
|
89
|
-
"XDG_",
|
|
90
|
-
];
|
|
91
25
|
function appendTail(text, addition, maxChars) {
|
|
92
26
|
const combined = text + addition;
|
|
93
27
|
return combined.length <= maxChars ? combined : combined.slice(combined.length - maxChars);
|
|
@@ -230,37 +164,18 @@ async function ensureAgentBrowserSocketDir(socketDir) {
|
|
|
230
164
|
return false;
|
|
231
165
|
}
|
|
232
166
|
}
|
|
233
|
-
function getChildEnvName(name) {
|
|
234
|
-
if (processPlatform === "win32") {
|
|
235
|
-
const upperName = name.toUpperCase();
|
|
236
|
-
if (INHERITED_ENV_NAMES.has(upperName))
|
|
237
|
-
return upperName;
|
|
238
|
-
return INHERITED_ENV_PREFIXES.some((prefix) => upperName.startsWith(prefix)) ? upperName : undefined;
|
|
239
|
-
}
|
|
240
|
-
if (INHERITED_ENV_NAMES.has(name) || INHERITED_ENV_PREFIXES.some((prefix) => name.startsWith(prefix))) {
|
|
241
|
-
return name;
|
|
242
|
-
}
|
|
243
|
-
return undefined;
|
|
244
|
-
}
|
|
245
167
|
export function buildAgentBrowserProcessEnv(baseEnv = processEnv, overrides = undefined) {
|
|
246
168
|
const childEnv = {};
|
|
247
169
|
for (const [name, value] of Object.entries(baseEnv)) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
childEnv[childName] = value;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (!overrides) {
|
|
254
|
-
clampUpstreamDefaultTimeout(childEnv);
|
|
255
|
-
return childEnv;
|
|
170
|
+
if (value !== undefined)
|
|
171
|
+
childEnv[name] = value;
|
|
256
172
|
}
|
|
257
|
-
for (const [name, value] of Object.entries(overrides)) {
|
|
258
|
-
const childName = getChildEnvName(name) ?? name;
|
|
173
|
+
for (const [name, value] of Object.entries(overrides ?? {})) {
|
|
259
174
|
if (value === undefined) {
|
|
260
|
-
delete childEnv[
|
|
175
|
+
delete childEnv[name];
|
|
261
176
|
}
|
|
262
177
|
else {
|
|
263
|
-
childEnv[
|
|
178
|
+
childEnv[name] = value;
|
|
264
179
|
}
|
|
265
180
|
}
|
|
266
181
|
clampUpstreamDefaultTimeout(childEnv);
|
|
@@ -27,7 +27,7 @@ export function redactModelFacingText(text) {
|
|
|
27
27
|
return redactSensitiveText(text);
|
|
28
28
|
}
|
|
29
29
|
export function redactModelFacingTextIfSensitive(text) {
|
|
30
|
-
return /(?:@|\b(?:api[_-]?key|auth|authorization|basic|bearer|cookie|pass(?:word)?|secret|session[_-]?id|token)\b)/i.test(text)
|
|
30
|
+
return /(?:@|\b(?:access[_-]?key|api[_-]?key|auth|authorization|basic|bearer|connection[_-]?string|cookie|database[_-]?url|db[_-]?url|mongo(?:db)?[_-]?uri|pass(?:word)?|private[_-]?key|redis[_-]?url|secret|session[_-]?id|token)\b)/i.test(text)
|
|
31
31
|
? redactModelFacingText(text)
|
|
32
32
|
: text;
|
|
33
33
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Scope: Diagnostic/result-state command presentation only; core orchestration stays in presentation.ts.
|
|
5
5
|
*/
|
|
6
6
|
import { isRecord } from "../../parsing.js";
|
|
7
|
-
import { redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
7
|
+
import { isSensitiveFieldName, redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
8
8
|
import { classifyNetworkRequestFailure, isApiLikeNetworkRequest, isNetworkArtifactNoiseRequest, summarizeNetworkFailures } from "../network.js";
|
|
9
9
|
import { withOptionalSessionArgs } from "../next-actions.js";
|
|
10
10
|
import { stringifyUnknown, truncateText } from "../text.js";
|
|
@@ -39,7 +39,6 @@ const NETWORK_FILTER_SENSITIVE_SEGMENT_TERMS = [
|
|
|
39
39
|
"session",
|
|
40
40
|
"token",
|
|
41
41
|
];
|
|
42
|
-
const SENSITIVE_PRESENTATION_FIELD_PATTERN = /^(?:access(?:_|-)?token|api(?:_|-)?key|auth(?:orization)?|bearer|client(?:_|-)?secret|cookie|id(?:_|-)?token|pass(?:word)?|proxy(?:_|-)?authorization|refresh(?:_|-)?token|secret|session(?:_|-)?id|set(?:_|-)?cookie|sig(?:nature)?|token|x(?:_|-)?api(?:_|-)?key)$/i;
|
|
43
42
|
const NETWORK_FILTER_OPAQUE_SEGMENT_PATTERN = /^(?:[A-Fa-f0-9]{16,}|(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9_-]{16,})$/;
|
|
44
43
|
const NETWORK_PREVIEW_FIELD_CANDIDATES = {
|
|
45
44
|
request: ["postData"],
|
|
@@ -873,9 +872,6 @@ function formatStateText(data) {
|
|
|
873
872
|
return "State cleared.";
|
|
874
873
|
return undefined;
|
|
875
874
|
}
|
|
876
|
-
function isSensitivePresentationField(key) {
|
|
877
|
-
return SENSITIVE_PRESENTATION_FIELD_PATTERN.test(key);
|
|
878
|
-
}
|
|
879
875
|
function redactStructuredPresentationValue(value) {
|
|
880
876
|
if (typeof value === "string")
|
|
881
877
|
return redactModelFacingTextIfSensitive(value);
|
|
@@ -885,7 +881,7 @@ function redactStructuredPresentationValue(value) {
|
|
|
885
881
|
return value;
|
|
886
882
|
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [
|
|
887
883
|
key,
|
|
888
|
-
|
|
884
|
+
isSensitiveFieldName(key) ? "[REDACTED]" : redactStructuredPresentationValue(entryValue),
|
|
889
885
|
]));
|
|
890
886
|
}
|
|
891
887
|
function redactStatefulValues(value, sensitiveKeys) {
|
|
@@ -25,7 +25,8 @@ const DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS = 5_000;
|
|
|
25
25
|
const INSPECTION_FLAGS = new Set(["--help", "-h", "--version", "-V"]);
|
|
26
26
|
const SENSITIVE_VALUE_FLAGS = new Set(["--body", "--headers", "--password", "--proxy"]);
|
|
27
27
|
const SENSITIVE_QUERY_PARAM_PATTERN = /^(?:access(?:_|-)?token|api(?:_|-)?key|auth|authorization|bearer|client(?:_|-)?secret|code|cookie|id(?:_|-)?token|key|pass(?:word)?|refresh(?:_|-)?token|secret|sentry(?:_|-)?key|session(?:_|-)?id|sig(?:nature)?|token|write(?:_|-)?key)$/i;
|
|
28
|
-
const SENSITIVE_FIELD_NAME_PATTERN = /^(?:access(?:_
|
|
28
|
+
const SENSITIVE_FIELD_NAME_PATTERN = /^(?:[A-Za-z0-9_-]*(?:api[_-]?key|access[_-]?key|private[_-]?key|secret(?:[_-]?(?:key|access[_-]?key))?|token|password|passwd|credentials?|database[_-]?url|db[_-]?url|connection[_-]?string|mongo(?:db)?[_-]?uri|redis[_-]?url)|[A-Za-z0-9]*(?:apiKey|ApiKey|apikey|privateKey|PrivateKey|databaseUrl|DatabaseUrl|dbUrl|DbUrl|connectionString|ConnectionString|mongoUri|MongoUri|mongodbUri|MongodbUri|mongoDbUri|MongoDbUri|redisUrl|RedisUrl|Token|Secret|Password|Credential|Credentials)|auth(?:orization)?|bearer|client(?:_|-)?secret|cookie|id(?:_|-)?token|pass(?:word)?|proxy(?:_|-)?authorization|refresh(?:_|-)?token|sentry(?:_|-)?key|session(?:_|-)?id|set(?:_|-)?cookie|sig(?:nature)?|write(?:_|-)?key|x(?:_|-)?api(?:_|-)?key)$/i;
|
|
29
|
+
const ENV_SECRET_ASSIGNMENT_PATTERN = /\b((?:export\s+)?([A-Za-z_][A-Za-z0-9_-]*)(\s*[:=]\s*))(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;]+)/g;
|
|
29
30
|
const DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM = {
|
|
30
31
|
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
31
32
|
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
@@ -40,7 +41,7 @@ function isStringArray(value) {
|
|
|
40
41
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
41
42
|
}
|
|
42
43
|
function shouldRedactQueryParam(name) {
|
|
43
|
-
return SENSITIVE_QUERY_PARAM_PATTERN.test(name);
|
|
44
|
+
return SENSITIVE_QUERY_PARAM_PATTERN.test(name) || isSensitiveFieldName(name);
|
|
44
45
|
}
|
|
45
46
|
function redactUrlToken(token) {
|
|
46
47
|
let parsed;
|
|
@@ -50,9 +51,6 @@ function redactUrlToken(token) {
|
|
|
50
51
|
catch {
|
|
51
52
|
return token;
|
|
52
53
|
}
|
|
53
|
-
if (!["http:", "https:", "ws:", "wss:"].includes(parsed.protocol)) {
|
|
54
|
-
return token;
|
|
55
|
-
}
|
|
56
54
|
let mutated = false;
|
|
57
55
|
if (parsed.username.length > 0) {
|
|
58
56
|
parsed.username = "[REDACTED]";
|
|
@@ -83,8 +81,31 @@ function redactUrlToken(token) {
|
|
|
83
81
|
}
|
|
84
82
|
return parsed.toString();
|
|
85
83
|
}
|
|
84
|
+
function redactLooseUrlParameterText(text) {
|
|
85
|
+
return text.replace(/([?#&])([^=&#\s]+)=([^&#\s]*)/g, (match, separator, rawName) => {
|
|
86
|
+
let name = rawName;
|
|
87
|
+
try {
|
|
88
|
+
name = decodeURIComponent(rawName.replace(/\+/g, " "));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Keep the raw name when percent decoding fails.
|
|
92
|
+
}
|
|
93
|
+
if (!shouldRedactQueryParam(name))
|
|
94
|
+
return match;
|
|
95
|
+
return `${separator}${rawName}=[REDACTED]`;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function redactLooseUrlUserinfo(text) {
|
|
99
|
+
return text.replace(/\b([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^\s"'`/@]+)@([^\s"'`]+)/g, (match, prefix, userinfo, suffix) => {
|
|
100
|
+
if (/%5Bredacted%5D/i.test(userinfo))
|
|
101
|
+
return match;
|
|
102
|
+
if (userinfo.includes("[REDACTED]"))
|
|
103
|
+
return redactLooseUrlParameterText(match);
|
|
104
|
+
return redactLooseUrlParameterText(`${prefix}${userinfo.includes(":") ? "[REDACTED]:[REDACTED]" : "[REDACTED]"}@${suffix}`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
86
107
|
function redactLooseUrlMatches(text) {
|
|
87
|
-
return text.replace(/\b
|
|
108
|
+
return text.replace(/\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s"'`<>\])]+/g, (match) => redactUrlToken(match));
|
|
88
109
|
}
|
|
89
110
|
function findBalancedJsonEnd(text, startIndex) {
|
|
90
111
|
const opener = text[startIndex];
|
|
@@ -188,10 +209,28 @@ function redactBearerCredentials(text) {
|
|
|
188
209
|
return formatRedactedCredential(label, credential, trailing);
|
|
189
210
|
});
|
|
190
211
|
}
|
|
212
|
+
export function isSensitiveFieldName(key) {
|
|
213
|
+
SENSITIVE_FIELD_NAME_PATTERN.lastIndex = 0;
|
|
214
|
+
return SENSITIVE_FIELD_NAME_PATTERN.test(key);
|
|
215
|
+
}
|
|
216
|
+
function isEnvSecretAssignmentKey(key) {
|
|
217
|
+
if (!isSensitiveFieldName(key))
|
|
218
|
+
return false;
|
|
219
|
+
if (key.includes("_") || key.includes("-") || key === key.toUpperCase())
|
|
220
|
+
return true;
|
|
221
|
+
return /(?:apiKey|ApiKey|privateKey|PrivateKey|databaseUrl|DatabaseUrl|dbUrl|DbUrl|connectionString|ConnectionString|mongoUri|MongoUri|mongodbUri|MongodbUri|mongoDbUri|MongoDbUri|redisUrl|RedisUrl|Token|Secret|Password|Credential|Credentials)$/.test(key);
|
|
222
|
+
}
|
|
223
|
+
function redactEnvSecretAssignments(text) {
|
|
224
|
+
return text.replace(ENV_SECRET_ASSIGNMENT_PATTERN, (match, prefix, key) => {
|
|
225
|
+
if (!isEnvSecretAssignmentKey(key))
|
|
226
|
+
return match;
|
|
227
|
+
return `${prefix}[REDACTED]`;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
191
230
|
export function redactSensitiveText(text) {
|
|
192
|
-
return redactEmbeddedStructuredText(redactStandaloneBasicCredential(redactBearerCredentials(redactLooseUrlMatches(text))
|
|
231
|
+
return redactEmbeddedStructuredText(redactEnvSecretAssignments(redactStandaloneBasicCredential(redactBearerCredentials(redactLooseUrlUserinfo(redactLooseUrlMatches(text)))
|
|
193
232
|
.replace(/\b(Authorization\s*:\s*Basic)\s+[^\s",]+/gi, "$1 [REDACTED]")
|
|
194
|
-
.replace(/\b(Cookie|Set-Cookie)\s*:\s*[^\n\r"]+/gi, "$1: [REDACTED]")));
|
|
233
|
+
.replace(/\b(Cookie|Set-Cookie)\s*:\s*[^\n\r"]+/gi, "$1: [REDACTED]"))));
|
|
195
234
|
}
|
|
196
235
|
export function redactSensitiveValue(value) {
|
|
197
236
|
if (typeof value === "string") {
|
|
@@ -204,7 +243,7 @@ export function redactSensitiveValue(value) {
|
|
|
204
243
|
return value;
|
|
205
244
|
}
|
|
206
245
|
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
|
|
207
|
-
if (
|
|
246
|
+
if (isSensitiveFieldName(key)) {
|
|
208
247
|
return [key, "[REDACTED]"];
|
|
209
248
|
}
|
|
210
249
|
return [key, redactSensitiveValue(entryValue)];
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -23,7 +23,7 @@ V1 exposes one native browser tool:
|
|
|
23
23
|
|
|
24
24
|
It may also expose one optional companion tool:
|
|
25
25
|
|
|
26
|
-
- `agent_browser_web_search`,
|
|
26
|
+
- `agent_browser_web_search`, available when an Exa or Brave Search credential source is configured or resolvable from startup config or trusted session config and runtime `webSearch.enabled` is not false
|
|
27
27
|
|
|
28
28
|
Why:
|
|
29
29
|
- keeps browser automation centered on `agent_browser`
|
|
@@ -68,11 +68,11 @@ 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,
|
|
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, credential source parsing, 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 when Pi reports the project is untrusted or when 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 from any loaded layer: literal values, `$ENV_VAR` / `${ENV_VAR}` interpolation, escapes (`$$`, `$!`), and leading `!command` resolved at request time. `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; prompt guidance is emitted from the highest-priority loaded layer, including project config when Pi trust/loading allows it.
|
|
72
72
|
|
|
73
|
-
`agent_browser_web_search`
|
|
73
|
+
`agent_browser_web_search` availability is conditional. Startup registration uses global, override, and environment fallback config without reading project-local config before Pi trust context exists; trusted project config can register the companion tool on `session_start`, and every execution reloads the final session config so `webSearch.enabled: false` still prevents a request even if a startup credential made the tool visible. 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 before they make the tool available; 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
|
-
Browser default config is intentionally
|
|
75
|
+
Browser default config is intentionally advisory. 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. Loaded project config can provide the same guidance as global and override config; Pi/project trust decides whether that project layer is loaded. 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
|
|
|
77
77
|
### Prompt guidance budget
|
|
78
78
|
|
|
@@ -199,7 +199,7 @@ This keeps the product centered on native tool usage instead of auxiliary skill
|
|
|
199
199
|
### `pi-agent-browser-native` owns
|
|
200
200
|
|
|
201
201
|
- tool registration and schema (including the optional `semanticAction` compilation path to upstream `find` or `select`)
|
|
202
|
-
- subprocess execution and JSON parsing through
|
|
202
|
+
- subprocess execution and JSON parsing through `buildAgentBrowserProcessEnv` in `extensions/agent-browser/lib/process.ts`: copies the parent process environment so user-approved provider credentials and other runtime variables reach upstream, then applies wrapper overrides such as the managed socket directory and clamped default operation timeout
|
|
203
203
|
- clear missing-binary errors
|
|
204
204
|
- compact result summaries, including presentation-time redaction: stateful browser-context commands (`auth`, `cookies`, `storage`, `dialog`, `frame`, `state`) use field-aware value redaction and compact formatters, while other structured upstream JSON (for example `network`, `diff`, `trace` / `profiler` / `record`, `console` / `errors` / `highlight` / `inspect` / `clipboard`, `stream`, `dashboard`, and `chat`) is passed through `redactPresentationData` in `extensions/agent-browser/lib/results/presentation.ts` so model-facing `details.data` and batch roll-ups stay compact and do not echo bearer tokens, proxy passwords, or similar fields verbatim; `redactInvocationArgs` in `extensions/agent-browser/lib/runtime.ts` masks trailing values for sensitive global flags such as `--body`, `--headers`, `--password`, and `--proxy`, preserves positional rules for `cookies set` and `storage local|session set`, and nested `batch` steps use the same argv and error-body scrubbing before echoing commands or errors
|
|
205
205
|
- bounded machine-readable outcome metadata on tool `details` (`resultCategory`, `successCategory`, `failureCategory`, optional `nextActions`, optional `pageChangeSummary` with per-step summaries on `batch`, optional `artifactVerification` with the same shape on each successful `batchSteps[]` row) so agents can branch without parsing prose; enums, classifier precedence, and generic follow-up payloads are implemented under `extensions/agent-browser/lib/results/` in focused modules (`contracts.ts` for shared types, `categories.ts` for `classifyAgentBrowserSuccessCategory` / `classifyAgentBrowserFailureCategory` / `buildAgentBrowserResultCategoryDetails`, `action-recommendations.ts` for `buildAgentBrowserNextActions`, `next-actions.ts` for the `AgentBrowserNextAction` shape and merge helpers, `recovery-actions.ts` for recovery id registries and `buildRecoveryNextActions`, `network.ts` for `classifyNetworkRequestFailure` / `summarizeNetworkFailures`, and related helpers; `shared.ts` in that directory is a **re-export-only** barrel so historical `./results/shared.js` imports keep working without growing a monolith). Per-session tab target, `refSnapshot` alignment, invalidation, and tab pinning observations flow through `extensions/agent-browser/lib/session-page-state.ts` from `extensions/agent-browser/index.ts`. Compact page-change summaries and artifact verification rollups are built in `extensions/agent-browser/lib/results/presentation.ts` (`buildPageChangeSummary`, `buildArtifactVerificationSummary`), and the human contract lives in [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details). Real Pi custom tools otherwise only mark a row failed when `execute` throws, so the extension also registers `pi.on("tool_result", …)` and patches `agent_browser` results whose `details.resultCategory` is `failure` to set `isError: true`. Prose results also receive a short category notice, while caller-requested `--json` results with parseable JSON content keep that text unchanged so JSONL transcripts, UI affordances, and the machine-readable contract stay aligned for wrapper-side reclassifications such as `qa-failure` (`buildAgentBrowserToolResultPatch` in `extensions/agent-browser/lib/pi-tool-rendering.ts`; transcript semantics in the same contract doc)
|
|
@@ -748,7 +748,7 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
|
|
|
748
748
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable set "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
749
749
|
```
|
|
750
750
|
|
|
751
|
-
The optional `agent_browser_web_search` tool is
|
|
751
|
+
The optional `agent_browser_web_search` tool is available when Exa or Brave credentials are visible from startup config or trusted session config and the runtime 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. Loaded config may use plaintext, custom env aliases, interpolation literals, malformed-or-late-bound `$` values, and command-backed web-search keys; the resolved secret reaches the provider request while model-facing tool output and status text stay redacted. `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`. 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.
|
|
752
752
|
|
|
753
753
|
Example config:
|
|
754
754
|
|
|
@@ -771,7 +771,7 @@ Example config:
|
|
|
771
771
|
}
|
|
772
772
|
```
|
|
773
773
|
|
|
774
|
-
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
|
|
774
|
+
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, in trusted project config, or through `PI_AGENT_BROWSER_CONFIG`. 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"`.
|
|
775
775
|
|
|
776
776
|
## Important global flags, config, and environment
|
|
777
777
|
|
|
@@ -819,7 +819,7 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
|
|
|
819
819
|
- `--confirm-interactive`: interactive confirmations; auto-denies when stdin is not a TTY. Environment: `AGENT_BROWSER_CONFIRM_INTERACTIVE`.
|
|
820
820
|
- `-p, --provider <name>`: provider such as `ios`, `browserbase`, `kernel`, `browseruse`, `browserless`, or `agentcore`. Environment: `AGENT_BROWSER_PROVIDER`.
|
|
821
821
|
- `--device <name>`: iOS device name. Environment: `AGENT_BROWSER_IOS_DEVICE`.
|
|
822
|
-
- Provider-specific iOS examples from upstream include `agent-browser -p ios device list`, `agent-browser -p ios swipe up`, and `agent-browser -p ios tap @e1`; in pi, pass those tokens through `args` rather than bash. iOS requires external Xcode/Appium setup, and cloud providers (`browserbase`, `kernel`, `browseruse`, `browserless`, `agentcore`) require their upstream accounts, credentials, and provider-specific environment variables. Common
|
|
822
|
+
- Provider-specific iOS examples from upstream include `agent-browser -p ios device list`, `agent-browser -p ios swipe up`, and `agent-browser -p ios tap @e1`; in pi, pass those tokens through `args` rather than bash. iOS requires external Xcode/Appium setup, and cloud providers (`browserbase`, `kernel`, `browseruse`, `browserless`, `agentcore`) require their upstream accounts, credentials, and provider-specific environment variables. Common provider variables include `BROWSERBASE_API_KEY`, `BROWSERBASE_PROJECT_ID`, `BROWSERLESS_API_KEY`, `BROWSERLESS_API_URL`, `BROWSERLESS_BROWSER_TYPE`, `BROWSERLESS_STEALTH`, `BROWSERLESS_TTL`, `BROWSER_USE_API_KEY`, `KERNEL_API_KEY`, `KERNEL_HEADLESS`, `KERNEL_STEALTH`, `KERNEL_TIMEOUT_SECONDS`, `KERNEL_PROFILE_NAME`, `AGENTCORE_API_KEY`, `AGENTCORE_REGION`, `AGENTCORE_BROWSER_ID`, `AGENTCORE_PROFILE_ID`, `AGENTCORE_SESSION_TIMEOUT`, plus AWS names used by AgentCore such as `AWS_PROFILE`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION`, and `AWS_DEFAULT_REGION`. The wrapper forwards parent environment variables plus provider flags and stays thin; it does not emulate provider setup or cloud browser behavior.
|
|
823
823
|
- `--model <name>`: AI model for `chat`. Environment: `AI_GATEWAY_MODEL`.
|
|
824
824
|
- `-v, --verbose`: show tool commands and raw output.
|
|
825
825
|
- `-q, --quiet`: show only AI text responses.
|
|
@@ -837,7 +837,7 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
|
|
|
837
837
|
|
|
838
838
|
Use `--config <path>` to load a specific config file. Boolean flags accept optional `true` or `false` values, such as `--headed false`, to override config. Browser extensions from user and project configs are merged rather than replaced.
|
|
839
839
|
|
|
840
|
-
Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGENT_BROWSER_STREAM_PORT`, `AGENT_BROWSER_IDLE_TIMEOUT_MS`, `AGENT_BROWSER_ENCRYPTION_KEY`, `AGENT_BROWSER_STATE_EXPIRE_DAYS`, `AGENT_BROWSER_IOS_DEVICE`, `AGENT_BROWSER_IOS_UDID`, `AI_GATEWAY_URL`, `AI_GATEWAY_API_KEY`,
|
|
840
|
+
Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGENT_BROWSER_STREAM_PORT`, `AGENT_BROWSER_IDLE_TIMEOUT_MS`, `AGENT_BROWSER_ENCRYPTION_KEY`, `AGENT_BROWSER_STATE_EXPIRE_DAYS`, `AGENT_BROWSER_IOS_DEVICE`, `AGENT_BROWSER_IOS_UDID`, `AI_GATEWAY_URL`, `AI_GATEWAY_API_KEY`, provider credential names, and AWS credential names when using AgentCore. The upstream child receives the parent environment plus wrapper overrides such as the managed socket directory and clamped default operation timeout (`buildAgentBrowserProcessEnv` in `extensions/agent-browser/lib/process.ts`). Model-facing output still redacts recognized secret values.
|
|
841
841
|
|
|
842
842
|
## Wrapper-specific behavior worth knowing
|
|
843
843
|
|
package/docs/RELEASE.md
CHANGED
|
@@ -178,7 +178,7 @@ Maintainer constraints for evolving scenarios and version bumps are summarized u
|
|
|
178
178
|
- `LICENSE` exists in the repo and the packed tarball
|
|
179
179
|
- canonical published docs are present
|
|
180
180
|
- `npm pack --json --dry-run` runs the `prepack` build and packs the compiled `dist/extensions/agent-browser/index.js` entrypoint
|
|
181
|
-
- GitHub/source installs run the package `prepare` build so Pi can load the ignored compiled `dist/extensions/agent-browser/index.js` entrypoint from a fresh clone
|
|
181
|
+
- GitHub/source installs run the package `prepare` build; when Pi installs with `npm install --omit=dev`, `scripts/prepare.mjs` installs source-build dev dependencies with lifecycle scripts disabled before building so Pi can load the ignored compiled `dist/extensions/agent-browser/index.js` entrypoint from a fresh clone
|
|
182
182
|
- the package-level doctor command and capability baseline are present
|
|
183
183
|
- compiled extension runtime files are present, including the split result-rendering modules required by the published facade
|
|
184
184
|
- source-only, agent-only, and superseded docs are absent from the tarball
|
package/docs/REQUIREMENTS.md
CHANGED
|
@@ -106,7 +106,7 @@ The design should comfortably support workflows such as:
|
|
|
106
106
|
- isolated authenticated browser sessions
|
|
107
107
|
- headless authenticated `chat.com` / ChatGPT / OpenAI browsing without forcing `--headed` or `--auto-connect`
|
|
108
108
|
- upstream profile/debug workflows without adding a local profile-cloning layer in this package
|
|
109
|
-
- provider-backed or iOS device launches where upstream owns credentials, env, and setup; the wrapper forwards argv and
|
|
109
|
+
- provider-backed or iOS device launches where upstream owns credentials, env, and setup; the wrapper forwards argv and the parent environment without emulating those backends
|
|
110
110
|
- desktop Electron targets using top-level `electron` for discover → isolated launch → attach → probe/cleanup, or raw `args: ["connect", …]` when the operator launches the real app with a debug port for signed-in state (see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#electron) and [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#electron-desktop-apps))
|
|
111
111
|
|
|
112
112
|
## Implications for the implementation
|
package/docs/SUPPORT_MATRIX.md
CHANGED
|
@@ -73,7 +73,7 @@ Runtime floor note: package metadata keeps Pi core package peer ranges wildcard
|
|
|
73
73
|
| Sessions, state, tabs, frames, dialogs, and windows | 20 canonical tokens from baseline section `state-tabs-frames-dialogs`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#session-state-frames-dialogs-windows-and-inspection-commands). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#session-state-frames-dialogs-windows-and-inspection-commands), stateful workflow notes, [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details). | Stateful summaries/redaction, state artifact handling, sessionless local command planning, managed-session restore, tab target pinning, and close alias cleanup. | Extension-validation stateful matrix, runtime session/resume tests, presentation redaction tests, lifecycle harness. | Supported. External profile/auth state remains operator-owned. |
|
|
74
74
|
| Network, storage, artifacts, diagnostics, and performance | 42 canonical tokens from baseline section `network-storage-artifacts-diagnostics`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#page-state-finding-mouse-settings-network-and-storage). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#page-state-finding-mouse-settings-network-and-storage), diagnostic sections, [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details). | Thin passthrough plus compact diagnostics, route-mock warnings, useful-but-redacted storage output, stream idempotency normalization, artifact metadata, missing-ffmpeg warnings, sensitive-data redaction, timeout bounds, and cleanup-pair guidance. | Fake non-core matrix and safe real-upstream coverage for network/HAR, diff, trace/profiler, console/errors/highlight, stream, vitals, and React missing-renderer. | Supported. Environment-sensitive operations need suitable local/browser state. |
|
|
75
75
|
| Batch, auth, confirmations, setup, dashboard, devices, and AI commands | 24 canonical tokens from baseline section `batch-auth-setup-ai`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#batch-auth-confirmations-sessions-chat-dashboard-devices-and-setup). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#batch-auth-confirmations-sessions-chat-dashboard-devices-and-setup), README security notes, release docs. | Native-tool batch stdin, generated `job`/`qa`/lookup batch plans, auth/confirmation redaction, sessionless local auth/setup/dashboard/doctor planning, timeout/cleanup guidance. | Unit/fake batch/auth/confirmation/dashboard/chat/doctor tests; extension-validation for structured input modes; efficiency benchmark scenarios. | Supported. Interactive side-effecting setup/auth/chat remains upstream-owned. |
|
|
76
|
-
| Global flags, config, providers, policy, and environment | 120 canonical tokens from baseline section `options-and-env`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#important-global-flags-config-and-environment). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#important-global-flags-config-and-environment), README provider/setup notes, [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#sessionmode), architecture/runtime docs. | Runtime handles command discovery, value-flag prevalidation, launch-scoped flags, redacted echoes, fresh-session recovery hints, explicit sessions, provider/device launch-scoping,
|
|
76
|
+
| Global flags, config, providers, policy, and environment | 120 canonical tokens from baseline section `options-and-env`; see [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs) and generated [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#important-global-flags-config-and-environment). | [`COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#important-global-flags-config-and-environment), README provider/setup notes, [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#sessionmode), architecture/runtime docs. | Runtime handles command discovery, value-flag prevalidation, launch-scoped flags, redacted echoes, fresh-session recovery hints, explicit sessions, provider/device launch-scoping, parent env forwarding with wrapper overrides, subprocess completion, and package-owned Pi-scoped config for optional companion features. | Runtime tests for flags/planning/redaction/session behavior; process tests for env and stdio-linger completion; config/web-search/CLI tests; fake provider/specialized-skill matrix; package doctor. | Supported. Provider clouds, iOS/Appium, proxies, profiles, and credentials require external setup. |
|
|
77
77
|
|
|
78
78
|
## Follow-up decision after closure
|
|
79
79
|
|
package/docs/TOOL_CONTRACT.md
CHANGED
|
@@ -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
|
|
39
|
+
`agent_browser_web_search` is a separate custom tool, not an `agent_browser` input mode. It is available when the extension can see at least one configured/resolvable Exa or Brave credential source from `~/.pi/config/pi-agent-browser-native/config.json`, `.pi/config/pi-agent-browser-native/config.json`, `PI_AGENT_BROWSER_CONFIG`, or the `EXA_API_KEY` / `BRAVE_API_KEY` environment fallbacks, and runtime execution still checks that the final available merged config has not set `webSearch.enabled` to `false`. Config layers merge global → project → `PI_AGENT_BROWSER_CONFIG` override; under Pi 0.79+, globally installed and CLI-loaded copies read `.pi/config/...` when Pi trust allows that project layer, and they skip the project layer when Pi reports the project is untrusted or when launched with `--no-approve`. Disable scope is explicit: a global disable is a normal user default, a project disable applies to one repo, and an override file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Credential sources may be plaintext, `$ENV_VAR` / `${ENV_VAR}` interpolation, escaped literals, or command sources such as `"!op read 'op://Private/Exa/API Key'"` from any loaded config layer; they make the tool available without exposing the value in status text, and command values resolve when the tool executes. Browser profile/executable config uses the same paths and emits prompt guidance from the highest-priority loaded layer, including project config when that layer is loaded.
|
|
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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agent-browser-native",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.51",
|
|
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)",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"platform-smoke.config.mjs",
|
|
36
36
|
"scripts/config.mjs",
|
|
37
37
|
"scripts/doctor.mjs",
|
|
38
|
+
"scripts/prepare.mjs",
|
|
38
39
|
"scripts/agent-browser-capability-baseline.mjs",
|
|
39
40
|
"scripts/platform-smoke.mjs",
|
|
40
41
|
"scripts/platform-smoke",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
"@earendil-works/pi-ai": "0.79.1",
|
|
66
67
|
"@earendil-works/pi-coding-agent": "0.79.1",
|
|
67
68
|
"@earendil-works/pi-tui": "0.79.1",
|
|
68
|
-
"@types/node": "^25.
|
|
69
|
+
"@types/node": "^25.9.3",
|
|
69
70
|
"tsx": "^4.21.0",
|
|
70
71
|
"typebox": "^1.1.38",
|
|
71
72
|
"typescript": "^6.0.3"
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
"build": "node ./scripts/build.mjs",
|
|
93
94
|
"startup-profile": "node ./scripts/profile-startup.mjs",
|
|
94
95
|
"prepack": "npm run build",
|
|
95
|
-
"prepare": "
|
|
96
|
+
"prepare": "node ./scripts/prepare.mjs"
|
|
96
97
|
},
|
|
97
98
|
"packageManager": "npm@11.14.0"
|
|
98
99
|
}
|
package/scripts/config.mjs
CHANGED
|
@@ -28,7 +28,6 @@ const {
|
|
|
28
28
|
getWebSearchProviderConfigKey,
|
|
29
29
|
getWebSearchProviderEnvVar,
|
|
30
30
|
getWebSearchProviderLabel,
|
|
31
|
-
isProjectSafeCredentialValueForProvider,
|
|
32
31
|
isWebSearchProvider,
|
|
33
32
|
loadAgentBrowserConfigStateSync,
|
|
34
33
|
summarizeConfigFiles,
|
|
@@ -50,9 +49,9 @@ Usage through npm exec:
|
|
|
50
49
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config paths
|
|
51
50
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config show
|
|
52
51
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search status
|
|
53
|
-
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-key --stdin --provider <exa|brave> [--global]
|
|
52
|
+
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-key --stdin --provider <exa|brave> [--global|--project]
|
|
54
53
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-env <ENV_VAR> [--provider brave|exa] [--global|--project]
|
|
55
|
-
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-command <command> --provider <exa|brave> [--global]
|
|
54
|
+
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search set-command <command> --provider <exa|brave> [--global|--project]
|
|
56
55
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search clear --provider <exa|brave|all> [--global|--project]
|
|
57
56
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search prefer <exa|brave|auto> [--global|--project]
|
|
58
57
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config web-search enable [--global|--project]
|
|
@@ -61,14 +60,14 @@ Usage through npm exec:
|
|
|
61
60
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser profile set <name|path> [--policy explicit-only|authenticated-only|always] [--global|--project]
|
|
62
61
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser profile clear [--global|--project]
|
|
63
62
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable status
|
|
64
|
-
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable set <path> [--global]
|
|
63
|
+
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable set <path> [--global|--project]
|
|
65
64
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable clear [--global|--project]
|
|
66
65
|
|
|
67
66
|
Notes:
|
|
68
67
|
Global config: ~/.pi/config/pi-agent-browser-native/config.json
|
|
69
68
|
Project config: .pi/config/pi-agent-browser-native/config.json
|
|
70
69
|
Override: ${AGENT_BROWSER_CONFIG_ENV}=/path/to/config.json
|
|
71
|
-
|
|
70
|
+
Loaded config may use plaintext, environment interpolation, or !command credential sources; displayed status redacts resolved keys.
|
|
72
71
|
Use --provider for set-key, set-command, and clear; set-env infers exa/brave from ${EXA_API_KEY_ENV} or ${BRAVE_API_KEY_ENV}.
|
|
73
72
|
`;
|
|
74
73
|
}
|
|
@@ -212,13 +211,12 @@ async function handleWebSearch(args, flags) {
|
|
|
212
211
|
}
|
|
213
212
|
if (action === "set-key") {
|
|
214
213
|
const provider = getWebSearchProvider(flags);
|
|
215
|
-
if (flags.get("--project")) throw new UsageError(`Plaintext ${getWebSearchProviderLabel(provider)} keys cannot be written to project-local config. Use set-env or set-command.`);
|
|
216
214
|
const key = await readSecretFromStdin(Boolean(flags.get("--stdin")));
|
|
217
|
-
const { path } = selectWritePath(flags);
|
|
215
|
+
const { path, scope } = selectWritePath(flags);
|
|
218
216
|
mutateConfig(path, (config) => {
|
|
219
217
|
setWebSearchCredential(config, provider, key);
|
|
220
218
|
});
|
|
221
|
-
console.log(`Saved ${getWebSearchProviderLabel(provider)} key to
|
|
219
|
+
console.log(`Saved ${getWebSearchProviderLabel(provider)} key to ${scope} config: ${path}`);
|
|
222
220
|
return;
|
|
223
221
|
}
|
|
224
222
|
if (action === "set-env") {
|
|
@@ -226,9 +224,6 @@ async function handleWebSearch(args, flags) {
|
|
|
226
224
|
if (!envName || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(envName)) throw new UsageError("set-env requires a valid environment variable name.");
|
|
227
225
|
const provider = getWebSearchProvider(flags, { envName });
|
|
228
226
|
const envReference = `$${envName}`;
|
|
229
|
-
if (flags.get("--project") && !isProjectSafeCredentialValueForProvider(envReference, provider)) {
|
|
230
|
-
throw new UsageError(`Project-local ${getWebSearchProviderLabel(provider)} env references must use ${getWebSearchProviderEnvVar(provider)} exactly; custom env aliases belong in global config or ${AGENT_BROWSER_CONFIG_ENV}.`);
|
|
231
|
-
}
|
|
232
227
|
const { path, scope } = selectWritePath(flags);
|
|
233
228
|
mutateConfig(path, (config) => {
|
|
234
229
|
setWebSearchCredential(config, provider, envReference);
|
|
@@ -238,7 +233,6 @@ async function handleWebSearch(args, flags) {
|
|
|
238
233
|
}
|
|
239
234
|
if (action === "set-command") {
|
|
240
235
|
const provider = getWebSearchProvider(flags);
|
|
241
|
-
if (flags.get("--project")) throw new UsageError(`Command-backed ${getWebSearchProviderLabel(provider)} keys cannot be written to project-local config. Use set-env there.`);
|
|
242
236
|
const command = args.slice(1).join(" ").trim();
|
|
243
237
|
if (!command) throw new UsageError("set-command requires a command string.");
|
|
244
238
|
const { path, scope } = selectWritePath(flags);
|
|
@@ -297,9 +291,6 @@ function handleBrowser(args, flags) {
|
|
|
297
291
|
if (!name) throw new UsageError("browser profile set requires a profile name or profile directory path.");
|
|
298
292
|
const policy = flags.get("--policy") || "authenticated-only";
|
|
299
293
|
if (!["explicit-only", "authenticated-only", "always"].includes(policy)) throw new UsageError("Invalid --policy value.");
|
|
300
|
-
if (flags.get("--project") && policy !== "explicit-only") {
|
|
301
|
-
throw new UsageError("Project-local browser profile config may only use --policy explicit-only; authenticated or always profile guidance must be configured globally or through PI_AGENT_BROWSER_CONFIG.");
|
|
302
|
-
}
|
|
303
294
|
const { path, scope } = selectWritePath(flags);
|
|
304
295
|
mutateConfig(path, (config) => {
|
|
305
296
|
config.browser = { ...(config.browser ?? {}), defaultProfile: { name, policy } };
|
|
@@ -325,9 +316,6 @@ function handleBrowser(args, flags) {
|
|
|
325
316
|
if (action === "set") {
|
|
326
317
|
const executablePath = args.slice(2).join(" ").trim();
|
|
327
318
|
if (!executablePath) throw new UsageError("browser executable set requires a browser executable path.");
|
|
328
|
-
if (flags.get("--project")) {
|
|
329
|
-
throw new UsageError("Project-local browser executable config cannot steer host launch guidance; configure it globally or through PI_AGENT_BROWSER_CONFIG.");
|
|
330
|
-
}
|
|
331
319
|
const { path, scope } = selectWritePath(flags);
|
|
332
320
|
mutateConfig(path, (config) => {
|
|
333
321
|
config.browser = { ...(config.browser ?? {}), executablePath };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Purpose: Build generated dist output for GitHub/source installs even when Pi invokes npm install --omit=dev.
|
|
4
|
+
* Responsibilities: Detect missing source-build dependencies, install dev dependencies with lifecycle scripts disabled, then run the canonical build.
|
|
5
|
+
* Scope: Package install lifecycle only; npm tarball contents and runtime behavior remain owned by scripts/build.mjs.
|
|
6
|
+
* Usage: package.json prepare script.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import process from "node:process";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
|
|
15
|
+
const execFile = promisify(execFileCallback);
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const REQUIRED_SOURCE_BUILD_MODULES = [
|
|
18
|
+
"typescript",
|
|
19
|
+
"typebox",
|
|
20
|
+
"@earendil-works/pi-coding-agent",
|
|
21
|
+
"@earendil-works/pi-tui",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function canResolveBuildDependencies() {
|
|
25
|
+
return REQUIRED_SOURCE_BUILD_MODULES.every((moduleName) => {
|
|
26
|
+
try {
|
|
27
|
+
require.resolve(moduleName);
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runNpmInstallDevDependencies() {
|
|
36
|
+
const npmExecPath = process.env.npm_execpath;
|
|
37
|
+
const options = process.platform === "win32" ? { shell: true } : {};
|
|
38
|
+
if (npmExecPath) {
|
|
39
|
+
await execFile(process.execPath, [npmExecPath, "install", "--include=dev", "--ignore-scripts"], {
|
|
40
|
+
...options,
|
|
41
|
+
cwd: process.cwd(),
|
|
42
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
await execFile("npm", ["install", "--include=dev", "--ignore-scripts"], {
|
|
47
|
+
...options,
|
|
48
|
+
cwd: process.cwd(),
|
|
49
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
if (!canResolveBuildDependencies()) {
|
|
55
|
+
await runNpmInstallDevDependencies();
|
|
56
|
+
}
|
|
57
|
+
await execFile(process.execPath, [join(process.cwd(), "scripts", "build.mjs")], {
|
|
58
|
+
cwd: process.cwd(),
|
|
59
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main().catch((error) => {
|
|
64
|
+
if (error?.stdout) process.stdout.write(error.stdout);
|
|
65
|
+
if (error?.stderr) process.stderr.write(error.stderr);
|
|
66
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
});
|