mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-23 20:34:49 -06:00
cdc1dbcc1d
A fresh browser connect during a parallel tool batch (e.g. several web_fetch) left completed siblings' tool blocks empty until a manual refresh: each tool_result SSE event fires the instant a sibling finishes, but the result messages persist only after the whole batch returns, so a fresh connect replayed neither the already-fired event (a fresh connect doesn't replay the ring buffer) nor a /history row. Route the fresh connect through the same delta replay a reconnect already uses. Persist the per-ws SSE ring-buffer high-water mark (_event_id) onto each saved conversation row. /history returns the committed snapshot up to a resolved-turn-boundary cursor and omits the trailing executing in-flight turn; the client opens its initial SSE with that cursor (Last-Event-ID) so the existing replay_ok path fast-forwards the in-flight turn whole -- tool blocks, results, and approve/plan prompts all rebuild from the ring buffer. The cut sits at the last resolved-turn boundary (not max(saved event_id)), so out-of-order result saves in the post-batch loop can't move it or strand a sibling. Gated on buffer-liveness (can_replay_from): reloaded / evicted / awaiting-approval cases keep the in-flight turn in /history and return a null cursor, falling back to the synthetic snapshot floor -- preserving the existing in-flight render and never leaving a turn unrenderable. - Migration 059: nullable event_id BIGINT on conversations + a (ws_id, event_id) index (keeps the cold-open high-water reseed a seek). - save_message(event_id=) across the storage wrapper / protocol / sqlite / postgres backends; get_max_event_id; reconstruct_messages surfaces the _event_id side-channel. - SessionUIBase: reseed _event_id from storage on construction (so the id space stays monotonic across restarts); can_replay_from() gate. - make_history_handler: _resume_cursor_and_trim() + cursor in the response (WorkstreamHistoryResponse.cursor). The shared projection, export, and coord-rebuild paths are untouched. - app.js: seed the resume cursor on the initial-connect path only, and gate the last_event_id param on != null so a cursor of 0 (a brand-new workstream's first-turn boundary) is not dropped. Tests: helper, storage round-trip, and seed unit tests; two make_history_handler integration tests (cursor + orphan-trim when replayable, null cursor + orphan kept when not); app.js static guards. Migration applies up and down on SQLite.