hermes-webui: sort child sessions so read-only delegates are last
This commit is contained in:
1 parent
d7c3c2b734
commit
2f5c911e2f
2 files changed
+102
-1
No files matched your search
+23
-1
@@ -6328,6 +6328,8 @@ function _attachChildSessionsToSidebarRows(collapsedRows, rawSessions, rawRefere
|
||||
};
|
||||
const orphans=[];
|
||||
const renderableChildIds=new Set((rawSessions||[]).map(s=>s&&s.session_id).filter(Boolean));
|
||||
const childAttachOrderById=new Map();
|
||||
let childAttachCursor=0;
|
||||
const attachQueueById=new Map();
|
||||
for(const candidate of [...(rawSessions||[]),...(referenceSessions||[])]){
|
||||
if(candidate&&candidate.session_id&&!attachQueueById.has(candidate.session_id)) attachQueueById.set(candidate.session_id,candidate);
|
||||
@@ -6382,6 +6384,9 @@ function _attachChildSessionsToSidebarRows(collapsedRows, rawSessions, rawRefere
|
||||
}
|
||||
if(parentRow){
|
||||
const childCopy={...child};
|
||||
if(!childAttachOrderById.has(childCopy.session_id)){
|
||||
childAttachOrderById.set(childCopy.session_id, childAttachCursor++);
|
||||
}
|
||||
if(parentSegment){
|
||||
childCopy._parent_segment_id=parentSegment.session_id;
|
||||
childCopy._parent_segment_title=_sessionDisplayTitle(parentSegment)||child.parent_title||'Untitled';
|
||||
@@ -6410,6 +6415,23 @@ function _attachChildSessionsToSidebarRows(collapsedRows, rawSessions, rawRefere
|
||||
orphans.push({...child,_orphan_child_session:true});
|
||||
}
|
||||
}
|
||||
const resolveReadOnlySession = typeof _isReadOnlySession === 'function'
|
||||
? _isReadOnlySession
|
||||
: ((session) => !!(session && session.read_only));
|
||||
for(const row of rows){
|
||||
if(Array.isArray(row._child_sessions)&&row._child_sessions.length>1){
|
||||
row._child_sessions.sort((a,b)=>{
|
||||
const readOnlyCmp = Number(resolveReadOnlySession(a))-Number(resolveReadOnlySession(b));
|
||||
if(readOnlyCmp!==0) return readOnlyCmp;
|
||||
const aOrder = childAttachOrderById.get(a&&a.session_id) || 0;
|
||||
const bOrder = childAttachOrderById.get(b&&b.session_id) || 0;
|
||||
return aOrder-bOrder;
|
||||
});
|
||||
}
|
||||
if(Array.isArray(row._child_sessions)){
|
||||
row._child_session_count=row._child_sessions.length;
|
||||
}
|
||||
}
|
||||
return [...rows,...orphans];
|
||||
}
|
||||
|
||||
@@ -7523,7 +7545,7 @@ function renderSessionListFromCache(){
|
||||
const childList=document.createElement('div');
|
||||
childList.className='session-child-sessions';
|
||||
['pointerdown','pointerup','click','touchstart','touchmove','touchend','touchcancel'].forEach(ev=>childList.addEventListener(ev,e=>e.stopPropagation()));
|
||||
const sortedChildren=[...s._child_sessions].sort((a,b)=>_sessionTimestampMs(b)-_sessionTimestampMs(a));
|
||||
const sortedChildren=[...s._child_sessions];
|
||||
const openChildSession=async(childSession)=>{
|
||||
// #5409: close mobile sidebar synchronously before navigation
|
||||
if(typeof closeMobileSidebar==='function')closeMobileSidebar();
|
||||
|
||||
@@ -554,6 +554,85 @@ console.log(JSON.stringify(rows));
|
||||
assert rows[0]["_child_sessions"][0]["session_id"] == "subagent_child"
|
||||
|
||||
|
||||
def test_read_only_child_sessions_are_stacked_after_writable_children():
|
||||
"""Read-only delegated children should render after fork/writable children."""
|
||||
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('_isReadOnlySession'));
|
||||
eval(extractFunc('_isChildSession'));
|
||||
eval(extractFunc('_isForkWithResolvableParent'));
|
||||
eval(extractFunc('_sidebarLineageKeyForRow'));
|
||||
eval(extractFunc('_attachChildSessionsToSidebarRows'));
|
||||
const collapsed = [{{
|
||||
session_id:'parent',
|
||||
title:'Parent conversation',
|
||||
message_count:3,
|
||||
updated_at: 1700000010,
|
||||
last_message_at: 1700000010,
|
||||
}}];
|
||||
const raw = [
|
||||
collapsed[0],
|
||||
{{
|
||||
session_id:'read_only_subagent',
|
||||
title:'Retained Subagent',
|
||||
parent_session_id:'parent',
|
||||
relationship_type:'child_session',
|
||||
read_only: true,
|
||||
raw_source:'subagent',
|
||||
source_tag:'subagent',
|
||||
session_source:'other',
|
||||
source_label:'Subagent',
|
||||
updated_at: 1700000030,
|
||||
last_message_at: 1700000030,
|
||||
}},
|
||||
{{
|
||||
session_id:'fork_child',
|
||||
title:'Fork',
|
||||
parent_session_id:'parent',
|
||||
session_source:'fork',
|
||||
raw_source:'webui',
|
||||
source_tag:'webui',
|
||||
source_label:'WebUI',
|
||||
updated_at: 1700000040,
|
||||
last_message_at: 1700000040,
|
||||
}},
|
||||
{{
|
||||
session_id:'writable_subagent',
|
||||
title:'Writable Subagent',
|
||||
parent_session_id:'parent',
|
||||
relationship_type:'child_session',
|
||||
read_only: false,
|
||||
raw_source:'subagent',
|
||||
source_tag:'subagent',
|
||||
session_source:'other',
|
||||
source_label:'Subagent',
|
||||
updated_at: 1700000020,
|
||||
last_message_at: 1700000020,
|
||||
}},
|
||||
];
|
||||
const rows = _attachChildSessionsToSidebarRows(collapsed, raw);
|
||||
console.log(JSON.stringify(rows[0]._child_sessions.map(row => row.session_id)));
|
||||
"""
|
||||
rows = json.loads(_run_node(source))
|
||||
assert rows == ["fork_child", "writable_subagent", "read_only_subagent"]
|
||||
assert "const sortedChildren=[...s._child_sessions];" in js
|
||||
assert "const sortedChildren=[...s._child_sessions].sort" not in js
|
||||
|
||||
|
||||
def test_parent_source_label_display_text_does_not_force_external_orphaning():
|
||||
"""Display-only source labels must not drive machine source classification."""
|
||||
js = SESSIONS_JS_PATH.read_text(encoding="utf-8")
|
||||
|
||||
Reference in new issue
Block a user