fix(streaming): preserve restored live tool cards on reconnect (#3707)#3724
fix(streaming): preserve restored live tool cards on reconnect (#3707)#3724rodboev wants to merge 7 commits into
Conversation
|
| Filename | Overview |
|---|---|
| static/messages.js | Adds restoredLiveTurn flag, _renderRestoredReconnectDisplay helper, and _replayRestoredLiveToolCardsIfMissing; the markdown render inside _normalizedRestoredMarkdownText is called on every hot-path frame without memoisation, and the replay helper fallback loop lacks dedup for tid-less tool calls. |
| static/ui.js | Adds replayLiveToolCardsFromState with per-tid dedup, but the tid&&inner short-circuit means tid-less tool calls are never deduplicated, causing duplicate cards on any subsequent _replayRestoredLiveToolCardsIfMissing call. |
| static/sessions.js | One-line change passes restoredLiveTurn from loadSession into attachLiveStream options; straightforward and correct. |
| tests/test_live_reconnect_restore.py | Source-level structural assertions verify the key invariants; consistent with the project testing approach for browser-side JS. |
Reviews (6): Last reviewed commit: "fix(streaming): compare restored reconne..." | Re-trigger Greptile
|
I pulled the branch and read the The mechanismOn a restored reconnect, let _smdReconnect=reconnecting&&!restoredLiveTurn;When the first new token makes the normalized text mismatch, _restoredReconnectDisplayActive=false;
assistantBody.classList.remove('stream-fade-active');
assistantBody.innerHTML=renderMd ? renderMd(target) : esc(target);
_sanitizeSmdLinks(assistantBody);
return false;Control then falls into the non-fade smd-init branch ( } else if(!_smdParser&&window.smd){
if(_smdReconnect){assistantBody.innerHTML='';_smdReconnect=false;} // _smdReconnect is false → no clear
_smdNewParser(assistantBody);
}
if(_smdParser){ _smdWrite(displayText); }Because The fade path shares itgreptile scoped this to the non-fade branch, but Minimal fixBoth branches already have the right clear logic — they're just disarmed. Re-arm _restoredReconnectDisplayActive=false;
assistantBody.classList.remove('stream-fade-active');
_smdReconnect=true; // re-arm: next smd init must clear the restored renderMd DOM
assistantBody.innerHTML=renderMd ? renderMd(target) : esc(target);
On the tests
|
|
CI failures are all the same pre-existing test: test_load_session_reattach_path_uses_attach_live_stream_for_running_sessions in test_inflight_stream_reuse.py. The assertion error actually shows the expected string is present in the slice, so the test contradicts its own output. Fails identically across Python 3.11, 3.12, and 3.13 on shard 2. Unrelated to this PR's changes. |
|
Pushed the narrow follow-up in rodboev/hermes-webui@67424f17. |
|
Thanks for chasing this one @rodboev. Closing as superseded by #3401 (shipped in v0.51.294, "live-to-final assistant reply redesign") — but with a note to re-verify the underlying #3707 first. Why supersededThis PR was built on v0.51.293 (pre-#3401) and targets the old reconnect architecture: it threads a More importantly, #3401 addresses the same #3707 root cause with a more integrated mechanism:
So the "restore tool cards, then immediately re-strip them on reconnect" path you fixed here is structurally guarded in the new code, by restoring the whole turn (cards and all) rather than replaying tool calls after the fact. Action for #3707I'm leaving #3707 open for now rather than auto-closing it — the original report called the behavior "erratic," and #3401 rewrote the surface enough that it deserves a fresh look on v0.51.298. @b3nw, if you can re-test the switch-away/switch-back-to-an-active-stream flow on the latest build and the tool cards now persist, we'll close #3707. If any residual remains, it'll need a fresh fix against the new snapshot/restore code (the entry point is Credit for the diagnosis here carries over — thank you. |
…ds on reconnect, fixes #3707) (#3766) * fix(streaming): replay restored live tool cards on reconnect (#3763, fixes #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]> * docs(changelog): stamp v0.51.309 — Release JY (stage-a5b #3763) --------- Co-authored-by: nesquena-hermes <[email protected]>
Thinking Path
loadSession()can successfully restore the saved live turn and then immediately reattach the stream; reconnect cleanup still treats those restored tool cards as stale live chrome.What Changed
static/sessions.js: pass restored-live-turn state into reconnect setup.static/messages.js: preserve or immediately replay restored live tool cards/text during reconnect.static/ui.js: add a small helper if needed so live tool-card replay stays centralized.tests/test_live_reconnect_restore.py: add regression coverage for restored snapshot plus reconnect cleanup.Why It Matters
Users switching between long-running sessions should see the same live progress they already saw, not a partially reset transcript. This closes the remaining residual from the live-turn restore bug without waiting for broad live-to-final redesign work.
Verification
Risks / Follow-ups
Model Used
GPT-5.5 via Codex CLI; implementation and adversarial review assisted by Codex/Claude agents.