Merge pull request #6473 from franksong2702/franksong2702/live-worklog-settle-no-blank
Preserve Live Worklog through final settlement
This commit is contained in:
3 files changed
+60
-6
No files matched your search
+4
-1
@@ -5953,7 +5953,10 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
_transient:true,
|
||||
});
|
||||
}
|
||||
clearLiveToolCards();
|
||||
// Keep the rendered live Worklog in place until the settled transcript swaps
|
||||
// it for the settled anchor scene. Removing it first exposes an empty
|
||||
// transcript frame on large sessions.
|
||||
clearLiveToolCards({preserveDom:true});
|
||||
S.busy=false;
|
||||
// No-reply guard (#373): if agent returned nothing, show inline error
|
||||
if(!S.messages.some(m=>m.role==='assistant'&&String(m.content||'').trim())&&!assistantText){removeThinking();S.messages.push({role:'assistant',content:'**No response received.** Check your API key and model selection.'});}
|
||||
|
||||
+7
-4
@@ -18241,14 +18241,17 @@ function _findLiveAssistantAnchorForSegment(inner, segmentSeq){
|
||||
}
|
||||
|
||||
function clearLiveToolCards(){
|
||||
const preserveDom=!!(arguments[0]&&arguments[0].preserveDom);
|
||||
if(typeof _clearActivityElapsedTimer==='function') _clearActivityElapsedTimer();
|
||||
const inner=_assistantTurnBlocks($('liveAssistantTurn'));
|
||||
if(inner) inner.querySelectorAll('.live-worklog[data-live-worklog-shell],.tool-worklog-group[data-live-tool-call-group],.tool-call-group[data-live-tool-call-group],.tool-card-row[data-live-tid]:not(.transparent-event-row),[data-anchor-scene-owner="1"],[data-anchor-scene-row="1"]').forEach(el=>el.remove());
|
||||
if(!preserveDom){
|
||||
const inner=_assistantTurnBlocks($('liveAssistantTurn'));
|
||||
if(inner) inner.querySelectorAll('.live-worklog[data-live-worklog-shell],.tool-worklog-group[data-live-tool-call-group],.tool-call-group[data-live-tool-call-group],.tool-card-row[data-live-tid]:not(.transparent-event-row),[data-anchor-scene-owner="1"],[data-anchor-scene-row="1"]').forEach(el=>el.remove());
|
||||
}
|
||||
// Reset the per-turn user expand intent so the next turn starts at the
|
||||
// default collapsed state (#1298).
|
||||
if(typeof _clearLiveActivityUserIntent==='function') _clearLiveActivityUserIntent();
|
||||
// Legacy #liveToolCards container cleanup — kept for safety in case any
|
||||
// leftover cards were inserted there before this refactor took effect.
|
||||
// Legacy #liveToolCards container cleanup (sibling to the settled-rendered
|
||||
// subtree). Always clear/hide it to avoid leaking stale fallback content.
|
||||
const container=$('liveToolCards');
|
||||
if(container){container.innerHTML='';container.style.display='none';}
|
||||
}
|
||||
|
||||
@@ -215,6 +215,55 @@ def test_stream_done_runs_scroll_preserving_collapse_pass_after_disarm():
|
||||
assert "_restoreMessageScrollSnapshotSameFrame" in wrapper
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node required for behavioral test")
|
||||
def test_done_defers_live_dom_removal_but_other_terminal_paths_still_clear():
|
||||
"""The normal done handoff must not expose an empty live-worklog frame.
|
||||
|
||||
The settled transcript render owns replacement of the live DOM. Calling the
|
||||
shared cleanup helper in preserve-DOM mode may stop timers and reset intent,
|
||||
but must leave the visible rows in place until that replacement occurs.
|
||||
Default cleanup remains destructive for error/cancel paths.
|
||||
"""
|
||||
clear_live = _extract("clearLiveToolCards")
|
||||
done_start = MESSAGES_JS.index("source.addEventListener('done',e=>{")
|
||||
done = MESSAGES_JS[done_start : done_start + 14000]
|
||||
harness = textwrap.dedent(f"""
|
||||
let removed = 0;
|
||||
let timerClears = 0;
|
||||
let intentClears = 0;
|
||||
const rows = [{{remove() {{ removed++; }}}}, {{remove() {{ removed++; }}}}];
|
||||
const inner = {{querySelectorAll() {{ return rows; }}}};
|
||||
const legacy = {{innerHTML: 'legacy', style: {{display: ''}}}};
|
||||
globalThis._clearActivityElapsedTimer = () => {{ timerClears++; }};
|
||||
globalThis._clearLiveActivityUserIntent = () => {{ intentClears++; }};
|
||||
globalThis._assistantTurnBlocks = () => inner;
|
||||
globalThis.$ = (id) => id === 'liveAssistantTurn' ? {{}} : id === 'liveToolCards' ? legacy : null;
|
||||
{clear_live}
|
||||
clearLiveToolCards({{preserveDom: true}});
|
||||
const preserved = {{removed, timerClears, intentClears, legacy: legacy.innerHTML, legacyDisplay: legacy.style.display}};
|
||||
clearLiveToolCards();
|
||||
const defaultCleared = {{removed, timerClears, intentClears, legacy: legacy.innerHTML, legacyDisplay: legacy.style.display}};
|
||||
console.log(JSON.stringify({{preserved, defaultCleared}}));
|
||||
""")
|
||||
res = subprocess.run(["node", "-e", harness], capture_output=True, text=True, timeout=30)
|
||||
assert res.returncode == 0, res.stderr
|
||||
out = json.loads(res.stdout.strip())
|
||||
assert out["preserved"] == {
|
||||
"removed": 0,
|
||||
"timerClears": 1,
|
||||
"intentClears": 1,
|
||||
"legacy": "",
|
||||
"legacyDisplay": "none",
|
||||
}, "preserveDom cleanup must leave the live worklog visible until the settled render replaces it"
|
||||
assert out["defaultCleared"]["removed"] == 2, "error/cancel cleanup must still remove live rows"
|
||||
assert out["defaultCleared"]["legacy"] == "", "default cleanup must still clear the legacy container"
|
||||
assert out["defaultCleared"]["legacyDisplay"] == "none", "legacy cleanup must hide the container"
|
||||
assert "clearLiveToolCards({preserveDom:true})" in done, (
|
||||
"the normal done handler must preserve the visible live Worklog until its "
|
||||
"settled transcript render replaces it"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node required for behavioral test")
|
||||
def test_prescroll_snapshot_bypasses_capture_no_option_fallback_still_captures():
|
||||
"""Sentinel _prescrollSnapshot reaches restore without re-capture.
|
||||
@@ -296,4 +345,3 @@ def test_prescroll_snapshot_bypasses_capture_no_option_fallback_still_captures()
|
||||
assert out["test3_usedCapture"] is True, (
|
||||
"empty-options call's captured snapshot must reach _restoreMessageScrollSnapshotSameFrame"
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user