From 447b4e6c0ff4fe7541e23d4568c6b412c092c505 Mon Sep 17 00:00:00 2001 From: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com> Date: Fri, 8 May 2026 21:21:25 +0200 Subject: [PATCH] fix: collapse stale compression sidebar segments --- static/sessions.js | 9 ++++-- tests/test_session_lineage_collapse.py | 43 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/static/sessions.js b/static/sessions.js index e0b29990..4e183206 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -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){ diff --git a/tests/test_session_lineage_collapse.py b/tests/test_session_lineage_collapse.py index d8d162f2..89e4af54 100644 --- a/tests/test_session_lineage_collapse.py +++ b/tests/test_session_lineage_collapse.py @@ -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():