Release v0.51.309 — Release JY (#3763 — replay restored live tool cards on reconnect, fixes #3707)#3766
Conversation
…ixes #3707) Post-#3401 (#3400 live-to-final epic) recovery residual. When a running session is restored from its in-memory live-turn snapshot and then reattached to the SSE stream, the restore-success path skipped replaying persisted live tool calls, leaving restored live text/thinking but an EMPTY Worklog until a later SSE event or the final render rebuilt the turn. - Extract the persisted-tool-card replay into replayPersistedLiveToolCards() (reads S.toolCalls or INFLIGHT[sid].toolCalls); run it on restoredLiveTurn && didReconnect, not only the !restoredLiveTurn fallback. - Dedup safety: restore-success replay passes {skipUnkeyedRestoredDuplicates:true} — when the restored snapshot already has .tool-card-row rows, an UNKEYED persisted tool is skipped to avoid a duplicate; keyed cards still replay and appendLiveToolCard's tid-dedup replaces the correct restored row. - appendLiveToolCard() and the new liveToolReplayId() both key on tid||id||tool_call_id||tool_use_id||call_id (consistent 5-alias set), so the dedup covers all known id shapes. - Both replay sites pass {sessionId, streamId} so the ownership guard applies. - Regression coverage: restore-success+reconnect replays tools; unkeyed-restored duplicates skipped; all-id-alias dedup; prior ordering invariants preserved. Correct post-#3401 fix for #3707 (supersedes the closed #3724). Co-authored-by: franksong2702 <[email protected]>
|
| Filename | Overview |
|---|---|
| static/sessions.js | Extracts live-tool replay into a replayPersistedLiveToolCards helper and adds a didReconnect flag so the restore-success + reconnect path now replays persisted tool cards with dedup; fallback branch inside the helper is dead code since S.toolCalls is always an array at call time. |
| static/ui.js | Expands the tid lookup in appendLiveToolCard from only tc.tid to all five known tool-id aliases, aligning it with the new liveToolReplayId helper so keyed replace-by-data-live-tid works for any alias variant. |
| tests/test_inflight_stream_reuse.py | Adds two new structural tests covering the restore-success + reconnect replay path and the unkeyed-duplicate-skip logic; updates existing test to match the refactored replayPersistedLiveToolCards call site. |
| tests/test_regressions.py | Updates regression test search string from the old inline appendLiveToolCard(tc) to const replayPersistedLiveToolCards=(opts)=> to track the helper definition, preserving the ordering invariant assertion. |
| tests/test_ui_tool_call_cleanup.py | Adds a static assertion that appendLiveToolCard uses the full 5-alias tid expression rather than only tc.tid. |
| CHANGELOG.md | Adds a v0.51.309 entry describing the fix for dropped live tool cards on reconnect. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[loadSession - INFLIGHT branch] --> B[S.activeStreamId = activeStreamId]
B --> C[define liveToolReplayId + replayPersistedLiveToolCards]
C --> D{INFLIGHT reattach?}
D -- yes --> E[attachLiveStream reconnecting:true\ndidReconnect = true]
D -- no --> F[didReconnect = false]
E --> G[syncTopbar / renderMessages]
F --> G
G --> H{restoreLiveTurnHtmlForSession available?}
H -- yes + hasStructuredLiveState --> I{hasCurrentWorklogContent?}
I -- yes --> J[restoredLiveTurn = true]
I -- no --> K[restoredLiveTurn = restoreHTML]
H -- yes + no structured state --> K
H -- no --> L[restoredLiveTurn = false]
J --> M{restoredLiveTurn AND didReconnect?}
K --> M
L --> M
M -- yes --> N[replayPersistedLiveToolCards\nskipUnkeyedRestoredDuplicates:true\nkeyed cards replace via data-live-tid]
M -- no --> O{!restoredLiveTurn?}
N --> P[done]
O -- yes --> Q[clearLiveToolCards\nplaceLiveToolCardsHost\nensureLiveWorklogShell\nreplayPersistedLiveToolCards]
O -- no --> P
Q --> P
Reviews (1): Last reviewed commit: "docs(changelog): stamp v0.51.309 — Relea..." | Re-trigger Greptile
| const liveToolCalls=Array.isArray(S.toolCalls) | ||
| ? S.toolCalls | ||
| : (Array.isArray(INFLIGHT[sid]&&INFLIGHT[sid].toolCalls)?INFLIGHT[sid].toolCalls:[]); |
There was a problem hiding this comment.
The fallback branch of the ternary is unreachable:
S.toolCalls is assigned INFLIGHT[sid].toolCalls||[] at line 953 (always an Array), so Array.isArray(S.toolCalls) is always true when the helper is called. The second operand — re-reading INFLIGHT[sid].toolCalls through the closure — is never evaluated. Simplifying removes the dead read and makes the intent clearer.
| const liveToolCalls=Array.isArray(S.toolCalls) | |
| ? S.toolCalls | |
| : (Array.isArray(INFLIGHT[sid]&&INFLIGHT[sid].toolCalls)?INFLIGHT[sid].toolCalls:[]); | |
| const liveToolCalls=S.toolCalls||[]; |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Release v0.51.309 — Release JY (#3763, fixes #3707)
Part of the #3400 live-to-final epic — the correct post-#3401 fix for #3707 (proper successor to the closed #3724, whose pre-refactor mechanism no longer existed). Re-gated fresh on current master after the author settled.
Fixed
!restoredLiveTurnfallback), so the Worklog isn't empty until a later SSE event. Dedup is handled two ways:skipUnkeyedRestoredDuplicatesskips an unkeyed persisted tool when the restored snapshot already shows tool rows, andappendLiveToolCard()+ the newliveToolReplayId()both key on the full 5-alias set (tid/id/tool_call_id/tool_use_id/call_id) so keyed cards replace-by-data-live-tidinstead of duplicating. Both replay sites pass the session/stream ownership guard.Gates (re-gated fresh on master after v0.51.302→308 shipped under it)
data-live-tidthrough theouterHTMLrestore so keyed replay replaces rather than duplicatesCloses #3763, fixes #3707.