From 4e9a51dd93ef4f832951de1bf000d0ce84378bd2 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 23 Jun 2026 03:40:58 -0700 Subject: [PATCH 1/3] fix: close fresh-agent parity gaps across freshclaude/freshcodex/freshopencode Drive each coding agent and compare UI surfaces + nondestructive commands; fix the four real shortcomings surfaced by the audit (TDD, red-green): 1. freshopencode serve-manager ignored the OPENCODE_CMD env var, breaking parity with CLAUDE_CMD (sdk-bridge) and CODEX_CMD (codex runtime), which both honor their *_CMD var in terminal AND fresh-agent modes. The serve manager now resolves command = options.command ?? env.OPENCODE_CMD ?? 'opencode'. 2. freshopencode logged a false-positive 'malformed_session_status' warning on every attach/resume of an idle session: the opencode /session/status map only reports active (busy/retry) sessions, so an idle session is absent (undefined). reconcileStatus now treats a missing entry as idle (matching the serve manager's onceIdle) and reserves the warning for present-but- malformed entries. 3. freshopencode leaked a 'Fresh-agent session ... is not tracked' error banner to the transcript during placeholder->real-id materialization: the adapter emits freshAgent.session.materialized mid-send (before the runtime manager registers the real id), so the ws-handler's subscribe hit the strict requireSession and threw. subscribe now uses requireOrRecoverSession so the freshopencode attach-recovery path covers the materialization transition (claude/codex fall through unchanged via canRecoverFreshOpenCode). 4. freshcodex transcript did not auto-render after a turn completed: the codex adapter fire-and-forgets send and its subscribe only wired onThreadLifecycle, whose thread_status_changed(idle) fires BEFORE the completed turn is committed to the app-server's thread history, leaving the client with an empty transcript until a manual Refresh. subscribe now also wires the runtime's onTurnCompleted (already public, used by terminal-mode codex) to emit a snapshot-invalidating idle event after the turn is committed, mirroring freshopencode's post-idle emit. --- server/fresh-agent/adapters/codex/adapter.ts | 17 +++++- .../fresh-agent/adapters/opencode/adapter.ts | 6 +- .../adapters/opencode/serve-manager.ts | 4 +- server/fresh-agent/runtime-manager.ts | 2 +- .../server/fresh-agent/codex-adapter.test.ts | 61 +++++++++++++++++++ .../opencode-serve-adapter.test.ts | 29 +++++++++ .../opencode-serve-manager.test.ts | 20 ++++++ .../fresh-agent/runtime-manager.test.ts | 34 +++++++++++ 8 files changed, 168 insertions(+), 5 deletions(-) diff --git a/server/fresh-agent/adapters/codex/adapter.ts b/server/fresh-agent/adapters/codex/adapter.ts index be7f75315..befbfa513 100644 --- a/server/fresh-agent/adapters/codex/adapter.ts +++ b/server/fresh-agent/adapters/codex/adapter.ts @@ -65,6 +65,7 @@ type CodexRuntimePort = { interruptTurn?: (input: CodexTurnInterruptParams) => Promise shutdown?: () => Promise onThreadLifecycle?: (handler: (event: CodexThreadLifecycleEvent) => void) => () => void + onTurnCompleted?: (handler: (event: { threadId: string; turnId?: string; params: Record }) => void) => () => void readThread: (input: { threadId: string; includeTurns?: boolean }) => Promise> listThreadTurns: (input: { threadId: string @@ -867,7 +868,7 @@ export function createCodexFreshAgentAdapter(deps: { if (!runtime.onThreadLifecycle) { throw new Error('Codex app-server runtime does not support thread lifecycle subscriptions.') } - return runtime.onThreadLifecycle((event) => { + const offLifecycle = runtime.onThreadLifecycle((event) => { if (event.kind === 'thread_started') { if (event.thread.id !== sessionId) return listener(makeCodexStatusEvent(sessionId, event.thread.status, event.thread.updatedAt)) @@ -891,6 +892,20 @@ export function createCodexFreshAgentAdapter(deps: { } listener(makeCodexStatusEvent(sessionId, event.status)) }) + // thread_status_changed(idle) can fire BEFORE the completed assistant turn + // is committed to the app-server's thread history, leaving the client with + // an empty transcript. onTurnCompleted fires after the turn is committed, so + // emit another snapshot-invalidating event here to make the client re-fetch + // the committed transcript (parity with freshopencode's post-idle emit). + const offTurnCompleted = runtime.onTurnCompleted?.((event) => { + if (event.threadId !== sessionId) return + activeTurnByThread.delete(sessionId) + listener(makeCodexStatusEvent(sessionId, 'idle')) + }) + return () => { + offLifecycle() + offTurnCompleted?.() + } }, async send(sessionId, input) { diff --git a/server/fresh-agent/adapters/opencode/adapter.ts b/server/fresh-agent/adapters/opencode/adapter.ts index 1e5edf170..619fdd55e 100644 --- a/server/fresh-agent/adapters/opencode/adapter.ts +++ b/server/fresh-agent/adapters/opencode/adapter.ts @@ -158,7 +158,11 @@ export function createOpencodeFreshAgentAdapter(options: CreateOpencodeFreshAgen } try { const status = await getSessionStatus.call(serveManager, realId, cwdRoute(state.cwd) ?? {}) - if (!status || typeof status !== 'object' || Array.isArray(status) || typeof status.type !== 'string') { + // The opencode /session/status map only reports active (busy/retry) sessions, + // so an idle session is absent (undefined). Treat a missing entry as idle — + // matching the serve manager's onceIdle semantics — rather than malformed. + if (status == null) return + if (typeof status !== 'object' || Array.isArray(status) || typeof status.type !== 'string') { log.warn({ ...logContext, reason: 'malformed_session_status', diff --git a/server/fresh-agent/adapters/opencode/serve-manager.ts b/server/fresh-agent/adapters/opencode/serve-manager.ts index a2fdfadac..a5d51ab9f 100644 --- a/server/fresh-agent/adapters/opencode/serve-manager.ts +++ b/server/fresh-agent/adapters/opencode/serve-manager.ts @@ -112,13 +112,13 @@ export class OpencodeServeManager { private shutdownRequested = false constructor(options: OpencodeServeManagerOptions = {}) { - this.command = options.command ?? 'opencode' + this.env = options.env ?? process.env + this.command = options.command ?? this.env.OPENCODE_CMD ?? 'opencode' this.spawnFn = options.spawnFn ?? spawn this.fetchFn = options.fetchFn ?? fetch this.allocatePort = options.allocatePort ?? allocateLocalhostPort this.connectEventStream = options.connectEventStream this.healthTimeoutMs = options.healthTimeoutMs ?? 20_000 - this.env = options.env ?? process.env this.idlePollMs = options.idlePollMs ?? DEFAULT_IDLE_POLL_MS this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS } diff --git a/server/fresh-agent/runtime-manager.ts b/server/fresh-agent/runtime-manager.ts index d3b3914bb..b1d25834a 100644 --- a/server/fresh-agent/runtime-manager.ts +++ b/server/fresh-agent/runtime-manager.ts @@ -205,7 +205,7 @@ export class FreshAgentRuntimeManager { } async subscribe(locator: FreshAgentSessionLocator, listener: (message: unknown) => void) { - const record = this.requireSession(locator) + const record = await this.requireOrRecoverSession(locator) if (!record.adapter.subscribe) { throw new FreshAgentUnsupportedCapabilityError(`Subscribe is not supported for ${record.sessionType}`) } diff --git a/test/unit/server/fresh-agent/codex-adapter.test.ts b/test/unit/server/fresh-agent/codex-adapter.test.ts index f2307ed8d..ede96aca8 100644 --- a/test/unit/server/fresh-agent/codex-adapter.test.ts +++ b/test/unit/server/fresh-agent/codex-adapter.test.ts @@ -1080,6 +1080,67 @@ describe('Codex fresh-agent adapter', () => { expect(off).toHaveBeenCalledTimes(1) }) + it('emits a snapshot event after a codex turn completes so the client re-fetches the committed transcript', async () => { + let lifecycleHandler: ((event: any) => void) | undefined + let turnCompletedHandler: ((event: any) => void) | undefined + const offLifecycle = vi.fn() + const offTurnCompleted = vi.fn() + const runtime = { + startThread: vi.fn(), + resumeThread: vi.fn(), + onThreadLifecycle: vi.fn((handler: any) => { + lifecycleHandler = handler + return offLifecycle + }), + onTurnCompleted: vi.fn((handler: any) => { + turnCompletedHandler = handler + return offTurnCompleted + }), + readThread: vi.fn(), + listThreadTurns: vi.fn(), + readThreadTurn: vi.fn(), + } + const adapter = createCodexFreshAgentAdapter({ runtime: runtime as any }) + const listener = vi.fn() + + const unsubscribe = await adapter.subscribe?.('thread-new-1', listener) + + expect(runtime.onThreadLifecycle).toHaveBeenCalledWith(expect.any(Function)) + expect(runtime.onTurnCompleted).toHaveBeenCalledWith(expect.any(Function)) + + // thread_status_changed(idle) fires BEFORE the completed turn is committed + // to the app-server's thread history, so the client re-fetches but gets + // an empty transcript. This produces one idle snapshot. + lifecycleHandler?.({ + kind: 'thread_status_changed', + threadId: 'thread-new-1', + status: { type: 'idle' }, + }) + + const idleSnapshotsBeforeCompletion = listener.mock.calls.filter( + ([event]: any[]) => event?.type === 'sdk.session.snapshot' && event?.status === 'idle', + ) + expect(idleSnapshotsBeforeCompletion).toHaveLength(1) + + // onTurnCompleted fires AFTER the turn is committed to the thread history. + // The adapter must emit another snapshot-invalidating event so the client + // re-fetches and renders the committed transcript (parity with freshopencode). + turnCompletedHandler?.({ threadId: 'thread-new-1', turnId: 'turn-1', params: {} }) + + const idleSnapshotsAfterCompletion = listener.mock.calls.filter( + ([event]: any[]) => event?.type === 'sdk.session.snapshot' && event?.status === 'idle', + ) + expect(idleSnapshotsAfterCompletion).toHaveLength(2) + + // Turn-completed events for other threads must not trigger emission. + turnCompletedHandler?.({ threadId: 'other-thread', turnId: 'turn-2', params: {} }) + expect(idleSnapshotsAfterCompletion).toHaveLength(2) + + unsubscribe?.() + expect(offLifecycle).toHaveBeenCalledTimes(1) + expect(offTurnCompleted).toHaveBeenCalledTimes(1) + }) + it('lazily resumes a Codex runtime before subscribing to a persisted thread after server reload', async () => { let lifecycleHandler: ((event: any) => void) | undefined const off = vi.fn() diff --git a/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts b/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts index 7736a6754..c1e886e69 100644 --- a/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts +++ b/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts @@ -420,6 +420,35 @@ describe('OpenCode serve adapter: create + send', () => { ) }) + it('treats a session absent from the status map as idle (no malformed warning)', async () => { + loggerMocks.logger.warn.mockClear() + const manager = makeFakeManager() + // The opencode /session/status map only reports active (busy/retry) sessions; + // an idle session is absent (undefined). This must NOT be treated as malformed + // (it matches the serve manager's onceIdle semantics). + manager.getSessionStatus = vi.fn(async () => undefined) + const adapter = makeAdapter(manager) + + await adapter.attach?.({ + sessionId: 'ses_idle_absent', + sessionType: 'freshopencode', + provider: 'opencode', + cwd: '/repo/safe', + }) + + await expect(adapter.getSnapshot?.({ + threadId: 'ses_idle_absent', + sessionType: 'freshopencode', + provider: 'opencode', + cwd: '/repo/safe', + })).resolves.toMatchObject({ status: 'idle' }) + expect(manager.getSessionStatus).toHaveBeenCalledWith('ses_idle_absent', { cwd: '/repo/safe' }) + expect(loggerMocks.logger.warn).not.toHaveBeenCalledWith( + expect.objectContaining({ reason: 'malformed_session_status' }), + expect.any(String), + ) + }) + it('keeps recovered sessions idle and warns when getSessionStatus is missing', async () => { loggerMocks.logger.warn.mockClear() const manager = makeFakeManager() diff --git a/test/unit/server/fresh-agent/opencode-serve-manager.test.ts b/test/unit/server/fresh-agent/opencode-serve-manager.test.ts index 2122693ce..7fe71bb1a 100644 --- a/test/unit/server/fresh-agent/opencode-serve-manager.test.ts +++ b/test/unit/server/fresh-agent/opencode-serve-manager.test.ts @@ -58,6 +58,26 @@ describe('OpencodeServeManager lifecycle', () => { expect(fetchFn).toHaveBeenCalledWith('http://127.0.0.1:47999/global/health', expect.anything()) }) + it('honors the OPENCODE_CMD env var to override the serve binary (parity with CODEX_CMD/CLAUDE_CMD)', async () => { + const { manager, spawnFn } = makeManager({ env: { OPENCODE_CMD: '/custom/opencode-bin' } }) + await manager.ensureStarted() + expect(spawnFn).toHaveBeenCalledWith( + '/custom/opencode-bin', + ['serve', '--hostname', '127.0.0.1', '--port', '47999'], + expect.objectContaining({ env: expect.objectContaining({ FRESHELL_OPENCODE_SIDECAR_ID: expect.any(String) }) }), + ) + }) + + it('falls back to the default opencode command when OPENCODE_CMD is unset', async () => { + const { manager, spawnFn } = makeManager({ env: {} }) + await manager.ensureStarted() + expect(spawnFn).toHaveBeenCalledWith( + 'opencode', + ['serve', '--hostname', '127.0.0.1', '--port', '47999'], + expect.objectContaining({ env: expect.objectContaining({ FRESHELL_OPENCODE_SIDECAR_ID: expect.any(String) }) }), + ) + }) + it('routes the requested session directory without changing the serve process cwd', async () => { const calls: Array<{ url: string; init: any }> = [] const fetchFn = vi.fn(async (url: string, init: any) => { diff --git a/test/unit/server/fresh-agent/runtime-manager.test.ts b/test/unit/server/fresh-agent/runtime-manager.test.ts index a1fed14a6..4dee74cb5 100644 --- a/test/unit/server/fresh-agent/runtime-manager.test.ts +++ b/test/unit/server/fresh-agent/runtime-manager.test.ts @@ -394,6 +394,40 @@ describe('FreshAgentRuntimeManager', () => { expect(opencodeAdapter.compact).toHaveBeenCalledWith('opencode-restored-1', { instructions: 'keep decisions' }) }) + it('recovers (attaches) a not-yet-registered FreshOpenCode session on subscribe instead of throwing (materialization race)', async () => { + const listener = vi.fn() + const off = vi.fn() + const opencodeAdapter = { + create: vi.fn().mockResolvedValue({ sessionId: 'freshopencode-req-1' }), + attach: vi.fn().mockResolvedValue({ sessionId: 'ses_materialized_1' }), + subscribe: vi.fn().mockReturnValue(off), + send: vi.fn().mockResolvedValue(undefined), + } + const registry = createFreshAgentProviderRegistry([{ + sessionType: 'freshopencode', + runtimeProvider: 'opencode', + adapter: opencodeAdapter as any, + }]) + const manager = new FreshAgentRuntimeManager({ registry }) + + // Simulate the materialization race: the real session id is not yet registered + // with the runtime manager (adapter.send hasn't resolved) when subscribe is + // called for the materialized real id. This must recover via attach rather + // than throwing "is not tracked" (which would leak to the client as an error). + await expect(manager.subscribe( + { sessionId: 'ses_materialized_1', sessionType: 'freshopencode', provider: 'opencode', cwd: '/repo/safe' }, + listener, + )).resolves.toBe(off) + + expect(opencodeAdapter.attach).toHaveBeenCalledWith(expect.objectContaining({ + sessionId: 'ses_materialized_1', + sessionType: 'freshopencode', + provider: 'opencode', + cwd: '/repo/safe', + })) + expect(opencodeAdapter.subscribe).toHaveBeenCalledWith('ses_materialized_1', listener) + }) + it('recovers a missing FreshOpenCode durable session with cwd before mutation', async () => { const opencodeAdapter = { create: vi.fn(), From edcaeac3796b28d7919395d3cad857c3209209f9 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 23 Jun 2026 23:14:02 -0700 Subject: [PATCH 2/3] fix(fresh-agent): load-bearing-driven hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OpenCode serve manager: OPENCODE_CMD ?? → || for empty-string parity with other env-var reads; isolate test makeManager() from host process.env (env:{}) so the 'opencode' default assertion holds when a developer has OPENCODE_CMD set in their shell (3 construction sites). - OpenCode adapter: reword onceIdle-equivalence comment to avoid overstating semantics; clarify the absent-as-idle rationale. - OpenCode adapter: add pinning test for concurrent attach-during-send idempotency — asserts serveManager.subscribe is called exactly once for the real id, guarding against a refactor that swaps remember() and emitMaterialized() ordering in materializeOrSend. --- .../fresh-agent/adapters/opencode/adapter.ts | 3 +- .../adapters/opencode/serve-manager.ts | 2 +- .../opencode-serve-adapter.test.ts | 31 +++++++++++++++++++ .../opencode-serve-manager.test.ts | 5 +++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/server/fresh-agent/adapters/opencode/adapter.ts b/server/fresh-agent/adapters/opencode/adapter.ts index 619fdd55e..c82d2cb94 100644 --- a/server/fresh-agent/adapters/opencode/adapter.ts +++ b/server/fresh-agent/adapters/opencode/adapter.ts @@ -160,7 +160,8 @@ export function createOpencodeFreshAgentAdapter(options: CreateOpencodeFreshAgen const status = await getSessionStatus.call(serveManager, realId, cwdRoute(state.cwd) ?? {}) // The opencode /session/status map only reports active (busy/retry) sessions, // so an idle session is absent (undefined). Treat a missing entry as idle — - // matching the serve manager's onceIdle semantics — rather than malformed. + // consistent with the serve manager's onceIdle treatment of absence as idle — + // rather than logging a false-positive malformed warning. if (status == null) return if (typeof status !== 'object' || Array.isArray(status) || typeof status.type !== 'string') { log.warn({ diff --git a/server/fresh-agent/adapters/opencode/serve-manager.ts b/server/fresh-agent/adapters/opencode/serve-manager.ts index a5d51ab9f..a0df6d613 100644 --- a/server/fresh-agent/adapters/opencode/serve-manager.ts +++ b/server/fresh-agent/adapters/opencode/serve-manager.ts @@ -113,7 +113,7 @@ export class OpencodeServeManager { constructor(options: OpencodeServeManagerOptions = {}) { this.env = options.env ?? process.env - this.command = options.command ?? this.env.OPENCODE_CMD ?? 'opencode' + this.command = options.command ?? (this.env.OPENCODE_CMD || 'opencode') this.spawnFn = options.spawnFn ?? spawn this.fetchFn = options.fetchFn ?? fetch this.allocatePort = options.allocatePort ?? allocateLocalhostPort diff --git a/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts b/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts index c1e886e69..ed2218060 100644 --- a/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts +++ b/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts @@ -110,6 +110,37 @@ describe('OpenCode serve adapter: create + send', () => { expect(manager.onceIdle).toHaveBeenCalledWith('ses_real_1', expect.any(Number), { cwd: '/repo' }) }) + it('attach during an in-flight send reuses the materialized state (no duplicate serve subscription)', async () => { + const idle = createDeferred() + const manager = makeFakeManager() + manager.onceIdle = vi.fn(() => idle.promise) + const adapter = makeAdapter(manager) + await adapter.create({ requestId: 'req-race', sessionType: 'freshopencode', provider: 'opencode', cwd: '/repo' }) + + // Start the send: it materializes (remember + bindServeStream subscribes once), + // emits freshAgent.session.materialized, then parks at `await idle`. + const sendPromise = adapter.send?.('freshopencode-req-race', { text: 'go' }) + // Wait until materialization is done and the send is in-flight at await idle + // (promptAsync called => past emitMaterialized, before onceIdle resolves). + await vi.waitFor(() => expect(manager.promptAsync).toHaveBeenCalledWith('ses_real_1', expect.anything(), expect.anything())) + + // Concurrently attach the real id while the send is still in-flight. This is the + // materialization race: emitMaterialized fires mid-send before the runtime + // manager registers the real id, so subscribe recovers via attach. attach MUST + // find the already-remembered state (existing-branch) and NOT bind a second + // serve stream — which would happen if remember() ran AFTER emitMaterialized. + const attached = await adapter.attach?.({ + sessionId: 'ses_real_1', sessionType: 'freshopencode', provider: 'opencode', cwd: '/repo', + }) + expect(attached).toEqual({ sessionId: 'ses_real_1', sessionRef: { provider: 'opencode', sessionId: 'ses_real_1' } }) + expect(manager.subscribe).toHaveBeenCalledTimes(1) + expect(manager.subscribe).toHaveBeenCalledWith('ses_real_1', expect.any(Function)) + + // The in-flight send still completes with the correct result once idle resolves. + idle.resolve() + await expect(sendPromise).resolves.toEqual({ sessionId: 'ses_real_1', sessionRef: { provider: 'opencode', sessionId: 'ses_real_1' } }) + }) + it('continues a materialized session on later sends without re-creating it', async () => { const manager = makeFakeManager() const adapter = makeAdapter(manager) diff --git a/test/unit/server/fresh-agent/opencode-serve-manager.test.ts b/test/unit/server/fresh-agent/opencode-serve-manager.test.ts index 7fe71bb1a..94619f6e9 100644 --- a/test/unit/server/fresh-agent/opencode-serve-manager.test.ts +++ b/test/unit/server/fresh-agent/opencode-serve-manager.test.ts @@ -32,6 +32,9 @@ function makeManager(overrides: Partial[ return jsonResponse({}) }) const manager = new OpencodeServeManager({ + // Isolate from host process.env so tests asserting the 'opencode' default + // command don't break when a developer has OPENCODE_CMD set in their shell. + env: {}, spawnFn: spawnFn as any, fetchFn: fetchFn as any, allocatePort: async () => ({ hostname: '127.0.0.1', port: 47999 }), @@ -145,6 +148,7 @@ describe('OpencodeServeManager lifecycle', () => { return jsonResponse({}) }) const manager = new OpencodeServeManager({ + env: {}, spawnFn: spawnFn as any, fetchFn: fetchFn as any, allocatePort: vi.fn().mockResolvedValue({ hostname: '127.0.0.1', port: 47999 }), @@ -455,6 +459,7 @@ describe('OpencodeServeManager HTTP client', () => { return jsonResponse({}, { status: 404 }) }) const manager = new OpencodeServeManager({ + env: {}, spawnFn: spawnFn as any, fetchFn: fetchFn as any, allocatePort: vi.fn() From a45d48ee8c14c7ca650802c197e836882250d876 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 23 Jun 2026 23:34:47 -0700 Subject: [PATCH 3/3] fix(fresh-agent): correct overstated pinning-test comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fresheyes review noted the concurrent-attach-during-send test claims to guard against a remember()/emitMaterialized() ordering swap, but attach runs after promptAsync (both have already executed). The test still meaningfully pins concurrent attach idempotency (exactly one serve subscription) — fix the comment to match what it actually verifies. --- .../server/fresh-agent/opencode-serve-adapter.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts b/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts index ed2218060..11735112d 100644 --- a/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts +++ b/test/unit/server/fresh-agent/opencode-serve-adapter.test.ts @@ -124,11 +124,11 @@ describe('OpenCode serve adapter: create + send', () => { // (promptAsync called => past emitMaterialized, before onceIdle resolves). await vi.waitFor(() => expect(manager.promptAsync).toHaveBeenCalledWith('ses_real_1', expect.anything(), expect.anything())) - // Concurrently attach the real id while the send is still in-flight. This is the - // materialization race: emitMaterialized fires mid-send before the runtime - // manager registers the real id, so subscribe recovers via attach. attach MUST - // find the already-remembered state (existing-branch) and NOT bind a second - // serve stream — which would happen if remember() ran AFTER emitMaterialized. + // Concurrently attach the real id while the send is still in-flight. attach + // MUST find the already-remembered state (existing-branch) and NOT bind a + // second serve stream. This pins concurrent attach idempotency: exactly one + // serve subscription for the real id, regardless of when attach arrives + // during the send lifecycle. const attached = await adapter.attach?.({ sessionId: 'ses_real_1', sessionType: 'freshopencode', provider: 'opencode', cwd: '/repo', })