Merge PR #7765 into stage/7765-subagent-project

This commit is contained in:
nesquena-hermes committed 2026-09-23 18:23:46 +00:00
commit 3f85d3bbc4
7 files changed
+266 -20

No files matched your search

+58 -9
View File
@@ -7671,7 +7671,50 @@ function _sidebarRowHasVisibleMessages(s, activeSidForSidebar){
(S.session&&s.session_id===S.session.session_id&&(S.session.message_count||0)>0);
}
function _isDelegatedSubagentRow(s){
if(!_isChildSession(s)) return false;
const role=[s.raw_source,s.source_tag,s.source].map(v=>String(v||'').trim().toLowerCase()).find(Boolean)||'';
return role==='subagent';
}
// Delegated subagents without a project_id take their nearest ancestor's project;
// every other row keeps its own. Memoized per render, so each lineage resolves once.
function _sidebarProjectResolver(rowsById){
const memo=new Map();
return function projectIdFor(s){
if(!s) return null;
const path=[];
const onPath=new Set();
let cur=s, result=null;
while(cur){
const sid=cur.session_id;
if(memo.has(sid)){ result=memo.get(sid); break; }
if(cur.project_id||!_isDelegatedSubagentRow(cur)){ result=cur.project_id||null; if(sid) path.push(sid); break; }
if(onPath.has(sid)) break;
onPath.add(sid); path.push(sid);
cur=rowsById?rowsById.get(cur.parent_session_id):null;
}
for(const sid of path) memo.set(sid,result);
return result;
};
}
function _sidebarRowsById(rows){
const byId=new Map();
for(const list of rows){
if(!Array.isArray(list)) continue;
for(const s of list) if(s&&s.session_id&&!byId.has(s.session_id)) byId.set(s.session_id,s);
}
return byId;
}
// True when any row resolves to no project, using the filter's own ancestor map.
function _sidebarHasUnprojectedRows(rows, projectIdFor){
return rows.some(s=>!projectIdFor(s));
}
function _partitionSidebarSessionRows(allMatched, activeSidForSidebar){
const projectIdFor=_sidebarProjectResolver(_sidebarRowsById([allMatched, typeof _sidebarReferenceSessions!=='undefined'?_sidebarReferenceSessions:null]));
let cliSessionCount=0;
const webuiProfileFiltered=[];
const cliProfileFiltered=[];
@@ -7690,10 +7733,11 @@ function _partitionSidebarSessionRows(allMatched, activeSidForSidebar){
const referenceRaw=isCli ? cliReferenceRaw : webuiReferenceRaw;
const sessionsRaw=isCli ? cliSessionsRaw : webuiSessionsRaw;
profileFiltered.push(s);
const projectId=projectIdFor(s);
if(_activeProject===NO_PROJECT_FILTER){
if(s.project_id) continue;
if(projectId) continue;
} else if(_activeProject){
if(s.project_id!==_activeProject) continue;
if(projectId!==_activeProject) continue;
}
referenceRaw.push(s);
if(s.archived){
@@ -7717,6 +7761,7 @@ function _partitionSidebarSessionRows(allMatched, activeSidForSidebar){
cliReferenceRaw,
webuiSessionsRaw,
cliSessionsRaw,
projectIdFor,
};
}
@@ -7728,15 +7773,18 @@ function _partitionSidebarSessionRows(allMatched, activeSidForSidebar){
// suppression context — silently hiding a visible child/fork whose archived
// ancestor lives outside the current view. Scope the references to the same
// project + source bucket as the render they feed before using them.
function _scopedSidebarReferenceRows(isCli){
// projectIdFor: the render's shared resolver; standalone callers get one over references + _allSessions.
function _scopedSidebarReferenceRows(isCli, projectIdFor){
if(typeof _sidebarReferenceSessions==='undefined'||!Array.isArray(_sidebarReferenceSessions)||!_sidebarReferenceSessions.length) return [];
const resolve=projectIdFor||_sidebarProjectResolver(_sidebarRowsById([_sidebarReferenceSessions, typeof _allSessions!=='undefined'?_allSessions:null]));
return _sidebarReferenceSessions.filter(s=>{
if(!s) return false;
// Source scope: only references in the same webui/cli bucket as this render.
if(_isCliSession(s)!==!!isCli) return false;
// Project scope: mirror _partitionSidebarSessionRows exactly.
if(_activeProject===NO_PROJECT_FILTER){ if(s.project_id) return false; }
else if(_activeProject){ if(s.project_id!==_activeProject) return false; }
const projectId=resolve(s);
if(_activeProject===NO_PROJECT_FILTER){ if(projectId) return false; }
else if(_activeProject){ if(projectId!==_activeProject) return false; }
return true;
});
}
@@ -7842,19 +7890,20 @@ function renderSessionListFromCache(){
cliReferenceRaw,
webuiSessionsRaw,
cliSessionsRaw,
projectIdFor,
}=_partitionSidebarSessionRows(allMatched, activeSidForSidebar);
const referenceRaw=_sessionSourceFilter==='cli'?cliReferenceRaw:webuiReferenceRaw;
const isCliView=_sessionSourceFilter==='cli';
const sessions=_renderSidebarRowsFromRawSessions(sessionsRaw, [...referenceRaw, ..._scopedSidebarReferenceRows(isCliView)]);
const sessions=_renderSidebarRowsFromRawSessions(sessionsRaw, [...referenceRaw, ..._scopedSidebarReferenceRows(isCliView, projectIdFor)]);
// Server-provided source bucket counts are authoritative for the current
// payload. When present, skip the expensive cross-bucket render/count pass;
// null is a deliberate "not computed" sentinel consumed only by
// _sessionSourceTabCount's fallback path below.
const renderedWebuiSessionCount=_serverWebuiSessionCount===null
? _renderSidebarRowsFromRawSessions(webuiSessionsRaw, [...webuiReferenceRaw, ..._scopedSidebarReferenceRows(false)]).length
? _renderSidebarRowsFromRawSessions(webuiSessionsRaw, [...webuiReferenceRaw, ..._scopedSidebarReferenceRows(false, projectIdFor)]).length
: null;
const renderedCliSessionCount=_serverCliSessionCount===null
? _renderSidebarRowsFromRawSessions(cliSessionsRaw, [...cliReferenceRaw, ..._scopedSidebarReferenceRows(true)]).length
? _renderSidebarRowsFromRawSessions(cliSessionsRaw, [...cliReferenceRaw, ..._scopedSidebarReferenceRows(true, projectIdFor)]).length
: null;
const webuiSessionTabCount=_sessionSourceTabCount('webui', renderedWebuiSessionCount, renderedCliSessionCount);
const cliSessionTabCount=_sessionSourceTabCount('cli', renderedWebuiSessionCount, renderedCliSessionCount);
@@ -7916,7 +7965,7 @@ function renderSessionListFromCache(){
}
// Project filter bar — show when there are real projects OR there are
// unassigned sessions (so the Unassigned chip has something to filter to).
const hasUnprojected=profileFiltered.some(s=>!s.project_id);
const hasUnprojected=_sidebarHasUnprojectedRows(profileFiltered, projectIdFor);
if(_allProjects.length>0||hasUnprojected){
const bar=document.createElement('div');
bar.className='project-bar';