pi-agent-browser-native 0.2.72 → 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.
- package/CHANGELOG.md +27 -0
- package/README.md +14 -12
- package/dist/extensions/agent-browser/index.js +104 -16
- package/dist/extensions/agent-browser/lib/argv-grammar.js +122 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +11 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
- package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
- package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +367 -0
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +625 -429
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +136 -56
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +28 -40
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
- package/dist/extensions/agent-browser/lib/playbook.js +9 -8
- package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
- package/dist/extensions/agent-browser/lib/process.js +270 -34
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +35 -12
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
- package/dist/extensions/agent-browser/lib/runtime.js +85 -85
- package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
- package/dist/extensions/agent-browser/lib/temp.js +13 -25
- package/docs/ARCHITECTURE.md +9 -8
- package/docs/COMMAND_REFERENCE.md +43 -23
- package/docs/ELECTRON.md +10 -10
- package/docs/RELEASE.md +3 -2
- package/docs/SUPPORT_MATRIX.md +19 -18
- package/docs/TOOL_CONTRACT.md +28 -25
- package/docs/platform-smoke.md +2 -2
- package/package.json +1 -1
- package/platform-smoke.config.mjs +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +11 -3
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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,
|
|
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
|
|
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
|
|
207
|
-
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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:
|
|
411
|
+
electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, namespace: prepared.executionPlan.namespace, sessionName: electronSessionName, signal });
|
|
378
412
|
}
|
|
379
413
|
catch (error) {
|
|
380
|
-
electronHandoff = {
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
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,
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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 };
|