pi-agent-browser-native 0.2.71 → 0.2.74

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 (52) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +14 -12
  3. package/dist/extensions/agent-browser/index.js +104 -16
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +124 -0
  5. package/dist/extensions/agent-browser/lib/command-taxonomy.js +12 -1
  6. package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
  7. package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
  8. package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
  9. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
  10. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
  11. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
  12. package/dist/extensions/agent-browser/lib/managed-session-restore.js +367 -0
  13. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
  14. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
  15. package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
  16. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  24. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  25. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  26. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +625 -429
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +136 -56
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +28 -40
  29. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
  30. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
  31. package/dist/extensions/agent-browser/lib/playbook.js +10 -9
  32. package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
  33. package/dist/extensions/agent-browser/lib/process.js +270 -34
  34. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
  35. package/dist/extensions/agent-browser/lib/results/categories.js +21 -2
  36. package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
  37. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +80 -12
  38. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
  39. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
  40. package/dist/extensions/agent-browser/lib/runtime.js +85 -85
  41. package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
  42. package/dist/extensions/agent-browser/lib/temp.js +13 -25
  43. package/docs/ARCHITECTURE.md +9 -8
  44. package/docs/COMMAND_REFERENCE.md +97 -32
  45. package/docs/ELECTRON.md +10 -10
  46. package/docs/RELEASE.md +3 -2
  47. package/docs/SUPPORT_MATRIX.md +22 -19
  48. package/docs/TOOL_CONTRACT.md +31 -28
  49. package/docs/platform-smoke.md +5 -5
  50. package/package.json +1 -1
  51. package/platform-smoke.config.mjs +3 -1
  52. package/scripts/agent-browser-capability-baseline.mjs +45 -3
@@ -0,0 +1,589 @@
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
+ import { realpathSync } from "node:fs";
5
+ import { basename, dirname, isAbsolute, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { parseArgvDescriptor } from "./argv-descriptor.js";
8
+ import { needsManagedSession } from "./command-policy.js";
9
+ import { getScreenshotPathTokenIndex } from "./orchestration/browser-run/artifact-paths.js";
10
+ import { parseBatchCommandArgument, parseUserBatchStdin } from "./orchestration/batch-stdin.js";
11
+ import { isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
12
+ import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, extractExplicitSessionName, optionalGlobalValueFlagConsumesNext, } from "./argv-grammar.js";
13
+ 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";
16
+ 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
+ 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
+ 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
+ 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.";
21
+ 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.";
22
+ const UNSAFE_BATCH_ARGUMENT_MESSAGE = "Batch command arguments could not be safely inspected. Use batch stdin JSON command arrays instead.";
23
+ const NESTED_BATCH_ARGUMENT_MESSAGE = "Nested batch commands are blocked by the wrapper's page-state safety policy. Flatten the batch steps instead.";
24
+ 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.";
25
+ const MAX_NON_BAIL_BATCH_PAGE_STATES = 64;
26
+ const EXPLICIT_NAVIGATION_COMMANDS = new Set(["a11y", "goto", "navigate", "open", "pushstate", "visit", "vitals", "web-vitals"]);
27
+ const FILE_PATH_GLOBAL_FLAGS = ["--action-policy", "--config", "--download-path", "--executable-path", "--extension", "--init-script", "--profile", "--screenshot-dir", "--state"];
28
+ const FILE_PATH_ENV_VARIABLES = [
29
+ "AGENT_BROWSER_ACTION_POLICY",
30
+ "AGENT_BROWSER_CONFIG",
31
+ "AGENT_BROWSER_DOWNLOAD_PATH",
32
+ "AGENT_BROWSER_EXECUTABLE_PATH",
33
+ "AGENT_BROWSER_PROFILE",
34
+ "AGENT_BROWSER_SCREENSHOT_DIR",
35
+ "AGENT_BROWSER_SKILLS_DIR",
36
+ "AGENT_BROWSER_SOCKET_DIR",
37
+ "AGENT_BROWSER_STATE",
38
+ ];
39
+ const FILE_PATH_LIST_ENV_VARIABLES = ["AGENT_BROWSER_EXTENSIONS", "AGENT_BROWSER_INIT_SCRIPTS"];
40
+ const POSITIONAL_VALUE_FLAGS = new Set([...VALUE_FLAGS, "--llms"]);
41
+ function decodeUrlComponent(value) {
42
+ let decoded = value;
43
+ for (let attempt = 0; attempt < 8; attempt += 1) {
44
+ try {
45
+ const next = decodeURIComponent(decoded);
46
+ if (next === decoded)
47
+ break;
48
+ decoded = next;
49
+ }
50
+ catch {
51
+ break;
52
+ }
53
+ }
54
+ return decoded;
55
+ }
56
+ function hasAgentBrowserPathComponent(value) {
57
+ return decodeUrlComponent(value).replaceAll("\\", "/").toLowerCase().split("/").some((component) => {
58
+ const windowsCanonicalComponent = component.replace(/^[a-z]:/i, "").split(":", 1)[0]?.replace(/[. ]+$/g, "");
59
+ return windowsCanonicalComponent === ".agent-browser";
60
+ });
61
+ }
62
+ function getFileUrlPath(value) {
63
+ if (!value)
64
+ return undefined;
65
+ const nestedFileUrl = /^(?:(?:blob|filesystem|view-source):)*(file:.*)$/i.exec(value)?.[1];
66
+ if (!nestedFileUrl)
67
+ return undefined;
68
+ try {
69
+ return fileURLToPath(new URL(nestedFileUrl));
70
+ }
71
+ catch {
72
+ try {
73
+ return decodeUrlComponent(new URL(nestedFileUrl).pathname);
74
+ }
75
+ catch {
76
+ return value;
77
+ }
78
+ }
79
+ }
80
+ export function isFileUrl(value) {
81
+ return getFileUrlPath(value) !== undefined;
82
+ }
83
+ function resolveThroughExistingAncestor(path) {
84
+ const suffix = [];
85
+ let current = path;
86
+ for (;;) {
87
+ try {
88
+ return resolve(realpathSync(current), ...suffix.reverse());
89
+ }
90
+ catch {
91
+ const parent = dirname(current);
92
+ if (parent === current)
93
+ return undefined;
94
+ suffix.push(basename(current));
95
+ current = parent;
96
+ }
97
+ }
98
+ }
99
+ export function isProtectedAgentBrowserFileTarget(value, cwd) {
100
+ if (!value)
101
+ return false;
102
+ const filePath = getFileUrlPath(value);
103
+ const windowsDrivePath = /^[a-z]:/i.test(value);
104
+ const windowsAbsolutePath = /^[a-z]:[\\/]/i.test(value);
105
+ const hasScheme = /^[a-z][a-z\d+.-]*:/i.test(value) && !windowsDrivePath;
106
+ if (hasScheme && filePath === undefined)
107
+ return false;
108
+ const pathValue = filePath ?? (isAbsolute(value) || windowsAbsolutePath ? value : resolve(cwd, value));
109
+ if (hasAgentBrowserPathComponent(pathValue))
110
+ return true;
111
+ const resolvedPath = resolveThroughExistingAncestor(pathValue);
112
+ return resolvedPath !== undefined && hasAgentBrowserPathComponent(resolvedPath);
113
+ }
114
+ export function getAgentBrowserStoragePathValidationError(value, cwd) {
115
+ return isProtectedAgentBrowserFileTarget(value, cwd) ? BLOCKED_MANAGED_BROWSER_FILE_MESSAGE : undefined;
116
+ }
117
+ export function getObservedBrowserPageValidationError(args, observedUrl, cwd) {
118
+ if (!isFileUrl(observedUrl))
119
+ return undefined;
120
+ if (isProtectedAgentBrowserFileTarget(observedUrl, cwd))
121
+ return BLOCKED_MANAGED_BROWSER_FILE_MESSAGE;
122
+ const descriptor = parseArgvDescriptor(args);
123
+ if (descriptor.commandInfo.command === "get" && descriptor.commandInfo.subcommand === "url")
124
+ return undefined;
125
+ const explicitTarget = getExplicitNavigationTarget(args);
126
+ return explicitTarget !== undefined && isFileUrl(explicitTarget) && !isProtectedAgentBrowserFileTarget(explicitTarget, cwd)
127
+ ? undefined
128
+ : BLOCKED_MANAGED_BROWSER_FILE_MESSAGE;
129
+ }
130
+ function getPositionalOperands(commandTokens) {
131
+ const values = [];
132
+ for (let index = 1; index < commandTokens.length; index += 1) {
133
+ const token = commandTokens[index];
134
+ if (!token)
135
+ continue;
136
+ if (token.includes("=") && token.startsWith("-"))
137
+ continue;
138
+ if (POSITIONAL_VALUE_FLAGS.has(token)) {
139
+ index += 1;
140
+ continue;
141
+ }
142
+ if (GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(token)) {
143
+ if (["true", "false"].includes(commandTokens[index + 1] ?? ""))
144
+ index += 1;
145
+ continue;
146
+ }
147
+ if (!token.startsWith("-") || token.includes("/") || token.includes("\\"))
148
+ values.push(token);
149
+ }
150
+ return values;
151
+ }
152
+ function getExplicitNavigationTarget(args) {
153
+ const descriptor = parseArgvDescriptor(args);
154
+ const positionals = getPositionalOperands(descriptor.commandTokens);
155
+ if (EXPLICIT_NAVIGATION_COMMANDS.has(descriptor.commandInfo.command ?? ""))
156
+ return positionals[0];
157
+ if (descriptor.commandInfo.command === "tab" && descriptor.commandInfo.subcommand === "new")
158
+ return positionals[1];
159
+ return undefined;
160
+ }
161
+ function getResultingExplicitNavigationTarget(args, currentPageUrl) {
162
+ const target = getExplicitNavigationTarget(args);
163
+ if (target === undefined)
164
+ return undefined;
165
+ const descriptor = parseArgvDescriptor(args);
166
+ if (descriptor.commandInfo.command !== "pushstate")
167
+ return target;
168
+ try {
169
+ return new URL(target).href;
170
+ }
171
+ catch {
172
+ if (!currentPageUrl)
173
+ return undefined;
174
+ try {
175
+ return new URL(target, currentPageUrl).href;
176
+ }
177
+ catch {
178
+ return undefined;
179
+ }
180
+ }
181
+ }
182
+ function getBrowserContentUrlOperands(args) {
183
+ const descriptor = parseArgvDescriptor(args);
184
+ const command = descriptor.commandInfo.command;
185
+ const positionals = getPositionalOperands(descriptor.commandTokens);
186
+ if (["a11y", "read", "vitals", "web-vitals"].includes(command ?? ""))
187
+ return positionals.slice(0, 1);
188
+ if (command === "auth" && descriptor.commandInfo.subcommand === "login")
189
+ return getFlagValues(descriptor.commandTokens, "--url", true);
190
+ if (command === "diff" && descriptor.commandInfo.subcommand === "url")
191
+ return positionals.slice(1, 3);
192
+ if (command === "record" && ["start", "restart"].includes(descriptor.commandInfo.subcommand ?? ""))
193
+ return positionals.slice(2, 3);
194
+ return [];
195
+ }
196
+ function trimUnicodeWhitespace(value) {
197
+ return value.replace(/^\p{White_Space}+|\p{White_Space}+$/gu, "");
198
+ }
199
+ function getRawBrowserArgsValues(args, env) {
200
+ const values = [env.AGENT_BROWSER_ARGS];
201
+ for (let index = 0; index < args.length; index += 1) {
202
+ const token = args[index];
203
+ if (token.startsWith("--args="))
204
+ values.push(token.slice("--args=".length));
205
+ else if (token === "--args")
206
+ values.push(args[++index]);
207
+ }
208
+ return values;
209
+ }
210
+ function getRawBrowserFileOperands(args, env) {
211
+ return getRawBrowserArgsValues(args, env).flatMap((value) => value?.split(/[,\r\n]/).flatMap((rawToken) => {
212
+ const token = trimUnicodeWhitespace(rawToken);
213
+ const equalsIndex = token.indexOf("=");
214
+ return equalsIndex < 0 ? [token] : [token, token.slice(equalsIndex + 1)];
215
+ }) ?? []);
216
+ }
217
+ function getBrowserFileEnvOperands(env) {
218
+ return [
219
+ ...FILE_PATH_ENV_VARIABLES.map((name) => env[name] === undefined ? undefined : trimUnicodeWhitespace(env[name])),
220
+ ...FILE_PATH_LIST_ENV_VARIABLES.flatMap((name) => env[name]?.split(/[,\r\n]/).map(trimUnicodeWhitespace) ?? []),
221
+ ];
222
+ }
223
+ function getBrowserFileOperands(args, env) {
224
+ const descriptor = parseArgvDescriptor(args);
225
+ const command = descriptor.commandInfo.command;
226
+ const subcommand = descriptor.commandInfo.subcommand;
227
+ const positionals = getPositionalOperands(descriptor.commandTokens);
228
+ const values = [
229
+ getExplicitNavigationTarget(args),
230
+ ...getBrowserContentUrlOperands(args),
231
+ ...FILE_PATH_GLOBAL_FLAGS.flatMap((flag) => getFlagValues(args, flag, true)),
232
+ ...getBrowserFileEnvOperands(env),
233
+ ...getRawBrowserFileOperands(args, env),
234
+ ];
235
+ if (command === "read")
236
+ values.push(positionals[0]);
237
+ if (command === "upload")
238
+ values.push(...positionals.slice(1));
239
+ if (command === "download")
240
+ values.push(...positionals.slice(1));
241
+ if (command === "pdf")
242
+ values.push(positionals[0]);
243
+ if (command === "screenshot") {
244
+ const pathIndex = getScreenshotPathTokenIndex(descriptor.commandTokens);
245
+ values.push(pathIndex === undefined ? undefined : descriptor.commandTokens[pathIndex]);
246
+ }
247
+ if (["profiler", "trace"].includes(command ?? "") && subcommand === "stop")
248
+ values.push(...positionals.slice(1));
249
+ if (command === "network" && subcommand === "har")
250
+ values.push(...positionals.slice(2));
251
+ if (command === "wait")
252
+ values.push(...getFlagValues(descriptor.commandTokens, "--download", true), ...getFlagValues(descriptor.commandTokens, "-d", true));
253
+ if (command === "cookies" && subcommand === "set")
254
+ values.push(...getFlagValues(descriptor.commandTokens, "--curl", true));
255
+ if (command === "state" && ["load", "rename", "save", "show"].includes(subcommand ?? ""))
256
+ values.push(...positionals.slice(1));
257
+ if (command === "diff" && subcommand === "url")
258
+ values.push(...positionals.slice(1));
259
+ if (command === "diff")
260
+ values.push(...getFlagValues(descriptor.commandTokens, "--baseline", true), ...getFlagValues(descriptor.commandTokens, "--output", true), ...getFlagValues(descriptor.commandTokens, "-o", true));
261
+ if (command === "record" && ["start", "restart"].includes(subcommand ?? ""))
262
+ values.push(...positionals.slice(1));
263
+ return values.filter((value) => typeof value === "string" && value.length > 0);
264
+ }
265
+ function isUpstreamEnvFlagEnabled(value) {
266
+ return value !== undefined && !["", "0", "false", "no"].includes(value.toLowerCase());
267
+ }
268
+ function rawBrowserArgsEnableFileAccess(args, env) {
269
+ 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
+ }
271
+ function fileAccessFlagIsEnabled(args, env) {
272
+ let enabled = isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_ALLOW_FILE_ACCESS);
273
+ for (let index = 0; index < args.length; index += 1) {
274
+ if (args[index]?.startsWith("--allow-file-access=") && !args[index]?.endsWith("=false")) {
275
+ enabled = true;
276
+ continue;
277
+ }
278
+ if (args[index] !== "--allow-file-access")
279
+ continue;
280
+ const value = args[index + 1];
281
+ enabled = value !== "false";
282
+ if (value === "true" || value === "false")
283
+ index += 1;
284
+ }
285
+ return enabled;
286
+ }
287
+ function getManagedBrowserFileAccessValidationError(options) {
288
+ const descriptor = parseArgvDescriptor(options.args);
289
+ const command = descriptor.commandInfo.command;
290
+ const subcommand = descriptor.commandInfo.subcommand;
291
+ const explicitTarget = getResultingExplicitNavigationTarget(options.args, options.currentPageUrl);
292
+ const closesPage = ["close", "exit", "quit"].includes(command ?? "") || (command === "tab" && subcommand === "close");
293
+ const safelyInspectsPageTarget = (command === "tab" && subcommand === "list") || (command === "get" && subcommand === "url");
294
+ const safelySelectsUnverifiedTab = command === "tab" && subcommand !== undefined && !["close", "list", "new"].includes(subcommand);
295
+ const safelyTransitionsUnverifiedPage = options.allowUnverifiedPageTransitions === true
296
+ && command !== "eval"
297
+ && isUnverifiedPageTransitionCommand(command, subcommand);
298
+ const safelyLeavesPage = explicitTarget !== undefined && !isFileUrl(explicitTarget) && !isProtectedAgentBrowserFileTarget(explicitTarget, options.cwd);
299
+ if (fileAccessFlagIsEnabled(options.args, options.env) || rawBrowserArgsEnableFileAccess(options.args, options.env))
300
+ return FILE_ACCESS_FLAG_MESSAGE;
301
+ if (getBrowserContentUrlOperands(options.args).some(isFileUrl))
302
+ return BLOCKED_MANAGED_BROWSER_FILE_MESSAGE;
303
+ if (getBrowserFileOperands(options.args, options.env).some((value) => isProtectedAgentBrowserFileTarget(value, options.cwd)))
304
+ return BLOCKED_MANAGED_BROWSER_FILE_MESSAGE;
305
+ if (command === "eval" && options.stdin !== undefined && /(?:\.agent-browser|%2eagent-browser)/i.test(options.stdin))
306
+ return BLOCKED_MANAGED_BROWSER_FILE_MESSAGE;
307
+ if (options.pageUrlUnknown && !closesPage && !safelyInspectsPageTarget && !safelySelectsUnverifiedTab && !safelyTransitionsUnverifiedPage && !safelyLeavesPage && !(options.trustedBatchTabSelection && command === "tab"))
308
+ return UNVERIFIED_PAGE_MESSAGE;
309
+ if (isProtectedAgentBrowserFileTarget(options.currentPageUrl, options.cwd) && !closesPage && !safelyInspectsPageTarget && !safelyLeavesPage)
310
+ return BLOCKED_MANAGED_BROWSER_FILE_MESSAGE;
311
+ if (isFileUrl(options.currentPageUrl) && !closesPage && !safelyInspectsPageTarget && !safelyLeavesPage)
312
+ return BLOCKED_MANAGED_BROWSER_FILE_MESSAGE;
313
+ return undefined;
314
+ }
315
+ function getBatchCommandSteps(args, stdin) {
316
+ const descriptor = parseArgvDescriptor(args);
317
+ if (descriptor.commandInfo.command !== "batch")
318
+ return { steps: [] };
319
+ const steps = [];
320
+ for (const command of descriptor.commandTokens.slice(1)) {
321
+ if (command === "--bail" || command.startsWith("--bail="))
322
+ continue;
323
+ if (decodeUrlComponent(command).toLowerCase().includes(".agent-browser"))
324
+ return { error: BLOCKED_MANAGED_BROWSER_FILE_MESSAGE, steps: [] };
325
+ const parsed = parseBatchCommandArgument(command);
326
+ if (parsed.error || parsed.step === undefined)
327
+ return { error: parsed.error ?? UNSAFE_BATCH_ARGUMENT_MESSAGE, steps: [] };
328
+ if (parseArgvDescriptor(parsed.step).commandInfo.command === "batch")
329
+ return { error: NESTED_BATCH_ARGUMENT_MESSAGE, steps: [] };
330
+ steps.push(parsed.step);
331
+ }
332
+ const parsedStdin = parseUserBatchStdin(stdin);
333
+ if (parsedStdin.error)
334
+ return { error: parsedStdin.error, steps: [] };
335
+ for (const step of parsedStdin.steps ?? []) {
336
+ if (parseArgvDescriptor(step).commandInfo.command === "batch")
337
+ return { error: NESTED_BATCH_ARGUMENT_MESSAGE, steps: [] };
338
+ steps.push(step);
339
+ }
340
+ return { steps };
341
+ }
342
+ function batchBailsOnFirstError(args) {
343
+ const descriptor = parseArgvDescriptor(args);
344
+ return descriptor.commandInfo.command === "batch" && descriptor.commandTokens.slice(1).includes("--bail");
345
+ }
346
+ function commandMayChangePageTarget(args, trustedBatchTabSelection) {
347
+ const descriptor = parseArgvDescriptor(args);
348
+ return getExplicitNavigationTarget(args) !== undefined
349
+ || (isUnverifiedPageTransitionCommand(descriptor.commandInfo.command, descriptor.commandInfo.subcommand)
350
+ && !(trustedBatchTabSelection && descriptor.commandInfo.command === "tab"));
351
+ }
352
+ function deduplicatePossibleBatchPageStates(states) {
353
+ const deduplicated = new Map();
354
+ for (const state of states) {
355
+ const key = `${state.pageUrlUnknown ? "unknown" : "known"}\0${state.currentPageUrl ?? ""}`;
356
+ const existing = deduplicated.get(key);
357
+ if (!existing)
358
+ deduplicated.set(key, state);
359
+ else
360
+ existing.retainedAfterFailedNavigation ||= state.retainedAfterFailedNavigation;
361
+ }
362
+ return [...deduplicated.values()];
363
+ }
364
+ export function managedSessionCommandRequiresLivePageVerification(args, stdin) {
365
+ const descriptor = parseArgvDescriptor(args);
366
+ if (descriptor.commandInfo.command === "eval")
367
+ return true;
368
+ if (descriptor.commandInfo.command !== "batch")
369
+ return false;
370
+ const batch = getBatchCommandSteps(args, stdin);
371
+ return batch.error === undefined && batch.steps.some((step) => managedSessionCommandRequiresLivePageVerification(step));
372
+ }
373
+ function getResultingPageState(options) {
374
+ const descriptor = parseArgvDescriptor(options.args);
375
+ const rawExplicitTarget = getExplicitNavigationTarget(options.args);
376
+ const explicitTarget = getResultingExplicitNavigationTarget(options.args, options.currentPageUrl);
377
+ if (explicitTarget !== undefined)
378
+ return { currentPageUrl: explicitTarget, pageUrlUnknown: false };
379
+ if (rawExplicitTarget !== undefined)
380
+ return { pageUrlUnknown: true };
381
+ if (isUnverifiedPageTransitionCommand(descriptor.commandInfo.command, descriptor.commandInfo.subcommand)) {
382
+ return options.trustedBatchTabSelection && descriptor.commandInfo.command === "tab"
383
+ ? { currentPageUrl: options.currentPageUrl, pageUrlUnknown: options.pageUrlUnknown }
384
+ : { pageUrlUnknown: true };
385
+ }
386
+ return { currentPageUrl: options.currentPageUrl, pageUrlUnknown: options.pageUrlUnknown };
387
+ }
388
+ export function getManagedSessionResultingPageState(options) {
389
+ const descriptor = parseArgvDescriptor(options.args);
390
+ let state = { currentPageUrl: options.currentPageUrl, pageUrlUnknown: options.pageUrlUnknown ?? false };
391
+ let pageTargetMayHaveChanged = false;
392
+ if (descriptor.commandInfo.command !== "batch") {
393
+ pageTargetMayHaveChanged = getExplicitNavigationTarget(options.args) !== undefined
394
+ || isUnverifiedPageTransitionCommand(descriptor.commandInfo.command, descriptor.commandInfo.subcommand);
395
+ return { ...getResultingPageState({ ...state, args: options.args, trustedBatchTabSelection: false }), pageTargetMayHaveChanged };
396
+ }
397
+ const batch = getBatchCommandSteps(options.args, options.stdin);
398
+ if (batch.error)
399
+ return { pageTargetMayHaveChanged: true, pageUrlUnknown: true };
400
+ for (let index = 0; index < batch.steps.length; index += 1) {
401
+ const step = batch.steps[index];
402
+ const trustedBatchTabSelection = options.trustedFirstBatchTabSelection === true && index === 0;
403
+ const stepDescriptor = parseArgvDescriptor(step);
404
+ pageTargetMayHaveChanged ||= getExplicitNavigationTarget(step) !== undefined
405
+ || (isUnverifiedPageTransitionCommand(stepDescriptor.commandInfo.command, stepDescriptor.commandInfo.subcommand)
406
+ && !(trustedBatchTabSelection && stepDescriptor.commandInfo.command === "tab"));
407
+ state = getResultingPageState({ ...state, args: step, trustedBatchTabSelection });
408
+ }
409
+ return { ...state, pageTargetMayHaveChanged };
410
+ }
411
+ export function getCallerOwnedSessionLivePageVerificationRequirement(options) {
412
+ const descriptor = parseArgvDescriptor(options.args);
413
+ if (!needsManagedSession(descriptor))
414
+ return undefined;
415
+ if (descriptor.commandInfo.command !== "eval" && isUnverifiedPageTransitionCommand(descriptor.commandInfo.command, descriptor.commandInfo.subcommand))
416
+ return undefined;
417
+ const validationError = getManagedSessionStateAccessValidationError({
418
+ args: options.args,
419
+ cwd: options.cwd,
420
+ env: {},
421
+ pageUrlUnknown: true,
422
+ parentEnv: {},
423
+ stdin: options.stdin,
424
+ allowUnverifiedPageTransitions: true,
425
+ trustedFirstBatchTabSelection: options.trustedFirstBatchTabSelection,
426
+ trustedPinnedEmptyConfig: true,
427
+ });
428
+ return validationError === UNVERIFIED_PAGE_MESSAGE || validationError === NON_BAIL_BATCH_NAVIGATION_MESSAGE
429
+ ? UNVERIFIED_PAGE_MESSAGE
430
+ : undefined;
431
+ }
432
+ export function getManagedSessionTargetAccessValidationError(args, ownedManagedSession, env = process.env) {
433
+ const sessionName = extractExplicitSessionName(args) ?? env.AGENT_BROWSER_SESSION;
434
+ return sessionName && isWrapperManagedSessionName(sessionName) && !ownedManagedSession
435
+ ? BLOCKED_MANAGED_SESSION_MESSAGE
436
+ : undefined;
437
+ }
438
+ function getFlagValues(args, flag, consumeDashValue = false) {
439
+ const values = [];
440
+ for (let index = 0; index < args.length; index += 1) {
441
+ const token = args[index];
442
+ if (token.startsWith(`${flag}=`)) {
443
+ values.push(token.slice(flag.length + 1));
444
+ continue;
445
+ }
446
+ if (token !== flag)
447
+ continue;
448
+ const value = args[index + 1];
449
+ if (flag === "--restore" && !optionalGlobalValueFlagConsumesNext(flag, value))
450
+ continue;
451
+ if (value !== undefined && (consumeDashValue || !value.startsWith("-")))
452
+ values.push(value);
453
+ }
454
+ return values;
455
+ }
456
+ function resolveExistingPath(cwd, value) {
457
+ try {
458
+ return realpathSync(resolve(cwd, value));
459
+ }
460
+ catch {
461
+ return undefined;
462
+ }
463
+ }
464
+ function getReferencedValues(args, cwd, env) {
465
+ const descriptor = parseArgvDescriptor(args);
466
+ const values = [
467
+ ...args,
468
+ ...getFlagValues(args, "--restore"),
469
+ ...getFlagValues(args, "--state", true),
470
+ env.AGENT_BROWSER_RESTORE,
471
+ env.AGENT_BROWSER_STATE,
472
+ ].filter((value) => typeof value === "string" && value.length > 0);
473
+ if (descriptor.commandInfo.command === "state" && ["clear", "load", "rename", "save", "show"].includes(descriptor.commandInfo.subcommand ?? "")) {
474
+ values.push(...descriptor.commandTokens.slice(2));
475
+ }
476
+ for (const value of [...values]) {
477
+ const realPath = resolveExistingPath(cwd, value);
478
+ if (realPath)
479
+ values.push(realPath);
480
+ }
481
+ return values;
482
+ }
483
+ export function getManagedSessionStateAccessValidationError(options) {
484
+ const effectiveEnv = { ...(options.parentEnv ?? process.env), ...options.env };
485
+ const descriptor = parseArgvDescriptor(options.args);
486
+ const command = descriptor.commandInfo.command;
487
+ const subcommand = descriptor.commandInfo.subcommand;
488
+ if (["close", "exit", "quit"].includes(command ?? ""))
489
+ return undefined;
490
+ if (!options.trustedPinnedEmptyConfig && needsManagedSession(descriptor) && agentBrowserConfigIsPresent(options.cwd, effectiveEnv, options.args))
491
+ return UPSTREAM_CONFIG_MESSAGE;
492
+ if (command === "batch") {
493
+ const batch = getBatchCommandSteps(options.args, options.stdin);
494
+ if (batch.error) {
495
+ if (batch.error === BLOCKED_MANAGED_BROWSER_FILE_MESSAGE || batch.error === NESTED_BATCH_ARGUMENT_MESSAGE || batch.error.startsWith("agent_browser batch stdin"))
496
+ return batch.error;
497
+ return UNSAFE_BATCH_ARGUMENT_MESSAGE;
498
+ }
499
+ const bailOnFirstError = batchBailsOnFirstError(options.args);
500
+ let possibleStates = [{
501
+ currentPageUrl: options.currentPageUrl,
502
+ pageUrlUnknown: options.pageUrlUnknown ?? false,
503
+ retainedAfterFailedNavigation: false,
504
+ }];
505
+ for (let index = 0; index < batch.steps.length; index += 1) {
506
+ const step = batch.steps[index];
507
+ const trustedBatchTabSelection = options.trustedFirstBatchTabSelection === true && index === 0;
508
+ let directError;
509
+ let failedNavigationHazard = false;
510
+ for (const state of possibleStates) {
511
+ const error = getManagedSessionStateAccessValidationError({
512
+ ...options,
513
+ args: step,
514
+ currentPageUrl: state.currentPageUrl,
515
+ pageUrlUnknown: state.pageUrlUnknown,
516
+ stdin: undefined,
517
+ trustedFirstBatchTabSelection: trustedBatchTabSelection,
518
+ });
519
+ if (!error)
520
+ continue;
521
+ if (state.retainedAfterFailedNavigation && [BLOCKED_MANAGED_BROWSER_FILE_MESSAGE, UNVERIFIED_PAGE_MESSAGE].includes(error)) {
522
+ failedNavigationHazard = true;
523
+ }
524
+ else {
525
+ directError ??= error;
526
+ }
527
+ }
528
+ if (directError)
529
+ return directError;
530
+ if (failedNavigationHazard)
531
+ return NON_BAIL_BATCH_NAVIGATION_MESSAGE;
532
+ const mayChangePageTarget = commandMayChangePageTarget(step, trustedBatchTabSelection);
533
+ const nextStates = [];
534
+ for (const state of possibleStates) {
535
+ const successState = getResultingPageState({
536
+ args: step,
537
+ currentPageUrl: state.currentPageUrl,
538
+ pageUrlUnknown: state.pageUrlUnknown,
539
+ trustedBatchTabSelection,
540
+ });
541
+ if (nextStates.length >= MAX_NON_BAIL_BATCH_PAGE_STATES)
542
+ return NON_BAIL_BATCH_NAVIGATION_MESSAGE;
543
+ nextStates.push({
544
+ ...successState,
545
+ retainedAfterFailedNavigation: mayChangePageTarget ? false : state.retainedAfterFailedNavigation,
546
+ });
547
+ if (!bailOnFirstError && mayChangePageTarget) {
548
+ if (nextStates.length >= MAX_NON_BAIL_BATCH_PAGE_STATES)
549
+ return NON_BAIL_BATCH_NAVIGATION_MESSAGE;
550
+ nextStates.push({ ...state, retainedAfterFailedNavigation: true });
551
+ }
552
+ }
553
+ possibleStates = deduplicatePossibleBatchPageStates(nextStates);
554
+ }
555
+ }
556
+ const browserFileError = getManagedBrowserFileAccessValidationError({
557
+ allowUnverifiedPageTransitions: options.allowUnverifiedPageTransitions,
558
+ args: options.args,
559
+ currentPageUrl: command === "batch" ? undefined : options.currentPageUrl,
560
+ cwd: options.cwd,
561
+ env: effectiveEnv,
562
+ pageUrlUnknown: command === "batch" ? false : options.pageUrlUnknown,
563
+ stdin: options.stdin,
564
+ trustedBatchTabSelection: options.trustedFirstBatchTabSelection,
565
+ });
566
+ if (browserFileError)
567
+ return browserFileError;
568
+ if (command === "state" && subcommand === "clean")
569
+ return BLOCKED_GLOBAL_STATE_MESSAGE;
570
+ 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)) {
573
+ return BLOCKED_GLOBAL_STATE_MESSAGE;
574
+ }
575
+ }
576
+ const referencedValues = [
577
+ ...getReferencedValues(options.args, options.cwd, effectiveEnv),
578
+ ...(options.stdin ? [options.stdin] : []),
579
+ ];
580
+ const referencedKeys = [...new Set(referencedValues.flatMap(extractManagedSessionRestoreKeys))];
581
+ if (referencedKeys.length === 0)
582
+ return undefined;
583
+ if (command === "state" && ["rename", "save"].includes(subcommand ?? ""))
584
+ return BLOCKED_GLOBAL_STATE_MESSAGE;
585
+ if (!hasManagedSessionRestoreProjectIdentity(options.cwd))
586
+ return BLOCKED_GLOBAL_STATE_MESSAGE;
587
+ const currentKey = createManagedSessionRestoreKey(options.cwd).toLowerCase();
588
+ return referencedKeys.every((key) => key === currentKey) ? undefined : BLOCKED_GLOBAL_STATE_MESSAGE;
589
+ }