pi-agent-browser-native 0.6.5 → 0.6.7

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 (37) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +20 -5
  3. package/dist/extensions/agent-browser/index.js +138 -75
  4. package/dist/extensions/agent-browser/lib/command-taxonomy.js +6 -5
  5. package/dist/extensions/agent-browser/lib/electron/cleanup.js +10 -1
  6. package/dist/extensions/agent-browser/lib/input-modes/params.js +20 -7
  7. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +0 -1
  8. package/dist/extensions/agent-browser/lib/managed-session-restore.js +13 -12
  9. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +6 -25
  10. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +2 -3
  11. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +10 -3
  12. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +4 -1
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +1 -1
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +104 -117
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +61 -35
  16. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +100 -135
  17. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +3 -1
  18. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +3 -1
  19. package/dist/extensions/agent-browser/lib/page-target-validation.js +10 -10
  20. package/dist/extensions/agent-browser/lib/parsing.js +7 -0
  21. package/dist/extensions/agent-browser/lib/playbook.js +5 -8
  22. package/dist/extensions/agent-browser/lib/process-identity.js +10 -2
  23. package/dist/extensions/agent-browser/lib/process.js +23 -7
  24. package/dist/extensions/agent-browser/lib/recording-reservations.js +3 -1
  25. package/dist/extensions/agent-browser/lib/results/envelope.js +4 -1
  26. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +45 -43
  27. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +4 -4
  28. package/dist/extensions/agent-browser/lib/runtime.js +18 -2
  29. package/dist/extensions/agent-browser/lib/session-page-state.js +29 -10
  30. package/docs/ARCHITECTURE.md +7 -4
  31. package/docs/COMMAND_REFERENCE.md +22 -15
  32. package/docs/ELECTRON.md +6 -6
  33. package/docs/RELEASE.md +14 -5
  34. package/docs/REQUIREMENTS.md +1 -1
  35. package/docs/SUPPORT_MATRIX.md +13 -3
  36. package/docs/TOOL_CONTRACT.md +33 -22
  37. package/package.json +1 -1
@@ -16,13 +16,13 @@ import { buildAgentBrowserResultCategoryDetails } from "../../results/categories
16
16
  import { applyNamespaceToNextActions } from "../../results/next-actions.js";
17
17
  import { buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions } from "../../results/recovery-next-actions.js";
18
18
  import { resolveVisibleRefActionFromSnapshot } from "../../results/selector-recovery.js";
19
- import { extractRefSnapshotFromData } from "../../session-page-state.js";
20
- import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createFreshSessionName, extractCommandTokens, extractUpstreamCommandTokens, getDefaultHeadlessCompatUserAgent, parseWaitCommandTokens, redactInvocationArgs, } from "../../runtime.js";
19
+ import { buildPageTransitionRefSnapshotInvalidation, extractRefSnapshotFromData, normalizeComparableUrl } from "../../session-page-state.js";
20
+ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createFreshSessionName, extractCommandTokens, extractUpstreamCommandTokens, getDefaultHeadlessCompatUserAgent, parseWaitCommandTokens, redactInvocationArgs, redactSensitiveText, } from "../../runtime.js";
21
21
  import { buildOwnedManagedSessionRestoreContext, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
22
22
  import { getAgentBrowserProcessEnvironment } from "../../process-environment.js";
23
23
  import { getExplicitSessionPageVerificationRequirement, getPageTargetValidationError, } from "../../page-target-validation.js";
24
24
  import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError } from "./managed-session-daemon-policy.js";
25
- import { applyOpenResultTabCorrection, buildManagedSessionOutcome, buildPinnedBatchPlan, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, extractStringResultField, collectAnySessionTabSelection, collectSessionTabSelection, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
25
+ import { buildManagedSessionOutcome, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, extractStringResultField, ensureSessionTabTarget, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
26
26
  import { getUpstreamEffectiveBatchSteps, parseBatchStdinJsonArray } from "../batch-stdin.js";
27
27
  import { buildElectronHostFailureResult, formatAgentBrowserNextActionsText, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
28
28
  import { prepareClickDispatchProbe } from "./click-dispatch.js";
@@ -241,8 +241,8 @@ function getSamePageFreshnessPreflightFailure(options) {
241
241
  const { refIds } = options;
242
242
  if (refIds.length === 0)
243
243
  return undefined;
244
- const previousUrl = options.previousSnapshot.target?.url;
245
- const currentUrl = options.currentSnapshot.target?.url;
244
+ const previousUrl = normalizeComparableUrl(options.previousSnapshot.target?.url);
245
+ const currentUrl = normalizeComparableUrl(options.currentSnapshot.target?.url);
246
246
  if (!previousUrl || !currentUrl || previousUrl !== currentUrl || currentUrl === "about:blank")
247
247
  return undefined;
248
248
  const mismatchedRefs = refIds.filter((refId) => {
@@ -267,8 +267,8 @@ async function collectSamePageRefFreshnessPreflight(options) {
267
267
  const refIds = [...new Set(getGuardedRefUsage(options.commandTokens, options.stdin))];
268
268
  if (!options.previousSnapshot || !options.sessionName || refIds.length === 0)
269
269
  return undefined;
270
- const previousUrl = options.previousSnapshot.target?.url;
271
- const currentTargetUrl = options.currentTarget?.url;
270
+ const previousUrl = normalizeComparableUrl(options.previousSnapshot.target?.url);
271
+ const currentTargetUrl = normalizeComparableUrl(options.currentTarget?.url);
272
272
  if (currentTargetUrl === "about:blank" || (previousUrl && currentTargetUrl && previousUrl !== currentTargetUrl))
273
273
  return undefined;
274
274
  const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
@@ -401,7 +401,29 @@ export async function prepareBrowserRun(options) {
401
401
  let managedSessionPolicyLockTransferred = false;
402
402
  let electronLaunchTransferred = false;
403
403
  try {
404
- const preparedArgs = await prepareAgentBrowserArgs(runtimeToolArgs, runtimeToolStdin, cwd);
404
+ let preparedArgs;
405
+ try {
406
+ preparedArgs = await prepareAgentBrowserArgs(runtimeToolArgs, runtimeToolStdin, cwd);
407
+ }
408
+ catch (error) {
409
+ signal?.throwIfAborted();
410
+ if (!(error instanceof Error) || !("syscall" in error) || error.syscall !== "mkdir" || !("path" in error) || typeof error.path !== "string")
411
+ throw error;
412
+ const guidance = "Choose a writable artifact path whose parent components are directories. Use absolute paths in raw batch artifact rows.";
413
+ const validationError = redactSensitiveText(`Could not prepare artifact directory ${error.path}: ${error.message}. ${guidance}`);
414
+ const nextActions = [{ artifactPath: redactSensitiveText(error.path), id: "verify-artifact-path", reason: guidance, safety: "The requested browser command did not run; inspect the directory with host file tools before retrying.", tool: "agent_browser" }];
415
+ return { kind: "early-result", result: {
416
+ content: [{ type: "text", text: validationError }],
417
+ details: {
418
+ agentBrowserStarted: false,
419
+ args: redactedArgs,
420
+ nextActions,
421
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, succeeded: false, validationError }),
422
+ validationError,
423
+ },
424
+ isError: true,
425
+ } };
426
+ }
405
427
  const userRequestedJson = runtimeToolArgs.includes("--json");
406
428
  let executionPlan = buildExecutionPlan(preparedArgs.args, {
407
429
  freshSessionName,
@@ -480,6 +502,8 @@ export async function prepareBrowserRun(options) {
480
502
  compatibilityUserAgent: compatibilityUserAgentApplied ? compatibilityUserAgent : undefined,
481
503
  wrapperInjectedUserAgent: compatibilityUserAgentApplied,
482
504
  });
505
+ let managedSessionDaemonInactive = false;
506
+ let managedSessionCleanupOnlyReason;
483
507
  if (!executionPlan.validationError && ownedManagedSession) {
484
508
  const closeCommand = isCloseCommand(executionPlan.commandInfo.command);
485
509
  const policy = await acquireOwnedManagedSessionDaemonPolicy({
@@ -488,7 +512,9 @@ export async function prepareBrowserRun(options) {
488
512
  signal,
489
513
  });
490
514
  managedSessionPolicyLock = policy.lock;
515
+ managedSessionDaemonInactive = policy.daemonStatus === "inactive";
491
516
  if (policy.error) {
517
+ managedSessionCleanupOnlyReason = policy.cleanupOnlyReason;
492
518
  executionPlan = {
493
519
  ...executionPlan,
494
520
  recoveryHint: undefined,
@@ -524,10 +550,68 @@ export async function prepareBrowserRun(options) {
524
550
  let priorSessionTabTarget = priorSessionPageState.tabTarget;
525
551
  let priorSessionTabTargetUnknown = priorSessionPageState.tabTargetUnknown;
526
552
  const sessionTabPinningReason = priorSessionPageState.pinningReason;
527
- const priorRefSnapshotState = priorSessionPageState.refSnapshot;
528
- const priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
553
+ let priorRefSnapshotState = priorSessionPageState.refSnapshot;
554
+ let priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
555
+ const coldManagedSession = (managedSessionDaemonInactive || priorSessionPageState.tabReopenPending === true)
556
+ && recordedOwnedSession !== undefined
557
+ && sessionTabPinningReason === "restore"
558
+ && ownedManagedSession?.restoreDecision === "enabled"
559
+ && !managedSessionRestoreDisabled()
560
+ && !options.preserveAttachedBrowserSession;
561
+ if (coldManagedSession && sessionStateKey) {
562
+ sessionPageState.setTabReopenPending({ pending: true, sessionName: sessionStateKey, update: options.sessionPageStateUpdate });
563
+ priorRefSnapshotState = undefined;
564
+ priorRefSnapshotInvalidation = buildPageTransitionRefSnapshotInvalidation("The managed browser shut down. Reopening its URL reloads the page; run snapshot -i before using page-scoped refs.");
565
+ sessionPageState.applyRefSnapshotInvalidation({ invalidation: priorRefSnapshotInvalidation, sessionName: sessionStateKey, update: options.sessionPageStateUpdate });
566
+ }
529
567
  let semanticActionVisibleRefResolution;
530
568
  let livePageVerified = false;
569
+ let sessionTabCorrection;
570
+ let sessionTabSelectionError;
571
+ const plannedCommandTokens = extractUpstreamCommandTokens(preparedArgs.args);
572
+ const knownStaleRef = buildStaleRefPreflight({ commandTokens: plannedCommandTokens, currentTarget: priorSessionTabTarget, refSnapshot: priorRefSnapshotState, refSnapshotInvalidation: priorRefSnapshotInvalidation, stdin: runtimeToolStdin });
573
+ const invalidStdin = validateStdinCommandContract({ command: executionPlan.commandInfo.command, commandTokens: plannedCommandTokens, stdin: runtimeToolStdin });
574
+ const pinSessionTab = shouldPinSessionTabForCommand({
575
+ command: executionPlan.commandInfo.command,
576
+ commandTokens: plannedCommandTokens,
577
+ // URL QA clears diagnostics before its explicit open; those clears do not need the old tab.
578
+ pinningRequired: sessionTabPinningReason !== undefined && compiledQaPreset?.checks.url === undefined,
579
+ reopenPending: coldManagedSession,
580
+ sessionName: executionPlan.sessionName,
581
+ stdin: runtimeToolStdin,
582
+ });
583
+ if (!executionPlan.validationError && !executionPlan.plainTextInspection && !knownStaleRef && !invalidStdin && priorSessionTabTarget && pinSessionTab) {
584
+ signal?.throwIfAborted();
585
+ const reopened = !coldManagedSession || await runSessionCommandData({
586
+ args: ["open", priorSessionTabTarget.url], cwd, namespace: executionPlan.namespace, sessionName: executionPlan.sessionName, signal, timeoutMs: params.timeoutMs,
587
+ onProcessResult: ({ agentBrowserStarted }) => {
588
+ // A started open may have navigated even if its CLI was aborted before replying.
589
+ if (agentBrowserStarted && sessionStateKey)
590
+ sessionPageState.setTabReopenPending({ pending: false, sessionName: sessionStateKey, update: options.sessionPageStateUpdate });
591
+ },
592
+ }) !== undefined;
593
+ const selection = signal?.aborted ? undefined : reopened
594
+ ? await ensureSessionTabTarget({ cwd, namespace: executionPlan.namespace, sessionName: executionPlan.sessionName, signal, target: priorSessionTabTarget })
595
+ : { error: "agent-browser could not reopen the remembered URL after the managed browser shut down. Navigate explicitly, then run snapshot -i before retrying." };
596
+ if (coldManagedSession && signal?.aborted) {
597
+ const errorText = "agent_browser was aborted while reopening the remembered page. The requested command did not run.";
598
+ return { kind: "early-result", result: {
599
+ content: [{ type: "text", text: errorText }],
600
+ details: {
601
+ aborted: true, args: redactedArgs, command: executionPlan.commandInfo.command,
602
+ effectiveArgs: redactInvocationArgs(executionPlan.effectiveArgs), sessionMode,
603
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession, executionPlan.namespace, managedSessionRestoreDisabled()),
604
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, command: executionPlan.commandInfo.command, errorText, failureCategory: "aborted", succeeded: false }),
605
+ },
606
+ isError: true,
607
+ } };
608
+ }
609
+ signal?.throwIfAborted();
610
+ sessionTabCorrection = selection?.correction;
611
+ sessionTabSelectionError = selection?.error;
612
+ if (selection?.error)
613
+ executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: selection.error };
614
+ }
531
615
  const isCallerOwnedExplicitSession = () => executionPlan.sessionName !== undefined
532
616
  && executionPlan.usedImplicitSession === false
533
617
  && ownedManagedSession === undefined;
@@ -655,7 +739,7 @@ export async function prepareBrowserRun(options) {
655
739
  freshSessionOrdinal += 1;
656
740
  }
657
741
  if (executionPlan.validationError) {
658
- const nextActions = applyNamespaceToNextActions(unsupportedScrollIntoViewRecovery?.nextActions, executionPlan.namespace);
742
+ const nextActions = applyNamespaceToNextActions(sessionTabSelectionError ? buildSessionTabRecoveryNextActions({ kind: "tab-drift", resultCategory: "failure", sessionName: executionPlan.sessionName, tabCorrection: sessionTabCorrection, target: priorSessionTabTarget }) : unsupportedScrollIntoViewRecovery?.nextActions, executionPlan.namespace);
659
743
  const nextActionsText = formatAgentBrowserNextActionsText(nextActions);
660
744
  return { kind: "early-result", statePatch, result: {
661
745
  content: [{ type: "text", text: [executionPlan.validationError, nextActionsText].filter((text) => text !== undefined).join("\n\n") }],
@@ -667,11 +751,16 @@ export async function prepareBrowserRun(options) {
667
751
  compiledSourceLookup: redactedCompiledSourceLookup,
668
752
  compiledNetworkSourceLookup: redactedCompiledNetworkSourceLookup,
669
753
  invalidValueFlag: executionPlan.invalidValueFlag,
754
+ managedSessionCleanupOnlyReason,
755
+ ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession, executionPlan.namespace, managedSessionRestoreDisabled()),
756
+ ...(managedSessionCleanupOnlyReason ? { namespace: ownedManagedSession?.namespace ?? "" } : {}),
670
757
  nextActions,
671
758
  sessionMode,
672
759
  sessionRecoveryHint: redactedRecoveryHint,
673
760
  startupScopedFlags: executionPlan.startupScopedFlags,
674
- ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, command: executionPlan.commandInfo.command, errorText: executionPlan.validationError, succeeded: false, validationError: executionPlan.validationError }),
761
+ ...(sessionTabSelectionError ? { effectiveArgs: redactedEffectiveArgs, sessionTabCorrection } : {}),
762
+ ...(coldManagedSession ? { refSnapshotInvalidation: priorRefSnapshotInvalidation } : {}),
763
+ ...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, command: executionPlan.commandInfo.command, errorText: executionPlan.validationError, failureCategory: sessionTabSelectionError ? "tab-drift" : undefined, succeeded: false, validationError: executionPlan.validationError }),
675
764
  validationError: executionPlan.validationError,
676
765
  },
677
766
  isError: true,
@@ -911,109 +1000,9 @@ export async function prepareBrowserRun(options) {
911
1000
  });
912
1001
  if (directAnchorDownload)
913
1002
  return { kind: "early-result", statePatch: { ...statePatch, artifactManifest: directAnchorDownload.artifactManifest ?? statePatch.artifactManifest }, result: directAnchorDownload.result };
914
- let pinnedBatchUnwrapMode;
915
- let includePinnedNavigationSummary = false;
916
- let sessionTabCorrection;
917
- let processArgs = executionPlan.effectiveArgs;
918
- let processStdin = preparedArgs.stdin ?? runtimeToolStdin;
919
- if (priorSessionTabTarget &&
920
- shouldPinSessionTabForCommand({
921
- command: executionPlan.commandInfo.command,
922
- commandTokens,
923
- pinningRequired: sessionTabPinningReason !== undefined,
924
- sessionName: executionPlan.sessionName,
925
- stdin: runtimeToolStdin,
926
- })) {
927
- const collectTabSelection = promptRefSnapshot && getGuardedRefUsage(commandTokens, runtimeToolStdin).length > 0 ? collectAnySessionTabSelection : collectSessionTabSelection;
928
- const plannedSessionTabSelection = await collectTabSelection({
929
- cwd,
930
- namespace: executionPlan.namespace,
931
- sessionName: executionPlan.sessionName,
932
- signal,
933
- target: priorSessionTabTarget,
934
- });
935
- if (plannedSessionTabSelection && executionPlan.sessionName) {
936
- if (executionPlan.commandInfo.command === "eval" && runtimeToolStdin !== undefined) {
937
- const appliedSessionTabSelection = await applyOpenResultTabCorrection({
938
- correction: plannedSessionTabSelection,
939
- cwd,
940
- namespace: executionPlan.namespace,
941
- sessionName: executionPlan.sessionName,
942
- signal,
943
- });
944
- if (!appliedSessionTabSelection) {
945
- const error = "agent-browser could not re-select the intended tab before running the command.";
946
- return { kind: "early-result", statePatch, result: {
947
- content: [{ type: "text", text: error }],
948
- details: {
949
- args: redactedArgs,
950
- command: executionPlan.commandInfo.command,
951
- compatibilityWorkaround,
952
- effectiveArgs: redactedEffectiveArgs,
953
- sessionMode,
954
- sessionTabCorrection: plannedSessionTabSelection,
955
- ...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: error, failureCategory: "tab-drift", succeeded: false, tabDrift: true, validationError: error }),
956
- nextActions: applyNamespaceToNextActions(buildSessionTabRecoveryNextActions({
957
- kind: "tab-drift",
958
- resultCategory: "failure",
959
- sessionName: executionPlan.sessionName,
960
- tabCorrection: plannedSessionTabSelection,
961
- target: priorSessionTabTarget,
962
- }), executionPlan.namespace),
963
- validationError: error,
964
- ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession, executionPlan.namespace, managedSessionRestoreDisabled()),
965
- },
966
- isError: true,
967
- } };
968
- }
969
- sessionTabCorrection = appliedSessionTabSelection;
970
- }
971
- else {
972
- const pinnedBatchPlan = buildPinnedBatchPlan({
973
- command: executionPlan.commandInfo.command,
974
- commandTokens,
975
- selectedTab: plannedSessionTabSelection.selectedTab,
976
- stdin: runtimeToolStdin,
977
- });
978
- if (pinnedBatchPlan && "error" in pinnedBatchPlan) {
979
- return { kind: "early-result", statePatch, result: {
980
- content: [{ type: "text", text: pinnedBatchPlan.error }],
981
- details: {
982
- args: redactedArgs,
983
- command: executionPlan.commandInfo.command,
984
- compatibilityWorkaround,
985
- effectiveArgs: redactedEffectiveArgs,
986
- sessionMode,
987
- sessionTabCorrection: plannedSessionTabSelection,
988
- ...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: pinnedBatchPlan.error, failureCategory: "tab-drift", succeeded: false, tabDrift: true, validationError: pinnedBatchPlan.error }),
989
- nextActions: applyNamespaceToNextActions(buildSessionTabRecoveryNextActions({
990
- kind: "tab-drift",
991
- resultCategory: "failure",
992
- sessionName: executionPlan.sessionName,
993
- tabCorrection: plannedSessionTabSelection,
994
- target: priorSessionTabTarget,
995
- }), executionPlan.namespace),
996
- validationError: pinnedBatchPlan.error,
997
- ...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession, executionPlan.namespace, managedSessionRestoreDisabled()),
998
- },
999
- isError: true,
1000
- } };
1001
- }
1002
- if (pinnedBatchPlan) {
1003
- sessionTabCorrection = plannedSessionTabSelection;
1004
- // The rewritten batch must keep the caller's fail-fast semantics:
1005
- // upstream reads only the exact --bail token, so re-emit it whenever
1006
- // the original batch tokens carried it (argv or stdin/job/qa mode).
1007
- const pinnedBailArgs = commandTokens.includes("--bail") ? ["--bail"] : [];
1008
- processArgs = ["--json", ...(executionPlan.namespace !== undefined ? ["--namespace", executionPlan.namespace] : []), "--session", executionPlan.sessionName, "batch", ...pinnedBailArgs];
1009
- processStdin = JSON.stringify(pinnedBatchPlan.steps);
1010
- includePinnedNavigationSummary = pinnedBatchPlan.includeNavigationSummary;
1011
- pinnedBatchUnwrapMode = pinnedBatchPlan.unwrapMode;
1012
- }
1013
- }
1014
- }
1015
- }
1016
- const clickDispatchProbe = pinnedBatchUnwrapMode === undefined && compiledElectron === undefined
1003
+ const processArgs = executionPlan.effectiveArgs;
1004
+ const processStdin = preparedArgs.stdin ?? runtimeToolStdin;
1005
+ const clickDispatchProbe = compiledElectron === undefined
1017
1006
  ? await prepareClickDispatchProbe({ commandTokens, cwd, namespace: executionPlan.namespace, refSnapshot: promptRefSnapshot, sessionName: executionPlan.sessionName, signal })
1018
1007
  : undefined;
1019
1008
  let readTimeoutPageUrl = priorSessionTabTarget?.url;
@@ -1061,9 +1050,7 @@ export async function prepareBrowserRun(options) {
1061
1050
  electronLaunch,
1062
1051
  exactSensitiveValues,
1063
1052
  executionPlan,
1064
- includePinnedNavigationSummary,
1065
1053
  ownedManagedSessionContext: ownedManagedSession,
1066
- pinnedBatchUnwrapMode,
1067
1054
  preparedArgs,
1068
1055
  priorRefSnapshotState,
1069
1056
  priorSessionTabTarget,
@@ -1,7 +1,9 @@
1
1
  import { rm } from "node:fs/promises";
2
+ import { parseArgvDescriptor } from "../../argv-descriptor.js";
3
+ import { needsManagedSession } from "../../command-policy.js";
2
4
  import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "../../argv-grammar.js";
3
5
  import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "../../batch-lifecycle.js";
4
- import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand, isUnverifiedPageTransitionCommand } from "../../command-taxonomy.js";
6
+ import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWindowOrDiffPageTransitionCommand } from "../../command-taxonomy.js";
5
7
  import { OPEN_RESULT_TAB_CORRECTION_FLAGS } from "../../launch-scoped-flags.js";
6
8
  import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
7
9
  import { getResultingPageTargetState, commandRequiresLivePageVerification } from "../../page-target-validation.js";
@@ -9,6 +11,7 @@ import { analyzeNetworkSourceLookupResults, analyzeSourceLookupResults, redactNe
9
11
  import { analyzeQaPresetResults, analyzeQaPresetTimeout, buildQaCompactFailureText, buildQaCompactPassText, extractQaPageContext } from "../../input-modes/job.js";
10
12
  import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "../../results/network-routes.js";
11
13
  import { buildToolPresentation } from "../../results/presentation.js";
14
+ import { compactLargePresentationOutput } from "../../results/presentation/large-output.js";
12
15
  import { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
13
16
  import { omitUpstreamLifecycle } from "../../results/presentation/common.js";
14
17
  import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
@@ -20,7 +23,7 @@ import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
20
23
  import { createFreshSessionName, extractUpstreamCommandTokens, resolveManagedSessionState } from "../../runtime.js";
21
24
  import { getUpstreamEffectiveBatchSteps } from "../batch-stdin.js";
22
25
  import { closeManagedSession, inspectManagedSessionDaemon } from "./managed-session-daemon-policy.js";
23
- import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, collectOpenResultTabCorrection, collectSessionTabSelection, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, unwrapPinnedSessionBatchEnvelope, updateTraceOwnerState, } from "./session-state.js";
26
+ import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, collectOpenResultTabCorrection, collectSessionTabSelection, commandChoosesSessionTabTarget, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, updateTraceOwnerState, } from "./session-state.js";
24
27
  import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
25
28
  import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
26
29
  import { repairScreenshotData } from "./prepare.js";
@@ -160,17 +163,13 @@ export async function processBrowserOutput(input) {
160
163
  let networkRoutesBySession = state.networkRoutesBySession;
161
164
  try {
162
165
  const persistentArtifactStore = getPersistentSessionArtifactStore(ctx);
163
- const parsed = await parseAgentBrowserEnvelope({ stdout: processResult.stdout, stdoutPath: processResult.stdoutSpillPath });
166
+ // Native upgrade prints text even with --json; all other command shapes keep strict JSON parsing.
167
+ const plainTextUpgrade = !prepared.executionPlan.plainTextInspection && prepared.executionPlan.commandInfo.command === "upgrade" && !needsManagedSession(parseArgvDescriptor(prepared.runtimeToolArgs));
168
+ const parsed = await parseAgentBrowserEnvelope({ stdout: processResult.stdout, stdoutPath: processResult.stdoutSpillPath, plainText: plainTextUpgrade });
164
169
  let parseError = parsed.parseError;
165
170
  let presentationEnvelope = parsed.envelope;
166
171
  let navigationSummary = undefined;
167
172
  let failedTransitionReverification = false;
168
- if (prepared.pinnedBatchUnwrapMode) {
169
- const pinnedBatchResult = unwrapPinnedSessionBatchEnvelope({ envelope: parsed.envelope, includeNavigationSummary: prepared.includePinnedNavigationSummary, mode: prepared.pinnedBatchUnwrapMode });
170
- parseError = pinnedBatchResult.parseError ?? parseError;
171
- presentationEnvelope = pinnedBatchResult.envelope ?? presentationEnvelope;
172
- navigationSummary = pinnedBatchResult.navigationSummary;
173
- }
174
173
  const repairedScreenshot = await repairScreenshotArtifact({ cwd, envelope: presentationEnvelope, request: prepared.preparedArgs.screenshotPathRequest });
175
174
  presentationEnvelope = repairedScreenshot.envelope;
176
175
  const repairedBatchScreenshots = await repairBatchScreenshotArtifacts({ cwd, envelope: presentationEnvelope, requests: prepared.preparedArgs.batchScreenshotPathRequests });
@@ -180,6 +179,15 @@ export async function processBrowserOutput(input) {
180
179
  const batchCommandSteps = prepared.executionPlan.commandInfo.command === "batch"
181
180
  ? getUpstreamEffectiveBatchSteps(prepared.commandTokens, prepared.runtimeToolStdin)
182
181
  : [];
182
+ const dispatchedCommands = prepared.executionPlan.commandInfo.command !== "batch"
183
+ ? [prepared.commandTokens]
184
+ : Array.isArray(presentationEnvelope?.data)
185
+ ? presentationEnvelope.data.flatMap((row, index) => isRecord(row) ? [Array.isArray(row.command) && row.command.every((token) => typeof token === "string") ? row.command : batchCommandSteps[index] ?? []] : [])
186
+ : batchCommandSteps;
187
+ const destinationTransition = dispatchedCommands.some((step) => {
188
+ const [command, subcommand] = extractUpstreamCommandTokens(step);
189
+ return isWindowOrDiffPageTransitionCommand(command, subcommand);
190
+ });
183
191
  const nestedBatchClose = prepared.executionPlan.commandInfo.command === "batch"
184
192
  ? getSuccessfulBatchCloseLifecycle(presentationEnvelope?.data, batchCommandSteps)
185
193
  : undefined;
@@ -198,7 +206,7 @@ export async function processBrowserOutput(input) {
198
206
  const parseFailureOutput = parseError && processResult.stdoutSpillPath
199
207
  ? { fullOutputUnavailable: "Malformed upstream output was discarded because it may contain sensitive browser data." }
200
208
  : {};
201
- const processSucceeded = !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
209
+ const processSucceeded = !processResult.timedOut && !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
202
210
  const plainTextInspection = prepared.executionPlan.plainTextInspection && processSucceeded;
203
211
  const parseSucceeded = plainTextInspection || parseError === undefined;
204
212
  if (isStreamEnableAlreadyEnabledNoop({ command: prepared.executionPlan.commandInfo.command, envelope: presentationEnvelope, processSucceeded, subcommand: prepared.executionPlan.commandInfo.subcommand })) {
@@ -209,6 +217,10 @@ export async function processBrowserOutput(input) {
209
217
  const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
210
218
  const sessionStateKey = getSessionContextKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace);
211
219
  const closeAllApplied = nestedBatchClosesAll || (directCloseAllRequested && succeeded);
220
+ if (sessionStateKey && processResult.agentBrowserStarted && sessionPageState.get(sessionStateKey).tabReopenPending === true) {
221
+ if (dispatchedCommands.some(commandChoosesSessionTabTarget))
222
+ sessionPageState.setTabReopenPending({ pending: false, sessionName: sessionStateKey, update: sessionPageStateUpdate });
223
+ }
212
224
  if (closeAllApplied) {
213
225
  networkRoutesBySession = withoutNamespaceEntries(networkRoutesBySession, prepared.executionPlan.namespace);
214
226
  deleteNamespaceEntries(state.attachedSessionKeys, prepared.executionPlan.namespace);
@@ -252,36 +264,32 @@ export async function processBrowserOutput(input) {
252
264
  }
253
265
  }
254
266
  const tabTransition = prepared.executionPlan.commandInfo.command === "tab" && prepared.executionPlan.commandInfo.subcommand !== undefined && !["list", "new"].includes(prepared.executionPlan.commandInfo.subcommand);
255
- const resultingPageState = getResultingPageTargetState({
256
- args: prepared.executionPlan.effectiveArgs,
257
- currentPageUrl: prepared.priorSessionTabTarget?.url,
258
- pageUrlUnknown: prepared.priorSessionTabTargetUnknown === true,
259
- stdin: prepared.runtimeToolStdin,
260
- trustedFirstBatchTabSelection: prepared.pinnedBatchUnwrapMode !== undefined,
261
- });
267
+ // Non-page rows (including a failed prefix) cannot retire a cold target for a navigation that never ran.
268
+ const resultingPageState = sessionPageState.get(sessionStateKey).tabReopenPending === true
269
+ ? { currentPageUrl: prepared.priorSessionTabTarget?.url, pageTargetMayHaveChanged: false, pageUrlUnknown: prepared.priorSessionTabTargetUnknown === true }
270
+ : getResultingPageTargetState({
271
+ args: prepared.executionPlan.effectiveArgs,
272
+ executedBatchSteps: dispatchedCommands,
273
+ currentPageUrl: prepared.priorSessionTabTarget?.url,
274
+ pageUrlUnknown: prepared.priorSessionTabTargetUnknown === true,
275
+ });
262
276
  if (succeeded &&
263
- !navigationSummary &&
264
277
  (shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data, prepared.executionPlan.commandInfo.subcommand) ||
265
278
  shouldCaptureSemanticActionNavigationSummary(prepared.compiledSemanticAction, presentationEnvelope?.data) ||
266
279
  commandRequiresLivePageVerification(prepared.executionPlan.effectiveArgs, prepared.runtimeToolStdin) ||
267
- tabTransition)) {
280
+ (destinationTransition && !nestedBatchClosed) || tabTransition)) {
268
281
  navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, reusePriorTitle: !tabTransition, sessionName: prepared.executionPlan.sessionName, signal });
269
282
  }
270
- // A failed eval/back/forward/reload/state-load/tab would otherwise drop the page to unverified and force the
271
- // agent through a manual get url round trip. Probe the live URL ourselves instead: an observed page
272
- // stays verified, and a failed
273
- // probe preserves the existing unknown-target behavior.
283
+ // Failed transitions may already have changed the page; keep only a live observed URL.
274
284
  if (succeeded === false &&
275
- !navigationSummary &&
276
285
  processResult.agentBrowserStarted &&
277
286
  !processResult.aborted &&
278
287
  !processResult.timedOut &&
279
- prepared.executionPlan.commandInfo.command !== "batch" &&
280
- isUnverifiedPageTransitionCommand(prepared.executionPlan.commandInfo.command, prepared.executionPlan.commandInfo.subcommand)) {
288
+ !nestedBatchClosed &&
289
+ (destinationTransition || (prepared.executionPlan.commandInfo.command !== "batch" &&
290
+ isUnverifiedPageTransitionCommand(prepared.executionPlan.commandInfo.command, prepared.executionPlan.commandInfo.subcommand)))) {
281
291
  navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
282
- // A failed transition command can still have mutated or replaced the document before throwing, so
283
- // keeping the verified URL must not keep the prior snapshot refs (markTabTargetUnknown dropped
284
- // them before this probe existed); invalidate so the next ref use requires a fresh snapshot.
292
+ // Re-verifying a failed transition's URL does not make its prior refs valid.
285
293
  failedTransitionReverification = navigationSummary !== undefined;
286
294
  }
287
295
  if (navigationSummary && presentationEnvelope && prepared.executionPlan.commandInfo.command !== "eval" && !Array.isArray(presentationEnvelope.data))
@@ -309,8 +317,9 @@ export async function processBrowserOutput(input) {
309
317
  const safeObservedSessionTabTarget = observedSessionTabTarget;
310
318
  let currentSessionTabTarget = safeObservedSessionTabTarget;
311
319
  if (!currentSessionTabTarget && nestedBatchClose === undefined) {
320
+ // Window/diff responses do not report the final URL; URL2 is intent, not redirect evidence.
312
321
  currentSessionTabTarget = resultingPageState.pageTargetMayHaveChanged
313
- ? succeeded ? normalizeSessionTabTarget({ url: resultingPageState.currentPageUrl }) : undefined
322
+ ? succeeded && !destinationTransition ? normalizeSessionTabTarget({ url: resultingPageState.currentPageUrl }) : undefined
314
323
  : deriveSessionTabTarget({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary, previousTarget: prepared.priorSessionTabTarget, subcommand: prepared.executionPlan.commandInfo.subcommand });
315
324
  }
316
325
  let aboutBlankSessionMismatch;
@@ -318,7 +327,8 @@ export async function processBrowserOutput(input) {
318
327
  let electronRefFreshnessDiagnostic;
319
328
  let electronSessionMismatch;
320
329
  let electronStatusAfterCommand;
321
- const shouldTreatAboutBlankAsMismatch = succeeded && !tabTransition && nestedBatchClose === undefined && prepared.priorSessionTabTarget !== undefined && !isAboutBlankSessionTabTarget(prepared.priorSessionTabTarget) && isAboutBlankSessionTabTarget(observedSessionTabTarget ?? currentSessionTabTarget) && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens);
330
+ const explicitlyTargetsAboutBlank = dispatchedCommands.some((step) => commandExplicitlyTargetsAboutBlank(extractUpstreamCommandTokens(step)));
331
+ const shouldTreatAboutBlankAsMismatch = succeeded && !tabTransition && !destinationTransition && !explicitlyTargetsAboutBlank && nestedBatchClose === undefined && prepared.priorSessionTabTarget !== undefined && !isAboutBlankSessionTabTarget(prepared.priorSessionTabTarget) && isAboutBlankSessionTabTarget(observedSessionTabTarget ?? currentSessionTabTarget);
322
332
  let sessionTabCorrection = prepared.sessionTabCorrection;
323
333
  if (shouldTreatAboutBlankAsMismatch && prepared.priorSessionTabTarget) {
324
334
  const aboutBlankObservedTarget = observedSessionTabTarget ?? currentSessionTabTarget;
@@ -536,7 +546,7 @@ export async function processBrowserOutput(input) {
536
546
  if (sessionStateKey && succeeded) {
537
547
  if (openResultTabCorrection || sessionTabCorrection || aboutBlankSessionMismatch?.recoveryApplied)
538
548
  sessionPageState.markPinning(sessionStateKey, "drift");
539
- else if (prepared.sessionTabPinningReason === "restore")
549
+ else if (prepared.sessionTabPinningReason === "restore" && observedSessionTabTarget)
540
550
  sessionPageState.clearRestorePinning(sessionStateKey);
541
551
  }
542
552
  if (replacedManagedSessionName) {
@@ -626,14 +636,23 @@ export async function processBrowserOutput(input) {
626
636
  electronLaunchRecord = electronFailedConnectCleanup.record;
627
637
  }
628
638
  }
629
- let errorText = getAgentBrowserErrorText({ aborted: processResult.aborted, command: prepared.executionPlan.commandInfo.command, effectiveArgs: prepared.redactedProcessArgs, envelope: presentationEnvelope, exitCode: processResult.exitCode, parseError, plainTextInspection, staleRefArgs: getStaleRefArgs(prepared.commandTokens, prepared.runtimeToolStdin), spawnError: processResult.spawnError, stderr: processResult.stderr, timedOut: processResult.timedOut, timeoutMs: processResult.timeoutMs, wrapperRecoveryHint: buildWrapperRecoveryHint({ pinnedBatchUnwrapMode: prepared.pinnedBatchUnwrapMode, sessionTabCorrection }) });
639
+ let errorText = getAgentBrowserErrorText({ aborted: processResult.aborted, command: prepared.executionPlan.commandInfo.command, effectiveArgs: prepared.redactedProcessArgs, envelope: presentationEnvelope, exitCode: processResult.exitCode, parseError, plainTextInspection, staleRefArgs: getStaleRefArgs(prepared.commandTokens, prepared.runtimeToolStdin), spawnError: processResult.spawnError, stderr: processResult.stderr, timedOut: processResult.timedOut, timeoutMs: processResult.timeoutMs, wrapperRecoveryHint: buildWrapperRecoveryHint({ sessionTabCorrection }) });
630
640
  if (errorText) {
631
641
  const clipboardWritePayloadCandidates = getClipboardWritePayloadCandidates(prepared.commandTokens);
632
642
  errorText = redactClipboardPermissionEcho(prepared.executionPlan.commandInfo, errorText);
633
643
  if (presentationEnvelope?.error !== undefined)
634
644
  presentationEnvelope = { ...presentationEnvelope, error: redactClipboardPermissionErrorValue(prepared.executionPlan.commandInfo, presentationEnvelope.error, clipboardWritePayloadCandidates) };
635
645
  }
636
- const 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` } : 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, sessionName: prepared.executionPlan.sessionName });
646
+ if (plainTextUpgrade && errorText)
647
+ presentationEnvelope = { ...presentationEnvelope, success: false, error: errorText };
648
+ 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` } : 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, sessionName: prepared.executionPlan.sessionName });
649
+ if (plainTextUpgrade && !succeeded && typeof presentationEnvelope?.data === "string") {
650
+ presentation.data = presentationEnvelope.data;
651
+ const errorContent = presentation.content[0];
652
+ if (errorContent?.type === "text" && presentationEnvelope.data)
653
+ errorContent.text += `\n\n${presentationEnvelope.data}`;
654
+ presentation = await compactLargePresentationOutput({ artifactManifest, commandInfo: prepared.executionPlan.commandInfo, data: presentation.data, persistentArtifactStore, presentation });
655
+ }
637
656
  if (electronHandoff?.error && electronHandoff.failureCategory)
638
657
  presentation.failureCategory = electronHandoff.failureCategory;
639
658
  networkRoutesBySession = applyBatchNetworkRouteState({ data: presentationEnvelope?.data, routesBySession: networkRoutesBySession, sessionName: sessionStateKey, succeeded });
@@ -738,7 +757,14 @@ export async function processBrowserOutput(input) {
738
757
  const evalResultWarning = getEvalResultWarning({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary: evalNavigationSummary, pageUrl: evalPageUrl, stdin: prepared.runtimeToolStdin });
739
758
  const resultArtifactManifest = presentation.artifactManifest ?? artifactManifest;
740
759
  const artifactCleanup = await getArtifactCleanupGuidance({ command: prepared.executionPlan.commandInfo.command, cwd, manifest: resultArtifactManifest, succeeded });
741
- const warningText = electronPostCommandHealth ? formatElectronPostCommandHealthText(electronPostCommandHealth) : electronSessionMismatch ? formatElectronSessionMismatchText(electronSessionMismatch) : aboutBlankSessionMismatch ? buildAboutBlankWarning(aboutBlankSessionMismatch) : undefined;
760
+ const recordingTransitionReached = prepared.executionPlan.commandInfo.command === "batch"
761
+ ? presentation.batchSteps?.some((step) => isRecordPageTransitionCommand(extractUpstreamCommandTokens(step.command ?? [])))
762
+ : isRecordPageTransitionCommand(prepared.commandTokens);
763
+ const recordingPageWarning = processResult.agentBrowserStarted && !prepared.executionPlan.plainTextInspection && recordingTransitionReached
764
+ ? "Page state: this recording command can replace or navigate the active page, even on failure; prior in-page DOM and JavaScript state may not carry over. Take a fresh snapshot before continuing with page-scoped refs."
765
+ : undefined;
766
+ const sessionWarning = electronPostCommandHealth ? formatElectronPostCommandHealthText(electronPostCommandHealth) : electronSessionMismatch ? formatElectronSessionMismatchText(electronSessionMismatch) : aboutBlankSessionMismatch ? buildAboutBlankWarning(aboutBlankSessionMismatch) : undefined;
767
+ const warningText = [sessionWarning, recordingPageWarning].filter(Boolean).join("\n\n") || undefined;
742
768
  const redactedContent = buildRedactedPresentationContent({ exactSensitiveValues: prepared.exactSensitiveValues, plainTextInspection, presentation, presentationEnvelope, succeeded, userRequestedJson: prepared.userRequestedJson, warningText });
743
769
  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 });
744
770
  currentRefSnapshot = finalRecoveryState.currentRefSnapshot;