Skip to content

CL-6380: in-flight reply survives navigation and resumes on return - #123

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-6380-stream-resume
Aug 20, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
cl-6380-stream-resume

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

  • A turn runs entirely server-side; closing the workbench's SSE EventSource on unmount was already a pure detach (no cancel/abort path exists), but the client had nothing to reattach to: useStreamingReply/useTurnActivity reset to null on every mount, so returning to a workbench mid-reply showed nothing until the next token happened to arrive.
  • Adds a catch-up read path: turnTextSnapshot reconstructs a running turn's committed text from the platform's own inference_turn/turn_part tables (no new storage — the same tables trace-reader.ts already reads), wired into GET /workbenches/:id/turns/:turnId behind an optional dep.
  • The client fetches this once per mount (fetchRunningTurn) and feeds it to useStreamingReply's new resumeFromTurn, which hydrates the reply immediately and is guarded to never override state a live SSE event already produced.

Teardown path found (honest accounting)

useWorkbenchStream (packages/chat-ui/src/use-workbench-stream.ts) only ever calls EventSource#close() on unmount — no fetch, no cancel/abort call exists anywhere in chat-ui/chat tied to unmount. The turn dispatch path (packages/chat/src/workbench-service.ts) has no dependency on any client connection, so navigating away was already safe for the turn itself. The actual gap was purely client-side: no snapshot on remount, and the streaming/typing indicator was derived from local component state (useState(null) on mount) rather than server turn state.

Test plan

  • bun test in packages/chat-ui, packages/chat, packages/insights (scoped)
  • bunx tsc --noEmit in packages/chat-ui, packages/chat, packages/insights, apps/hub
  • eslint/prettier on touched files

Covers reconstructing a running turn's committed text from turn_part
rows, the turns route attaching that snapshot only while a turn is
running, and useStreamingReply hydrating from a fetched snapshot on a
fresh mount without ever clobbering a live event that already opened
the reply.
A turn already ran entirely server-side — closing the SSE EventSource on
unmount never touched it — but the client had no way to catch up:
useStreamingReply/useTurnActivity reset to null on every mount, so
returning to a workbench mid-reply showed nothing until the next token
happened to arrive.

Adds a read path for the catch-up: turnTextSnapshot reconstructs a
running turn's visible text from the platform's own inference_turn/
turn_part rows (no new storage, same tables trace-reader.ts already
reads), wired into GET /workbenches/:id/turns/:turnId behind an
optional dep so a deployment without it just serves no snapshot. The
client fetches this once per mount and hands it to
useStreamingReply's new resumeFromTurn, which hydrates the reply
immediately and is guarded to never override state a live event
already produced.
@TheGreatAxios
TheGreatAxios merged commit 50c65e0 into main Aug 20, 2026
0 of 2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-6380-stream-resume branch August 25, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant