diff --git a/packages/sdk-typescript/src/daemon/ui/transcript.ts b/packages/sdk-typescript/src/daemon/ui/transcript.ts index 363e96e0192..8917175a05f 100644 --- a/packages/sdk-typescript/src/daemon/ui/transcript.ts +++ b/packages/sdk-typescript/src/daemon/ui/transcript.ts @@ -316,7 +316,7 @@ function handleStateResyncRequired( appendStatusBlock( state, 'error', - `State resync required: ${formatMissedRange(event.lastDeliveredId, event.earliestAvailableId)}. Reload the session to recover.`, + `State resync required: ${formatMissedRange(event.lastDeliveredId, event.earliestAvailableId)}.`, event, ); } diff --git a/packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx b/packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx index 811e983eba0..b30b5e3a92e 100644 --- a/packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx +++ b/packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx @@ -1934,7 +1934,7 @@ describe('DaemonSessionProvider', () => { ]); }); - it('clears awaitingResync on replay_complete after ring-evicted resync', async () => { + it('resets store on ring-evicted resync so replay events rebuild transcript', async () => { const liveDelivered = createDeferred(); const session = createMockSession({ lastEventId: 10, @@ -1950,13 +1950,24 @@ describe('DaemonSessionProvider', () => { earliestAvailableId: 12, }, }; + yield { + id: 12, + v: 1, + type: 'session_update', + data: { + update: { + sessionUpdate: 'agent_message_chunk', + content: { type: 'text', text: 'replayed history' }, + }, + }, + }; yield { v: 1, type: 'replay_complete', - data: { replayedCount: 0 }, + data: { replayedCount: 1 }, }; yield { - id: 12, + id: 13, v: 1, type: 'session_update', data: { @@ -1997,12 +2008,24 @@ describe('DaemonSessionProvider', () => { expect(awaitingResync).toBe(false); expect(blocks).toEqual( expect.arrayContaining([ + expect.objectContaining({ + kind: 'assistant', + text: 'replayed history', + }), expect.objectContaining({ kind: 'assistant', text: 'live after replay', }), ]), ); + expect(blocks).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + kind: 'error', + text: expect.stringContaining('State resync required'), + }), + ]), + ); }); it('clears ring-evicted awaitingResync on same-session reattach', async () => { @@ -2078,7 +2101,7 @@ describe('DaemonSessionProvider', () => { await firstStreamDone.promise; await flushPromises(); }); - expect(blocks).toEqual( + expect(blocks).not.toEqual( expect.arrayContaining([ expect.objectContaining({ kind: 'error', diff --git a/packages/webui/src/daemon/session/DaemonSessionProvider.tsx b/packages/webui/src/daemon/session/DaemonSessionProvider.tsx index 1b8aeffd6ee..09a3aac52bb 100644 --- a/packages/webui/src/daemon/session/DaemonSessionProvider.tsx +++ b/packages/webui/src/daemon/session/DaemonSessionProvider.tsx @@ -508,12 +508,11 @@ export function DaemonSessionProvider({ : undefined; setPromptStatus('idle'); clearPassiveAssistantDoneTimer(passiveAssistantDoneTimerRef); + store.reset(); if (reason === 'epoch_reset') { - store.reset(); activeSession.setLastEventId(0); } else if (reason !== 'ring_evicted') { resyncRequested = true; - store.reset(); session = undefined; sessionRef.current = undefined; setConnection((current) => ({