mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 10:26:26 +01:00
The clean-room chat revealed a finished reply instead of writing one: the live session accumulated agent deltas in its turn waiter, the package session server awaited the whole turn and answered with one JSON body, and the browser waited on `response.json()`. Nothing between the provider and the page could show work in progress, so the board saw the answer appear all at once. A turn now streams: - `Phase7LiveSession` publishes bounded, ordered live turn events and mirrors the waiter's assembled text into the transcript entry the reply will finally occupy, so one rendered card grows and the settled text is still assembled in exactly one place. - `src/phase7/turn-stream.ts` defines the NDJSON frame codec and reader. The decoder refuses a replayed, reordered, oversized, late, or truncated frame rather than applying a stale view. - `POST /api/phase7/ui/message` flushes headers, then writes one frame per event-loop tick of provider activity. Admission checks still answer with a JSON body and their own status code, because they are decided before the first frame. Client disconnect interrupts the provider turn. - The browser client consumes frames and renders each projection in place; the settled payload is applied last and remains the authority. Stop, reset, and `New chat` abandon the stream, and frames from an abandoned turn generation are dropped. Measured against real Codex through real runnerd: headers at 265 ms, 62 growing assistant states from the first character to a 301-character reply at 3.1 s, the last interim state identical to the settled one, and a single assistant card in the settled thread. The scenario explorer and every fixture, preset, and replay route are unchanged and stay deterministic. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>