pi-agent-browser-native 0.2.32 → 0.2.33

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 (62) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +27 -16
  3. package/docs/ARCHITECTURE.md +3 -2
  4. package/docs/COMMAND_REFERENCE.md +18 -10
  5. package/docs/ELECTRON.md +23 -4
  6. package/docs/RELEASE.md +4 -2
  7. package/docs/REQUIREMENTS.md +1 -1
  8. package/docs/SUPPORT_MATRIX.md +28 -16
  9. package/docs/TOOL_CONTRACT.md +29 -24
  10. package/extensions/agent-browser/index.ts +404 -4371
  11. package/extensions/agent-browser/lib/input-modes/electron.ts +170 -0
  12. package/extensions/agent-browser/lib/input-modes/job.ts +203 -0
  13. package/extensions/agent-browser/lib/input-modes/lookups.ts +447 -0
  14. package/extensions/agent-browser/lib/input-modes/params.ts +188 -0
  15. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +107 -0
  16. package/extensions/agent-browser/lib/input-modes/shared.ts +46 -0
  17. package/extensions/agent-browser/lib/input-modes/types.ts +221 -0
  18. package/extensions/agent-browser/lib/input-modes.ts +41 -0
  19. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +696 -0
  20. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +450 -0
  21. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +46 -0
  22. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +711 -0
  23. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +386 -0
  24. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +868 -0
  25. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +476 -0
  26. package/extensions/agent-browser/lib/orchestration/browser-run.ts +1 -0
  27. package/extensions/agent-browser/lib/orchestration/input-plan.ts +338 -0
  28. package/extensions/agent-browser/lib/playbook.ts +12 -11
  29. package/extensions/agent-browser/lib/process.ts +106 -4
  30. package/extensions/agent-browser/lib/results/action-recommendations.ts +269 -0
  31. package/extensions/agent-browser/lib/results/artifact-manifest.ts +114 -0
  32. package/extensions/agent-browser/lib/results/artifact-state.ts +13 -0
  33. package/extensions/agent-browser/lib/results/categories.ts +106 -0
  34. package/extensions/agent-browser/lib/results/contracts.ts +220 -0
  35. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +72 -0
  36. package/extensions/agent-browser/lib/results/envelope.ts +2 -1
  37. package/extensions/agent-browser/lib/results/network.ts +64 -0
  38. package/extensions/agent-browser/lib/results/next-actions.ts +117 -0
  39. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +506 -0
  40. package/extensions/agent-browser/lib/results/presentation/batch.ts +355 -0
  41. package/extensions/agent-browser/lib/results/presentation/common.ts +53 -0
  42. package/extensions/agent-browser/lib/results/presentation/content.ts +36 -0
  43. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +730 -0
  44. package/extensions/agent-browser/lib/results/presentation/errors.ts +125 -0
  45. package/extensions/agent-browser/lib/results/presentation/large-output.ts +182 -0
  46. package/extensions/agent-browser/lib/results/presentation/navigation.ts +216 -0
  47. package/extensions/agent-browser/lib/results/presentation/registry.ts +154 -0
  48. package/extensions/agent-browser/lib/results/presentation/skills.ts +143 -0
  49. package/extensions/agent-browser/lib/results/presentation.ts +87 -2399
  50. package/extensions/agent-browser/lib/results/recovery-actions.ts +139 -0
  51. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +71 -0
  52. package/extensions/agent-browser/lib/results/selector-recovery.ts +312 -0
  53. package/extensions/agent-browser/lib/results/shared.ts +17 -789
  54. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +262 -0
  55. package/extensions/agent-browser/lib/results/snapshot-refs.ts +100 -0
  56. package/extensions/agent-browser/lib/results/snapshot-segments.ts +366 -0
  57. package/extensions/agent-browser/lib/results/snapshot-spill.ts +63 -0
  58. package/extensions/agent-browser/lib/results/snapshot.ts +37 -489
  59. package/extensions/agent-browser/lib/results/text.ts +40 -0
  60. package/extensions/agent-browser/lib/results.ts +16 -5
  61. package/extensions/agent-browser/lib/session-page-state.ts +486 -0
  62. package/package.json +2 -1
@@ -0,0 +1,711 @@
1
+ import { copyFile, mkdir, stat } from "node:fs/promises";
2
+ import { dirname, extname, isAbsolute, resolve } from "node:path";
3
+
4
+ import { launchElectronApp, type ElectronLaunchSuccess } from "../../electron/launch.js";
5
+ import { getCompiledSemanticActionSessionPrefix, type CompiledAgentBrowserSemanticAction } from "../../input-modes.js";
6
+ import { SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS } from "../../process.js";
7
+ import { buildAgentBrowserResultCategoryDetails } from "../../results.js";
8
+ import { buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions } from "../../results/recovery-next-actions.js";
9
+ import { resolveVisibleRefActionFromSnapshot } from "../../results/selector-recovery.js";
10
+ import { type SessionRefSnapshot } from "../../session-page-state.js";
11
+ import {
12
+ buildExecutionPlan,
13
+ createFreshSessionName,
14
+ extractCommandTokens,
15
+ redactInvocationArgs,
16
+ type CompatibilityWorkaround,
17
+ } from "../../runtime.js";
18
+ import {
19
+ applyOpenResultTabCorrection,
20
+ buildManagedSessionOutcome,
21
+ buildPinnedBatchPlan,
22
+ buildSessionDetailFields,
23
+ buildStaleRefPreflight,
24
+ collectSessionTabSelection,
25
+ getTraceOwnerGuardMessage,
26
+ runSessionCommandData,
27
+ shouldPinSessionTabForCommand,
28
+ } from "./session-state.js";
29
+ import { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
30
+ import { collectScrollPositionSnapshot } from "./diagnostics.js";
31
+ import type {
32
+ BrowserRunInputFields,
33
+ BrowserRunOptions,
34
+ PreparedAgentBrowserArgs,
35
+ PreparedBrowserRun,
36
+ PrepareBrowserRunResult,
37
+ ScreenshotArtifactRequest,
38
+ ScreenshotPathRequest,
39
+ SemanticActionVisibleRefResolution,
40
+ } from "./types.js";
41
+
42
+ const SCREENSHOT_VALUE_FLAGS = new Set(["--screenshot-dir", "--screenshot-format", "--screenshot-quality"]);
43
+ const SCREENSHOT_IMAGE_EXTENSIONS = new Set([".jpeg", ".jpg", ".png", ".webp"]);
44
+
45
+ export function normalizeRunInput(input: BrowserRunOptions["input"]): BrowserRunInputFields {
46
+ const base = { redactedArgs: input.redactedArgs, toolArgs: input.toolArgs, toolStdin: input.toolStdin };
47
+ switch (input.kind) {
48
+ case "electron":
49
+ return { ...base, compiledElectron: input.compiledElectron, redactedCompiledElectron: input.redactedCompiledElectron };
50
+ case "job":
51
+ return { ...base, compiledJob: input.compiledJob, redactedCompiledJob: input.redactedCompiledJob };
52
+ case "networkSourceLookup":
53
+ return { ...base, compiledNetworkSourceLookup: input.compiledNetworkSourceLookup, redactedCompiledNetworkSourceLookup: input.redactedCompiledNetworkSourceLookup };
54
+ case "qa":
55
+ return { ...base, compiledJob: input.compiledJob, compiledQaPreset: input.compiledQaPreset, redactedCompiledJob: input.redactedCompiledJob, redactedCompiledQaPreset: input.redactedCompiledQaPreset };
56
+ case "semanticAction":
57
+ return { ...base, compiledSemanticAction: input.compiledSemanticAction, redactedCompiledSemanticAction: input.redactedCompiledSemanticAction };
58
+ case "sourceLookup":
59
+ return { ...base, compiledSourceLookup: input.compiledSourceLookup, redactedCompiledSourceLookup: input.redactedCompiledSourceLookup };
60
+ case "args":
61
+ return base;
62
+ }
63
+ }
64
+
65
+ export function buildInvocationPreview(effectiveArgs: string[]): string {
66
+ const preview = effectiveArgs.join(" ");
67
+ return preview.length > 120 ? `${preview.slice(0, 117)}...` : preview;
68
+ }
69
+
70
+ function isImagePathToken(token: string): boolean {
71
+ const extension = extname(token).toLowerCase();
72
+ return SCREENSHOT_IMAGE_EXTENSIONS.has(extension);
73
+ }
74
+
75
+ export function getScreenshotPathTokenIndex(commandTokens: string[]): number | undefined {
76
+ if (commandTokens[0] !== "screenshot") {
77
+ return undefined;
78
+ }
79
+
80
+ const positionalIndices: number[] = [];
81
+ for (let index = 1; index < commandTokens.length; index += 1) {
82
+ const token = commandTokens[index];
83
+ if (token === "--") {
84
+ for (let positionalIndex = index + 1; positionalIndex < commandTokens.length; positionalIndex += 1) {
85
+ positionalIndices.push(positionalIndex);
86
+ }
87
+ break;
88
+ }
89
+ if (token.startsWith("-")) {
90
+ const normalizedToken = token.split("=", 1)[0] ?? token;
91
+ if (SCREENSHOT_VALUE_FLAGS.has(normalizedToken) && !token.includes("=")) {
92
+ index += 1;
93
+ }
94
+ continue;
95
+ }
96
+ positionalIndices.push(index);
97
+ }
98
+
99
+ if (positionalIndices.length === 0) {
100
+ return undefined;
101
+ }
102
+ const candidateIndex = positionalIndices[positionalIndices.length - 1];
103
+ const candidate = commandTokens[candidateIndex];
104
+ if (positionalIndices.length >= 2 || isImagePathToken(candidate) || isAbsolute(candidate) || candidate.startsWith("./") || candidate.startsWith("../")) {
105
+ return candidateIndex;
106
+ }
107
+ return undefined;
108
+ }
109
+
110
+ async function normalizeScreenshotPathInTokens(commandTokens: string[], cwd: string): Promise<{
111
+ request?: ScreenshotPathRequest;
112
+ tokens: string[];
113
+ }> {
114
+ const screenshotPathTokenIndex = getScreenshotPathTokenIndex(commandTokens);
115
+ if (screenshotPathTokenIndex === undefined) {
116
+ return { tokens: commandTokens };
117
+ }
118
+
119
+ const requestedPath = commandTokens[screenshotPathTokenIndex];
120
+ const absolutePath = resolve(cwd, requestedPath);
121
+ await mkdir(dirname(absolutePath), { recursive: true });
122
+
123
+ const tokens = [...commandTokens];
124
+ tokens[screenshotPathTokenIndex] = absolutePath;
125
+ const terminatorIndex = tokens.indexOf("--");
126
+ if (terminatorIndex >= 0) {
127
+ tokens.splice(terminatorIndex, 1);
128
+ }
129
+
130
+ return {
131
+ request: {
132
+ absolutePath,
133
+ path: requestedPath,
134
+ },
135
+ tokens,
136
+ };
137
+ }
138
+
139
+ async function prepareBatchScreenshotPaths(args: string[], stdin: string | undefined, cwd: string): Promise<PreparedAgentBrowserArgs | undefined> {
140
+ const commandTokens = extractCommandTokens(args);
141
+ if (commandTokens[0] !== "batch" || stdin === undefined) {
142
+ return undefined;
143
+ }
144
+ let steps: unknown;
145
+ try {
146
+ steps = JSON.parse(stdin);
147
+ } catch {
148
+ return undefined;
149
+ }
150
+ if (!Array.isArray(steps)) {
151
+ return undefined;
152
+ }
153
+
154
+ let changed = false;
155
+ const batchScreenshotPathRequests: Array<ScreenshotPathRequest | undefined> = [];
156
+ const preparedSteps = await Promise.all(steps.map(async (step, index) => {
157
+ if (!Array.isArray(step) || !step.every((item) => typeof item === "string") || step[0] !== "screenshot") {
158
+ return step;
159
+ }
160
+ const normalized = await normalizeScreenshotPathInTokens(step, cwd);
161
+ batchScreenshotPathRequests[index] = normalized.request;
162
+ if (normalized.request) {
163
+ changed = true;
164
+ }
165
+ return normalized.tokens;
166
+ }));
167
+
168
+ return changed
169
+ ? {
170
+ args,
171
+ batchScreenshotPathRequests,
172
+ stdin: JSON.stringify(preparedSteps),
173
+ }
174
+ : undefined;
175
+ }
176
+
177
+ export async function prepareAgentBrowserArgs(args: string[], stdin: string | undefined, cwd: string): Promise<PreparedAgentBrowserArgs> {
178
+ const preparedBatch = await prepareBatchScreenshotPaths(args, stdin, cwd);
179
+ if (preparedBatch) {
180
+ return preparedBatch;
181
+ }
182
+
183
+ const commandTokens = extractCommandTokens(args);
184
+ const normalized = await normalizeScreenshotPathInTokens(commandTokens, cwd);
185
+ if (!normalized.request) {
186
+ return { args };
187
+ }
188
+
189
+ const commandStartIndex = args.length - commandTokens.length;
190
+ return {
191
+ args: [...args.slice(0, commandStartIndex), ...normalized.tokens],
192
+ screenshotPathRequest: normalized.request,
193
+ };
194
+ }
195
+
196
+ async function pathExists(path: string): Promise<boolean> {
197
+ try {
198
+ await stat(path);
199
+ return true;
200
+ } catch {
201
+ return false;
202
+ }
203
+ }
204
+
205
+ async function repairScreenshotData(options: {
206
+ cwd: string;
207
+ data: Record<string, unknown>;
208
+ request: ScreenshotPathRequest;
209
+ }): Promise<{ data: Record<string, unknown>; request: ScreenshotArtifactRequest }> {
210
+ const { cwd, data, request } = options;
211
+ const reportedPath = typeof data.path === "string" ? data.path : undefined;
212
+ const reportedAbsolutePath = reportedPath ? resolve(cwd, reportedPath) : undefined;
213
+ let status: ScreenshotArtifactRequest["status"] = await pathExists(request.absolutePath) ? "saved" : "missing";
214
+ let tempPath: string | undefined;
215
+
216
+ if (reportedAbsolutePath && reportedAbsolutePath !== request.absolutePath) {
217
+ tempPath = reportedAbsolutePath;
218
+ if (status === "missing" && await pathExists(reportedAbsolutePath)) {
219
+ await mkdir(dirname(request.absolutePath), { recursive: true });
220
+ await copyFile(reportedAbsolutePath, request.absolutePath);
221
+ status = "repaired-from-temp";
222
+ }
223
+ }
224
+
225
+ return {
226
+ data: {
227
+ ...data,
228
+ path: request.absolutePath,
229
+ },
230
+ request: {
231
+ ...request,
232
+ status,
233
+ tempPath,
234
+ },
235
+ };
236
+ }
237
+
238
+ export { repairScreenshotData };
239
+
240
+ function parseMillisecondsToken(token: string | undefined): number | undefined {
241
+ if (token === undefined || !/^\d+$/.test(token)) {
242
+ return undefined;
243
+ }
244
+ const parsed = Number(token);
245
+ return Number.isSafeInteger(parsed) ? parsed : undefined;
246
+ }
247
+
248
+ function findWaitTimeoutMs(commandTokens: string[]): { timeoutMs: number; source: string } | undefined {
249
+ if (commandTokens[0] !== "wait") {
250
+ return undefined;
251
+ }
252
+ for (let index = 1; index < commandTokens.length; index += 1) {
253
+ const token = commandTokens[index];
254
+ if (token === "--timeout") {
255
+ const timeoutMs = parseMillisecondsToken(commandTokens[index + 1]);
256
+ return timeoutMs === undefined ? undefined : { source: "wait --timeout", timeoutMs };
257
+ }
258
+ if (token.startsWith("--timeout=")) {
259
+ const timeoutMs = parseMillisecondsToken(token.slice("--timeout=".length));
260
+ return timeoutMs === undefined ? undefined : { source: "wait --timeout", timeoutMs };
261
+ }
262
+ if (!token.startsWith("-")) {
263
+ const timeoutMs = parseMillisecondsToken(token);
264
+ if (timeoutMs !== undefined) {
265
+ return { source: "wait", timeoutMs };
266
+ }
267
+ }
268
+ }
269
+ return undefined;
270
+ }
271
+
272
+ function buildIpcUnsafeWaitError(source: string, timeoutMs: number, batchStep?: number): string {
273
+ const location = batchStep === undefined ? source : `batch step ${batchStep + 1} (${source})`;
274
+ return `${location} requests ${timeoutMs}ms, but upstream agent-browser CLI calls must stay under its 30s IPC read timeout. Use ${SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS}ms or less per wait, split long waits into multiple tool calls, or use a page-specific shorter condition.`;
275
+ }
276
+
277
+ export function validateWaitIpcTimeoutContract(commandTokens: string[], stdin: string | undefined): string | undefined {
278
+ const directWaitTimeout = findWaitTimeoutMs(commandTokens);
279
+ if (directWaitTimeout && directWaitTimeout.timeoutMs > SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS) {
280
+ return buildIpcUnsafeWaitError(directWaitTimeout.source, directWaitTimeout.timeoutMs);
281
+ }
282
+ if (commandTokens[0] !== "batch" || stdin === undefined) {
283
+ return undefined;
284
+ }
285
+ let steps: unknown;
286
+ try {
287
+ steps = JSON.parse(stdin);
288
+ } catch {
289
+ return undefined;
290
+ }
291
+ if (!Array.isArray(steps)) {
292
+ return undefined;
293
+ }
294
+ for (let index = 0; index < steps.length; index += 1) {
295
+ const step = steps[index];
296
+ if (!Array.isArray(step) || !step.every((item) => typeof item === "string")) {
297
+ continue;
298
+ }
299
+ const waitTimeout = findWaitTimeoutMs(step);
300
+ if (waitTimeout && waitTimeout.timeoutMs > SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS) {
301
+ return buildIpcUnsafeWaitError(waitTimeout.source, waitTimeout.timeoutMs, index);
302
+ }
303
+ }
304
+ return undefined;
305
+ }
306
+
307
+ function isPasswordStdinAuthSave(options: { command?: string; commandTokens: string[] }): boolean {
308
+ return options.command === "auth" && options.commandTokens[1] === "save" && options.commandTokens.includes("--password-stdin");
309
+ }
310
+
311
+ export function getExactSensitiveStdinValues(options: { command?: string; commandTokens: string[]; stdin?: string }): string[] {
312
+ if (options.stdin === undefined || !isPasswordStdinAuthSave(options)) {
313
+ return [];
314
+ }
315
+ return [...new Set([options.stdin, options.stdin.trimEnd(), options.stdin.trim()].filter((value) => value.length > 0))];
316
+ }
317
+
318
+ export function validateStdinCommandContract(options: { command?: string; commandTokens: string[]; stdin?: string }): string | undefined {
319
+ if (options.stdin === undefined) {
320
+ return undefined;
321
+ }
322
+ if (options.command === "batch") {
323
+ return undefined;
324
+ }
325
+ if (options.command === "eval" && options.commandTokens.includes("--stdin")) {
326
+ return undefined;
327
+ }
328
+ if (isPasswordStdinAuthSave(options)) {
329
+ return undefined;
330
+ }
331
+ const commandLabel = options.command ? `\`${options.command}\`` : "the requested command";
332
+ 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.`;
333
+ }
334
+
335
+ export async function resolveSemanticActionVisibleRefArgs(options: {
336
+ compiled: CompiledAgentBrowserSemanticAction | undefined;
337
+ cwd: string;
338
+ sessionName?: string;
339
+ signal?: AbortSignal;
340
+ }): Promise<SemanticActionVisibleRefResolution | undefined> {
341
+ if (!options.compiled || !options.sessionName || options.compiled.locator !== "role" || !["check", "click", "uncheck"].includes(options.compiled.action)) return undefined;
342
+ const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
343
+ const resolution = resolveVisibleRefActionFromSnapshot({ compiledAction: options.compiled, snapshotData });
344
+ if (!resolution) return undefined;
345
+ return { args: [...getCompiledSemanticActionSessionPrefix(options.compiled), ...resolution.args], snapshot: resolution.snapshot };
346
+ }
347
+
348
+ export async function prepareBrowserRun(options: BrowserRunOptions): Promise<PrepareBrowserRunResult> {
349
+ const { cwd, implicitSessionIdleTimeoutMs, onUpdate, params, signal, state } = options;
350
+ const { sessionPageState, traceOwners, managedSessionBaseName, ephemeralSessionSeed } = state;
351
+ let freshSessionOrdinal = state.freshSessionOrdinal;
352
+ const {
353
+ compiledElectron,
354
+ compiledJob,
355
+ compiledNetworkSourceLookup,
356
+ compiledQaPreset,
357
+ compiledSemanticAction,
358
+ compiledSourceLookup,
359
+ redactedArgs,
360
+ redactedCompiledElectron,
361
+ redactedCompiledJob,
362
+ redactedCompiledNetworkSourceLookup,
363
+ redactedCompiledQaPreset,
364
+ redactedCompiledSemanticAction,
365
+ redactedCompiledSourceLookup,
366
+ toolArgs,
367
+ toolStdin,
368
+ } = normalizeRunInput(options.input);
369
+ let runtimeToolArgs = toolArgs;
370
+ let runtimeToolStdin = toolStdin;
371
+ let electronLaunch: ElectronLaunchSuccess | undefined;
372
+ const sessionMode = compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto";
373
+ const freshSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1);
374
+ if (compiledElectron?.action === "launch") {
375
+ const launchResult = await launchElectronApp(compiledElectron);
376
+ if (!launchResult.ok) {
377
+ const managedSessionOutcome = buildManagedSessionOutcome({
378
+ activeAfter: state.managedSessionActive,
379
+ activeBefore: state.managedSessionActive,
380
+ attemptedSessionName: freshSessionName,
381
+ command: "connect",
382
+ currentSessionName: state.managedSessionName,
383
+ previousSessionName: state.managedSessionName,
384
+ sessionMode: "fresh",
385
+ succeeded: false,
386
+ });
387
+ return { kind: "early-result", result: buildElectronHostFailureResult({
388
+ compiledElectron: redactedCompiledElectron ?? compiledElectron,
389
+ errorText: launchResult.failure.error,
390
+ failureCategory: getElectronLaunchFailureCategory(launchResult.failure),
391
+ launchFailure: launchResult.failure,
392
+ managedSessionOutcome,
393
+ status: launchResult.failure.reason,
394
+ }) };
395
+ }
396
+ electronLaunch = launchResult.value;
397
+ runtimeToolArgs = ["connect", electronLaunch.connectArg];
398
+ runtimeToolStdin = undefined;
399
+ }
400
+ const preparedArgs = await prepareAgentBrowserArgs(runtimeToolArgs, runtimeToolStdin, cwd);
401
+ const userRequestedJson = runtimeToolArgs.includes("--json");
402
+ let executionPlan = buildExecutionPlan(preparedArgs.args, {
403
+ freshSessionName,
404
+ managedSessionActive: state.managedSessionActive,
405
+ managedSessionName: state.managedSessionName,
406
+ sessionMode,
407
+ });
408
+ let semanticActionVisibleRefResolution: SemanticActionVisibleRefResolution | undefined;
409
+ if (!executionPlan.validationError && executionPlan.managedSessionName !== freshSessionName) {
410
+ semanticActionVisibleRefResolution = await resolveSemanticActionVisibleRefArgs({
411
+ compiled: compiledSemanticAction,
412
+ cwd,
413
+ sessionName: executionPlan.sessionName,
414
+ signal,
415
+ });
416
+ if (semanticActionVisibleRefResolution) {
417
+ executionPlan = buildExecutionPlan(semanticActionVisibleRefResolution.args, {
418
+ freshSessionName,
419
+ managedSessionActive: state.managedSessionActive,
420
+ managedSessionName: state.managedSessionName,
421
+ sessionMode,
422
+ });
423
+ }
424
+ }
425
+ const redactedEffectiveArgs = redactInvocationArgs(executionPlan.effectiveArgs);
426
+ const redactedRecoveryHint = redactRecoveryHint(executionPlan.recoveryHint);
427
+ const compatibilityWorkaround: CompatibilityWorkaround | undefined = executionPlan.compatibilityWorkaround;
428
+ const statePatch = executionPlan.managedSessionName === freshSessionName
429
+ ? { freshSessionOrdinal: freshSessionOrdinal + 1 }
430
+ : {};
431
+ if (executionPlan.managedSessionName === freshSessionName) {
432
+ freshSessionOrdinal += 1;
433
+ }
434
+
435
+ if (executionPlan.validationError) {
436
+ return { kind: "early-result", statePatch, result: {
437
+ content: [{ type: "text", text: executionPlan.validationError }],
438
+ details: {
439
+ args: redactedArgs,
440
+ compiledElectron: redactedCompiledElectron,
441
+ compiledJob: redactedCompiledJob,
442
+ compiledQaPreset: redactedCompiledQaPreset,
443
+ compiledSourceLookup: redactedCompiledSourceLookup,
444
+ compiledNetworkSourceLookup: redactedCompiledNetworkSourceLookup,
445
+ invalidValueFlag: executionPlan.invalidValueFlag,
446
+ sessionMode,
447
+ sessionRecoveryHint: redactedRecoveryHint,
448
+ startupScopedFlags: executionPlan.startupScopedFlags,
449
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, command: executionPlan.commandInfo.command, errorText: executionPlan.validationError, succeeded: false, validationError: executionPlan.validationError }),
450
+ validationError: executionPlan.validationError,
451
+ },
452
+ isError: true,
453
+ } };
454
+ }
455
+
456
+ const commandTokens = semanticActionVisibleRefResolution ? extractCommandTokens(semanticActionVisibleRefResolution.args) : extractCommandTokens(preparedArgs.args);
457
+ const exactSensitiveValues = getExactSensitiveStdinValues({
458
+ command: executionPlan.commandInfo.command,
459
+ commandTokens,
460
+ stdin: runtimeToolStdin,
461
+ });
462
+ const traceOwnerGuardMessage = getTraceOwnerGuardMessage({
463
+ command: executionPlan.commandInfo.command,
464
+ sessionName: executionPlan.sessionName,
465
+ subcommand: executionPlan.commandInfo.subcommand,
466
+ traceOwners,
467
+ });
468
+ if (traceOwnerGuardMessage) {
469
+ return { kind: "early-result", statePatch, result: {
470
+ content: [{ type: "text", text: traceOwnerGuardMessage }],
471
+ details: {
472
+ args: redactedArgs,
473
+ command: executionPlan.commandInfo.command,
474
+ compatibilityWorkaround,
475
+ effectiveArgs: redactedEffectiveArgs,
476
+ sessionMode,
477
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: traceOwnerGuardMessage, succeeded: false, validationError: traceOwnerGuardMessage }),
478
+ validationError: traceOwnerGuardMessage,
479
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
480
+ },
481
+ isError: true,
482
+ } };
483
+ }
484
+ const stdinValidationError = validateStdinCommandContract({
485
+ command: executionPlan.commandInfo.command,
486
+ commandTokens,
487
+ stdin: runtimeToolStdin,
488
+ });
489
+ if (stdinValidationError) {
490
+ return { kind: "early-result", statePatch, result: {
491
+ content: [{ type: "text", text: stdinValidationError }],
492
+ details: {
493
+ args: redactedArgs,
494
+ command: executionPlan.commandInfo.command,
495
+ compatibilityWorkaround,
496
+ effectiveArgs: redactedEffectiveArgs,
497
+ sessionMode,
498
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: stdinValidationError, succeeded: false, validationError: stdinValidationError }),
499
+ validationError: stdinValidationError,
500
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
501
+ },
502
+ isError: true,
503
+ } };
504
+ }
505
+ const waitIpcTimeoutError = validateWaitIpcTimeoutContract(commandTokens, runtimeToolStdin);
506
+ if (waitIpcTimeoutError) {
507
+ return { kind: "early-result", statePatch, result: {
508
+ content: [{ type: "text", text: waitIpcTimeoutError }],
509
+ details: {
510
+ args: redactedArgs,
511
+ command: executionPlan.commandInfo.command,
512
+ compatibilityWorkaround,
513
+ effectiveArgs: redactedEffectiveArgs,
514
+ sessionMode,
515
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: waitIpcTimeoutError, succeeded: false, timedOut: true, validationError: waitIpcTimeoutError }),
516
+ validationError: waitIpcTimeoutError,
517
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
518
+ },
519
+ isError: true,
520
+ } };
521
+ }
522
+
523
+ const priorSessionPageState = sessionPageState.get(executionPlan.sessionName);
524
+ const priorSessionTabTarget = priorSessionPageState.tabTarget;
525
+ const sessionTabPinningReason = priorSessionPageState.pinningReason;
526
+ const priorRefSnapshotState = priorSessionPageState.refSnapshot;
527
+ const priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
528
+ const resolvedSemanticActionRefSnapshot: SessionRefSnapshot | undefined = semanticActionVisibleRefResolution?.snapshot
529
+ ? { ...semanticActionVisibleRefResolution.snapshot, target: semanticActionVisibleRefResolution.snapshot.target ?? priorSessionTabTarget }
530
+ : undefined;
531
+ const staleRefPreflight = buildStaleRefPreflight({
532
+ commandTokens,
533
+ currentTarget: priorSessionTabTarget,
534
+ refSnapshot: resolvedSemanticActionRefSnapshot ?? priorRefSnapshotState,
535
+ refSnapshotInvalidation: resolvedSemanticActionRefSnapshot ? undefined : priorRefSnapshotInvalidation,
536
+ stdin: runtimeToolStdin,
537
+ });
538
+ if (staleRefPreflight) {
539
+ return { kind: "early-result", statePatch, result: {
540
+ content: [{ type: "text", text: staleRefPreflight.message }],
541
+ details: {
542
+ args: redactedArgs,
543
+ command: executionPlan.commandInfo.command,
544
+ compatibilityWorkaround,
545
+ effectiveArgs: redactedEffectiveArgs,
546
+ nextActions: buildSessionAwareStaleRefNextActions(executionPlan.sessionName),
547
+ refIds: staleRefPreflight.refIds,
548
+ refSnapshot: staleRefPreflight.snapshot,
549
+ refSnapshotInvalidation: staleRefPreflight.snapshotInvalidation,
550
+ sessionMode,
551
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: staleRefPreflight.message, failureCategory: "stale-ref", succeeded: false }),
552
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
553
+ },
554
+ isError: true,
555
+ } };
556
+ }
557
+
558
+ let pinnedBatchUnwrapMode: PreparedBrowserRun["pinnedBatchUnwrapMode"];
559
+ let includePinnedNavigationSummary = false;
560
+ let sessionTabCorrection: PreparedBrowserRun["sessionTabCorrection"];
561
+ let processArgs = executionPlan.effectiveArgs;
562
+ let processStdin = preparedArgs.stdin ?? runtimeToolStdin;
563
+ if (
564
+ priorSessionTabTarget &&
565
+ shouldPinSessionTabForCommand({
566
+ command: executionPlan.commandInfo.command,
567
+ commandTokens,
568
+ pinningRequired: sessionTabPinningReason !== undefined,
569
+ sessionName: executionPlan.sessionName,
570
+ stdin: runtimeToolStdin,
571
+ })
572
+ ) {
573
+ const plannedSessionTabSelection = await collectSessionTabSelection({
574
+ cwd,
575
+ sessionName: executionPlan.sessionName,
576
+ signal,
577
+ target: priorSessionTabTarget,
578
+ });
579
+ if (plannedSessionTabSelection && executionPlan.sessionName) {
580
+ if (executionPlan.commandInfo.command === "eval" && runtimeToolStdin !== undefined) {
581
+ const appliedSessionTabSelection = await applyOpenResultTabCorrection({
582
+ correction: plannedSessionTabSelection,
583
+ cwd,
584
+ sessionName: executionPlan.sessionName,
585
+ signal,
586
+ });
587
+ if (!appliedSessionTabSelection) {
588
+ const error = "agent-browser could not re-select the intended tab before running the command.";
589
+ return { kind: "early-result", statePatch, result: {
590
+ content: [{ type: "text", text: error }],
591
+ details: {
592
+ args: redactedArgs,
593
+ command: executionPlan.commandInfo.command,
594
+ compatibilityWorkaround,
595
+ effectiveArgs: redactedEffectiveArgs,
596
+ sessionMode,
597
+ sessionTabCorrection: plannedSessionTabSelection,
598
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: error, failureCategory: "tab-drift", succeeded: false, tabDrift: true, validationError: error }),
599
+ nextActions: buildSessionTabRecoveryNextActions({
600
+ kind: "tab-drift",
601
+ resultCategory: "failure",
602
+ sessionName: executionPlan.sessionName,
603
+ tabCorrection: plannedSessionTabSelection,
604
+ target: priorSessionTabTarget,
605
+ }),
606
+ validationError: error,
607
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
608
+ },
609
+ isError: true,
610
+ } };
611
+ }
612
+ sessionTabCorrection = appliedSessionTabSelection;
613
+ } else {
614
+ const pinnedBatchPlan = buildPinnedBatchPlan({
615
+ command: executionPlan.commandInfo.command,
616
+ commandTokens,
617
+ selectedTab: plannedSessionTabSelection.selectedTab,
618
+ stdin: runtimeToolStdin,
619
+ });
620
+ if (pinnedBatchPlan && "error" in pinnedBatchPlan) {
621
+ return { kind: "early-result", statePatch, result: {
622
+ content: [{ type: "text", text: pinnedBatchPlan.error }],
623
+ details: {
624
+ args: redactedArgs,
625
+ command: executionPlan.commandInfo.command,
626
+ compatibilityWorkaround,
627
+ effectiveArgs: redactedEffectiveArgs,
628
+ sessionMode,
629
+ sessionTabCorrection: plannedSessionTabSelection,
630
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: pinnedBatchPlan.error, failureCategory: "tab-drift", succeeded: false, tabDrift: true, validationError: pinnedBatchPlan.error }),
631
+ nextActions: buildSessionTabRecoveryNextActions({
632
+ kind: "tab-drift",
633
+ resultCategory: "failure",
634
+ sessionName: executionPlan.sessionName,
635
+ tabCorrection: plannedSessionTabSelection,
636
+ target: priorSessionTabTarget,
637
+ }),
638
+ validationError: pinnedBatchPlan.error,
639
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
640
+ },
641
+ isError: true,
642
+ } };
643
+ }
644
+ if (pinnedBatchPlan) {
645
+ sessionTabCorrection = plannedSessionTabSelection;
646
+ processArgs = ["--json", "--session", executionPlan.sessionName, "batch"];
647
+ processStdin = JSON.stringify(pinnedBatchPlan.steps);
648
+ includePinnedNavigationSummary = pinnedBatchPlan.includeNavigationSummary;
649
+ pinnedBatchUnwrapMode = pinnedBatchPlan.unwrapMode;
650
+ }
651
+ }
652
+ }
653
+ }
654
+ const redactedProcessArgs = redactInvocationArgs(processArgs);
655
+ const shouldProbeScrollNoop = executionPlan.commandInfo.command === "scroll" && executionPlan.startupScopedFlags.length === 0;
656
+ const scrollPositionBefore = shouldProbeScrollNoop
657
+ ? await collectScrollPositionSnapshot({ cwd, sessionName: executionPlan.sessionName, signal })
658
+ : undefined;
659
+
660
+ onUpdate?.({
661
+ content: [{ type: "text", text: `Running agent-browser ${buildInvocationPreview(redactedProcessArgs)}` }],
662
+ details: {
663
+ compatibilityWorkaround,
664
+ effectiveArgs: redactedProcessArgs,
665
+ sessionMode,
666
+ sessionTabCorrection,
667
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
668
+ },
669
+ });
670
+
671
+ return { kind: "ready", prepared: {
672
+ commandTokens,
673
+ compiledElectron,
674
+ compiledJob,
675
+ compiledNetworkSourceLookup,
676
+ compiledQaPreset,
677
+ compiledSemanticAction,
678
+ compiledSourceLookup,
679
+ compatibilityWorkaround,
680
+ electronLaunch,
681
+ exactSensitiveValues,
682
+ executionPlan,
683
+ includePinnedNavigationSummary,
684
+ pinnedBatchUnwrapMode,
685
+ preparedArgs,
686
+ priorRefSnapshotState,
687
+ priorSessionTabTarget,
688
+ processArgs,
689
+ processStdin,
690
+ redactedArgs,
691
+ redactedCompiledElectron,
692
+ redactedCompiledJob,
693
+ redactedCompiledNetworkSourceLookup,
694
+ redactedCompiledQaPreset,
695
+ redactedCompiledSemanticAction,
696
+ redactedCompiledSourceLookup,
697
+ redactedEffectiveArgs,
698
+ redactedProcessArgs,
699
+ redactedRecoveryHint,
700
+ resolvedSemanticActionRefSnapshot,
701
+ runtimeToolArgs,
702
+ runtimeToolStdin,
703
+ scrollPositionBefore,
704
+ sessionMode,
705
+ sessionTabCorrection,
706
+ sessionTabPinningReason,
707
+ shouldProbeScrollNoop,
708
+ statePatch,
709
+ userRequestedJson,
710
+ } };
711
+ }