pi-agent-browser-native 0.2.34 → 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 +27 -0
- package/README.md +14 -14
- package/docs/ARCHITECTURE.md +19 -13
- package/docs/COMMAND_REFERENCE.md +257 -42
- package/docs/ELECTRON.md +3 -3
- package/docs/RELEASE.md +11 -11
- package/docs/REQUIREMENTS.md +5 -5
- package/docs/SUPPORT_MATRIX.md +23 -21
- package/docs/TOOL_CONTRACT.md +38 -27
- package/extensions/agent-browser/index.ts +518 -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/params.ts +6 -6
- 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 +10 -28
- 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 +48 -22
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +33 -10
- 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 +26 -1
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +860 -0
- package/extensions/agent-browser/lib/playbook.ts +9 -9
- 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/presentation/navigation.ts +2 -34
- 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
|
@@ -26,8 +26,11 @@ import {
|
|
|
26
26
|
runSessionCommandData,
|
|
27
27
|
shouldPinSessionTabForCommand,
|
|
28
28
|
} from "./session-state.js";
|
|
29
|
+
import { parseBatchStdinJsonArray, parseValidBatchStepEntries } from "../batch-stdin.js";
|
|
29
30
|
import { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
|
|
31
|
+
import { prepareClickDispatchProbe } from "./click-dispatch.js";
|
|
30
32
|
import { collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
|
|
33
|
+
import { findRequestedArtifactCloseViolation, findStopBoundaryViolation } from "./prompt-guards.js";
|
|
31
34
|
import type {
|
|
32
35
|
BrowserRunInputFields,
|
|
33
36
|
BrowserRunOptions,
|
|
@@ -141,19 +144,14 @@ async function prepareBatchScreenshotPaths(args: string[], stdin: string | undef
|
|
|
141
144
|
if (commandTokens[0] !== "batch" || stdin === undefined) {
|
|
142
145
|
return undefined;
|
|
143
146
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
steps = JSON.parse(stdin);
|
|
147
|
-
} catch {
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
if (!Array.isArray(steps)) {
|
|
147
|
+
const parsed = parseBatchStdinJsonArray(stdin);
|
|
148
|
+
if (parsed.error || parsed.steps === undefined) {
|
|
151
149
|
return undefined;
|
|
152
150
|
}
|
|
153
151
|
|
|
154
152
|
let changed = false;
|
|
155
153
|
const batchScreenshotPathRequests: Array<ScreenshotPathRequest | undefined> = [];
|
|
156
|
-
const preparedSteps = await Promise.all(steps.map(async (step, index) => {
|
|
154
|
+
const preparedSteps = await Promise.all(parsed.steps.map(async (step, index) => {
|
|
157
155
|
if (!Array.isArray(step) || !step.every((item) => typeof item === "string") || step[0] !== "screenshot") {
|
|
158
156
|
return step;
|
|
159
157
|
}
|
|
@@ -282,20 +280,7 @@ export function validateWaitIpcTimeoutContract(commandTokens: string[], stdin: s
|
|
|
282
280
|
if (commandTokens[0] !== "batch" || stdin === undefined) {
|
|
283
281
|
return undefined;
|
|
284
282
|
}
|
|
285
|
-
|
|
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
|
-
}
|
|
283
|
+
for (const { index, step } of parseValidBatchStepEntries(stdin)) {
|
|
299
284
|
const waitTimeout = findWaitTimeoutMs(step);
|
|
300
285
|
if (waitTimeout && waitTimeout.timeoutMs > SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS) {
|
|
301
286
|
return buildIpcUnsafeWaitError(waitTimeout.source, waitTimeout.timeoutMs, index);
|
|
@@ -528,6 +513,43 @@ export async function prepareBrowserRun(options: BrowserRunOptions): Promise<Pre
|
|
|
528
513
|
const resolvedSemanticActionRefSnapshot: SessionRefSnapshot | undefined = semanticActionVisibleRefResolution?.snapshot
|
|
529
514
|
? { ...semanticActionVisibleRefResolution.snapshot, target: semanticActionVisibleRefResolution.snapshot.target ?? priorSessionTabTarget }
|
|
530
515
|
: undefined;
|
|
516
|
+
const promptRefSnapshot = resolvedSemanticActionRefSnapshot ?? priorRefSnapshotState;
|
|
517
|
+
const stopBoundaryViolation = findStopBoundaryViolation({ commandTokens, promptPolicy: options.promptPolicy, refSnapshot: promptRefSnapshot, stdin: runtimeToolStdin });
|
|
518
|
+
if (stopBoundaryViolation) {
|
|
519
|
+
return { kind: "early-result", statePatch, result: {
|
|
520
|
+
content: [{ type: "text", text: stopBoundaryViolation.message }],
|
|
521
|
+
details: {
|
|
522
|
+
args: redactedArgs,
|
|
523
|
+
command: executionPlan.commandInfo.command,
|
|
524
|
+
compatibilityWorkaround,
|
|
525
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
526
|
+
promptGuard: stopBoundaryViolation,
|
|
527
|
+
sessionMode,
|
|
528
|
+
...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: stopBoundaryViolation.message, failureCategory: "policy-blocked", succeeded: false, validationError: stopBoundaryViolation.message }),
|
|
529
|
+
validationError: stopBoundaryViolation.message,
|
|
530
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
531
|
+
},
|
|
532
|
+
isError: true,
|
|
533
|
+
} };
|
|
534
|
+
}
|
|
535
|
+
const requestedArtifactCloseViolation = await findRequestedArtifactCloseViolation({ artifactManifest: state.artifactManifest, command: executionPlan.commandInfo.command, cwd, promptPolicy: options.promptPolicy });
|
|
536
|
+
if (requestedArtifactCloseViolation) {
|
|
537
|
+
return { kind: "early-result", statePatch, result: {
|
|
538
|
+
content: [{ type: "text", text: requestedArtifactCloseViolation.message }],
|
|
539
|
+
details: {
|
|
540
|
+
args: redactedArgs,
|
|
541
|
+
command: executionPlan.commandInfo.command,
|
|
542
|
+
compatibilityWorkaround,
|
|
543
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
544
|
+
promptGuard: requestedArtifactCloseViolation,
|
|
545
|
+
sessionMode,
|
|
546
|
+
...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: requestedArtifactCloseViolation.message, failureCategory: "policy-blocked", succeeded: false, validationError: requestedArtifactCloseViolation.message }),
|
|
547
|
+
validationError: requestedArtifactCloseViolation.message,
|
|
548
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
549
|
+
},
|
|
550
|
+
isError: true,
|
|
551
|
+
} };
|
|
552
|
+
}
|
|
531
553
|
const staleRefPreflight = buildStaleRefPreflight({
|
|
532
554
|
commandTokens,
|
|
533
555
|
currentTarget: priorSessionTabTarget,
|
|
@@ -676,6 +698,9 @@ export async function prepareBrowserRun(options: BrowserRunOptions): Promise<Pre
|
|
|
676
698
|
}
|
|
677
699
|
}
|
|
678
700
|
}
|
|
701
|
+
const clickDispatchProbe = pinnedBatchUnwrapMode === undefined && compiledElectron === undefined
|
|
702
|
+
? await prepareClickDispatchProbe({ commandTokens, cwd, sessionName: executionPlan.sessionName, signal })
|
|
703
|
+
: undefined;
|
|
679
704
|
const redactedProcessArgs = redactInvocationArgs(processArgs);
|
|
680
705
|
const shouldProbeScrollNoop = executionPlan.commandInfo.command === "scroll" && executionPlan.startupScopedFlags.length === 0;
|
|
681
706
|
const scrollPositionBefore = shouldProbeScrollNoop
|
|
@@ -702,6 +727,7 @@ export async function prepareBrowserRun(options: BrowserRunOptions): Promise<Pre
|
|
|
702
727
|
compiledSemanticAction,
|
|
703
728
|
compiledSourceLookup,
|
|
704
729
|
compatibilityWorkaround,
|
|
730
|
+
clickDispatchProbe,
|
|
705
731
|
electronLaunch,
|
|
706
732
|
exactSensitiveValues,
|
|
707
733
|
executionPlan,
|
|
@@ -1,5 +1,6 @@
|
|
|
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 {
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
import type { PersistentSessionArtifactEviction, PersistentSessionArtifactStore } from "../../temp.js";
|
|
39
40
|
import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../../temp.js";
|
|
40
41
|
import { isRecord } from "../../parsing.js";
|
|
41
|
-
import { hasLaunchScopedTabCorrectionFlag, resolveManagedSessionState } from "../../runtime.js";
|
|
42
|
+
import { createFreshSessionName, hasLaunchScopedTabCorrectionFlag, resolveManagedSessionState } from "../../runtime.js";
|
|
42
43
|
import {
|
|
43
44
|
applyOpenResultTabCorrection,
|
|
44
45
|
buildAboutBlankRecoveryHint,
|
|
@@ -62,6 +63,7 @@ import {
|
|
|
62
63
|
unwrapPinnedSessionBatchEnvelope,
|
|
63
64
|
updateTraceOwnerState,
|
|
64
65
|
} from "./session-state.js";
|
|
66
|
+
import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
|
|
65
67
|
import {
|
|
66
68
|
buildScrollNoopDiagnostic,
|
|
67
69
|
collectComboboxFocusDiagnostic,
|
|
@@ -177,6 +179,7 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
177
179
|
const { prepared, processResult } = input;
|
|
178
180
|
const { electronChildProcesses, electronLaunchRecords, sessionPageState, traceOwners } = state;
|
|
179
181
|
let artifactManifest = state.artifactManifest;
|
|
182
|
+
let freshSessionOrdinal = state.freshSessionOrdinal;
|
|
180
183
|
let managedSessionActive = state.managedSessionActive;
|
|
181
184
|
let managedSessionCwd = state.managedSessionCwd;
|
|
182
185
|
let managedSessionName = state.managedSessionName;
|
|
@@ -208,6 +211,15 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
208
211
|
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
209
212
|
updateTraceOwnerState({ command: prepared.executionPlan.commandInfo.command, sessionName: prepared.executionPlan.sessionName, subcommand: prepared.executionPlan.commandInfo.subcommand, succeeded, traceOwners });
|
|
210
213
|
|
|
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
|
+
|
|
211
223
|
if (
|
|
212
224
|
succeeded &&
|
|
213
225
|
!navigationSummary &&
|
|
@@ -220,7 +232,7 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
220
232
|
let overlayBlockerDiagnostic: Awaited<ReturnType<typeof collectOverlayBlockerDiagnostic>>;
|
|
221
233
|
|
|
222
234
|
let openResultTabCorrection: Awaited<ReturnType<typeof collectOpenResultTabCorrection>>;
|
|
223
|
-
if (succeeded && prepared.executionPlan.sessionName && hasLaunchScopedTabCorrectionFlag(prepared.runtimeToolArgs) &&
|
|
235
|
+
if (succeeded && prepared.executionPlan.sessionName && hasLaunchScopedTabCorrectionFlag(prepared.runtimeToolArgs) && isOpenNavigationCommand(prepared.executionPlan.commandInfo.command)) {
|
|
224
236
|
const targetTitle = extractStringResultField(presentationEnvelope?.data, "title");
|
|
225
237
|
const targetUrl = extractStringResultField(presentationEnvelope?.data, "url");
|
|
226
238
|
const plannedTabCorrection = await collectOpenResultTabCorrection({ cwd, sessionName: prepared.executionPlan.sessionName, signal, targetTitle, targetUrl });
|
|
@@ -275,7 +287,7 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
275
287
|
fillVerificationDiagnostic = await collectFillVerificationDiagnostic({ commandTokens: prepared.commandTokens, cwd, sessionName: prepared.executionPlan.sessionName, signal });
|
|
276
288
|
electronRefFreshnessDiagnostic = buildElectronRefFreshnessDiagnostic({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, record: electronRecordForCommand, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin });
|
|
277
289
|
}
|
|
278
|
-
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 });
|
|
279
291
|
if (succeeded) {
|
|
280
292
|
selectorTextVisibilityDiagnostics = await collectSelectorTextVisibilityDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, cwd, data: presentationEnvelope?.data, sessionName: prepared.executionPlan.sessionName, signal });
|
|
281
293
|
electronBroadGetTextScopeDiagnostics = collectElectronBroadGetTextScopeDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, currentTarget: currentSessionTabTarget, data: presentationEnvelope?.data, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName });
|
|
@@ -287,8 +299,10 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
287
299
|
let currentRefSnapshotInvalidation: SessionRefSnapshotInvalidation | undefined;
|
|
288
300
|
const batchRefSnapshotState = prepared.executionPlan.commandInfo.command === "batch" ? extractLatestRefSnapshotStateFromBatchResults(presentationEnvelope?.data) : undefined;
|
|
289
301
|
if (prepared.executionPlan.sessionName) {
|
|
290
|
-
if (prepared.executionPlan.commandInfo.command
|
|
291
|
-
|
|
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) {
|
|
292
306
|
const tabUpdate = sessionPageState.applyTabTarget({ sessionName: prepared.executionPlan.sessionName, target: currentSessionTabTarget, update: sessionPageStateUpdate });
|
|
293
307
|
if (!tabUpdate.applied && succeeded) sessionPageState.markPinning(prepared.executionPlan.sessionName, "drift");
|
|
294
308
|
}
|
|
@@ -307,11 +321,19 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
307
321
|
const priorManagedSessionActive = managedSessionActive;
|
|
308
322
|
const priorManagedSessionCwd = managedSessionCwd;
|
|
309
323
|
const priorManagedSessionName = managedSessionName;
|
|
310
|
-
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 });
|
|
311
329
|
const replacedManagedSessionName = managedSessionState.replacedSessionName;
|
|
312
330
|
managedSessionActive = managedSessionState.active;
|
|
313
331
|
managedSessionName = managedSessionState.sessionName;
|
|
314
|
-
|
|
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 });
|
|
315
337
|
if (prepared.executionPlan.managedSessionName && succeeded) managedSessionCwd = cwd;
|
|
316
338
|
if (prepared.executionPlan.sessionName && succeeded) {
|
|
317
339
|
if (openResultTabCorrection || sessionTabCorrection || aboutBlankSessionMismatch?.recoveryApplied) sessionPageState.markPinning(prepared.executionPlan.sessionName, "drift");
|
|
@@ -319,7 +341,8 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
319
341
|
}
|
|
320
342
|
if (replacedManagedSessionName) {
|
|
321
343
|
sessionPageState.clearSession(replacedManagedSessionName);
|
|
322
|
-
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);
|
|
323
346
|
}
|
|
324
347
|
|
|
325
348
|
let electronLaunchRecord: ElectronLaunchRecord | undefined;
|
|
@@ -404,8 +427,8 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
404
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 });
|
|
405
428
|
currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
|
|
406
429
|
currentRefSnapshotInvalidation = finalRecoveryState.currentRefSnapshotInvalidation;
|
|
407
|
-
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 });
|
|
408
|
-
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 };
|
|
409
432
|
return { result, statePatch };
|
|
410
433
|
} finally {
|
|
411
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;
|
|
@@ -353,6 +375,7 @@ export interface PreparedBrowserRun {
|
|
|
353
375
|
exactSensitiveValues: string[];
|
|
354
376
|
executionPlan: AgentBrowserExecutionPlan;
|
|
355
377
|
includePinnedNavigationSummary: boolean;
|
|
378
|
+
clickDispatchProbe?: ClickDispatchProbe;
|
|
356
379
|
pinnedBatchUnwrapMode?: PinnedBatchUnwrapMode;
|
|
357
380
|
preparedArgs: PreparedAgentBrowserArgs;
|
|
358
381
|
priorRefSnapshotState?: SessionRefSnapshot;
|
|
@@ -417,6 +440,8 @@ export interface FinalResultInput {
|
|
|
417
440
|
aboutBlankSessionMismatch?: AboutBlankSessionMismatch;
|
|
418
441
|
artifactCleanup?: ArtifactCleanupGuidance;
|
|
419
442
|
categoryDetails: AgentBrowserResultCategoryDetails;
|
|
443
|
+
clickDispatchDiagnostic?: ClickDispatchDiagnostic;
|
|
444
|
+
commandTokens: string[];
|
|
420
445
|
comboboxFocusDiagnostic?: ComboboxFocusDiagnostic;
|
|
421
446
|
compiledNetworkSourceLookup?: CompiledAgentBrowserNetworkSourceLookup;
|
|
422
447
|
compiledSemanticAction?: CompiledAgentBrowserSemanticAction;
|