feat(agents): stream cursor InteractionUpdate deltas (#951) #988

Merged
reviewer merged 1 commit from code-lead/951 into main 2026-05-08 15:11:32 +00:00
Collaborator

Wire the cursor SDK's onDelta callback so the runner gets fine-grained text / thinking / tool-call / shell-output / token / summary updates alongside Run.stream(); whole-message events that overlap are suppressed when the delta path is active so each turn renders once.

Test plan

  • Unit: interactionUpdateToTaskEvents maps every documented InteractionUpdate variant; decodeShellOutputDelta handles string + Uint8Array + garbage frames.
  • Integration: streamRunWithStallAndAbort interleaves SDK stream + delta queue and suppresses whole-message duplicates.
  • Backpressure: 10 000 shell-output-delta updates stay bounded by DeltaQueue capacity (oldest-dropped); 10 000 deltas through event-log produce one shell_output row capped at 16 KiB.
  • Coalescing: text_delta accumulates into one assistant_delta row per turn (64 KiB tail); turn_ended and result reset state.
  • SSE wire: text_delta / shell_output_delta / usage_delta broadcast verbatim to live subscribers.
  • bun run --cwd apps/server qa — 3371 tests pass.

Closes #951

Wire the cursor SDK's `onDelta` callback so the runner gets fine-grained text / thinking / tool-call / shell-output / token / summary updates alongside `Run.stream()`; whole-message events that overlap are suppressed when the delta path is active so each turn renders once. ## Test plan - [x] Unit: `interactionUpdateToTaskEvents` maps every documented `InteractionUpdate` variant; `decodeShellOutputDelta` handles string + Uint8Array + garbage frames. - [x] Integration: `streamRunWithStallAndAbort` interleaves SDK stream + delta queue and suppresses whole-message duplicates. - [x] Backpressure: 10 000 `shell-output-delta` updates stay bounded by `DeltaQueue` capacity (oldest-dropped); 10 000 deltas through `event-log` produce one `shell_output` row capped at 16 KiB. - [x] Coalescing: `text_delta` accumulates into one `assistant_delta` row per turn (64 KiB tail); `turn_ended` and `result` reset state. - [x] SSE wire: `text_delta` / `shell_output_delta` / `usage_delta` broadcast verbatim to live subscribers. - [x] `bun run --cwd apps/server qa` — 3371 tests pass. Closes #951
feat(agents): stream cursor InteractionUpdate deltas (#951)
All checks were successful
qa / dockerfile (pull_request) Successful in 20s
qa / sql-layer-check (pull_request) Successful in 20s
qa / i18n-string-check (pull_request) Successful in 24s
qa / db-schema (pull_request) Successful in 30s
qa / qa-1 (pull_request) Successful in 2m13s
qa / qa (pull_request) Successful in 0s
c74a8d4393
Wire the cursor SDK's `onDelta` callback so the runner gets fine-grained
text / thinking / tool-call / shell-output / token / summary updates
alongside `Run.stream()`. The whole-message events that overlap are
suppressed when delta path is active so each turn renders once.

The event log coalesces deltas into rolling per-turn / per-callId rows
(64 KiB text tail, 16 KiB shell-stream tail) and broadcasts every
delta verbatim over SSE for live dashboard typing — global
`MAX_EVENTS_PER_TASK` stays meaningful even under 10 k+ shell deltas.

Adapters whose underlying SDK doesn't expose deltas (claude-code today)
keep emitting whole-message turns; consumers must tolerate either path.

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

All AC from #951 met. Port types, adapter mapping, coalescing, backpressure, and docs are correct.

Nit: DeltaQueue.dropped is mentioned in comments as surfacing via a system event, but nothing actually reads .dropped to emit one — operator can't tell when frames were silently dropped. Not blocking.

All AC from #951 met. Port types, adapter mapping, coalescing, backpressure, and docs are correct. Nit: `DeltaQueue.dropped` is mentioned in comments as surfacing via a system event, but nothing actually reads `.dropped` to emit one — operator can't tell when frames were silently dropped. Not blocking.
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!988
No description provided.