You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(tui): deduplicate assistant messages across sessions.changed reload (#96980)
* fix(tui): suppress adjacent history-replay/live-final duplicate in ChatLog (#96967)
When sessions.changed triggers loadHistory(), assistant rows are replayed
via finalizeAssistant(text) without a runId. A late live final with the
same text and a runId can land immediately afterward, producing a visible
duplicate assistant reply.
Track the most recent history-replay assistant component in ChatLog and
suppress a following live final when the text matches and no other
component was appended in between. This is simpler and more targeted
than the prior event-handler approach because it operates at the
rendering layer where the duplicate is actually visible.
Fixes#96967
* fix(tui): bound replay-final dedupe window in ChatLog
* fix(tui): bound replay-final dedupe marker with TTL expiration
* fix(tui): satisfy no-promise-executor-return lint rule in chat-log test
* fix(tui): replace ChatLog text heuristic with runId-based surrender tracking
Drop the lastHistoryAssistant text-matching dedupe in ChatLog
(posed P1 false-suppression risk). Instead, track active runIds
surrendered to loadHistory() on sessions.changed 'new' — late
events for surrendered runs are suppressed by exact runId match.
Add ChatLog.hasStreamingRun() so the event handler can detect
when loadHistory restored a surrendered run as in-flight streaming.
Retain chatFinalizedRuns / chatFinalizedRunsWithDisplay from the
original fix for post-reload delta/error dedup of finalized runs.
Incorporates the precise surrender-tracking approach from closed
PRs #96979/#96986 while keeping the scoped ChatLog API minimal.
* fix(tui): render displayable late final for surrendered non-streaming run
When a sessions.changed 'new' surrenders an in-flight run and
loadHistory does not restore it as streaming, a late displayable
final must still be rendered — otherwise the user never sees the
assistant reply. Only suppress non-displayable finals (empty
message, no errorMessage).
Add focused test for non-displayable final suppression.
* test(tui): add coverage for empty in-flight restore + late displayable final
Covers the rank-up scenario from #96979: surrendered run restored
by loadHistory as empty streaming, late displayable final passes
through surrender check and renders to chatLog.
* fix(tui): suppress late finals for surrendered finalized runs to prevent history-replay duplicate
When sessions.changed 'new' fires, now surrender chatFinalizedRuns entries
too (as 'finalized'), not just sessionRuns ('in-flight'). This prevents
the finalized-before-reload history-replay duplicate: a late final for an
already-displayed run gets suppressed because the history-replay static
row covers the visible content.
In-flight surrendered runs still render displayable finals because the
user may not have seen the streaming text before the reload.
SurrenderedToHistoryRunIds is now Map<string,'in-flight'|'finalized'>
so the surrender check can branch on source. Move surrender check before
the finalizedRuns/chatFinalizedRuns guard so surrendered finalized runs
are routed correctly.
Add focused tests for history-replayed-visible (suppress) and
history-replay-missing (render) late finals.
* fix(tui): extend surrender coverage to sessions.changed reset reload path
Reset also calls clearTrackedRunState() + loadHistory() with the same
sessionKey, so late chat events for runs from the old session window
can race with the history replay the same way as 'new'. Surrender
sessionRuns and chatFinalizedRuns on both reasons (#96979 P1 rank-up).
* fix(tui): keep surrendered finalized marker after late delta to block later final
For finalized surrendered runs, a late delta must not clear the
surrender marker — otherwise a subsequent late final would slip
through and produce a duplicate. Only in-flight surrendered runs
clear the marker on delta (their final needs to render).
* fix(tui): gate session history reloads on persistence
Co-authored-by: xialonglee <[email protected]>
* docs(changelog): note TUI external-run dedupe
* test(tui): type history load harness results
* test(tui): defer history results without async mocks
* chore(changelog): defer TUI note to release
---------
Co-authored-by: Peter Steinberger <[email protected]>
0 commit comments