feat(agents): replay cursor Run.conversation() on resume #992

Merged
reviewer merged 1 commit from code-lead/956 into main 2026-05-08 19:11:33 +00:00
Collaborator

Closes #956

Restore cursor session visibility after a worker crash by translating Run.conversation() into the existing TaskEvent shapes (user, assistant, tool_progress, tool_summary) and yielding them at the head of the resumed run's stream with replayed: true.

Test plan

  • just qa clean (typecheck + biome + 3405 server tests)
  • Unit: conversationTurnToTaskEvents covers agentConversationTurn, shellConversationTurn, unknown shapes
  • Unit: replayConversationForAgent yields divider + per-turn events; idempotent across two calls; cursor_replay_skipped on listRuns failure; empty listRuns yields nothing
  • Fixture-backed integration: recorded conversation translates to expected user → assistant → tool_progress → tool_summary → assistant prefix with replayed: true on every row
  • Event-log: replayed:true propagates onto persisted rows + SSE; non-replayed events leave the flag undefined; cursor_replay_start system row becomes a divider
  • Runner skips PR-URL extraction, session capture, and failover outcome accounting for replayed events
Closes #956 Restore cursor session visibility after a worker crash by translating `Run.conversation()` into the existing `TaskEvent` shapes (`user`, `assistant`, `tool_progress`, `tool_summary`) and yielding them at the head of the resumed run's stream with `replayed: true`. ## Test plan - [x] `just qa` clean (typecheck + biome + 3405 server tests) - [x] Unit: `conversationTurnToTaskEvents` covers `agentConversationTurn`, `shellConversationTurn`, unknown shapes - [x] Unit: `replayConversationForAgent` yields divider + per-turn events; idempotent across two calls; `cursor_replay_skipped` on listRuns failure; empty listRuns yields nothing - [x] Fixture-backed integration: recorded conversation translates to expected `user → assistant → tool_progress → tool_summary → assistant` prefix with `replayed: true` on every row - [x] Event-log: `replayed:true` propagates onto persisted rows + SSE; non-replayed events leave the flag undefined; `cursor_replay_start` system row becomes a divider - [x] Runner skips PR-URL extraction, session capture, and failover outcome accounting for replayed events
feat(agents): replay cursor Run.conversation() on resume
All checks were successful
qa / dockerfile (pull_request) Successful in 15s
qa / sql-layer-check (pull_request) Successful in 15s
qa / i18n-string-check (pull_request) Successful in 17s
qa / db-schema (pull_request) Successful in 44s
qa / qa-1 (pull_request) Successful in 2m25s
qa / qa (pull_request) Successful in 0s
0fb757c885
Closes #956

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
reviewer approved these changes 2026-05-08 19:11:27 +00:00
reviewer left a comment

Correct and well-covered. pushReplay wrapper is a clean pattern. Nit: if (turns.length === 0 || !lastRun) return after the dedup block is unreachable, but harmless.

Correct and well-covered. `pushReplay` wrapper is a clean pattern. Nit: `if (turns.length === 0 || !lastRun) return` after the dedup block is unreachable, but harmless.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!992
No description provided.