Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
c867bd2
refactor(native-chat): remove the unused terminal handoff
brennanb2025 Sep 25, 2026
eab4ef1
fix(native-chat): never let the pre-stop snapshot hold a chat's stop
brennanb2025 Sep 25, 2026
abafbbc
refactor(native-chat): drop helpers only the terminal handoff called
brennanb2025 Sep 25, 2026
3cbf6b5
docs(native-chat): stop citing the removed handoff in lifecycle comments
brennanb2025 Sep 25, 2026
e048579
test(native-chat): type the stalled snapshot drain without a cast
brennanb2025 Sep 25, 2026
b50c574
test(native-chat): pin that a start dead before proving owes no settl…
brennanb2025 Sep 25, 2026
12efdef
fix(native-chat): keep the owner-status read behind an in-flight attach
brennanb2025 Sep 25, 2026
42d10ee
refactor(terminal): remove the agent-session PTY write gate
brennanb2025 Sep 25, 2026
0483915
refactor(native-chat): drop the transcript helpers only the handoff c…
brennanb2025 Sep 25, 2026
d0f4dae
fix(native-chat): stop calling a starting chat "mid-handoff"
brennanb2025 Sep 25, 2026
faecece
test(native-chat): type the stand-in roster decoder without a cast
brennanb2025 Sep 25, 2026
250250e
refactor(codex): name the pinned rollout lookup for what it does
brennanb2025 Sep 25, 2026
b588185
refactor(native-chat): type the owner-status reply as the host sends it
brennanb2025 Sep 25, 2026
35742fd
refactor(native-chat): normalize terminal-handoff lease values once a…
brennanb2025 Sep 25, 2026
54ce5fc
refactor(native-chat): stop threading the owner kind through a reserv…
brennanb2025 Sep 25, 2026
713b126
test(native-chat): pin the legacy-lease rewrite with a transaction th…
brennanb2025 Sep 25, 2026
44d7977
fix(native-chat): name a chat write by its target, not the owner gene…
brennanb2025 Sep 25, 2026
6dad39a
fix(native-chat): every journal append reaches the chats that are open
brennanb2025 Sep 25, 2026
97bf3bb
test(native-chat): an epoch replacement reaches the open chat
brennanb2025 Sep 25, 2026
427dc8d
test(native-chat): each row reaches an open chat once, and a live han…
brennanb2025 Sep 25, 2026
2bd44f5
test(native-chat): give the legacy-lease store test a tab id so the b…
brennanb2025 Sep 25, 2026
f79eb9b
test(worktree-activation): restore the OMP surfaced-agent resume test
brennanb2025 Sep 25, 2026
50d2e33
perf(native-chat): a publish behind a delivered commit reads nothing
brennanb2025 Sep 25, 2026
bd72b13
test(native-chat): state why the teardown test's fake journal is safe…
brennanb2025 Sep 25, 2026
fa9b76a
docs(native-chat): say mutation admission checks only the writer lease
brennanb2025 Sep 25, 2026
5435973
docs(native-chat): drop the send rebase from comments that still desc…
brennanb2025 Sep 25, 2026
8fd4558
Merge #22811 into the PR 1 base
brennanb2025 Sep 25, 2026
12dde37
Merge #22812 into the PR 1 base
brennanb2025 Sep 25, 2026
c753e8a
fix(native-chat): a message is accepted, then delivered
brennanb2025 Sep 25, 2026
82f9bfd
fix(native-chat): settle queued messages only for the child that ended
brennanb2025 Sep 25, 2026
f3e52d2
fix(native-chat): an adoption that fails to import keeps the conversa…
brennanb2025 Sep 25, 2026
29a340b
perf(native-chat): the recovering open reads the journal once
brennanb2025 Sep 25, 2026
c7a652f
fix(native-chat): an attach that fails after indexing its child leave…
brennanb2025 Sep 25, 2026
34d0bb0
fix(native-chat): a withdrawn message shows no error, and a rejection…
brennanb2025 Sep 25, 2026
46b6472
fix(orchestration): a structured worker whose agent outlasts the prea…
brennanb2025 Sep 25, 2026
283819d
fix(native-chat): a message rejected while its chat was closed reads …
brennanb2025 Sep 25, 2026
40dabe8
test(orchestration): name why the readiness settlement fakes are cast
brennanb2025 Sep 25, 2026
31444bf
fix(native-chat): keep each pane's own fence on frames so a failed re…
brennanb2025 Sep 25, 2026
494d8c7
docs(native-chat): drop the fence from the admission the send effects…
brennanb2025 Sep 25, 2026
0e74529
docs(native-chat): give the fence move on release the reason that sti…
brennanb2025 Sep 25, 2026
6c16f35
docs(native-chat): stop citing a write fence check in launch and mail…
brennanb2025 Sep 25, 2026
d2f646b
refactor(native-chat): the provider child is its own record
brennanb2025 Sep 25, 2026
25fa38a
Merge remote-tracking branch 'origin/main' into brennanb2025/chat-wri…
brennanb2025 Sep 25, 2026
aa56b50
fix(native-chat): the delivery loop alone settles a message its start…
brennanb2025 Sep 25, 2026
d23cca4
Merge #22812 and main into PR 1
brennanb2025 Sep 25, 2026
4993696
refactor(native-chat): a stopped child ends on the one reading of its…
brennanb2025 Sep 25, 2026
a2ad8f2
feat(native-chat): the host says it accepts a send before any agent h…
brennanb2025 Sep 25, 2026
48def96
refactor(native-chat): an attach never opens a journal of its own
brennanb2025 Sep 25, 2026
da65d57
fix(native-chat): a moved fence resends nothing on a host that accept…
brennanb2025 Sep 25, 2026
0184a3d
refactor(native-chat): a child's end says whether the user or the hos…
brennanb2025 Sep 25, 2026
12577d8
fix(native-chat): a chat whose only work is a queued message is not o…
brennanb2025 Sep 25, 2026
a59eed9
test(native-chat): type the queued-message fixtures in the resume-off…
brennanb2025 Sep 25, 2026
ccaf412
fix(native-chat): a start that dies while a message waits on it is th…
brennanb2025 Sep 25, 2026
0efca93
docs(native-chat): say what an attach's open conversation and unconfi…
brennanb2025 Sep 25, 2026
74d6506
test(native-chat): pin what a failed start settles, and what a resume…
brennanb2025 Sep 25, 2026
9c671f1
test(native-chat): the failed-start pins fail on what the message bec…
brennanb2025 Sep 25, 2026
80ccb33
test(orchestration): the preamble's host stub is typed, not cast
brennanb2025 Sep 25, 2026
e6dadd1
Merge main into PR 1
brennanb2025 Sep 26, 2026
6374f35
Merge main into PR 1
brennanb2025 Sep 27, 2026
724b961
Merge main into PR 1
brennanb2025 Sep 27, 2026
c39151d
fix(native-chat): a view never restarts a chat whose last start failed
brennanb2025 Sep 27, 2026
9613178
test(native-chat): start the child the loop waits on with an attach, …
brennanb2025 Sep 27, 2026
0224459
fix(native-chat): settle a gone generation's turn wherever a conversa…
brennanb2025 Sep 27, 2026
2276cef
test(native-chat): prove the next child's start settles the turn an e…
brennanb2025 Sep 27, 2026
9275869
test(native-chat): count a failed start's rows by row, not by text
brennanb2025 Sep 27, 2026
158ca0a
test(native-chat): give the failed-start and stale-turn waits a loade…
brennanb2025 Sep 27, 2026
8f66a25
test(native-chat): pin the open's and the send's start and row counts…
brennanb2025 Sep 27, 2026
ad04c3b
fix(native-chat): settle a gone generation's turn at every open but a…
brennanb2025 Sep 27, 2026
05b8b93
test(native-chat): hold the create's start open until the views bind
brennanb2025 Sep 27, 2026
d5716f3
Merge main into PR 1
brennanb2025 Sep 27, 2026
740ed1f
Merge main into PR 1
brennanb2025 Sep 28, 2026
7002b1b
refactor(native-chat): drop the composer's second error formatter
brennanb2025 Sep 28, 2026
cb5ce82
test(native-chat): pin the reason on a message rejected while its cha…
brennanb2025 Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main/claude/claude-structured-dispatch-test-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ClaudeSession } from './claude-structured-session-state'
import { ClaudeBackgroundTaskTracker } from './claude-background-task-tracker'
import { ClaudeChildWorkDecoder } from './claude-child-work-decoder'
import { ClaudeSlashCommandCatalog } from './claude-slash-command-catalog'
import { createClaudeSessionStartupGate } from './claude-structured-session-startup-gate'
import { createClaudeSessionStartup } from './claude-structured-session-startup-state'

export function sessionFor(send: Mock = vi.fn().mockResolvedValue(undefined)): ClaudeSession {
return {
Expand All @@ -32,7 +32,7 @@ export function sessionFor(send: Mock = vi.fn().mockResolvedValue(undefined)): C
capabilities: [],
events: undefined,
translator: null,
startup: { ...createClaudeSessionStartupGate(), state: 'proven' }
startup: { ...createClaudeSessionStartup(), state: 'proven' }
}
}

Expand Down
19 changes: 4 additions & 15 deletions src/main/claude/claude-structured-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,8 @@ import {
import { AgentSessionPreDispatchError } from '../native-chat/agent-session-wire/structured-agent-session-operation-settlement'
import {
claudeStartupFailureReason,
claudeStartupHoldsWrites,
failClaudeStartupGate,
holdClaudeStartupWrite
} from './claude-structured-session-startup-gate'
failClaudeStartup
} from './claude-structured-session-startup-state'

const MAX_ACTIVE_DISPATCH_WAITERS = 64

Expand Down Expand Up @@ -226,7 +224,7 @@ export function settleCancelledClaudeDispatchWaiters(
* Retired rather than dropped: their identities stay joinable, bounded by
* `MAX_RETIRED_DISPATCH_WAITERS`. */
export function retireClaudeDispatchWaiters(session: ClaudeSession): void {
failClaudeStartupGate(session, new Error('claude stream-json ended before startup completed'))
failClaudeStartup(session, new Error('claude stream-json ended before startup completed'))
for (const waiter of session.dispatchWaiters.splice(0)) {
retireWaiter(session, waiter)
waiter.resolve(null)
Expand All @@ -236,8 +234,7 @@ export function retireClaudeDispatchWaiters(session: ClaudeSession): void {
export async function dispatchClaudeTurn(
session: ClaudeSession,
input: { clientMessageId?: string; body: AgentJournalMessageItem; requestedAt?: number },
beforeDispatch?: () => Promise<void>,
onSettledLate?: ClaudeLateDispatchSettlement
beforeDispatch?: () => Promise<void>
): Promise<AgentSessionDispatchOutcome> {
let content: unknown[]
try {
Expand Down Expand Up @@ -276,14 +273,6 @@ export async function dispatchClaudeTurn(
parent_tool_use_id: null,
session_id: session.providerSessionId
}
if (claudeStartupHoldsWrites(session)) {
return holdClaudeStartupWrite(session, {
message,
arm,
...(beforeDispatch ? { beforeDispatch } : {}),
...(onSettledLate ? { settleLate: onSettledLate } : {})
})
}
const pending = { replay: beforeDispatch ? undefined : arm() }
const authorize = beforeDispatch
? async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/main/claude/claude-structured-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { ClaudeBackgroundTaskTracker } from './claude-background-task-tracker'
import { ClaudeChildWorkDecoder } from './claude-child-work-decoder'
import { ClaudeSlashCommandCatalog } from './claude-slash-command-catalog'
import { createClaudeSessionStartupGate } from './claude-structured-session-startup-gate'
import { createClaudeSessionStartup } from './claude-structured-session-startup-state'
import {
claudeStructuredSessionOptionsFrom,
observeClaudeFastModeFacts,
Expand Down Expand Up @@ -48,7 +48,7 @@ function sessionFor(setModel: ClaudeSession['connection']['setModel']): ClaudeSe
capabilities: [],
events: undefined,
translator: null,
startup: { ...createClaudeSessionStartupGate(), state: 'proven' }
startup: { ...createClaudeSessionStartup(), state: 'proven' }
}
}

Expand Down
18 changes: 4 additions & 14 deletions src/main/claude/claude-structured-prompt-ownership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ import { buildClaudePromptReply } from './claude-structured-prompt-replies'
import type { ClaudeSession } from './claude-structured-session-state'
import type { ClaudePendingPrompt } from './claude-prompt-registry'
import type { PermissionResult } from '@anthropic-ai/claude-agent-sdk'
import {
claudeStartupHoldsWrites,
rejectClaudeStartupWrites
} from './claude-structured-session-startup-gate'
import { DISPATCH_REJECTED_CANCELLED } from '../../shared/structured-agent-session-dispatch-rejection'

/** Conservative user-facing window: below the 30s control deadline, trading
* residual slow-pump risk for ensuring delivery bookkeeping cannot block Stop indefinitely. */
Expand Down Expand Up @@ -107,14 +102,9 @@ export async function cancelClaudeStructuredTurn(input: {
const session = requireSession(sessions, request.sessionId)
const acquisitionGeneration = session.acquisitionGeneration
const prompt = request.prompt
// A held prompt was never written, so Stop withdraws it; the drain only writes what it still
// holds. Before startup lands nothing was written, so there is nothing to interrupt either.
let withdrewHeld = false
if (!prompt && claudeStartupHoldsWrites(session) && session.fence === request.fence) {
withdrewHeld = rejectClaudeStartupWrites(session, DISPATCH_REJECTED_CANCELLED)
if (session.startup.state === 'pending') {
return { cancelled: withdrewHeld }
}
// Before startup lands nothing was written, so there is nothing to interrupt.
if (!prompt && session.startup.state === 'pending') {
return { cancelled: false }
Comment on lines +105 to +107

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

set -eu
printf '%s\n' '--- diff target ---'
git diff --unified=80 16784c1a671f4a84a7993658c1387dade815f362 e6dadd18386daf1ce29f2c29454bbfed2f55630c -- src/main/claude/claude-structured-prompt-ownership.ts
printf '%s\n' '--- target outline ---'
ast-grep outline src/main/claude/claude-structured-prompt-ownership.ts
printf '%s\n' '--- target relevant source ---'
rg -n -C 18 'cancelClaudeStructuredTurn|ClaudeStructuredSessionAdapter|dispatchClaudeTurn|awaitStarted|startup\.state|cancelClaudeTurn' src/main/claude/claude-structured-prompt-ownership.ts
printf '%s\n' '--- related definitions and callers ---'
rg -n -C 12 'ClaudeStructuredSessionAdapter|cancelClaudeStructuredTurn|dispatchClaudeTurn|awaitStarted|acquireClaudeStructured|dispatch\(' src/main src test tests 2>/dev/null || true

Repository: stablyai/orca

Length of output: 45664


🤖 get_repo_knowledge executed:

get_repo_knowledge stablyai/orca /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings

Length of output: 10855


🏁 Script executed:

set -eu
printf '%s\n' '--- exact symbol files ---'
rg -l --glob '*.ts' 'ClaudeStructuredSessionAdapter|cancelClaudeStructuredTurn|dispatchClaudeTurn|awaitStarted|acquireClaudeStructured' src/main/claude src/main/native-chat src/main | sort -u
printf '%s\n' '--- exact symbol matches ---'
rg -n -C 10 --glob '*.ts' 'ClaudeStructuredSessionAdapter|cancelClaudeStructuredTurn|dispatchClaudeTurn|awaitStarted|acquireClaudeStructured' src/main/claude src/main/native-chat
printf '%s\n' '--- startup symbols ---'
rg -n -C 12 --glob '*.ts' 'claudeStartupHoldsWrites|rejectClaudeStartupWrites|startup\.state|startup:|await.*Started|started' src/main/claude
printf '%s\n' '--- adapter declarations ---'
rg -n -C 30 --glob '*.ts' 'export .*StructuredAgentSessionAdapter|interface StructuredAgentSessionAdapter|class .*SessionAdapter|dispatch\(.*prompt|cancelTurn' src/main/native-chat src/main/claude

Repository: stablyai/orca

Length of output: 45668


🏁 Script executed:

set -eu
printf '%s\n' '--- claude adapter ---'
sed -n '1,320p' src/main/claude/claude-structured-session-adapter.ts
printf '%s\n' '--- claude dispatch ---'
sed -n '1,280p' src/main/claude/claude-structured-dispatch.ts
printf '%s\n' '--- acquisition launch ---'
sed -n '1,300p' src/main/claude/claude-structured-acquisition-launch.ts
printf '%s\n' '--- session acquisition ---'
sed -n '1,320p' src/main/claude/claude-structured-session-acquisition.ts
printf '%s\n' '--- delivery loop exact symbols ---'
rg -n -C 24 --glob '*.ts' 'awaitStarted|adapter\.dispatch|\.dispatch\(|deliver|startup' src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.ts src/main/native-chat/agent-session-wire/structured-agent-session-adapter-router.ts src/main/runtime/structured-claude-runtime-adapter.ts
printf '%s\n' '--- exact awaitStarted sites ---'
rg -n -C 20 --glob '*.ts' 'awaitStarted' src/main

Repository: stablyai/orca

Length of output: 43493


🏁 Script executed:

set -eu
printf '%s\n' '--- dispatch function ---'
rg -n '^(export )?(async )?function dispatchClaudeTurn|dispatchClaudeTurn|beforeDispatch|connection\.send|startupFailure' src/main/claude/claude-structured-dispatch.ts
sed -n '130,330p' src/main/claude/claude-structured-dispatch.ts
printf '%s\n' '--- adapter interface ---'
sed -n '1,280p' src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
printf '%s\n' '--- focused adapter test dispatch/acquire order ---'
rg -n -C 18 --glob 'claude-structured-session-adapter*.test.ts' 'adapter\.dispatch|\.dispatch\(|awaitStarted|acquire\(' src/main/claude
printf '%s\n' '--- focused ownership test setup and dispatch helpers ---'
rg -n -C 14 --glob 'claude-structured-prompt-ownership.test.ts' 'function acquired|function startTurn|adapter\.dispatch|awaitStarted|startup' src/main/claude

Repository: stablyai/orca

Length of output: 41727


🏁 Script executed:

set -eu
printf '%s\n' '--- cancel implementation ---'
rg -n -C 28 --glob '*.ts' 'export async function cancelClaudeTurn|function cancelClaudeTurn|cancelClaudeTurn\(' src/main/claude/claude-structured-control-actions.ts src/main/claude
printf '%s\n' '--- dispatch sequence state ---'
rg -n -C 18 --glob '*.ts' 'dispatchSequence|startup:|type ClaudeSession|interface ClaudeSession' src/main/claude/claude-structured-session-state.ts src/main/claude
printf '%s\n' '--- acquisition publication/startup excerpts ---'
rg -n -C 22 --glob '*.ts' 'providerChildPhase|published|startup\.settled|settled.*startup|publish.*session' src/main/claude/claude-structured-session-acquisition.ts src/main/claude/claude-structured-session-state.ts

Repository: stablyai/orca

Length of output: 42365


<verification_comment>Preserve cancellation for direct startup dispatches.

ClaudeStructuredSessionAdapter.dispatch can call dispatchClaudeTurn before awaitStarted. Acquisition publishes the session while startup is pending, and dispatchClaudeTurn can write the message before startup settles. This branch then returns { cancelled: false } without reaching the interrupt path.

Route a pending direct dispatch through the cancellation and admission checks. Return false early only when startup is pending and no dispatch has been armed or sent. Ensure the direct caller's dispatch sequence identifies that pending dispatch.</verification_comment>

}
if (prompt && session.fence !== request.fence) {
return { cancelled: false }
Expand Down Expand Up @@ -197,7 +187,7 @@ export async function cancelClaudeStructuredTurn(input: {
} else if (claim) {
session.prompts.releaseClaim(claim)
}
return withdrewHeld ? { ...result, cancelled: true } : result
return result
} catch (error) {
if (claim && !interruptConfirmed) {
session.prompts.releaseClaim(claim)
Expand Down
2 changes: 1 addition & 1 deletion src/main/claude/claude-structured-real-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function realAdapter(
const acquire = adapter.acquire
adapter.acquire = async (input) => {
const acquisition = await acquire(input)
await adapter.drainStartup(input.identity.sessionId)
await adapter.awaitStarted(input.identity.sessionId)
return acquisition
}
return adapter
Expand Down
50 changes: 27 additions & 23 deletions src/main/claude/claude-structured-session-acquisition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,30 +248,34 @@ export async function acquireClaudeSession({
event()
}
})
session.startup.settled = settleClaudeSessionStartup({
session,
facts: readClaudeStartupFacts({
connection,
initProof,
sessionId,
providerSessionId: launch.providerSessionId,
resumesTranscript: launch.resumesTranscript,
inputOptions: input.options,
requestTimeoutMs: deps.requestTimeoutMs,
emit
}),
isCurrent: () => sessions.get(sessionId) === session,
requestTimeoutMs: deps.requestTimeoutMs,
fault: (error) => callbacks.handleExit(sessionId, attempt, error),
onStarted: (options) =>
emit({
type: 'started',
// Whichever comes first: the start landing or faulting, or the child being ended.
session.startup.settled = Promise.race([
session.startup.settled,
settleClaudeSessionStartup({
session,
facts: readClaudeStartupFacts({
connection,
initProof,
sessionId,
fence: input.fence,
acquisitionGeneration: session.acquisitionGeneration,
...options
})
})
providerSessionId: launch.providerSessionId,
resumesTranscript: launch.resumesTranscript,
inputOptions: input.options,
requestTimeoutMs: deps.requestTimeoutMs,
emit
}),
isCurrent: () => sessions.get(sessionId) === session,
requestTimeoutMs: deps.requestTimeoutMs,
fault: (error) => callbacks.handleExit(sessionId, attempt, error),
onStarted: (options) =>
emit({
type: 'started',
sessionId,
fence: input.fence,
acquisitionGeneration: session.acquisitionGeneration,
...options
})
})
])
// A child whose exit already reached `handleExit` is not handed over as live: the create
// fails with the CLI's own diagnostic, as one that died before publish does.
if (sessions.get(sessionId) !== session) {
Expand Down
22 changes: 15 additions & 7 deletions src/main/claude/claude-structured-session-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { acquireClaudeSession } from './claude-structured-session-acquisition'
import { supportsClaudeStructuredLocation } from './claude-structured-location-support'
import { setClaudeStructuredSessionOption } from './claude-structured-options'
import { readClaudeStructuredSessionOptions } from './claude-structured-session-options'
import { claudeStartupSettledWithin } from './claude-structured-session-startup-gate'
import {
claudeStartupFailureReason,
claudeStartupSettledWithin
} from './claude-structured-session-startup-state'
import { CLAUDE_DEFAULT_REQUEST_TIMEOUT_MS } from './claude-agent-sdk-control-requests'
import {
ClaudeAcquisitionRegistry,
Expand Down Expand Up @@ -115,9 +118,16 @@ export class ClaudeStructuredSessionAdapter implements StructuredAgentSessionAda
* apart without guessing at wall-clock. */
drainObservedExits = (): Promise<void> => drainClaudeObservedExits(this.exits)

/** Resolves once a published session's startup has landed or faulted it. */
drainStartup = (sessionId: string): Promise<void> =>
this.sessions.get(sessionId)?.startup.settled ?? Promise.resolve()
/** Resolves once a published session's startup has landed, faulted, or been ended by a close;
* with the reason when it did not land. */
awaitStarted = async (sessionId: string): Promise<void | string> => {
const session = this.sessions.get(sessionId)
if (!session) {
return
}
await session.startup.settled
return claudeStartupFailureReason(session) ?? undefined
}

/** Restart reconciliation reads the transcript a resume replays; these maps track liveness. */
providerHistoryWindow: NonNullable<StructuredAgentSessionAdapter['providerHistoryWindow']> = (
Expand Down Expand Up @@ -179,9 +189,7 @@ export class ClaudeStructuredSessionAdapter implements StructuredAgentSessionAda
}

dispatch: StructuredAgentSessionAdapter['dispatch'] = (input) =>
dispatchClaudeTurn(this.session(input.sessionId), input, input.beforeDispatch, (settlement) =>
this.deps.onDispatchSettledLate?.({ sessionId: input.sessionId, ...settlement })
)
dispatchClaudeTurn(this.session(input.sessionId), input, input.beforeDispatch)

compact: NonNullable<StructuredAgentSessionAdapter['compact']> = (input) =>
compactClaudeSession(this.session(input.sessionId), this.compactions, input)
Expand Down
4 changes: 2 additions & 2 deletions src/main/claude/claude-structured-session-exit-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
claudeRootExitObserved,
settleClaudeExitedSession
} from './claude-structured-session-close'
import { failClaudeStartupGate } from './claude-structured-session-startup-gate'
import { failClaudeStartup } from './claude-structured-session-startup-state'
import type {
ClaudeAcquisitionAttempt,
ClaudeSession,
Expand Down Expand Up @@ -32,7 +32,7 @@ export function observeClaudeSessionExit(
return
}
lifecycle.sessions.delete(sessionId)
failClaudeStartupGate(session, error)
failClaudeStartup(session, error)
// Re-enter the provider's close ladder before publishing lifecycle recovery.
// An exit callback is root evidence only; the retained tree proof must run
// before the host releases and reacquires this exact child.
Expand Down
4 changes: 2 additions & 2 deletions src/main/claude/claude-structured-session-publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { ClaudeSession } from './claude-structured-session-state'
import { ClaudeBackgroundTaskTracker } from './claude-background-task-tracker'
import { ClaudeChildWorkDecoder } from './claude-child-work-decoder'
import { ClaudeSlashCommandCatalog } from './claude-slash-command-catalog'
import { createClaudeSessionStartupGate } from './claude-structured-session-startup-gate'
import { createClaudeSessionStartup } from './claude-structured-session-startup-state'

/** The session as published at spawn: nothing the CLI reports at init is assumed yet. */
export function createClaudeSessionPublication(input: {
Expand Down Expand Up @@ -67,7 +67,7 @@ export function createClaudeSessionPublication(input: {
translator: input.translator,
events: input.events,
...(input.unbindReadingControl ? { unbindReadingControl: input.unbindReadingControl } : {}),
startup: createClaudeSessionStartupGate()
startup: createClaudeSessionStartup()
}
}
}
2 changes: 1 addition & 1 deletion src/main/claude/claude-structured-session-recovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ describe('ClaudeStructuredSessionAdapter close and exit recovery', () => {
spawnToken: 'spawn-9',
events: journalSink
})
await adapter.drainStartup('session-1')
await adapter.awaitStarted('session-1')
const first = claude.connections[0]
const oldPrompt = invokeCanUseTool(first, 'Bash', 'permission-retained', 'tool-retained')
const oldSession = (
Expand Down
Loading
Loading