pi-agent-browser-native 0.2.77 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +69 -35
  3. package/dist/extensions/agent-browser/index.js +911 -121
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -8
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +48 -7
  6. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +1 -6
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -8
  9. package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
  10. package/dist/extensions/agent-browser/lib/config.js +0 -6
  11. package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
  12. package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
  13. package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
  14. package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
  15. package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
  16. package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -9
  17. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -7
  18. package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -29
  19. package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
  20. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -17
  21. package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
  22. package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
  23. package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
  24. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +12 -5
  25. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
  26. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -4
  27. package/dist/extensions/agent-browser/lib/managed-session-restore.js +55 -48
  28. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -9
  29. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +48 -36
  30. package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -29
  31. package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
  32. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +27 -26
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +58 -33
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +22 -14
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  41. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  42. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  43. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +148 -34
  44. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +225 -61
  45. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +74 -30
  46. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +47 -26
  47. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +40 -19
  48. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
  49. package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
  50. package/dist/extensions/agent-browser/lib/parsing.js +0 -7
  51. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +36 -11
  52. package/dist/extensions/agent-browser/lib/playbook.js +20 -24
  53. package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
  54. package/dist/extensions/agent-browser/lib/process-identity.js +4 -9
  55. package/dist/extensions/agent-browser/lib/process.js +131 -48
  56. package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
  57. package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
  58. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -13
  59. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +68 -11
  60. package/dist/extensions/agent-browser/lib/results/categories.js +7 -9
  61. package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
  62. package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
  63. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
  64. package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
  65. package/dist/extensions/agent-browser/lib/results/network.js +0 -7
  66. package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -35
  67. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -44
  68. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
  69. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
  70. package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -5
  71. package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
  72. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -6
  73. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
  74. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
  75. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
  76. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -16
  77. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
  78. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -15
  79. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
  80. package/dist/extensions/agent-browser/lib/results/presentation.js +6 -8
  81. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -7
  82. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
  83. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -15
  84. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -14
  85. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
  86. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
  87. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
  88. package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
  89. package/dist/extensions/agent-browser/lib/results/text.js +0 -7
  90. package/dist/extensions/agent-browser/lib/runtime.js +119 -52
  91. package/dist/extensions/agent-browser/lib/session-page-state.js +62 -17
  92. package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
  93. package/dist/extensions/agent-browser/lib/temp.js +0 -7
  94. package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
  95. package/dist/extensions/agent-browser/lib/web-search.js +0 -5
  96. package/dist/extensions/agent-browser/script-worker.js +169 -0
  97. package/dist/scripts/agent-browser-target.mjs +3 -0
  98. package/docs/ARCHITECTURE.md +50 -31
  99. package/docs/COMMAND_REFERENCE.md +97 -41
  100. package/docs/ELECTRON.md +3 -19
  101. package/docs/RELEASE.md +8 -20
  102. package/docs/REQUIREMENTS.md +8 -6
  103. package/docs/SUPPORT_MATRIX.md +32 -25
  104. package/docs/TOOL_CONTRACT.md +107 -64
  105. package/docs/platform-smoke.md +1 -1
  106. package/package.json +7 -6
  107. package/platform-smoke.config.mjs +2 -2
  108. package/scripts/agent-browser-capability-baseline.mjs +24 -6
  109. package/scripts/agent-browser-target.mjs +3 -0
  110. package/scripts/build.mjs +41 -0
  111. package/scripts/doctor.mjs +8 -7
  112. package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
  113. package/scripts/platform-smoke/targets.mjs +12 -6
  114. package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
  115. package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
  116. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
  117. package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
  118. package/dist/extensions/agent-browser/lib/results.js +0 -14
@@ -1,23 +1,17 @@
1
- /**
2
- * Purpose: Build safe, deterministic agent-browser invocations and persisted session state for the pi-agent-browser extension.
3
- * Responsibilities: Validate raw tool arguments, derive extension-managed session names from the pi session identity, restore managed-session state from persisted tool details, redact sensitive invocation text, classify browser-oriented prompts, and build the effective CLI argument list passed to the upstream agent-browser binary.
4
- * Scope: Runtime-planning helpers only; no subprocess execution or filesystem access.
5
- * Usage: Imported by the extension entrypoint and unit tests before spawning the upstream CLI.
6
- * Invariants/Assumptions: The wrapper stays thin, preserves upstream command vocabulary, keeps plain-text inspection stateless,
7
- * and only injects wrapper-owned flags: `--json`, an extension-managed `--session` when appropriate, the narrow
8
- * site-specific headless compatibility `--user-agent` when that workaround applies.
9
- */
10
1
  import { createHash, randomUUID } from "node:crypto";
11
2
  import { basename } from "node:path";
12
- import { findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
13
- import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, getBooleanFlagValue, GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, PREVALIDATED_VALUE_FLAGS, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
3
+ import { extractUpstreamCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
4
+ import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
5
+ import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, getBooleanFlagValue, GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, PREVALIDATED_VALUE_FLAGS, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
14
6
  import { needsManagedSession } from "./command-policy.js";
15
7
  import { isWrapperManagedSessionName, redactManagedSessionRestoreKeys } from "./managed-session-capabilities.js";
16
- import { isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
8
+ import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
17
9
  import { hasLaunchScopedFlagToken, LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL, } from "./launch-scoped-flags.js";
18
10
  import { MANAGED_SESSION_NAME_PREFIX, } from "./managed-session-restore.js";
19
- export { extractCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo } from "./argv-descriptor.js";
11
+ export { extractCommandTokens, extractUpstreamCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, parseWaitCommandTokens } from "./argv-descriptor.js";
20
12
  import { isRecord } from "./parsing.js";
13
+ import { getAgentBrowserProcessEnvironment } from "./process-environment.js";
14
+ import { TARGET_AGENT_BROWSER_VERSION } from "./upstream-version.js";
21
15
  const OPENAI_HEADLESS_COMPAT_HOSTS = new Set(["chat.com", "chat.openai.com", "chatgpt.com"]);
22
16
  const CLOUDFLARE_HEADLESS_COMPAT_HOST = "dash.cloudflare.com";
23
17
  const AGENT_BROWSER_IDLE_TIMEOUT_ENV = "AGENT_BROWSER_IDLE_TIMEOUT_MS";
@@ -37,6 +31,7 @@ const DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM = {
37
31
  };
38
32
  const FALLBACK_HEADLESS_COMPAT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
39
33
  const SHELL_OPERATOR_TOKENS = new Set(["&&", "||", "|", ";", ">", ">>", "<"]);
34
+ const ANDROID_SESSION_NAME_IDENTITY_LENGTH = 20;
40
35
  const MAX_PROJECT_SLUG_LENGTH = 24;
41
36
  const SESSION_NAME_CWD_HASH_LENGTH = 8;
42
37
  const SESSION_NAME_SESSION_ID_LENGTH = 12;
@@ -320,7 +315,7 @@ function parseTimeoutMs(rawValue, minimumValue) {
320
315
  }
321
316
  return parsedValue;
322
317
  }
323
- export function getImplicitSessionIdleTimeoutMs(env = process.env) {
318
+ export function getImplicitSessionIdleTimeoutMs(env = getAgentBrowserProcessEnvironment()) {
324
319
  return parseTimeoutMs(env[IMPLICIT_SESSION_IDLE_TIMEOUT_ENV], 0) ??
325
320
  parseTimeoutMs(env[AGENT_BROWSER_IDLE_TIMEOUT_ENV], 0) ??
326
321
  DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS;
@@ -342,7 +337,7 @@ function getUnsupportedLeadingIdentityAssignment(args) {
342
337
  }
343
338
  return undefined;
344
339
  }
345
- export function getImplicitSessionCloseTimeoutMs(env = process.env) {
340
+ export function getImplicitSessionCloseTimeoutMs(env = getAgentBrowserProcessEnvironment()) {
346
341
  return parseTimeoutMs(env[IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV], 0) ?? DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS;
347
342
  }
348
343
  export function resolveManagedSessionState(options) {
@@ -368,7 +363,7 @@ export function resolveManagedSessionState(options) {
368
363
  sessionName: managedSessionName,
369
364
  };
370
365
  }
371
- function isRestorableManagedSessionName(sessionName, fallbackSessionName) {
366
+ export function isRestorableManagedSessionName(sessionName, fallbackSessionName) {
372
367
  return sessionName === fallbackSessionName || sessionName.startsWith(`${fallbackSessionName}-fresh-`);
373
368
  }
374
369
  function getManagedSessionRestoreRank(options) {
@@ -390,15 +385,18 @@ function getManagedSessionRestoreRank(options) {
390
385
  function getRestorableManagedSessionName(value, fallbackSessionName) {
391
386
  return typeof value === "string" && isRestorableManagedSessionName(value, fallbackSessionName) ? value : undefined;
392
387
  }
393
- function getElectronCleanupClosedManagedSessionNames(details, fallbackSessionName) {
388
+ function getElectronCleanupClosedManagedSessions(details, fallbackSessionName) {
394
389
  const electron = isRecord(details.electron) ? details.electron : undefined;
395
390
  const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
396
391
  const results = Array.isArray(cleanup?.results) ? cleanup.results : [];
397
- const closedSessionNames = new Set();
392
+ const closedSessions = [];
398
393
  for (const result of results) {
399
394
  if (!isRecord(result) || !Array.isArray(result.steps))
400
395
  continue;
401
396
  const record = isRecord(result.record) ? result.record : undefined;
397
+ const fallbackNamespace = typeof record?.namespace === "string"
398
+ ? record.namespace
399
+ : typeof details.namespace === "string" ? details.namespace : undefined;
402
400
  for (const step of result.steps) {
403
401
  if (!isRecord(step) || step.resource !== "managed-session")
404
402
  continue;
@@ -406,11 +404,12 @@ function getElectronCleanupClosedManagedSessionNames(details, fallbackSessionNam
406
404
  continue;
407
405
  const sessionName = getRestorableManagedSessionName(step.sessionName, fallbackSessionName)
408
406
  ?? getRestorableManagedSessionName(record?.sessionName, fallbackSessionName);
407
+ const namespace = typeof step.namespace === "string" ? step.namespace : fallbackNamespace;
409
408
  if (sessionName)
410
- closedSessionNames.add(sessionName);
409
+ closedSessions.push({ namespace, sessionName });
411
410
  }
412
411
  }
413
- return [...closedSessionNames];
412
+ return closedSessions;
414
413
  }
415
414
  export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName) {
416
415
  const restoreDisabledIdentities = new Map();
@@ -450,16 +449,23 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
450
449
  if (isPlainTextInspectionArgs(args)) {
451
450
  continue;
452
451
  }
453
- for (const sessionName of getElectronCleanupClosedManagedSessionNames(details, fallbackSessionName)) {
454
- applyManagedClose(sessionName);
452
+ for (const cleanupSession of getElectronCleanupClosedManagedSessions(details, fallbackSessionName)) {
453
+ applyManagedClose(cleanupSession.sessionName, cleanupSession.namespace);
455
454
  }
456
455
  const explicitSessionName = extractExplicitSessionName(args);
457
456
  const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
458
457
  const namespace = canonicalizeAgentBrowserNamespace(typeof details.namespace === "string" ? details.namespace : undefined);
459
458
  const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
460
459
  const usedImplicitSession = details.usedImplicitSession === true;
460
+ const commandTokens = extractUpstreamCommandTokens(args);
461
461
  const command = typeof details.command === "string" ? details.command : parseCommandInfo(args).command;
462
- const commandClosesSession = isCloseCommand(command);
462
+ const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
463
+ const nestedBatchEndsClosed = batchCloseLifecycle?.endsClosed === true;
464
+ const nestedBatchRemainsActive = batchCloseLifecycle?.endsClosed === false;
465
+ const closeAllApplied = details.closeAllApplied === true
466
+ || (message.isError !== true && isCloseAllCommand(commandTokens))
467
+ || batchHasSuccessfulCloseAll(details.batchSteps);
468
+ const commandClosesSession = isCloseCommand(command) || nestedBatchEndsClosed;
463
469
  const outcome = typeof details.managedSessionOutcome === "object" && details.managedSessionOutcome !== null ? details.managedSessionOutcome : undefined;
464
470
  const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
465
471
  const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
@@ -468,6 +474,14 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
468
474
  ? outcomeAttemptedSessionName ?? getRestorableManagedSessionName(outcomeCurrentSessionName, fallbackSessionName) ?? getRestorableManagedSessionName(sessionName, fallbackSessionName)
469
475
  : undefined;
470
476
  const restorableDetailSessionName = getRestorableManagedSessionName(sessionName, fallbackSessionName);
477
+ if (closeAllApplied && restoredState.active) {
478
+ const restoredKey = getAgentBrowserSessionIdentityKey(restoredState.sessionName, restoredState.namespace);
479
+ const resultKey = restorableDetailSessionName ? getAgentBrowserSessionIdentityKey(restorableDetailSessionName, namespace) : undefined;
480
+ const retainsCurrentSession = nestedBatchRemainsActive && resultKey === restoredKey;
481
+ if (isAgentBrowserSessionIdentityKeyInNamespace(restoredKey, namespace) && !retainsCurrentSession) {
482
+ applyManagedClose(restoredState.sessionName, restoredState.namespace);
483
+ }
484
+ }
471
485
  const explicitCloseSessionName = commandClosesSession && explicitSessionName && restorableDetailSessionName === explicitSessionName
472
486
  ? restorableDetailSessionName
473
487
  : undefined;
@@ -476,13 +490,13 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
476
490
  if (details.managedSessionRestoreDisabled === true && typeof sessionName === "string") {
477
491
  restoreDisabledIdentities.set(getAgentBrowserSessionIdentityKey(sessionName, namespace), { namespace, sessionName });
478
492
  }
479
- const managedSessionName = !explicitSessionName &&
493
+ const managedSessionName = outcomeClosedSessionName ?? (!explicitSessionName &&
480
494
  restorableDetailSessionName &&
481
495
  (usedImplicitSession || sessionMode === "fresh")
482
496
  ? restorableDetailSessionName
483
497
  : commandClosesSession
484
- ? outcomeClosedSessionName ?? explicitCloseSessionName
485
- : undefined;
498
+ ? explicitCloseSessionName
499
+ : undefined);
486
500
  if (!managedSessionName) {
487
501
  continue;
488
502
  }
@@ -499,9 +513,11 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
499
513
  const exitCode = typeof details.exitCode === "number" ? details.exitCode : undefined;
500
514
  const outcomeActiveAfter = outcome?.activeAfter === true;
501
515
  const outcomeRepresentsActiveCurrentSession = outcomeActiveAfter && outcomeCurrentSessionName === managedSessionName && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged");
502
- const succeeded = outcomeRepresentsActiveCurrentSession ? true : messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
503
- if (commandClosesSession) {
504
- if (succeeded) {
516
+ const succeeded = outcomeRepresentsActiveCurrentSession || nestedBatchRemainsActive
517
+ ? true
518
+ : messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
519
+ if (commandClosesSession || outcomeClosedSessionName) {
520
+ if (nestedBatchEndsClosed || outcomeClosedSessionName || succeeded) {
505
521
  restoreDisabledIdentities.delete(getAgentBrowserSessionIdentityKey(managedSessionName, namespace));
506
522
  applyManagedClose(managedSessionName, namespace);
507
523
  }
@@ -538,15 +554,24 @@ export function createEphemeralSessionSeed() {
538
554
  function createCwdHash(cwd) {
539
555
  return createHash("sha256").update(`cwd:${cwd}`).digest("hex").slice(0, SESSION_NAME_CWD_HASH_LENGTH);
540
556
  }
541
- export function createImplicitSessionName(sessionId, cwd, ephemeralSeed) {
557
+ export function createImplicitSessionName(sessionId, cwd, ephemeralSeed, platform = process.platform) {
558
+ const normalizedSessionId = sessionId?.replaceAll("-", "").toLowerCase();
559
+ if (platform === "android") {
560
+ const identity = normalizedSessionId ? `session:${normalizedSessionId}:cwd:${cwd}` : `ephemeral:${cwd}:${ephemeralSeed}`;
561
+ const digest = createHash("sha256").update(identity).digest("hex").slice(0, ANDROID_SESSION_NAME_IDENTITY_LENGTH);
562
+ return `${MANAGED_SESSION_NAME_PREFIX}${digest}`;
563
+ }
542
564
  const slug = basename(cwd)
543
565
  .toLowerCase()
544
566
  .replace(/[^a-z0-9]+/g, "-")
545
567
  .replace(/^-+|-+$/g, "")
546
568
  .slice(0, MAX_PROJECT_SLUG_LENGTH) || "project";
547
569
  const cwdHash = createCwdHash(cwd);
548
- const stableSessionId = sessionId?.replace(/-/g, "").slice(0, SESSION_NAME_SESSION_ID_LENGTH);
549
- if (stableSessionId && stableSessionId.length > 0) {
570
+ if (normalizedSessionId) {
571
+ const stableSessionId = createHash("sha256")
572
+ .update(`session:${normalizedSessionId}`)
573
+ .digest("hex")
574
+ .slice(0, SESSION_NAME_SESSION_ID_LENGTH);
550
575
  return `${MANAGED_SESSION_NAME_PREFIX}${slug}-${stableSessionId}-${cwdHash}`;
551
576
  }
552
577
  const digest = createHash("sha256")
@@ -563,7 +588,7 @@ export function createFreshSessionName(baseSessionName, ephemeralSeed, ordinal)
563
588
  return `${baseSessionName}-fresh-${suffix}`;
564
589
  }
565
590
  function getSingleKeyCommandValidationError(args) {
566
- const { commandInfo, commandTokens } = parseArgvDescriptor(args);
591
+ const { commandInfo, upstreamCommandTokens: commandTokens } = parseArgvDescriptor(args);
567
592
  const command = commandInfo.command;
568
593
  if (command !== "press" && command !== "key" && command !== "keydown" && command !== "keyup")
569
594
  return undefined;
@@ -573,13 +598,19 @@ function getSingleKeyCommandValidationError(args) {
573
598
  return `agent-browser ${label} accepts exactly one key argument. Do not pass a selector or ref to ${label}; focus or click the target first, then run ${command} <key> (for example: focus @e1, then press Enter).`;
574
599
  }
575
600
  function getBareMcpValidationError(args) {
576
- const { commandInfo, commandTokens } = parseArgvDescriptor(args);
601
+ const { commandInfo, upstreamCommandTokens: commandTokens } = parseArgvDescriptor(args);
577
602
  if (commandInfo.command !== "mcp")
578
603
  return undefined;
579
604
  if (commandTokens.includes("--help") || commandTokens.includes("-h"))
580
605
  return undefined;
581
606
  return "agent-browser mcp starts a stdio MCP server for external MCP clients, not a one-shot native agent_browser tool workflow. Use the native agent_browser tool modes directly, or configure an MCP client to launch `agent-browser mcp`. Use `mcp --help` for help.";
582
607
  }
608
+ function getUnsupportedInlineWaitDownloadError(args) {
609
+ const descriptor = parseArgvDescriptor(args);
610
+ if (descriptor.commandInfo.command !== "wait" || !descriptor.upstreamCommandTokens.some((token) => token.startsWith("--download=")))
611
+ return undefined;
612
+ return `agent-browser ${TARGET_AGENT_BROWSER_VERSION} does not support \`wait --download=<path>\`. Pass the optional path as a separate argument: \`wait --download <path>\` (or \`wait -d <path>\`).`;
613
+ }
583
614
  export function validateToolArgs(args) {
584
615
  if (args.length === 0) {
585
616
  return "`args` must contain at least one agent-browser command token.";
@@ -592,7 +623,7 @@ export function validateToolArgs(args) {
592
623
  if (sessionModeArg) {
593
624
  return "Do not pass `--session-mode` in args. Use the top-level agent_browser `sessionMode` field instead, for example { args: [\"--profile\", \"Default\", \"open\", \"https://example.com\"], sessionMode: \"fresh\" }.";
594
625
  }
595
- return getBareMcpValidationError(args) ?? getSingleKeyCommandValidationError(args);
626
+ return getBareMcpValidationError(args) ?? getSingleKeyCommandValidationError(args) ?? getUnsupportedInlineWaitDownloadError(args);
596
627
  }
597
628
  function getInvalidValueFlagDetails(args) {
598
629
  for (let index = 0; index < args.length; index += 1) {
@@ -616,7 +647,7 @@ function getInvalidValueFlagDetails(args) {
616
647
  continue;
617
648
  }
618
649
  const receivedToken = args[index + 1];
619
- if (receivedToken === undefined) {
650
+ if (receivedToken === undefined || (normalizedToken === "--args" && receivedToken.length === 0)) {
620
651
  return {
621
652
  flag: normalizedToken,
622
653
  index,
@@ -692,16 +723,15 @@ function parseComparableNavigationUrl(url) {
692
723
  export function getDefaultHeadlessCompatUserAgent(platform = process.platform) {
693
724
  return DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM[platform] ?? FALLBACK_HEADLESS_COMPAT_USER_AGENT;
694
725
  }
695
- export function canUseHeadlessCompatibilityUserAgent(args, env = process.env) {
726
+ export function canUseHeadlessCompatibilityUserAgent(args, env = getAgentBrowserProcessEnvironment()) {
696
727
  if (hasFlagToken(args, "--user-agent") || hasFlagToken(args, "--args"))
697
728
  return false;
698
729
  if (hasFlagToken(args, "--cdp") || hasFlagToken(args, "--provider") || hasFlagToken(args, "-p"))
699
730
  return false;
700
731
  if (env.AGENT_BROWSER_USER_AGENT !== undefined || env.AGENT_BROWSER_ARGS !== undefined || env.AGENT_BROWSER_CDP !== undefined || env.AGENT_BROWSER_PROVIDER !== undefined)
701
732
  return false;
702
- const envFlagEnabled = (value) => value !== undefined && !["", "0", "false", "no"].includes(value.toLowerCase());
703
- if ((getBooleanFlagValue(args, "--headed") ?? envFlagEnabled(env.AGENT_BROWSER_HEADED))
704
- || (getBooleanFlagValue(args, "--auto-connect") ?? envFlagEnabled(env.AGENT_BROWSER_AUTO_CONNECT)))
733
+ if ((getBooleanFlagValue(args, "--headed") ?? isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_HEADED))
734
+ || (getBooleanFlagValue(args, "--auto-connect") ?? isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_AUTO_CONNECT)))
705
735
  return false;
706
736
  const engine = scanUpstreamGlobalFlagOccurrences(args, "--engine").at(-1)?.value ?? env.AGENT_BROWSER_ENGINE;
707
737
  return !engine || engine === "chrome";
@@ -726,6 +756,14 @@ function getCompatibilityWorkaround(args, commandInfo) {
726
756
  reason: "OpenAI web properties currently challenge the default headless Chrome user agent; inject a normal Chrome user agent to preserve the default headless workflow without requiring headed mode or auto-connect.",
727
757
  };
728
758
  }
759
+ function stripExplicitSessionArgs(args) {
760
+ const sessionTokenIndexes = new Set();
761
+ for (const occurrence of scanUpstreamGlobalFlagOccurrences(args, "--session")) {
762
+ sessionTokenIndexes.add(occurrence.index);
763
+ sessionTokenIndexes.add(occurrence.index + 1);
764
+ }
765
+ return args.filter((_token, index) => !sessionTokenIndexes.has(index));
766
+ }
729
767
  function stripExplicitNamespaceArgs(args) {
730
768
  const namespaceTokenIndexes = new Set();
731
769
  for (const occurrence of scanUpstreamGlobalFlagOccurrences(args, "--namespace")) {
@@ -751,7 +789,7 @@ export function buildExecutionPlan(args, options) {
751
789
  const commandInfo = argvDescriptor.commandInfo;
752
790
  const commandNeedsManagedSession = !plainTextInspection && needsManagedSession(argvDescriptor);
753
791
  const effectiveArgs = plainTextInspection ? [...args] : args.includes("--json") ? [] : ["--json"];
754
- let namespace = explicitNamespace;
792
+ let namespace = explicitNamespacePresent ? explicitNamespace ?? "" : undefined;
755
793
  if (plainTextInspection) {
756
794
  return {
757
795
  commandInfo,
@@ -780,7 +818,7 @@ export function buildExecutionPlan(args, options) {
780
818
  plainTextInspection: false,
781
819
  startupScopedFlags: [],
782
820
  usedImplicitSession: false,
783
- validationError: `${unsupportedIdentityAssignment}=... is not supported by agent-browser 0.33.2. Pass ${unsupportedIdentityAssignment} and its value as separate arguments.`,
821
+ validationError: `${unsupportedIdentityAssignment}=... is not supported by agent-browser ${TARGET_AGENT_BROWSER_VERSION}. Pass ${unsupportedIdentityAssignment} and its value as separate arguments.`,
784
822
  };
785
823
  }
786
824
  for (const flag of ["--session", "--namespace"]) {
@@ -797,7 +835,7 @@ export function buildExecutionPlan(args, options) {
797
835
  }
798
836
  const explicitSessionName = extractExplicitSessionName(args);
799
837
  if (explicitSessionName && !isWrapperManagedSessionName(explicitSessionName)) {
800
- namespace = resolveAgentBrowserNamespace(args, process.env.AGENT_BROWSER_NAMESPACE);
838
+ namespace = resolveAgentBrowserNamespace(args, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
801
839
  }
802
840
  const shouldCreateFreshManagedSession = !explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && !isCloseCommand(commandInfo.command);
803
841
  let argsToAppend = args;
@@ -826,8 +864,8 @@ export function buildExecutionPlan(args, options) {
826
864
  ].join(" ");
827
865
  }
828
866
  else {
829
- namespace = explicitNamespacePresent ? explicitNamespace : managedSessionNamespace;
830
- if (namespace)
867
+ namespace = explicitNamespacePresent ? explicitNamespace ?? "" : managedSessionNamespace;
868
+ if (namespace !== undefined)
831
869
  effectiveArgs.push("--namespace", namespace);
832
870
  effectiveArgs.push("--session", options.managedSessionName);
833
871
  if (explicitNamespacePresent)
@@ -838,7 +876,7 @@ export function buildExecutionPlan(args, options) {
838
876
  }
839
877
  }
840
878
  else if (shouldCreateFreshManagedSession && commandNeedsManagedSession) {
841
- if (namespace)
879
+ if (namespace !== undefined)
842
880
  effectiveArgs.push("--namespace", namespace);
843
881
  effectiveArgs.push("--session", options.freshSessionName);
844
882
  if (explicitNamespacePresent)
@@ -846,14 +884,43 @@ export function buildExecutionPlan(args, options) {
846
884
  managedSessionName = options.freshSessionName;
847
885
  sessionName = options.freshSessionName;
848
886
  }
849
- if (!compatibilityWorkaround
850
- && canUseHeadlessCompatibilityUserAgent(args)
851
- && options.managedSessionActive
887
+ const targetsActiveManagedSession = options.managedSessionActive
852
888
  && sessionName
853
- && getAgentBrowserSessionIdentityKey(sessionName, namespace) === getAgentBrowserSessionIdentityKey(options.managedSessionName, options.managedSessionNamespace)) {
854
- compatibilityWorkaround = options.managedSessionCompatibilityWorkaround;
889
+ && getAgentBrowserSessionIdentityKey(sessionName, namespace) === getAgentBrowserSessionIdentityKey(options.managedSessionName, options.managedSessionNamespace);
890
+ if (targetsActiveManagedSession && startupScopedFlags.length > 0 && !isCloseCommand(commandInfo.command) && !validationError) {
891
+ const recoveryArgs = explicitSessionName ? stripExplicitSessionArgs(args) : args;
892
+ recoveryHint = {
893
+ exampleArgs: recoveryArgs,
894
+ exampleParams: { args: recoveryArgs, sessionMode: "fresh" },
895
+ reason: `Launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}) need a fresh upstream launch once the extension-managed session is already active.`,
896
+ recommendedSessionMode: "fresh",
897
+ };
898
+ validationError = [
899
+ `The current extension-managed agent-browser session is already running, so launch-scoped flags ${startupScopedFlags.join(", ")} would replace or be ignored by upstream agent-browser.`,
900
+ explicitSessionName
901
+ ? "Remove the explicit `--session` and retry with `sessionMode: \"fresh\"` to force a fresh upstream launch."
902
+ : "Retry this call with `sessionMode: \"fresh\"` to force a fresh upstream launch.",
903
+ ].join(" ");
904
+ }
905
+ if (targetsActiveManagedSession && canUseHeadlessCompatibilityUserAgent(args)) {
906
+ if (requestedCompatibilityWorkaround && !options.managedSessionCompatibilityWorkaround && !validationError) {
907
+ compatibilityWorkaround = undefined;
908
+ const recoveryArgs = explicitSessionName ? stripExplicitSessionArgs(args) : args;
909
+ recoveryHint = {
910
+ exampleArgs: recoveryArgs,
911
+ exampleParams: { args: recoveryArgs, sessionMode: "fresh" },
912
+ reason: "The requested site compatibility user agent is launch-scoped and needs a fresh browser session.",
913
+ recommendedSessionMode: "fresh",
914
+ };
915
+ validationError = explicitSessionName
916
+ ? "The current extension-managed agent-browser session is already running without the user agent required by this site. Remove the explicit `--session` and retry with `sessionMode: \"fresh\"` so the compatibility user agent is applied at launch."
917
+ : "The current extension-managed agent-browser session is already running without the user agent required by this site. Retry this call with `sessionMode: \"fresh\"` so the compatibility user agent is applied at launch.";
918
+ }
919
+ else {
920
+ compatibilityWorkaround = requestedCompatibilityWorkaround ?? options.managedSessionCompatibilityWorkaround;
921
+ }
855
922
  }
856
- if (compatibilityWorkaround) {
923
+ if (compatibilityWorkaround && !targetsActiveManagedSession) {
857
924
  effectiveArgs.push("--user-agent", getDefaultHeadlessCompatUserAgent());
858
925
  }
859
926
  effectiveArgs.push(...argsToAppend);
@@ -1,12 +1,7 @@
1
- /**
2
- * Purpose: Own wrapper-known per-session browser page target, ref snapshot, invalidation, and pinning state.
3
- * Responsibilities: Restore state from persisted tool details, apply ordered tab/ref updates atomically, and expose order-free public state views to the extension entrypoint.
4
- * Scope: Session page state only; browser process execution, tab probing, and presentation policies stay in the extension entrypoint.
5
- * Usage: `index.ts` creates one store per Pi session lifecycle and records observations through update tokens.
6
- * Invariants/Assumptions: One tool-call update token must govern all page-state observations from that invocation; stale overlapping updates must not overwrite newer state.
7
- */
8
- import { getAgentBrowserSessionIdentityKey } from "./argv-grammar.js";
9
- import { isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
1
+ import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
2
+ import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "./argv-grammar.js";
3
+ import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
4
+ import { isCloseAllCommand, isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
10
5
  import { isRecord } from "./parsing.js";
11
6
  import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
12
7
  import { enrichSnapshotRefEntries, getSnapshotRefEntries } from "./results/snapshot-refs.js";
@@ -100,6 +95,11 @@ export function extractSessionTabTargetFromBatchResults(data) {
100
95
  }
101
96
  const [name, subcommand] = extractBatchResultCommand(item);
102
97
  const result = item.result;
98
+ if (isCloseCommand(name)) {
99
+ currentTarget = undefined;
100
+ pendingTitle = undefined;
101
+ continue;
102
+ }
103
103
  if (name === "get" && subcommand === "title") {
104
104
  pendingTitle = extractStringResultField(result, "title");
105
105
  continue;
@@ -204,6 +204,12 @@ export function buildNoActivePageRefSnapshotInvalidation() {
204
204
  summary: "The latest snapshot for this session reported No active page. Old page-scoped refs are invalid until snapshot -i succeeds.",
205
205
  };
206
206
  }
207
+ export function buildPageTransitionRefSnapshotInvalidation(summary) {
208
+ return {
209
+ reason: "page-transition",
210
+ summary: summary ?? "A recording command (record start, or record restart with a URL) replaced or navigated the active page and invalidated the prior snapshot. Run snapshot -i before using page-scoped refs.",
211
+ };
212
+ }
207
213
  export function isNoActivePageSnapshotFailure(command, text) {
208
214
  return command === "snapshot" && /\bno active page\b/i.test(text ?? "");
209
215
  }
@@ -214,7 +220,16 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
214
220
  for (const item of data) {
215
221
  if (!isRecord(item))
216
222
  continue;
217
- const [name] = extractBatchResultCommand(item);
223
+ const commandTokens = extractBatchResultCommand(item);
224
+ const [name] = commandTokens;
225
+ if (item.success !== false && isCloseCommand(name)) {
226
+ latestState = undefined;
227
+ continue;
228
+ }
229
+ if (isRecordPageTransitionCommand(commandTokens)) {
230
+ latestState = { invalidation: buildPageTransitionRefSnapshotInvalidation() };
231
+ continue;
232
+ }
218
233
  if (name !== "snapshot")
219
234
  continue;
220
235
  if (item.success === false) {
@@ -232,9 +247,10 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
232
247
  }
233
248
  function getRestoredRefSnapshotInvalidation(details, command) {
234
249
  const invalidation = isRecord(details.refSnapshotInvalidation) ? details.refSnapshotInvalidation : undefined;
235
- if (invalidation && invalidation.reason === "no-active-page") {
250
+ if (invalidation?.reason === "no-active-page")
236
251
  return buildNoActivePageRefSnapshotInvalidation();
237
- }
252
+ if (invalidation?.reason === "page-transition")
253
+ return buildPageTransitionRefSnapshotInvalidation(typeof invalidation.summary === "string" ? invalidation.summary : undefined);
238
254
  const errorText = typeof details.error === "string"
239
255
  ? details.error
240
256
  : typeof details.summary === "string"
@@ -323,14 +339,27 @@ export class SessionPageState {
323
339
  const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
324
340
  const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
325
341
  const sessionKey = getSessionPageStateKey(sessionName, namespace);
342
+ const args = Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string") ? details.args : [];
343
+ const commandTokens = extractUpstreamCommandTokens(args);
344
+ const command = typeof details.command === "string" ? details.command : commandTokens[0];
345
+ const subcommand = typeof details.subcommand === "string" ? details.subcommand : commandTokens[1];
346
+ const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
347
+ const closeAllApplied = details.closeAllApplied === true
348
+ || (message.isError !== true && isCloseAllCommand(commandTokens))
349
+ || batchHasSuccessfulCloseAll(details.batchSteps);
350
+ if (closeAllApplied) {
351
+ restoredOrder += 1;
352
+ state.clearNamespace(namespace);
353
+ if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
354
+ continue;
355
+ }
326
356
  if (!sessionKey)
327
357
  continue;
328
- const command = typeof details.command === "string" ? details.command : undefined;
329
- const subcommand = typeof details.subcommand === "string" ? details.subcommand : undefined;
330
- if (isCloseCommand(command) && message.isError !== true) {
358
+ if (!closeAllApplied && ((isCloseCommand(command) && message.isError !== true) || batchCloseLifecycle)) {
331
359
  restoredOrder += 1;
332
360
  state.clearSession(sessionKey);
333
- continue;
361
+ if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
362
+ continue;
334
363
  }
335
364
  const tabTarget = getRestoredSessionTabTarget(details, command, subcommand);
336
365
  const tabTargetUnknown = details.sessionTabTargetUnknown === true;
@@ -340,8 +369,11 @@ export class SessionPageState {
340
369
  continue;
341
370
  restoredOrder += 1;
342
371
  if (tabTargetUnknown) {
343
- state.refSnapshotInvalidations.delete(sessionKey);
344
372
  state.refSnapshots.delete(sessionKey);
373
+ if (refSnapshotInvalidation)
374
+ state.refSnapshotInvalidations.set(sessionKey, { ...refSnapshotInvalidation, order: restoredOrder });
375
+ else
376
+ state.refSnapshotInvalidations.delete(sessionKey);
345
377
  state.tabTargets.delete(sessionKey);
346
378
  state.tabTargetUnknownOrders.set(sessionKey, restoredOrder);
347
379
  continue;
@@ -438,6 +470,19 @@ export class SessionPageState {
438
470
  this.tabTargetUnknownOrders.delete(sessionName);
439
471
  this.tabTargets.delete(sessionName);
440
472
  }
473
+ clearNamespace(namespace) {
474
+ const sessionKeys = new Set([
475
+ ...this.refSnapshotInvalidations.keys(),
476
+ ...this.refSnapshots.keys(),
477
+ ...this.tabPinningReasons.keys(),
478
+ ...this.tabTargetUnknownOrders.keys(),
479
+ ...this.tabTargets.keys(),
480
+ ]);
481
+ for (const sessionKey of sessionKeys) {
482
+ if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, namespace))
483
+ this.clearSession(sessionKey);
484
+ }
485
+ }
441
486
  markPinning(sessionName, reason) {
442
487
  this.tabPinningReasons.set(sessionName, reason);
443
488
  }
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Build compact JSON-schema string enums without importing pi runtime helpers.
3
- * Responsibilities: Mirror pi-ai StringEnum's `{ type: "string", enum: [...] }` shape while keeping extension startup imports light.
4
- * Scope: Schema construction only.
5
- */
6
1
  import { JsonSchema } from "./json-schema.js";
7
2
  export function StringEnum(values, options) {
8
3
  return JsonSchema.Unsafe({
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Create private temporary and persisted spill files for the pi-agent-browser extension without leaking artifacts broadly on disk.
3
- * Responsibilities: Maintain a process-private temp root, stamp explicit ownership/protected-child markers, enforce an aggregate temp-artifact disk budget, create securely permissioned temp files, create session-scoped persisted spill files for resumable sessions, prune explicitly owned stale temp roots from prior runs without deleting protected children, and best-effort clean all owned roots on process exit.
4
- * Scope: Artifact lifecycle helpers only; callers decide what data to write and when to delete or retain long-lived references.
5
- * Usage: Imported by result/process helpers when they need secure spill files instead of world-readable shared tmp paths.
6
- * Invariants/Assumptions: Temp artifacts live under the OS temp directory, each active run uses a dedicated 0700 directory, files are created with exclusive 0600 permissions, session-scoped persisted artifacts stay under the pi session directory, and stale pruning only touches roots with an explicit pi-agent-browser ownership marker.
7
- */
8
1
  import { randomBytes } from "node:crypto";
9
2
  import { existsSync, readdirSync, rmSync } from "node:fs";
10
3
  import { chmod, mkdir, mkdtemp, open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
@@ -0,0 +1,14 @@
1
+ import { TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL } from "../../../scripts/agent-browser-target.mjs";
2
+ export { TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL };
3
+ export function parseAgentBrowserVersionOutput(stdout) {
4
+ const match = stdout.trim().match(/^agent-browser\s+(\S+)$/);
5
+ return match?.[1];
6
+ }
7
+ export function getAgentBrowserVersionValidationError(stdout) {
8
+ const observed = parseAgentBrowserVersionOutput(stdout);
9
+ if (observed === TARGET_AGENT_BROWSER_VERSION)
10
+ return undefined;
11
+ return observed
12
+ ? `Installed agent-browser ${observed} does not match this extension's exact ${TARGET_AGENT_BROWSER_VERSION} capability baseline. Install ${TARGET_AGENT_BROWSER_VERSION_LABEL}, run pi-agent-browser-doctor, then reload Pi.`
13
+ : `agent-browser --version returned an unrecognized value; expected exactly ${JSON.stringify(TARGET_AGENT_BROWSER_VERSION_LABEL)}. Run pi-agent-browser-doctor and install the exact supported upstream version.`;
14
+ }
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Provide the optional provider-backed `agent_browser_web_search` companion tool.
3
- * Responsibilities: Define strict search input schema, resolve configured Brave/Exa credentials lazily, call the selected search API with cancellation/timeout, normalize compact results, and keep secrets out of content/details.
4
- * Scope: Live web search only; browser automation remains in the `agent_browser` tool.
5
- */
6
1
  import { JsonSchema } from "./json-schema.js";
7
2
  import { WEB_SEARCH_PROMPT_GUIDELINE } from "./playbook.js";
8
3
  import { StringEnum as localStringEnum } from "./string-enum-schema.js";