pi-agent-browser-native 0.5.0 → 0.6.5
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 +138 -0
- package/README.md +75 -42
- package/dist/extensions/agent-browser/index.js +13 -83
- package/dist/extensions/agent-browser/lib/argv-grammar.js +8 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +1 -1
- package/dist/extensions/agent-browser/lib/command-policy.js +4 -7
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +19 -11
- package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
- package/dist/extensions/agent-browser/lib/config.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/job.js +0 -9
- package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +18 -4
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -138
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +1 -81
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +4 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +31 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +60 -8
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +0 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +31 -40
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +76 -70
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +6 -8
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +5 -10
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
- package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
- package/dist/extensions/agent-browser/lib/playbook.js +13 -12
- package/dist/extensions/agent-browser/lib/process-identity.js +1 -8
- package/dist/extensions/agent-browser/lib/process.js +18 -82
- package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -78
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +27 -14
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +20 -2
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +11 -16
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +12 -9
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +2 -2
- package/dist/extensions/agent-browser/lib/results/presentation.js +31 -4
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +1 -1
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +3 -3
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
- package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
- package/dist/extensions/agent-browser/lib/runtime.js +73 -72
- package/dist/extensions/agent-browser/lib/session-page-state.js +12 -3
- package/dist/extensions/agent-browser/lib/temp.js +1 -2
- package/dist/extensions/agent-browser/lib/upstream-version.js +5 -5
- package/dist/extensions/agent-browser/lib/web-search.js +108 -24
- package/dist/scripts/agent-browser-target.mjs +19 -1
- package/docs/ARCHITECTURE.md +24 -20
- package/docs/COMMAND_REFERENCE.md +181 -49
- package/docs/ELECTRON.md +2 -2
- package/docs/RELEASE.md +10 -8
- package/docs/REQUIREMENTS.md +8 -7
- package/docs/SUPPORT_MATRIX.md +31 -26
- package/docs/TOOL_CONTRACT.md +89 -56
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +65 -5
- package/scripts/agent-browser-target.mjs +19 -1
- package/scripts/config.mjs +1 -0
- package/scripts/doctor.mjs +15 -9
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -601
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { JsonSchema } from "./json-schema.js";
|
|
2
2
|
import { WEB_SEARCH_PROMPT_GUIDELINE } from "./playbook.js";
|
|
3
3
|
import { StringEnum as localStringEnum } from "./string-enum-schema.js";
|
|
4
|
-
import { DEFAULT_WEB_SEARCH_PROVIDER, WEB_SEARCH_PROVIDERS, resolvePreferredWebSearchCredential, } from "./config.js";
|
|
4
|
+
import { DEFAULT_WEB_SEARCH_PROVIDER, EXA_SEARCH_TYPES, WEB_SEARCH_PROVIDERS, resolvePreferredWebSearchCredential, } from "./config.js";
|
|
5
5
|
export const AGENT_BROWSER_WEB_SEARCH_TOOL_NAME = "agent_browser_web_search";
|
|
6
6
|
export const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
|
|
7
7
|
export const EXA_SEARCH_ENDPOINT = "https://api.exa.ai/search";
|
|
8
8
|
export const DEFAULT_SEARCH_RESULT_COUNT = 5;
|
|
9
9
|
export const MAX_SEARCH_RESULT_COUNT = 10;
|
|
10
10
|
export const SEARCH_REQUEST_TIMEOUT_MS = 15_000;
|
|
11
|
-
export const
|
|
11
|
+
export const EXA_DEEP_LITE_SEARCH_REQUEST_TIMEOUT_MS = 45_000;
|
|
12
|
+
export const EXA_DEEP_SEARCH_REQUEST_TIMEOUT_MS = 60_000;
|
|
13
|
+
export const EXA_DEEP_REASONING_SEARCH_REQUEST_TIMEOUT_MS = 90_000;
|
|
14
|
+
export const EXA_DYNAMIC_HIGHLIGHTS_BETA = "dynamic-highlights-2026-08-28";
|
|
12
15
|
export const WEB_SEARCH_MIN_REQUEST_INTERVAL_MS = 1_100;
|
|
13
|
-
export const
|
|
16
|
+
export const EXA_SEARCH_SYSTEM_PROMPT = "Prefer primary, official sources. Respect any requested version or date. Avoid duplicate or equivalent results.";
|
|
17
|
+
export { EXA_SEARCH_TYPES };
|
|
14
18
|
export const WEB_SEARCH_PROVIDER_PARAM_VALUES = ["auto", ...WEB_SEARCH_PROVIDERS];
|
|
19
|
+
export const EXA_SEARCH_CATEGORIES = ["company", "people", "publication", "news", "personal site", "financial report"];
|
|
20
|
+
const MAX_EXA_DOMAIN_FILTERS = 20;
|
|
21
|
+
const MAX_EXA_ADDITIONAL_QUERIES = 10;
|
|
15
22
|
export function createAgentBrowserWebSearchParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
16
23
|
return Type.Object({
|
|
17
24
|
query: Type.String({
|
|
@@ -22,7 +29,28 @@ export function createAgentBrowserWebSearchParamsSchema(Type = JsonSchema, Strin
|
|
|
22
29
|
description: `Optional provider override. auto uses configured keys and preferredProvider; when both Exa and Brave are available, the default preferred provider is ${DEFAULT_WEB_SEARCH_PROVIDER}.`,
|
|
23
30
|
})),
|
|
24
31
|
searchType: Type.Optional(StringEnum(EXA_SEARCH_TYPES, {
|
|
25
|
-
description: "
|
|
32
|
+
description: "Exa mode; omitted uses webSearch.defaultSearchType, then auto. instant (~250ms) is only for trivial lookups; fast (~450ms) favors latency; auto (~1s) is balanced. Pass searchType: deep-lite (~4s) for research before implementation unless config already defaults it; do not assume auto is deep enough. deep (4–15s) handles hard multi-source research; deep-reasoning (12–40s) is only for the hardest work. Brave ignores this field.",
|
|
33
|
+
})),
|
|
34
|
+
includeDomains: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
35
|
+
minItems: 1,
|
|
36
|
+
maxItems: MAX_EXA_DOMAIN_FILTERS,
|
|
37
|
+
description: `Exa only. Limit results to 1–${MAX_EXA_DOMAIN_FILTERS} hostnames, path prefixes (exa.ai/docs), or wildcard subdomains (*.substack.com).`,
|
|
38
|
+
})),
|
|
39
|
+
excludeDomains: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
40
|
+
minItems: 1,
|
|
41
|
+
maxItems: MAX_EXA_DOMAIN_FILTERS,
|
|
42
|
+
description: `Exa only. Exclude 1–${MAX_EXA_DOMAIN_FILTERS} hostnames, path prefixes, or wildcard subdomains. Not compatible with category company or people.`,
|
|
43
|
+
})),
|
|
44
|
+
category: Type.Optional(StringEnum(EXA_SEARCH_CATEGORIES, {
|
|
45
|
+
description: "Exa-only result category. company and people cannot be combined with freshness or excludeDomains.",
|
|
46
|
+
})),
|
|
47
|
+
additionalQueries: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
48
|
+
minItems: 1,
|
|
49
|
+
maxItems: MAX_EXA_ADDITIONAL_QUERIES,
|
|
50
|
+
description: `Exa only. Add 1–${MAX_EXA_ADDITIONAL_QUERIES} query variations when the effective searchType is deep-lite, deep, or deep-reasoning.`,
|
|
51
|
+
})),
|
|
52
|
+
highlightsDynamic: Type.Optional(Type.Boolean({
|
|
53
|
+
description: "Exa-only research preview. Allocate one highlight budget across all results; the wrapper sends the required Exa-Beta header. Regular per-page highlights remain the default.",
|
|
26
54
|
})),
|
|
27
55
|
count: Type.Optional(Type.Integer({
|
|
28
56
|
minimum: 1,
|
|
@@ -198,12 +226,14 @@ export function normalizeBraveSearchResult(result) {
|
|
|
198
226
|
const url = normalizeSearchUrl(result.url);
|
|
199
227
|
if (!title || !url)
|
|
200
228
|
return undefined;
|
|
229
|
+
const pageDate = cleanSearchText(result.page_age, 80);
|
|
201
230
|
return {
|
|
202
231
|
title,
|
|
203
232
|
url,
|
|
204
233
|
description: cleanSearchText(result.description, 320),
|
|
205
234
|
source: cleanSearchText(result.profile?.name, 120) ?? cleanSearchText(result.meta_url?.hostname, 120),
|
|
206
235
|
age: cleanSearchText(result.age, 80),
|
|
236
|
+
...(pageDate ? { pageDate } : {}),
|
|
207
237
|
language: cleanSearchText(result.language, 40),
|
|
208
238
|
};
|
|
209
239
|
}
|
|
@@ -213,13 +243,14 @@ export function normalizeExaSearchResult(result) {
|
|
|
213
243
|
if (!title || !url)
|
|
214
244
|
return undefined;
|
|
215
245
|
const highlights = normalizeHighlightList(result.highlights);
|
|
246
|
+
const pageDate = cleanSearchText(result.publishedDate, 80);
|
|
216
247
|
return {
|
|
217
248
|
title,
|
|
218
249
|
url,
|
|
219
250
|
description: cleanSearchText(result.summary, 320) ?? highlights?.[0] ?? cleanSearchText(result.text, 320),
|
|
220
251
|
highlights,
|
|
221
252
|
source: cleanSearchText(result.author, 120) ?? cleanSearchText(getHostname(url), 120),
|
|
222
|
-
|
|
253
|
+
...(pageDate ? { pageDate } : {}),
|
|
223
254
|
};
|
|
224
255
|
}
|
|
225
256
|
function getProviderLabel(provider) {
|
|
@@ -236,6 +267,8 @@ export function formatSearchResults(provider, query, results) {
|
|
|
236
267
|
lines.push(` URL: ${result.url}`);
|
|
237
268
|
if (result.source)
|
|
238
269
|
lines.push(` Source: ${result.source}`);
|
|
270
|
+
if (result.pageDate)
|
|
271
|
+
lines.push(` ${provider === "exa" ? "Published" : "Page date"}: ${result.pageDate}`);
|
|
239
272
|
if (result.age)
|
|
240
273
|
lines.push(` Age: ${result.age}`);
|
|
241
274
|
if (result.description)
|
|
@@ -277,14 +310,30 @@ function getStartPublishedDate(freshness, now) {
|
|
|
277
310
|
return new Date(now().getTime() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
278
311
|
}
|
|
279
312
|
export function buildExaSearchRequestBody(params, now = () => new Date()) {
|
|
313
|
+
const searchType = params.searchType ?? "auto";
|
|
314
|
+
if (params.additionalQueries?.length && !searchType.startsWith("deep")) {
|
|
315
|
+
throw new Error(`additionalQueries requires deep-lite, deep, or deep-reasoning; received ${searchType}.`);
|
|
316
|
+
}
|
|
317
|
+
if ((params.category === "company" || params.category === "people") && (params.freshness || params.excludeDomains?.length)) {
|
|
318
|
+
throw new Error(`category ${params.category} cannot be combined with freshness or excludeDomains.`);
|
|
319
|
+
}
|
|
280
320
|
const body = {
|
|
281
321
|
query: params.query,
|
|
282
|
-
type:
|
|
322
|
+
type: searchType,
|
|
283
323
|
numResults: Math.min(params.count + params.offset, 100),
|
|
284
|
-
contents: { highlights: true },
|
|
324
|
+
contents: { highlights: params.highlightsDynamic ? { dynamic: true } : true },
|
|
325
|
+
systemPrompt: EXA_SEARCH_SYSTEM_PROMPT,
|
|
285
326
|
};
|
|
327
|
+
if (params.category)
|
|
328
|
+
body.category = params.category;
|
|
286
329
|
if (params.country)
|
|
287
330
|
body.userLocation = params.country.toUpperCase();
|
|
331
|
+
if (params.includeDomains?.length)
|
|
332
|
+
body.includeDomains = params.includeDomains;
|
|
333
|
+
if (params.excludeDomains?.length)
|
|
334
|
+
body.excludeDomains = params.excludeDomains;
|
|
335
|
+
if (params.additionalQueries?.length)
|
|
336
|
+
body.additionalQueries = params.additionalQueries;
|
|
288
337
|
if (params.safesearch && params.safesearch !== "off")
|
|
289
338
|
body.moderation = true;
|
|
290
339
|
const startPublishedDate = getStartPublishedDate(params.freshness, now);
|
|
@@ -396,7 +445,20 @@ export async function fetchBraveSearchJson(url, apiKey, signal) {
|
|
|
396
445
|
});
|
|
397
446
|
}
|
|
398
447
|
function getExaRequestTimeoutMs(searchType) {
|
|
399
|
-
|
|
448
|
+
if (searchType === "deep-lite")
|
|
449
|
+
return EXA_DEEP_LITE_SEARCH_REQUEST_TIMEOUT_MS;
|
|
450
|
+
if (searchType === "deep")
|
|
451
|
+
return EXA_DEEP_SEARCH_REQUEST_TIMEOUT_MS;
|
|
452
|
+
if (searchType === "deep-reasoning")
|
|
453
|
+
return EXA_DEEP_REASONING_SEARCH_REQUEST_TIMEOUT_MS;
|
|
454
|
+
return SEARCH_REQUEST_TIMEOUT_MS;
|
|
455
|
+
}
|
|
456
|
+
function usesDynamicHighlights(body) {
|
|
457
|
+
const contents = body.contents;
|
|
458
|
+
if (!contents || typeof contents !== "object" || Array.isArray(contents))
|
|
459
|
+
return false;
|
|
460
|
+
const highlights = contents.highlights;
|
|
461
|
+
return Boolean(highlights && typeof highlights === "object" && !Array.isArray(highlights) && highlights.dynamic === true);
|
|
400
462
|
}
|
|
401
463
|
export async function fetchExaSearchJson(body, apiKey, signal, timeoutMs = SEARCH_REQUEST_TIMEOUT_MS) {
|
|
402
464
|
return fetchSearchJson({
|
|
@@ -408,6 +470,7 @@ export async function fetchExaSearchJson(body, apiKey, signal, timeoutMs = SEARC
|
|
|
408
470
|
Accept: "application/json",
|
|
409
471
|
"Content-Type": "application/json",
|
|
410
472
|
"x-api-key": apiKey,
|
|
473
|
+
...(usesDynamicHighlights(body) ? { "Exa-Beta": EXA_DYNAMIC_HIGHLIGHTS_BETA } : {}),
|
|
411
474
|
},
|
|
412
475
|
method: "POST",
|
|
413
476
|
},
|
|
@@ -449,15 +512,7 @@ const EXA_WEB_SEARCH_ADAPTER = {
|
|
|
449
512
|
buildRequest(params) {
|
|
450
513
|
const searchType = params.searchType ?? "auto";
|
|
451
514
|
return {
|
|
452
|
-
body: buildExaSearchRequestBody(
|
|
453
|
-
query: params.query,
|
|
454
|
-
count: params.count,
|
|
455
|
-
offset: params.offset,
|
|
456
|
-
country: params.country,
|
|
457
|
-
safesearch: params.safesearch,
|
|
458
|
-
freshness: params.freshness,
|
|
459
|
-
searchType,
|
|
460
|
-
}),
|
|
515
|
+
body: buildExaSearchRequestBody(params),
|
|
461
516
|
timeoutMs: getExaRequestTimeoutMs(searchType),
|
|
462
517
|
};
|
|
463
518
|
},
|
|
@@ -469,7 +524,7 @@ const EXA_WEB_SEARCH_ADAPTER = {
|
|
|
469
524
|
return {
|
|
470
525
|
extraDetails: {
|
|
471
526
|
requestId: cleanSearchText(response.requestId, 120),
|
|
472
|
-
searchType
|
|
527
|
+
searchType,
|
|
473
528
|
},
|
|
474
529
|
results: (response.results ?? [])
|
|
475
530
|
.map(normalizeExaSearchResult)
|
|
@@ -486,6 +541,15 @@ export const WEB_SEARCH_PROVIDER_ADAPTERS = {
|
|
|
486
541
|
export function getWebSearchProviderAdapter(provider) {
|
|
487
542
|
return WEB_SEARCH_PROVIDER_ADAPTERS[provider];
|
|
488
543
|
}
|
|
544
|
+
export function dedupeSearchResults(results) {
|
|
545
|
+
const seen = new Set();
|
|
546
|
+
return results.filter((result) => {
|
|
547
|
+
if (seen.has(result.url))
|
|
548
|
+
return false;
|
|
549
|
+
seen.add(result.url);
|
|
550
|
+
return true;
|
|
551
|
+
});
|
|
552
|
+
}
|
|
489
553
|
function buildMissingCredentialError(provider) {
|
|
490
554
|
if (provider === "brave")
|
|
491
555
|
return "agent_browser_web_search provider brave was requested but no BRAVE_API_KEY/config credential resolved.";
|
|
@@ -498,13 +562,13 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
498
562
|
return {
|
|
499
563
|
name: AGENT_BROWSER_WEB_SEARCH_TOOL_NAME,
|
|
500
564
|
label: "Agent Browser Web Search",
|
|
501
|
-
description: `Search the web with Exa or Brave
|
|
565
|
+
description: `Search the live web with Exa or Brave for current or external information. For Exa research tasks, use searchType deep-lite or deeper. Returns up to ${MAX_SEARCH_RESULT_COUNT} concise web results.`,
|
|
502
566
|
promptSnippet: "Search the live web with Exa or Brave for current or external information.",
|
|
503
567
|
promptGuidelines: [
|
|
504
568
|
WEB_SEARCH_PROMPT_GUIDELINE,
|
|
505
569
|
"agent_browser_web_search chooses Exa or Brave from configured keys; when both are available, Exa is preferred by default unless webSearch.preferredProvider says otherwise. Use provider only when the user/config calls for a specific provider.",
|
|
506
|
-
"
|
|
507
|
-
"
|
|
570
|
+
"Use Exa deep only when deep-lite may miss angles, and deep-reasoning only for exhaustive or still-thin research. Do not run parallel agent_browser_web_search calls; make one high-signal query, inspect its results, then at most one follow-up.",
|
|
571
|
+
"If agent_browser_web_search returns HTTP 429, stop searching and tell the user the API plan/rate limit needs time or a plan change.",
|
|
508
572
|
"After using agent_browser_web_search, cite result URLs in the final answer when web evidence informed the answer.",
|
|
509
573
|
],
|
|
510
574
|
parameters: AgentBrowserWebSearchParams,
|
|
@@ -520,6 +584,18 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
520
584
|
const resolved = await resolvePreferredWebSearchCredential(runtimeConfigState, { provider: requestedProvider, signal });
|
|
521
585
|
if (!resolved)
|
|
522
586
|
throw new Error(buildMissingCredentialError(requestedProvider));
|
|
587
|
+
if (resolved.provider === "brave") {
|
|
588
|
+
const exaOnlyFields = [
|
|
589
|
+
params.includeDomains ? "includeDomains" : undefined,
|
|
590
|
+
params.excludeDomains ? "excludeDomains" : undefined,
|
|
591
|
+
params.category ? "category" : undefined,
|
|
592
|
+
params.additionalQueries ? "additionalQueries" : undefined,
|
|
593
|
+
params.highlightsDynamic ? "highlightsDynamic" : undefined,
|
|
594
|
+
].filter((field) => Boolean(field));
|
|
595
|
+
if (exaOnlyFields.length > 0) {
|
|
596
|
+
throw new Error(`${exaOnlyFields.join(", ")} ${exaOnlyFields.length === 1 ? "requires" : "require"} provider exa; resolved provider was brave.`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
523
599
|
const query = params.query.trim();
|
|
524
600
|
if (!query)
|
|
525
601
|
throw new Error("query must not be blank");
|
|
@@ -527,18 +603,25 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
527
603
|
const offset = Math.max(params.offset ?? 0, 0);
|
|
528
604
|
const adapter = getWebSearchProviderAdapter(resolved.provider);
|
|
529
605
|
const executionParams = {
|
|
606
|
+
additionalQueries: params.additionalQueries,
|
|
607
|
+
category: params.category,
|
|
530
608
|
country: params.country,
|
|
531
609
|
count,
|
|
610
|
+
excludeDomains: params.excludeDomains,
|
|
532
611
|
freshness: params.freshness,
|
|
612
|
+
highlightsDynamic: params.highlightsDynamic,
|
|
613
|
+
includeDomains: params.includeDomains,
|
|
533
614
|
offset,
|
|
534
615
|
query,
|
|
535
616
|
safesearch: params.safesearch,
|
|
536
617
|
searchLang: params.searchLang,
|
|
537
|
-
searchType: params.searchType ?? "auto",
|
|
618
|
+
searchType: params.searchType ?? runtimeConfigState.config.webSearch?.defaultSearchType ?? "auto",
|
|
538
619
|
};
|
|
539
620
|
const request = adapter.buildRequest(executionParams);
|
|
540
621
|
const data = await requestGate.run(signal, () => adapter.fetchJson(request, resolved.credential.value, signal));
|
|
541
622
|
const normalized = adapter.normalizeResponse(data, executionParams);
|
|
623
|
+
const results = dedupeSearchResults(normalized.results);
|
|
624
|
+
const duplicatesRemoved = normalized.results.length - results.length;
|
|
542
625
|
const details = {
|
|
543
626
|
provider: adapter.provider,
|
|
544
627
|
query,
|
|
@@ -547,10 +630,11 @@ export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
|
547
630
|
offset,
|
|
548
631
|
...normalized.extraDetails,
|
|
549
632
|
fetchedAt: new Date().toISOString(),
|
|
550
|
-
results
|
|
633
|
+
results,
|
|
634
|
+
duplicatesRemoved: duplicatesRemoved || undefined,
|
|
551
635
|
};
|
|
552
636
|
return {
|
|
553
|
-
content: [{ type: "text", text: formatSearchResults(adapter.provider, normalized.returnedQuery,
|
|
637
|
+
content: [{ type: "text", text: `${formatSearchResults(adapter.provider, normalized.returnedQuery, results)}${duplicatesRemoved ? `\n\nDuplicate URLs removed: ${duplicatesRemoved}.` : ""}` }],
|
|
554
638
|
details,
|
|
555
639
|
};
|
|
556
640
|
},
|
|
@@ -1,3 +1,21 @@
|
|
|
1
1
|
export const TARGET_AGENT_BROWSER_SOURCE = "scripts/agent-browser-target.mjs";
|
|
2
|
-
export const TARGET_AGENT_BROWSER_VERSION = "0.
|
|
2
|
+
export const TARGET_AGENT_BROWSER_VERSION = "0.36.0";
|
|
3
3
|
export const TARGET_AGENT_BROWSER_VERSION_LABEL = `agent-browser ${TARGET_AGENT_BROWSER_VERSION}`;
|
|
4
|
+
export const MINIMUM_AGENT_BROWSER_VERSION = "0.35.0";
|
|
5
|
+
export const MINIMUM_AGENT_BROWSER_VERSION_LABEL = `agent-browser ${MINIMUM_AGENT_BROWSER_VERSION}`;
|
|
6
|
+
export const SUPPORTED_AGENT_BROWSER_VERSION_LABEL = `${MINIMUM_AGENT_BROWSER_VERSION_LABEL} or newer`;
|
|
7
|
+
function stableVersionParts(version) {
|
|
8
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version);
|
|
9
|
+
return match?.slice(1).map(Number);
|
|
10
|
+
}
|
|
11
|
+
export function isSupportedAgentBrowserVersion(version) {
|
|
12
|
+
const actual = stableVersionParts(version);
|
|
13
|
+
const minimum = stableVersionParts(MINIMUM_AGENT_BROWSER_VERSION);
|
|
14
|
+
if (!actual || !minimum)
|
|
15
|
+
return false;
|
|
16
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
17
|
+
if (actual[index] !== minimum[index])
|
|
18
|
+
return actual[index] > minimum[index];
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -40,7 +40,7 @@ The extension should:
|
|
|
40
40
|
- inject `--json`
|
|
41
41
|
- complete each upstream invocation when the direct `agent-browser` child exits even if Node delays `"close"`: piped stdio can stay referenced by longer-lived descendant processes, so `runAgentBrowserProcess` watches `exit` and `close` together, leaves stdio intact during a short post-`exit` grace so normal `close` can still win, destroys streams only when the post-`exit` fallback fires, and prefers `close` codes then wrapper timeout (`124`) over signal-shaped `exit` codes (`watchSpawnedChildCompletion` / `resolveSpawnedChildExitCode` in `extensions/agent-browser/lib/process.ts`) so the tool cannot hang after the CLI process has already terminated
|
|
42
42
|
- support optional stdin only for `eval --stdin`, `batch`, `auth save --password-stdin`, and wrapper-generated `batch` stdin from top-level `job`, `qa`, `sourceLookup`, or `networkSourceLookup`, rejecting other command/stdin combinations before launch; top-level `electron` never accepts caller `stdin` (see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#electron))
|
|
43
|
-
- support optional top-level `outputPath` for successful browser results by writing `details.data` (or model-facing text when no structured data exists) to a caller-requested local file and reporting `details.outputFile`, without changing upstream argv semantics or overwriting a browser artifact when both destinations resolve to the same file
|
|
43
|
+
- support optional top-level `outputPath` for successful browser results by writing `details.data` (or model-facing text when no structured data exists) to a caller-requested local file and reporting `details.outputFile`, without changing upstream argv semantics or overwriting a browser artifact when both destinations resolve to the same file. If presentation compacted direct data, a batch result row, or a whole batch, apply the relevant command-specific redactor before spilling and rehydrate each full pre-compaction value only from its matching live wrapper-manifest spill; fail without writing compact metadata when any required spill is unavailable or untrusted
|
|
44
44
|
- support optional top-level `timeoutMs` as a per-call subprocess watchdog override for browser CLI input modes while keeping Electron-specific timeouts inside the `electron` object
|
|
45
45
|
- accept an optional top-level `script` string as a mutually exclusive one-shot orchestration mode for loops, conditional page branches, and multi-page aggregation. Source runs in a separate permissioned Node child with a constrained VM context; only null-prototype `browser({ args, stdin?, timeoutMs? })` and `emit(value)` task functions cross a bounded JSON-lines IPC bridge. The parent serializes at most one inner call at a time through the same full ordinary tool executor, clears ambient upstream launch/proxy controls across helpers and cleanup, caps calls/source/post-redaction output/time, injects one unique restore-disabled wrapper-owned session, writes a strict Pi custom-entry cleanup lease before first launch, closes in `finally`, aborts and awaits active-script cleanup on branch change/shutdown, and recovers exact non-closed active-branch leases afterward. Script requires Pi session persistence and exposes no profile/attachment/session-control, host API, named recipe, import, or persistent workflow-state surface (see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#script)).
|
|
46
46
|
- accept an optional native `semanticAction` object as a mutually exclusive alternative to `args` on a single tool call (and to `script`, `job`, `qa`, `sourceLookup`, `networkSourceLookup`, and `electron` on the same call), compile locator actions into upstream `find` argv, direct selector/ref click/check/fill into upstream command argv, and native dropdown selection into upstream `select <selector> <value...>` argv (with optional `semanticAction.session` expanding to a leading `--session <name>` before the compiled command when targeting a named upstream browser instead of the managed default), and echo the compiled shape in `details.compiledSemanticAction` for observability (see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#semanticaction))
|
|
@@ -54,7 +54,7 @@ The extension should:
|
|
|
54
54
|
|
|
55
55
|
### One-shot script isolation
|
|
56
56
|
|
|
57
|
-
`script` is orchestration around the native tool, not a second browser runtime. Its custom Pi call renderer keeps the approval boundary inspectable with a bounded terminal-safe preview whose line breaks render as `↵` and full terminal-safe source when expanded; JavaScript line terminators stay visible as newlines and removed controls become visible markers. The child never imports this extension or invokes `agent-browser`; it only emits bounded JSON call requests. The parent validates each request against script-specific policy, injects `--namespace "" --session piab-script-<uuid>`, and recursively uses the registered tool's ordinary executor. This preserves the same argv parsing,
|
|
57
|
+
`script` is orchestration around the native tool, not a second browser runtime. Its custom Pi call renderer keeps the approval boundary inspectable with a bounded terminal-safe preview whose line breaks render as `↵` and full terminal-safe source when expanded; JavaScript line terminators stay visible as newlines and removed controls become visible markers. The child never imports this extension or invokes `agent-browser`; it only emits bounded JSON call requests. The parent validates each request against script-specific policy, injects `--namespace "" --session piab-script-<uuid>`, and recursively uses the registered tool's ordinary executor. This preserves the same argv parsing, page-target validation, process lifecycle, presentation/redaction, spill/artifact verification, result categories, and timeout behavior instead of creating a weaker bare-process shortcut. The one deliberate process difference is stricter: an async-local isolation scope filters ambient `AGENT_BROWSER_*` and standard proxy variables during planning and spawn, then the wrapper-owned namespace, timeout, and compatibility values are applied.
|
|
58
58
|
|
|
59
59
|
Isolation is layered:
|
|
60
60
|
|
|
@@ -84,9 +84,9 @@ Pi docs use `settings.json` for package/resource loading and filtering, not arbi
|
|
|
84
84
|
- project-local: `.pi/config/pi-agent-browser-native/config.json`
|
|
85
85
|
- explicit override: `PI_AGENT_BROWSER_CONFIG=/path/to/config.json`
|
|
86
86
|
|
|
87
|
-
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.84.0+, 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.
|
|
87
|
+
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.84.0+, 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.defaultSearchType`, `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.
|
|
88
88
|
|
|
89
|
-
`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.
|
|
89
|
+
`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. Exa search-type precedence is per-call value, merged config default, then `auto`; bounded Exa filters stay on the Exa adapter and explicit Exa-only options fail before a Brave request. The tool calls Exa `/search` with highlights plus a fixed primary-source/version/date/distinct-result system prompt, or Brave Search, then removes later exact normalized-URL duplicates while retaining provider order. It does not guess across distinct paths/query URLs or overfetch replacements; provider page dates remain clues rather than claimed crawl/version evidence. Compact result details report any `duplicatesRemoved` without exposing keys.
|
|
90
90
|
|
|
91
91
|
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.
|
|
92
92
|
|
|
@@ -116,7 +116,7 @@ The published package should load from the `pi` manifest in `package.json`.
|
|
|
116
116
|
|
|
117
117
|
Local checkout validation has two intentional modes:
|
|
118
118
|
|
|
119
|
-
- **
|
|
119
|
+
- **Checkout-only extension mode:** use explicit CLI loading such as `pi --approve --no-extensions -e .` from the intentionally trusted repository root. This disables automatic extension loading and avoids duplicate `agent_browser` registrations, but settings, configured package resolution, and other resource types remain active. Temporary `HOME` and `PI_CODING_AGENT_DIR` directories isolate test settings; `PI_OFFLINE=1` disables automatic startup network/update operations. Omit `--approve` only when testing Project Trust.
|
|
120
120
|
- **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.84.0+ `--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.
|
|
121
121
|
|
|
122
122
|
The repo should not add a repo-local `.pi/extensions/` autoload shim as the documented checkout path.
|
|
@@ -124,7 +124,7 @@ The repo should not add a repo-local `.pi/extensions/` autoload shim as the docu
|
|
|
124
124
|
Why:
|
|
125
125
|
- avoids duplicate `agent_browser` registrations when the package is also installed globally
|
|
126
126
|
- keeps the product contract centered on the package manifest instead of repo-local autoload wiring
|
|
127
|
-
- keeps reload and exact-session relaunch validation tied to Pi's configured-source lifecycle instead of an
|
|
127
|
+
- keeps reload and exact-session relaunch validation tied to Pi's configured-source lifecycle instead of an explicit-extension quick-test path, while `session_tree` state changes stay covered by focused extension harness tests
|
|
128
128
|
- keeps the published tarball focused on the package manifest, extension code, canonical docs, and license
|
|
129
129
|
|
|
130
130
|
The published package should exclude agent-only and internal planning materials such as `AGENTS.md`.
|
|
@@ -158,28 +158,31 @@ Practical policy:
|
|
|
158
158
|
- preserve the current branch-visible extension-managed session across `/reload`, exact-session relaunch, `/resume`, and Pi 0.84.0+ `session_tree` branch transitions so persisted sessions can keep following the live browser after lifecycle changes
|
|
159
159
|
- close the active extension-managed session when the originating `pi` process quits, while leaving explicit caller-provided sessions alone
|
|
160
160
|
- set an idle timeout on extension-managed sessions as a backstop for abnormal exits or cleanup failures, and apply that same `AGENT_BROWSER_IDLE_TIMEOUT_MS` value to every upstream subprocess (including wrapper helper snapshots, tab lists, and navigation-summary reads) because changing the launch environment between calls can make upstream restart the background browser, discard the active tab, and invalidate fresh refs
|
|
161
|
-
- for wrapper-owned managed sessions only, also set a Pi-transcript- and Git-checkout-generation-scoped `AGENT_BROWSER_RESTORE` key on every compatible non-close upstream subprocess so cookies, localStorage, and sessionStorage autosave/restore across idle shutdowns, `/reload`, exact-session relaunch, and `/resume` of the same Pi transcript. Upstream 0.33.2 loads the newest file matching one restore key regardless of browser session suffix, so unrelated concurrent Pi transcripts must use distinct keys to prevent state loss or cross-chat cookie/storage bleed. Fresh browser rotations within one Pi transcript share its scope. The wrapper stores a UUID in the resolved Git admin directory and combines it with the checkout root, Git-admin directory filesystem identities, and the transcript's cwd-derived managed-session base name. Android app storage rejects hard-link marker publication, so the Android path uses exclusive-create publication; its device/inode identity remains stable while Node's Android birth-time field follows mutable ctime. On other POSIX hosts the candidate hard-link publication and device/inode/birth-time identity remain unchanged: the generation marker survives renames, but the composite key includes that cwd-derived base name, so a renamed checkout or a different working directory yields a new key (fail-closed; re-authenticate once); copied/path-replacement checkouts and different Pi transcripts get new keys, non-Git directories fail closed, and cwd-only keys are not adopted. Policy lives in `extensions/agent-browser/lib/managed-session-restore.ts`; ownership is resolved by `resolveOwnedManagedSessionContext` (injected managed session, or explicit `--session` equal to the current managed name and namespace) and applied through `AsyncLocalStorage` `withOwnedManagedSessionContext` for prepare helpers plus main process/output, with typed `ownedManagedSession` process options for owned main/close spawns rather than an internal marker leaked into the child environment. `buildOwnedManagedSessionRestoreContext` sets call-scoped `restoreSuppressed` from main-plan argv so helper probes skip restore on incompatible plans without sticky-disabling when prepare returns early; sticky disable commits only after an owned-context subprocess actually starts with suppressed restore policy: POSIX commits on child `spawn`, while PowerShell-backed Windows commits after completion unless command-not-found stderr proves `agent-browser.cmd` never started. No-spawn preflight and missing-binary failures never commit an identity. Duplicate `--session` or `--namespace` flags are rejected, as are leading equals forms that upstream 0.33.2 does not recognize; global identity/config scanning follows upstream across the full argv rather than treating `--` as a sentinel. Native Windows command-first launcher adaptation relocates only valid leading global syntax, canonicalizes a valued optional `--restore <name>` to `--restore=<name>`, consumes only exact lowercase boolean literals, and leaves command-scoped, unknown, or unsupported equals-form input untouched so invalid calls cannot become valid browser activity. Namespace values are canonicalized with upstream's lowercase `sanitize_session_component` algorithm before ownership, sticky/page state, details, socket, or restore-directory identity comparisons; every wrapper-owned subprocess also pins that canonical namespace, including an empty default namespace, so parent environment cannot redirect helpers or close. Electron status target reads and current-managed probes acquire the same daemon-policy lock and owned restore context as ordinary commands for their underlying reads. Probe results then persist the same namespace plus top-level tab/ref state, keeping branch replay keyed to the probed identity. Ownership is typed rather than inferred from a name prefix, and `piab-*` live-session names are reserved: an explicit target is accepted only when it is the current/generated managed session or appears in this extension instance's ownership records. `session list` hides those rows, and the same reservation is rechecked at the final process boundary so another Pi process cannot attach to a managed authenticated browser through the shared per-user daemon socket. Skip when the caller already set restore/profile/state/CDP/provider/auto-connect/containment/session-name or a browser mutation surface (custom executable, extension, init script, raw launch args, proxy, plugin, WebGPU, or related engine/device controls) via argv or matching parent env, when the command is `connect`, when raw batch argv is used, when batch stdin contains nested `connect`/`batch`, or when `PI_AGENT_BROWSER_MANAGED_SESSION_RESTORE=0`. The wrapper's own site-specific headless user-agent compatibility injection is excluded from caller-mutation policy and retained as managed-session state, but its launch flags are omitted while daemon inspection confirms that session is active. Initial launches and sessions proven inactive receive the retained compatibility launch values, including a fixed, comma-safe Chrome `--user-agent=...` argument so new targets inherit the value; re-emitting either to an active launch-configured session can replace its browser with `about:blank` in upstream 0.34.0. A user-private immutable ticket-claim lock keyed by canonical namespace/session serializes this inspect-through-spawn decision across cooperating Pi processes; every contender publishes a unique claim, deterministic tickets elect one owner, and the winner also holds the legacy v2 path as a bridge. The bridge is transitional for pre-release branch processes and is scheduled for removal after v0.2.74 in [#93](https://github.com/fitchmultz/pi-agent-browser-native/issues/93). Live pre-update processes and their in-flight candidate gaps therefore block new acquisition; an abandoned v2 owner fails closed for manual repair, while current-protocol recovery removes only unique claims and artifacts with proven-dead PID/start identity. Waits are asynchronous and bounded. Ordinary acquisition waits one second and fails with retry guidance rather than queueing behind another process's in-flight command, which may retain the lock through a 35-second daemon inspection and the requested browser operation. Every policy-lock winner re-runs `session info` even when this process previously recorded the applied/observed restore key, because another process can restart the same daemon identity between calls. That inspection uses a fixed bounded timeout independent of a caller's shorter `PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS` override. Before an incompatible call, the wrapper reads `session info` for the actual same-identity daemon and fails before the requested spawn when that daemon retains any restore key or cannot be inspected. This covers restore-enabled daemons missing from transcript state after a crash and managed sessions launched with an explicit caller restore key; a confirmed inactive daemon remains reusable; a restore-disabled daemon is reusable only when this process recorded its expected null/custom restore policy after an owned spawn or successful policy match. After reload clears process-only provenance, an inactive old daemon may be restarted without restore and that started subprocess records a null daemon policy for its next follow-up. Same-process `session_tree` branch changes retain that process-owned provenance; a new extension instance after reload, restart, or `/resume` intentionally starts without it and fails closed on a still-live restore-disabled daemon even when the transcript restores sticky-disable state. Close the retained-key daemon first, use a fresh wrapper session, or choose a distinct explicit session. Once a managed session hits any allowed incompatible launch path, restore stays disabled for later bare follow-ups on that same session identity. Sticky identities live in the extension-owned `ManagedSessionRestoreState` instance, persist as `details.managedSessionRestoreDisabled`, and are replaced from current-branch rows during branch restore rather than stored in module-global process state. The opt-out returns before config/storage probes and sticky-records a successfully spawned identity as restore-disabled, allowing later calls to reuse that non-restore daemon without tripping the active restore-enabled conflict gate. This is env-based persistence, not a hidden argv relaunch. Upstream still owns restore file paths/modes under `~/.agent-browser/`; set `AGENT_BROWSER_ENCRYPTION_KEY` on multi-user hosts if plaintext session files are unacceptable. Passive upstream `./agent-browser.json` and `~/.agent-browser/config.json` files do not disable managed restore because accepted browser-backed subprocesses pin a protected empty config. Only an explicit `--config` argument or `AGENT_BROWSER_CONFIG` environment override disables restore without reading caller-selected content in the Pi host; an owned spawn with either explicit override sticky-disables that session identity. Each subprocess that receives the wrapper restore key, plus every wrapper-owned close, overrides config discovery with a process-private empty `AGENT_BROWSER_CONFIG` (`0400` on POSIX) inside the canonical marked `0700` secure-temp root, closing the check-to-spawn race without trusting project or user config while retaining normal shutdown cleanup and PID/start-identity stale-root recovery after abnormal exit on POSIX and native Windows; versioned Windows identities treat legacy cross-format markers as unknown instead of incorrectly proving PID reuse, and temp ownership marker schema v2 makes older readers ignore new-format markers. Spawn-time revalidation rejects changed checkout identity, restore storage, unpinned launch-mutator environment, foreign managed-session targets, or forbidden managed-state access before agent-browser starts; the same check runs again after protected-config and socket-directory awaits immediately adjacent to the synchronous spawn. A failed fresh command that started agent-browser triggers an exact-identity daemon probe; an active or uninspectable daemon remains current and wrapper-owned so shutdown cleanup can close it, while pre-aborted and missing-binary calls remain unowned. Wrapper-owned close commands canonicalize upstream argv to JSON plus the known namespace/session and `close`, discarding caller config/restore globals, and do not inject a newly derived restore key into an existing daemon, so checkout replacement cannot make old auth save under the replacement generation; the close path retains the observed wrapper key long enough to record the returned old-generation snapshot safely. Because upstream writes a snapshot per daemon session, a successful wrapper-owned close requests JSON output and persists only the returned state path as an atomic record in a lockless convergent per-key ownership directory beside the snapshots (`0700`, with `0600` records, on POSIX). Cleanup carries that ownership proof across Pi restarts, self-heals malformed or stale regular records without claiming their snapshots, uses immutable atomic record names plus rescan-after-delete convergence so concurrent closers cannot skip ownership recording or exceed the aggregate cap, removes proven snapshots older than 30 days for the exact restore key while retaining the two newest, expires stale ownership-proven snapshots and empty manifests from other restore-key generations after 30 days only when a private lineage record proves the same canonical checkout path, caps young close churn at 256 records per key, and never deletes matching unrecorded files or the current checkout key. Upstream restore files under `~/.agent-browser/` remain plaintext unless `AGENT_BROWSER_ENCRYPTION_KEY` is set; before automatic managed restore the wrapper requires a durable Git generation and absolute platform home root; it pins the planned encryption-key value after caller env merging; on POSIX it also resolves `HOME` once, validates owner-trusted non-writable ancestry plus stable device/inode/birth-time metadata for both checkout and Git-admin directories (Android recognizes the private app-data sandbox and uses stable device/inode identity plus the generation UUID because Node reports mutable ctime as birth time), and pins that canonical value, enforces mode `0700` without silently repairing unsafe existing paths, and rejects symlinks/non-directories along the exact `~/.agent-browser[/namespaces/<canonical>/state]/sessions` path and its `.tmp` transactional-write area, while Windows requires an absolute `USERPROFILE` and the documented 64-character hex encryption key because POSIX mode checks cannot verify profile ACLs; malformed keys fail closed on every platform. POSIX process-start probes use absolute `/bin/ps` then `/usr/bin/ps`; Android/Termux first uses `ps` beside `process.execPath`, where Termux installs it. If no platform candidate is available, managed policy locking fails closed with an actionable validation message. Managed `piab-r2-*` keys and key-bearing paths are redacted from visible/structured/JSON transcript surfaces. Malformed oversized upstream output is discarded after parsing rather than copied into a persistent parse-failure spill, and raw parse-failure stdout is omitted from result details. `session list` and `state list` filter wrapper-managed rows, and the pre-spawn policy blocks foreign managed restore/state references, broad clear/clean operations, and managed save/rename targets while preserving targeted caller-owned state workflows.
|
|
162
|
-
- clean up process-private temp spill artifacts on shutdown,
|
|
161
|
+
- for wrapper-owned implicit sessions only, set a transcript- and checkout-scoped `AGENT_BROWSER_RESTORE` key on compatible calls so cookies and web storage can survive idle shutdown, reload, and resume. Explicit caller sessions, restore/state choices, profiles, upstream config, file access, launch arguments, environment variables, local pages, output paths, and close arguments remain upstream-owned and pass through unchanged. `piab-*` names are not reserved; session/state lists and restore identifiers are not filtered or redacted. The wrapper validates only its automatic restore checkout/storage identity and coordinates same-daemon reuse so its own restore pools cannot mix. Ambiguous page-target transitions still require live `get url` verification before content calls. The current v3 ticket-claim lock is the only managed-daemon coordination protocol; no earlier lock bridge or compatibility path remains.
|
|
162
|
+
- redact snapshot spill payloads before writing them, clean up process-private temp spill artifacts on shutdown, and keep persisted-session spill files in a private session-scoped artifact directory with a bounded per-session budget so `details.fullOutputPath` stays usable after reload/resume without unbounded growth
|
|
163
163
|
- keep explicit screenshots, downloads, PDFs, traces, HAR captures, and recordings written to caller-chosen paths on disk after a successful upstream close command (`close`, `quit`, or `exit`); before artifact-producing commands run, create missing parent directories for requested host paths, and for simple loopback HTML anchor downloads with resolvable HTTP(S) hrefs the wrapper may save directly to the requested path before upstream fallback. When the bounded `details.artifactManifest` has entries, successful close commands also surface `details.artifactCleanup` and a compact `Artifact lifecycle` note pointing to structured explicit paths so operators remove files with normal host tools—the native tool does not delete arbitrary user paths (`extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts`, `getArtifactCleanupGuidance`); contract in [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details), checklist `RQ-0079` in [`SUPPORT_MATRIX.md`](SUPPORT_MATRIX.md)
|
|
164
164
|
- reconstruct the current branch-visible extension-managed session, every transcript-proven still-active wrapper-owned managed identity, page-scoped refs, newest-revision aggregate artifact manifest, unbounded active-recording reservation events, and Electron launch records from the active transcript branch on `session_start` and `session_tree` so later default and explicit off-current calls keep following owned browsers after resume/reload or branch switching; restore also honors successful explicit `--session <wrapper-owned> close` rows, terminal nested-batch close outcomes even when aggregate artifact verification failed, and `electron.cleanup` managed-session steps so closed wrapper-owned sessions are not resurrected; a nested close invalidates the pre-close page target so a lifecycle-proven relaunch at `about:blank` is not treated as stale focus drift; explicit lifecycle evidence that a later diagnostic did not launch a browser preserves the terminal close, while any later row—including a failed row—whose lifecycle reports a browser launch keeps active/attached provenance; failed-step presentation persists only that bounded launch boolean so transcript replay reaches the same decision, missing lifecycle evidence remains conservatively active even on the first managed call, successful closes clear wrapper trace/profiler ownership before ordered later successful rows can rebuild it, namespace-scoped `close --all` clears all matching managed/attached/page/ref/route/trace/recording ownership, and recording starts after close are rejected before spawn
|
|
165
|
-
- keep active recording destination reservations separate from the bounded metadata-only artifact manifest. The process-wide map is keyed by canonical namespace/session identity, rebuilt from append-only branch events, and retained for still-live process-owned recordings across branch switches. Shutdown/reload appends both terminal tombstones and still-live reservations onto the current branch so restart cannot resurrect a cross-branch close or lose a live-daemon reservation. One artifact lifecycle/output queue makes global destination preflight and reservation updates atomic across otherwise-concurrent caller-owned session queues. Every successful direct, ordered nested-batch, managed replacement, script, Electron, or shutdown close retires its exact identity at that lifecycle point; only the newest pending recording path remains authoritative across
|
|
165
|
+
- keep active recording destination reservations separate from the bounded metadata-only artifact manifest. The process-wide map is keyed by canonical namespace/session identity, rebuilt from append-only branch events, and retained for still-live process-owned recordings across branch switches. Shutdown/reload appends both terminal tombstones and still-live reservations onto the current branch so restart cannot resurrect a cross-branch close or lose a live-daemon reservation. One artifact lifecycle/output queue makes global destination preflight and reservation updates atomic across otherwise-concurrent caller-owned session queues. Every successful direct, ordered nested-batch, managed replacement, script, Electron, or shutdown close retires its exact identity at that lifecycle point; only the newest pending recording path remains authoritative across current transition replay (including same-timestamp restart rows), and recording starts after a nested close are rejected because upstream can falsely report success. Existing and dangling symlink ancestry, hardlink inode identity, full Unicode/platform case folding, and same-call `outputPath` comparison prevent alias reuse. One shared command-token projection mirrors upstream's full-argv global cleanup before artifact, recording, and presentation parsing; wait-download detection removes only the first timeout pair, follows upstream long/short mode precedence, and accepts both `--download` and `-d` wherever download mode wins; screenshot destinations use upstream's exact-flag, selector-prefix, case-sensitive extension, slash-path, and second-positional rules, while retaining the wrapper's intentional slash-bearing hidden-workspace path normalization. Current recording transitions are replayed directly; artifact manifests are not treated as reservation events
|
|
166
166
|
- keep process-owned cleanup registries for extension-managed sessions and wrapper-launched Electron records separate from the current branch-visible view; `session_tree` restore and wrapper-owned browser commands are serialized with managed-session work, while caller-owned explicit-session commands are serialized by process-local queues keyed to effective canonical namespace/session across prepare helpers (explicit namespace argv overrides inherited `AGENT_BROWSER_NAMESPACE`, including an explicit empty default) and main execution. macOS and Windows additionally normalize and case-fold namespace and session components to match case-insensitive daemon identity. Different caller-owned identities remain concurrent, except namespace-scoped `close --all` drains and exclusively barriers managed plus matching caller-owned work before clearing global namespace state; nested helpers never re-enter the outer queue, policy/route/artifact deltas merge across unrelated managed-state commits, and a separate branch-restore generation guard prevents stale completions from overwriting newer branch-visible state; aggregate artifact results use monotonic revisions so transcript replay cannot lose a concurrently completed entry. Branch switches still must not drop resources the current Pi process owns and must keep fresh-session allocation monotonic
|
|
167
|
-
- record successful `connect`, `--cdp`, enabled `--auto-connect`, environment-configured CDP/auto-connect, and wrapper Electron attachment identities in branch-visible state
|
|
167
|
+
- record successful `connect`, `--cdp`, enabled `--auto-connect`, environment-configured CDP/auto-connect, and wrapper Electron attachment identities in branch-visible state. First-use and later content-bearing calls live-check `get url` because attached targets can drift outside Pi. Caller config, file access, launch arguments, and environment pass through unchanged; only wrapper-injected compatibility launch arguments are omitted on active attachments. A terminal successful close removes the marker; a close followed by a later step whose lifecycle reports a browser launch preserves it, while a non-launching diagnostic such as `stream status` leaves the close terminal
|
|
168
168
|
- when a successful close targets the current extension-managed session, including an explicit `--session <current> close` or an `electron.cleanup` managed-session step, clear page/ref state, mark that session inactive, untrack cleanup ownership, and rotate the next default auto call to a fresh wrapper-generated session name rather than reusing the closed name
|
|
169
169
|
- on non-quit shutdown such as `/reload`, close off-branch owned managed sessions and off-branch owned Electron launches before clearing process-local ownership, but preserve the current branch-visible active managed session and Electron launch plus that launch's isolated `userDataDir` so reload continuity still works from the active transcript branch
|
|
170
170
|
- expose still-owned off-branch Electron launch records to `electron.status { launchId }`, `electron.status { all: true }`, `electron.probe { launchId }`, and `electron.cleanup`, while leaving default `electron.probe` scoped to the current managed session
|
|
171
171
|
- if an unnamed fresh launch replaces an active extension-managed session, best-effort close the old managed session after the switch succeeds; `managedSessionOutcome.replacedSessionClosed` records whether that cleanup succeeded, and a failed close keeps the older identity wrapper-owned across transcript resume for explicit follow-up or cleanup
|
|
172
|
-
-
|
|
172
|
+
- expose `details.browserWindow` and one visible login handoff only when a successful first/fresh local wrapper-managed headed result, including `batch`, is not an attachment and has upstream `lifecycle.effectiveLaunch.browserLaunched: true` and a `created`/`replaced` managed-session outcome. Keep `visibility: "unverified"`: this is launch evidence, never a claim about the user's OS desktop
|
|
173
|
+
- leave explicit caller-provided `--session` choices alone unless the caller closes them explicitly, but before any content-bearing read or interaction against a caller-owned explicit session, live-probe that session with `get url` instead of trusting missing or stale transcript page state; hold the effective canonical namespace/session queue from that probe through semantic snapshot resolution and the main command so another same-instance call cannot change tabs in between. Non-bail batch analysis retains every possible page left by a failed transition up to a fixed bound and blocks later content only when the target is unverified; exceeding the bound also fails closed to exact `batch --bail` guidance. Nested `batch` steps remain unsupported, and raw batch command strings mirror upstream's ASCII-space tokenizer, including quote/backslash handling, rather than splitting on other Unicode whitespace.
|
|
173
174
|
- after profiled `open` / `goto` / `navigate` calls, verify the active tab still matches the returned page URL and best-effort switch back when restored profile tabs steal focus
|
|
174
175
|
- once the wrapper observes tab-drift risk for a session (profile restore correction, overlapping stale opens, or restored session state), later active-tab commands may synthesize a tiny upstream `batch` that re-selects that tab and then runs the requested command in the same upstream invocation; routine same-session commands avoid `tab list` preflights to reduce probes that can perturb upstream click behavior
|
|
175
176
|
- for sessions with observed tab-drift risk, after a successful command on a known tab target, the wrapper may best-effort restore that same target again if restored/background tabs steal focus after the command returns; routine same-session commands skip this post-command `tab list` probe
|
|
176
|
-
- after successful `tab close`, read the now-active URL
|
|
177
|
-
- keep a per-session `refSnapshot` aligned with the last successful `snapshot` (including refs merged from a successful `batch` by taking the last successful `snapshot` step in batch result order): restore it from persisted tool `details` when reloading, resuming, or moving to a different Pi session-tree branch, store bounded ref role/name metadata from the same snapshot for wrapper-side current-ref diagnostics, drop it on successful close commands (`close`, `quit`, or `exit`), replace it with a persisted `page-transition` invalidation after any upstream-executed `record start` attempt (direct or batch; upstream swaps the session to a fresh active page before its already-active check, so failed starts count)
|
|
177
|
+
- after successful standalone tab selection or `tab close`, read the now-active URL and fresh non-blank title—even when two tabs share a URL—before updating per-session page state because upstream selection/close payloads are not sufficient page-target evidence; retain an explicitly selected existing `about:blank` tab or a blank tab revealed by close instead of treating either as accidental drift
|
|
178
|
+
- keep a per-session `refSnapshot` aligned with the last successful `snapshot` (including refs merged from a successful `batch` by taking the last successful `snapshot` step in batch result order): restore it from persisted tool `details` when reloading, resuming, or moving to a different Pi session-tree branch, store bounded ref role/name metadata from the same snapshot for wrapper-side current-ref diagnostics, drop it on successful close commands (`close`, `quit`, or `exit`), replace it with a persisted `page-transition` invalidation after any upstream-executed `record start` attempt (direct or batch; upstream swaps the session to a fresh active page before its already-active check, so failed starts count), a `record restart` with a URL operand, or WebMCP `invoke` / `result` / `cancel` (these page-provided tools can mutate, rerender, or navigate; when a spawned `batch` yields no parseable result rows, for example after a wrapper timeout, planned transition steps still record the invalidation), or after a failed non-batch transition command (`eval`, `back`, `forward`, `reload`, `connect`, `state load`, `tab` selection) whose live URL re-verification probe observed the page (a failed transition can still have mutated or replaced the document before throwing, so keeping the verified URL must not keep the prior refs; transcript replay preserves the persisted invalidation summary), and refuse page-scoped `@e…` argv before spawn when the active tab URL no longer matches the snapshot URL, when a ref id was never in that snapshot, when the snapshot state is invalidated, or when a `batch` step would reuse `@e…` on a guarded getter or mutation step after an earlier invalidating step (including `record start`, URL-bearing `record restart`, and WebMCP `invoke` / `result` / `cancel`) without a later `snapshot` step in the same plan; batch steps come from the source upstream actually executes (raw batch argument strings exclusively when any exist — filtering only the exact `--bail` token like upstream — stdin only otherwise, via `getUpstreamEffectiveBatchSteps` in `extensions/agent-browser/lib/orchestration/batch-stdin.ts`); the tab-pinned batch rewrite (which re-emits the caller's exact `--bail` token so fail-fast semantics survive the rewrite), artifact/recording preflight, batch screenshot path preparation (parent directories are created for effective raw rows too, without rewriting raw strings), and stale-ref echo args use that same selection so pinning and preflights cannot act on upstream-ignored stdin, while the pre-spawn state-policy validator deliberately keeps scanning parseable stdin alongside argv as a fail-closed content superset and treats stdin parse failures as fatal only when upstream would actually read stdin (its raw-token filter also uses the exact `--bail` token only). Same-snapshot `fill @e…` rows are guarded but do not themselves set that invalidation latch, so ordinary form fills can precede a click/submit row in one batch—see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) for the agent-visible contract and failure text; typed per-session tab/ref/pinning state lives in `extensions/agent-browser/lib/session-page-state.ts` and is updated from `extensions/agent-browser/index.ts` after each tool result
|
|
179
|
+
- when a direct or batched WebMCP call returns `status: "pending"`, or `result` / `cancel` fails while that target is unknown, keep its tab target unknown and discard same-call snapshot evidence instead of treating an immediate post-dispatch URL probe as stable; `webmcp result` / `cancel`, `get url`, and explicit navigation remain available while unknown; replace the generic blocked snapshot action with `verify-page-target-after-pending-webmcp` (`get url`), and let a completed `batch --bail` use that verification before `snapshot -i` to re-establish both target and refs
|
|
178
180
|
- for top-level non-Electron direct `click` commands with an eligible target, install a bounded in-page target-specific event probe before upstream runs; if upstream reports success but no trusted pointer/mouse/click event reached the resolved target, fail the tool and report `details.clickDispatch` with explicit retry/inspect next actions (the wrapper does not replay clicks in-page). The probe covers `xpath=` targets and current `@e…` / `ref=` refs whose latest stored `refSnapshot.refs` role is `button`, `checkbox`, `menuitem`, `radio`, `switch`, or `tab`; it uses that role/name metadata, including snapshot-order `duplicateIndex` for duplicate-name refs, instead of taking a fresh pre-click snapshot that could recycle upstream refs. The probe is intentionally skipped for CSS selector clicks, unresolved `find … click` locators, and `batch`/`job`/`qa` click steps
|
|
179
181
|
- derive narrow prompt guards only for concrete evidence invariants: explicitly requested screenshot/recording output paths block browser close until the artifact manifest verifies those paths, while bare inbound attachment paths remain inputs. The wrapper intentionally does not infer broad business/user intent from prompt text such as order/payment/post boundaries; agents must follow those instructions themselves. The artifact guard is bounded preflight policy (`details.promptGuard`, `failureCategory: "policy-blocked"`), not a reusable browser recipe layer
|
|
182
|
+
- reject direct and effective batch `scrollintoview text=...` / `scrollinto text=...` before dispatch because current upstream can falsely report success without movement, while leaving help forms untouched; return only native recovery (`find text ... hover` or fresh snapshot/ref), leaving CSS, XPath, and current-ref behavior upstream-owned
|
|
180
183
|
- after successful `get text` on a qualifying non-ref CSS selector, optionally issue one read-only `eval --stdin` probe per selector when multiple DOM matches or a hidden first match with visible peers could misread tabbed or off-screen content; simple id selectors and sensitive-looking literals skip this probe. Merge `details.selectorTextVisibility` / `selectorTextVisibilityAll`, visible warning lines, and `inspect-visible-text-candidates*` next actions as documented in [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) and `RQ-0074` in [`SUPPORT_MATRIX.md`](SUPPORT_MATRIX.md)
|
|
181
184
|
- for local Unix launches, set a short private socket directory so extension-generated session names do not fail on the upstream Unix socket-path length limit; require the selected path to be absolute, owned by the current uid, mode `0700`, under trusted non-replaceable ancestry, and free of symlink, foreign-owner, or special planted entries; reject pre-existing unsafe modes instead of repairing them, then recheck before spawn. Android/Termux uses `/data/data/<package>/piab`, treats the owner-only app-data directory as the trust anchor, permits the app's matching private uid/gid ancestry, compacts generated managed identities to one 80-bit digest so ordinary namespace plus fresh-session paths remain within the limit, and places policy-lock coordination under `os.tmpdir()` because Android `/tmp` is shell-owned and inaccessible
|
|
182
|
-
- keep wrapper-spawned upstream CLI calls bounded by clamping `AGENT_BROWSER_DEFAULT_TIMEOUT` to the upstream documented 25-second default while deriving a longer subprocess watchdog for explicit long `wait <ms>` / `wait --timeout <ms
|
|
185
|
+
- keep wrapper-spawned upstream CLI calls bounded by clamping `AGENT_BROWSER_DEFAULT_TIMEOUT` to the upstream documented 25-second default while deriving a longer subprocess watchdog for explicit long `wait <ms>` / `wait --timeout <ms>`, read, and WebMCP calls from the effective direct or raw-argument-else-stdin batch steps; dialog commands, likely dialog-trigger clicks/taps/finds, and `eval --stdin` snippets that look like alert/confirm/prompt/dialog triggers use shorter wrapper subprocess budgets so blocking JavaScript prompts surface recovery actions before the full default watchdog. Timeout recovery removes standalone snapshots when the target is unknown and emits one executable session-scoped `batch --bail` (`get url`, then `snapshot -i`); blocking-dialog status/accept/dismiss remains allowed under the same unknown-target guard
|
|
183
186
|
|
|
184
187
|
This is primarily about ownership clarity and avoiding surprise, not adding a heavy safety wrapper. If the extension invented the session, the extension should own its lifecycle without breaking reload, resume, or branch-tree semantics. If the caller explicitly chose the upstream session model, the extension should stay out of the way.
|
|
185
188
|
|
|
@@ -188,7 +191,7 @@ This is primarily about ownership clarity and avoiding surprise, not adding a he
|
|
|
188
191
|
`agent-browser` startup flags are sticky once a session is already running.
|
|
189
192
|
The extension should surface that clearly and avoid hidden restart behavior in v1.
|
|
190
193
|
|
|
191
|
-
That means explicit startup-scoping flags like `--allowed-domains`, `--auto-connect`, `--args`, `--user-agent`, `--cdp`, `--enable`, `--executable-path`, `--webgpu`, `--headed`, `--init-script`, `--device`, `--namespace`, `--profile`, `--provider`, `-p`, `--restore`, `--restore-save`, restore check flags, `--session-name`, and `--state` should remain explicit upstream argv choices instead of being wrapped in extra hidden restart or cloning logic. The one deliberate exception is the env-only managed-session `AGENT_BROWSER_RESTORE` key above, which does not inject `--restore` into argv and therefore does not trip launch-scoped `sessionMode: "fresh"` recovery.
|
|
194
|
+
That means explicit startup-scoping flags like `--allowed-domains`, `--auto-connect`, `--args`, `--user-agent`, `--cdp`, `--enable`, `--executable-path`, `--webgpu`, `--no-webmcp`, `--headed`, `--init-script`, `--device`, `--namespace`, `--profile`, `--provider`, `-p`, `--restore`, `--restore-save`, restore check flags, `--session-name`, and `--state` should remain explicit upstream argv choices instead of being wrapped in extra hidden restart or cloning logic. The one deliberate exception is the env-only managed-session `AGENT_BROWSER_RESTORE` key above, which does not inject `--restore` into argv and therefore does not trip launch-scoped `sessionMode: "fresh"` recovery.
|
|
192
195
|
|
|
193
196
|
The wrapper may still apply narrow compatibility normalizations when observed behavior justifies them and the result remains thin, local, and opt-out. For example, OpenAI web properties and `dash.cloudflare.com` reject the default local `HeadlessChrome` user agent while the same flow works with a normal Chrome UA, so the extension injects a domain-specific fallback only when the caller did not already choose raw Chrome arguments, a custom user agent, headed mode, CDP, auto-connect, a provider-backed launch, or a non-Chrome engine through argv or matching upstream environment. Managed sessions retain the injected value as per-session wrapper state across helper calls and branch reload/resume. Active daemons omit both launch forms so upstream does not replace a launch-configured browser; a session proven inactive receives the retained compatibility launch values, including the same fixed compatibility value as a comma-safe Chrome launch argument covering tabs and SSO popups that do not inherit upstream's per-page CDP override. Wrapper-owned headed launches also default upstream periodic restore autosave off because agent-browser 0.33.2 collects non-current origins through visible temporary targets while holding the daemon state lock; save-on-close remains enabled, and an explicit `AGENT_BROWSER_AUTOSAVE_INTERVAL_MS` value opts in when the daemon launches. The effective interval is retained in owned-session state and transcript results; changing it in either direction on a running wrapper-owned headed daemon is rejected until close plus a fresh launch.
|
|
194
197
|
|
|
@@ -198,14 +201,15 @@ The current-session failure should include a structured recovery hint pointing t
|
|
|
198
201
|
|
|
199
202
|
Implementation detail lives in `extensions/agent-browser/lib/launch-scoped-flags.ts` (canonical flag metadata shared with playbook/docs assertions), `extensions/agent-browser/lib/argv-descriptor.ts` and `extensions/agent-browser/lib/argv-grammar.ts` (command discovery, `VALUE_FLAGS`, `parseArgvDescriptor`) plus `extensions/agent-browser/lib/runtime.ts` (`getStartupScopedFlags`, `buildExecutionPlan`):
|
|
200
203
|
|
|
201
|
-
- **Command discovery:** Leading argv is scanned with a value-taking allowlist so known global flags and documented command flags consume their values before the upstream command word is identified. Missing-value prevalidation is intentionally limited to upstream global value flags; command-scoped flags and literal text are left to upstream parsing so values like `fill #field --password` are not rejected by wrapper heuristics before the CLI sees them. When upstream adds new global flags that take values ahead of the command, extend both the command-discovery and prevalidation allowlists; when it adds command-specific flags, extend only command discovery/redaction as needed. A smaller set of global boolean flags may be followed by an optional `true`/`false` literal; when present, that literal is consumed as the flag value before command discovery continues.
|
|
204
|
+
- **Command discovery:** Leading argv is scanned with a value-taking allowlist so known global flags and documented command flags consume their values before the upstream command word is identified. Missing-value prevalidation is intentionally limited to upstream global value flags; command-scoped flags and literal text are left to upstream parsing so values like `fill #field --password` are not rejected by wrapper heuristics before the CLI sees them. Upstream 0.35.0 and newer accept `--restore=<key>` but reject other global `--flag=value` tokens during normal command execution, so the wrapper rejects those unsupported assignments before they can be mistaken for command or artifact operands. Plain help/version inspection preserves exact caller argv because upstream accepts those top-level shapes. Nested batch rows do not parse global flags, so their equals forms never receive the top-level inspection or restore exceptions. When upstream adds new global flags that take values ahead of the command, extend both the command-discovery and prevalidation allowlists; when it adds command-specific flags, extend only command discovery/redaction as needed. A smaller set of global boolean flags may be followed by an optional `true`/`false` literal; when present, that literal is consumed as the flag value before command discovery continues.
|
|
202
205
|
- **`--state` disambiguation:** Persisted browser `--state` before the command participates in launch-scoped validation and tab-correction hints. The same flag spelling after a `wait` command is excluded from startup-scoped detection so upstream help examples such as `wait @ref --state hidden` do not spuriously require `sessionMode: "fresh"` while an implicit session is active. As of the current upstream baseline, the parser still does not implement those `wait --state` examples as distinct wait modes, so agent-facing docs recommend `wait --fn` predicates for disappearance checks instead.
|
|
203
206
|
- **`--auto-connect`:** Treated as launch-scoped only when enabled (`--auto-connect` bare or `true`). `--auto-connect false` is ignored for startup-scoped blocking so disabled attach hints do not force a fresh launch.
|
|
204
207
|
- **`--webgpu`:** Treated as launch-scoped for both enabled and explicit `false` values. Enabled WebGPU selects upstream's platform-specific local-launch preset; explicit false can override an environment/config default and still belongs to a fresh browser launch. Upstream rejects enabled WebGPU with CDP, auto-connect, or provider launches.
|
|
208
|
+
- **`--no-webmcp`:** Treated as launch-scoped for bare/`true` and explicit `false` values because it selects whether upstream 0.36.0 enables experimental WebMCP for a locally managed Chrome launch.
|
|
205
209
|
- **`--headed`:** Treated as launch-scoped for both enabled and explicit `false` values so a visible-window choice cannot be silently ignored by an already-running managed session.
|
|
206
|
-
- **`--allowed-domains`:** Treated as launch-scoped so
|
|
210
|
+
- **`--allowed-domains`:** Treated as launch-scoped so it cannot silently reuse an active implicit browser. Upstream 0.32.0 owns request, worker, popup, and WebRTC containment plus incompatible-mode rejection; the wrapper passes the setting and result through unchanged.
|
|
207
211
|
|
|
208
|
-
**Sessionless inspection and local commands:** Plain-text
|
|
212
|
+
**Sessionless inspection and local commands:** Plain-text help/version probes and upstream commands that do not require a page skip implicit managed-session injection. This includes read-only skills, local auth/profile/setup commands, `session list`, and syntactically local state lifecycle operations. State/session rows, restore identifiers, wrapper-prefixed session targets, caller-selected paths, upstream config, file access, launch arguments, and environment variables pass through unchanged. Browser-backed or context-dependent commands receive normal managed-session injection only when the caller did not choose an explicit session. `extensions/agent-browser/lib/page-target-validation.ts` owns only page-target correctness: after an ambiguous tab, attachment, history, script, or state-load transition, content reads require a live `get url` or explicit navigation so the wrapper cannot silently act on the wrong page. Command-shape allowlisting lives in `extensions/agent-browser/lib/command-policy.ts` (`needsManagedSession`), while `extensions/agent-browser/lib/runtime.ts` (`isPlainTextInspectionArgs`, `buildExecutionPlan`) applies that decision to execution planning.
|
|
209
213
|
|
|
210
214
|
A successful unnamed `sessionMode: "fresh"` launch should become the new extension-managed session so later default calls follow that browser instead of silently snapping back to the older managed session.
|
|
211
215
|
|
|
@@ -228,8 +232,8 @@ Upstream restore-state persistence remains upstream-owned. The wrapper passes an
|
|
|
228
232
|
- tool registration and schema (including the optional `semanticAction` compilation path to upstream `find` or `select`)
|
|
229
233
|
- 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
|
|
230
234
|
- clear missing-binary errors
|
|
231
|
-
- 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
|
|
232
|
-
- 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). 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)
|
|
235
|
+
- 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. Shared URL redaction covers SAMLRequest/SAMLResponse/RelayState and auth-context-only state/nonce at model/persisted-artifact boundaries while retaining exact internal page-target URLs; snapshot spills are redacted before disk writes
|
|
236
|
+
- bounded machine-readable outcome metadata on tool `details` (`resultCategory`, `successCategory`, `failureCategory`, optional `nextActions`, optional `pageChangeSummary` with observed-vs-dispatched evidence and 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). 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)
|
|
233
237
|
- inline screenshots/images for the plain `screenshot` command; other image-like saves (for example `diff screenshot`) still appear in `details.artifacts` and summaries but are not auto-inlined as Pi image attachments (see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details))
|
|
234
238
|
- lightweight session convenience
|
|
235
239
|
- docs, including a repo-readable command reference that mirrors the blocked direct-binary help path closely enough for normal agent work
|