fix: collapse stale compression sidebar segments

This commit is contained in:
ai-ag2026
2026-05-08 21:21:25 +02:00
committed by nesquena-hermes
parent dec2d25fcc
commit 447b4e6c0f
2 changed files with 50 additions and 2 deletions
+7 -2
View File
@@ -1819,12 +1819,17 @@ function _isChildSession(s){
function _sessionLineageKey(s, sessionIdsInList){
if(!s||!s.session_id) return null;
if(_isChildSession(s)) return null;
const lineageKey=s._lineage_root_id||s.lineage_root_id||null;
if(lineageKey) return lineageKey;
// If parent_session_id points to another session in the current list,
// this is a subagent child — don't collapse it into lineage (#494).
// this is a subagent/fork child without compression metadata — don't
// collapse it into lineage (#494). Compression continuations carry an
// explicit lineage root, even when stale optimistic rows leave parent
// segments in the browser cache during active compression.
if(s.parent_session_id && sessionIdsInList && sessionIdsInList.has(s.parent_session_id)){
return null;
}
return s._lineage_root_id || s.lineage_root_id || s.parent_session_id || null;
return s.parent_session_id || null;
}
function _sessionLineageContainsSession(s, sid){
+43
View File
@@ -126,6 +126,49 @@ console.log(JSON.stringify({{sid: collapsed[0].session_id, containsRoot: _sessio
assert '"containsRoot":true' in result
def test_stale_optimistic_compression_tips_collapse_even_when_parents_are_visible():
"""Active compression can leave old streaming tips in browser memory.
The server/index already expose only the latest tip, but client-side
optimistic rows from previous tips may still include parent_session_id links.
Those rows carry explicit lineage metadata and must collapse as one sidebar
conversation instead of rendering 7/8/9/10 segment duplicates.
"""
js = SESSIONS_JS_PATH.read_text(encoding="utf-8")
source = f"""
const src = {js!r};
function extractFunc(name) {{
const re = new RegExp('function\\\\s+' + name + '\\\\s*\\\\(');
const start = src.search(re);
if (start < 0) throw new Error(name + ' not found');
let i = src.indexOf('{{', start);
let depth = 1; i++;
while (depth > 0 && i < src.length) {{
if (src[i] === '{{') depth++;
else if (src[i] === '}}') depth--;
i++;
}}
return src.slice(start, i);
}}
eval(extractFunc('_sessionTimestampMs'));
eval(extractFunc('_isChildSession'));
eval(extractFunc('_sessionLineageKey'));
eval(extractFunc('_collapseSessionLineageForSidebar'));
const sessions = [
{{session_id:'seg7', title:'Graphify', parent_session_id:'seg6', message_count:1141, updated_at:70, last_message_at:70, _lineage_root_id:'root', _compression_segment_count:7}},
{{session_id:'seg8', title:'Graphify', parent_session_id:'seg7', message_count:1254, updated_at:80, last_message_at:80, _lineage_root_id:'root', _compression_segment_count:8, pending_user_message:'old'}},
{{session_id:'seg9', title:'Graphify', parent_session_id:'seg8', message_count:1404, updated_at:90, last_message_at:90, _lineage_root_id:'root', _compression_segment_count:9, active_stream_id:'old-stream'}},
{{session_id:'seg10', title:'Graphify', parent_session_id:'seg9', message_count:1490, updated_at:100, last_message_at:100, _lineage_root_id:'root', _compression_segment_count:10, active_stream_id:'current-stream'}},
];
const collapsed = _collapseSessionLineageForSidebar(sessions);
console.log(JSON.stringify(collapsed));
"""
collapsed = json.loads(_run_node(source))
assert [row["session_id"] for row in collapsed] == ["seg10"]
assert collapsed[0]["_lineage_collapsed_count"] == 4
assert collapsed[0]["_compression_segment_count"] == 10
assert [seg["session_id"] for seg in collapsed[0]["_lineage_segments"]] == ["seg10", "seg9", "seg8", "seg7"]
def test_sidebar_attaches_child_sessions_to_collapsed_hidden_parent_lineage():