From 43f01f1cf828798f627296e83ca067a80e4e42d5 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:49:13 -0700 Subject: [PATCH] refactor(native-chat): move the provider-exit proof out of structured-agent-session-adapter.ts so main's lint passes structured-agent-session-adapter.ts reached 301 lines once two changes on main met, one over the 300-line limit, so oxlint fails on main. The file is the adapter contract: its types, its interface and the typed failure verdicts. The only logic in it read what a cleanup or a stop proved about the provider child's exit: rethrowing a failed acquisition with the verdict its cleanup reached, and reading whether a stop left the provider root gone. That logic moves unchanged to structured-agent-session-provider-exit-proof.ts, which imports the verdicts from the adapter; nothing imports back. Its six importers now import from the new module. No behavior change. --- .../claude-structured-acquisition-launch.ts | 2 +- .../structured-agent-session-acquisition.ts | 2 +- .../structured-agent-session-adapter.test.ts | 4 +- .../structured-agent-session-adapter.ts | 59 +--------------- ...structured-agent-session-attach-failure.ts | 4 +- ...structured-agent-session-event-recovery.ts | 6 +- .../structured-agent-session-eviction.ts | 8 +-- ...tured-agent-session-provider-exit-proof.ts | 67 +++++++++++++++++++ 8 files changed, 80 insertions(+), 72 deletions(-) create mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-provider-exit-proof.ts diff --git a/src/main/claude/claude-structured-acquisition-launch.ts b/src/main/claude/claude-structured-acquisition-launch.ts index 05d2d39fbd3d..b3764b16d99e 100644 --- a/src/main/claude/claude-structured-acquisition-launch.ts +++ b/src/main/claude/claude-structured-acquisition-launch.ts @@ -1,9 +1,9 @@ import { AgentSessionAcquisitionExitUnprovenError, AgentSessionPreSpawnError, - stopAgentSessionProviderRoot, type StructuredAgentSessionAcquireInput } from '../native-chat/agent-session-wire/structured-agent-session-adapter' +import { stopAgentSessionProviderRoot } from '../native-chat/agent-session-wire/structured-agent-session-provider-exit-proof' import { withAgentSessionCreatePhase } from '../observability/agent-session-instrumentation' import type { ClaudeStructuredLaunch } from './claude-structured-launch-resolution' import { diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-acquisition.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-acquisition.ts index ac2f1823e5fc..6d6589521df7 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-acquisition.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-acquisition.ts @@ -3,9 +3,9 @@ import type { AgentSessionRecord } from '../../../shared/agent-session-record' import { AgentSessionPreSpawnError, isAgentSessionPreSpawnError, - rethrowAfterAgentSessionAcquisitionCleanup, type StructuredAgentSessionProviderChildPhase } from './structured-agent-session-adapter' +import { rethrowAfterAgentSessionAcquisitionCleanup } from './structured-agent-session-provider-exit-proof' import { journalIdentityFor } from './structured-agent-session-attach' import type { AttachFlowInput } from './structured-agent-session-attach-flow' import { readNativeSessionOptions } from './structured-agent-session-option-restoration' diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.test.ts index 93f9b9037df7..daa7d2028428 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.test.ts @@ -4,9 +4,9 @@ import { AgentSessionAcquisitionExitProvenError, AgentSessionAcquisitionExitUnprovenError, AgentSessionAcquisitionRefusal, - AgentSessionAcquisitionRootExitObservedError, - rethrowAfterAgentSessionAcquisitionCleanup + AgentSessionAcquisitionRootExitObservedError } from './structured-agent-session-adapter' +import { rethrowAfterAgentSessionAcquisitionCleanup } from './structured-agent-session-provider-exit-proof' describe('failed agent-session acquisition cleanup', () => { it('names a failure exit-proven after proven cleanup, keeping its diagnostic and cause', async () => { diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts index 4525cda08c3e..91f6d945c50b 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts @@ -31,10 +31,7 @@ import type { AgentSessionSlashCommand, AgentSessionThreadGoalChange } from '../../../shared/agent-session-wire' -import { - isAgentSessionWireRefusalCode, - type AgentSessionRefusalReason -} from '../../../shared/agent-session-wire-refusals' +import type { AgentSessionRefusalReason } from '../../../shared/agent-session-wire-refusals' import type { ProviderDiagnostic, SubmissionRejectionFact @@ -397,57 +394,3 @@ export type StructuredAgentSessionAdapter = StructuredAgentSessionAdapterStop & /** Host acknowledgement that the proven-dead child, lease and journal owner are released. */ acknowledgeSessionRelease?(sessionId: string): void } - -export async function rethrowAfterAgentSessionAcquisitionCleanup( - adapter: Pick, - sessionId: string, - cause: unknown -): Promise { - let released: boolean - try { - released = (await adapter.releaseAcquisition?.({ sessionId })) === true - } catch (cleanupError) { - // A root exit the cleanup observed first-hand keeps its classification and its - // provider diagnostic; the failure that triggered cleanup rides along as cause. - throw cleanupError instanceof AgentSessionAcquisitionRootExitObservedError - ? new AgentSessionAcquisitionRootExitObservedError( - new AggregateError([cause, cleanupError], cleanupError.message) - ) - : new AgentSessionAcquisitionExitUnprovenError( - new AggregateError([cause, cleanupError], 'agent session acquisition cleanup failed') - ) - } - if (released) { - throw provenExitAcquisitionFailure(cause) - } - throw new AgentSessionAcquisitionExitUnprovenError(cause) -} - -/** A failure whose child cleanup proved gone. One that already names its own verdict — a - * refusal, a typed exit proof, or a host store code — keeps it. */ -function provenExitAcquisitionFailure(cause: unknown): unknown { - const classified = - cause instanceof AgentSessionAcquisitionRefusal || - cause instanceof AgentSessionAcquisitionRootExitObservedError || - cause instanceof AgentSessionAcquisitionExitUnprovenError || - isAgentSessionPreSpawnError(cause) || - (cause instanceof Error && isAgentSessionWireRefusalCode(cause.message)) - return classified ? cause : new AgentSessionAcquisitionExitProvenError(cause) -} - -/** Whether a stop left the provider root gone. The lease follows the root, so a first-hand root - * exit or a processless child ends the session whatever its descendants did; any other - * failure still throws. */ -export async function stopAgentSessionProviderRoot(stop: () => Promise): Promise { - try { - return (await stop()) === true - } catch (error) { - if ( - error instanceof AgentSessionAcquisitionRootExitObservedError || - isAgentSessionPreSpawnError(error) - ) { - return true - } - throw error - } -} diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-attach-failure.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-attach-failure.ts index 16af00e32ea5..361c8f73aac8 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-attach-failure.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-attach-failure.ts @@ -2,9 +2,9 @@ import type { AgentSessionRecord } from '../../../shared/agent-session-record' import type { AttachFlowInput } from './structured-agent-session-attach-flow' import { AgentSessionAcquisitionExitUnprovenError, - AgentSessionAcquisitionRootExitObservedError, - rethrowAfterAgentSessionAcquisitionCleanup + AgentSessionAcquisitionRootExitObservedError } from './structured-agent-session-adapter' +import { rethrowAfterAgentSessionAcquisitionCleanup } from './structured-agent-session-provider-exit-proof' export async function settlePostAcquisitionAttachFailure( input: AttachFlowInput, diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-event-recovery.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-event-recovery.ts index f7a50627370e..56a0acf853d4 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-event-recovery.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-event-recovery.ts @@ -1,8 +1,6 @@ import { agentSessionFailureFact } from '../../../shared/agent-session-failure' -import { - stopAgentSessionProviderRoot, - type StructuredAgentSessionLifecycleEvent -} from './structured-agent-session-adapter' +import type { StructuredAgentSessionLifecycleEvent } from './structured-agent-session-adapter' +import { stopAgentSessionProviderRoot } from './structured-agent-session-provider-exit-proof' import type { StructuredAgentSessionHostDeps, StructuredAgentSessionHostSession diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-eviction.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-eviction.ts index fc45a2c77154..182d2e701921 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-eviction.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-eviction.ts @@ -17,11 +17,11 @@ // reach it; forgetting it anyway stranded the process forever and reported success. Leaving the // session in place is what makes the next close a real retry instead of a no-op. -import { - stopAgentSessionProviderRoot, - type StructuredAgentSessionAdapter, - type StructuredAgentSessionStopCause +import type { + StructuredAgentSessionAdapter, + StructuredAgentSessionStopCause } from './structured-agent-session-adapter' +import { stopAgentSessionProviderRoot } from './structured-agent-session-provider-exit-proof' import type { DeferredStructuredAgentSessionEventSink } from './structured-agent-session-event-sink' import type { StructuredAgentSessionStopVerdict } from './structured-agent-session-host-types' import { withTimeout } from '../../../shared/promise-timeout-fallback' diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-provider-exit-proof.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-provider-exit-proof.ts new file mode 100644 index 000000000000..9d27e570db4a --- /dev/null +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-provider-exit-proof.ts @@ -0,0 +1,67 @@ +// What a cleanup or a stop proved about a provider child's exit, read from the adapter's typed +// verdicts: a failed acquisition rethrows with the verdict its cleanup reached, and a stop answers +// whether the provider root is gone. + +import { isAgentSessionWireRefusalCode } from '../../../shared/agent-session-wire-refusals' +import { + AgentSessionAcquisitionExitProvenError, + AgentSessionAcquisitionExitUnprovenError, + AgentSessionAcquisitionRefusal, + AgentSessionAcquisitionRootExitObservedError, + isAgentSessionPreSpawnError, + type StructuredAgentSessionAdapter +} from './structured-agent-session-adapter' + +export async function rethrowAfterAgentSessionAcquisitionCleanup( + adapter: Pick, + sessionId: string, + cause: unknown +): Promise { + let released: boolean + try { + released = (await adapter.releaseAcquisition?.({ sessionId })) === true + } catch (cleanupError) { + // A root exit the cleanup observed first-hand keeps its classification and its + // provider diagnostic; the failure that triggered cleanup rides along as cause. + throw cleanupError instanceof AgentSessionAcquisitionRootExitObservedError + ? new AgentSessionAcquisitionRootExitObservedError( + new AggregateError([cause, cleanupError], cleanupError.message) + ) + : new AgentSessionAcquisitionExitUnprovenError( + new AggregateError([cause, cleanupError], 'agent session acquisition cleanup failed') + ) + } + if (released) { + throw provenExitAcquisitionFailure(cause) + } + throw new AgentSessionAcquisitionExitUnprovenError(cause) +} + +/** A failure whose child cleanup proved gone. One that already names its own verdict — a + * refusal, a typed exit proof, or a host store code — keeps it. */ +function provenExitAcquisitionFailure(cause: unknown): unknown { + const classified = + cause instanceof AgentSessionAcquisitionRefusal || + cause instanceof AgentSessionAcquisitionRootExitObservedError || + cause instanceof AgentSessionAcquisitionExitUnprovenError || + isAgentSessionPreSpawnError(cause) || + (cause instanceof Error && isAgentSessionWireRefusalCode(cause.message)) + return classified ? cause : new AgentSessionAcquisitionExitProvenError(cause) +} + +/** Whether a stop left the provider root gone. The lease follows the root, so a first-hand root + * exit or a processless child ends the session whatever its descendants did; any other + * failure still throws. */ +export async function stopAgentSessionProviderRoot(stop: () => Promise): Promise { + try { + return (await stop()) === true + } catch (error) { + if ( + error instanceof AgentSessionAcquisitionRootExitObservedError || + isAgentSessionPreSpawnError(error) + ) { + return true + } + throw error + } +}