fix(sidebar): nest delegated subagents under messaging parents (#7263)

Nest delegated subagent sessions under the parent conversation that spawned them instead of forcing them to the sidebar top level.

Co-authored-by: lowlandsheperd <lowlandsheperd@users.noreply.github.com>
This commit is contained in:
Kenanandlowlandsheperd authored and GitHub committed 2026-09-15 16:31:54 -07:00
1 parent 68008e824b
commit b56dd423e7
2 files changed
+186 -6

No files matched your search

+11 -6
View File
@@ -7134,6 +7134,12 @@ function _attachChildSessionsToSidebarRows(collapsedRows, rawSessions, rawRefere
const childRenderable=!!(child&&child.session_id&&renderableChildIds.has(child.session_id));
if(child&&child.session_id&&visibleBySid.has(child.session_id)) continue;
const isForkChild=_isForkWithResolvableParent(child, sessionIdsInList)&&!(child&&child.pinned);
const childRawRole=[
child&&child.raw_source,
child&&child.source_tag,
child&&child.source,
].map(source=>String(source||'').trim().toLowerCase()).find(Boolean)||'';
const childIsDelegatedSubagent=_isChildSession(child)&&childRawRole==='subagent';
const childLineageKey=child&&(child._lineage_root_id||child.lineage_root_id||child.parent_session_id);
const isHiddenLineageReferenceChild=!!(child&&child.archived&&child.parent_session_id&&childLineageKey&&!child.pinned&&!childRenderable);
if(!_isChildSession(child)&&!isForkChild&&!isHiddenLineageReferenceChild) continue;
@@ -7158,11 +7164,10 @@ function _attachChildSessionsToSidebarRows(collapsedRows, rawSessions, rawRefere
hiddenArchivedChildTree.add(child.session_id);
continue;
}
// Cross-surface rows (for example a WebUI continuation from a Telegram
// conversation) should remain top-level when there is no WebUI-owned parent
// row to stack under. But if the parent is visible in this same sidebar
// render, attach normally — delegated subagent rows are also cross-source
// relative to their WebUI parent and should not be forced into orphans.
// Independent cross-surface rows (for example a WebUI continuation from a
// Telegram conversation) remain top-level instead of nesting under an
// external parent. Delegated subagents are also cross-source, but they are
// parent-owned work and should still attach to the visible parent row.
const parentSourceMarker=String(parentRow&&(
parentRow.session_source||parentRow.raw_source||parentRow.source_tag||parentRow.source
)||'').toLowerCase();
@@ -7173,7 +7178,7 @@ function _attachChildSessionsToSidebarRows(collapsedRows, rawSessions, rawRefere
parentRow.session_source==='messaging'||
(parentSourceMarker&&parentSourceMarker!=='webui'&&parentSourceMarker!=='subagent'&&parentSourceMarker!=='other'&&parentSourceMarker!=='fork')
);
if(parentRow&&child._cross_surface_child_session&&parentIsExternal){
if(parentRow&&child._cross_surface_child_session&&parentIsExternal&&!childIsDelegatedSubagent){
if(childRenderable) orphans.push({...child,_orphan_child_session:true});
continue;
}
+175
View File
@@ -499,6 +499,181 @@ console.log(JSON.stringify(rows));
assert "_orphan_child_session" not in rows[0]["_child_sessions"][0]
def test_cross_surface_subagents_stack_under_visible_messaging_parent():
"""Delegated subagents stay nested even when their visible parent is external."""
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);
}}
function _isExternalSession(s) {{ return !!(s && (s.is_cli_session || s.session_source === 'messaging')); }}
eval(extractFunc('_isChildSession'));
eval(extractFunc('_isForkWithResolvableParent'));
eval(extractFunc('_sidebarLineageKeyForRow'));
eval(extractFunc('_attachChildSessionsToSidebarRows'));
const collapsed = [{{
session_id:'messaging_parent',
title:'Messaging parent',
raw_source:'weixin',
source_tag:'weixin',
session_source:'messaging',
message_count:3,
}}];
const raw = [
collapsed[0],
...['delegate_a', 'delegate_b', 'delegate_c'].map(session_id => ({{
session_id,
title:'Subagent Session',
parent_session_id:'messaging_parent',
relationship_type:'child_session',
raw_source:'subagent',
source_tag:'subagent',
session_source:'other',
source_label:'Subagent',
_parent_lineage_root_id:'messaging_parent',
_cross_surface_child_session:true,
}})),
];
const rows = _attachChildSessionsToSidebarRows(collapsed, raw);
console.log(JSON.stringify(rows));
"""
rows = json.loads(_run_node(source))
assert [row["session_id"] for row in rows] == ["messaging_parent"]
assert rows[0]["_child_session_count"] == 3
assert [child["session_id"] for child in rows[0]["_child_sessions"]] == [
"delegate_a",
"delegate_b",
"delegate_c",
]
assert all("_orphan_child_session" not in child for child in rows[0]["_child_sessions"])
def test_delegated_subagent_source_precedence_ignores_stale_metadata():
"""Only the first nonblank raw-role marker may assert delegation."""
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('_isChildSession'));
eval(extractFunc('_isForkWithResolvableParent'));
eval(extractFunc('_sidebarLineageKeyForRow'));
eval(extractFunc('_attachChildSessionsToSidebarRows'));
const parent = {{
session_id:'messaging_parent',
title:'Messaging parent',
raw_source:'weixin',
source_tag:'weixin',
session_source:'messaging',
}};
function runCase(name, overrides, includeChildInCollapsed=false) {{
const child = {{
session_id:name,
title:name,
parent_session_id:'messaging_parent',
relationship_type:'child_session',
raw_source:'api_server',
source_tag:'api_server',
source:'api_server',
session_source:'api',
_parent_lineage_root_id:'messaging_parent',
_cross_surface_child_session:true,
...overrides,
}};
const collapsed = includeChildInCollapsed ? [{{...parent}}, child] : [{{...parent}}];
const rows = _attachChildSessionsToSidebarRows(collapsed, [collapsed[0], child]);
const parentRow = rows.find(row => row.session_id === 'messaging_parent');
return {{
name,
topLevel:rows.map(row => row.session_id),
nested:(parentRow._child_sessions || []).map(row => row.session_id),
}};
}}
const results = [
runCase('stale_source_tag', {{
raw_source:'api_server',
source_tag:'subagent',
}}),
runCase('stale_session_source', {{
raw_source:'api_server',
source_tag:'api_server',
source:'api_server',
session_source:'subagent',
}}),
runCase('source_tag_fallback', {{
raw_source:' ',
source_tag:' SubAgent ',
source:'api_server',
session_source:'other',
}}),
runCase('authentic_raw_source', {{
raw_source:'subagent',
source_tag:'api_server',
source:'api_server',
session_source:'other',
}}),
runCase('not_child_session', {{
relationship_type:null,
raw_source:'subagent',
source_tag:'subagent',
source:'subagent',
session_source:'other',
}}, true),
];
console.log(JSON.stringify(results));
"""
assert json.loads(_run_node(source)) == [
{
"name": "stale_source_tag",
"topLevel": ["messaging_parent", "stale_source_tag"],
"nested": [],
},
{
"name": "stale_session_source",
"topLevel": ["messaging_parent", "stale_session_source"],
"nested": [],
},
{
"name": "source_tag_fallback",
"topLevel": ["messaging_parent"],
"nested": ["source_tag_fallback"],
},
{
"name": "authentic_raw_source",
"topLevel": ["messaging_parent"],
"nested": ["authentic_raw_source"],
},
{
"name": "not_child_session",
"topLevel": ["messaging_parent", "not_child_session"],
"nested": [],
},
]
def test_cross_surface_subagent_child_stacks_under_visible_fork_parent():
"""Forked WebUI conversations can still own subagent child rows."""