pi-agent-browser-native 0.5.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 (64) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +75 -42
  3. package/dist/extensions/agent-browser/index.js +13 -83
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +8 -2
  5. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +1 -1
  6. package/dist/extensions/agent-browser/lib/command-policy.js +4 -7
  7. package/dist/extensions/agent-browser/lib/command-taxonomy.js +19 -11
  8. package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
  9. package/dist/extensions/agent-browser/lib/config.js +1 -1
  10. package/dist/extensions/agent-browser/lib/input-modes/job.js +0 -9
  11. package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
  12. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +18 -4
  13. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -138
  14. package/dist/extensions/agent-browser/lib/managed-session-restore.js +1 -81
  15. package/dist/extensions/agent-browser/lib/managed-session-storage.js +4 -1
  16. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +31 -26
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +60 -8
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +0 -3
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +31 -40
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +76 -70
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +6 -8
  24. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +5 -10
  25. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
  26. package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
  27. package/dist/extensions/agent-browser/lib/playbook.js +13 -12
  28. package/dist/extensions/agent-browser/lib/process-identity.js +1 -8
  29. package/dist/extensions/agent-browser/lib/process.js +18 -82
  30. package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -78
  31. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +1 -1
  32. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +27 -14
  33. package/dist/extensions/agent-browser/lib/results/presentation/common.js +20 -2
  34. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +11 -16
  35. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +12 -9
  36. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +2 -2
  37. package/dist/extensions/agent-browser/lib/results/presentation.js +31 -4
  38. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +1 -1
  39. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
  40. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +3 -3
  41. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
  42. package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
  43. package/dist/extensions/agent-browser/lib/runtime.js +73 -72
  44. package/dist/extensions/agent-browser/lib/session-page-state.js +12 -3
  45. package/dist/extensions/agent-browser/lib/temp.js +1 -2
  46. package/dist/extensions/agent-browser/lib/upstream-version.js +5 -5
  47. package/dist/extensions/agent-browser/lib/web-search.js +108 -24
  48. package/dist/scripts/agent-browser-target.mjs +19 -1
  49. package/docs/ARCHITECTURE.md +24 -20
  50. package/docs/COMMAND_REFERENCE.md +181 -49
  51. package/docs/ELECTRON.md +2 -2
  52. package/docs/RELEASE.md +10 -8
  53. package/docs/REQUIREMENTS.md +8 -7
  54. package/docs/SUPPORT_MATRIX.md +31 -26
  55. package/docs/TOOL_CONTRACT.md +89 -56
  56. package/package.json +1 -1
  57. package/scripts/agent-browser-capability-baseline.mjs +65 -5
  58. package/scripts/agent-browser-target.mjs +19 -1
  59. package/scripts/config.mjs +1 -0
  60. package/scripts/doctor.mjs +15 -9
  61. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
  62. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -601
  63. package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
  64. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
@@ -2,6 +2,7 @@ import { isRecord } from "../../../parsing.js";
2
2
  import { buildAgentBrowserResultCategoryDetails } from "../../../results/categories.js";
3
3
  import { buildSnapshotPresentation } from "../../../results/snapshot.js";
4
4
  import { extractRefSnapshotFromData } from "../../../session-page-state.js";
5
+ import { redactSensitiveText } from "../../../runtime.js";
5
6
  import { collectScrollPositionSnapshot } from "../diagnostics.js";
6
7
  import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
7
8
  function parseSnapshotFilterRequest(commandTokens) {
@@ -42,6 +43,116 @@ function parseSnapshotFilterRequest(commandTokens) {
42
43
  return undefined;
43
44
  return { cleanArgs, diff, role, search, viewport };
44
45
  }
46
+ const RENDERED_TEXT_SEARCH_MAX_MATCHES = 8;
47
+ function buildRenderedTextSearchEval(search) {
48
+ return `(() => {
49
+ const query = ${JSON.stringify(search.trim().toLowerCase())};
50
+ const limit = ${RENDERED_TEXT_SEARCH_MAX_MATCHES};
51
+ const normalize = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();
52
+ const attributeTextOf = (element) => [element.getAttribute("aria-label"), element.getAttribute("title"), element.getAttribute("placeholder")].filter(Boolean).join(" ");
53
+ const searchableTextOf = (element) => normalize([attributeTextOf(element), element.textContent].filter(Boolean).join(" "));
54
+ const renderedTextOf = (element) => normalize([attributeTextOf(element), element instanceof HTMLElement ? element.innerText : element.textContent].filter(Boolean).join(" "));
55
+ const isRendered = (element) => {
56
+ if (!(element instanceof Element) || element.getClientRects().length === 0) return false;
57
+ const visibility = getComputedStyle(element).visibility;
58
+ if (visibility === "hidden" || visibility === "collapse") return false;
59
+ for (let current = element; current; current = current.parentElement) {
60
+ if (Number(getComputedStyle(current).opacity) === 0) return false;
61
+ }
62
+ return true;
63
+ };
64
+ const validationPattern = /(?:^|[-_\\s])(?:error|invalid|validation|warning)(?:$|[-_\\s])/i;
65
+ const validationAncestor = (element) => {
66
+ for (let current = element; current && current !== document.body; current = current.parentElement) {
67
+ const role = current.getAttribute("role");
68
+ const live = current.getAttribute("aria-live");
69
+ const descriptor = [current.id, current.className, current.getAttribute("data-testid")].map(normalize).join(" ");
70
+ if (["alert", "alertdialog", "status"].includes(role || "") || current.getAttribute("aria-invalid") === "true" || (live && live !== "off") || validationPattern.test(descriptor)) return current;
71
+ }
72
+ return null;
73
+ };
74
+ const candidates = Array.from(document.querySelectorAll("body *")).filter((element) => searchableTextOf(element).toLowerCase().includes(query));
75
+ const candidateSet = new Set(candidates);
76
+ const minimal = candidates.filter((element) => !Array.from(element.children).some((child) => candidateSet.has(child)));
77
+ const rendered = minimal.filter((element) => isRendered(element) && renderedTextOf(element).toLowerCase().includes(query));
78
+ const matches = rendered.map((element, index) => {
79
+ const semantic = validationAncestor(element);
80
+ const source = semantic || element;
81
+ const text = renderedTextOf(element);
82
+ const matchIndex = text.toLowerCase().indexOf(query);
83
+ const start = Math.max(0, matchIndex - 80);
84
+ const snippet = text.slice(start, start + 240);
85
+ const rect = element.getBoundingClientRect();
86
+ const name = normalize(element.getAttribute("aria-label") || element.getAttribute("title") || element.getAttribute("placeholder")) || undefined;
87
+ return {
88
+ index,
89
+ kind: semantic ? "validation" : "text",
90
+ name,
91
+ offscreen: rect.bottom < 0 || rect.right < 0 || rect.top > innerHeight || rect.left > innerWidth,
92
+ role: source.getAttribute("role") || undefined,
93
+ tagName: element.tagName.toLowerCase(),
94
+ text: snippet,
95
+ };
96
+ }).sort((left, right) => (left.kind === right.kind ? left.index - right.index : left.kind === "validation" ? -1 : 1));
97
+ const unique = [];
98
+ const seen = new Set();
99
+ for (const match of matches) {
100
+ const key = [match.kind, match.role, match.name, match.text].join("\\n");
101
+ if (seen.has(key)) continue;
102
+ seen.add(key);
103
+ const { index, ...visible } = match;
104
+ unique.push(visible);
105
+ }
106
+ return { matches: unique.slice(0, limit), totalMatches: unique.length, truncated: unique.length > limit };
107
+ })()`;
108
+ }
109
+ function extractRenderedTextSearchResult(data) {
110
+ const result = isRecord(data) && isRecord(data.result) ? data.result : data;
111
+ if (!isRecord(result) || !Array.isArray(result.matches))
112
+ return undefined;
113
+ const matches = result.matches.flatMap((value) => {
114
+ if (!isRecord(value) || (value.kind !== "text" && value.kind !== "validation") || typeof value.offscreen !== "boolean" || typeof value.tagName !== "string" || typeof value.text !== "string")
115
+ return [];
116
+ return [{
117
+ kind: value.kind,
118
+ ...(typeof value.name === "string" ? { name: redactSensitiveText(value.name) } : {}),
119
+ offscreen: value.offscreen,
120
+ ...(typeof value.role === "string" ? { role: value.role } : {}),
121
+ tagName: value.tagName,
122
+ text: redactSensitiveText(value.text),
123
+ }];
124
+ }).slice(0, RENDERED_TEXT_SEARCH_MAX_MATCHES);
125
+ const totalMatches = typeof result.totalMatches === "number" && Number.isInteger(result.totalMatches) && result.totalMatches >= matches.length ? result.totalMatches : matches.length;
126
+ return { matches, totalMatches, truncated: result.truncated === true || totalMatches > matches.length };
127
+ }
128
+ function attachRenderedTextMatchRefs(matches, snapshotData) {
129
+ if (!isRecord(snapshotData) || !isRecord(snapshotData.refs))
130
+ return matches;
131
+ const refs = snapshotData.refs;
132
+ return matches.map((match) => {
133
+ if (!match.name)
134
+ return match;
135
+ const normalizedName = match.name.replace(/\s+/g, " ").trim().toLowerCase();
136
+ const candidates = Object.entries(refs).filter(([, value]) => {
137
+ if (!isRecord(value) || typeof value.name !== "string")
138
+ return false;
139
+ const nameMatches = value.name.replace(/\s+/g, " ").trim().toLowerCase() === normalizedName;
140
+ return nameMatches && (!match.role || typeof value.role !== "string" || value.role.toLowerCase() === match.role.toLowerCase());
141
+ });
142
+ return candidates.length === 1 ? { ...match, ref: candidates[0][0] } : match;
143
+ });
144
+ }
145
+ function formatRenderedTextSearchMatches(result) {
146
+ if (!result || result.matches.length === 0)
147
+ return undefined;
148
+ const lines = result.matches.map((match) => {
149
+ const context = [match.kind === "validation" ? "validation" : undefined, match.offscreen ? "outside viewport" : undefined, match.role, match.tagName, match.ref ? `@${match.ref}` : undefined].filter(Boolean).join(", ");
150
+ return `- ${JSON.stringify(match.text)}${context ? ` (${context})` : ""}`;
151
+ });
152
+ if (result.truncated)
153
+ lines.push(`- ... (${result.totalMatches - result.matches.length} additional rendered-text matches omitted)`);
154
+ return ["Rendered page text matches:", ...lines].join("\n");
155
+ }
45
156
  function buildSnapshotDiff(previous, current) {
46
157
  if (!current)
47
158
  return undefined;
@@ -113,6 +224,11 @@ export async function trySnapshotFilter(options) {
113
224
  const filtered = request.role || request.search ? filterSnapshotData(snapshotData, request) : isRecord(snapshotData) ? { data: snapshotData, matchedRefs: isRecord(snapshotData.refs) ? Object.keys(snapshotData.refs).length : 0, totalLines: typeof snapshotData.snapshot === "string" ? snapshotData.snapshot.split(/\r?\n/).filter((line) => line.length > 0).length : 0, totalRefs: isRecord(snapshotData.refs) ? Object.keys(snapshotData.refs).length : 0, visibleLines: typeof snapshotData.snapshot === "string" ? snapshotData.snapshot.split(/\r?\n/).filter((line) => line.length > 0).length : 0 } : undefined;
114
225
  if (!filtered)
115
226
  return undefined;
227
+ const renderedTextSearch = request.search
228
+ ? extractRenderedTextSearchResult(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildRenderedTextSearchEval(request.search) }))
229
+ : undefined;
230
+ if (renderedTextSearch)
231
+ renderedTextSearch.matches = attachRenderedTextMatchRefs(renderedTextSearch.matches, snapshotData);
116
232
  const viewport = request.viewport ? await collectScrollPositionSnapshot({ cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }) : undefined;
117
233
  const fullSnapshot = extractRefSnapshotFromData(snapshotData);
118
234
  const diff = request.diff ? buildSnapshotDiff(options.previousRefSnapshot, fullSnapshot) : undefined;
@@ -126,7 +242,8 @@ export async function trySnapshotFilter(options) {
126
242
  : "Snapshot viewport metadata collected.";
127
243
  const viewportText = viewport ? `Viewport: ${viewport.innerWidth}×${viewport.innerHeight}, scroll ${viewport.scrollX},${viewport.scrollY}, document ${viewport.scrollWidth}×${viewport.scrollHeight}, sampled scroll containers ${viewport.containers.length}/${viewport.containerCount}.` : undefined;
128
244
  const diffText = diff && (request.role || request.search) ? diff.summary : undefined;
129
- const prefix = [summary, diffText, viewportText].filter((line) => line !== undefined).join("\n");
245
+ const renderedTextSearchText = formatRenderedTextSearchMatches(renderedTextSearch);
246
+ const prefix = [summary, renderedTextSearchText, diffText, viewportText].filter((line) => line !== undefined).join("\n\n");
130
247
  if (presentation.content[0]?.type === "text")
131
248
  presentation.content[0] = { ...presentation.content[0], text: `${prefix}\n\n${presentation.content[0].text}` };
132
249
  return {
@@ -146,7 +263,7 @@ export async function trySnapshotFilter(options) {
146
263
  refSnapshot: fullSnapshot,
147
264
  sessionMode: options.sessionMode,
148
265
  snapshotDiff: diff,
149
- snapshotFilter: request.role || request.search ? { cleanArgs: request.cleanArgs, matchedRefs: filtered.matchedRefs, role: request.role, search: request.search, totalLines: filtered.totalLines, totalRefs: filtered.totalRefs, visibleLines: filtered.visibleLines } : undefined,
266
+ snapshotFilter: request.role || request.search ? { cleanArgs: request.cleanArgs, matchedRefs: filtered.matchedRefs, renderedTextMatches: renderedTextSearch?.matches, renderedTextTotalMatches: renderedTextSearch?.totalMatches, renderedTextTruncated: renderedTextSearch?.truncated, role: request.role, search: request.search, totalLines: filtered.totalLines, totalRefs: filtered.totalRefs, visibleLines: filtered.visibleLines } : undefined,
150
267
  snapshotViewport: viewport,
151
268
  ...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: "snapshot", succeeded: true }),
152
269
  ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession, options.namespace, options.managedSessionRestoreDisabled()),
@@ -1,7 +1,7 @@
1
1
  import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS } from "../../../argv-grammar.js";
2
2
  import { isOpenNavigationCommand } from "../../../command-taxonomy.js";
3
3
  import { getAgentBrowserProcessTimeoutMs } from "../../../process.js";
4
- import { parseValidBatchStepEntries } from "../../batch-stdin.js";
4
+ import { getUpstreamEffectiveBatchSteps } from "../../batch-stdin.js";
5
5
  const POSITIONAL_VALUE_FLAGS = new Set([...VALUE_FLAGS, "--llms"]);
6
6
  const COMMAND_PROCESS_TIMEOUT_GRACE_MS = 5_000;
7
7
  function parseMillisecondsToken(token) {
@@ -12,7 +12,8 @@ function parseMillisecondsToken(token) {
12
12
  return Number.isSafeInteger(parsed) ? parsed : undefined;
13
13
  }
14
14
  function findCommandTimeoutMs(commandTokens) {
15
- if (commandTokens[0] !== "wait" && commandTokens[0] !== "read")
15
+ const [command, subcommand] = commandTokens;
16
+ if (command !== "wait" && command !== "read" && !(command === "webmcp" && ["invoke", "result"].includes(subcommand ?? "")))
16
17
  return undefined;
17
18
  for (let index = 1; index < commandTokens.length; index += 1) {
18
19
  const token = commandTokens[index];
@@ -73,11 +74,11 @@ function findCommandTimeoutBudgetMs(commandTokens, stdin, activePageUrl) {
73
74
  const directTimeout = commandTimeoutBudgetMs(commandTokens, activePageUrl);
74
75
  if (directTimeout !== undefined)
75
76
  return directTimeout;
76
- if (commandTokens[0] !== "batch" || stdin === undefined)
77
+ if (commandTokens[0] !== "batch")
77
78
  return undefined;
78
79
  let batchTimeoutTotal = 0;
79
80
  let batchPageUrl = activePageUrl;
80
- for (const { step } of parseValidBatchStepEntries(stdin)) {
81
+ for (const step of getUpstreamEffectiveBatchSteps(commandTokens, stdin)) {
81
82
  batchTimeoutTotal += commandTimeoutBudgetMs(step, batchPageUrl) ?? 0;
82
83
  if (isOpenNavigationCommand(step[0]))
83
84
  batchPageUrl = findFirstPositionalArgument(step);
@@ -87,10 +88,10 @@ function findCommandTimeoutBudgetMs(commandTokens, stdin, activePageUrl) {
87
88
  export function commandTimeoutNeedsActivePageUrl(commandTokens, stdin) {
88
89
  if (commandTokens[0] === "read")
89
90
  return findCommandTimeoutMs(commandTokens) !== undefined && readUsesActivePageUrl(commandTokens);
90
- if (commandTokens[0] !== "batch" || stdin === undefined)
91
+ if (commandTokens[0] !== "batch")
91
92
  return false;
92
93
  let hasKnownPageUrl = false;
93
- for (const { step } of parseValidBatchStepEntries(stdin)) {
94
+ for (const step of getUpstreamEffectiveBatchSteps(commandTokens, stdin)) {
94
95
  if (isOpenNavigationCommand(step[0]) && findFirstPositionalArgument(step))
95
96
  hasKnownPageUrl = true;
96
97
  if (!hasKnownPageUrl && findCommandTimeoutMs(step) !== undefined && step[0] === "read" && readUsesActivePageUrl(step))
@@ -18,15 +18,15 @@ import { buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextAction
18
18
  import { resolveVisibleRefActionFromSnapshot } from "../../results/selector-recovery.js";
19
19
  import { extractRefSnapshotFromData } from "../../session-page-state.js";
20
20
  import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createFreshSessionName, extractCommandTokens, extractUpstreamCommandTokens, getDefaultHeadlessCompatUserAgent, parseWaitCommandTokens, redactInvocationArgs, } from "../../runtime.js";
21
- import { buildOwnedManagedSessionRestoreContext, canonicalizeOwnedManagedSessionCloseArgs, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
21
+ import { buildOwnedManagedSessionRestoreContext, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
22
22
  import { getAgentBrowserProcessEnvironment } from "../../process-environment.js";
23
- import { getCallerOwnedSessionLivePageVerificationRequirement, getManagedSessionStateAccessValidationError, getManagedSessionTargetAccessValidationError, } from "../../managed-session-state-policy.js";
23
+ import { getExplicitSessionPageVerificationRequirement, getPageTargetValidationError, } from "../../page-target-validation.js";
24
24
  import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError } from "./managed-session-daemon-policy.js";
25
25
  import { applyOpenResultTabCorrection, buildManagedSessionOutcome, buildPinnedBatchPlan, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, extractStringResultField, collectAnySessionTabSelection, collectSessionTabSelection, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
26
26
  import { getUpstreamEffectiveBatchSteps, parseBatchStdinJsonArray } from "../batch-stdin.js";
27
- import { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
27
+ import { buildElectronHostFailureResult, formatAgentBrowserNextActionsText, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
28
28
  import { prepareClickDispatchProbe } from "./click-dispatch.js";
29
- import { collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
29
+ import { buildUnsupportedScrollIntoViewRecovery, collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
30
30
  import { getScreenshotPathTokenIndex } from "./artifact-paths.js";
31
31
  import { findRequestedArtifactCloseViolation } from "./prompt-guards.js";
32
32
  export function normalizeRunInput(input) {
@@ -283,10 +283,9 @@ async function collectSamePageRefFreshnessPreflight(options) {
283
283
  }
284
284
  function getIdleTimeoutMismatch(args, configuredValue) {
285
285
  for (let index = 0; index < args.length; index += 1) {
286
- const token = args[index];
287
- if (token !== "--idle-timeout" && !token.startsWith("--idle-timeout="))
286
+ if (args[index] !== "--idle-timeout")
288
287
  continue;
289
- const requestedToken = token.includes("=") ? token.slice(token.indexOf("=") + 1) : args[++index];
288
+ const requestedToken = args[++index];
290
289
  if (!requestedToken || !/^\d+$/.test(requestedToken) || Number(requestedToken) === Number(configuredValue))
291
290
  continue;
292
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.`;
@@ -353,22 +352,20 @@ export async function prepareBrowserRun(options) {
353
352
  let electronLaunch;
354
353
  const sessionMode = compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto";
355
354
  const freshSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1);
356
- const rawManagedStateAccessError = getManagedSessionStateAccessValidationError({
355
+ const rawPageTargetError = getPageTargetValidationError({
357
356
  args: runtimeToolArgs,
358
- cwd,
359
- parentEnv: agentBrowserProcessEnv,
360
357
  stdin: runtimeToolStdin,
361
358
  trustedFirstBatchTabSelection: true,
362
359
  });
363
- if (rawManagedStateAccessError) {
360
+ if (rawPageTargetError) {
364
361
  return {
365
362
  kind: "early-result",
366
363
  result: {
367
- content: [{ type: "text", text: rawManagedStateAccessError }],
364
+ content: [{ type: "text", text: rawPageTargetError }],
368
365
  details: {
369
366
  args: redactedArgs,
370
- ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, errorText: rawManagedStateAccessError, succeeded: false, validationError: rawManagedStateAccessError }),
371
- validationError: rawManagedStateAccessError,
367
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, errorText: rawPageTargetError, succeeded: false, validationError: rawPageTargetError }),
368
+ validationError: rawPageTargetError,
372
369
  },
373
370
  isError: true,
374
371
  },
@@ -419,16 +416,14 @@ export async function prepareBrowserRun(options) {
419
416
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: idleTimeoutMismatch };
420
417
  const ownedSessionKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
421
418
  const plannedSessionPageState = sessionPageState.get(ownedSessionKey);
422
- const managedStateAccessError = getManagedSessionStateAccessValidationError({
419
+ const pageTargetError = getPageTargetValidationError({
423
420
  args: executionPlan.effectiveArgs,
424
421
  currentPageUrl: plannedSessionPageState.tabTarget?.url,
425
422
  pageUrlUnknown: plannedSessionPageState.tabTargetUnknown === true,
426
- cwd,
427
- parentEnv: agentBrowserProcessEnv,
428
423
  stdin: runtimeToolStdin,
429
424
  });
430
- if (!executionPlan.validationError && managedStateAccessError)
431
- executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: managedStateAccessError };
425
+ if (!executionPlan.validationError && pageTargetError)
426
+ executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: pageTargetError };
432
427
  const recordedOwnedSession = ownedSessionKey ? state.ownedManagedSessions.get(ownedSessionKey) : undefined;
433
428
  const targetsCurrentManagedSession = state.managedSessionActive
434
429
  && ownedSessionKey === getSessionContextKey(state.managedSessionName, state.managedSessionNamespace);
@@ -462,6 +457,7 @@ export async function prepareBrowserRun(options) {
462
457
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: autosavePolicyChangeError };
463
458
  }
464
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;
465
461
  const headedManagedAutosaveDisabled = retainedHeadedAutosaveDisabled || (explicitAutosaveInterval === undefined && headedLaunch);
466
462
  const headedManagedAutosaveInterval = retainedHeadedAutosaveInterval ?? (headedLaunch ? explicitAutosaveInterval ?? "0" : undefined);
467
463
  const compatibilityUserAgent = executionPlan.compatibilityWorkaround ? getDefaultHeadlessCompatUserAgent() : undefined;
@@ -484,9 +480,6 @@ export async function prepareBrowserRun(options) {
484
480
  compatibilityUserAgent: compatibilityUserAgentApplied ? compatibilityUserAgent : undefined,
485
481
  wrapperInjectedUserAgent: compatibilityUserAgentApplied,
486
482
  });
487
- const managedSessionTargetError = getManagedSessionTargetAccessValidationError(executionPlan.effectiveArgs, ownedManagedSession !== undefined, agentBrowserProcessEnv);
488
- if (!executionPlan.validationError && managedSessionTargetError)
489
- executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: managedSessionTargetError };
490
483
  if (!executionPlan.validationError && ownedManagedSession) {
491
484
  const closeCommand = isCloseCommand(executionPlan.commandInfo.command);
492
485
  const policy = await acquireOwnedManagedSessionDaemonPolicy({
@@ -523,17 +516,6 @@ export async function prepareBrowserRun(options) {
523
516
  effectiveArgs: ["--user-agent", compatibilityUserAgent, ...executionPlan.effectiveArgs],
524
517
  };
525
518
  }
526
- else if (closeCommand && managedSessionPolicyLock) {
527
- executionPlan = {
528
- ...executionPlan,
529
- effectiveArgs: canonicalizeOwnedManagedSessionCloseArgs({
530
- args: executionPlan.effectiveArgs,
531
- cwd,
532
- ownedManagedSession: true,
533
- restoreState: state.managedSessionRestoreState,
534
- }, true),
535
- };
536
- }
537
519
  }
538
520
  return await withOwnedManagedSessionContext(ownedManagedSession, async () => {
539
521
  const managedSessionRestoreDisabled = () => state.managedSessionRestoreState.isDisabled(executionPlan.sessionName, executionPlan.namespace);
@@ -577,12 +559,10 @@ export async function prepareBrowserRun(options) {
577
559
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: request.requirement };
578
560
  return;
579
561
  }
580
- const livePageValidationError = getManagedSessionStateAccessValidationError({
562
+ const livePageValidationError = getPageTargetValidationError({
581
563
  args: request.args,
582
564
  currentPageUrl: liveUrl,
583
- cwd,
584
565
  pageUrlUnknown: false,
585
- parentEnv: agentBrowserProcessEnv,
586
566
  stdin: request.stdin,
587
567
  });
588
568
  if (livePageValidationError) {
@@ -598,7 +578,7 @@ export async function prepareBrowserRun(options) {
598
578
  if (!executionPlan.validationError && mayResolveSemanticVisibleRef && requiresLivePageVerification()) {
599
579
  await verifyLivePage({
600
580
  args: ["snapshot", "-i"],
601
- requirement: getCallerOwnedSessionLivePageVerificationRequirement({ args: ["snapshot", "-i"], cwd }),
581
+ requirement: getExplicitSessionPageVerificationRequirement({ args: ["snapshot", "-i"] }),
602
582
  });
603
583
  }
604
584
  if (!executionPlan.validationError && mayResolveSemanticVisibleRef) {
@@ -632,6 +612,13 @@ export async function prepareBrowserRun(options) {
632
612
  });
633
613
  }
634
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 };
635
622
  const resolvedSemanticActionRefSnapshot = semanticActionVisibleRefResolution?.snapshot
636
623
  ? { ...semanticActionVisibleRefResolution.snapshot, target: semanticActionVisibleRefResolution.snapshot.target ?? priorSessionTabTarget }
637
624
  : undefined;
@@ -648,9 +635,8 @@ export async function prepareBrowserRun(options) {
648
635
  && requiresLivePageVerification();
649
636
  const livePageRequirement = livePageAccessEligible
650
637
  && !livePageVerified
651
- ? getCallerOwnedSessionLivePageVerificationRequirement({
638
+ ? getExplicitSessionPageVerificationRequirement({
652
639
  args: executionPlan.effectiveArgs,
653
- cwd,
654
640
  stdin: runtimeToolStdin,
655
641
  })
656
642
  : undefined;
@@ -669,8 +655,10 @@ export async function prepareBrowserRun(options) {
669
655
  freshSessionOrdinal += 1;
670
656
  }
671
657
  if (executionPlan.validationError) {
658
+ const nextActions = applyNamespaceToNextActions(unsupportedScrollIntoViewRecovery?.nextActions, executionPlan.namespace);
659
+ const nextActionsText = formatAgentBrowserNextActionsText(nextActions);
672
660
  return { kind: "early-result", statePatch, result: {
673
- content: [{ type: "text", text: executionPlan.validationError }],
661
+ content: [{ type: "text", text: [executionPlan.validationError, nextActionsText].filter((text) => text !== undefined).join("\n\n") }],
674
662
  details: {
675
663
  args: redactedArgs,
676
664
  compiledElectron: redactedCompiledElectron,
@@ -679,6 +667,7 @@ export async function prepareBrowserRun(options) {
679
667
  compiledSourceLookup: redactedCompiledSourceLookup,
680
668
  compiledNetworkSourceLookup: redactedCompiledNetworkSourceLookup,
681
669
  invalidValueFlag: executionPlan.invalidValueFlag,
670
+ nextActions,
682
671
  sessionMode,
683
672
  sessionRecoveryHint: redactedRecoveryHint,
684
673
  startupScopedFlags: executionPlan.startupScopedFlags,
@@ -1058,6 +1047,8 @@ export async function prepareBrowserRun(options) {
1058
1047
  kind: "ready",
1059
1048
  prepared: {
1060
1049
  commandTokens,
1050
+ headedLaunch,
1051
+ providerLaunch,
1061
1052
  managedSessionPolicyLock,
1062
1053
  compiledElectron,
1063
1054
  compiledJob,