pi-agent-browser-native 0.2.71 → 0.2.74

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 +46 -0
  2. package/README.md +14 -12
  3. package/dist/extensions/agent-browser/index.js +104 -16
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +124 -0
  5. package/dist/extensions/agent-browser/lib/command-taxonomy.js +12 -1
  6. package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
  7. package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
  8. package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
  9. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
  10. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
  11. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
  12. package/dist/extensions/agent-browser/lib/managed-session-restore.js +367 -0
  13. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
  14. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
  15. package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
  16. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  24. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  25. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  26. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +625 -429
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +136 -56
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +28 -40
  29. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
  30. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
  31. package/dist/extensions/agent-browser/lib/playbook.js +10 -9
  32. package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
  33. package/dist/extensions/agent-browser/lib/process.js +270 -34
  34. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
  35. package/dist/extensions/agent-browser/lib/results/categories.js +21 -2
  36. package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
  37. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +80 -12
  38. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
  39. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
  40. package/dist/extensions/agent-browser/lib/runtime.js +85 -85
  41. package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
  42. package/dist/extensions/agent-browser/lib/temp.js +13 -25
  43. package/docs/ARCHITECTURE.md +9 -8
  44. package/docs/COMMAND_REFERENCE.md +97 -32
  45. package/docs/ELECTRON.md +10 -10
  46. package/docs/RELEASE.md +3 -2
  47. package/docs/SUPPORT_MATRIX.md +22 -19
  48. package/docs/TOOL_CONTRACT.md +31 -28
  49. package/docs/platform-smoke.md +5 -5
  50. package/package.json +1 -1
  51. package/platform-smoke.config.mjs +3 -1
  52. package/scripts/agent-browser-capability-baseline.mjs +45 -3
@@ -1,23 +1,25 @@
1
- import { readFile, rm } from "node:fs/promises";
1
+ import { rm } from "node:fs/promises";
2
2
  import { isCloseCommand, isNavigationObservableCommandName, isOpenNavigationCommand } from "../../command-taxonomy.js";
3
3
  import { OPEN_RESULT_TAB_CORRECTION_FLAGS } from "../../launch-scoped-flags.js";
4
4
  import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
5
5
  import { getAllowedDomainsViolation, parseAllowedDomainsPolicyFromArgs } from "../../navigation-policy.js";
6
+ import { getManagedSessionResultingPageState, getObservedBrowserPageValidationError, managedSessionCommandRequiresLivePageVerification } from "../../managed-session-state-policy.js";
6
7
  import { analyzeNetworkSourceLookupResults, analyzeQaPresetResults, analyzeQaPresetTimeout, analyzeSourceLookupResults, buildQaCompactPassText, extractQaPageContext, redactNetworkSourceLookupAnalysis, } from "../../input-modes.js";
7
8
  import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics, buildToolPresentation, getAgentBrowserErrorText, parseAgentBrowserEnvelope, } from "../../results.js";
8
- import { buildEvictedSessionArtifactEntries, formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest, } from "../../results/artifact-manifest.js";
9
9
  import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
10
10
  import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
11
11
  import { commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
12
- import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../../temp.js";
13
12
  import { isRecord } from "../../parsing.js";
13
+ import { pruneOwnedManagedSessionRestoreSnapshots } from "../../managed-session-restore.js";
14
+ import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
14
15
  import { createFreshSessionName, extractCommandTokens, resolveManagedSessionState } from "../../runtime.js";
15
- import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, closeManagedSession, collectOpenResultTabCorrection, collectSessionTabSelection, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, unwrapPinnedSessionBatchEnvelope, updateTraceOwnerState, } from "./session-state.js";
16
+ import { closeManagedSession, inspectManagedSessionDaemon } from "./managed-session-daemon-policy.js";
17
+ 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";
16
18
  import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
17
19
  import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
18
20
  import { repairScreenshotData } from "./prepare.js";
19
21
  import { getPersistentSessionArtifactStore } from "./session-artifacts.js";
20
- import { buildFinalAgentBrowserToolResult, buildRedactedPresentationContent, buildWrapperRecoveryHint, prepareFinalResultRecoveryState, redactExactSensitiveText, redactExactSensitiveValue, } from "./final-result.js";
22
+ import { buildFinalAgentBrowserToolResult, buildRedactedPresentationContent, buildWrapperRecoveryHint, prepareFinalResultRecoveryState, redactExactSensitiveValue, } from "./final-result.js";
21
23
  async function repairScreenshotArtifact(options) {
22
24
  const { cwd, envelope, request } = options;
23
25
  if (!request || !envelope || !isRecord(envelope.data))
@@ -93,37 +95,6 @@ function applyBatchNetworkRouteState(options) {
93
95
  }
94
96
  return setNetworkRouteState({ routes, routesBySession: options.routesBySession, sessionName: options.sessionName });
95
97
  }
96
- export async function preserveParseFailureOutput(options) {
97
- if (!options.stdoutSpillPath)
98
- return {};
99
- try {
100
- const rawOutput = redactExactSensitiveText(await readFile(options.stdoutSpillPath, "utf8"), options.exactSensitiveValues ?? []);
101
- const nowMs = Date.now();
102
- let evictedArtifacts = [];
103
- let fullOutputPath;
104
- let storageScope;
105
- if (options.persistentArtifactStore) {
106
- const result = await writePersistentSessionArtifactFile({ content: rawOutput, prefix: "pi-agent-browser-parse-failure-output", store: options.persistentArtifactStore, suffix: ".txt" });
107
- fullOutputPath = result.path;
108
- evictedArtifacts = result.evictedArtifacts;
109
- storageScope = "persistent-session";
110
- }
111
- else {
112
- fullOutputPath = await writeSecureTempFile({ content: rawOutput, prefix: "pi-agent-browser-parse-failure-output", suffix: ".txt" });
113
- storageScope = "process-temp";
114
- }
115
- const artifactManifest = mergeSessionArtifactManifest({
116
- base: options.artifactManifest,
117
- entries: [{ command: "agent-browser", createdAtMs: nowMs, kind: "spill", path: fullOutputPath, retentionState: storageScope === "persistent-session" ? "live" : "ephemeral", storageScope }, ...buildEvictedSessionArtifactEntries(evictedArtifacts, nowMs)],
118
- nowMs,
119
- });
120
- return { artifactManifest, artifactRetentionSummary: artifactManifest ? formatSessionArtifactRetentionSummary(artifactManifest) : undefined, fullOutputPath };
121
- }
122
- catch (error) {
123
- const message = error instanceof Error ? error.message : String(error);
124
- return { fullOutputUnavailable: message };
125
- }
126
- }
127
98
  export async function processBrowserOutput(input) {
128
99
  const { ctx, cwd, electronPostCommandStatusSettleMs, implicitSessionCloseTimeoutMs, sessionPageStateUpdate, signal, state } = input;
129
100
  const { prepared, processResult } = input;
@@ -154,9 +125,16 @@ export async function processBrowserOutput(input) {
154
125
  presentationEnvelope = repairedBatchScreenshots.envelope;
155
126
  const screenshotArtifactRequest = repairedScreenshot.request;
156
127
  const batchScreenshotArtifactRequests = repairedBatchScreenshots.requests;
128
+ const rawCloseStatePath = isCloseCommand(prepared.executionPlan.commandInfo.command)
129
+ && isRecord(presentationEnvelope?.data)
130
+ && typeof presentationEnvelope.data.statePath === "string"
131
+ ? presentationEnvelope.data.statePath
132
+ : undefined;
157
133
  if (presentationEnvelope && prepared.exactSensitiveValues.length > 0)
158
134
  presentationEnvelope = redactExactSensitiveValue(presentationEnvelope, prepared.exactSensitiveValues);
159
- const parseFailureOutput = parseError ? await preserveParseFailureOutput({ artifactManifest, exactSensitiveValues: prepared.exactSensitiveValues, persistentArtifactStore, stdoutSpillPath: processResult.stdoutSpillPath }) : {};
135
+ const parseFailureOutput = parseError && processResult.stdoutSpillPath
136
+ ? { fullOutputUnavailable: "Malformed upstream output was discarded because it may contain sensitive browser data." }
137
+ : {};
160
138
  const processSucceeded = !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
161
139
  const plainTextInspection = prepared.executionPlan.plainTextInspection && processSucceeded;
162
140
  const parseSucceeded = plainTextInspection || parseError === undefined;
@@ -176,6 +154,13 @@ export async function processBrowserOutput(input) {
176
154
  presentationEnvelope = { ...(presentationEnvelope ?? {}), error: clickDispatchDiagnostic.summary, success: false };
177
155
  }
178
156
  }
157
+ const resultingPageState = getManagedSessionResultingPageState({
158
+ args: prepared.executionPlan.effectiveArgs,
159
+ currentPageUrl: prepared.priorSessionTabTarget?.url,
160
+ pageUrlUnknown: prepared.priorSessionTabTargetUnknown === true,
161
+ stdin: prepared.runtimeToolStdin,
162
+ trustedFirstBatchTabSelection: prepared.pinnedBatchUnwrapMode !== undefined,
163
+ });
179
164
  const presentationDataRecord = isRecord(presentationEnvelope?.data) ? presentationEnvelope.data : undefined;
180
165
  const dataClicked = typeof presentationDataRecord?.clicked === "string" ? presentationDataRecord.clicked : undefined;
181
166
  const cssClickWithoutHref = prepared.executionPlan.commandInfo.command === "click" && dataClicked !== undefined && !dataClicked.startsWith("@") && !dataClicked.startsWith("ref=") && typeof presentationDataRecord?.href !== "string";
@@ -189,10 +174,11 @@ export async function processBrowserOutput(input) {
189
174
  (shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data) ||
190
175
  shouldCaptureSemanticActionNavigationSummary(prepared.compiledSemanticAction, presentationEnvelope?.data) ||
191
176
  shouldCaptureAllowedDomainNavigationSummary ||
177
+ managedSessionCommandRequiresLivePageVerification(prepared.executionPlan.effectiveArgs, prepared.runtimeToolStdin) ||
192
178
  (prepared.executionPlan.commandInfo.command === "tab" && prepared.executionPlan.commandInfo.subcommand === "close"))) {
193
179
  navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
194
180
  }
195
- if (navigationSummary && presentationEnvelope && !Array.isArray(presentationEnvelope.data))
181
+ if (navigationSummary && presentationEnvelope && prepared.executionPlan.commandInfo.command !== "eval" && !Array.isArray(presentationEnvelope.data))
196
182
  presentationEnvelope = { ...presentationEnvelope, data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary) };
197
183
  let overlayBlockerDiagnostic;
198
184
  let openResultTabCorrection;
@@ -203,8 +189,20 @@ export async function processBrowserOutput(input) {
203
189
  if (plannedTabCorrection)
204
190
  openResultTabCorrection = await applyOpenResultTabCorrection({ correction: plannedTabCorrection, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
205
191
  }
206
- const observedSessionTabTarget = normalizeSessionTabTarget(navigationSummary) ?? extractSessionTabTargetFromBatchResults(presentationEnvelope?.data) ?? extractSessionTabTargetFromCommandData(prepared.commandTokens, presentationEnvelope?.data);
207
- let currentSessionTabTarget = deriveSessionTabTarget({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary, previousTarget: prepared.priorSessionTabTarget, subcommand: prepared.executionPlan.commandInfo.subcommand });
192
+ const verifiesCurrentUrl = prepared.executionPlan.commandInfo.command === "get" && prepared.executionPlan.commandInfo.subcommand === "url";
193
+ const trustsReportedPageTarget = !resultingPageState.pageUrlUnknown || verifiesCurrentUrl;
194
+ const observedSessionTabTarget = normalizeSessionTabTarget(navigationSummary)
195
+ ?? (trustsReportedPageTarget ? extractSessionTabTargetFromBatchResults(presentationEnvelope?.data) : undefined)
196
+ ?? (succeeded && trustsReportedPageTarget ? extractSessionTabTargetFromCommandData(prepared.commandTokens, presentationEnvelope?.data) : undefined);
197
+ const observedPageValidationError = succeeded ? getObservedBrowserPageValidationError(prepared.executionPlan.effectiveArgs, observedSessionTabTarget?.url, cwd) : undefined;
198
+ if (observedPageValidationError) {
199
+ succeeded = false;
200
+ presentationEnvelope = { error: observedPageValidationError, success: false };
201
+ }
202
+ const safeObservedSessionTabTarget = observedPageValidationError ? normalizeSessionTabTarget({ url: observedSessionTabTarget?.url }) : observedSessionTabTarget;
203
+ let currentSessionTabTarget = safeObservedSessionTabTarget ?? (resultingPageState.pageTargetMayHaveChanged
204
+ ? succeeded ? normalizeSessionTabTarget({ url: resultingPageState.currentPageUrl }) : undefined
205
+ : deriveSessionTabTarget({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary, previousTarget: prepared.priorSessionTabTarget, subcommand: prepared.executionPlan.commandInfo.subcommand }));
208
206
  let aboutBlankSessionMismatch;
209
207
  let electronPostCommandHealth;
210
208
  let electronRefFreshnessDiagnostic;
@@ -265,6 +263,9 @@ export async function processBrowserOutput(input) {
265
263
  let selectorTextVisibilityDiagnostics = [];
266
264
  let electronBroadGetTextScopeDiagnostics = [];
267
265
  const timeoutPartialProgress = processResult.timedOut ? await collectTimeoutPartialProgress({ command: prepared.executionPlan.commandInfo.command, compiledJob: prepared.compiledJob, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin }) : undefined;
266
+ if (!currentSessionTabTarget && timeoutPartialProgress?.currentPage?.source === "live") {
267
+ currentSessionTabTarget = normalizeSessionTabTarget(timeoutPartialProgress.currentPage);
268
+ }
268
269
  if (succeeded) {
269
270
  const fillRefSnapshot = prepared.resolvedSemanticActionRefSnapshot ?? prepared.priorRefSnapshotState;
270
271
  fillVerificationDiagnostic = await collectFillVerificationDiagnostic({ commandTokens: prepared.commandTokens, cwd, forceValueVerification: electronRecordForCommand !== undefined, namespace: prepared.executionPlan.namespace, refSnapshot: fillRefSnapshot, sessionName: prepared.executionPlan.sessionName, signal });
@@ -307,6 +308,9 @@ export async function processBrowserOutput(input) {
307
308
  if (!tabUpdate.applied && succeeded)
308
309
  sessionPageState.markPinning(sessionStateKey, "drift");
309
310
  }
311
+ else if (processResult.agentBrowserStarted && (resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged)) {
312
+ sessionPageState.markTabTargetUnknown({ sessionName: sessionStateKey, update: sessionPageStateUpdate });
313
+ }
310
314
  const refSnapshot = prepared.executionPlan.commandInfo.command === "batch" ? batchRefSnapshotState?.snapshot : succeeded ? prepared.executionPlan.commandInfo.command === "snapshot" ? extractRefSnapshotFromData(presentationEnvelope?.data) : prepared.resolvedSemanticActionRefSnapshot ?? overlayBlockerDiagnostic?.snapshot : undefined;
311
315
  if (refSnapshot) {
312
316
  const refUpdate = sessionPageState.applyRefSnapshot({ fallbackTarget: currentSessionTabTarget, sessionName: sessionStateKey, snapshot: refSnapshot, update: sessionPageStateUpdate });
@@ -331,19 +335,49 @@ export async function processBrowserOutput(input) {
331
335
  const policyBlockedFreshManagedSession = allowedDomainsViolation !== undefined && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName === prepared.executionPlan.sessionName;
332
336
  const postLaunchBatchFailure = !succeeded && processSucceeded && parseSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && batchStartedManagedBrowser(presentationEnvelope?.data);
333
337
  const postLaunchTimeoutWithPage = !succeeded && processResult.timedOut && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && timeoutPartialProgress?.liveUrlRecovered === true;
334
- const managedTransitionSucceeded = succeeded || policyBlockedFreshManagedSession || postLaunchBatchFailure || postLaunchTimeoutWithPage;
338
+ const failedFreshSessionMayHaveStarted = !succeeded
339
+ && (processResult.agentBrowserStarted || (!processResult.aborted && processResult.spawnError === undefined))
340
+ && prepared.sessionMode === "fresh"
341
+ && prepared.executionPlan.managedSessionName === prepared.executionPlan.sessionName;
342
+ const failedFreshDaemon = failedFreshSessionMayHaveStarted && prepared.executionPlan.sessionName
343
+ ? await inspectManagedSessionDaemon({
344
+ cwd,
345
+ namespace: prepared.executionPlan.namespace,
346
+ sessionName: prepared.executionPlan.sessionName,
347
+ timeoutMs: Math.min(implicitSessionCloseTimeoutMs, 2_000),
348
+ })
349
+ : undefined;
350
+ if (failedFreshDaemon?.status === "active") {
351
+ state.managedSessionRestoreState.recordDaemonRestoreKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace, failedFreshDaemon.restoreKey);
352
+ }
353
+ // Only a confirmed inactive daemon proves that a started fresh command did not establish browser ownership.
354
+ const postLaunchFreshFailure = failedFreshDaemon !== undefined && failedFreshDaemon.status !== "inactive";
355
+ const managedTransitionSucceeded = succeeded || policyBlockedFreshManagedSession || postLaunchBatchFailure || postLaunchTimeoutWithPage || postLaunchFreshFailure;
335
356
  const managedSessionState = resolveManagedSessionState({ command: prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, managedSessionNamespace: prepared.executionPlan.namespace, priorActive: priorManagedSessionActive, priorNamespace: priorManagedSessionNamespace, priorSessionName: priorManagedSessionName, succeeded: managedTransitionSucceeded });
357
+ if (!managedTransitionSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName) {
358
+ state.managedSessionRestoreState.clear(prepared.executionPlan.managedSessionName, prepared.executionPlan.namespace);
359
+ }
336
360
  const replacedManagedSessionName = managedSessionState.replacedSessionName;
337
361
  managedSessionActive = managedSessionState.active;
338
362
  managedSessionName = managedSessionState.sessionName;
339
363
  managedSessionNamespace = managedSessionState.namespace;
340
364
  if (commandClosesSession && succeeded && managedCloseSessionName === priorManagedSessionName && !managedSessionActive) {
365
+ const daemonRestoreKey = state.managedSessionRestoreState.getDaemonRestoreKey(managedCloseSessionName, priorManagedSessionNamespace);
366
+ const ownedRestoreKey = !state.managedSessionRestoreState.isDisabled(managedCloseSessionName, priorManagedSessionNamespace)
367
+ && isManagedSessionRestoreKey(daemonRestoreKey) ? daemonRestoreKey : null;
368
+ state.managedSessionRestoreState.clear(managedCloseSessionName, priorManagedSessionNamespace);
369
+ pruneOwnedManagedSessionRestoreSnapshots({
370
+ cwd,
371
+ namespace: priorManagedSessionNamespace,
372
+ restoreKey: ownedRestoreKey,
373
+ statePath: rawCloseStatePath,
374
+ });
341
375
  freshSessionOrdinal += 1;
342
376
  managedSessionName = createFreshSessionName(state.managedSessionBaseName, state.ephemeralSessionSeed, freshSessionOrdinal);
343
377
  managedSessionNamespace = undefined;
344
378
  }
345
379
  let managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: managedSessionActive, activeBefore: priorManagedSessionActive, attemptedSessionName: managedCloseSessionName, command: prepared.executionPlan.commandInfo.command, currentSessionName: managedSessionName, currentSessionNamespace: managedSessionNamespace, previousSessionName: priorManagedSessionName, replacedSessionName: replacedManagedSessionName, replacedSessionNamespace: priorManagedSessionNamespace, sessionMode: prepared.sessionMode, succeeded: managedTransitionSucceeded });
346
- if (prepared.executionPlan.managedSessionName && succeeded && managedSessionActive) {
380
+ if (prepared.executionPlan.managedSessionName && managedTransitionSucceeded && managedSessionActive) {
347
381
  managedSessionCwd = cwd;
348
382
  managedSessionNamespace = prepared.executionPlan.namespace;
349
383
  }
@@ -360,7 +394,7 @@ export async function processBrowserOutput(input) {
360
394
  networkRoutesBySession = new Map(networkRoutesBySession);
361
395
  networkRoutesBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
362
396
  sessionPageState.clearSession(replacedSessionStateKey ?? replacedManagedSessionName);
363
- const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, namespace: priorManagedSessionNamespace, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
397
+ const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, namespace: priorManagedSessionNamespace, restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
364
398
  if (!replacedCloseError)
365
399
  state.closedManagedSessionNames.add(replacedSessionStateKey ?? replacedManagedSessionName);
366
400
  }
@@ -369,23 +403,61 @@ export async function processBrowserOutput(input) {
369
403
  let electronHandoff = prepared.electronHandoff;
370
404
  if (prepared.electronLaunch) {
371
405
  if (succeeded && prepared.executionPlan.sessionName) {
372
- electronLaunchRecord = { ...prepared.electronLaunch.record, sessionName: prepared.executionPlan.sessionName };
373
- electronLaunchRecords.set(electronLaunchRecord.launchId, electronLaunchRecord);
374
- electronChildProcesses.set(electronLaunchRecord.launchId, prepared.electronLaunch.child);
406
+ const electronSessionName = prepared.executionPlan.sessionName;
407
+ const electronSessionStateKey = sessionStateKey ?? electronSessionName;
408
+ electronLaunchRecord = { ...prepared.electronLaunch.record, sessionName: electronSessionName };
375
409
  const electronHandoffMode = prepared.compiledElectron?.action === "launch" ? prepared.compiledElectron.handoff : "connect";
376
410
  try {
377
- electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
411
+ electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, namespace: prepared.executionPlan.namespace, sessionName: electronSessionName, signal });
378
412
  }
379
413
  catch (error) {
380
- electronHandoff = { error: error instanceof Error ? error.message : String(error), handoff: electronHandoffMode };
414
+ electronHandoff = {
415
+ error: error instanceof Error ? error.message : String(error),
416
+ failureCategory: signal?.aborted ? "aborted" : "upstream-error",
417
+ handoff: electronHandoffMode,
418
+ };
419
+ }
420
+ if (electronHandoff.error) {
421
+ succeeded = false;
422
+ presentationEnvelope = { error: electronHandoff.error, success: false };
423
+ const closeError = await closeManagedSession({ cwd, namespace: prepared.executionPlan.namespace, policyLock: prepared.managedSessionPolicyLock, restoreState: state.managedSessionRestoreState, sessionName: electronSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
424
+ electronFailedConnectCleanup = await cleanupElectronLaunchResources({ child: prepared.electronLaunch.child, record: electronLaunchRecord, timeoutMs: implicitSessionCloseTimeoutMs });
425
+ electronLaunchRecord = electronFailedConnectCleanup.record;
426
+ if (electronFailedConnectCleanup.partial) {
427
+ electronLaunchRecords.set(electronLaunchRecord.launchId, electronLaunchRecord);
428
+ electronChildProcesses.set(electronLaunchRecord.launchId, prepared.electronLaunch.child);
429
+ }
430
+ else {
431
+ electronLaunchRecords.delete(electronLaunchRecord.launchId);
432
+ electronChildProcesses.delete(electronLaunchRecord.launchId);
433
+ }
434
+ if (!closeError) {
435
+ state.closedManagedSessionNames.add(electronSessionStateKey);
436
+ allowedDomainsBySession = new Map(allowedDomainsBySession);
437
+ allowedDomainsBySession.delete(electronSessionStateKey);
438
+ networkRoutesBySession = new Map(networkRoutesBySession);
439
+ networkRoutesBySession.delete(electronSessionStateKey);
440
+ sessionPageState.clearSession(electronSessionStateKey);
441
+ if (managedSessionName === electronSessionName && managedSessionNamespace === prepared.executionPlan.namespace) {
442
+ managedSessionActive = false;
443
+ freshSessionOrdinal += 1;
444
+ managedSessionName = createFreshSessionName(state.managedSessionBaseName, state.ephemeralSessionSeed, freshSessionOrdinal);
445
+ managedSessionNamespace = undefined;
446
+ }
447
+ }
448
+ managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: managedSessionActive, activeBefore: priorManagedSessionActive, attemptedSessionName: electronSessionName, command: prepared.executionPlan.commandInfo.command, currentSessionName: managedSessionName, currentSessionNamespace: managedSessionNamespace, previousSessionName: priorManagedSessionName, replacedSessionName: replacedManagedSessionName, replacedSessionNamespace: priorManagedSessionNamespace, sessionMode: prepared.sessionMode, succeeded: false });
381
449
  }
382
- if (electronHandoff?.refSnapshot) {
383
- const refUpdate = sessionPageState.applyRefSnapshot({ sessionName: sessionStateKey ?? prepared.executionPlan.sessionName ?? "", snapshot: electronHandoff.refSnapshot, update: sessionPageStateUpdate });
384
- currentRefSnapshot = refUpdate.refSnapshot;
385
- currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
386
- if (electronHandoff.refSnapshot.target) {
387
- currentSessionTabTarget = electronHandoff.refSnapshot.target;
388
- sessionPageState.applyTabTarget({ sessionName: sessionStateKey ?? prepared.executionPlan.sessionName ?? "", target: electronHandoff.refSnapshot.target, update: sessionPageStateUpdate });
450
+ else {
451
+ electronLaunchRecords.set(electronLaunchRecord.launchId, electronLaunchRecord);
452
+ electronChildProcesses.set(electronLaunchRecord.launchId, prepared.electronLaunch.child);
453
+ if (electronHandoff.refSnapshot) {
454
+ const refUpdate = sessionPageState.applyRefSnapshot({ sessionName: electronSessionStateKey, snapshot: electronHandoff.refSnapshot, update: sessionPageStateUpdate });
455
+ currentRefSnapshot = refUpdate.refSnapshot;
456
+ currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
457
+ if (electronHandoff.refSnapshot.target) {
458
+ const targetUpdate = sessionPageState.applyTabTarget({ sessionName: electronSessionStateKey, target: electronHandoff.refSnapshot.target, update: sessionPageStateUpdate });
459
+ currentSessionTabTarget = targetUpdate.tabTarget;
460
+ }
389
461
  }
390
462
  }
391
463
  }
@@ -402,6 +474,10 @@ export async function processBrowserOutput(input) {
402
474
  presentationEnvelope = { ...presentationEnvelope, error: redactClipboardPermissionErrorValue(prepared.executionPlan.commandInfo, presentationEnvelope.error, clipboardWritePayloadCandidates) };
403
475
  }
404
476
  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, 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 });
477
+ if (observedPageValidationError)
478
+ presentation.failureCategory = "validation-error";
479
+ if (electronHandoff?.error && electronHandoff.failureCategory)
480
+ presentation.failureCategory = electronHandoff.failureCategory;
405
481
  networkRoutesBySession = applyBatchNetworkRouteState({ data: presentationEnvelope?.data, routesBySession: networkRoutesBySession, sessionName: sessionStateKey, succeeded });
406
482
  if (presentation.resultCategory === "failure" && succeeded) {
407
483
  succeeded = false;
@@ -494,7 +570,11 @@ export async function processBrowserOutput(input) {
494
570
  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 });
495
571
  currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
496
572
  currentRefSnapshotInvalidation = finalRecoveryState.currentRefSnapshotInvalidation;
497
- const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, inspectionText, managedSessionOutcome, navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
573
+ const authoritativePageState = sessionStateKey ? sessionPageState.get(sessionStateKey) : undefined;
574
+ if (sessionStateKey)
575
+ currentSessionTabTarget = authoritativePageState?.tabTarget;
576
+ const currentSessionTabTargetUnknown = authoritativePageState?.tabTargetUnknown === true ? true : undefined;
577
+ 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, inspectionText, 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, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
498
578
  const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession };
499
579
  return { result, statePatch };
500
580
  }
@@ -1,10 +1,10 @@
1
1
  import { rm } from "node:fs/promises";
2
2
  import { runAgentBrowserProcess } from "../../process.js";
3
- import { buildAgentBrowserNextActions, getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results.js";
3
+ import { buildAgentBrowserNextActions, parseAgentBrowserEnvelope } from "../../results.js";
4
4
  import { buildNextToolAction, withOptionalNamespaceArgs, withOptionalSessionArgs } from "../../results/next-actions.js";
5
5
  import { getSessionPageStateKey, isAboutBlankUrl, normalizeComparableUrl, normalizeSessionTabTarget, targetsMatch, } from "../../session-page-state.js";
6
6
  import { isCloseCommand, isElectronPostCommandHealthCommand, isNavigationObservableCommandName, isRefGuardedCommand, isRefInvalidatingBatchCommand, isSessionTabPinningExcludedCommand, isSessionTabPostCommandCorrectionExcludedCommand, } from "../../command-taxonomy.js";
7
- import { chooseOpenResultTabCorrection, redactInvocationArgs } from "../../runtime.js";
7
+ import { chooseOpenResultTabCorrection } from "../../runtime.js";
8
8
  import { isRecord } from "../../parsing.js";
9
9
  import { parseUserBatchStdin } from "../batch-stdin.js";
10
10
  export const NAVIGATION_SUMMARY_EVAL = `({ title: document.title, url: location.href })`;
@@ -29,8 +29,17 @@ export function applyBrowserRunStatePatch(state, patch) {
29
29
  state.networkRoutesBySession = patch.networkRoutesBySession;
30
30
  }
31
31
  export const getSessionContextKey = getSessionPageStateKey;
32
- export function buildSessionDetailFields(sessionName, usedImplicitSession, namespace) {
33
- return { ...(namespace ? { namespace } : {}), ...(sessionName ? { sessionName, usedImplicitSession } : {}) };
32
+ export function buildSessionDetailFields(sessionName, usedImplicitSession, namespace, managedSessionRestoreDisabled = false) {
33
+ return {
34
+ ...(namespace ? { namespace } : {}),
35
+ ...(sessionName
36
+ ? {
37
+ sessionName,
38
+ usedImplicitSession,
39
+ ...(managedSessionRestoreDisabled ? { managedSessionRestoreDisabled: true } : {}),
40
+ }
41
+ : {}),
42
+ };
34
43
  }
35
44
  export function buildManagedSessionOutcome(options) {
36
45
  const { activeAfter, activeBefore, attemptedSessionName, command, currentSessionName, currentSessionNamespace, previousSessionName, replacedSessionName, replacedSessionNamespace, sessionMode, succeeded } = options;
@@ -224,6 +233,8 @@ export function extractNavigationSummaryFromData(data) {
224
233
  return title || url ? { title, url } : undefined;
225
234
  }
226
235
  export function shouldCaptureNavigationSummary(command, data) {
236
+ if (command === "eval")
237
+ return true;
227
238
  if (isRecord(data) && typeof data.clicked === "string" && !data.clicked.startsWith("@") && !data.clicked.startsWith("ref=") && typeof data.href !== "string")
228
239
  return false;
229
240
  return (isNavigationObservableCommandName(command) &&
@@ -481,11 +492,12 @@ export function unwrapPinnedSessionBatchEnvelope(options) {
481
492
  };
482
493
  }
483
494
  export async function runSessionCommandData(options) {
484
- const { args, cwd, namespace, sessionName, signal, stdin, timeoutMs } = options;
495
+ const { allowManagedSessionTarget, args, cwd, namespace, pinNamespace, sessionName, signal, stdin, throwOnFailure, timeoutMs } = options;
485
496
  if (!sessionName)
486
497
  return undefined;
487
498
  const processResult = await runAgentBrowserProcess({
488
- args: ["--json", ...(namespace ? ["--namespace", namespace] : []), "--session", sessionName, ...args],
499
+ allowManagedSessionTarget,
500
+ args: ["--json", ...(namespace !== undefined || pinNamespace ? ["--namespace", namespace ?? ""] : []), "--session", sessionName, ...args],
489
501
  cwd,
490
502
  signal,
491
503
  stdin,
@@ -493,6 +505,14 @@ export async function runSessionCommandData(options) {
493
505
  });
494
506
  try {
495
507
  if (processResult.aborted || processResult.spawnError || processResult.exitCode !== 0) {
508
+ if (throwOnFailure) {
509
+ const reason = processResult.aborted
510
+ ? "command was aborted"
511
+ : processResult.spawnError
512
+ ? "process could not start"
513
+ : `process exited with code ${processResult.exitCode}`;
514
+ throw new Error(`agent-browser ${reason}`);
515
+ }
496
516
  return undefined;
497
517
  }
498
518
  const parsed = await parseAgentBrowserEnvelope({
@@ -500,6 +520,8 @@ export async function runSessionCommandData(options) {
500
520
  stdoutPath: processResult.stdoutSpillPath,
501
521
  });
502
522
  if (parsed.parseError || parsed.envelope?.success === false) {
523
+ if (throwOnFailure)
524
+ throw new Error(parsed.parseError ? "agent-browser returned invalid structured output" : "agent-browser reported failure");
503
525
  return undefined;
504
526
  }
505
527
  return parsed.envelope?.data;
@@ -728,38 +750,4 @@ export function buildElectronRefFreshnessDiagnostic(options) {
728
750
  export function formatElectronRefFreshnessText(diagnostic) {
729
751
  return diagnostic?.summary;
730
752
  }
731
- export async function closeManagedSession(options) {
732
- const controller = new AbortController();
733
- const timer = setTimeout(() => controller.abort(), options.timeoutMs);
734
- let stdoutSpillPath;
735
- const closeArgs = [...(options.namespace ? ["--namespace", options.namespace] : []), "--session", options.sessionName, "close"];
736
- try {
737
- const processResult = await runAgentBrowserProcess({
738
- args: closeArgs,
739
- cwd: options.cwd,
740
- signal: controller.signal,
741
- });
742
- stdoutSpillPath = processResult.stdoutSpillPath;
743
- return getAgentBrowserErrorText({
744
- aborted: processResult.aborted,
745
- command: "close",
746
- effectiveArgs: redactInvocationArgs(closeArgs),
747
- exitCode: processResult.exitCode,
748
- plainTextInspection: false,
749
- spawnError: processResult.spawnError,
750
- stderr: processResult.stderr,
751
- timedOut: processResult.timedOut,
752
- timeoutMs: processResult.timeoutMs,
753
- });
754
- }
755
- catch (error) {
756
- return error instanceof Error ? error.message : String(error);
757
- }
758
- finally {
759
- clearTimeout(timer);
760
- if (stdoutSpillPath) {
761
- await rm(stdoutSpillPath, { force: true }).catch(() => undefined);
762
- }
763
- }
764
- }
765
753
  export { extractBatchResultCommand };