pi-agent-browser-native 0.3.0 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/README.md +130 -54
  3. package/dist/extensions/agent-browser/index.js +781 -169
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +50 -2
  6. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +5 -8
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +53 -12
  9. package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
  10. package/dist/extensions/agent-browser/lib/config.js +1 -1
  11. package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -13
  12. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
  13. package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -24
  14. package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
  15. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
  16. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +26 -4
  17. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +6 -139
  18. package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -116
  19. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
  20. package/dist/extensions/agent-browser/lib/managed-session-storage.js +54 -25
  21. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
  24. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +54 -48
  25. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +71 -5
  26. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +6 -7
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
  29. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +152 -64
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +244 -102
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +63 -37
  33. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +20 -21
  34. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
  35. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
  36. package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
  37. package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
  38. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
  39. package/dist/extensions/agent-browser/lib/playbook.js +29 -25
  40. package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
  41. package/dist/extensions/agent-browser/lib/process-identity.js +5 -12
  42. package/dist/extensions/agent-browser/lib/process.js +130 -104
  43. package/dist/extensions/agent-browser/lib/recording-reservations.js +116 -0
  44. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -6
  45. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
  46. package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
  47. package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
  48. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
  49. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +86 -18
  50. package/dist/extensions/agent-browser/lib/results/presentation/common.js +38 -2
  51. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +18 -17
  52. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
  53. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +38 -20
  54. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +60 -15
  55. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
  56. package/dist/extensions/agent-browser/lib/results/presentation.js +36 -6
  57. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +3 -1
  58. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
  59. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +54 -11
  60. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
  61. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
  62. package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
  63. package/dist/extensions/agent-browser/lib/runtime.js +186 -108
  64. package/dist/extensions/agent-browser/lib/session-page-state.js +71 -10
  65. package/dist/extensions/agent-browser/lib/temp.js +1 -2
  66. package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
  67. package/dist/extensions/agent-browser/lib/web-search.js +108 -24
  68. package/dist/extensions/agent-browser/script-worker.js +169 -0
  69. package/dist/scripts/agent-browser-target.mjs +21 -0
  70. package/docs/ARCHITECTURE.md +57 -34
  71. package/docs/COMMAND_REFERENCE.md +255 -68
  72. package/docs/ELECTRON.md +2 -2
  73. package/docs/RELEASE.md +12 -10
  74. package/docs/REQUIREMENTS.md +11 -8
  75. package/docs/SUPPORT_MATRIX.md +36 -24
  76. package/docs/TOOL_CONTRACT.md +169 -95
  77. package/package.json +3 -1
  78. package/platform-smoke.config.mjs +2 -2
  79. package/scripts/agent-browser-capability-baseline.mjs +87 -9
  80. package/scripts/agent-browser-target.mjs +21 -0
  81. package/scripts/build.mjs +41 -0
  82. package/scripts/config.mjs +1 -0
  83. package/scripts/doctor.mjs +16 -9
  84. package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
  85. package/scripts/platform-smoke/targets.mjs +12 -6
  86. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
  87. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -583
  88. package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
  89. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
@@ -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,15 +17,16 @@ 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";
21
- import { buildOwnedManagedSessionRestoreContext, canonicalizeOwnedManagedSessionCloseArgs, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
22
- import { getCallerOwnedSessionLivePageVerificationRequirement, getManagedSessionStateAccessValidationError, getManagedSessionTargetAccessValidationError, } from "../../managed-session-state-policy.js";
20
+ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createFreshSessionName, extractCommandTokens, extractUpstreamCommandTokens, getDefaultHeadlessCompatUserAgent, parseWaitCommandTokens, redactInvocationArgs, } from "../../runtime.js";
21
+ import { buildOwnedManagedSessionRestoreContext, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
22
+ import { getAgentBrowserProcessEnvironment } from "../../process-environment.js";
23
+ import { getExplicitSessionPageVerificationRequirement, getPageTargetValidationError, } from "../../page-target-validation.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 { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
26
+ import { getUpstreamEffectiveBatchSteps, parseBatchStdinJsonArray } from "../batch-stdin.js";
27
+ import { buildElectronHostFailureResult, formatAgentBrowserNextActionsText, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
27
28
  import { prepareClickDispatchProbe } from "./click-dispatch.js";
28
- import { collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
29
+ import { buildUnsupportedScrollIntoViewRecovery, collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
29
30
  import { getScreenshotPathTokenIndex } from "./artifact-paths.js";
30
31
  import { findRequestedArtifactCloseViolation } from "./prompt-guards.js";
31
32
  export function normalizeRunInput(input) {
@@ -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,17 +276,16 @@ 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 };
261
283
  }
262
284
  function getIdleTimeoutMismatch(args, configuredValue) {
263
285
  for (let index = 0; index < args.length; index += 1) {
264
- const token = args[index];
265
- if (token !== "--idle-timeout" && !token.startsWith("--idle-timeout="))
286
+ if (args[index] !== "--idle-timeout")
266
287
  continue;
267
- const requestedToken = token.includes("=") ? token.slice(token.indexOf("=") + 1) : args[++index];
288
+ const requestedToken = args[++index];
268
289
  if (!requestedToken || !/^\d+$/.test(requestedToken) || Number(requestedToken) === Number(configuredValue))
269
290
  continue;
270
291
  return `--idle-timeout ${requestedToken} conflicts with this Pi process's managed-session idle timeout (${configuredValue} ms). Restart Pi with PI_AGENT_BROWSER_IMPLICIT_SESSION_IDLE_TIMEOUT_MS=${requestedToken} and omit --idle-timeout; changing the launch value for one call can restart the upstream browser and discard the active tab.`;
@@ -297,7 +318,14 @@ export function validateStdinCommandContract(options) {
297
318
  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
319
  }
299
320
  function canResolveSemanticVisibleRef(compiled) {
300
- return compiled !== undefined && compiled.locator === "role" && ["check", "click", "fill"].includes(compiled.action);
321
+ if (!compiled?.locator)
322
+ return false;
323
+ if (compiled.action === "select")
324
+ return true;
325
+ return compiled.locator === "role" && ["check", "click", "fill"].includes(compiled.action);
326
+ }
327
+ function requiresResolvedSemanticVisibleRef(compiled) {
328
+ return compiled?.action === "select" && compiled.locator !== undefined;
301
329
  }
302
330
  function resolveSemanticActionVisibleRefArgsFromSnapshot(compiled, snapshotData) {
303
331
  if (!canResolveSemanticVisibleRef(compiled))
@@ -316,6 +344,7 @@ export async function resolveSemanticActionVisibleRefArgs(options) {
316
344
  export async function prepareBrowserRun(options) {
317
345
  const { cwd, onUpdate, params, signal, state } = options;
318
346
  const { sessionPageState, traceOwners, managedSessionBaseName, ephemeralSessionSeed } = state;
347
+ const agentBrowserProcessEnv = getAgentBrowserProcessEnvironment();
319
348
  let freshSessionOrdinal = state.freshSessionOrdinal;
320
349
  const { compiledElectron, compiledJob, compiledNetworkSourceLookup, compiledQaPreset, compiledSemanticAction, compiledSourceLookup, redactedArgs, redactedCompiledElectron, redactedCompiledJob, redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset, redactedCompiledSemanticAction, redactedCompiledSourceLookup, toolArgs, toolStdin, } = normalizeRunInput(options.input);
321
350
  let runtimeToolArgs = toolArgs;
@@ -323,21 +352,20 @@ export async function prepareBrowserRun(options) {
323
352
  let electronLaunch;
324
353
  const sessionMode = compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto";
325
354
  const freshSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1);
326
- const rawManagedStateAccessError = getManagedSessionStateAccessValidationError({
355
+ const rawPageTargetError = getPageTargetValidationError({
327
356
  args: runtimeToolArgs,
328
- cwd,
329
357
  stdin: runtimeToolStdin,
330
358
  trustedFirstBatchTabSelection: true,
331
359
  });
332
- if (rawManagedStateAccessError) {
360
+ if (rawPageTargetError) {
333
361
  return {
334
362
  kind: "early-result",
335
363
  result: {
336
- content: [{ type: "text", text: rawManagedStateAccessError }],
364
+ content: [{ type: "text", text: rawPageTargetError }],
337
365
  details: {
338
366
  args: redactedArgs,
339
- ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, errorText: rawManagedStateAccessError, succeeded: false, validationError: rawManagedStateAccessError }),
340
- validationError: rawManagedStateAccessError,
367
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, errorText: rawPageTargetError, succeeded: false, validationError: rawPageTargetError }),
368
+ validationError: rawPageTargetError,
341
369
  },
342
370
  isError: true,
343
371
  },
@@ -388,30 +416,53 @@ export async function prepareBrowserRun(options) {
388
416
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: idleTimeoutMismatch };
389
417
  const ownedSessionKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
390
418
  const plannedSessionPageState = sessionPageState.get(ownedSessionKey);
391
- const managedStateAccessError = getManagedSessionStateAccessValidationError({
419
+ const pageTargetError = getPageTargetValidationError({
392
420
  args: executionPlan.effectiveArgs,
393
421
  currentPageUrl: plannedSessionPageState.tabTarget?.url,
394
422
  pageUrlUnknown: plannedSessionPageState.tabTargetUnknown === true,
395
- cwd,
396
423
  stdin: runtimeToolStdin,
397
424
  });
398
- if (!executionPlan.validationError && managedStateAccessError)
399
- executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: managedStateAccessError };
425
+ if (!executionPlan.validationError && pageTargetError)
426
+ executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: pageTargetError };
400
427
  const recordedOwnedSession = ownedSessionKey ? state.ownedManagedSessions.get(ownedSessionKey) : undefined;
401
428
  const targetsCurrentManagedSession = state.managedSessionActive
402
429
  && ownedSessionKey === getSessionContextKey(state.managedSessionName, state.managedSessionNamespace);
430
+ const targetsOffCurrentOwnedSession = recordedOwnedSession !== undefined && !targetsCurrentManagedSession;
431
+ const offCurrentLaunchScopedFlags = targetsOffCurrentOwnedSession
432
+ ? executionPlan.startupScopedFlags.filter((flag) => flag !== "--namespace")
433
+ : [];
434
+ const offCurrentCompatibilityUpgrade = targetsOffCurrentOwnedSession
435
+ && executionPlan.compatibilityWorkaround !== undefined
436
+ && recordedOwnedSession.compatibilityWorkaround === undefined;
437
+ if (targetsOffCurrentOwnedSession && canUseHeadlessCompatibilityUserAgent(preparedArgs.args, agentBrowserProcessEnv)) {
438
+ const compatibilityWorkaround = executionPlan.compatibilityWorkaround ?? recordedOwnedSession.compatibilityWorkaround;
439
+ if (compatibilityWorkaround) {
440
+ const userAgentIndex = executionPlan.effectiveArgs.indexOf("--user-agent");
441
+ executionPlan = {
442
+ ...executionPlan,
443
+ compatibilityWorkaround,
444
+ effectiveArgs: userAgentIndex < 0
445
+ ? executionPlan.effectiveArgs
446
+ : [...executionPlan.effectiveArgs.slice(0, userAgentIndex), ...executionPlan.effectiveArgs.slice(userAgentIndex + 2)],
447
+ };
448
+ }
449
+ }
403
450
  const retainedHeadedAutosaveDisabled = recordedOwnedSession?.headedManagedAutosaveDisabled === true
404
451
  || (targetsCurrentManagedSession && state.managedSessionHeadedAutosaveDisabled === true);
405
452
  const retainedHeadedAutosaveInterval = recordedOwnedSession?.headedManagedAutosaveInterval
406
453
  ?? (targetsCurrentManagedSession ? state.managedSessionHeadedAutosaveInterval : undefined);
407
- const explicitAutosaveInterval = resolveExplicitAutosaveInterval(process.env.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS);
454
+ const explicitAutosaveInterval = resolveExplicitAutosaveInterval(agentBrowserProcessEnv.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS);
408
455
  const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(retainedHeadedAutosaveInterval, isCloseCommand(executionPlan.commandInfo.command));
409
456
  if (!executionPlan.validationError && autosavePolicyChangeError) {
410
457
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: autosavePolicyChangeError };
411
458
  }
412
- const headedLaunch = getBooleanFlagValue(executionPlan.effectiveArgs, "--headed") ?? isUpstreamEnvFlagEnabled(process.env.AGENT_BROWSER_HEADED);
459
+ const headedLaunch = getBooleanFlagValue(executionPlan.effectiveArgs, "--headed") ?? isUpstreamEnvFlagEnabled(agentBrowserProcessEnv.AGENT_BROWSER_HEADED);
460
+ const providerLaunch = executionPlan.startupScopedFlags.some((flag) => flag === "--provider" || flag === "-p") || agentBrowserProcessEnv.AGENT_BROWSER_PROVIDER !== undefined;
413
461
  const headedManagedAutosaveDisabled = retainedHeadedAutosaveDisabled || (explicitAutosaveInterval === undefined && headedLaunch);
414
462
  const headedManagedAutosaveInterval = retainedHeadedAutosaveInterval ?? (headedLaunch ? explicitAutosaveInterval ?? "0" : undefined);
463
+ const compatibilityUserAgent = executionPlan.compatibilityWorkaround ? getDefaultHeadlessCompatUserAgent() : undefined;
464
+ const compatibilityUserAgentApplied = compatibilityUserAgent !== undefined
465
+ && executionPlan.effectiveArgs.some((token, index) => token === "--user-agent" && executionPlan.effectiveArgs[index + 1] === compatibilityUserAgent);
415
466
  const ownedManagedSession = buildOwnedManagedSessionRestoreContext({
416
467
  args: executionPlan.effectiveArgs,
417
468
  cwd: recordedOwnedSession?.cwd ?? cwd,
@@ -421,16 +472,14 @@ export async function prepareBrowserRun(options) {
421
472
  headedManagedAutosaveInterval,
422
473
  managedSessionName: executionPlan.managedSessionName,
423
474
  namespace: executionPlan.namespace,
475
+ parentEnv: agentBrowserProcessEnv,
424
476
  recordedOwnedSession,
425
477
  restoreState: state.managedSessionRestoreState,
426
478
  sessionName: executionPlan.sessionName,
427
479
  stdin: runtimeToolStdin,
428
- compatibilityUserAgent: executionPlan.compatibilityWorkaround ? getDefaultHeadlessCompatUserAgent() : undefined,
429
- wrapperInjectedUserAgent: executionPlan.compatibilityWorkaround !== undefined,
480
+ compatibilityUserAgent: compatibilityUserAgentApplied ? compatibilityUserAgent : undefined,
481
+ wrapperInjectedUserAgent: compatibilityUserAgentApplied,
430
482
  });
431
- const managedSessionTargetError = getManagedSessionTargetAccessValidationError(executionPlan.effectiveArgs, ownedManagedSession !== undefined);
432
- if (!executionPlan.validationError && managedSessionTargetError)
433
- executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: managedSessionTargetError };
434
483
  if (!executionPlan.validationError && ownedManagedSession) {
435
484
  const closeCommand = isCloseCommand(executionPlan.commandInfo.command);
436
485
  const policy = await acquireOwnedManagedSessionDaemonPolicy({
@@ -446,15 +495,25 @@ export async function prepareBrowserRun(options) {
446
495
  validationError: policy.error,
447
496
  };
448
497
  }
449
- else if (closeCommand && managedSessionPolicyLock) {
498
+ else if (!closeCommand && policy.daemonStatus === "active" && offCurrentLaunchScopedFlags.length > 0) {
450
499
  executionPlan = {
451
500
  ...executionPlan,
452
- effectiveArgs: canonicalizeOwnedManagedSessionCloseArgs({
453
- args: executionPlan.effectiveArgs,
454
- cwd,
455
- ownedManagedSession: true,
456
- restoreState: state.managedSessionRestoreState,
457
- }, true),
501
+ recoveryHint: undefined,
502
+ 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\".`,
503
+ };
504
+ }
505
+ else if (!closeCommand && policy.daemonStatus === "active" && offCurrentCompatibilityUpgrade) {
506
+ executionPlan = {
507
+ ...executionPlan,
508
+ recoveryHint: undefined,
509
+ 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\".",
510
+ };
511
+ }
512
+ else if (!closeCommand && policy.daemonStatus === "inactive" && compatibilityUserAgent && !compatibilityUserAgentApplied) {
513
+ ownedManagedSession.compatibilityUserAgent = compatibilityUserAgent;
514
+ executionPlan = {
515
+ ...executionPlan,
516
+ effectiveArgs: ["--user-agent", compatibilityUserAgent, ...executionPlan.effectiveArgs],
458
517
  };
459
518
  }
460
519
  }
@@ -500,10 +559,9 @@ export async function prepareBrowserRun(options) {
500
559
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: request.requirement };
501
560
  return;
502
561
  }
503
- const livePageValidationError = getManagedSessionStateAccessValidationError({
562
+ const livePageValidationError = getPageTargetValidationError({
504
563
  args: request.args,
505
564
  currentPageUrl: liveUrl,
506
- cwd,
507
565
  pageUrlUnknown: false,
508
566
  stdin: request.stdin,
509
567
  });
@@ -515,11 +573,12 @@ export async function prepareBrowserRun(options) {
515
573
  priorSessionTabTarget ??= { url: liveUrl };
516
574
  priorSessionTabTargetUnknown = undefined;
517
575
  };
518
- const mayResolveSemanticVisibleRef = executionPlan.managedSessionName !== freshSessionName && canResolveSemanticVisibleRef(compiledSemanticAction);
576
+ const hasPotentialLiveSemanticSession = state.managedSessionActive || priorSessionTabTarget !== undefined || isCallerOwnedExplicitSession() || options.preserveAttachedBrowserSession === true;
577
+ const mayResolveSemanticVisibleRef = executionPlan.managedSessionName !== freshSessionName && hasPotentialLiveSemanticSession && canResolveSemanticVisibleRef(compiledSemanticAction);
519
578
  if (!executionPlan.validationError && mayResolveSemanticVisibleRef && requiresLivePageVerification()) {
520
579
  await verifyLivePage({
521
580
  args: ["snapshot", "-i"],
522
- requirement: getCallerOwnedSessionLivePageVerificationRequirement({ args: ["snapshot", "-i"], cwd }),
581
+ requirement: getExplicitSessionPageVerificationRequirement({ args: ["snapshot", "-i"] }),
523
582
  });
524
583
  }
525
584
  if (!executionPlan.validationError && mayResolveSemanticVisibleRef) {
@@ -531,6 +590,17 @@ export async function prepareBrowserRun(options) {
531
590
  signal,
532
591
  });
533
592
  }
593
+ if (!executionPlan.validationError && requiresResolvedSemanticVisibleRef(compiledSemanticAction) && !semanticActionVisibleRefResolution) {
594
+ const freshLocatorError = executionPlan.managedSessionName === freshSessionName
595
+ ? "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."
596
+ : undefined;
597
+ executionPlan = {
598
+ ...executionPlan,
599
+ validationError: freshLocatorError ?? (hasPotentialLiveSemanticSession
600
+ ? "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."
601
+ : "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."),
602
+ };
603
+ }
534
604
  if (semanticActionVisibleRefResolution) {
535
605
  executionPlan = buildExecutionPlan(semanticActionVisibleRefResolution.args, {
536
606
  freshSessionName,
@@ -541,7 +611,14 @@ export async function prepareBrowserRun(options) {
541
611
  sessionMode,
542
612
  });
543
613
  }
544
- const commandTokens = semanticActionVisibleRefResolution ? extractCommandTokens(semanticActionVisibleRefResolution.args) : extractCommandTokens(preparedArgs.args);
614
+ const commandTokens = semanticActionVisibleRefResolution ? extractUpstreamCommandTokens(semanticActionVisibleRefResolution.args) : extractUpstreamCommandTokens(preparedArgs.args);
615
+ const unsupportedScrollIntoViewRecovery = executionPlan.validationError || executionPlan.plainTextInspection
616
+ ? undefined
617
+ : [commandTokens, ...getUpstreamEffectiveBatchSteps(commandTokens, runtimeToolStdin)]
618
+ .map((tokens) => buildUnsupportedScrollIntoViewRecovery({ commandTokens: tokens, sessionName: executionPlan.sessionName }))
619
+ .find((recovery) => recovery !== undefined);
620
+ if (unsupportedScrollIntoViewRecovery)
621
+ executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: unsupportedScrollIntoViewRecovery.error };
545
622
  const resolvedSemanticActionRefSnapshot = semanticActionVisibleRefResolution?.snapshot
546
623
  ? { ...semanticActionVisibleRefResolution.snapshot, target: semanticActionVisibleRefResolution.snapshot.target ?? priorSessionTabTarget }
547
624
  : undefined;
@@ -558,9 +635,8 @@ export async function prepareBrowserRun(options) {
558
635
  && requiresLivePageVerification();
559
636
  const livePageRequirement = livePageAccessEligible
560
637
  && !livePageVerified
561
- ? getCallerOwnedSessionLivePageVerificationRequirement({
638
+ ? getExplicitSessionPageVerificationRequirement({
562
639
  args: executionPlan.effectiveArgs,
563
- cwd,
564
640
  stdin: runtimeToolStdin,
565
641
  })
566
642
  : undefined;
@@ -579,8 +655,10 @@ export async function prepareBrowserRun(options) {
579
655
  freshSessionOrdinal += 1;
580
656
  }
581
657
  if (executionPlan.validationError) {
658
+ const nextActions = applyNamespaceToNextActions(unsupportedScrollIntoViewRecovery?.nextActions, executionPlan.namespace);
659
+ const nextActionsText = formatAgentBrowserNextActionsText(nextActions);
582
660
  return { kind: "early-result", statePatch, result: {
583
- content: [{ type: "text", text: executionPlan.validationError }],
661
+ content: [{ type: "text", text: [executionPlan.validationError, nextActionsText].filter((text) => text !== undefined).join("\n\n") }],
584
662
  details: {
585
663
  args: redactedArgs,
586
664
  compiledElectron: redactedCompiledElectron,
@@ -589,6 +667,7 @@ export async function prepareBrowserRun(options) {
589
667
  compiledSourceLookup: redactedCompiledSourceLookup,
590
668
  compiledNetworkSourceLookup: redactedCompiledNetworkSourceLookup,
591
669
  invalidValueFlag: executionPlan.invalidValueFlag,
670
+ nextActions,
592
671
  sessionMode,
593
672
  sessionRecoveryHint: redactedRecoveryHint,
594
673
  startupScopedFlags: executionPlan.startupScopedFlags,
@@ -696,6 +775,7 @@ export async function prepareBrowserRun(options) {
696
775
  cwd,
697
776
  currentTarget: priorSessionTabTarget,
698
777
  previousSnapshot: resolvedSemanticActionRefSnapshot ? undefined : priorRefSnapshotState,
778
+ stdin: runtimeToolStdin,
699
779
  namespace: executionPlan.namespace,
700
780
  sessionName: executionPlan.sessionName,
701
781
  signal,
@@ -921,7 +1001,11 @@ export async function prepareBrowserRun(options) {
921
1001
  }
922
1002
  if (pinnedBatchPlan) {
923
1003
  sessionTabCorrection = plannedSessionTabSelection;
924
- processArgs = ["--json", ...(executionPlan.namespace ? ["--namespace", executionPlan.namespace] : []), "--session", executionPlan.sessionName, "batch"];
1004
+ // The rewritten batch must keep the caller's fail-fast semantics:
1005
+ // upstream reads only the exact --bail token, so re-emit it whenever
1006
+ // the original batch tokens carried it (argv or stdin/job/qa mode).
1007
+ const pinnedBailArgs = commandTokens.includes("--bail") ? ["--bail"] : [];
1008
+ processArgs = ["--json", ...(executionPlan.namespace !== undefined ? ["--namespace", executionPlan.namespace] : []), "--session", executionPlan.sessionName, "batch", ...pinnedBailArgs];
925
1009
  processStdin = JSON.stringify(pinnedBatchPlan.steps);
926
1010
  includePinnedNavigationSummary = pinnedBatchPlan.includeNavigationSummary;
927
1011
  pinnedBatchUnwrapMode = pinnedBatchPlan.unwrapMode;
@@ -963,6 +1047,8 @@ export async function prepareBrowserRun(options) {
963
1047
  kind: "ready",
964
1048
  prepared: {
965
1049
  commandTokens,
1050
+ headedLaunch,
1051
+ providerLaunch,
966
1052
  managedSessionPolicyLock,
967
1053
  compiledElectron,
968
1054
  compiledJob,
@@ -990,7 +1076,9 @@ export async function prepareBrowserRun(options) {
990
1076
  redactedCompiledJob,
991
1077
  redactedCompiledNetworkSourceLookup,
992
1078
  redactedCompiledQaPreset,
993
- redactedCompiledSemanticAction,
1079
+ redactedCompiledSemanticAction: semanticActionVisibleRefResolution && redactedCompiledSemanticAction?.action === "select"
1080
+ ? { ...redactedCompiledSemanticAction, args: redactInvocationArgs(semanticActionVisibleRefResolution.args) }
1081
+ : redactedCompiledSemanticAction,
994
1082
  redactedCompiledSourceLookup,
995
1083
  redactedEffectiveArgs,
996
1084
  redactedProcessArgs,