pi-agent-browser-native 0.2.33 → 0.2.35
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.
- package/CHANGELOG.md +46 -0
- package/README.md +47 -17
- package/docs/ARCHITECTURE.md +25 -13
- package/docs/COMMAND_REFERENCE.md +285 -47
- package/docs/ELECTRON.md +3 -3
- package/docs/RELEASE.md +22 -14
- package/docs/REQUIREMENTS.md +5 -5
- package/docs/SUPPORT_MATRIX.md +26 -22
- package/docs/TOOL_CONTRACT.md +97 -32
- package/extensions/agent-browser/index.ts +519 -2402
- package/extensions/agent-browser/lib/argv-descriptor.ts +90 -0
- package/extensions/agent-browser/lib/argv-grammar.ts +128 -0
- package/extensions/agent-browser/lib/command-policy.ts +71 -0
- package/extensions/agent-browser/lib/command-taxonomy.ts +336 -0
- package/extensions/agent-browser/lib/electron/cleanup.ts +1 -0
- package/extensions/agent-browser/lib/executable-path.ts +19 -0
- package/extensions/agent-browser/lib/input-modes/job.ts +62 -0
- package/extensions/agent-browser/lib/input-modes/params.ts +8 -8
- package/extensions/agent-browser/lib/input-modes.ts +3 -0
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +65 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/browser-action-model.ts +154 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +149 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +77 -29
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +6 -2
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +33 -27
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +74 -23
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +67 -17
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +93 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +19 -123
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +32 -1
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +860 -0
- package/extensions/agent-browser/lib/playbook.ts +24 -23
- package/extensions/agent-browser/lib/prompt-policy.ts +122 -0
- package/extensions/agent-browser/lib/results/action-recommendations.ts +3 -23
- package/extensions/agent-browser/lib/results/categories.ts +1 -1
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +2 -34
- package/extensions/agent-browser/lib/results/presentation/registry.ts +34 -6
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +133 -0
- package/extensions/agent-browser/lib/results/presentation.ts +11 -6
- package/extensions/agent-browser/lib/runtime.ts +93 -227
- package/extensions/agent-browser/lib/session-page-state.ts +31 -14
- package/extensions/agent-browser/lib/temp.ts +148 -23
- package/package.json +4 -4
- package/scripts/agent-browser-capability-baseline.mjs +198 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { readFile, rm } from "node:fs/promises";
|
|
2
2
|
|
|
3
|
+
import { isCloseCommand, isOpenNavigationCommand } from "../../command-taxonomy.js";
|
|
3
4
|
import { cleanupElectronLaunchResources, inspectElectronLaunchStatus, type ElectronCleanupResult } from "../../electron/cleanup.js";
|
|
4
5
|
import type { ElectronLaunchRecord } from "../../electron/launch.js";
|
|
5
6
|
import {
|
|
6
7
|
analyzeNetworkSourceLookupResults,
|
|
7
8
|
analyzeQaPresetResults,
|
|
8
9
|
analyzeSourceLookupResults,
|
|
10
|
+
buildQaCompactPassText,
|
|
11
|
+
extractQaPageContext,
|
|
9
12
|
redactNetworkSourceLookupAnalysis,
|
|
10
13
|
} from "../../input-modes.js";
|
|
11
14
|
import {
|
|
@@ -20,6 +23,7 @@ import {
|
|
|
20
23
|
mergeSessionArtifactManifest,
|
|
21
24
|
} from "../../results/artifact-manifest.js";
|
|
22
25
|
import type { SessionArtifactManifest } from "../../results/contracts.js";
|
|
26
|
+
import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
|
|
23
27
|
import {
|
|
24
28
|
commandExplicitlyTargetsAboutBlank,
|
|
25
29
|
deriveSessionTabTarget,
|
|
@@ -35,7 +39,7 @@ import {
|
|
|
35
39
|
import type { PersistentSessionArtifactEviction, PersistentSessionArtifactStore } from "../../temp.js";
|
|
36
40
|
import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../../temp.js";
|
|
37
41
|
import { isRecord } from "../../parsing.js";
|
|
38
|
-
import { hasLaunchScopedTabCorrectionFlag, resolveManagedSessionState } from "../../runtime.js";
|
|
42
|
+
import { createFreshSessionName, hasLaunchScopedTabCorrectionFlag, resolveManagedSessionState } from "../../runtime.js";
|
|
39
43
|
import {
|
|
40
44
|
applyOpenResultTabCorrection,
|
|
41
45
|
buildAboutBlankRecoveryHint,
|
|
@@ -59,6 +63,7 @@ import {
|
|
|
59
63
|
unwrapPinnedSessionBatchEnvelope,
|
|
60
64
|
updateTraceOwnerState,
|
|
61
65
|
} from "./session-state.js";
|
|
66
|
+
import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
|
|
62
67
|
import {
|
|
63
68
|
buildScrollNoopDiagnostic,
|
|
64
69
|
collectComboboxFocusDiagnostic,
|
|
@@ -174,6 +179,7 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
174
179
|
const { prepared, processResult } = input;
|
|
175
180
|
const { electronChildProcesses, electronLaunchRecords, sessionPageState, traceOwners } = state;
|
|
176
181
|
let artifactManifest = state.artifactManifest;
|
|
182
|
+
let freshSessionOrdinal = state.freshSessionOrdinal;
|
|
177
183
|
let managedSessionActive = state.managedSessionActive;
|
|
178
184
|
let managedSessionCwd = state.managedSessionCwd;
|
|
179
185
|
let managedSessionName = state.managedSessionName;
|
|
@@ -205,14 +211,28 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
205
211
|
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
206
212
|
updateTraceOwnerState({ command: prepared.executionPlan.commandInfo.command, sessionName: prepared.executionPlan.sessionName, subcommand: prepared.executionPlan.commandInfo.subcommand, succeeded, traceOwners });
|
|
207
213
|
|
|
208
|
-
|
|
214
|
+
let clickDispatchDiagnostic: Awaited<ReturnType<typeof collectClickDispatchDiagnostic>>;
|
|
215
|
+
if (succeeded && prepared.clickDispatchProbe) {
|
|
216
|
+
clickDispatchDiagnostic = await collectClickDispatchDiagnostic({ cwd, probe: prepared.clickDispatchProbe, sessionName: prepared.executionPlan.sessionName, signal });
|
|
217
|
+
if (clickDispatchDiagnostic) {
|
|
218
|
+
succeeded = false;
|
|
219
|
+
presentationEnvelope = { ...(presentationEnvelope ?? {}), error: clickDispatchDiagnostic.summary, success: false };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
succeeded &&
|
|
225
|
+
!navigationSummary &&
|
|
226
|
+
(shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data) ||
|
|
227
|
+
shouldCaptureSemanticActionNavigationSummary(prepared.compiledSemanticAction, presentationEnvelope?.data))
|
|
228
|
+
) {
|
|
209
229
|
navigationSummary = await collectNavigationSummary({ cwd, sessionName: prepared.executionPlan.sessionName, signal });
|
|
210
230
|
}
|
|
211
231
|
if (navigationSummary && presentationEnvelope) presentationEnvelope = { ...presentationEnvelope, data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary) };
|
|
212
232
|
let overlayBlockerDiagnostic: Awaited<ReturnType<typeof collectOverlayBlockerDiagnostic>>;
|
|
213
233
|
|
|
214
234
|
let openResultTabCorrection: Awaited<ReturnType<typeof collectOpenResultTabCorrection>>;
|
|
215
|
-
if (succeeded && prepared.executionPlan.sessionName && hasLaunchScopedTabCorrectionFlag(prepared.runtimeToolArgs) &&
|
|
235
|
+
if (succeeded && prepared.executionPlan.sessionName && hasLaunchScopedTabCorrectionFlag(prepared.runtimeToolArgs) && isOpenNavigationCommand(prepared.executionPlan.commandInfo.command)) {
|
|
216
236
|
const targetTitle = extractStringResultField(presentationEnvelope?.data, "title");
|
|
217
237
|
const targetUrl = extractStringResultField(presentationEnvelope?.data, "url");
|
|
218
238
|
const plannedTabCorrection = await collectOpenResultTabCorrection({ cwd, sessionName: prepared.executionPlan.sessionName, signal, targetTitle, targetUrl });
|
|
@@ -267,7 +287,7 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
267
287
|
fillVerificationDiagnostic = await collectFillVerificationDiagnostic({ commandTokens: prepared.commandTokens, cwd, sessionName: prepared.executionPlan.sessionName, signal });
|
|
268
288
|
electronRefFreshnessDiagnostic = buildElectronRefFreshnessDiagnostic({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, record: electronRecordForCommand, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin });
|
|
269
289
|
}
|
|
270
|
-
if (succeeded && !sessionTabCorrection && !aboutBlankSessionMismatch && !electronRecordForCommand) overlayBlockerDiagnostic = await collectOverlayBlockerDiagnostic({ command: prepared.executionPlan.commandInfo.command, cwd, data: presentationEnvelope?.data, navigationSummary, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
|
|
290
|
+
if (succeeded && !sessionTabCorrection && !aboutBlankSessionMismatch && !electronRecordForCommand && !clickDispatchDiagnostic) overlayBlockerDiagnostic = await collectOverlayBlockerDiagnostic({ command: prepared.executionPlan.commandInfo.command, cwd, data: presentationEnvelope?.data, navigationSummary, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
|
|
271
291
|
if (succeeded) {
|
|
272
292
|
selectorTextVisibilityDiagnostics = await collectSelectorTextVisibilityDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, cwd, data: presentationEnvelope?.data, sessionName: prepared.executionPlan.sessionName, signal });
|
|
273
293
|
electronBroadGetTextScopeDiagnostics = collectElectronBroadGetTextScopeDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, currentTarget: currentSessionTabTarget, data: presentationEnvelope?.data, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName });
|
|
@@ -279,8 +299,10 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
279
299
|
let currentRefSnapshotInvalidation: SessionRefSnapshotInvalidation | undefined;
|
|
280
300
|
const batchRefSnapshotState = prepared.executionPlan.commandInfo.command === "batch" ? extractLatestRefSnapshotStateFromBatchResults(presentationEnvelope?.data) : undefined;
|
|
281
301
|
if (prepared.executionPlan.sessionName) {
|
|
282
|
-
if (prepared.executionPlan.commandInfo.command
|
|
283
|
-
|
|
302
|
+
if (isCloseCommand(prepared.executionPlan.commandInfo.command) && succeeded) {
|
|
303
|
+
sessionPageState.clearSession(prepared.executionPlan.sessionName);
|
|
304
|
+
state.closedManagedSessionNames.add(prepared.executionPlan.sessionName);
|
|
305
|
+
} else if (currentSessionTabTarget) {
|
|
284
306
|
const tabUpdate = sessionPageState.applyTabTarget({ sessionName: prepared.executionPlan.sessionName, target: currentSessionTabTarget, update: sessionPageStateUpdate });
|
|
285
307
|
if (!tabUpdate.applied && succeeded) sessionPageState.markPinning(prepared.executionPlan.sessionName, "drift");
|
|
286
308
|
}
|
|
@@ -299,11 +321,19 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
299
321
|
const priorManagedSessionActive = managedSessionActive;
|
|
300
322
|
const priorManagedSessionCwd = managedSessionCwd;
|
|
301
323
|
const priorManagedSessionName = managedSessionName;
|
|
302
|
-
const
|
|
324
|
+
const commandClosesSession = isCloseCommand(prepared.executionPlan.commandInfo.command);
|
|
325
|
+
const managedCloseSessionName = commandClosesSession && succeeded && prepared.executionPlan.sessionName === priorManagedSessionName
|
|
326
|
+
? prepared.executionPlan.sessionName
|
|
327
|
+
: prepared.executionPlan.managedSessionName;
|
|
328
|
+
const managedSessionState = resolveManagedSessionState({ command: prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, priorActive: priorManagedSessionActive, priorSessionName: priorManagedSessionName, succeeded });
|
|
303
329
|
const replacedManagedSessionName = managedSessionState.replacedSessionName;
|
|
304
330
|
managedSessionActive = managedSessionState.active;
|
|
305
331
|
managedSessionName = managedSessionState.sessionName;
|
|
306
|
-
|
|
332
|
+
if (commandClosesSession && succeeded && managedCloseSessionName === priorManagedSessionName && !managedSessionActive) {
|
|
333
|
+
freshSessionOrdinal += 1;
|
|
334
|
+
managedSessionName = createFreshSessionName(state.managedSessionBaseName, state.ephemeralSessionSeed, freshSessionOrdinal);
|
|
335
|
+
}
|
|
336
|
+
let managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: managedSessionActive, activeBefore: priorManagedSessionActive, attemptedSessionName: managedCloseSessionName, command: prepared.executionPlan.commandInfo.command, currentSessionName: managedSessionName, previousSessionName: priorManagedSessionName, replacedSessionName: replacedManagedSessionName, sessionMode: prepared.sessionMode, succeeded });
|
|
307
337
|
if (prepared.executionPlan.managedSessionName && succeeded) managedSessionCwd = cwd;
|
|
308
338
|
if (prepared.executionPlan.sessionName && succeeded) {
|
|
309
339
|
if (openResultTabCorrection || sessionTabCorrection || aboutBlankSessionMismatch?.recoveryApplied) sessionPageState.markPinning(prepared.executionPlan.sessionName, "drift");
|
|
@@ -311,7 +341,8 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
311
341
|
}
|
|
312
342
|
if (replacedManagedSessionName) {
|
|
313
343
|
sessionPageState.clearSession(replacedManagedSessionName);
|
|
314
|
-
await closeManagedSession({ cwd: priorManagedSessionCwd, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
|
|
344
|
+
const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
|
|
345
|
+
if (!replacedCloseError) state.closedManagedSessionNames.add(replacedManagedSessionName);
|
|
315
346
|
}
|
|
316
347
|
|
|
317
348
|
let electronLaunchRecord: ElectronLaunchRecord | undefined;
|
|
@@ -341,7 +372,7 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
341
372
|
}
|
|
342
373
|
|
|
343
374
|
const errorText = getAgentBrowserErrorText({ aborted: processResult.aborted, command: prepared.executionPlan.commandInfo.command, effectiveArgs: prepared.redactedProcessArgs, envelope: presentationEnvelope, exitCode: processResult.exitCode, parseError, plainTextInspection, staleRefArgs: getStaleRefArgs(prepared.commandTokens, prepared.runtimeToolStdin), spawnError: processResult.spawnError, stderr: processResult.stderr, timedOut: processResult.timedOut, timeoutMs: processResult.timeoutMs, wrapperRecoveryHint: buildWrapperRecoveryHint({ pinnedBatchUnwrapMode: prepared.pinnedBatchUnwrapMode, sessionTabCorrection }) });
|
|
344
|
-
const presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text" as const, text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : await buildToolPresentation({ args: prepared.redactedProcessArgs, artifactManifest, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, cwd, envelope: presentationEnvelope, errorText, persistentArtifactStore, sessionName: prepared.executionPlan.sessionName });
|
|
375
|
+
const presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text" as const, text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : await buildToolPresentation({ args: prepared.redactedProcessArgs, artifactManifest, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd, envelope: presentationEnvelope, errorText, persistentArtifactStore, sessionName: prepared.executionPlan.sessionName });
|
|
345
376
|
if (parseFailureOutput.artifactManifest) { presentation.artifactManifest = parseFailureOutput.artifactManifest; presentation.artifactRetentionSummary = parseFailureOutput.artifactRetentionSummary; }
|
|
346
377
|
if (parseFailureOutput.fullOutputPath || parseFailureOutput.fullOutputUnavailable) {
|
|
347
378
|
const existingText = presentation.content[0]?.type === "text" ? presentation.content[0].text : "";
|
|
@@ -351,7 +382,9 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
351
382
|
}
|
|
352
383
|
if (presentation.artifactManifest) artifactManifest = presentation.artifactManifest;
|
|
353
384
|
const qaPreset = prepared.compiledQaPreset ? analyzeQaPresetResults(presentationEnvelope?.data) : undefined;
|
|
354
|
-
|
|
385
|
+
let qaAttachedTarget = prepared.compiledQaPreset?.checks.attached
|
|
386
|
+
? await collectQaAttachedTarget({ currentTarget: currentSessionTabTarget ?? prepared.priorSessionTabTarget, cwd, sessionName: prepared.executionPlan.sessionName, signal })
|
|
387
|
+
: undefined;
|
|
355
388
|
const sourceLookupElectronContext = prepared.compiledSourceLookup ? getSourceLookupElectronContext({ currentTarget: currentSessionTabTarget, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName }) : undefined;
|
|
356
389
|
const sourceLookup = prepared.compiledSourceLookup ? await analyzeSourceLookupResults(presentationEnvelope?.data, prepared.compiledSourceLookup, cwd, { electronContext: sourceLookupElectronContext, workspaceRoot: cwd }) : undefined;
|
|
357
390
|
const networkSourceLookup = prepared.compiledNetworkSourceLookup ? redactNetworkSourceLookupAnalysis(await analyzeNetworkSourceLookupResults(presentationEnvelope?.data, prepared.compiledNetworkSourceLookup, cwd)) : undefined;
|
|
@@ -359,15 +392,32 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
359
392
|
else if (networkSourceLookup) presentation.content.unshift({ type: "text", text: networkSourceLookup.summary });
|
|
360
393
|
if (sourceLookup && presentation.content[0]?.type === "text") presentation.content[0] = { ...presentation.content[0], text: `${sourceLookup.summary}\n\n${presentation.content[0].text}` };
|
|
361
394
|
else if (sourceLookup) presentation.content.unshift({ type: "text", text: sourceLookup.summary });
|
|
362
|
-
if (qaPreset &&
|
|
363
|
-
|
|
395
|
+
if (qaPreset && !qaPreset.passed) {
|
|
396
|
+
succeeded = false;
|
|
397
|
+
presentation.failureCategory = "qa-failure";
|
|
364
398
|
presentation.summary = qaPreset.summary;
|
|
365
399
|
if (presentation.content[0]?.type === "text") presentation.content[0] = { ...presentation.content[0], text: `${qaPreset.summary}\n\n${presentation.content[0].text}` };
|
|
366
400
|
else presentation.content.unshift({ type: "text", text: qaPreset.summary });
|
|
401
|
+
} else if (qaPreset?.passed && prepared.compiledQaPreset) {
|
|
402
|
+
const compactText = buildQaCompactPassText({
|
|
403
|
+
artifactVerification: presentation.artifactVerification,
|
|
404
|
+
batchStepCount: presentation.batchSteps?.length ?? prepared.compiledQaPreset.steps.length,
|
|
405
|
+
checks: prepared.compiledQaPreset.checks,
|
|
406
|
+
page: extractQaPageContext({
|
|
407
|
+
attachedTarget: qaAttachedTarget,
|
|
408
|
+
batchData: presentationEnvelope?.data,
|
|
409
|
+
compiled: prepared.compiledQaPreset,
|
|
410
|
+
}),
|
|
411
|
+
qaPreset,
|
|
412
|
+
});
|
|
413
|
+
presentation.summary = qaPreset.summary;
|
|
414
|
+
const nonTextContent = presentation.content.filter((item) => item.type !== "text");
|
|
415
|
+
presentation.content = [{ type: "text", text: compactText }, ...nonTextContent];
|
|
367
416
|
}
|
|
368
417
|
const qaAttachedTargetText = formatQaAttachedTargetText(qaAttachedTarget);
|
|
369
|
-
|
|
370
|
-
|
|
418
|
+
const skipAttachedTargetBanner = qaPreset?.passed && prepared.compiledQaPreset?.checks.attached;
|
|
419
|
+
if (!skipAttachedTargetBanner && qaAttachedTargetText && presentation.content[0]?.type === "text") presentation.content[0] = { ...presentation.content[0], text: `${qaAttachedTargetText}\n\n${presentation.content[0].text}` };
|
|
420
|
+
else if (!skipAttachedTargetBanner && qaAttachedTargetText) presentation.content.unshift({ type: "text", text: qaAttachedTargetText });
|
|
371
421
|
if (managedSessionOutcome && managedSessionOutcome.succeeded !== succeeded) managedSessionOutcome = { ...managedSessionOutcome, succeeded };
|
|
372
422
|
const evalStdinHint = getEvalStdinHint({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, stdin: prepared.runtimeToolStdin });
|
|
373
423
|
const resultArtifactManifest = presentation.artifactManifest ?? artifactManifest;
|
|
@@ -377,8 +427,8 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
377
427
|
const finalRecoveryState = await prepareFinalResultRecoveryState({ aboutBlankSessionMismatch, batchRefSnapshotState, commandTokens: prepared.commandTokens, compiledSemanticAction: prepared.compiledSemanticAction, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, cwd, electronPostCommandHealth, errorText, executionPlan: prepared.executionPlan, parseError, plainTextInspection, presentation, processResult, redactedProcessArgs: prepared.redactedProcessArgs, runtimeToolArgs: prepared.runtimeToolArgs, sessionPageState, sessionPageStateUpdate, sessionTabCorrection, signal, succeeded });
|
|
378
428
|
currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
|
|
379
429
|
currentRefSnapshotInvalidation = finalRecoveryState.currentRefSnapshotInvalidation;
|
|
380
|
-
const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, inspectionText, managedSessionOutcome, navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
381
|
-
const statePatch: BrowserRunStatePatch = { artifactManifest, managedSessionActive, managedSessionCwd, managedSessionName };
|
|
430
|
+
const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, inspectionText, managedSessionOutcome, navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
431
|
+
const statePatch: BrowserRunStatePatch = { artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCwd, managedSessionName };
|
|
382
432
|
return { result, statePatch };
|
|
383
433
|
} finally {
|
|
384
434
|
if (processResult.stdoutSpillPath) await rm(processResult.stdoutSpillPath, { force: true }).catch(() => undefined);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isAbsolute, resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { isCloseCommand } from "../../command-taxonomy.js";
|
|
4
|
+
import { executableExistsOnPath } from "../../executable-path.js";
|
|
5
|
+
import type { SessionArtifactManifest } from "../../results/contracts.js";
|
|
6
|
+
import type { PromptPolicy, PromptRequestedArtifact } from "../../prompt-policy.js";
|
|
7
|
+
import type { SessionRefSnapshot } from "../../session-page-state.js";
|
|
8
|
+
import { findBlockedFinalizingAction, STOP_BOUNDARY_GUARD_SCOPE, type BrowserFinalizingAction } from "./browser-action-model.js";
|
|
9
|
+
|
|
10
|
+
export interface StopBoundaryViolation {
|
|
11
|
+
action: BrowserFinalizingAction;
|
|
12
|
+
command: string[];
|
|
13
|
+
message: string;
|
|
14
|
+
reason: "explicit-user-stop-boundary";
|
|
15
|
+
stepIndex?: number;
|
|
16
|
+
target?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RequestedArtifactCloseViolation {
|
|
20
|
+
message: string;
|
|
21
|
+
missingArtifacts: PromptRequestedArtifact[];
|
|
22
|
+
reason: "requested-artifacts-missing-before-close";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatStopBoundaryActionPhrase(action: BrowserFinalizingAction): string {
|
|
26
|
+
if (action.kind === "keyboard-submit") return "keyboard submit (Enter/Return)";
|
|
27
|
+
return "click-like action";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function findStopBoundaryViolation(options: { commandTokens: string[]; promptPolicy: PromptPolicy; refSnapshot?: SessionRefSnapshot; stdin?: string }): StopBoundaryViolation | undefined {
|
|
31
|
+
if (!options.promptPolicy.stopBoundary) return undefined;
|
|
32
|
+
const blocked = findBlockedFinalizingAction({
|
|
33
|
+
commandTokens: options.commandTokens,
|
|
34
|
+
refSnapshot: options.refSnapshot,
|
|
35
|
+
stdin: options.stdin,
|
|
36
|
+
});
|
|
37
|
+
if (!blocked) return undefined;
|
|
38
|
+
const target = blocked.targetLabel;
|
|
39
|
+
const actionPhrase = formatStopBoundaryActionPhrase(blocked);
|
|
40
|
+
const scopeNote = `Best-effort guard scope covers ${STOP_BOUNDARY_GUARD_SCOPE.covered.join(", ")}; it does not block ${STOP_BOUNDARY_GUARD_SCOPE.excluded.join(", ")}.`;
|
|
41
|
+
if (blocked.stepIndex === undefined) {
|
|
42
|
+
return {
|
|
43
|
+
action: blocked,
|
|
44
|
+
command: blocked.command,
|
|
45
|
+
message: `Blocked likely final submit/order ${actionPhrase} (${target}) because the latest user prompt set an explicit stop boundary. Gather evidence on the current page instead of activating the final action. ${scopeNote}`,
|
|
46
|
+
reason: "explicit-user-stop-boundary",
|
|
47
|
+
target,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
action: blocked,
|
|
52
|
+
command: blocked.command,
|
|
53
|
+
message: `Blocked likely final submit/order ${actionPhrase} in batch step ${blocked.stepIndex + 1} (${target}) because the latest user prompt set an explicit stop boundary. Gather evidence on the current page instead of activating the final action. ${scopeNote}`,
|
|
54
|
+
reason: "explicit-user-stop-boundary",
|
|
55
|
+
stepIndex: blocked.stepIndex,
|
|
56
|
+
target,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveArtifactPath(cwd: string, path: string): string {
|
|
61
|
+
return isAbsolute(path) ? path : resolve(cwd, path);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function manifestContainsArtifact(manifest: SessionArtifactManifest | undefined, cwd: string, artifact: PromptRequestedArtifact): boolean {
|
|
65
|
+
if (!manifest) return false;
|
|
66
|
+
const requestedAbsolutePath = resolveArtifactPath(cwd, artifact.path);
|
|
67
|
+
const expectedKind = artifact.kind === "screenshot" ? "image" : "video";
|
|
68
|
+
return manifest.entries.some((entry) => {
|
|
69
|
+
const entryAbsolutePath = entry.absolutePath ?? resolveArtifactPath(cwd, entry.path);
|
|
70
|
+
return entry.storageScope === "explicit-path" && entry.kind === expectedKind && entryAbsolutePath === requestedAbsolutePath && entry.retentionState === "live" && entry.exists === true;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function isArtifactRequired(artifact: PromptRequestedArtifact): Promise<boolean> {
|
|
75
|
+
if (artifact.required) return true;
|
|
76
|
+
return artifact.kind === "recording" && await executableExistsOnPath("ffmpeg");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function findRequestedArtifactCloseViolation(options: { artifactManifest?: SessionArtifactManifest; command: string | undefined; cwd: string; promptPolicy: PromptPolicy }): Promise<RequestedArtifactCloseViolation | undefined> {
|
|
80
|
+
if (!isCloseCommand(options.command)) return undefined;
|
|
81
|
+
const missingArtifacts: PromptRequestedArtifact[] = [];
|
|
82
|
+
for (const artifact of options.promptPolicy.requestedArtifacts) {
|
|
83
|
+
if (!await isArtifactRequired(artifact)) continue;
|
|
84
|
+
if (!manifestContainsArtifact(options.artifactManifest, options.cwd, artifact)) missingArtifacts.push(artifact);
|
|
85
|
+
}
|
|
86
|
+
if (missingArtifacts.length === 0) return undefined;
|
|
87
|
+
const missingList = missingArtifacts.map((artifact) => `${artifact.kind}: ${artifact.path}`).join(", ");
|
|
88
|
+
return {
|
|
89
|
+
message: `Blocked browser close because requested artifact path${missingArtifacts.length === 1 ? " is" : "s are"} missing or unverified: ${missingList}. Save the requested artifact path first, or report why an optional artifact is unavailable before closing.`,
|
|
90
|
+
missingArtifacts,
|
|
91
|
+
reason: "requested-artifacts-missing-before-close",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -14,8 +14,18 @@ import {
|
|
|
14
14
|
type SessionRefSnapshotInvalidation,
|
|
15
15
|
type SessionTabTarget,
|
|
16
16
|
} from "../../session-page-state.js";
|
|
17
|
+
import {
|
|
18
|
+
isCloseCommand,
|
|
19
|
+
isElectronPostCommandHealthCommand,
|
|
20
|
+
isNavigationObservableCommandName,
|
|
21
|
+
isRefGuardedCommand,
|
|
22
|
+
isRefInvalidatingBatchCommand,
|
|
23
|
+
isSessionTabPinningExcludedCommand,
|
|
24
|
+
isSessionTabPostCommandCorrectionExcludedCommand,
|
|
25
|
+
} from "../../command-taxonomy.js";
|
|
17
26
|
import { chooseOpenResultTabCorrection, redactInvocationArgs, type OpenResultTabCorrection } from "../../runtime.js";
|
|
18
27
|
import { isRecord } from "../../parsing.js";
|
|
28
|
+
import { parseUserBatchStdin } from "../batch-stdin.js";
|
|
19
29
|
import type {
|
|
20
30
|
AboutBlankSessionMismatch,
|
|
21
31
|
BatchCommandStep,
|
|
@@ -35,65 +45,8 @@ import type {
|
|
|
35
45
|
TraceOwner,
|
|
36
46
|
} from "./types.js";
|
|
37
47
|
|
|
38
|
-
export const NAVIGATION_SUMMARY_COMMANDS = new Set(["back", "click", "dblclick", "forward", "reload"]);
|
|
39
48
|
export const NAVIGATION_SUMMARY_EVAL = `({ title: document.title, url: location.href })`;
|
|
40
49
|
|
|
41
|
-
const SESSION_TAB_PINNING_EXCLUDED_COMMANDS = new Set(["close", "goto", "navigate", "open", "session", "tab"]);
|
|
42
|
-
const SESSION_TAB_POST_COMMAND_CORRECTION_EXCLUDED_COMMANDS = new Set(["batch", "close", "session", "tab"]);
|
|
43
|
-
const REF_INVALIDATING_BATCH_COMMANDS = new Set([
|
|
44
|
-
"back",
|
|
45
|
-
"check",
|
|
46
|
-
"click",
|
|
47
|
-
"dblclick",
|
|
48
|
-
"drag",
|
|
49
|
-
"forward",
|
|
50
|
-
"goto",
|
|
51
|
-
"keyboard",
|
|
52
|
-
"mouse",
|
|
53
|
-
"navigate",
|
|
54
|
-
"open",
|
|
55
|
-
"press",
|
|
56
|
-
"reload",
|
|
57
|
-
"select",
|
|
58
|
-
"type",
|
|
59
|
-
"uncheck",
|
|
60
|
-
"upload",
|
|
61
|
-
]);
|
|
62
|
-
const REF_GUARDED_COMMANDS = new Set([
|
|
63
|
-
"check",
|
|
64
|
-
"click",
|
|
65
|
-
"dblclick",
|
|
66
|
-
"download",
|
|
67
|
-
"drag",
|
|
68
|
-
"fill",
|
|
69
|
-
"focus",
|
|
70
|
-
"hover",
|
|
71
|
-
"keyboard",
|
|
72
|
-
"mouse",
|
|
73
|
-
"press",
|
|
74
|
-
"scrollintoview",
|
|
75
|
-
"select",
|
|
76
|
-
"type",
|
|
77
|
-
"uncheck",
|
|
78
|
-
"upload",
|
|
79
|
-
]);
|
|
80
|
-
const ELECTRON_POST_COMMAND_HEALTH_COMMANDS = new Set([
|
|
81
|
-
"back",
|
|
82
|
-
"check",
|
|
83
|
-
"click",
|
|
84
|
-
"dblclick",
|
|
85
|
-
"fill",
|
|
86
|
-
"find",
|
|
87
|
-
"forward",
|
|
88
|
-
"keyboard",
|
|
89
|
-
"mouse",
|
|
90
|
-
"press",
|
|
91
|
-
"reload",
|
|
92
|
-
"select",
|
|
93
|
-
"type",
|
|
94
|
-
"uncheck",
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
50
|
export function applyBrowserRunStatePatch(state: BrowserRunState, patch: BrowserRunStatePatch | undefined): void {
|
|
98
51
|
if (!patch) return;
|
|
99
52
|
if ("artifactManifest" in patch) state.artifactManifest = patch.artifactManifest;
|
|
@@ -103,10 +56,6 @@ export function applyBrowserRunStatePatch(state: BrowserRunState, patch: Browser
|
|
|
103
56
|
if (patch.managedSessionName !== undefined) state.managedSessionName = patch.managedSessionName;
|
|
104
57
|
}
|
|
105
58
|
|
|
106
|
-
export function mergeBrowserRunStatePatch(left: BrowserRunStatePatch | undefined, right: BrowserRunStatePatch | undefined): BrowserRunStatePatch {
|
|
107
|
-
return { ...(left ?? {}), ...(right ?? {}) };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
59
|
export function buildSessionDetailFields(sessionName: string | undefined, usedImplicitSession: boolean): Record<string, unknown> {
|
|
111
60
|
return sessionName ? { sessionName, usedImplicitSession } : {};
|
|
112
61
|
}
|
|
@@ -126,7 +75,7 @@ export function buildManagedSessionOutcome(options: {
|
|
|
126
75
|
if (!attemptedSessionName) return undefined;
|
|
127
76
|
let status: ManagedSessionOutcome["status"];
|
|
128
77
|
let summary: string;
|
|
129
|
-
if (command
|
|
78
|
+
if (isCloseCommand(command)) {
|
|
130
79
|
status = succeeded ? "closed" : activeBefore ? "preserved" : "abandoned";
|
|
131
80
|
summary = succeeded
|
|
132
81
|
? `Managed session ${attemptedSessionName} was closed.`
|
|
@@ -238,8 +187,7 @@ export function extractNavigationSummaryFromData(data: unknown): NavigationSumma
|
|
|
238
187
|
|
|
239
188
|
export function shouldCaptureNavigationSummary(command: string | undefined, data: unknown): boolean {
|
|
240
189
|
return (
|
|
241
|
-
command
|
|
242
|
-
NAVIGATION_SUMMARY_COMMANDS.has(command) &&
|
|
190
|
+
isNavigationObservableCommandName(command) &&
|
|
243
191
|
(!isRecord(data) || (typeof data.title !== "string" && typeof data.url !== "string"))
|
|
244
192
|
);
|
|
245
193
|
}
|
|
@@ -263,58 +211,6 @@ function extractBatchResultCommand(item: Record<string, unknown>): string[] {
|
|
|
263
211
|
return Array.isArray(item.command) ? item.command.filter((token): token is string => typeof token === "string") : [];
|
|
264
212
|
}
|
|
265
213
|
|
|
266
|
-
function validateUserBatchStep(
|
|
267
|
-
step: unknown,
|
|
268
|
-
index: number,
|
|
269
|
-
):
|
|
270
|
-
| { ok: true; step: BatchCommandStep }
|
|
271
|
-
| { ok: false; error: string } {
|
|
272
|
-
if (!Array.isArray(step)) {
|
|
273
|
-
return {
|
|
274
|
-
ok: false,
|
|
275
|
-
error: `agent_browser batch stdin step ${index} must be a non-empty array of string command tokens.`,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
if (step.length === 0) {
|
|
279
|
-
return {
|
|
280
|
-
ok: false,
|
|
281
|
-
error: `agent_browser batch stdin step ${index} must not be empty.`,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
const invalidTokenIndex = step.findIndex((token) => typeof token !== "string");
|
|
285
|
-
if (invalidTokenIndex !== -1) {
|
|
286
|
-
return {
|
|
287
|
-
ok: false,
|
|
288
|
-
error: `agent_browser batch stdin step ${index} token ${invalidTokenIndex} must be a string.`,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
return { ok: true, step: step as BatchCommandStep };
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function parseUserBatchStdin(stdin: string | undefined): { error?: string; steps?: BatchCommandStep[] } {
|
|
295
|
-
if (stdin === undefined) {
|
|
296
|
-
return { steps: [] };
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
const parsed = JSON.parse(stdin) as unknown;
|
|
300
|
-
if (!Array.isArray(parsed)) {
|
|
301
|
-
return { error: "agent_browser batch stdin must be a JSON array of command steps." };
|
|
302
|
-
}
|
|
303
|
-
const steps: BatchCommandStep[] = [];
|
|
304
|
-
for (const [index, rawStep] of parsed.entries()) {
|
|
305
|
-
const validated = validateUserBatchStep(rawStep, index);
|
|
306
|
-
if (!validated.ok) {
|
|
307
|
-
return { error: validated.error };
|
|
308
|
-
}
|
|
309
|
-
steps.push(validated.step);
|
|
310
|
-
}
|
|
311
|
-
return { steps };
|
|
312
|
-
} catch (error) {
|
|
313
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
314
|
-
return { error: `agent_browser batch stdin could not be parsed as JSON: ${message}` };
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
214
|
export function getStaleRefArgs(commandTokens: string[], stdin?: string): string[] {
|
|
319
215
|
if (commandTokens[0] !== "batch" || stdin === undefined) {
|
|
320
216
|
return commandTokens;
|
|
@@ -331,7 +227,7 @@ function collectRefsFromTokens(tokens: string[]): string[] {
|
|
|
331
227
|
}
|
|
332
228
|
|
|
333
229
|
export function getGuardedRefUsage(commandTokens: string[], stdin?: string, options: { includeRefsAfterBatchSnapshot?: boolean } = {}): string[] {
|
|
334
|
-
const collectFromStep = (step: string[]) =>
|
|
230
|
+
const collectFromStep = (step: string[]) => isRefGuardedCommand(step[0]) ? collectRefsFromTokens(step) : [];
|
|
335
231
|
if (commandTokens[0] !== "batch" || stdin === undefined) {
|
|
336
232
|
return collectFromStep(commandTokens);
|
|
337
233
|
}
|
|
@@ -357,10 +253,10 @@ function getBatchRefInvalidationMessage(commandTokens: string[], stdin?: string)
|
|
|
357
253
|
priorStepInvalidatesRefs = false;
|
|
358
254
|
}
|
|
359
255
|
const refIds = collectRefsFromTokens(step);
|
|
360
|
-
if (refIds.length > 0 &&
|
|
256
|
+
if (refIds.length > 0 && isRefGuardedCommand(step[0]) && priorStepInvalidatesRefs) {
|
|
361
257
|
return `Batch step ${step[0]} uses page-scoped ref ${refIds.map((refId) => `@${refId}`).join(", ")} after an earlier batch step can navigate or mutate the page. Split the batch, run snapshot -i after the page-changing step, then retry with current refs.`;
|
|
362
258
|
}
|
|
363
|
-
if (
|
|
259
|
+
if (isRefInvalidatingBatchCommand(step[0])) {
|
|
364
260
|
priorStepInvalidatesRefs = true;
|
|
365
261
|
}
|
|
366
262
|
}
|
|
@@ -438,7 +334,7 @@ export function shouldPinSessionTabForCommand(options: {
|
|
|
438
334
|
options.pinningRequired === true &&
|
|
439
335
|
options.sessionName !== undefined &&
|
|
440
336
|
options.command !== undefined &&
|
|
441
|
-
!
|
|
337
|
+
!isSessionTabPinningExcludedCommand(options.command) &&
|
|
442
338
|
supportsPinnedStdinCommand(options)
|
|
443
339
|
);
|
|
444
340
|
}
|
|
@@ -464,7 +360,7 @@ export function buildPinnedBatchPlan(options: {
|
|
|
464
360
|
if (options.commandTokens.length === 0) {
|
|
465
361
|
return undefined;
|
|
466
362
|
}
|
|
467
|
-
const includeNavigationSummary =
|
|
363
|
+
const includeNavigationSummary = isNavigationObservableCommandName(options.command);
|
|
468
364
|
const tabSelectionStep: BatchCommandStep = ["tab", options.selectedTab];
|
|
469
365
|
const commandStep = options.commandTokens as BatchCommandStep;
|
|
470
366
|
const navigationSummarySteps: BatchCommandStep[] = includeNavigationSummary ? [["eval", NAVIGATION_SUMMARY_EVAL]] : [];
|
|
@@ -480,7 +376,7 @@ export function shouldCorrectSessionTabAfterCommand(options: { command?: string;
|
|
|
480
376
|
options.pinningRequired === true &&
|
|
481
377
|
options.sessionName !== undefined &&
|
|
482
378
|
options.command !== undefined &&
|
|
483
|
-
!
|
|
379
|
+
!isSessionTabPostCommandCorrectionExcludedCommand(options.command)
|
|
484
380
|
);
|
|
485
381
|
}
|
|
486
382
|
|
|
@@ -747,7 +643,7 @@ export function formatElectronSessionMismatchText(mismatch: ElectronSessionMisma
|
|
|
747
643
|
}
|
|
748
644
|
|
|
749
645
|
export function shouldInspectElectronPostCommandHealth(command: string | undefined): boolean {
|
|
750
|
-
return
|
|
646
|
+
return isElectronPostCommandHealthCommand(command);
|
|
751
647
|
}
|
|
752
648
|
|
|
753
649
|
export function buildElectronLifecycleNextActions(record: ElectronLaunchRecord): AgentBrowserNextAction[] {
|
|
@@ -20,7 +20,9 @@ import type { SessionArtifactManifest } from "../../results/contracts.js";
|
|
|
20
20
|
import type { RichInputRecoveryDiagnostic, VisibleRefFallbackDiagnostic } from "../../results/selector-recovery.js";
|
|
21
21
|
import type { SessionPageState, SessionRefSnapshot, SessionRefSnapshotInvalidation, SessionTabTarget } from "../../session-page-state.js";
|
|
22
22
|
import type { buildExecutionPlan, CompatibilityWorkaround, OpenResultTabCorrection } from "../../runtime.js";
|
|
23
|
+
import type { PromptPolicy } from "../../prompt-policy.js";
|
|
23
24
|
import type { AgentBrowserExecuteParams, ResolvedAgentBrowserValidInput } from "../input-plan.js";
|
|
25
|
+
import type { BatchCommandStep } from "../batch-stdin.js";
|
|
24
26
|
|
|
25
27
|
export type AgentBrowserToolResult = AgentToolResult<unknown> & { isError?: boolean };
|
|
26
28
|
export type AgentBrowserProcessResult = Awaited<ReturnType<typeof runAgentBrowserProcess>>;
|
|
@@ -30,7 +32,7 @@ export type AgentBrowserResultCategoryDetails = ReturnType<typeof buildAgentBrow
|
|
|
30
32
|
|
|
31
33
|
export type TraceOwner = "profiler" | "trace";
|
|
32
34
|
export type PinnedBatchUnwrapMode = "single-command" | "user-batch";
|
|
33
|
-
export type BatchCommandStep
|
|
35
|
+
export type { BatchCommandStep } from "../batch-stdin.js";
|
|
34
36
|
|
|
35
37
|
export interface BrowserRunContext {
|
|
36
38
|
cwd: string;
|
|
@@ -61,6 +63,7 @@ export interface BrowserRunInputFields {
|
|
|
61
63
|
|
|
62
64
|
export interface BrowserRunState {
|
|
63
65
|
artifactManifest?: SessionArtifactManifest;
|
|
66
|
+
closedManagedSessionNames: Set<string>;
|
|
64
67
|
electronChildProcesses: Map<string, ChildProcess>;
|
|
65
68
|
electronLaunchRecords: Map<string, ElectronLaunchRecord>;
|
|
66
69
|
ephemeralSessionSeed: string;
|
|
@@ -91,6 +94,7 @@ export interface BrowserRunOptions {
|
|
|
91
94
|
input: ResolvedAgentBrowserValidInput;
|
|
92
95
|
onUpdate?: (result: AgentToolResult<unknown>) => void;
|
|
93
96
|
params: AgentBrowserExecuteParams;
|
|
97
|
+
promptPolicy: PromptPolicy;
|
|
94
98
|
sessionPageStateUpdate: ReturnType<SessionPageState["beginUpdate"]>;
|
|
95
99
|
signal?: AbortSignal;
|
|
96
100
|
state: BrowserRunState;
|
|
@@ -120,6 +124,24 @@ export interface OverlayBlockerDiagnostic {
|
|
|
120
124
|
summary: string;
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
export interface ClickDispatchProbeTarget {
|
|
128
|
+
kind: "selector" | "xpath";
|
|
129
|
+
selector: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ClickDispatchProbe {
|
|
133
|
+
marker: string;
|
|
134
|
+
target: ClickDispatchProbeTarget;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface ClickDispatchDiagnostic {
|
|
138
|
+
nativeEventCount: number;
|
|
139
|
+
reason: "native-click-produced-no-target-dom-event";
|
|
140
|
+
status: "no-native-event-observed";
|
|
141
|
+
summary: string;
|
|
142
|
+
target: ClickDispatchProbeTarget;
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
export interface SelectorTextVisibilityDiagnostic {
|
|
124
146
|
firstMatchVisible?: boolean;
|
|
125
147
|
firstVisibleTextPreview?: string;
|
|
@@ -140,11 +162,17 @@ export interface ElectronBroadGetTextScopeDiagnostic {
|
|
|
140
162
|
}
|
|
141
163
|
|
|
142
164
|
export interface QaAttachedTarget {
|
|
165
|
+
error?: string;
|
|
143
166
|
sessionName: string;
|
|
144
167
|
title?: string;
|
|
145
168
|
url?: string;
|
|
146
169
|
}
|
|
147
170
|
|
|
171
|
+
export interface QaAttachedPreconditionFailure {
|
|
172
|
+
error: string;
|
|
173
|
+
nextActions: AgentBrowserNextAction[];
|
|
174
|
+
}
|
|
175
|
+
|
|
148
176
|
export interface TimeoutArtifactEvidence {
|
|
149
177
|
absolutePath: string;
|
|
150
178
|
exists: boolean;
|
|
@@ -347,6 +375,7 @@ export interface PreparedBrowserRun {
|
|
|
347
375
|
exactSensitiveValues: string[];
|
|
348
376
|
executionPlan: AgentBrowserExecutionPlan;
|
|
349
377
|
includePinnedNavigationSummary: boolean;
|
|
378
|
+
clickDispatchProbe?: ClickDispatchProbe;
|
|
350
379
|
pinnedBatchUnwrapMode?: PinnedBatchUnwrapMode;
|
|
351
380
|
preparedArgs: PreparedAgentBrowserArgs;
|
|
352
381
|
priorRefSnapshotState?: SessionRefSnapshot;
|
|
@@ -411,6 +440,8 @@ export interface FinalResultInput {
|
|
|
411
440
|
aboutBlankSessionMismatch?: AboutBlankSessionMismatch;
|
|
412
441
|
artifactCleanup?: ArtifactCleanupGuidance;
|
|
413
442
|
categoryDetails: AgentBrowserResultCategoryDetails;
|
|
443
|
+
clickDispatchDiagnostic?: ClickDispatchDiagnostic;
|
|
444
|
+
commandTokens: string[];
|
|
414
445
|
comboboxFocusDiagnostic?: ComboboxFocusDiagnostic;
|
|
415
446
|
compiledNetworkSourceLookup?: CompiledAgentBrowserNetworkSourceLookup;
|
|
416
447
|
compiledSemanticAction?: CompiledAgentBrowserSemanticAction;
|