mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-08-19 05:33:29 +00:00
Fix session panel agent grouping
This commit is contained in:
parent
e28f5f5f67
commit
0572e974aa
3 changed files with 158 additions and 13 deletions
|
|
@ -105,12 +105,14 @@ function SectionList({ children }: { children: ReactNode }) {
|
|||
);
|
||||
}
|
||||
|
||||
function AgentsSection({
|
||||
function AgentCategorySection({
|
||||
title,
|
||||
items,
|
||||
scope,
|
||||
headerAction,
|
||||
onSelect,
|
||||
}: {
|
||||
title: string;
|
||||
items: SessionAgentItem[];
|
||||
scope: SessionPanelScope;
|
||||
headerAction?: ReactNode;
|
||||
|
|
@ -134,9 +136,11 @@ function AgentsSection({
|
|||
onClick={() => onSelect(item)}
|
||||
>
|
||||
{item.name ||
|
||||
t('layout.session-panel-remote-subagent', {
|
||||
defaultValue: 'Remote subagent',
|
||||
})}
|
||||
(item.subagent
|
||||
? t('layout.session-panel-remote-subagent', {
|
||||
defaultValue: 'Remote subagent',
|
||||
})
|
||||
: t('agents.agent', { defaultValue: 'Agent' }))}
|
||||
</SidePanelListRow>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -144,7 +148,7 @@ function AgentsSection({
|
|||
|
||||
return (
|
||||
<SidePanelAccordionBox
|
||||
title={t('layout.agents')}
|
||||
title={title}
|
||||
titleSuffix={<CountPill count={primary.length} />}
|
||||
headerAction={headerAction}
|
||||
defaultOpen={false}
|
||||
|
|
@ -451,6 +455,14 @@ export function SessionActivityPanel({
|
|||
() => buildProjectSessionPanelData(scopedRuns, skills, connectors),
|
||||
[connectors, scopedRuns, skills]
|
||||
);
|
||||
const agents = useMemo(
|
||||
() => panelData.agents.filter((agent) => !agent.subagent),
|
||||
[panelData.agents]
|
||||
);
|
||||
const subagents = useMemo(
|
||||
() => panelData.agents.filter((agent) => agent.subagent),
|
||||
[panelData.agents]
|
||||
);
|
||||
const projectFiles = useProjectOutputFiles(
|
||||
projectId,
|
||||
activeTask,
|
||||
|
|
@ -562,14 +574,25 @@ export function SessionActivityPanel({
|
|||
<div className="relative flex min-h-0 w-full min-w-0 flex-col overflow-hidden">
|
||||
<div className="scrollbar-always-visible flex min-h-0 min-w-0 flex-col overflow-y-auto overflow-x-hidden">
|
||||
<SectionList>
|
||||
{panelData.agents.length > 0 ? (
|
||||
<AgentsSection
|
||||
items={panelData.agents}
|
||||
{agents.length > 0 ? (
|
||||
<AgentCategorySection
|
||||
title={t('layout.agents')}
|
||||
items={agents}
|
||||
scope={scope}
|
||||
headerAction={agentHeaderAction}
|
||||
onSelect={setSelectedAgent}
|
||||
/>
|
||||
) : null}
|
||||
{subagents.length > 0 ? (
|
||||
<AgentCategorySection
|
||||
title={t('agents.sub-agents', {
|
||||
defaultValue: 'Sub Agents',
|
||||
})}
|
||||
items={subagents}
|
||||
scope={scope}
|
||||
onSelect={setSelectedAgent}
|
||||
/>
|
||||
) : null}
|
||||
{panelData.progress.length > 0 ? (
|
||||
<ProgressSection
|
||||
items={panelData.progress}
|
||||
|
|
|
|||
|
|
@ -247,6 +247,8 @@ function collectAgents(
|
|||
calls: SessionToolCall[]
|
||||
): SessionAgentItem[] {
|
||||
const agents = new Map<string, SessionAgentItem>();
|
||||
const isInternalAgent = (name: string) =>
|
||||
normalizeContextKey(name) === 'questionconfirmagent';
|
||||
const put = (
|
||||
run: ProjectSessionRun,
|
||||
identity: string,
|
||||
|
|
@ -284,19 +286,24 @@ function collectAgents(
|
|||
for (const node of run.nodes) {
|
||||
if (node.kind !== 'activity') continue;
|
||||
if (node.activityType === 'agent') {
|
||||
const text = `${node.eventType} ${node.title}`.toLowerCase();
|
||||
const name = node.agentName || node.title;
|
||||
if (isInternalAgent(name)) continue;
|
||||
const text = `${node.eventType} ${node.title} ${name}`.toLowerCase();
|
||||
const subagent = /sub.?agent|remote/.test(text);
|
||||
put(
|
||||
run,
|
||||
node.agentId || node.agentName || node.title,
|
||||
node.agentName || node.title,
|
||||
// Agent UUIDs are process instances and change on every Run. The
|
||||
// stable semantic name identifies the logical agent in the panel.
|
||||
name || node.agentId || 'agent',
|
||||
name,
|
||||
node.detail || '',
|
||||
subagent
|
||||
);
|
||||
} else if (node.agentId || node.agentName) {
|
||||
if (isInternalAgent(node.agentName || '')) continue;
|
||||
put(
|
||||
run,
|
||||
node.agentId || node.agentName || 'agent',
|
||||
node.agentName || node.agentId || 'agent',
|
||||
node.agentName || '',
|
||||
'',
|
||||
false
|
||||
|
|
@ -310,7 +317,11 @@ function collectAgents(
|
|||
if (!run) continue;
|
||||
const callText = `${call.toolkitName} ${call.method}`.toLowerCase();
|
||||
const subagent = /sub.?agent|remote/.test(callText);
|
||||
const identity = call.agentName || (subagent ? 'remote-subagent' : 'agent');
|
||||
// Typed tool lifecycle events may not carry agent identity. They are not
|
||||
// independent agents and must not create a phantom "Remote subagent" row.
|
||||
if (!call.agentName && !subagent) continue;
|
||||
if (isInternalAgent(call.agentName)) continue;
|
||||
const identity = call.agentName || 'remote-subagent';
|
||||
put(run, identity, call.agentName, '', subagent);
|
||||
const key = `${subagent ? 'subagent' : 'agent'}:${normalizeContextKey(
|
||||
identity
|
||||
|
|
|
|||
|
|
@ -67,6 +67,30 @@ function toolNode(
|
|||
};
|
||||
}
|
||||
|
||||
function agentNode(
|
||||
runId: string,
|
||||
eventId: string,
|
||||
sequence: number,
|
||||
agentId: string,
|
||||
agentName: string,
|
||||
eventType = 'legacy.create_agent',
|
||||
title = agentName
|
||||
): ChatActivityNode {
|
||||
return {
|
||||
...baseNode(runId, eventId, sequence),
|
||||
eventType,
|
||||
legacyStep: eventType.startsWith('legacy.')
|
||||
? eventType.replace('legacy.', '')
|
||||
: null,
|
||||
kind: 'activity',
|
||||
activityType: 'agent',
|
||||
status: 'running',
|
||||
title,
|
||||
agentId,
|
||||
agentName,
|
||||
};
|
||||
}
|
||||
|
||||
function todoActivity(
|
||||
eventId: string,
|
||||
sequence: number,
|
||||
|
|
@ -119,6 +143,93 @@ function makeRun(
|
|||
}
|
||||
|
||||
describe('buildProjectSessionPanelData', () => {
|
||||
it('deduplicates logical agents across Runs and skips anonymous tool frames', () => {
|
||||
const oldRun = makeRun('run-old', false, [
|
||||
agentNode(
|
||||
'run-old',
|
||||
'question-confirm',
|
||||
1,
|
||||
'confirm-agent-id',
|
||||
'question_confirm_agent'
|
||||
),
|
||||
agentNode(
|
||||
'run-old',
|
||||
'single-agent-old',
|
||||
2,
|
||||
'single-agent-instance-a',
|
||||
'single_agent'
|
||||
),
|
||||
]);
|
||||
const currentRun = makeRun('run-current', true, [
|
||||
agentNode(
|
||||
'run-current',
|
||||
'single-agent-current',
|
||||
1,
|
||||
'single-agent-instance-b',
|
||||
'single_agent'
|
||||
),
|
||||
{
|
||||
...toolNode(
|
||||
'run-current',
|
||||
'named-tool-frame',
|
||||
2,
|
||||
'running',
|
||||
'Registering agent'
|
||||
),
|
||||
agentId: undefined,
|
||||
agentName: 'single_agent',
|
||||
},
|
||||
{
|
||||
...toolNode(
|
||||
'run-current',
|
||||
'anonymous-canonical-tool',
|
||||
3,
|
||||
'running',
|
||||
'Tool without agent identity'
|
||||
),
|
||||
agentId: undefined,
|
||||
agentName: undefined,
|
||||
},
|
||||
]);
|
||||
|
||||
const data = buildProjectSessionPanelData([oldRun, currentRun], []);
|
||||
|
||||
expect(data.agents).toMatchObject([
|
||||
{
|
||||
id: 'agent:singleagent',
|
||||
name: 'single_agent',
|
||||
historical: false,
|
||||
subagent: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('classifies remote delegated agents separately from primary agents', () => {
|
||||
const run = makeRun('run-current', true, [
|
||||
agentNode(
|
||||
'run-current',
|
||||
'primary-agent',
|
||||
1,
|
||||
'primary-instance',
|
||||
'single_agent'
|
||||
),
|
||||
agentNode(
|
||||
'run-current',
|
||||
'remote-agent',
|
||||
2,
|
||||
'remote-instance',
|
||||
'research_helper',
|
||||
'agent.remote_started',
|
||||
'Remote subagent research_helper'
|
||||
),
|
||||
]);
|
||||
|
||||
expect(buildProjectSessionPanelData([run], []).agents).toMatchObject([
|
||||
{ name: 'single_agent', type: 'agent', subagent: false },
|
||||
{ name: 'research_helper', type: 'subagent', subagent: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('pairs semantic tool lifecycle events by durable call id', () => {
|
||||
const run = makeRun('run-1', true, [
|
||||
toolNode('run-1', 'tool-start', 1, 'running', 'Searching', 'call-1'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue