pi-agent-browser-native 0.6.16 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/README.md +59 -165
  3. package/dist/extensions/agent-browser/index.js +634 -501
  4. package/dist/extensions/agent-browser/lib/browser-transcript.js +33 -0
  5. package/dist/extensions/agent-browser/lib/command-taxonomy.js +8 -4
  6. package/dist/extensions/agent-browser/lib/execution-cwd.js +66 -0
  7. package/dist/extensions/agent-browser/lib/input-modes/job.js +2 -213
  8. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +1 -1
  9. package/dist/extensions/agent-browser/lib/input-modes/params.js +146 -142
  10. package/dist/extensions/agent-browser/lib/input-modes/script.js +64 -92
  11. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +170 -24
  12. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +27 -20
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +14 -2
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +26 -13
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +21 -1
  16. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -2
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +1 -3
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +41 -24
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +3 -1
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +79 -0
  21. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +3 -4
  22. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +19 -52
  23. package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +3 -3
  24. package/dist/extensions/agent-browser/lib/orchestration/operation-paths.js +76 -0
  25. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +3 -3
  26. package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +91 -278
  27. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +9 -9
  28. package/dist/extensions/agent-browser/lib/playbook.js +46 -25
  29. package/dist/extensions/agent-browser/lib/process-identity.js +22 -6
  30. package/dist/extensions/agent-browser/lib/process.js +16 -13
  31. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +2 -2
  32. package/dist/extensions/agent-browser/lib/results/next-actions.js +1 -2
  33. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +25 -4
  34. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +6 -2
  35. package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -0
  36. package/dist/extensions/agent-browser/lib/results/presentation/content.js +39 -0
  37. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +3 -3
  38. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +41 -1
  39. package/dist/extensions/agent-browser/lib/results/presentation.js +16 -11
  40. package/dist/extensions/agent-browser/lib/runtime.js +18 -6
  41. package/dist/extensions/agent-browser/lib/session-page-state.js +3 -4
  42. package/dist/extensions/agent-browser/lib/tool-surface.js +131 -0
  43. package/dist/extensions/agent-browser/script-worker.js +4 -1
  44. package/docs/ARCHITECTURE.md +47 -61
  45. package/docs/COMMAND_REFERENCE.md +78 -137
  46. package/docs/ELECTRON.md +51 -49
  47. package/docs/RELEASE.md +31 -15
  48. package/docs/REQUIREMENTS.md +18 -14
  49. package/docs/SUPPORT_MATRIX.md +33 -15
  50. package/docs/TOOL_CONTRACT.md +218 -401
  51. package/package.json +4 -4
  52. package/scripts/doctor.mjs +2 -2
@@ -4,7 +4,7 @@ import { parseArgvDescriptor } from "../../argv-descriptor.js";
4
4
  import { isBrowserIndependentRead, needsManagedSession } from "../../command-policy.js";
5
5
  import { deleteIdentityKeysInNamespace, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "../../argv-grammar.js";
6
6
  import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "../../batch-lifecycle.js";
7
- import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWindowOrDiffPageTransitionCommand } from "../../command-taxonomy.js";
7
+ import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand, isPageMutationCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWindowOrDiffPageTransitionCommand } from "../../command-taxonomy.js";
8
8
  import { OPEN_RESULT_TAB_CORRECTION_FLAGS } from "../../launch-scoped-flags.js";
9
9
  import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
10
10
  import { getResultingPageTargetState, commandRequiresLivePageVerification } from "../../page-target-validation.js";
@@ -147,6 +147,7 @@ function applyBatchNetworkRouteState(options) {
147
147
  }
148
148
  export async function processBrowserOutput(input) {
149
149
  const { ctx, cwd, electronPostCommandStatusSettleMs, implicitSessionCloseTimeoutMs, sessionPageStateUpdate, signal, state } = input;
150
+ const operationCwd = input.operationCwd ?? cwd;
150
151
  const { prepared, processResult } = input;
151
152
  const { electronChildProcesses, electronLaunchRecords, sessionPageState, traceOwners } = state;
152
153
  let artifactManifest = state.artifactManifest;
@@ -167,6 +168,7 @@ export async function processBrowserOutput(input) {
167
168
  observeNativeWebMcp(parsed.envelope?.data);
168
169
  let parseError = parsed.parseError;
169
170
  const recordingStopRecovery = await recoverRecordingStop({
171
+ modelVisible: input.modelVisible,
170
172
  artifactManifest, artifactRunStartedAtMs: input.artifactRunStartedAtMs, commandTokens: prepared.commandTokens, cwd,
171
173
  envelope: parsed.envelope, namespace: prepared.executionPlan.namespace, parseError, processResult,
172
174
  reservation: prepared.executionPlan.sessionName ? state.activeRecordingReservations?.get(getAgentBrowserSessionIdentityKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace)) : undefined,
@@ -240,6 +242,12 @@ export async function processBrowserOutput(input) {
240
242
  const envelopeSuccess = plainTextInspection ? true : presentationEnvelope?.success !== false;
241
243
  let succeeded = (processSucceeded && parseSucceeded && envelopeSuccess) || recordingStopRecovery?.recovery.healed === true;
242
244
  const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
245
+ const unobservedMutation = processResult.agentBrowserStarted && !plainTextInspection
246
+ && (processResult.aborted || processResult.timedOut || !parseSucceeded || presentationEnvelope === undefined)
247
+ && (prepared.executionPlan.commandInfo.command === "batch" ? batchCommandSteps : [prepared.commandTokens]).some(step => {
248
+ const action = step[0] === "find" ? step[step[1] === "nth" ? 4 : 3] ?? "click" : step[0];
249
+ return isPageMutationCommand(action, step[1]);
250
+ });
243
251
  const sessionStateKey = getSessionContextKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace);
244
252
  const closeAllApplied = nestedBatchClosesAll || (directCloseAllRequested && succeeded);
245
253
  if (sessionStateKey && processResult.agentBrowserStarted && sessionPageState.get(sessionStateKey).tabReopenPending === true) {
@@ -334,14 +342,14 @@ export async function processBrowserOutput(input) {
334
342
  const failedWebMcpSettlement = prepared.priorSessionTabTargetUnknown === true && ((!succeeded && isWebMcpSettlementCommand(prepared.executionPlan.commandInfo.command, prepared.executionPlan.commandInfo.subcommand))
335
343
  || (prepared.executionPlan.commandInfo.command === "batch" && batchHasFailedWebMcpSettlement(presentationEnvelope?.data)));
336
344
  const unsettledWebMcpMutation = pendingWebMcpMutation || failedWebMcpSettlement;
337
- const observedSessionTabTarget = unsettledWebMcpMutation
345
+ const observedSessionTabTarget = unsettledWebMcpMutation || (unobservedMutation && !failedTransitionReverification)
338
346
  ? undefined
339
347
  : normalizeSessionTabTarget(navigationSummary)
340
348
  ?? (trustsReportedPageTarget ? extractSessionTabTargetFromBatchResults(presentationEnvelope?.data) : undefined)
341
349
  ?? (succeeded && trustsReportedPageTarget ? extractSessionTabTargetFromCommandData(prepared.commandTokens, presentationEnvelope?.data) : undefined);
342
350
  const safeObservedSessionTabTarget = observedSessionTabTarget;
343
351
  let currentSessionTabTarget = safeObservedSessionTabTarget;
344
- if (!currentSessionTabTarget && nestedBatchClose === undefined) {
352
+ if (!currentSessionTabTarget && nestedBatchClose === undefined && !unobservedMutation) {
345
353
  // Window/diff responses do not report the final URL; URL2 is intent, not redirect evidence.
346
354
  currentSessionTabTarget = resultingPageState.pageTargetMayHaveChanged
347
355
  ? succeeded && !destinationTransition ? normalizeSessionTabTarget({ url: resultingPageState.currentPageUrl }) : undefined
@@ -395,8 +403,8 @@ export async function processBrowserOutput(input) {
395
403
  let fillVerificationDiagnostic;
396
404
  let selectorTextVisibilityDiagnostics = [];
397
405
  let electronBroadGetTextScopeDiagnostics = [];
398
- const timeoutPartialProgress = processResult.timedOut && !recordingStopRecovery && !prepared.readConfirmation ? await collectTimeoutPartialProgress({ commandTokens: prepared.commandTokens, compiledJob: prepared.compiledJob, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin }) : undefined;
399
- if (!currentSessionTabTarget && timeoutPartialProgress?.currentPage?.source === "live") {
406
+ const timeoutPartialProgress = processResult.timedOut && !recordingStopRecovery && !prepared.readConfirmation ? await collectTimeoutPartialProgress({ commandTokens: prepared.commandTokens, compiledJob: prepared.compiledJob, cwd, operationCwd: input.operationCwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin }) : undefined;
407
+ if (!currentSessionTabTarget && timeoutPartialProgress?.liveUrlRecovered === true) {
400
408
  currentSessionTabTarget = normalizeSessionTabTarget(timeoutPartialProgress.currentPage);
401
409
  }
402
410
  if (succeeded) {
@@ -444,24 +452,26 @@ export async function processBrowserOutput(input) {
444
452
  const plannedBatchTransitionInvalidation = !Array.isArray(presentationEnvelope?.data)
445
453
  ? batchCommandSteps.map(getCommandRefSnapshotInvalidation).find((invalidation) => invalidation !== undefined)
446
454
  : undefined;
447
- const pageTransitionInvalidation = unsettledWebMcpMutation
448
- ? buildPageTransitionRefSnapshotInvalidation("A detached WebMCP invocation is still pending or failed to settle and can mutate, rerender, or navigate the page, so prior snapshot refs remain invalid after URL verification. Run webmcp result or cancel, then take a fresh snapshot before using page-scoped refs.")
449
- : processResult.agentBrowserStarted && (directTransitionInvalidation || plannedBatchTransitionInvalidation)
450
- ? directTransitionInvalidation ?? plannedBatchTransitionInvalidation
451
- : failedTransitionReverification
452
- ? buildPageTransitionRefSnapshotInvalidation("A failed eval/back/forward/reload/connect/state-load/tab command may still have changed the page, so the prior snapshot refs were invalidated. Run snapshot -i before using page-scoped refs.")
453
- : batchRefSnapshotState?.invalidation?.reason === "page-transition"
454
- ? batchRefSnapshotState.invalidation
455
- : undefined;
455
+ const pageTransitionInvalidation = unobservedMutation && !failedTransitionReverification
456
+ ? buildPageTransitionRefSnapshotInvalidation("A dispatched mutation was interrupted without a final outcome. Verify the current URL and take a fresh snapshot before deciding whether to retry.")
457
+ : unsettledWebMcpMutation
458
+ ? buildPageTransitionRefSnapshotInvalidation("A detached WebMCP invocation is still pending or failed to settle and can mutate, rerender, or navigate the page, so prior snapshot refs remain invalid after URL verification. Run webmcp result or cancel, then take a fresh snapshot before using page-scoped refs.")
459
+ : processResult.agentBrowserStarted && (directTransitionInvalidation || plannedBatchTransitionInvalidation)
460
+ ? directTransitionInvalidation ?? plannedBatchTransitionInvalidation
461
+ : failedTransitionReverification
462
+ ? buildPageTransitionRefSnapshotInvalidation("A failed eval/back/forward/reload/connect/state-load/tab command may still have changed the page, so the prior snapshot refs were invalidated. Run snapshot -i before using page-scoped refs.")
463
+ : batchRefSnapshotState?.invalidation?.reason === "page-transition"
464
+ ? batchRefSnapshotState.invalidation
465
+ : undefined;
456
466
  if (currentSessionTabTarget) {
457
467
  const tabUpdate = sessionPageState.applyTabTarget({ sessionName: sessionStateKey, target: currentSessionTabTarget, update: sessionPageStateUpdate });
458
468
  if (!tabUpdate.applied && succeeded)
459
469
  sessionPageState.markPinning(sessionStateKey, "drift");
460
470
  }
461
- else if (processResult.agentBrowserStarted && (resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged) && !(prepared.commandTokens[0] === "session" && prepared.commandTokens[1] === "info")) {
471
+ else if (processResult.agentBrowserStarted && (unobservedMutation || resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged) && !(prepared.commandTokens[0] === "session" && prepared.commandTokens[1] === "info")) {
462
472
  sessionPageState.markTabTargetUnknown({ sessionName: sessionStateKey, update: sessionPageStateUpdate });
463
473
  }
464
- let refSnapshot = unsettledWebMcpMutation
474
+ let refSnapshot = unsettledWebMcpMutation || unobservedMutation
465
475
  ? undefined
466
476
  : prepared.executionPlan.commandInfo.command === "batch"
467
477
  ? batchRefSnapshotState?.snapshot
@@ -472,7 +482,7 @@ export async function processBrowserOutput(input) {
472
482
  ? prepared.commandTokens.filter((token) => token !== "--delta" && token !== "--full") : undefined);
473
483
  // Native owns delta baselines. Read complete refs without advancing that baseline
474
484
  // rather than maintaining a second revision/tree cache in the wrapper.
475
- if (refreshArgs && !unsettledWebMcpMutation) {
485
+ if (refreshArgs && !unsettledWebMcpMutation && !unobservedMutation) {
476
486
  refSnapshot = extractRefSnapshotFromData(await runSessionCommandData({ args: refreshArgs, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal }));
477
487
  if (!refSnapshot)
478
488
  sessionPageState.applyRefSnapshotInvalidation({ invalidation: buildPageTransitionRefSnapshotInvalidation("The native snapshot delta did not include complete refs and the full ref read failed. Run snapshot -i before using refs."), sessionName: sessionStateKey, update: sessionPageStateUpdate });
@@ -574,7 +584,8 @@ export async function processBrowserOutput(input) {
574
584
  }
575
585
  let managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: managedSessionActive, activeBefore: priorManagedSessionActive, attemptedSessionName: managedCloseSessionName, command: commandClosesSession ? "close" : prepared.executionPlan.commandInfo.command, currentSessionName: managedSessionName, currentSessionNamespace: managedSessionNamespace, previousSessionName: priorManagedSessionName, replacedSessionName: replacedManagedSessionName, replacedSessionNamespace: priorManagedSessionNamespace, sessionMode: prepared.sessionMode, succeeded: managedTransitionSucceeded });
576
586
  if (prepared.executionPlan.managedSessionName && managedTransitionSucceeded && managedSessionActive) {
577
- managedSessionCwd = cwd;
587
+ if (!priorManagedSessionActive || replacedManagedSessionName)
588
+ managedSessionCwd = cwd;
578
589
  managedSessionNamespace = prepared.executionPlan.namespace;
579
590
  }
580
591
  if (sessionStateKey && succeeded) {
@@ -681,7 +692,7 @@ export async function processBrowserOutput(input) {
681
692
  }
682
693
  if (plainTextUpgrade && errorText)
683
694
  presentationEnvelope = { ...presentationEnvelope, success: false, error: errorText };
684
- let presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text", text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : recordingStopRecovery && !recordingStopRecovery.batch ? recordingStopRecovery.presentation : await buildToolPresentation({ args: prepared.redactedProcessArgs, artifactManifest, artifactMaxUpdatedAtMs: Date.now(), artifactMinUpdatedAtMs: input.artifactRunStartedAtMs, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd, envelope: presentationEnvelope, errorText, namespace: prepared.executionPlan.namespace, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, previousRecordingContactSheetPath: sessionStateKey ? state.activeRecordingReservations?.get(sessionStateKey)?.contactSheetPath : undefined, piCleanupOwnership: sessionStateKey && (state.ownedManagedSessions.has(sessionStateKey) || prepared.executionPlan.managedSessionName !== undefined) ? "wrapper-managed" : "caller-owned", sessionName: prepared.executionPlan.sessionName });
695
+ let presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text", text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : recordingStopRecovery && !recordingStopRecovery.batch ? recordingStopRecovery.presentation : await buildToolPresentation({ modelVisible: input.modelVisible, args: prepared.redactedProcessArgs, artifactManifest, artifactMaxUpdatedAtMs: Date.now(), artifactMinUpdatedAtMs: input.artifactRunStartedAtMs, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd: operationCwd, envelope: presentationEnvelope, errorText, namespace: prepared.executionPlan.namespace, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, previousRecordingContactSheetPath: sessionStateKey ? state.activeRecordingReservations?.get(sessionStateKey)?.contactSheetPath : undefined, piCleanupOwnership: sessionStateKey && (state.ownedManagedSessions.has(sessionStateKey) || prepared.executionPlan.managedSessionName !== undefined) ? "wrapper-managed" : "caller-owned", sessionName: prepared.executionPlan.sessionName });
685
696
  if (recordingStopRecovery)
686
697
  presentation = mergeRecordingRecoveryPresentation(presentation, recordingStopRecovery);
687
698
  const confirmation = readConfirmationEvent ?? prepared.readConfirmation;
@@ -701,7 +712,8 @@ export async function processBrowserOutput(input) {
701
712
  const errorContent = presentation.content[0];
702
713
  if (errorContent?.type === "text" && presentationEnvelope.data)
703
714
  errorContent.text += `\n\n${presentationEnvelope.data}`;
704
- presentation = await compactLargePresentationOutput({ artifactManifest, commandInfo: prepared.executionPlan.commandInfo, data: presentation.data, persistentArtifactStore, presentation });
715
+ if (input.modelVisible !== false)
716
+ presentation = await compactLargePresentationOutput({ artifactManifest, commandInfo: prepared.executionPlan.commandInfo, data: presentation.data, persistentArtifactStore, presentation });
705
717
  }
706
718
  if (electronHandoff?.error && electronHandoff.failureCategory)
707
719
  presentation.failureCategory = electronHandoff.failureCategory;
@@ -741,12 +753,17 @@ export async function processBrowserOutput(input) {
741
753
  const qaPreset = prepared.compiledQaPreset
742
754
  ? (processResult.timedOut ? analyzeQaPresetTimeout(prepared.compiledQaPreset) ?? analyzeQaPresetResults(presentationEnvelope?.data, prepared.compiledQaPreset) : analyzeQaPresetResults(presentationEnvelope?.data, prepared.compiledQaPreset))
743
755
  : undefined;
756
+ if (qaPreset?.passed && presentation.resultCategory === "failure") {
757
+ qaPreset.passed = false;
758
+ qaPreset.summary = presentation.summary;
759
+ qaPreset.failedChecks.push(presentation.summary);
760
+ }
744
761
  let qaAttachedTarget = prepared.compiledQaPreset?.checks.attached
745
762
  ? await collectQaAttachedTarget({ currentTarget: currentSessionTabTarget ?? prepared.priorSessionTabTarget, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal })
746
763
  : undefined;
747
764
  const sourceLookupElectronContext = prepared.compiledSourceLookup ? getSourceLookupElectronContext({ currentTarget: currentSessionTabTarget, electronLaunchRecords, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName }) : undefined;
748
- const sourceLookup = prepared.compiledSourceLookup ? await analyzeSourceLookupResults(presentationEnvelope?.data, prepared.compiledSourceLookup, cwd, { electronContext: sourceLookupElectronContext, workspaceRoot: cwd }) : undefined;
749
- const networkSourceLookup = prepared.compiledNetworkSourceLookup ? redactNetworkSourceLookupAnalysis(await analyzeNetworkSourceLookupResults(presentationEnvelope?.data, prepared.compiledNetworkSourceLookup, cwd)) : undefined;
765
+ const sourceLookup = prepared.compiledSourceLookup ? await analyzeSourceLookupResults(presentationEnvelope?.data, prepared.compiledSourceLookup, operationCwd, { electronContext: sourceLookupElectronContext, workspaceRoot: operationCwd }) : undefined;
766
+ const networkSourceLookup = prepared.compiledNetworkSourceLookup ? redactNetworkSourceLookupAnalysis(await analyzeNetworkSourceLookupResults(presentationEnvelope?.data, prepared.compiledNetworkSourceLookup, operationCwd)) : undefined;
750
767
  if (networkSourceLookup && presentation.content[0]?.type === "text")
751
768
  presentation.content[0] = { ...presentation.content[0], text: `${networkSourceLookup.summary}\n\n${presentation.content[0].text}` };
752
769
  else if (networkSourceLookup)
@@ -817,7 +834,7 @@ export async function processBrowserOutput(input) {
817
834
  : undefined;
818
835
  const sessionWarning = electronPostCommandHealth ? formatElectronPostCommandHealthText(electronPostCommandHealth) : electronSessionMismatch ? formatElectronSessionMismatchText(electronSessionMismatch) : aboutBlankSessionMismatch ? buildAboutBlankWarning(aboutBlankSessionMismatch) : undefined;
819
836
  const warningText = [sessionWarning, recordingPageWarning].filter(Boolean).join("\n\n") || undefined;
820
- const redactedContent = buildRedactedPresentationContent({ exactSensitiveValues: prepared.exactSensitiveValues, plainTextInspection, presentation, presentationEnvelope, succeeded, userRequestedJson: prepared.userRequestedJson, warningText });
837
+ const redactedContent = input.modelVisible === false ? [] : buildRedactedPresentationContent({ exactSensitiveValues: prepared.exactSensitiveValues, plainTextInspection, presentation, presentationEnvelope, succeeded, userRequestedJson: prepared.userRequestedJson, warningText });
821
838
  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 });
822
839
  currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
823
840
  currentRefSnapshotInvalidation = finalRecoveryState.currentRefSnapshotInvalidation;
@@ -834,7 +851,7 @@ export async function processBrowserOutput(input) {
834
851
  const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !prepared.ownedManagedSessionContext?.reuseOnly && !(commandClosesSession && succeeded)
835
852
  ? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
836
853
  : undefined;
837
- 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, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, headedLaunch: prepared.headedLaunch, inspectionText, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession === true, providerLaunch: prepared.providerLaunch, managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), 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, unsettledWebMcpMutation, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
854
+ const result = buildFinalAgentBrowserToolResult({ modelVisible: input.modelVisible, aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, headedLaunch: prepared.headedLaunch, inspectionText, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession === true, providerLaunch: prepared.providerLaunch, managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), 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, unsettledWebMcpMutation, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
838
855
  const resultWithCloseAll = closeAllApplied
839
856
  ? { ...result, details: { ...(isRecord(result.details) ? result.details : {}), closeAllApplied: true } }
840
857
  : result;
@@ -95,6 +95,7 @@ export async function recoverRecordingStop(options) {
95
95
  && !currentUsesSamePath;
96
96
  const data = matchedData ?? (expected ? { path: expected.absolutePath, recordingId: expected.recordingId, success: null } : undefined);
97
97
  const presentation = await buildToolPresentation({
98
+ modelVisible: options.modelVisible,
98
99
  artifactManifest: options.artifactManifest, artifactMinUpdatedAtMs: expected?.startedAtMs ?? options.artifactRunStartedAtMs, artifactMaxUpdatedAtMs: Date.now(),
99
100
  artifactRequest: expected ? { path: expected.path, absolutePath: expected.absolutePath, status: terminalMeasurements ? undefined : "unverified" } : undefined,
100
101
  commandInfo: { command: "record", subcommand: "stop" }, cwd: expected?.cwd ?? options.cwd,
@@ -139,7 +140,8 @@ export async function recoverRecordingStop(options) {
139
140
  presentation.nextActions = followups;
140
141
  presentation.recordingRecovery = recovery;
141
142
  const text = `Recording receipt recovery (${recovery.status}): ${recovery.reason}\nOriginal attempt: ${attemptError}`;
142
- presentation.content.unshift({ type: "text", text });
143
+ if (options.modelVisible !== false)
144
+ presentation.content.unshift({ type: "text", text });
143
145
  return { batch, envelope, partialBatch: batch && !rows, stopIndex, presentation, recovery };
144
146
  }
145
147
  export function mergeRecordingRecoveryPresentation(base, result) {
@@ -0,0 +1,79 @@
1
+ import { isRecord } from "../../parsing.js";
2
+ import { getScreenshotPathTokenIndex, getScreenshotPositionalIndices } from "./artifact-paths.js";
3
+ import { runSessionCommandData } from "./session-state.js";
4
+ export function getScreenshotCapture(command) {
5
+ if (command[0] !== "screenshot")
6
+ return { kind: "unknown" };
7
+ if (command.includes("--full") || command.includes("-f"))
8
+ return { kind: "full-page" };
9
+ const positional = getScreenshotPositionalIndices(command);
10
+ const selectorIndex = positional.find(index => index !== getScreenshotPathTokenIndex(command));
11
+ return selectorIndex === undefined ? { kind: "viewport" } : { kind: "element", selector: command[selectorIndex] };
12
+ }
13
+ function finiteRecord(value, keys) {
14
+ return isRecord(value) && keys.every(key => typeof value[key] === "number" && Number.isFinite(value[key]));
15
+ }
16
+ export async function collectScreenshotSample(options) {
17
+ if (options.command[0] !== "screenshot" || options.signal?.aborted)
18
+ return undefined;
19
+ const { selector } = getScreenshotCapture(options.command);
20
+ const script = `(() => {
21
+ const root = document.documentElement, body = document.body, v = window.visualViewport;
22
+ let element;
23
+ try {
24
+ const selector = ${JSON.stringify(selector ?? null)};
25
+ const matches = selector ? document.querySelectorAll(selector) : [];
26
+ if (matches.length === 1) {
27
+ const rect = matches[0].getBoundingClientRect();
28
+ element = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
29
+ }
30
+ } catch {}
31
+ return { url: location.href, frame: window === window.top ? "main" : "child", childFrameCount: window.frames.length,
32
+ viewport: { width: innerWidth, height: innerHeight },
33
+ document: { width: Math.max(root.scrollWidth, body?.scrollWidth || 0, innerWidth), height: Math.max(root.scrollHeight, body?.scrollHeight || 0, innerHeight) },
34
+ scroll: { x: scrollX, y: scrollY }, dpr: devicePixelRatio,
35
+ visualViewport: { x: v?.offsetLeft ?? 0, y: v?.offsetTop ?? 0, scale: v?.scale ?? 1 }, element };
36
+ })()`;
37
+ try {
38
+ const data = await runSessionCommandData({ ...options, args: ["eval", "--stdin"], stdin: script, timeoutMs: 2_000 });
39
+ const sample = isRecord(data) ? data.result : undefined;
40
+ if (!isRecord(sample) || typeof sample.url !== "string" || !["main", "child"].includes(String(sample.frame))
41
+ || !finiteRecord(sample.viewport, ["width", "height"]) || !finiteRecord(sample.document, ["width", "height"])
42
+ || !finiteRecord(sample.scroll, ["x", "y"]) || !finiteRecord(sample.visualViewport, ["x", "y", "scale"])
43
+ || typeof sample.childFrameCount !== "number" || !Number.isInteger(sample.childFrameCount) || sample.childFrameCount < 0
44
+ || typeof sample.dpr !== "number" || !Number.isFinite(sample.dpr) || sample.dpr <= 0
45
+ || (sample.element !== undefined && !finiteRecord(sample.element, ["x", "y", "width", "height"])))
46
+ return undefined;
47
+ return sample;
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ export function buildScreenshotGeometry(options) {
54
+ const { before, after, capture, pixels } = options;
55
+ const unknown = (reason) => ({ status: "unknown", reason, before, after });
56
+ if (!before || !after)
57
+ return unknown("Capture was not bracketed by browser geometry samples; coordinates are unknown.");
58
+ if (JSON.stringify(before) !== JSON.stringify(after))
59
+ return unknown("Browser geometry changed across capture; no coordinate mapping is asserted.");
60
+ if (before.frame !== "main")
61
+ return unknown("Probe observed a child frame; its relation to the captured page is unknown.");
62
+ if (before.visualViewport.scale !== 1 || before.visualViewport.x !== 0 || before.visualViewport.y !== 0)
63
+ return unknown("Visual viewport is zoomed or offset; capture origin is unknown.");
64
+ let crop;
65
+ if (capture === "viewport")
66
+ crop = { ...before.scroll, ...before.viewport };
67
+ else if (capture === "full-page")
68
+ crop = { x: 0, y: 0, ...before.document };
69
+ else if (capture === "element" && before.element && before.childFrameCount === 0 && before.scroll.x === 0 && before.scroll.y === 0)
70
+ crop = before.element;
71
+ else
72
+ return unknown("Element crop/frame provenance is unverified (scrolled page, child frames, non-CSS selector, or ambiguous target).");
73
+ if (!pixels || crop.width <= 0 || crop.height <= 0 || crop.x < 0 || crop.y < 0
74
+ || !Object.values(crop).every(Number.isInteger)
75
+ || pixels.width !== crop.width * before.dpr || pixels.height !== crop.height * before.dpr)
76
+ return unknown("Image dimensions do not establish the sampled CSS crop at this DPR.");
77
+ return { status: "measured", before, after, crop, pixelsPerCssPixel: { x: pixels.width / crop.width, y: pixels.height / crop.height },
78
+ reason: "Matching pre/post samples, not an atomic capture guarantee. Pixels map to CSS document coordinates within crop; native mouse uses current viewport CSS coordinates. Recheck scroll/frame before input." };
79
+ }
@@ -4,6 +4,7 @@ import { boundElectronProbeString } from "../../electron/cdp.js";
4
4
  import { buildOwnedManagedSessionRestoreContext, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
5
5
  import { getPageTargetValidationError } from "../../page-target-validation.js";
6
6
  import { isRecord } from "../../parsing.js";
7
+ import { getBrowserResultMessage } from "../../browser-transcript.js";
7
8
  import { withAttachedBrowserSessionContext } from "../../process.js";
8
9
  import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
9
10
  import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
@@ -125,10 +126,8 @@ function isElectronLaunchRecord(value) {
125
126
  export function restoreElectronLaunchRecordsFromBranch(branch) {
126
127
  const records = new Map();
127
128
  for (const entry of branch) {
128
- if (!isRecord(entry) || entry.type !== "message")
129
- continue;
130
- const message = isRecord(entry.message) ? entry.message : undefined;
131
- if (!message || message.toolName !== "agent_browser")
129
+ const message = getBrowserResultMessage(entry);
130
+ if (!message)
132
131
  continue;
133
132
  const details = isRecord(message.details) ? message.details : undefined;
134
133
  const electron = isRecord(details?.electron) ? details.electron : undefined;
@@ -3,10 +3,9 @@ import { normalizeUrlLessOpen } from "./batch-stdin.js";
3
3
  import { isPlainTextInspectionArgs, validateToolArgs, redactInvocationArgs, redactSensitiveText } from "../runtime.js";
4
4
  import { buildAgentBrowserResultCategoryDetails } from "../results/categories.js";
5
5
  import { compileAgentBrowserElectron } from "../input-modes/electron.js";
6
- import { compileAgentBrowserJob, compileAgentBrowserQaPreset } from "../input-modes/job.js";
6
+ import { compileAgentBrowserQaPreset } from "../input-modes/job.js";
7
7
  import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup, redactNetworkSourceLookupArgs, redactNetworkSourceLookupUrl } from "../input-modes/lookups.js";
8
8
  import { compileAgentBrowserSemanticAction } from "../input-modes/semantic-action.js";
9
- import { AGENT_BROWSER_SCRIPT_MAX_TIMEOUT_MS, compileAgentBrowserScript } from "../input-modes/script.js";
10
9
  function redactCompiledElectron(compiled) {
11
10
  if (!compiled)
12
11
  return undefined;
@@ -68,46 +67,39 @@ function normalizeExplicitEvalStdinArgs(args, stdin) {
68
67
  export function resolveAgentBrowserInput(options) {
69
68
  const { getBatchPreflightValidationError, params } = options;
70
69
  const semanticActionResult = params.semanticAction === undefined ? {} : compileAgentBrowserSemanticAction(params.semanticAction);
71
- const jobResult = params.job === undefined ? {} : compileAgentBrowserJob(params.job);
72
70
  const qaResult = params.qa === undefined ? {} : compileAgentBrowserQaPreset(params.qa);
73
71
  const sourceLookupResult = params.sourceLookup === undefined ? {} : compileAgentBrowserSourceLookup(params.sourceLookup);
74
72
  const networkSourceLookupResult = params.networkSourceLookup === undefined ? {} : compileAgentBrowserNetworkSourceLookup(params.networkSourceLookup);
75
73
  const electronResult = params.electron === undefined ? {} : compileAgentBrowserElectron(params.electron);
76
- const scriptResult = params.script === undefined ? {} : compileAgentBrowserScript(params.script);
77
74
  const hasExplicitArgs = Array.isArray(params.args);
78
75
  const explicitInputModes = [
79
76
  hasExplicitArgs,
80
77
  Boolean(semanticActionResult.compiled),
81
- Boolean(jobResult.compiled),
82
78
  Boolean(qaResult.compiled),
83
79
  Boolean(sourceLookupResult.compiled),
84
80
  Boolean(networkSourceLookupResult.compiled),
85
81
  Boolean(electronResult.compiled),
86
- Boolean(scriptResult.compiled),
87
82
  ].filter(Boolean).length;
88
83
  const inputModeError = explicitInputModes !== 1
89
- ? "Provide exactly one of script, args, semanticAction, job, qa, sourceLookup, networkSourceLookup, or electron."
84
+ ? "Provide exactly one of args, semanticAction, qa, sourceLookup, networkSourceLookup, or electron."
90
85
  : undefined;
91
86
  const compiledSemanticAction = semanticActionResult.compiled;
92
87
  const compiledQaPreset = qaResult.compiled;
93
88
  const compiledSourceLookup = sourceLookupResult.compiled;
94
89
  const compiledNetworkSourceLookup = networkSourceLookupResult.compiled;
95
90
  const compiledElectron = electronResult.compiled;
96
- const compiledScript = scriptResult.compiled;
97
- const compiledJob = jobResult.compiled ?? compiledQaPreset;
91
+ const compiledJob = compiledQaPreset;
98
92
  const compiledGeneratedBatch = compiledNetworkSourceLookup ?? compiledSourceLookup ?? compiledJob;
99
93
  const normalizedExplicitArgs = normalizeExplicitEvalStdinArgs(params.args ?? [], params.stdin);
100
- const toolArgs = compiledElectron || compiledScript ? [] : compiledSemanticAction?.args ?? compiledGeneratedBatch?.args ?? normalizedExplicitArgs.args;
94
+ const toolArgs = compiledElectron ? [] : compiledSemanticAction?.args ?? compiledGeneratedBatch?.args ?? normalizedExplicitArgs.args;
101
95
  const toolStdin = compiledGeneratedBatch?.stdin ?? normalizedExplicitArgs.stdin;
102
96
  const redactedArgs = redactInvocationArgs(toolArgs);
103
97
  const generatedStdinError = params.stdin !== undefined
104
98
  ? compiledGeneratedBatch
105
- ? "Do not provide stdin with job, qa, sourceLookup, or networkSourceLookup; those modes generate their own batch stdin."
99
+ ? "Do not provide stdin with qa, sourceLookup, or networkSourceLookup; those modes generate their own batch stdin."
106
100
  : compiledElectron
107
101
  ? "Do not provide stdin with electron; electron mode is host-only or manages its own input."
108
- : compiledScript
109
- ? "Do not provide stdin with script; browser call stdin belongs inside browser({ args, stdin })."
110
- : undefined
102
+ : undefined
111
103
  : undefined;
112
104
  const outputPathError = params.outputPath !== undefined && (typeof params.outputPath !== "string" || params.outputPath.trim().length === 0)
113
105
  ? "outputPath must be a non-empty string when provided."
@@ -118,52 +110,40 @@ export function resolveAgentBrowserInput(options) {
118
110
  ? compiledElectron.action === "list"
119
111
  ? "electron.list has no configurable timeout; remove top-level timeoutMs."
120
112
  : "Use electron.timeoutMs for this action; top-level timeoutMs applies only to browser CLI subprocess calls."
121
- : compiledScript && params.timeoutMs !== undefined && params.timeoutMs > AGENT_BROWSER_SCRIPT_MAX_TIMEOUT_MS
122
- ? `script timeoutMs must be ${AGENT_BROWSER_SCRIPT_MAX_TIMEOUT_MS} or less.`
123
- : undefined;
124
- const scriptSessionModeError = compiledScript && params.sessionMode !== undefined
125
- ? "Do not provide sessionMode with script; script always uses its own isolated session."
126
- : undefined;
113
+ : undefined;
127
114
  const attachedQaSessionError = compiledQaPreset?.checks.attached
128
115
  ? params.sessionMode === "fresh"
129
116
  ? "qa.attached cannot be used with sessionMode=fresh; attach or launch a session first, then run qa.attached with the current session."
130
117
  : undefined
131
118
  : undefined;
132
119
  const validationError = semanticActionResult.error
133
- ?? jobResult.error
134
120
  ?? qaResult.error
135
121
  ?? sourceLookupResult.error
136
122
  ?? networkSourceLookupResult.error
137
123
  ?? electronResult.error
138
- ?? scriptResult.error
139
124
  ?? inputModeError
140
125
  ?? generatedStdinError
141
126
  ?? outputPathError
142
127
  ?? timeoutMsError
143
- ?? scriptSessionModeError
144
128
  ?? attachedQaSessionError
145
- ?? (compiledElectron || compiledScript ? undefined : validateToolArgs(toolArgs) ?? getBatchPreflightValidationError(toolArgs, toolStdin));
129
+ ?? (compiledElectron ? undefined : validateToolArgs(toolArgs) ?? getBatchPreflightValidationError(toolArgs, toolStdin));
146
130
  const redactedCompiledJob = redactCompiledJob(compiledJob);
147
131
  const redactedCompiledSemanticAction = compiledSemanticAction
148
132
  ? { ...compiledSemanticAction, args: redactInvocationArgs(compiledSemanticAction.args) }
149
133
  : undefined;
150
134
  const attemptedKind = compiledElectron
151
135
  ? "electron"
152
- : compiledScript
153
- ? "script"
154
- : compiledNetworkSourceLookup
155
- ? "networkSourceLookup"
156
- : compiledSourceLookup
157
- ? "sourceLookup"
158
- : compiledQaPreset
159
- ? "qa"
160
- : jobResult.compiled
161
- ? "job"
162
- : compiledSemanticAction
163
- ? "semanticAction"
164
- : hasExplicitArgs
165
- ? "args"
166
- : undefined;
136
+ : compiledNetworkSourceLookup
137
+ ? "networkSourceLookup"
138
+ : compiledSourceLookup
139
+ ? "sourceLookup"
140
+ : compiledQaPreset
141
+ ? "qa"
142
+ : compiledSemanticAction
143
+ ? "semanticAction"
144
+ : hasExplicitArgs
145
+ ? "args"
146
+ : undefined;
167
147
  const redactedCompiledElectron = redactCompiledElectron(compiledElectron);
168
148
  const redactedCompiledNetworkSourceLookup = redactCompiledNetworkSourceLookup(compiledNetworkSourceLookup);
169
149
  const redactedCompiledQaPreset = compiledQaPreset && redactedCompiledJob ? { ...redactedCompiledJob, checks: compiledQaPreset.checks } : undefined;
@@ -195,9 +175,6 @@ export function resolveAgentBrowserInput(options) {
195
175
  if (compiledElectron && redactedCompiledElectron) {
196
176
  return { ...resolvedBase, compiledElectron, kind: "electron", redactedCompiledElectron, status: "valid" };
197
177
  }
198
- if (compiledScript) {
199
- return { ...resolvedBase, compiledScript, kind: "script", status: "valid" };
200
- }
201
178
  if (compiledNetworkSourceLookup && redactedCompiledNetworkSourceLookup) {
202
179
  return {
203
180
  ...resolvedBase,
@@ -230,16 +207,6 @@ export function resolveAgentBrowserInput(options) {
230
207
  status: "valid",
231
208
  };
232
209
  }
233
- if (jobResult.compiled && redactedCompiledJob) {
234
- return {
235
- ...resolvedBase,
236
- compiledGeneratedBatch: jobResult.compiled,
237
- compiledJob: jobResult.compiled,
238
- kind: "job",
239
- redactedCompiledJob,
240
- status: "valid",
241
- };
242
- }
243
210
  if (compiledSemanticAction && redactedCompiledSemanticAction) {
244
211
  return { ...resolvedBase, compiledSemanticAction, kind: "semanticAction", redactedCompiledSemanticAction, status: "valid" };
245
212
  }
@@ -75,7 +75,7 @@ async function readNativeIdentity(path, cwd, signal, rootFallback, browserComman
75
75
  export async function withNativeSessionDefaults(input, cwd, signal, run, root) {
76
76
  if (input.kind === "electron" && input.compiledElectron.action === "launch")
77
77
  return withAgentBrowserProcessEnvironment({ AGENT_BROWSER_SESSION: undefined }, () => run(input));
78
- if (input.kind === "script" || input.kind === "electron" || isPlainTextInspectionArgs(input.toolArgs))
78
+ if (input.kind === "electron" || isPlainTextInspectionArgs(input.toolArgs))
79
79
  return run(input);
80
80
  const env = getAgentBrowserProcessEnvironment();
81
81
  const configArg = scanUpstreamGlobalFlagOccurrences(input.toolArgs, "--config")[0];
@@ -127,8 +127,8 @@ export async function withNativeSessionDefaults(input, cwd, signal, run, root) {
127
127
  ...(configPath !== undefined ? { AGENT_BROWSER_CONFIG: resolve(cwd, configPath) } : {}),
128
128
  ...(session !== undefined ? { AGENT_BROWSER_SESSION: session } : {}),
129
129
  ...(namespace !== undefined ? { AGENT_BROWSER_NAMESPACE: namespace } : {}),
130
- }, () => run({ ...input, toolArgs: args, chromeStartupArgs, persistentChromeArgs, configuredChromeLaunch }, !rootDefault ? undefined : async (browserRun) => {
131
- const daemon = await inspectManagedSessionDaemon({ cwd, signal, sessionName: rootDefault,
130
+ }, () => run({ ...input, toolArgs: args, chromeStartupArgs, persistentChromeArgs, configuredChromeLaunch }, !rootDefault ? undefined : async (browserRun, launchSignal = signal) => {
131
+ const daemon = await inspectManagedSessionDaemon({ cwd, signal: launchSignal, sessionName: rootDefault,
132
132
  namespace: scanUpstreamGlobalFlagOccurrences(args, "--namespace").at(-1)?.value ?? namespace, timeoutMs: 5_000 });
133
133
  if (daemon.status === "missing-binary")
134
134
  return {
@@ -0,0 +1,76 @@
1
+ import { isAbsolute, resolve } from "node:path";
2
+ import { projectUpstreamGlobalFlags, scanUpstreamGlobalFlagOccurrences } from "../argv-grammar.js";
3
+ import { getDiffFilePathIndices, getExplicitArtifactDestinationIndex } from "./browser-run/artifact-paths.js";
4
+ import { parseBatchCommandArgument, parseUserBatchStdin } from "./batch-stdin.js";
5
+ /** Bind caller file operands without moving the native process/config or interpreting literal form data. */
6
+ export function resolveOperationPaths(args, stdin, cwd, batchStep = false) {
7
+ let resolved = args;
8
+ const setPath = (index, prefix = "") => {
9
+ const path = index === undefined ? undefined : args[index]?.slice(prefix.length);
10
+ if (index === undefined || !path || isAbsolute(path))
11
+ return;
12
+ if (resolved === args)
13
+ resolved = [...args];
14
+ resolved[index] = prefix + resolve(cwd, path);
15
+ };
16
+ const { tokens, indices } = batchStep ? { tokens: args, indices: args.map((_, index) => index) } : projectUpstreamGlobalFlags(args);
17
+ if (!batchStep) {
18
+ for (const flag of ["--config", "--ca-cert", "--executable-path", "--extension", "--init-script", "--download-path", "--screenshot-dir", "--action-policy", "--state"]) {
19
+ for (const occurrence of scanUpstreamGlobalFlagOccurrences(args, flag)) {
20
+ if (flag === "--state" && tokens[0] === "wait" && occurrence.index > indices[0])
21
+ continue;
22
+ setPath(occurrence.index + 1);
23
+ }
24
+ }
25
+ for (const occurrence of scanUpstreamGlobalFlagOccurrences(args, "--profile")) {
26
+ // Native Chrome profile names are identities, not workspace file paths.
27
+ if (occurrence.value && /[\\/]/.test(occurrence.value) && !occurrence.value.startsWith("~"))
28
+ setPath(occurrence.index + 1);
29
+ }
30
+ }
31
+ if (tokens[0] === "batch") {
32
+ if (tokens.slice(1).some(token => token !== "--bail")) {
33
+ for (let index = 1; index < tokens.length; index++) {
34
+ if (tokens[index] === "--bail")
35
+ continue;
36
+ const row = parseBatchCommandArgument(tokens[index]).step;
37
+ if (!row)
38
+ continue;
39
+ const bound = resolveOperationPaths(row, undefined, cwd, true).args;
40
+ if (bound === row)
41
+ continue;
42
+ if (resolved === args)
43
+ resolved = [...args];
44
+ resolved[indices[index]] = bound.map(token => `'${token.replaceAll("'", "'\\''")}'`).join(" ");
45
+ }
46
+ return { args: resolved, stdin }; // Native ignores stdin when raw rows exist.
47
+ }
48
+ const rows = parseUserBatchStdin(stdin).steps;
49
+ if (!rows?.length)
50
+ return { args: resolved, stdin };
51
+ const bound = rows.map(row => resolveOperationPaths(row, undefined, cwd, true).args);
52
+ return { args: resolved, stdin: bound.some((row, index) => row !== rows[index]) ? JSON.stringify(bound) : stdin };
53
+ }
54
+ setPath(indices[getExplicitArtifactDestinationIndex(tokens) ?? -1]);
55
+ if (tokens[0] === "upload")
56
+ for (let index = 2; index < tokens.length; index++)
57
+ setPath(indices[index]);
58
+ if (tokens[0] === "state" && tokens[1] === "load")
59
+ setPath(indices[2]);
60
+ setPath(indices[getDiffFilePathIndices(tokens).baseline ?? -1]);
61
+ if (tokens[0] === "cookies" && tokens[1] === "set") {
62
+ const index = tokens.indexOf("--curl");
63
+ if (index >= 0)
64
+ setPath(indices[index + 1]);
65
+ }
66
+ if (tokens[0] === "webmcp" && tokens[1] === "invoke") {
67
+ for (let index = 3; index < tokens.length; index++) {
68
+ if (!["--params", "--frame", "--timeout"].includes(tokens[index]))
69
+ continue;
70
+ if (tokens[index] === "--params" && tokens[index + 1]?.startsWith("@"))
71
+ setPath(indices[index + 1], "@");
72
+ index++;
73
+ }
74
+ }
75
+ return { args: resolved, stdin };
76
+ }
@@ -138,7 +138,7 @@ export async function applyAgentBrowserOutputPath(options) {
138
138
  return {
139
139
  ...options.result,
140
140
  content: appendOutputFileNotice(options.result, `Output file rejected: ${message}`, true),
141
- details: { ...details, failureCategory: "validation-error", outputFile, resultCategory: "failure" },
141
+ details: { ...details, error: message, summary: "Output file rejected.", failureCategory: "validation-error", outputFile, resultCategory: "failure" },
142
142
  isError: true,
143
143
  };
144
144
  }
@@ -163,9 +163,9 @@ export async function applyAgentBrowserOutputPath(options) {
163
163
  ? (() => {
164
164
  const rest = { ...options.result.details };
165
165
  delete rest.successCategory;
166
- return { ...rest, failureCategory: rest.failureCategory ?? "upstream-error", outputFile, resultCategory: "failure" };
166
+ return { ...rest, error: message, summary: "Output file failed.", failureCategory: rest.failureCategory ?? "upstream-error", outputFile, resultCategory: "failure" };
167
167
  })()
168
- : { failureCategory: "upstream-error", outputFile, resultCategory: "failure" };
168
+ : { error: message, summary: "Output file failed.", failureCategory: "upstream-error", outputFile, resultCategory: "failure" };
169
169
  return {
170
170
  ...options.result,
171
171
  content: appendOutputFileNotice(options.result, `Output file failed: ${requestedPath} (${message}).`, true),