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,8 +1,3 @@
1
- /**
2
- * Purpose: Build the small JSON Schema subset used by Pi tool schemas without importing TypeBox at runtime.
3
- * Responsibilities: Preserve plain JSON Schema objects Pi consumes while keeping extension startup cheap.
4
- * Scope: Schema construction only; runtime validation still belongs to Pi and the tool input compilers.
5
- */
6
1
  const OPTIONAL_SCHEMA = Symbol("pi-agent-browser-optional-schema");
7
2
  function withOptions(schema, options) {
8
3
  return { ...schema, ...(options ?? {}) };
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Canonical launch-scoped agent-browser flag metadata shared by runtime planning and agent-facing guidance.
3
- * Responsibilities: Define which upstream flags require a fresh launch, explain why, expose stable guidance labels, and share launch-token detection used by runtime and managed restore policy.
4
- * Scope: Launch-scoped metadata and token classification; broader execution planning lives in runtime.ts.
5
- */
6
1
  import { findCommandStartIndex } from "./argv-descriptor.js";
7
2
  import { isBooleanFlagEnabled, optionalGlobalValueFlagConsumesNext } from "./argv-grammar.js";
8
3
  export const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
@@ -42,6 +37,18 @@ export const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
42
37
  flag: "--idle-timeout",
43
38
  reason: "configures background browser lifecycle for the launched session",
44
39
  },
40
+ {
41
+ flag: "--args",
42
+ reason: "selects raw Chrome arguments for the browser launch",
43
+ },
44
+ {
45
+ flag: "--user-agent",
46
+ reason: "selects the browser user agent at launch time",
47
+ },
48
+ {
49
+ flag: "--headed",
50
+ reason: "selects whether the launched browser has a visible window",
51
+ },
45
52
  {
46
53
  flag: "--device",
47
54
  reason: "selects the provider device for the upstream launch",
@@ -1,5 +1,3 @@
1
- // Purpose: Identify and redact wrapper-owned managed restore capabilities.
2
- // Scope: Pure string helpers shared by storage, policy, and presentation paths.
3
1
  const MANAGED_SESSION_RESTORE_KEY_PATTERN = /piab-r(?:2)?-[a-f\d]{32}/gi;
4
2
  const MANAGED_SESSION_RESTORE_KEY_EXACT_PATTERN = /^piab-r2-[a-f\d]{32}$/i;
5
3
  const WRAPPER_MANAGED_SESSION_PREFIX = "piab-";
@@ -1,6 +1,3 @@
1
- // Purpose: Serialize wrapper-owned daemon policy decisions across cooperating Pi processes.
2
- // Responsibilities: Elect one live claim, bridge the pre-update v2 path, recover proven-dead current-protocol artifacts, and release only the caller's immutable token.
3
- // Scope: Wrapper coordination only; agent-browser never reads these claims.
4
1
  import { createHash, randomUUID } from "node:crypto";
5
2
  import { lstat, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
6
3
  import { tmpdir } from "node:os";
@@ -15,7 +12,9 @@ const LOCK_TICKET_FILE = "ticket.json";
15
12
  function getCoordinationDirectory(platform = process.platform) {
16
13
  if (platform !== "win32") {
17
14
  const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
18
- return `/tmp/pi-agent-browser-policy${uid === undefined ? "" : `-${uid}`}`;
15
+ return platform === "android"
16
+ ? join(tmpdir(), `pi-agent-browser-policy${uid === undefined ? "" : `-${uid}`}`)
17
+ : `/tmp/pi-agent-browser-policy${uid === undefined ? "" : `-${uid}`}`;
19
18
  }
20
19
  const user = process.env.USERNAME ?? process.env.USER ?? "unknown";
21
20
  const suffix = createHash("sha256").update(user).digest("hex").slice(0, 12);
@@ -1,18 +1,13 @@
1
- /**
2
- * Purpose: Own automatic restore policy for wrapper-managed browser sessions.
3
- * Responsibilities: Resolve owned session identity, isolate per-call policy, reject incompatible argv/env/config, and persist sticky restore-disable state.
4
- * Scope: Restore policy only; storage identity and snapshot retention live in focused sibling modules.
5
- */
6
1
  import { AsyncLocalStorage } from "node:async_hooks";
7
2
  import { chmodSync, lstatSync, readFileSync, unlinkSync } from "node:fs";
8
- import { join } from "node:path";
9
- import { extractCommandTokens, parseCommandInfo } from "./argv-descriptor.js";
10
- import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, extractRequestedRestoreKey, getAgentBrowserSessionIdentityKey, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
3
+ import { extractUpstreamCommandTokens, parseCommandInfo } from "./argv-descriptor.js";
4
+ import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, extractRequestedRestoreKey, getAgentBrowserSessionIdentityKey, isUpstreamEnvFlagEnabled, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
11
5
  import { hasLaunchScopedFlagToken, MANAGED_RESTORE_INCOMPATIBLE_BOOLEAN_ENVS, MANAGED_RESTORE_INCOMPATIBLE_ENVS, MANAGED_RESTORE_INCOMPATIBLE_FLAGS, } from "./launch-scoped-flags.js";
12
- import { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure, getManagedSessionRestoreProtectedStorageEnv, hasManagedSessionRestoreProjectIdentity, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
6
+ import { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure, getManagedSessionRestoreScope, getManagedSessionRestoreProtectedStorageEnv, hasManagedSessionRestoreProjectIdentity, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
13
7
  import { parseUserBatchStdin } from "./orchestration/batch-stdin.js";
8
+ import { getAgentBrowserProcessEnvironment } from "./process-environment.js";
14
9
  import { writeSecureTempFile } from "./temp.js";
15
- export { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure } from "./managed-session-storage.js";
10
+ export { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure, getManagedSessionRestoreScope } from "./managed-session-storage.js";
16
11
  export { pruneOwnedManagedSessionRestoreSnapshots } from "./managed-session-snapshots.js";
17
12
  const AGENT_BROWSER_CONFIG_ENV = "AGENT_BROWSER_CONFIG";
18
13
  const AGENT_BROWSER_RESTORE_ENV = "AGENT_BROWSER_RESTORE";
@@ -28,10 +23,6 @@ function isDisabledEnvFlag(value) {
28
23
  return false;
29
24
  return ["0", "false", "no", "off"].includes(value.trim().toLowerCase());
30
25
  }
31
- /** Match upstream env_var_is_truthy exactly: lowercase only, without trimming or accepting "off". */
32
- function isUpstreamEnvFlagEnabled(value) {
33
- return value !== undefined && !["", "0", "false", "no"].includes(value.toLowerCase());
34
- }
35
26
  function hasUpstreamEnvValue(env, name) {
36
27
  return env?.[name] !== undefined;
37
28
  }
@@ -70,7 +61,7 @@ export class ManagedSessionRestoreState {
70
61
  if (sessionName)
71
62
  this.#daemonRestoreKeys.set(getAgentBrowserSessionIdentityKey(sessionName, namespace), restoreKey);
72
63
  }
73
- replace(identities, options = {}) {
64
+ replace(identities = [], options = {}) {
74
65
  if (!options.preserveDaemonRestoreKeys)
75
66
  this.#daemonRestoreKeys.clear();
76
67
  this.#disabled.clear();
@@ -82,6 +73,9 @@ const ownedManagedSessionStorage = new AsyncLocalStorage();
82
73
  export async function withOwnedManagedSessionContext(context, run) {
83
74
  return await ownedManagedSessionStorage.run(context, run);
84
75
  }
76
+ export function getOwnedManagedSessionRestoreKey() {
77
+ return ownedManagedSessionStorage.getStore()?.restoreKey;
78
+ }
85
79
  export function resolveOwnedManagedSessionContext(options) {
86
80
  const namespace = canonicalizeAgentBrowserNamespace(options.namespace);
87
81
  const currentNamespace = canonicalizeAgentBrowserNamespace(options.currentManagedSessionNamespace);
@@ -105,33 +99,18 @@ function ownedContextMatches(sessionName, namespace) {
105
99
  export function isOwnedManagedSessionTarget(args) {
106
100
  return ownedContextMatches(extractExplicitSessionName(args), extractExplicitNamespace(args)) !== undefined;
107
101
  }
108
- function pathExistsOrIsUnreadable(path) {
109
- try {
110
- lstatSync(path);
111
- return true;
112
- }
113
- catch (error) {
114
- return error.code !== "ENOENT";
115
- }
116
- }
117
102
  function hasExplicitConfigArg(args) {
118
103
  return scanUpstreamGlobalFlagOccurrences(args, "--config").length > 0;
119
104
  }
120
105
  function closesBrowserSession(args) {
121
106
  return ["close", "exit", "quit"].includes(parseCommandInfo(args).command ?? "");
122
107
  }
123
- export function agentBrowserConfigIsPresent(cwd, parentEnv = process.env, args = [], platform = process.platform) {
124
- if (hasExplicitConfigArg(args) || hasUpstreamEnvValue(parentEnv, AGENT_BROWSER_CONFIG_ENV))
125
- return true;
126
- const paths = [join(cwd, "agent-browser.json")];
127
- const home = resolveManagedSessionRestoreHome(parentEnv, platform);
128
- if (home)
129
- paths.push(join(home, ".agent-browser", "config.json"));
130
- return paths.some(pathExistsOrIsUnreadable);
108
+ export function agentBrowserExplicitConfigIsPresent(parentEnv = getAgentBrowserProcessEnvironment(), args = []) {
109
+ return hasExplicitConfigArg(args) || hasUpstreamEnvValue(parentEnv, AGENT_BROWSER_CONFIG_ENV);
131
110
  }
132
- /** Any upstream config disables automatic restore; content inspection would add parser and resource-exhaustion gaps. */
133
- export function agentBrowserConfigBlocksManagedRestore(cwd, parentEnv = process.env, args = [], platform = process.platform) {
134
- return !resolveManagedSessionRestoreHome(parentEnv, platform) || agentBrowserConfigIsPresent(cwd, parentEnv, args, platform);
111
+ /** Explicit config overrides disable restore; passive files are ignored because every browser-backed subprocess pins a protected empty config. */
112
+ export function agentBrowserConfigBlocksManagedRestore(_cwd, parentEnv = getAgentBrowserProcessEnvironment(), args = [], platform = process.platform) {
113
+ return !resolveManagedSessionRestoreHome(parentEnv, platform) || agentBrowserExplicitConfigIsPresent(parentEnv, args);
135
114
  }
136
115
  function omitWrapperInjectedUserAgent(args, enabled) {
137
116
  if (!enabled)
@@ -140,7 +119,7 @@ function omitWrapperInjectedUserAgent(args, enabled) {
140
119
  return index < 0 ? args : [...args.slice(0, index), ...args.slice(index + 2)];
141
120
  }
142
121
  function batchHasManagedSessionRestoreConflict(args, stdin) {
143
- const [command, ...commandArgs] = extractCommandTokens(args);
122
+ const [command, ...commandArgs] = extractUpstreamCommandTokens(args);
144
123
  if (command !== "batch")
145
124
  return false;
146
125
  if (commandArgs.some((token) => token !== "--bail"))
@@ -151,7 +130,7 @@ function batchHasManagedSessionRestoreConflict(args, stdin) {
151
130
  return parsed.steps.some((step) => ["connect", "batch"].includes(parseCommandInfo(step).command ?? ""));
152
131
  }
153
132
  function hasManagedSessionRestoreLaunchConflict(options) {
154
- const parentEnv = options.parentEnv ?? process.env;
133
+ const parentEnv = options.parentEnv ?? getAgentBrowserProcessEnvironment();
155
134
  const effectiveEnv = { ...parentEnv, ...options.env };
156
135
  const args = omitWrapperInjectedUserAgent(options.args, options.wrapperInjectedUserAgent);
157
136
  if (MANAGED_RESTORE_INCOMPATIBLE_ENVS.some((name) => hasUpstreamEnvValue(effectiveEnv, name)))
@@ -165,7 +144,7 @@ function hasManagedSessionRestoreLaunchConflict(options) {
165
144
  return hasExplicitConfigArg(args) || hasUpstreamEnvValue({ ...parentEnv, ...options.env }, AGENT_BROWSER_CONFIG_ENV);
166
145
  }
167
146
  function managedSessionRestoreOptedOut(options) {
168
- const effectiveEnv = { ...(options.parentEnv ?? process.env), ...options.env };
147
+ const effectiveEnv = { ...(options.parentEnv ?? getAgentBrowserProcessEnvironment()), ...options.env };
169
148
  return isDisabledEnvFlag(effectiveEnv[MANAGED_SESSION_RESTORE_ENV]);
170
149
  }
171
150
  function isManagedSessionRestoreIncompatible(options) {
@@ -173,7 +152,7 @@ function isManagedSessionRestoreIncompatible(options) {
173
152
  return true;
174
153
  if (managedSessionRestoreOptedOut(options))
175
154
  return false;
176
- const effectiveEnv = { ...(options.parentEnv ?? process.env), ...options.env };
155
+ const effectiveEnv = { ...(options.parentEnv ?? getAgentBrowserProcessEnvironment()), ...options.env };
177
156
  const args = omitWrapperInjectedUserAgent(options.args, options.wrapperInjectedUserAgent);
178
157
  if (options.cwd && !hasManagedSessionRestoreProjectIdentity(options.cwd))
179
158
  return true;
@@ -182,7 +161,7 @@ function isManagedSessionRestoreIncompatible(options) {
182
161
  return !ensureManagedSessionRestoreStorageIsSecure(effectiveEnv, process.platform, extractExplicitNamespace(args));
183
162
  }
184
163
  function resolveManagedSessionRestorePolicy(options) {
185
- const parentEnv = options.parentEnv ?? process.env;
164
+ const parentEnv = options.parentEnv ?? getAgentBrowserProcessEnvironment();
186
165
  const sessionName = extractExplicitSessionName(options.args);
187
166
  const namespace = extractExplicitNamespace(options.args);
188
167
  const ownedContext = ownedContextMatches(sessionName, namespace);
@@ -194,11 +173,35 @@ export function getOwnedManagedSessionNamespaceEnv(options) {
194
173
  const { namespace, owned, ownedContext } = resolveManagedSessionRestorePolicy(options);
195
174
  return owned ? { AGENT_BROWSER_NAMESPACE: ownedContext?.namespace ?? namespace ?? "" } : {};
196
175
  }
176
+ const DEFAULT_AUTOSAVE_INTERVAL_MS = "30000";
177
+ const MAX_U64 = 18446744073709551615n;
178
+ export function resolveExplicitAutosaveInterval(value) {
179
+ if (value === undefined)
180
+ return undefined;
181
+ if (!/^\+?\d+$/.test(value))
182
+ return DEFAULT_AUTOSAVE_INTERVAL_MS;
183
+ try {
184
+ const parsed = BigInt(value);
185
+ return parsed <= MAX_U64 ? parsed.toString() : DEFAULT_AUTOSAVE_INTERVAL_MS;
186
+ }
187
+ catch {
188
+ return DEFAULT_AUTOSAVE_INTERVAL_MS;
189
+ }
190
+ }
197
191
  export function getOwnedManagedSessionCompatibilityEnv(options) {
198
192
  const { owned, ownedContext } = resolveManagedSessionRestorePolicy(options);
199
- return owned && ownedContext?.compatibilityUserAgent
200
- ? { AGENT_BROWSER_USER_AGENT: ownedContext.compatibilityUserAgent }
201
- : {};
193
+ if (!owned || !ownedContext)
194
+ return {};
195
+ const parentEnv = options.parentEnv ?? getAgentBrowserProcessEnvironment();
196
+ const callEnv = options.env ?? {};
197
+ const explicitRawInterval = Object.hasOwn(callEnv, "AGENT_BROWSER_AUTOSAVE_INTERVAL_MS")
198
+ ? callEnv.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS
199
+ : parentEnv.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS;
200
+ const explicitIntervalMatches = resolveExplicitAutosaveInterval(explicitRawInterval) === ownedContext.headedManagedAutosaveInterval;
201
+ return {
202
+ ...(ownedContext.compatibilityUserAgent ? { AGENT_BROWSER_USER_AGENT: ownedContext.compatibilityUserAgent } : {}),
203
+ ...(ownedContext.headedManagedAutosaveInterval !== undefined && !explicitIntervalMatches ? { AGENT_BROWSER_AUTOSAVE_INTERVAL_MS: ownedContext.headedManagedAutosaveInterval } : {}),
204
+ };
202
205
  }
203
206
  export function shouldOmitOwnedManagedSessionRestoreEnv(options) {
204
207
  return resolveManagedSessionRestorePolicy(options).owned && closesBrowserSession(options.args);
@@ -280,7 +283,7 @@ export function getManagedSessionRestoreProtectedEnv(options, restoreEnv) {
280
283
  return {};
281
284
  if (ownedContext?.protectedStorageEnv)
282
285
  return { ...ownedContext.protectedStorageEnv };
283
- const effectiveEnv = { ...(options.parentEnv ?? process.env), ...options.env };
286
+ const effectiveEnv = { ...(options.parentEnv ?? getAgentBrowserProcessEnvironment()), ...options.env };
284
287
  return getManagedSessionRestoreProtectedStorageEnv(true, effectiveEnv);
285
288
  }
286
289
  export function validateManagedSessionRestoreContextForSpawn(options) {
@@ -288,7 +291,7 @@ export function validateManagedSessionRestoreContextForSpawn(options) {
288
291
  if (closesBrowserSession(options.args) || ownedContext?.restoreDecision !== "enabled")
289
292
  return true;
290
293
  const ownedCwd = ownedContext.cwd ?? options.cwd;
291
- if (!ownedContext.restoreKey || createManagedSessionRestoreKey(ownedCwd) !== ownedContext.restoreKey || !hasManagedSessionRestoreProjectIdentity(ownedCwd))
294
+ if (!ownedContext.restoreKey || !ownedContext.restoreScope || createManagedSessionRestoreKey(ownedCwd, ownedContext.restoreScope) !== ownedContext.restoreKey || !hasManagedSessionRestoreProjectIdentity(ownedCwd))
292
295
  return false;
293
296
  const effectiveEnv = { ...parentEnv, ...options.env };
294
297
  if (isDisabledEnvFlag(effectiveEnv[MANAGED_SESSION_RESTORE_ENV]))
@@ -315,7 +318,7 @@ export function getManagedSessionRestoreEnv(options) {
315
318
  return {};
316
319
  if (restoreState.isDisabled(sessionName, namespace) || !sessionName)
317
320
  return {};
318
- return { [AGENT_BROWSER_RESTORE_ENV]: createManagedSessionRestoreKey(options.cwd) };
321
+ return { [AGENT_BROWSER_RESTORE_ENV]: createManagedSessionRestoreKey(options.cwd, getManagedSessionRestoreScope(sessionName)) };
319
322
  }
320
323
  /** Commit sticky suppression only after an owned-context subprocess has actually started. */
321
324
  export function commitManagedSessionRestoreSuppression(options) {
@@ -360,15 +363,19 @@ export function buildOwnedManagedSessionRestoreContext(options) {
360
363
  const projectIdentityAvailable = !optedOut && hasManagedSessionRestoreProjectIdentity(ownedCwd);
361
364
  const incompatible = !optedOut && isManagedSessionRestoreIncompatible(policyOptions);
362
365
  const enabled = !optedOut && !incompatible;
363
- const effectiveEnv = { ...(options.parentEnv ?? process.env), ...options.env };
364
- const restoreKey = projectIdentityAvailable ? createManagedSessionRestoreKey(ownedCwd) : undefined;
366
+ const effectiveEnv = { ...(options.parentEnv ?? getAgentBrowserProcessEnvironment()), ...options.env };
367
+ const restoreScope = getManagedSessionRestoreScope(owned.sessionName);
368
+ const restoreKey = projectIdentityAvailable ? createManagedSessionRestoreKey(ownedCwd, restoreScope) : undefined;
365
369
  return {
366
370
  ...owned,
367
371
  compatibilityUserAgent: options.compatibilityUserAgent,
372
+ headedManagedAutosaveDisabled: options.headedManagedAutosaveDisabled,
373
+ headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
368
374
  expectedDaemonRestoreKey: enabled ? restoreKey : extractRequestedRestoreKey(options.args, owned.sessionName, effectiveEnv[AGENT_BROWSER_RESTORE_ENV]),
369
375
  protectedStorageEnv: enabled ? getManagedSessionRestoreProtectedStorageEnv(true, effectiveEnv) : undefined,
370
376
  restoreDecision: optedOut ? "opted-out" : incompatible ? "incompatible" : "enabled",
371
377
  restoreKey,
378
+ restoreScope,
372
379
  restoreSuppressed: optedOut || incompatible,
373
380
  };
374
381
  }
@@ -1,12 +1,7 @@
1
- /**
2
- * Purpose: Retain only wrapper-owned managed-restore snapshots after successful close operations.
3
- * Responsibilities: Persist atomic per-snapshot ownership records, self-heal malformed records, and apply per-key plus superseded-generation retention without deleting unproven paths.
4
- * Scope: Snapshot ownership and pruning only; restore eligibility and daemon policy live in sibling modules.
5
- */
6
1
  import { createHash, randomUUID } from "node:crypto";
7
2
  import { chmodSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs";
8
3
  import { basename, dirname, isAbsolute, join } from "node:path";
9
- import { createManagedSessionRestoreKey, directoryContainsSymlink, ensureManagedSessionRestoreStorageIsSecure, ensureOwnerOnlyDirectory, getManagedRestoreSessionsDirectory, hasManagedSessionRestoreProjectIdentity, isManagedSessionRestoreKey, resolveManagedSessionRestoreCheckoutRoot, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
4
+ import { createManagedSessionRestoreKey, directoryContainsSymlink, ensureManagedSessionRestoreStorageIsSecure, hasManagedSessionRestoreProjectIdentity, ensureOwnerOnlyDirectory, getManagedRestoreSessionsDirectory, isManagedSessionRestoreKey, resolveManagedSessionRestoreCheckoutRoot, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
10
5
  const OWNED_RESTORE_SNAPSHOT_FAMILIES_TO_KEEP = 2;
11
6
  const OWNED_RESTORE_SNAPSHOT_MAX_RECORDS = 256;
12
7
  const OWNED_RESTORE_SNAPSHOT_RECORD_MAX_BYTES = 16 * 1_024;
@@ -349,9 +344,7 @@ export function pruneOwnedManagedSessionRestoreSnapshots(options) {
349
344
  if (!changed)
350
345
  break;
351
346
  }
352
- const protectedRestoreKey = hasManagedSessionRestoreProjectIdentity(options.cwd)
353
- ? createManagedSessionRestoreKey(options.cwd)
354
- : restoreKey;
347
+ const protectedRestoreKey = restoreKey;
355
348
  if (!lineage)
356
349
  return removed;
357
350
  removed += pruneExpiredOtherRestoreKeys({
@@ -1,24 +1,23 @@
1
- // Purpose: Prevent native tool calls from crossing wrapper-owned managed-state boundaries.
2
- // Responsibilities: Guard destructive global state operations, foreign checkout restore/state references, and browser file access to local agent-browser state.
3
- // Scope: Pre-spawn policy only; result redaction and managed snapshot retention live elsewhere.
4
1
  import { realpathSync } from "node:fs";
5
2
  import { basename, dirname, isAbsolute, resolve } from "node:path";
6
3
  import { fileURLToPath } from "node:url";
7
4
  import { parseArgvDescriptor } from "./argv-descriptor.js";
8
5
  import { needsManagedSession } from "./command-policy.js";
9
6
  import { getScreenshotPathTokenIndex } from "./orchestration/browser-run/artifact-paths.js";
10
- import { parseBatchCommandArgument, parseUserBatchStdin } from "./orchestration/batch-stdin.js";
7
+ import { getUpstreamEffectiveBatchSteps, parseBatchCommandArgument, parseUserBatchStdin } from "./orchestration/batch-stdin.js";
11
8
  import { isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
12
- import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, extractExplicitSessionName, optionalGlobalValueFlagConsumesNext, } from "./argv-grammar.js";
9
+ import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, extractExplicitSessionName, isUpstreamEnvFlagEnabled, optionalGlobalValueFlagConsumesNext, } from "./argv-grammar.js";
13
10
  import { extractManagedSessionRestoreKeys, isWrapperManagedSessionName } from "./managed-session-capabilities.js";
14
- import { agentBrowserConfigIsPresent } from "./managed-session-restore.js";
15
- import { createManagedSessionRestoreKey, hasManagedSessionRestoreProjectIdentity } from "./managed-session-storage.js";
11
+ import { agentBrowserExplicitConfigIsPresent } from "./managed-session-restore.js";
12
+ import { hasManagedSessionRestoreProjectIdentity } from "./managed-session-storage.js";
13
+ import { getAgentBrowserProcessEnvironment } from "./process-environment.js";
16
14
  const BLOCKED_GLOBAL_STATE_MESSAGE = "This operation could read or modify wrapper-owned browser state outside the current checkout. Use a caller-owned state name or path instead.";
17
15
  const BLOCKED_MANAGED_BROWSER_FILE_MESSAGE = "Browser access to local .agent-browser storage is blocked because state files can contain authenticated cookies and storage. Use guarded state commands instead.";
18
16
  const BLOCKED_MANAGED_SESSION_MESSAGE = "This session name is reserved for a browser managed by this extension instance. Use the current managed session or a caller-owned session name instead.";
19
17
  const FILE_ACCESS_FLAG_MESSAGE = "Browser file-access enablement is blocked because a local page could read and exfiltrate authenticated .agent-browser state.";
20
- const UPSTREAM_CONFIG_MESSAGE = "Upstream agent-browser config is blocked for browser-backed native calls because it can load protected state, profiles, extensions, or file-access settings. Remove the config and pass safe settings explicitly.";
18
+ const UPSTREAM_CONFIG_MESSAGE = "Explicit upstream agent-browser config from --config or AGENT_BROWSER_CONFIG is blocked for browser-backed native calls because it can load protected state, profiles, extensions, or file-access settings. Remove that override and pass safe settings explicitly. Passive agent-browser.json files are ignored through the wrapper's protected empty config.";
21
19
  const UNVERIFIED_PAGE_MESSAGE = "The active page became unverified after a tab, attachment, history, script, or state-load transition. Run get url or navigate explicitly to a safe URL before page-content inspection.";
20
+ const BATCH_UNVERIFIED_PAGE_MESSAGE = `${UNVERIFIED_PAGE_MESSAGE} In a batch, put get url after the transition before later content steps, or split the batch at that boundary.`;
22
21
  const UNSAFE_BATCH_ARGUMENT_MESSAGE = "Batch command arguments could not be safely inspected. Use batch stdin JSON command arrays instead.";
23
22
  const NESTED_BATCH_ARGUMENT_MESSAGE = "Nested batch commands are blocked by the wrapper's page-state safety policy. Flatten the batch steps instead.";
24
23
  const NON_BAIL_BATCH_NAVIGATION_MESSAGE = "Batches that navigate before page-content access must use exact batch --bail so a failed navigation cannot expose the prior page.";
@@ -62,7 +61,8 @@ function hasAgentBrowserPathComponent(value) {
62
61
  function getFileUrlPath(value) {
63
62
  if (!value)
64
63
  return undefined;
65
- const nestedFileUrl = /^(?:(?:blob|filesystem|view-source):)*(file:.*)$/i.exec(value)?.[1];
64
+ const trimmedValue = value.replaceAll(/[\t\n\r]/g, "").trim();
65
+ const nestedFileUrl = /^(?:(?:blob|filesystem|view-source):)*(file:.*)$/i.exec(trimmedValue)?.[1];
66
66
  if (!nestedFileUrl)
67
67
  return undefined;
68
68
  try {
@@ -73,7 +73,7 @@ function getFileUrlPath(value) {
73
73
  return decodeUrlComponent(new URL(nestedFileUrl).pathname);
74
74
  }
75
75
  catch {
76
- return value;
76
+ return trimmedValue;
77
77
  }
78
78
  }
79
79
  }
@@ -151,7 +151,7 @@ function getPositionalOperands(commandTokens) {
151
151
  }
152
152
  function getExplicitNavigationTarget(args) {
153
153
  const descriptor = parseArgvDescriptor(args);
154
- const positionals = getPositionalOperands(descriptor.commandTokens);
154
+ const positionals = getPositionalOperands(descriptor.upstreamCommandTokens);
155
155
  if (EXPLICIT_NAVIGATION_COMMANDS.has(descriptor.commandInfo.command ?? ""))
156
156
  return positionals[0];
157
157
  if (descriptor.commandInfo.command === "tab" && descriptor.commandInfo.subcommand === "new")
@@ -182,11 +182,11 @@ function getResultingExplicitNavigationTarget(args, currentPageUrl) {
182
182
  function getBrowserContentUrlOperands(args) {
183
183
  const descriptor = parseArgvDescriptor(args);
184
184
  const command = descriptor.commandInfo.command;
185
- const positionals = getPositionalOperands(descriptor.commandTokens);
185
+ const positionals = getPositionalOperands(descriptor.upstreamCommandTokens);
186
186
  if (["a11y", "read", "vitals", "web-vitals"].includes(command ?? ""))
187
187
  return positionals.slice(0, 1);
188
188
  if (command === "auth" && descriptor.commandInfo.subcommand === "login")
189
- return getFlagValues(descriptor.commandTokens, "--url", true);
189
+ return getFlagValues(descriptor.upstreamCommandTokens, "--url", true);
190
190
  if (command === "diff" && descriptor.commandInfo.subcommand === "url")
191
191
  return positionals.slice(1, 3);
192
192
  if (command === "record" && ["start", "restart"].includes(descriptor.commandInfo.subcommand ?? ""))
@@ -224,7 +224,7 @@ function getBrowserFileOperands(args, env) {
224
224
  const descriptor = parseArgvDescriptor(args);
225
225
  const command = descriptor.commandInfo.command;
226
226
  const subcommand = descriptor.commandInfo.subcommand;
227
- const positionals = getPositionalOperands(descriptor.commandTokens);
227
+ const positionals = getPositionalOperands(descriptor.upstreamCommandTokens);
228
228
  const values = [
229
229
  getExplicitNavigationTarget(args),
230
230
  ...getBrowserContentUrlOperands(args),
@@ -241,30 +241,27 @@ function getBrowserFileOperands(args, env) {
241
241
  if (command === "pdf")
242
242
  values.push(positionals[0]);
243
243
  if (command === "screenshot") {
244
- const pathIndex = getScreenshotPathTokenIndex(descriptor.commandTokens);
245
- values.push(pathIndex === undefined ? undefined : descriptor.commandTokens[pathIndex]);
244
+ const pathIndex = getScreenshotPathTokenIndex(descriptor.upstreamCommandTokens);
245
+ values.push(pathIndex === undefined ? undefined : descriptor.upstreamCommandTokens[pathIndex]);
246
246
  }
247
247
  if (["profiler", "trace"].includes(command ?? "") && subcommand === "stop")
248
248
  values.push(...positionals.slice(1));
249
249
  if (command === "network" && subcommand === "har")
250
250
  values.push(...positionals.slice(2));
251
251
  if (command === "wait")
252
- values.push(...getFlagValues(descriptor.commandTokens, "--download", true), ...getFlagValues(descriptor.commandTokens, "-d", true));
252
+ values.push(...getFlagValues(descriptor.upstreamCommandTokens, "--download", true), ...getFlagValues(descriptor.upstreamCommandTokens, "-d", true));
253
253
  if (command === "cookies" && subcommand === "set")
254
- values.push(...getFlagValues(descriptor.commandTokens, "--curl", true));
254
+ values.push(...getFlagValues(descriptor.upstreamCommandTokens, "--curl", true));
255
255
  if (command === "state" && ["load", "rename", "save", "show"].includes(subcommand ?? ""))
256
256
  values.push(...positionals.slice(1));
257
257
  if (command === "diff" && subcommand === "url")
258
258
  values.push(...positionals.slice(1));
259
259
  if (command === "diff")
260
- values.push(...getFlagValues(descriptor.commandTokens, "--baseline", true), ...getFlagValues(descriptor.commandTokens, "--output", true), ...getFlagValues(descriptor.commandTokens, "-o", true));
260
+ values.push(...getFlagValues(descriptor.upstreamCommandTokens, "--baseline", true), ...getFlagValues(descriptor.upstreamCommandTokens, "--output", true), ...getFlagValues(descriptor.upstreamCommandTokens, "-o", true));
261
261
  if (command === "record" && ["start", "restart"].includes(subcommand ?? ""))
262
262
  values.push(...positionals.slice(1));
263
263
  return values.filter((value) => typeof value === "string" && value.length > 0);
264
264
  }
265
- function isUpstreamEnvFlagEnabled(value) {
266
- return value !== undefined && !["", "0", "false", "no"].includes(value.toLowerCase());
267
- }
268
265
  function rawBrowserArgsEnableFileAccess(args, env) {
269
266
  return getRawBrowserArgsValues(args, env).some((value) => typeof value === "string" && /(?:^|[,\p{White_Space}])--(?:allow-file-access(?:-from-files)?|disable-web-security)(?=$|[=,\p{White_Space}])/iu.test(value));
270
267
  }
@@ -312,13 +309,21 @@ function getManagedBrowserFileAccessValidationError(options) {
312
309
  return BLOCKED_MANAGED_BROWSER_FILE_MESSAGE;
313
310
  return undefined;
314
311
  }
312
+ // Deliberately unions raw argv steps AND parseable stdin steps even though
313
+ // upstream executes argv steps exclusively when any exist: this pre-spawn
314
+ // validator must stay a superset of anything upstream could run, so it fails
315
+ // closed on unsafe content in either source. Stdin parse failures are fatal
316
+ // only when upstream would actually read stdin (no argv steps); ignored
317
+ // unparseable stdin cannot execute and must not reject a valid raw-argv call.
318
+ // The upstream-exact selection lives in getUpstreamEffectiveBatchSteps
319
+ // (batch-stdin.ts) for guard/folding paths.
315
320
  function getBatchCommandSteps(args, stdin) {
316
321
  const descriptor = parseArgvDescriptor(args);
317
322
  if (descriptor.commandInfo.command !== "batch")
318
323
  return { steps: [] };
319
324
  const steps = [];
320
- for (const command of descriptor.commandTokens.slice(1)) {
321
- if (command === "--bail" || command.startsWith("--bail="))
325
+ for (const command of descriptor.upstreamCommandTokens.slice(1)) {
326
+ if (command === "--bail")
322
327
  continue;
323
328
  if (decodeUrlComponent(command).toLowerCase().includes(".agent-browser"))
324
329
  return { error: BLOCKED_MANAGED_BROWSER_FILE_MESSAGE, steps: [] };
@@ -330,7 +335,7 @@ function getBatchCommandSteps(args, stdin) {
330
335
  steps.push(parsed.step);
331
336
  }
332
337
  const parsedStdin = parseUserBatchStdin(stdin);
333
- if (parsedStdin.error)
338
+ if (parsedStdin.error && steps.length === 0)
334
339
  return { error: parsedStdin.error, steps: [] };
335
340
  for (const step of parsedStdin.steps ?? []) {
336
341
  if (parseArgvDescriptor(step).commandInfo.command === "batch")
@@ -339,9 +344,16 @@ function getBatchCommandSteps(args, stdin) {
339
344
  }
340
345
  return { steps };
341
346
  }
347
+ export function invocationMayNavigateToLocalFile(args, stdin) {
348
+ if (isFileUrl(getResultingExplicitNavigationTarget(args)))
349
+ return true;
350
+ const descriptor = parseArgvDescriptor(args);
351
+ return getUpstreamEffectiveBatchSteps(descriptor.upstreamCommandTokens, stdin)
352
+ .some((step) => isFileUrl(getResultingExplicitNavigationTarget(step)));
353
+ }
342
354
  function batchBailsOnFirstError(args) {
343
355
  const descriptor = parseArgvDescriptor(args);
344
- return descriptor.commandInfo.command === "batch" && descriptor.commandTokens.slice(1).includes("--bail");
356
+ return descriptor.commandInfo.command === "batch" && descriptor.upstreamCommandTokens.slice(1).includes("--bail");
345
357
  }
346
358
  function commandMayChangePageTarget(args, trustedBatchTabSelection) {
347
359
  const descriptor = parseArgvDescriptor(args);
@@ -425,11 +437,11 @@ export function getCallerOwnedSessionLivePageVerificationRequirement(options) {
425
437
  trustedFirstBatchTabSelection: options.trustedFirstBatchTabSelection,
426
438
  trustedPinnedEmptyConfig: true,
427
439
  });
428
- return validationError === UNVERIFIED_PAGE_MESSAGE || validationError === NON_BAIL_BATCH_NAVIGATION_MESSAGE
440
+ return validationError === UNVERIFIED_PAGE_MESSAGE || validationError === BATCH_UNVERIFIED_PAGE_MESSAGE || validationError === NON_BAIL_BATCH_NAVIGATION_MESSAGE
429
441
  ? UNVERIFIED_PAGE_MESSAGE
430
442
  : undefined;
431
443
  }
432
- export function getManagedSessionTargetAccessValidationError(args, ownedManagedSession, env = process.env) {
444
+ export function getManagedSessionTargetAccessValidationError(args, ownedManagedSession, env = getAgentBrowserProcessEnvironment()) {
433
445
  const sessionName = extractExplicitSessionName(args) ?? env.AGENT_BROWSER_SESSION;
434
446
  return sessionName && isWrapperManagedSessionName(sessionName) && !ownedManagedSession
435
447
  ? BLOCKED_MANAGED_SESSION_MESSAGE
@@ -471,7 +483,7 @@ function getReferencedValues(args, cwd, env) {
471
483
  env.AGENT_BROWSER_STATE,
472
484
  ].filter((value) => typeof value === "string" && value.length > 0);
473
485
  if (descriptor.commandInfo.command === "state" && ["clear", "load", "rename", "save", "show"].includes(descriptor.commandInfo.subcommand ?? "")) {
474
- values.push(...descriptor.commandTokens.slice(2));
486
+ values.push(...descriptor.upstreamCommandTokens.slice(2));
475
487
  }
476
488
  for (const value of [...values]) {
477
489
  const realPath = resolveExistingPath(cwd, value);
@@ -481,13 +493,13 @@ function getReferencedValues(args, cwd, env) {
481
493
  return values;
482
494
  }
483
495
  export function getManagedSessionStateAccessValidationError(options) {
484
- const effectiveEnv = { ...(options.parentEnv ?? process.env), ...options.env };
496
+ const effectiveEnv = { ...(options.parentEnv ?? getAgentBrowserProcessEnvironment()), ...options.env };
485
497
  const descriptor = parseArgvDescriptor(options.args);
486
498
  const command = descriptor.commandInfo.command;
487
499
  const subcommand = descriptor.commandInfo.subcommand;
488
500
  if (["close", "exit", "quit"].includes(command ?? ""))
489
501
  return undefined;
490
- if (!options.trustedPinnedEmptyConfig && needsManagedSession(descriptor) && agentBrowserConfigIsPresent(options.cwd, effectiveEnv, options.args))
502
+ if (!options.trustedPinnedEmptyConfig && needsManagedSession(descriptor) && agentBrowserExplicitConfigIsPresent(effectiveEnv, options.args))
491
503
  return UPSTREAM_CONFIG_MESSAGE;
492
504
  if (command === "batch") {
493
505
  const batch = getBatchCommandSteps(options.args, options.stdin);
@@ -526,7 +538,7 @@ export function getManagedSessionStateAccessValidationError(options) {
526
538
  }
527
539
  }
528
540
  if (directError)
529
- return directError;
541
+ return directError === UNVERIFIED_PAGE_MESSAGE ? BATCH_UNVERIFIED_PAGE_MESSAGE : directError;
530
542
  if (failedNavigationHazard)
531
543
  return NON_BAIL_BATCH_NAVIGATION_MESSAGE;
532
544
  const mayChangePageTarget = commandMayChangePageTarget(step, trustedBatchTabSelection);
@@ -568,8 +580,8 @@ export function getManagedSessionStateAccessValidationError(options) {
568
580
  if (command === "state" && subcommand === "clean")
569
581
  return BLOCKED_GLOBAL_STATE_MESSAGE;
570
582
  if (command === "state" && subcommand === "clear") {
571
- const target = descriptor.commandTokens.slice(2).find((token) => !token.startsWith("-"));
572
- if (!target || descriptor.commandTokens.includes("--all") || descriptor.commandTokens.includes("-a") || isWrapperManagedSessionName(target)) {
583
+ const target = descriptor.upstreamCommandTokens.slice(2).find((token) => !token.startsWith("-"));
584
+ if (!target || descriptor.upstreamCommandTokens.includes("--all") || descriptor.upstreamCommandTokens.includes("-a") || isWrapperManagedSessionName(target)) {
573
585
  return BLOCKED_GLOBAL_STATE_MESSAGE;
574
586
  }
575
587
  }
@@ -582,8 +594,8 @@ export function getManagedSessionStateAccessValidationError(options) {
582
594
  return undefined;
583
595
  if (command === "state" && ["rename", "save"].includes(subcommand ?? ""))
584
596
  return BLOCKED_GLOBAL_STATE_MESSAGE;
585
- if (!hasManagedSessionRestoreProjectIdentity(options.cwd))
597
+ if (!hasManagedSessionRestoreProjectIdentity(options.cwd) || !options.managedSessionRestoreKey)
586
598
  return BLOCKED_GLOBAL_STATE_MESSAGE;
587
- const currentKey = createManagedSessionRestoreKey(options.cwd).toLowerCase();
599
+ const currentKey = options.managedSessionRestoreKey.toLowerCase();
588
600
  return referencedKeys.every((key) => key === currentKey) ? undefined : BLOCKED_GLOBAL_STATE_MESSAGE;
589
601
  }