pi-agent-browser-native 0.3.0 → 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 (73) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +63 -20
  3. package/dist/extensions/agent-browser/index.js +787 -105
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +44 -2
  6. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +1 -1
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -2
  9. package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -4
  10. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
  11. package/dist/extensions/agent-browser/lib/input-modes/params.js +22 -23
  12. package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
  13. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
  14. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +8 -0
  15. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -1
  16. package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -36
  17. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
  18. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +47 -29
  19. package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -24
  20. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +26 -25
  24. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +17 -3
  25. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
  26. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +6 -4
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +129 -32
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +191 -55
  29. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +57 -29
  30. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +15 -11
  31. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
  32. package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
  33. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
  34. package/dist/extensions/agent-browser/lib/playbook.js +18 -15
  35. package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
  36. package/dist/extensions/agent-browser/lib/process-identity.js +4 -4
  37. package/dist/extensions/agent-browser/lib/process.js +131 -41
  38. package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
  39. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +62 -5
  40. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
  41. package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
  42. package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
  43. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
  44. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
  45. package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -0
  46. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -1
  47. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
  48. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -11
  49. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
  50. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
  51. package/dist/extensions/agent-browser/lib/results/presentation.js +6 -3
  52. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -0
  53. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -8
  54. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
  55. package/dist/extensions/agent-browser/lib/runtime.js +116 -39
  56. package/dist/extensions/agent-browser/lib/session-page-state.js +62 -10
  57. package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
  58. package/dist/extensions/agent-browser/script-worker.js +169 -0
  59. package/dist/scripts/agent-browser-target.mjs +3 -0
  60. package/docs/ARCHITECTURE.md +40 -21
  61. package/docs/COMMAND_REFERENCE.md +90 -35
  62. package/docs/RELEASE.md +3 -3
  63. package/docs/REQUIREMENTS.md +4 -2
  64. package/docs/SUPPORT_MATRIX.md +26 -19
  65. package/docs/TOOL_CONTRACT.md +93 -52
  66. package/package.json +3 -1
  67. package/platform-smoke.config.mjs +2 -2
  68. package/scripts/agent-browser-capability-baseline.mjs +24 -6
  69. package/scripts/agent-browser-target.mjs +3 -0
  70. package/scripts/build.mjs +41 -0
  71. package/scripts/doctor.mjs +7 -6
  72. package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
  73. package/scripts/platform-smoke/targets.mjs +12 -6
@@ -1,6 +1,6 @@
1
1
  import { copyFile, mkdir } from "node:fs/promises";
2
2
  import { dirname, resolve } from "node:path";
3
- import { getBooleanFlagValue, isUpstreamEnvFlagEnabled } from "../../argv-grammar.js";
3
+ import { getBooleanFlagValue, isUpstreamEnvFlagEnabled, projectUpstreamGlobalFlags } from "../../argv-grammar.js";
4
4
  import { isCloseCommand } from "../../command-taxonomy.js";
5
5
  import { cleanupElectronLaunchResources } from "../../electron/cleanup.js";
6
6
  import { launchElectronApp } from "../../electron/launch.js";
@@ -17,12 +17,13 @@ import { applyNamespaceToNextActions } from "../../results/next-actions.js";
17
17
  import { buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions } from "../../results/recovery-next-actions.js";
18
18
  import { resolveVisibleRefActionFromSnapshot } from "../../results/selector-recovery.js";
19
19
  import { extractRefSnapshotFromData } from "../../session-page-state.js";
20
- import { buildExecutionPlan, createFreshSessionName, extractCommandTokens, getDefaultHeadlessCompatUserAgent, redactInvocationArgs, } from "../../runtime.js";
20
+ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createFreshSessionName, extractCommandTokens, extractUpstreamCommandTokens, getDefaultHeadlessCompatUserAgent, parseWaitCommandTokens, redactInvocationArgs, } from "../../runtime.js";
21
21
  import { buildOwnedManagedSessionRestoreContext, canonicalizeOwnedManagedSessionCloseArgs, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
22
+ import { getAgentBrowserProcessEnvironment } from "../../process-environment.js";
22
23
  import { getCallerOwnedSessionLivePageVerificationRequirement, getManagedSessionStateAccessValidationError, getManagedSessionTargetAccessValidationError, } from "../../managed-session-state-policy.js";
23
24
  import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError } from "./managed-session-daemon-policy.js";
24
25
  import { applyOpenResultTabCorrection, buildManagedSessionOutcome, buildPinnedBatchPlan, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, extractStringResultField, collectAnySessionTabSelection, collectSessionTabSelection, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
25
- import { parseBatchStdinJsonArray } from "../batch-stdin.js";
26
+ import { getUpstreamEffectiveBatchSteps, parseBatchStdinJsonArray } from "../batch-stdin.js";
26
27
  import { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
27
28
  import { prepareClickDispatchProbe } from "./click-dispatch.js";
28
29
  import { collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
@@ -43,6 +44,7 @@ export function normalizeRunInput(input) {
43
44
  return { ...base, compiledSemanticAction: input.compiledSemanticAction, redactedCompiledSemanticAction: input.redactedCompiledSemanticAction };
44
45
  case "sourceLookup":
45
46
  return { ...base, compiledSourceLookup: input.compiledSourceLookup, redactedCompiledSourceLookup: input.redactedCompiledSourceLookup };
47
+ case "script":
46
48
  case "args":
47
49
  return base;
48
50
  }
@@ -58,12 +60,8 @@ function getArtifactParentPathTokenIndex(commandTokens) {
58
60
  return 1;
59
61
  if (commandTokens[0] === "state" && commandTokens[1] === "save" && commandTokens.length >= 3)
60
62
  return 2;
61
- if (commandTokens[0] === "wait") {
62
- const downloadIndex = commandTokens.findIndex((token) => token === "--download");
63
- const pathIndex = downloadIndex >= 0 ? downloadIndex + 1 : -1;
64
- if (pathIndex > 0 && typeof commandTokens[pathIndex] === "string" && !commandTokens[pathIndex].startsWith("-"))
65
- return pathIndex;
66
- }
63
+ if (commandTokens[0] === "wait")
64
+ return parseWaitCommandTokens(commandTokens).downloadPathIndex;
67
65
  return undefined;
68
66
  }
69
67
  async function ensureArtifactParentDirectory(commandTokens, cwd) {
@@ -76,10 +74,14 @@ async function ensureArtifactParentDirectory(commandTokens, cwd) {
76
74
  await mkdir(dirname(resolve(cwd, requestedPath)), { recursive: true });
77
75
  }
78
76
  async function normalizeScreenshotPathInTokens(commandTokens, cwd) {
79
- const screenshotPathTokenIndex = getScreenshotPathTokenIndex(commandTokens);
80
- if (screenshotPathTokenIndex === undefined) {
77
+ const scopedCommandTokens = extractCommandTokens(commandTokens);
78
+ const projection = projectUpstreamGlobalFlags(scopedCommandTokens);
79
+ const projectedPathTokenIndex = getScreenshotPathTokenIndex(projection.tokens);
80
+ const scopedPathTokenIndex = projectedPathTokenIndex === undefined ? undefined : projection.indices[projectedPathTokenIndex];
81
+ if (scopedPathTokenIndex === undefined) {
81
82
  return { tokens: commandTokens };
82
83
  }
84
+ const screenshotPathTokenIndex = commandTokens.length - scopedCommandTokens.length + scopedPathTokenIndex;
83
85
  const requestedPath = commandTokens[screenshotPathTokenIndex];
84
86
  const absolutePath = resolve(cwd, requestedPath);
85
87
  await mkdir(dirname(absolutePath), { recursive: true });
@@ -98,8 +100,28 @@ async function normalizeScreenshotPathInTokens(commandTokens, cwd) {
98
100
  };
99
101
  }
100
102
  async function prepareBatchScreenshotPaths(args, stdin, cwd) {
101
- const commandTokens = extractCommandTokens(args);
102
- if (commandTokens[0] !== "batch" || stdin === undefined) {
103
+ const commandTokens = extractUpstreamCommandTokens(args);
104
+ if (commandTokens[0] !== "batch") {
105
+ return undefined;
106
+ }
107
+ const argumentSteps = getUpstreamEffectiveBatchSteps(commandTokens, undefined);
108
+ if (argumentSteps.length > 0) {
109
+ // Upstream executes raw argument steps exclusively and ignores stdin, so
110
+ // prepare parent directories for the rows that will run and skip stdin
111
+ // preparation (no directories for never-executed rows).
112
+ for (const step of argumentSteps) {
113
+ const stepTokens = extractUpstreamCommandTokens(step);
114
+ await ensureArtifactParentDirectory(stepTokens, cwd);
115
+ if (stepTokens[0] === "screenshot") {
116
+ // Reuse the screenshot path resolution for its parent-directory side
117
+ // effect only: raw strings are never rewritten, so the normalized
118
+ // tokens and path request are deliberately discarded.
119
+ await normalizeScreenshotPathInTokens(step, cwd);
120
+ }
121
+ }
122
+ return undefined;
123
+ }
124
+ if (stdin === undefined) {
103
125
  return undefined;
104
126
  }
105
127
  const parsed = parseBatchStdinJsonArray(stdin);
@@ -112,8 +134,9 @@ async function prepareBatchScreenshotPaths(args, stdin, cwd) {
112
134
  if (!Array.isArray(step) || !step.every((item) => typeof item === "string")) {
113
135
  return step;
114
136
  }
115
- await ensureArtifactParentDirectory(step, cwd);
116
- if (step[0] !== "screenshot") {
137
+ const upstreamStep = extractUpstreamCommandTokens(step);
138
+ await ensureArtifactParentDirectory(upstreamStep, cwd);
139
+ if (upstreamStep[0] !== "screenshot") {
117
140
  return step;
118
141
  }
119
142
  const normalized = await normalizeScreenshotPathInTokens(step, cwd);
@@ -137,7 +160,7 @@ export async function prepareAgentBrowserArgs(args, stdin, cwd) {
137
160
  return preparedBatch;
138
161
  }
139
162
  const commandTokens = extractCommandTokens(args);
140
- await ensureArtifactParentDirectory(commandTokens, cwd);
163
+ await ensureArtifactParentDirectory(extractUpstreamCommandTokens(args), cwd);
141
164
  const normalized = await normalizeScreenshotPathInTokens(commandTokens, cwd);
142
165
  if (!normalized.request) {
143
166
  return { args };
@@ -181,7 +204,7 @@ const LIKELY_DIALOG_TRIGGER_PROCESS_TIMEOUT_MS = 8_000;
181
204
  const LIKELY_DIALOG_TRIGGER_PROCESS_TIMEOUT_ENV = "PI_AGENT_BROWSER_DIALOG_TRIGGER_PROCESS_TIMEOUT_MS";
182
205
  const DIALOG_TRIGGER_TEXT_PATTERN = /\b(?:alert|confirm|dialog|prompt)\b/i;
183
206
  function getPositiveIntegerEnv(name) {
184
- const value = process.env[name];
207
+ const value = getAgentBrowserProcessEnvironment()[name];
185
208
  if (!value || !/^\d+$/.test(value.trim()))
186
209
  return undefined;
187
210
  const parsed = Number(value.trim());
@@ -215,9 +238,7 @@ function describeRef(refSnapshot, refId) {
215
238
  return ref ? `${ref.role} ${JSON.stringify(ref.name)}` : "not present";
216
239
  }
217
240
  function getSamePageFreshnessPreflightFailure(options) {
218
- if (options.commandTokens[0] === "batch")
219
- return undefined;
220
- const refIds = getRefIdsFromDirectCommand(options.commandTokens);
241
+ const { refIds } = options;
221
242
  if (refIds.length === 0)
222
243
  return undefined;
223
244
  const previousUrl = options.previousSnapshot.target?.url;
@@ -243,7 +264,8 @@ function getSamePageFreshnessPreflightFailure(options) {
243
264
  };
244
265
  }
245
266
  async function collectSamePageRefFreshnessPreflight(options) {
246
- if (!options.previousSnapshot || !options.sessionName || options.commandTokens[0] === "batch" || getRefIdsFromDirectCommand(options.commandTokens).length === 0)
267
+ const refIds = [...new Set(getGuardedRefUsage(options.commandTokens, options.stdin))];
268
+ if (!options.previousSnapshot || !options.sessionName || refIds.length === 0)
247
269
  return undefined;
248
270
  const previousUrl = options.previousSnapshot.target?.url;
249
271
  const currentTargetUrl = options.currentTarget?.url;
@@ -254,7 +276,7 @@ async function collectSamePageRefFreshnessPreflight(options) {
254
276
  if (!currentSnapshot)
255
277
  return undefined;
256
278
  const snapshotWithTarget = { ...currentSnapshot, target: currentSnapshot.target ?? options.currentTarget };
257
- const mismatch = getSamePageFreshnessPreflightFailure({ commandTokens: options.commandTokens, currentSnapshot: snapshotWithTarget, previousSnapshot: options.previousSnapshot });
279
+ const mismatch = getSamePageFreshnessPreflightFailure({ currentSnapshot: snapshotWithTarget, previousSnapshot: options.previousSnapshot, refIds });
258
280
  if (!mismatch)
259
281
  return undefined;
260
282
  return { message: mismatch.message, refIds: mismatch.refIds, snapshot: snapshotWithTarget };
@@ -297,7 +319,14 @@ export function validateStdinCommandContract(options) {
297
319
  return `agent_browser stdin is only supported for \`batch\`, \`eval --stdin\`, and \`auth save --password-stdin\`; remove stdin from ${commandLabel} or use one of those command forms.`;
298
320
  }
299
321
  function canResolveSemanticVisibleRef(compiled) {
300
- return compiled !== undefined && compiled.locator === "role" && ["check", "click", "fill"].includes(compiled.action);
322
+ if (!compiled?.locator)
323
+ return false;
324
+ if (compiled.action === "select")
325
+ return true;
326
+ return compiled.locator === "role" && ["check", "click", "fill"].includes(compiled.action);
327
+ }
328
+ function requiresResolvedSemanticVisibleRef(compiled) {
329
+ return compiled?.action === "select" && compiled.locator !== undefined;
301
330
  }
302
331
  function resolveSemanticActionVisibleRefArgsFromSnapshot(compiled, snapshotData) {
303
332
  if (!canResolveSemanticVisibleRef(compiled))
@@ -316,6 +345,7 @@ export async function resolveSemanticActionVisibleRefArgs(options) {
316
345
  export async function prepareBrowserRun(options) {
317
346
  const { cwd, onUpdate, params, signal, state } = options;
318
347
  const { sessionPageState, traceOwners, managedSessionBaseName, ephemeralSessionSeed } = state;
348
+ const agentBrowserProcessEnv = getAgentBrowserProcessEnvironment();
319
349
  let freshSessionOrdinal = state.freshSessionOrdinal;
320
350
  const { compiledElectron, compiledJob, compiledNetworkSourceLookup, compiledQaPreset, compiledSemanticAction, compiledSourceLookup, redactedArgs, redactedCompiledElectron, redactedCompiledJob, redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset, redactedCompiledSemanticAction, redactedCompiledSourceLookup, toolArgs, toolStdin, } = normalizeRunInput(options.input);
321
351
  let runtimeToolArgs = toolArgs;
@@ -326,6 +356,7 @@ export async function prepareBrowserRun(options) {
326
356
  const rawManagedStateAccessError = getManagedSessionStateAccessValidationError({
327
357
  args: runtimeToolArgs,
328
358
  cwd,
359
+ parentEnv: agentBrowserProcessEnv,
329
360
  stdin: runtimeToolStdin,
330
361
  trustedFirstBatchTabSelection: true,
331
362
  });
@@ -393,6 +424,7 @@ export async function prepareBrowserRun(options) {
393
424
  currentPageUrl: plannedSessionPageState.tabTarget?.url,
394
425
  pageUrlUnknown: plannedSessionPageState.tabTargetUnknown === true,
395
426
  cwd,
427
+ parentEnv: agentBrowserProcessEnv,
396
428
  stdin: runtimeToolStdin,
397
429
  });
398
430
  if (!executionPlan.validationError && managedStateAccessError)
@@ -400,18 +432,41 @@ export async function prepareBrowserRun(options) {
400
432
  const recordedOwnedSession = ownedSessionKey ? state.ownedManagedSessions.get(ownedSessionKey) : undefined;
401
433
  const targetsCurrentManagedSession = state.managedSessionActive
402
434
  && ownedSessionKey === getSessionContextKey(state.managedSessionName, state.managedSessionNamespace);
435
+ const targetsOffCurrentOwnedSession = recordedOwnedSession !== undefined && !targetsCurrentManagedSession;
436
+ const offCurrentLaunchScopedFlags = targetsOffCurrentOwnedSession
437
+ ? executionPlan.startupScopedFlags.filter((flag) => flag !== "--namespace")
438
+ : [];
439
+ const offCurrentCompatibilityUpgrade = targetsOffCurrentOwnedSession
440
+ && executionPlan.compatibilityWorkaround !== undefined
441
+ && recordedOwnedSession.compatibilityWorkaround === undefined;
442
+ if (targetsOffCurrentOwnedSession && canUseHeadlessCompatibilityUserAgent(preparedArgs.args, agentBrowserProcessEnv)) {
443
+ const compatibilityWorkaround = executionPlan.compatibilityWorkaround ?? recordedOwnedSession.compatibilityWorkaround;
444
+ if (compatibilityWorkaround) {
445
+ const userAgentIndex = executionPlan.effectiveArgs.indexOf("--user-agent");
446
+ executionPlan = {
447
+ ...executionPlan,
448
+ compatibilityWorkaround,
449
+ effectiveArgs: userAgentIndex < 0
450
+ ? executionPlan.effectiveArgs
451
+ : [...executionPlan.effectiveArgs.slice(0, userAgentIndex), ...executionPlan.effectiveArgs.slice(userAgentIndex + 2)],
452
+ };
453
+ }
454
+ }
403
455
  const retainedHeadedAutosaveDisabled = recordedOwnedSession?.headedManagedAutosaveDisabled === true
404
456
  || (targetsCurrentManagedSession && state.managedSessionHeadedAutosaveDisabled === true);
405
457
  const retainedHeadedAutosaveInterval = recordedOwnedSession?.headedManagedAutosaveInterval
406
458
  ?? (targetsCurrentManagedSession ? state.managedSessionHeadedAutosaveInterval : undefined);
407
- const explicitAutosaveInterval = resolveExplicitAutosaveInterval(process.env.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS);
459
+ const explicitAutosaveInterval = resolveExplicitAutosaveInterval(agentBrowserProcessEnv.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS);
408
460
  const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(retainedHeadedAutosaveInterval, isCloseCommand(executionPlan.commandInfo.command));
409
461
  if (!executionPlan.validationError && autosavePolicyChangeError) {
410
462
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: autosavePolicyChangeError };
411
463
  }
412
- const headedLaunch = getBooleanFlagValue(executionPlan.effectiveArgs, "--headed") ?? isUpstreamEnvFlagEnabled(process.env.AGENT_BROWSER_HEADED);
464
+ const headedLaunch = getBooleanFlagValue(executionPlan.effectiveArgs, "--headed") ?? isUpstreamEnvFlagEnabled(agentBrowserProcessEnv.AGENT_BROWSER_HEADED);
413
465
  const headedManagedAutosaveDisabled = retainedHeadedAutosaveDisabled || (explicitAutosaveInterval === undefined && headedLaunch);
414
466
  const headedManagedAutosaveInterval = retainedHeadedAutosaveInterval ?? (headedLaunch ? explicitAutosaveInterval ?? "0" : undefined);
467
+ const compatibilityUserAgent = executionPlan.compatibilityWorkaround ? getDefaultHeadlessCompatUserAgent() : undefined;
468
+ const compatibilityUserAgentApplied = compatibilityUserAgent !== undefined
469
+ && executionPlan.effectiveArgs.some((token, index) => token === "--user-agent" && executionPlan.effectiveArgs[index + 1] === compatibilityUserAgent);
415
470
  const ownedManagedSession = buildOwnedManagedSessionRestoreContext({
416
471
  args: executionPlan.effectiveArgs,
417
472
  cwd: recordedOwnedSession?.cwd ?? cwd,
@@ -421,14 +476,15 @@ export async function prepareBrowserRun(options) {
421
476
  headedManagedAutosaveInterval,
422
477
  managedSessionName: executionPlan.managedSessionName,
423
478
  namespace: executionPlan.namespace,
479
+ parentEnv: agentBrowserProcessEnv,
424
480
  recordedOwnedSession,
425
481
  restoreState: state.managedSessionRestoreState,
426
482
  sessionName: executionPlan.sessionName,
427
483
  stdin: runtimeToolStdin,
428
- compatibilityUserAgent: executionPlan.compatibilityWorkaround ? getDefaultHeadlessCompatUserAgent() : undefined,
429
- wrapperInjectedUserAgent: executionPlan.compatibilityWorkaround !== undefined,
484
+ compatibilityUserAgent: compatibilityUserAgentApplied ? compatibilityUserAgent : undefined,
485
+ wrapperInjectedUserAgent: compatibilityUserAgentApplied,
430
486
  });
431
- const managedSessionTargetError = getManagedSessionTargetAccessValidationError(executionPlan.effectiveArgs, ownedManagedSession !== undefined);
487
+ const managedSessionTargetError = getManagedSessionTargetAccessValidationError(executionPlan.effectiveArgs, ownedManagedSession !== undefined, agentBrowserProcessEnv);
432
488
  if (!executionPlan.validationError && managedSessionTargetError)
433
489
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: managedSessionTargetError };
434
490
  if (!executionPlan.validationError && ownedManagedSession) {
@@ -446,6 +502,27 @@ export async function prepareBrowserRun(options) {
446
502
  validationError: policy.error,
447
503
  };
448
504
  }
505
+ else if (!closeCommand && policy.daemonStatus === "active" && offCurrentLaunchScopedFlags.length > 0) {
506
+ executionPlan = {
507
+ ...executionPlan,
508
+ recoveryHint: undefined,
509
+ validationError: `This older wrapper-owned session is already running, so launch-scoped flags ${offCurrentLaunchScopedFlags.join(", ")} would replace or be ignored by upstream agent-browser. Close it first, or remove the explicit --session and retry with sessionMode: \"fresh\".`,
510
+ };
511
+ }
512
+ else if (!closeCommand && policy.daemonStatus === "active" && offCurrentCompatibilityUpgrade) {
513
+ executionPlan = {
514
+ ...executionPlan,
515
+ recoveryHint: undefined,
516
+ validationError: "This older wrapper-owned session is already running without the user agent required by this site. Close it first, or remove the explicit --session and retry with sessionMode: \"fresh\".",
517
+ };
518
+ }
519
+ else if (!closeCommand && policy.daemonStatus === "inactive" && compatibilityUserAgent && !compatibilityUserAgentApplied) {
520
+ ownedManagedSession.compatibilityUserAgent = compatibilityUserAgent;
521
+ executionPlan = {
522
+ ...executionPlan,
523
+ effectiveArgs: ["--user-agent", compatibilityUserAgent, ...executionPlan.effectiveArgs],
524
+ };
525
+ }
449
526
  else if (closeCommand && managedSessionPolicyLock) {
450
527
  executionPlan = {
451
528
  ...executionPlan,
@@ -505,6 +582,7 @@ export async function prepareBrowserRun(options) {
505
582
  currentPageUrl: liveUrl,
506
583
  cwd,
507
584
  pageUrlUnknown: false,
585
+ parentEnv: agentBrowserProcessEnv,
508
586
  stdin: request.stdin,
509
587
  });
510
588
  if (livePageValidationError) {
@@ -515,7 +593,8 @@ export async function prepareBrowserRun(options) {
515
593
  priorSessionTabTarget ??= { url: liveUrl };
516
594
  priorSessionTabTargetUnknown = undefined;
517
595
  };
518
- const mayResolveSemanticVisibleRef = executionPlan.managedSessionName !== freshSessionName && canResolveSemanticVisibleRef(compiledSemanticAction);
596
+ const hasPotentialLiveSemanticSession = state.managedSessionActive || priorSessionTabTarget !== undefined || isCallerOwnedExplicitSession() || options.preserveAttachedBrowserSession === true;
597
+ const mayResolveSemanticVisibleRef = executionPlan.managedSessionName !== freshSessionName && hasPotentialLiveSemanticSession && canResolveSemanticVisibleRef(compiledSemanticAction);
519
598
  if (!executionPlan.validationError && mayResolveSemanticVisibleRef && requiresLivePageVerification()) {
520
599
  await verifyLivePage({
521
600
  args: ["snapshot", "-i"],
@@ -531,6 +610,17 @@ export async function prepareBrowserRun(options) {
531
610
  signal,
532
611
  });
533
612
  }
613
+ if (!executionPlan.validationError && requiresResolvedSemanticVisibleRef(compiledSemanticAction) && !semanticActionVisibleRefResolution) {
614
+ const freshLocatorError = executionPlan.managedSessionName === freshSessionName
615
+ ? "semanticAction select with locator cannot resolve a current @ref in sessionMode fresh. Open the page first, then reuse that session, or pass selector plus value/values."
616
+ : undefined;
617
+ executionPlan = {
618
+ ...executionPlan,
619
+ validationError: freshLocatorError ?? (hasPotentialLiveSemanticSession
620
+ ? "semanticAction select with locator could not resolve to exactly one current visible combobox/listbox ref. Run snapshot -i and retry with selector or a more specific role/name."
621
+ : "semanticAction select with locator requires an active browser session so the wrapper can resolve a current @ref; open a page first or pass selector plus value/values."),
622
+ };
623
+ }
534
624
  if (semanticActionVisibleRefResolution) {
535
625
  executionPlan = buildExecutionPlan(semanticActionVisibleRefResolution.args, {
536
626
  freshSessionName,
@@ -541,7 +631,7 @@ export async function prepareBrowserRun(options) {
541
631
  sessionMode,
542
632
  });
543
633
  }
544
- const commandTokens = semanticActionVisibleRefResolution ? extractCommandTokens(semanticActionVisibleRefResolution.args) : extractCommandTokens(preparedArgs.args);
634
+ const commandTokens = semanticActionVisibleRefResolution ? extractUpstreamCommandTokens(semanticActionVisibleRefResolution.args) : extractUpstreamCommandTokens(preparedArgs.args);
545
635
  const resolvedSemanticActionRefSnapshot = semanticActionVisibleRefResolution?.snapshot
546
636
  ? { ...semanticActionVisibleRefResolution.snapshot, target: semanticActionVisibleRefResolution.snapshot.target ?? priorSessionTabTarget }
547
637
  : undefined;
@@ -696,6 +786,7 @@ export async function prepareBrowserRun(options) {
696
786
  cwd,
697
787
  currentTarget: priorSessionTabTarget,
698
788
  previousSnapshot: resolvedSemanticActionRefSnapshot ? undefined : priorRefSnapshotState,
789
+ stdin: runtimeToolStdin,
699
790
  namespace: executionPlan.namespace,
700
791
  sessionName: executionPlan.sessionName,
701
792
  signal,
@@ -921,7 +1012,11 @@ export async function prepareBrowserRun(options) {
921
1012
  }
922
1013
  if (pinnedBatchPlan) {
923
1014
  sessionTabCorrection = plannedSessionTabSelection;
924
- processArgs = ["--json", ...(executionPlan.namespace ? ["--namespace", executionPlan.namespace] : []), "--session", executionPlan.sessionName, "batch"];
1015
+ // The rewritten batch must keep the caller's fail-fast semantics:
1016
+ // upstream reads only the exact --bail token, so re-emit it whenever
1017
+ // the original batch tokens carried it (argv or stdin/job/qa mode).
1018
+ const pinnedBailArgs = commandTokens.includes("--bail") ? ["--bail"] : [];
1019
+ processArgs = ["--json", ...(executionPlan.namespace !== undefined ? ["--namespace", executionPlan.namespace] : []), "--session", executionPlan.sessionName, "batch", ...pinnedBailArgs];
925
1020
  processStdin = JSON.stringify(pinnedBatchPlan.steps);
926
1021
  includePinnedNavigationSummary = pinnedBatchPlan.includeNavigationSummary;
927
1022
  pinnedBatchUnwrapMode = pinnedBatchPlan.unwrapMode;
@@ -990,7 +1085,9 @@ export async function prepareBrowserRun(options) {
990
1085
  redactedCompiledJob,
991
1086
  redactedCompiledNetworkSourceLookup,
992
1087
  redactedCompiledQaPreset,
993
- redactedCompiledSemanticAction,
1088
+ redactedCompiledSemanticAction: semanticActionVisibleRefResolution && redactedCompiledSemanticAction?.action === "select"
1089
+ ? { ...redactedCompiledSemanticAction, args: redactInvocationArgs(semanticActionVisibleRefResolution.args) }
1090
+ : redactedCompiledSemanticAction,
994
1091
  redactedCompiledSourceLookup,
995
1092
  redactedEffectiveArgs,
996
1093
  redactedProcessArgs,