commit
7c666d8e4d
3 files changed
+129
No files matched your search
@@ -10,6 +10,8 @@
|
||||
### Fixed
|
||||
|
||||
- Trim session-level tool call payloads to the returned message window for paginated `/api/session` loads, so long tool-heavy sessions do not send historical tool call summaries during ordinary session switching.
|
||||
- Sidebar compression lineage collapse now prefers the current continuation tip over a preserved parent snapshot when both rows share the same backend segment count. This keeps reloads after context compression from reopening the older parent transcript and making the active conversation appear to disappear.
|
||||
- Reloading a stale `/session/<parent>` compression URL now resolves to the visible continuation tip from the sidebar payload instead of reopening the archived parent snapshot.
|
||||
|
||||
## [v0.51.136] — 2026-05-25 — Release DH (stage-batch18 — 5-PR streaming + session index batch)
|
||||
|
||||
|
||||
@@ -543,6 +543,10 @@ async function newSession(flash, options={}){
|
||||
|
||||
async function loadSession(sid){
|
||||
const opts = arguments[1] || {};
|
||||
if(!opts.skipLineageResolve && typeof _resolveSessionIdFromSidebarLineage==='function'){
|
||||
const resolvedSid=_resolveSessionIdFromSidebarLineage(sid);
|
||||
if(resolvedSid&&resolvedSid!==sid) sid=resolvedSid;
|
||||
}
|
||||
const forceReload = !!opts.force;
|
||||
const currentSid = S.session ? S.session.session_id : null;
|
||||
// Clicking the already-open session in the sidebar is a no-op. Reloading it
|
||||
@@ -2661,6 +2665,39 @@ function _sessionLineageContainsSession(s, sid){
|
||||
return false;
|
||||
}
|
||||
|
||||
function _resolveSessionIdFromSidebarLineage(sid){
|
||||
sid=String(sid||'').trim();
|
||||
if(!sid||!Array.isArray(_allSessions)||!_allSessions.length) return sid||null;
|
||||
const visibleRows=_collapseSessionLineageForSidebar(_allSessions).filter(row=>row&&!_isChildSession(row));
|
||||
if(visibleRows.some(row=>row&&row.session_id===sid)) return sid;
|
||||
const candidates=[];
|
||||
for(const row of visibleRows){
|
||||
if(!row||!row.session_id) continue;
|
||||
if(row.session_source==='fork'||row.relationship_type==='child_session') continue;
|
||||
const lineageLike=!!(
|
||||
row._lineage_key||row._lineage_root_id||row.lineage_root_id||
|
||||
row._compression_segment_count||row.pre_compression_snapshot||
|
||||
(Array.isArray(row._lineage_segments)&&row._lineage_segments.length>1)
|
||||
);
|
||||
if(!lineageLike) continue;
|
||||
const key=_sidebarLineageKeyForRow(row);
|
||||
if(key===sid||row.parent_session_id===sid||row._lineage_root_id===sid||row.lineage_root_id===sid||_sessionLineageContainsSession(row,sid)){
|
||||
candidates.push(row);
|
||||
}
|
||||
}
|
||||
if(!candidates.length) return sid;
|
||||
candidates.sort((a,b)=>{
|
||||
const bSeg=Number(b&&b._compression_segment_count||b&&b._lineage_collapsed_count||0);
|
||||
const aSeg=Number(a&&a._compression_segment_count||a&&a._lineage_collapsed_count||0);
|
||||
if(bSeg!==aSeg) return bSeg-aSeg;
|
||||
const bSnapshot=!!(b&&b.pre_compression_snapshot);
|
||||
const aSnapshot=!!(a&&a.pre_compression_snapshot);
|
||||
if(bSnapshot!==aSnapshot) return aSnapshot-bSnapshot;
|
||||
return _sessionTimestampMs(b)-_sessionTimestampMs(a);
|
||||
});
|
||||
return candidates[0].session_id||sid;
|
||||
}
|
||||
|
||||
function _sessionSegmentCount(s){
|
||||
if(!s) return 0;
|
||||
const counts=[];
|
||||
@@ -2838,6 +2875,13 @@ function _collapseSessionLineageForSidebar(sessions){
|
||||
if(bSeg||aSeg){
|
||||
if(bSeg!==aSeg) return bSeg-aSeg;
|
||||
}
|
||||
// Preserved pre-compression parents can share the same backend segment
|
||||
// count as the continuation. Prefer the non-snapshot tip before falling
|
||||
// back to timestamps, otherwise a recently-polled parent reopens the
|
||||
// older transcript and makes the active continuation look lost.
|
||||
const bSnapshot=!!(b&&b.pre_compression_snapshot);
|
||||
const aSnapshot=!!(a&&a.pre_compression_snapshot);
|
||||
if(bSnapshot!==aSnapshot) return aSnapshot-bSnapshot;
|
||||
return _sessionTimestampMs(b)-_sessionTimestampMs(a);
|
||||
});
|
||||
const chosen=sorted[0];
|
||||
|
||||
@@ -253,6 +253,89 @@ console.log(JSON.stringify(collapsed));
|
||||
|
||||
|
||||
|
||||
def test_sidebar_lineage_collapse_prefers_current_tip_over_same_segment_snapshot():
|
||||
"""A preserved parent snapshot can share the child's backend segment count.
|
||||
|
||||
Loading/polling the parent refreshes its timestamp, but the collapsed row
|
||||
must still open the non-snapshot continuation tip. Otherwise a reload after
|
||||
compression jumps back to the older parent transcript and looks like the
|
||||
active conversation disappeared.
|
||||
"""
|
||||
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:'parent', title:'Duplicate Assistant Text Blocks', message_count:64, updated_at:300, last_message_at:300, pre_compression_snapshot:true, _lineage_root_id:'parent', _compression_segment_count:2}},
|
||||
{{session_id:'child', title:'Duplicate Assistant Text Blocks', parent_session_id:'parent', message_count:86, updated_at:200, last_message_at:200, _lineage_root_id:'parent', _compression_segment_count:2}},
|
||||
];
|
||||
const collapsed = _collapseSessionLineageForSidebar(sessions);
|
||||
console.log(JSON.stringify(collapsed));
|
||||
"""
|
||||
collapsed = json.loads(_run_node(source))
|
||||
assert [row["session_id"] for row in collapsed] == ["child"]
|
||||
assert collapsed[0]["_lineage_collapsed_count"] == 2
|
||||
assert [seg["session_id"] for seg in collapsed[0]["_lineage_segments"]] == ["child", "parent"]
|
||||
|
||||
|
||||
|
||||
def test_direct_parent_restore_resolves_to_visible_compression_tip():
|
||||
"""A stale /session/<parent> URL should reopen the visible continuation tip.
|
||||
|
||||
The sidebar payload may omit the archived pre-compression parent but still
|
||||
include the latest continuation with lineage metadata pointing back to the
|
||||
parent. Boot restore should use that visible tip instead of loading the old
|
||||
parent transcript and making the continuation look lost.
|
||||
"""
|
||||
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);
|
||||
}}
|
||||
var _allSessions = [
|
||||
{{session_id:'child', title:'Duplicate Assistant Text Blocks', parent_session_id:'parent', message_count:86, updated_at:200, last_message_at:200, _lineage_root_id:'parent', _compression_segment_count:2}},
|
||||
{{session_id:'other', title:'Other', message_count:4, updated_at:100, last_message_at:100}},
|
||||
];
|
||||
eval(extractFunc('_sessionTimestampMs'));
|
||||
eval(extractFunc('_isChildSession'));
|
||||
eval(extractFunc('_sessionLineageKey'));
|
||||
eval(extractFunc('_sessionLineageContainsSession'));
|
||||
eval(extractFunc('_sidebarLineageKeyForRow'));
|
||||
eval(extractFunc('_collapseSessionLineageForSidebar'));
|
||||
eval(extractFunc('_resolveSessionIdFromSidebarLineage'));
|
||||
console.log(JSON.stringify({{parent:_resolveSessionIdFromSidebarLineage('parent'), child:_resolveSessionIdFromSidebarLineage('child'), other:_resolveSessionIdFromSidebarLineage('other')}}));
|
||||
"""
|
||||
result = json.loads(_run_node(source))
|
||||
assert result == {"parent": "child", "child": "child", "other": "other"}
|
||||
|
||||
|
||||
def test_sidebar_attaches_child_sessions_to_collapsed_hidden_parent_lineage():
|
||||
js = SESSIONS_JS_PATH.read_text(encoding="utf-8")
|
||||
source = f"""
|
||||
|
||||
Reference in new issue
Block a user