Repository navigation
AA04: Reattach ring buffer and chat.resume - #856
Merged
Merged
Conversation
A client that loses its socket mid-turn reconnects, sends chat.resume, and gets the missed events of a running turn; a settled turn is never replayed (it could resurrect a card saved later). - shared/authorWire: chat.resume gains optional afterSeq; GET /assistant/sessions/:id now returns AuthorSessionRecord (AuthorSession + runningRequestId) - server/author/replayBuffer.ts: per-session ring of the last turn's events (REPLAY_CAPACITY 4000, evicting text.delta first); settled entries answer only the terminal event and expire after 120 s - server/author/authorStream.ts: records every runner event and answers chat.resume (replay tail / terminal event / UNKNOWN_REQUEST) - server/author/routes.ts: GET /:id returns the record; runtimeClient getAuthorSession typed accordingly - src/author/reattach.ts: streamEndRecovery, acceptEvent seq dedupe, pending-request localStorage helpers, reattach constants Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
A client that loses its socket (or reloads) mid-turn reconnects, sends
chat.resume, gets the missed events of a running turn, and otherwise reconciles from the stored transcript. Settled turns are never replayed.shared/authorWire.ts:chat.resumegains an optionalafterSeqcursor; newAuthorSessionRecord extends AuthorSession { runningRequestId }is whatGET /assistant/sessions/:idnow returns.server/author/replayBuffer.ts(new): per-session ring of the last turn'sAuthorServerEvents.turn.startedresets the entry; overflow pastREPLAY_CAPACITY(4000) evicts the oldesttext.deltas first so everytool.*survives; a settled entry answers only its terminal event and is evicted lazily afterREPLAY_SETTLED_TTL_MS(120 s).server/author/authorStream.ts: oneReplayBufferper server records every runner event before broadcast;chat.resumeis answered on the asking socket only —replaysends the buffered events withseq > afterSeq(live events follow on the same socket),settledsends only the terminal event,unknownsends anUNKNOWN_REQUESTturn.error(seq 0). MalformedafterSeqis aBAD_MESSAGE.server/author/routes.ts:GET /assistant/sessions/:idreturnsAuthorSessionRecordwithrunningRequestIdfromrunner.running(id);runtimeClient.getAuthorSessionretyped accordingly.src/author/reattach.ts(new): the pure helpers the chat hook will drive —streamEndRecovery(stoppedByUser → none, BUSY → busy, drop mid-stream → drop),acceptEventseq dedupe (seq 0 always applies; new requestId always applies; same requestId only past lastSeq), pending-request localStorage helpers with try/catch around every access, and the reattach timeout/retry/gave-up constants.fallow-ignore-next-line unused-exportsuppressions insrc/author/authorStream.tsthat fallow reports as stale (the exports are used by the existing test entry point).How it was tested
server/author/__tests__/replayBuffer.test.ts(new, 7 tests): replay-after-cursor, unknown requestId/session/replaced turn, settled → terminal only (bothturn.finishedandturn.error), settled TTL expiry at exactly 120 s + 1 (fake clock via thenowseam), running turns don't expire, and a 5 101-event overflow keeps all 2 500tool.*events while dropping the oldest 1 101text.deltas.server/__tests__/authorStream.test.ts(extended, 8 tests): a gated provider parks a turn mid-stream; the client drops after seq 3, reconnects, andchat.resume { afterSeq: 3 }delivers seqs 4–5 from the buffer then 6–7 live with no duplicates; a resume after settle delivers only the terminal event; an unknown requestId getsUNKNOWN_REQUEST. Also assertsGET /assistant/sessions/:idreportsrunningRequestIdwhile the turn runs andnullafter.src/author/__tests__/reattach.test.ts(new, 12 tests): thestreamEndRecoverytable,acceptEventdedupe cases, storage round-trip/malformed data, and a throwinglocalStorage.server/__tests__/authorSessions.test.ts: updated the GET/:idbody assertion for the newrunningRequestIdfield.Command results:
npm run typecheck✓ ·npm run typecheck:server✓ ·npm test -- --run✓ 968 passed ·npm run test:server✓ 1012 passed ·npm run lint✓ (0 errors)npx tsc --noEmit✓ ·npm run lint✓ ·npm run test:coverage✓ 7163 passed ·npm run fallow✓ ·npm run docs:ratchet✓ ·npm run api:check✓ ·npm run docs:llms:check✓ ·npm run size-check✓Acceptance criteria
chat.resume { afterSeq: 3 }delivers events 4…n then the live tail, with no duplicates.text.delta, notool.*).UNKNOWN_REQUEST.unknown.text.deltafirst and keeps everytool.*event.Generated with Devin