pi-agent-browser-native 0.2.72 → 0.2.75

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 +33 -0
  2. package/README.md +14 -12
  3. package/dist/extensions/agent-browser/index.js +148 -16
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +122 -0
  5. package/dist/extensions/agent-browser/lib/command-taxonomy.js +11 -0
  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 +374 -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 +629 -430
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +148 -57
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +30 -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 +9 -8
  32. package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
  33. package/dist/extensions/agent-browser/lib/process.js +271 -34
  34. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
  35. package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
  36. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +35 -12
  37. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
  38. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
  39. package/dist/extensions/agent-browser/lib/runtime.js +114 -107
  40. package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
  41. package/dist/extensions/agent-browser/lib/temp.js +13 -25
  42. package/docs/ARCHITECTURE.md +10 -9
  43. package/docs/COMMAND_REFERENCE.md +43 -23
  44. package/docs/ELECTRON.md +10 -10
  45. package/docs/RELEASE.md +3 -2
  46. package/docs/REQUIREMENTS.md +1 -1
  47. package/docs/SUPPORT_MATRIX.md +19 -18
  48. package/docs/TOOL_CONTRACT.md +29 -26
  49. package/docs/platform-smoke.md +2 -2
  50. package/package.json +1 -1
  51. package/platform-smoke.config.mjs +1 -1
  52. package/scripts/agent-browser-capability-baseline.mjs +11 -3
@@ -1,23 +1,26 @@
1
- import { readFile, rm } from "node:fs/promises";
1
+ import { rm } from "node:fs/promises";
2
+ import { getAgentBrowserSessionIdentityKey } from "../../argv-grammar.js";
2
3
  import { isCloseCommand, isNavigationObservableCommandName, isOpenNavigationCommand } from "../../command-taxonomy.js";
3
4
  import { OPEN_RESULT_TAB_CORRECTION_FLAGS } from "../../launch-scoped-flags.js";
4
5
  import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
5
6
  import { getAllowedDomainsViolation, parseAllowedDomainsPolicyFromArgs } from "../../navigation-policy.js";
7
+ import { getManagedSessionResultingPageState, getObservedBrowserPageValidationError, managedSessionCommandRequiresLivePageVerification } from "../../managed-session-state-policy.js";
6
8
  import { analyzeNetworkSourceLookupResults, analyzeQaPresetResults, analyzeQaPresetTimeout, analyzeSourceLookupResults, buildQaCompactPassText, extractQaPageContext, redactNetworkSourceLookupAnalysis, } from "../../input-modes.js";
7
9
  import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics, buildToolPresentation, getAgentBrowserErrorText, parseAgentBrowserEnvelope, } from "../../results.js";
8
- import { buildEvictedSessionArtifactEntries, formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest, } from "../../results/artifact-manifest.js";
9
10
  import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
10
11
  import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
11
12
  import { commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
12
- import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../../temp.js";
13
13
  import { isRecord } from "../../parsing.js";
14
+ import { pruneOwnedManagedSessionRestoreSnapshots } from "../../managed-session-restore.js";
15
+ import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
14
16
  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";
17
+ import { closeManagedSession, inspectManagedSessionDaemon } from "./managed-session-daemon-policy.js";
18
+ 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
19
  import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
17
20
  import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
18
21
  import { repairScreenshotData } from "./prepare.js";
19
22
  import { getPersistentSessionArtifactStore } from "./session-artifacts.js";
20
- import { buildFinalAgentBrowserToolResult, buildRedactedPresentationContent, buildWrapperRecoveryHint, prepareFinalResultRecoveryState, redactExactSensitiveText, redactExactSensitiveValue, } from "./final-result.js";
23
+ import { buildFinalAgentBrowserToolResult, buildRedactedPresentationContent, buildWrapperRecoveryHint, prepareFinalResultRecoveryState, redactExactSensitiveValue, } from "./final-result.js";
21
24
  async function repairScreenshotArtifact(options) {
22
25
  const { cwd, envelope, request } = options;
23
26
  if (!request || !envelope || !isRecord(envelope.data))
@@ -93,37 +96,6 @@ function applyBatchNetworkRouteState(options) {
93
96
  }
94
97
  return setNetworkRouteState({ routes, routesBySession: options.routesBySession, sessionName: options.sessionName });
95
98
  }
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
99
  export async function processBrowserOutput(input) {
128
100
  const { ctx, cwd, electronPostCommandStatusSettleMs, implicitSessionCloseTimeoutMs, sessionPageStateUpdate, signal, state } = input;
129
101
  const { prepared, processResult } = input;
@@ -132,6 +104,7 @@ export async function processBrowserOutput(input) {
132
104
  let artifactManifest = state.artifactManifest;
133
105
  let freshSessionOrdinal = state.freshSessionOrdinal;
134
106
  let managedSessionActive = state.managedSessionActive;
107
+ let managedSessionCompatibilityWorkaround = state.managedSessionCompatibilityWorkaround;
135
108
  let managedSessionCwd = state.managedSessionCwd;
136
109
  let managedSessionName = state.managedSessionName;
137
110
  let managedSessionNamespace = state.managedSessionNamespace;
@@ -154,9 +127,16 @@ export async function processBrowserOutput(input) {
154
127
  presentationEnvelope = repairedBatchScreenshots.envelope;
155
128
  const screenshotArtifactRequest = repairedScreenshot.request;
156
129
  const batchScreenshotArtifactRequests = repairedBatchScreenshots.requests;
130
+ const rawCloseStatePath = isCloseCommand(prepared.executionPlan.commandInfo.command)
131
+ && isRecord(presentationEnvelope?.data)
132
+ && typeof presentationEnvelope.data.statePath === "string"
133
+ ? presentationEnvelope.data.statePath
134
+ : undefined;
157
135
  if (presentationEnvelope && prepared.exactSensitiveValues.length > 0)
158
136
  presentationEnvelope = redactExactSensitiveValue(presentationEnvelope, prepared.exactSensitiveValues);
159
- const parseFailureOutput = parseError ? await preserveParseFailureOutput({ artifactManifest, exactSensitiveValues: prepared.exactSensitiveValues, persistentArtifactStore, stdoutSpillPath: processResult.stdoutSpillPath }) : {};
137
+ const parseFailureOutput = parseError && processResult.stdoutSpillPath
138
+ ? { fullOutputUnavailable: "Malformed upstream output was discarded because it may contain sensitive browser data." }
139
+ : {};
160
140
  const processSucceeded = !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
161
141
  const plainTextInspection = prepared.executionPlan.plainTextInspection && processSucceeded;
162
142
  const parseSucceeded = plainTextInspection || parseError === undefined;
@@ -176,6 +156,13 @@ export async function processBrowserOutput(input) {
176
156
  presentationEnvelope = { ...(presentationEnvelope ?? {}), error: clickDispatchDiagnostic.summary, success: false };
177
157
  }
178
158
  }
159
+ const resultingPageState = getManagedSessionResultingPageState({
160
+ args: prepared.executionPlan.effectiveArgs,
161
+ currentPageUrl: prepared.priorSessionTabTarget?.url,
162
+ pageUrlUnknown: prepared.priorSessionTabTargetUnknown === true,
163
+ stdin: prepared.runtimeToolStdin,
164
+ trustedFirstBatchTabSelection: prepared.pinnedBatchUnwrapMode !== undefined,
165
+ });
179
166
  const presentationDataRecord = isRecord(presentationEnvelope?.data) ? presentationEnvelope.data : undefined;
180
167
  const dataClicked = typeof presentationDataRecord?.clicked === "string" ? presentationDataRecord.clicked : undefined;
181
168
  const cssClickWithoutHref = prepared.executionPlan.commandInfo.command === "click" && dataClicked !== undefined && !dataClicked.startsWith("@") && !dataClicked.startsWith("ref=") && typeof presentationDataRecord?.href !== "string";
@@ -189,10 +176,11 @@ export async function processBrowserOutput(input) {
189
176
  (shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data) ||
190
177
  shouldCaptureSemanticActionNavigationSummary(prepared.compiledSemanticAction, presentationEnvelope?.data) ||
191
178
  shouldCaptureAllowedDomainNavigationSummary ||
179
+ managedSessionCommandRequiresLivePageVerification(prepared.executionPlan.effectiveArgs, prepared.runtimeToolStdin) ||
192
180
  (prepared.executionPlan.commandInfo.command === "tab" && prepared.executionPlan.commandInfo.subcommand === "close"))) {
193
181
  navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
194
182
  }
195
- if (navigationSummary && presentationEnvelope && !Array.isArray(presentationEnvelope.data))
183
+ if (navigationSummary && presentationEnvelope && prepared.executionPlan.commandInfo.command !== "eval" && !Array.isArray(presentationEnvelope.data))
196
184
  presentationEnvelope = { ...presentationEnvelope, data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary) };
197
185
  let overlayBlockerDiagnostic;
198
186
  let openResultTabCorrection;
@@ -203,8 +191,20 @@ export async function processBrowserOutput(input) {
203
191
  if (plannedTabCorrection)
204
192
  openResultTabCorrection = await applyOpenResultTabCorrection({ correction: plannedTabCorrection, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
205
193
  }
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 });
194
+ const verifiesCurrentUrl = prepared.executionPlan.commandInfo.command === "get" && prepared.executionPlan.commandInfo.subcommand === "url";
195
+ const trustsReportedPageTarget = !resultingPageState.pageUrlUnknown || verifiesCurrentUrl;
196
+ const observedSessionTabTarget = normalizeSessionTabTarget(navigationSummary)
197
+ ?? (trustsReportedPageTarget ? extractSessionTabTargetFromBatchResults(presentationEnvelope?.data) : undefined)
198
+ ?? (succeeded && trustsReportedPageTarget ? extractSessionTabTargetFromCommandData(prepared.commandTokens, presentationEnvelope?.data) : undefined);
199
+ const observedPageValidationError = succeeded ? getObservedBrowserPageValidationError(prepared.executionPlan.effectiveArgs, observedSessionTabTarget?.url, cwd) : undefined;
200
+ if (observedPageValidationError) {
201
+ succeeded = false;
202
+ presentationEnvelope = { error: observedPageValidationError, success: false };
203
+ }
204
+ const safeObservedSessionTabTarget = observedPageValidationError ? normalizeSessionTabTarget({ url: observedSessionTabTarget?.url }) : observedSessionTabTarget;
205
+ let currentSessionTabTarget = safeObservedSessionTabTarget ?? (resultingPageState.pageTargetMayHaveChanged
206
+ ? succeeded ? normalizeSessionTabTarget({ url: resultingPageState.currentPageUrl }) : undefined
207
+ : deriveSessionTabTarget({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary, previousTarget: prepared.priorSessionTabTarget, subcommand: prepared.executionPlan.commandInfo.subcommand }));
208
208
  let aboutBlankSessionMismatch;
209
209
  let electronPostCommandHealth;
210
210
  let electronRefFreshnessDiagnostic;
@@ -265,6 +265,9 @@ export async function processBrowserOutput(input) {
265
265
  let selectorTextVisibilityDiagnostics = [];
266
266
  let electronBroadGetTextScopeDiagnostics = [];
267
267
  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;
268
+ if (!currentSessionTabTarget && timeoutPartialProgress?.currentPage?.source === "live") {
269
+ currentSessionTabTarget = normalizeSessionTabTarget(timeoutPartialProgress.currentPage);
270
+ }
268
271
  if (succeeded) {
269
272
  const fillRefSnapshot = prepared.resolvedSemanticActionRefSnapshot ?? prepared.priorRefSnapshotState;
270
273
  fillVerificationDiagnostic = await collectFillVerificationDiagnostic({ commandTokens: prepared.commandTokens, cwd, forceValueVerification: electronRecordForCommand !== undefined, namespace: prepared.executionPlan.namespace, refSnapshot: fillRefSnapshot, sessionName: prepared.executionPlan.sessionName, signal });
@@ -307,6 +310,9 @@ export async function processBrowserOutput(input) {
307
310
  if (!tabUpdate.applied && succeeded)
308
311
  sessionPageState.markPinning(sessionStateKey, "drift");
309
312
  }
313
+ else if (processResult.agentBrowserStarted && (resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged)) {
314
+ sessionPageState.markTabTargetUnknown({ sessionName: sessionStateKey, update: sessionPageStateUpdate });
315
+ }
310
316
  const refSnapshot = prepared.executionPlan.commandInfo.command === "batch" ? batchRefSnapshotState?.snapshot : succeeded ? prepared.executionPlan.commandInfo.command === "snapshot" ? extractRefSnapshotFromData(presentationEnvelope?.data) : prepared.resolvedSemanticActionRefSnapshot ?? overlayBlockerDiagnostic?.snapshot : undefined;
311
317
  if (refSnapshot) {
312
318
  const refUpdate = sessionPageState.applyRefSnapshot({ fallbackTarget: currentSessionTabTarget, sessionName: sessionStateKey, snapshot: refSnapshot, update: sessionPageStateUpdate });
@@ -331,19 +337,58 @@ export async function processBrowserOutput(input) {
331
337
  const policyBlockedFreshManagedSession = allowedDomainsViolation !== undefined && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName === prepared.executionPlan.sessionName;
332
338
  const postLaunchBatchFailure = !succeeded && processSucceeded && parseSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && batchStartedManagedBrowser(presentationEnvelope?.data);
333
339
  const postLaunchTimeoutWithPage = !succeeded && processResult.timedOut && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && timeoutPartialProgress?.liveUrlRecovered === true;
334
- const managedTransitionSucceeded = succeeded || policyBlockedFreshManagedSession || postLaunchBatchFailure || postLaunchTimeoutWithPage;
340
+ const failedFreshSessionMayHaveStarted = !succeeded
341
+ && (processResult.agentBrowserStarted || (!processResult.aborted && processResult.spawnError === undefined))
342
+ && prepared.sessionMode === "fresh"
343
+ && prepared.executionPlan.managedSessionName === prepared.executionPlan.sessionName;
344
+ const failedFreshDaemon = failedFreshSessionMayHaveStarted && prepared.executionPlan.sessionName
345
+ ? await inspectManagedSessionDaemon({
346
+ cwd,
347
+ namespace: prepared.executionPlan.namespace,
348
+ sessionName: prepared.executionPlan.sessionName,
349
+ timeoutMs: Math.min(implicitSessionCloseTimeoutMs, 2_000),
350
+ })
351
+ : undefined;
352
+ if (failedFreshDaemon?.status === "active") {
353
+ state.managedSessionRestoreState.recordDaemonRestoreKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace, failedFreshDaemon.restoreKey);
354
+ }
355
+ // Only a confirmed inactive daemon proves that a started fresh command did not establish browser ownership.
356
+ const postLaunchFreshFailure = failedFreshDaemon !== undefined && failedFreshDaemon.status !== "inactive";
357
+ const managedTransitionSucceeded = succeeded || policyBlockedFreshManagedSession || postLaunchBatchFailure || postLaunchTimeoutWithPage || postLaunchFreshFailure;
335
358
  const managedSessionState = resolveManagedSessionState({ command: prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, managedSessionNamespace: prepared.executionPlan.namespace, priorActive: priorManagedSessionActive, priorNamespace: priorManagedSessionNamespace, priorSessionName: priorManagedSessionName, succeeded: managedTransitionSucceeded });
359
+ if (!managedTransitionSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName) {
360
+ state.managedSessionRestoreState.clear(prepared.executionPlan.managedSessionName, prepared.executionPlan.namespace);
361
+ }
336
362
  const replacedManagedSessionName = managedSessionState.replacedSessionName;
337
363
  managedSessionActive = managedSessionState.active;
338
364
  managedSessionName = managedSessionState.sessionName;
339
365
  managedSessionNamespace = managedSessionState.namespace;
366
+ const executionTargetsManagedSession = prepared.executionPlan.sessionName
367
+ && getAgentBrowserSessionIdentityKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace)
368
+ === getAgentBrowserSessionIdentityKey(managedSessionName, managedSessionNamespace);
369
+ if (!managedSessionActive) {
370
+ managedSessionCompatibilityWorkaround = undefined;
371
+ }
372
+ else if (managedTransitionSucceeded && executionTargetsManagedSession) {
373
+ managedSessionCompatibilityWorkaround = prepared.compatibilityWorkaround;
374
+ }
340
375
  if (commandClosesSession && succeeded && managedCloseSessionName === priorManagedSessionName && !managedSessionActive) {
376
+ const daemonRestoreKey = state.managedSessionRestoreState.getDaemonRestoreKey(managedCloseSessionName, priorManagedSessionNamespace);
377
+ const ownedRestoreKey = !state.managedSessionRestoreState.isDisabled(managedCloseSessionName, priorManagedSessionNamespace)
378
+ && isManagedSessionRestoreKey(daemonRestoreKey) ? daemonRestoreKey : null;
379
+ state.managedSessionRestoreState.clear(managedCloseSessionName, priorManagedSessionNamespace);
380
+ pruneOwnedManagedSessionRestoreSnapshots({
381
+ cwd,
382
+ namespace: priorManagedSessionNamespace,
383
+ restoreKey: ownedRestoreKey,
384
+ statePath: rawCloseStatePath,
385
+ });
341
386
  freshSessionOrdinal += 1;
342
387
  managedSessionName = createFreshSessionName(state.managedSessionBaseName, state.ephemeralSessionSeed, freshSessionOrdinal);
343
388
  managedSessionNamespace = undefined;
344
389
  }
345
390
  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) {
391
+ if (prepared.executionPlan.managedSessionName && managedTransitionSucceeded && managedSessionActive) {
347
392
  managedSessionCwd = cwd;
348
393
  managedSessionNamespace = prepared.executionPlan.namespace;
349
394
  }
@@ -360,7 +405,7 @@ export async function processBrowserOutput(input) {
360
405
  networkRoutesBySession = new Map(networkRoutesBySession);
361
406
  networkRoutesBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
362
407
  sessionPageState.clearSession(replacedSessionStateKey ?? replacedManagedSessionName);
363
- const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, namespace: priorManagedSessionNamespace, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
408
+ const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, namespace: priorManagedSessionNamespace, restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
364
409
  if (!replacedCloseError)
365
410
  state.closedManagedSessionNames.add(replacedSessionStateKey ?? replacedManagedSessionName);
366
411
  }
@@ -369,23 +414,61 @@ export async function processBrowserOutput(input) {
369
414
  let electronHandoff = prepared.electronHandoff;
370
415
  if (prepared.electronLaunch) {
371
416
  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);
417
+ const electronSessionName = prepared.executionPlan.sessionName;
418
+ const electronSessionStateKey = sessionStateKey ?? electronSessionName;
419
+ electronLaunchRecord = { ...prepared.electronLaunch.record, sessionName: electronSessionName };
375
420
  const electronHandoffMode = prepared.compiledElectron?.action === "launch" ? prepared.compiledElectron.handoff : "connect";
376
421
  try {
377
- electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
422
+ electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, namespace: prepared.executionPlan.namespace, sessionName: electronSessionName, signal });
378
423
  }
379
424
  catch (error) {
380
- electronHandoff = { error: error instanceof Error ? error.message : String(error), handoff: electronHandoffMode };
425
+ electronHandoff = {
426
+ error: error instanceof Error ? error.message : String(error),
427
+ failureCategory: signal?.aborted ? "aborted" : "upstream-error",
428
+ handoff: electronHandoffMode,
429
+ };
430
+ }
431
+ if (electronHandoff.error) {
432
+ succeeded = false;
433
+ presentationEnvelope = { error: electronHandoff.error, success: false };
434
+ const closeError = await closeManagedSession({ cwd, namespace: prepared.executionPlan.namespace, policyLock: prepared.managedSessionPolicyLock, restoreState: state.managedSessionRestoreState, sessionName: electronSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
435
+ electronFailedConnectCleanup = await cleanupElectronLaunchResources({ child: prepared.electronLaunch.child, record: electronLaunchRecord, timeoutMs: implicitSessionCloseTimeoutMs });
436
+ electronLaunchRecord = electronFailedConnectCleanup.record;
437
+ if (electronFailedConnectCleanup.partial) {
438
+ electronLaunchRecords.set(electronLaunchRecord.launchId, electronLaunchRecord);
439
+ electronChildProcesses.set(electronLaunchRecord.launchId, prepared.electronLaunch.child);
440
+ }
441
+ else {
442
+ electronLaunchRecords.delete(electronLaunchRecord.launchId);
443
+ electronChildProcesses.delete(electronLaunchRecord.launchId);
444
+ }
445
+ if (!closeError) {
446
+ state.closedManagedSessionNames.add(electronSessionStateKey);
447
+ allowedDomainsBySession = new Map(allowedDomainsBySession);
448
+ allowedDomainsBySession.delete(electronSessionStateKey);
449
+ networkRoutesBySession = new Map(networkRoutesBySession);
450
+ networkRoutesBySession.delete(electronSessionStateKey);
451
+ sessionPageState.clearSession(electronSessionStateKey);
452
+ if (managedSessionName === electronSessionName && managedSessionNamespace === prepared.executionPlan.namespace) {
453
+ managedSessionActive = false;
454
+ freshSessionOrdinal += 1;
455
+ managedSessionName = createFreshSessionName(state.managedSessionBaseName, state.ephemeralSessionSeed, freshSessionOrdinal);
456
+ managedSessionNamespace = undefined;
457
+ }
458
+ }
459
+ 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
460
  }
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 });
461
+ else {
462
+ electronLaunchRecords.set(electronLaunchRecord.launchId, electronLaunchRecord);
463
+ electronChildProcesses.set(electronLaunchRecord.launchId, prepared.electronLaunch.child);
464
+ if (electronHandoff.refSnapshot) {
465
+ const refUpdate = sessionPageState.applyRefSnapshot({ sessionName: electronSessionStateKey, snapshot: electronHandoff.refSnapshot, update: sessionPageStateUpdate });
466
+ currentRefSnapshot = refUpdate.refSnapshot;
467
+ currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
468
+ if (electronHandoff.refSnapshot.target) {
469
+ const targetUpdate = sessionPageState.applyTabTarget({ sessionName: electronSessionStateKey, target: electronHandoff.refSnapshot.target, update: sessionPageStateUpdate });
470
+ currentSessionTabTarget = targetUpdate.tabTarget;
471
+ }
389
472
  }
390
473
  }
391
474
  }
@@ -402,6 +485,10 @@ export async function processBrowserOutput(input) {
402
485
  presentationEnvelope = { ...presentationEnvelope, error: redactClipboardPermissionErrorValue(prepared.executionPlan.commandInfo, presentationEnvelope.error, clipboardWritePayloadCandidates) };
403
486
  }
404
487
  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 });
488
+ if (observedPageValidationError)
489
+ presentation.failureCategory = "validation-error";
490
+ if (electronHandoff?.error && electronHandoff.failureCategory)
491
+ presentation.failureCategory = electronHandoff.failureCategory;
405
492
  networkRoutesBySession = applyBatchNetworkRouteState({ data: presentationEnvelope?.data, routesBySession: networkRoutesBySession, sessionName: sessionStateKey, succeeded });
406
493
  if (presentation.resultCategory === "failure" && succeeded) {
407
494
  succeeded = false;
@@ -494,8 +581,12 @@ export async function processBrowserOutput(input) {
494
581
  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
582
  currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
496
583
  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 });
498
- const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession };
584
+ const authoritativePageState = sessionStateKey ? sessionPageState.get(sessionStateKey) : undefined;
585
+ if (sessionStateKey)
586
+ currentSessionTabTarget = authoritativePageState?.tabTarget;
587
+ const currentSessionTabTargetUnknown = authoritativePageState?.tabTargetUnknown === true ? true : undefined;
588
+ 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 });
589
+ const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession };
499
590
  return { result, statePatch };
500
591
  }
501
592
  finally {
@@ -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 })`;
@@ -19,6 +19,8 @@ export function applyBrowserRunStatePatch(state, patch) {
19
19
  state.freshSessionOrdinal = patch.freshSessionOrdinal;
20
20
  if (patch.managedSessionActive !== undefined)
21
21
  state.managedSessionActive = patch.managedSessionActive;
22
+ if ("managedSessionCompatibilityWorkaround" in patch)
23
+ state.managedSessionCompatibilityWorkaround = patch.managedSessionCompatibilityWorkaround;
22
24
  if (patch.managedSessionCwd !== undefined)
23
25
  state.managedSessionCwd = patch.managedSessionCwd;
24
26
  if (patch.managedSessionName !== undefined)
@@ -29,8 +31,17 @@ export function applyBrowserRunStatePatch(state, patch) {
29
31
  state.networkRoutesBySession = patch.networkRoutesBySession;
30
32
  }
31
33
  export const getSessionContextKey = getSessionPageStateKey;
32
- export function buildSessionDetailFields(sessionName, usedImplicitSession, namespace) {
33
- return { ...(namespace ? { namespace } : {}), ...(sessionName ? { sessionName, usedImplicitSession } : {}) };
34
+ export function buildSessionDetailFields(sessionName, usedImplicitSession, namespace, managedSessionRestoreDisabled = false) {
35
+ return {
36
+ ...(namespace ? { namespace } : {}),
37
+ ...(sessionName
38
+ ? {
39
+ sessionName,
40
+ usedImplicitSession,
41
+ ...(managedSessionRestoreDisabled ? { managedSessionRestoreDisabled: true } : {}),
42
+ }
43
+ : {}),
44
+ };
34
45
  }
35
46
  export function buildManagedSessionOutcome(options) {
36
47
  const { activeAfter, activeBefore, attemptedSessionName, command, currentSessionName, currentSessionNamespace, previousSessionName, replacedSessionName, replacedSessionNamespace, sessionMode, succeeded } = options;
@@ -224,6 +235,8 @@ export function extractNavigationSummaryFromData(data) {
224
235
  return title || url ? { title, url } : undefined;
225
236
  }
226
237
  export function shouldCaptureNavigationSummary(command, data) {
238
+ if (command === "eval")
239
+ return true;
227
240
  if (isRecord(data) && typeof data.clicked === "string" && !data.clicked.startsWith("@") && !data.clicked.startsWith("ref=") && typeof data.href !== "string")
228
241
  return false;
229
242
  return (isNavigationObservableCommandName(command) &&
@@ -481,11 +494,12 @@ export function unwrapPinnedSessionBatchEnvelope(options) {
481
494
  };
482
495
  }
483
496
  export async function runSessionCommandData(options) {
484
- const { args, cwd, namespace, sessionName, signal, stdin, timeoutMs } = options;
497
+ const { allowManagedSessionTarget, args, cwd, namespace, pinNamespace, sessionName, signal, stdin, throwOnFailure, timeoutMs } = options;
485
498
  if (!sessionName)
486
499
  return undefined;
487
500
  const processResult = await runAgentBrowserProcess({
488
- args: ["--json", ...(namespace ? ["--namespace", namespace] : []), "--session", sessionName, ...args],
501
+ allowManagedSessionTarget,
502
+ args: ["--json", ...(namespace !== undefined || pinNamespace ? ["--namespace", namespace ?? ""] : []), "--session", sessionName, ...args],
489
503
  cwd,
490
504
  signal,
491
505
  stdin,
@@ -493,6 +507,14 @@ export async function runSessionCommandData(options) {
493
507
  });
494
508
  try {
495
509
  if (processResult.aborted || processResult.spawnError || processResult.exitCode !== 0) {
510
+ if (throwOnFailure) {
511
+ const reason = processResult.aborted
512
+ ? "command was aborted"
513
+ : processResult.spawnError
514
+ ? "process could not start"
515
+ : `process exited with code ${processResult.exitCode}`;
516
+ throw new Error(`agent-browser ${reason}`);
517
+ }
496
518
  return undefined;
497
519
  }
498
520
  const parsed = await parseAgentBrowserEnvelope({
@@ -500,6 +522,8 @@ export async function runSessionCommandData(options) {
500
522
  stdoutPath: processResult.stdoutSpillPath,
501
523
  });
502
524
  if (parsed.parseError || parsed.envelope?.success === false) {
525
+ if (throwOnFailure)
526
+ throw new Error(parsed.parseError ? "agent-browser returned invalid structured output" : "agent-browser reported failure");
503
527
  return undefined;
504
528
  }
505
529
  return parsed.envelope?.data;
@@ -728,38 +752,4 @@ export function buildElectronRefFreshnessDiagnostic(options) {
728
752
  export function formatElectronRefFreshnessText(diagnostic) {
729
753
  return diagnostic?.summary;
730
754
  }
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
755
  export { extractBatchResultCommand };