fix(streaming): count live-worklog shell/reason as preserved structure (Codex r2)

Codex round 2 caught that _structuralCount() only enumerated .tool-call-group/
.tool-card-row/.tool-worklog-group but NOT the live Worklog shell
(.live-worklog[data-live-worklog-shell=1]) or its .wl-reason content (built by
_flushPendingSegmentRender / ensureLiveWorklogContainer, see messages.js:2386 +
ui.js:6712/6655). So a turn whose live-ahead structure is a Worklog (not a bare
tool-call-group) undercounted preserved structure → wrongly took the segment-swap
path → detached the visible live Worklog container for a frame.

Fix: add .live-worklog[data-live-worklog-shell=1] and .wl-reason to the structural
count (mirrors the canonical 'has current worklog content' selector set at
sessions.js:1125-1131). Added a structural-test assertion + verified live: a
preserved turn with a live worklog .wl-reason the rebuild lacks now takes the
whole-turn restore path and the worklog + parser both survive.

Convergence: Codex findings narrowing each round (tie-guard → tail-segment →
worklog-selector), all real, all fixed + empirically verified.
This commit is contained in:
Hermes Agent
2026-06-11 03:12:40 +00:00
parent c37c0d1397
commit 3ec93ff6bf
3 changed files with 15 additions and 1 deletions

View File

@@ -9032,7 +9032,9 @@ function renderMessages(options){
// Otherwise (rebuild has >= the preserved turn's structural blocks) do
// the precise segment swap so rebuilt-only structure is kept.
const _structuralCount=(turn)=> turn?turn.querySelectorAll(
'[data-live-assistant="1"],.tool-call-group,.tool-card-row,.tool-worklog-group,.agent-activity-thinking,.thinking-card-row'
'[data-live-assistant="1"],.tool-call-group,.tool-card-row,'+
'.tool-worklog-group,.live-worklog[data-live-worklog-shell="1"],'+
'.wl-reason,.agent-activity-thinking,.thinking-card-row'
).length:0;
const _preservedStructure=_structuralCount(_preservedLiveTurn);
const _rebuiltStructure=_structuralCount(_rebuilt);

View File

@@ -133,6 +133,15 @@ def test_reattach_swaps_at_segment_level_to_preserve_rebuilt_structure():
"segment-level swap only when the rebuild is the structural superset; "
"otherwise restore the whole preserved turn so live-only tool cards are kept"
)
# The structural count must include the LIVE WORKLOG shell + reason content, not
# just .tool-call-group — a live worklog (data-live-worklog-shell) landing before
# the throttled persist is exactly the live-ahead structure a segment-only swap
# would detach (Codex round-2 CORE finding).
assert '.live-worklog[data-live-worklog-shell="1"]' in reattach, (
"structural count must include the live worklog shell so a worklog-only "
"live-ahead turn takes the whole-turn restore path"
)
assert ".wl-reason" in reattach
# Segment-level swap is the superset path.
assert "_rebuiltSeg.replaceWith(_preservedSeg)" in reattach, (
"the swap must be segment-level (replace the rebuilt live segment with the "

3
uv.lock generated Normal file
View File

@@ -0,0 +1,3 @@
version = 1
revision = 3
requires-python = ">=3.11"