#14946: fix(webchat): accumulate text across blocks in streaming buffer
app: web-ui
gateway
size: M
trusted-contributor
experienced-contributor
Fixes #14361
## Summary
- Track per-run block state (`blockBases`, `lastBlockTexts`) in `ChatRunState` so that when the agent emits a new text block after a tool call, previous blocks are preserved in the streaming buffer
- Detect block boundaries via prefix check (`!text.startsWith(lastBlock)`) — safe because the agent runner suppresses non-prefix updates within a block
- Centralize buffer cleanup in `deleteRunBufferState()` and thread it through all abort/TTL paths to prevent stale state leaks
## Root Cause
`emitChatDelta` replaced the streaming buffer with each incoming `text` value. Between text blocks (after tool calls), the agent runner resets its accumulated text, so the new block's `text` starts fresh — overwriting the buffer and losing all previous blocks' content.
## Test plan
- [x] New test: "accumulates text across multiple assistant blocks" — verifies delta broadcast contains text from both blocks
- [x] New test: "includes all blocks in the final chat message" — verifies lifecycle end emits a final payload with both blocks joined
- [x] All 11 tests in `server-chat.agent-events.test.ts` pass (7 existing + 2 new + 2 registry)
- [x] `pnpm build && pnpm check` pass
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR fixes webchat streaming buffer behavior when assistant output arrives in multiple text blocks (e.g., around tool calls). It extends `ChatRunState` with per-run block tracking (`blockBases`, `lastBlockTexts`) and updates `emitChatDelta` (`src/gateway/server-chat.ts:250+`) to detect block boundaries and build `fullText` as “previous blocks + current block”, so deltas no longer overwrite prior content.
Cleanup of buffer-related state is centralized via `deleteRunBufferState` and threaded through abort and maintenance TTL paths (`src/gateway/chat-abort.ts`, `src/gateway/server-maintenance.ts`, and context plumbing in `src/gateway/server.impl.ts`), preventing stale state leaks. New vitest cases in `src/gateway/server-chat.agent-events.test.ts` cover multi-block accumulation and final payload behavior, including the edge case where lifecycle end fires without a trailing delta after the last boundary.
<h3>Confidence Score: 5/5</h3>
- This PR appears safe to merge with minimal risk.
- Changes are localized to webchat streaming state handling, include centralized cleanup to prevent leaks, and are covered by targeted unit tests for multi-block streaming and final message composition. No regressions or missing plumbing were found in the reviewed changeset.
- No files require special attention
<sub>Last reviewed commit: d967c83</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#8353: fix(ui): display tool calls during webchat streaming
by MarvinDontPanic · 2026-02-03
80.1%
#16949: fix(gateway): deliver chat:final even when sessionKey is unresolved (…
by ekleziast · 2026-02-15
79.1%
#10612: fix: trim leading blank lines on first emitted chunk only (#5530)
by 1kuna · 2026-02-06
79.0%
#16733: fix(ui): avoid injected newlines when tool output is hidden
by jp117 · 2026-02-15
78.6%
#14309: fix(ui): resolve chat event session key mismatch
by justonlyforyou · 2026-02-11
78.0%
#12180: fix: merge multi-block assistant texts into single reply
by 1960697431 · 2026-02-08
78.0%
#13104: fix: persist user command message in chat transcript
by mcaxtr · 2026-02-10
77.9%
#23144: fix(ui): strip reply directive tags from assistant messages in WebC...
by echoVic · 2026-02-22
77.1%
#14966: fix(webchat): preserve user message visibility after chat.send
by BenediktSchackenberg · 2026-02-12
77.0%
#15050: fix: transcript corruption resilience — strip aborted tool_use bloc...
by yashchitneni · 2026-02-12
76.9%