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:
1 parent
68008e824b
commit
b56dd423e7
2 files changed
+186
-6
No files matched your search
+11
-6
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in new issue
Block a user