Refine agents command center topology visuals

Strengthen the Ultron command center with clearer blueprint-style hierarchy, embedded route telemetry, and test coverage for active path visualization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 21:42:01 +08:00
parent b8d135a7e2
commit aafa05dc1c
3 changed files with 1875 additions and 564 deletions

View File

@@ -1,14 +1,35 @@
export interface Agent {
id: string
name: string
role: string // 中文角色名
roleKey: string // 英文 key: master/planner/executor/librarian/analyst
role: string
roleKey: string
description: string
systemPrompt: string
enabled: boolean
isMaster?: boolean
}
export interface SubCommander {
id: string
parentId: MainAgentId
name: string
role: string
description: string
relationLabel: string
toolScopeLabel: string
}
export interface CommanderSkill {
id: string
label: string
title: string
description: string
relatedAgentIds: string[]
stateLabel: string
}
export type MainAgentId = 'master' | 'planner' | 'executor' | 'librarian' | 'analyst'
export const DEFAULT_AGENTS: Agent[] = [
{
id: 'master',
@@ -58,12 +79,14 @@ export const DEFAULT_AGENTS: Agent[] = [
},
]
export const AGENT_RELATIONS: Record<string, string[]> = {
export const MAIN_AGENT_ORDER: MainAgentId[] = ['planner', 'executor', 'librarian', 'analyst']
export const AGENT_RELATIONS: Record<MainAgentId, string[]> = {
master: ['planner', 'executor', 'librarian', 'analyst'],
planner: [],
executor: [],
librarian: [],
analyst: [],
planner: ['planner_scope', 'planner_steps'],
executor: ['executor_tasks', 'executor_forum'],
librarian: ['librarian_retrieval', 'librarian_graph'],
analyst: ['analyst_progress', 'analyst_insights'],
}
export const RELATION_LABELS: Record<string, string> = {
@@ -71,4 +94,137 @@ export const RELATION_LABELS: Record<string, string> = {
'master-executor': '执行指令',
'master-librarian': '查询知识',
'master-analyst': '请求分析',
'planner-planner_scope': '收束目标',
'planner-planner_steps': '拆解路径',
'executor-executor_tasks': '调度任务工具',
'executor-executor_forum': '调度论坛工具',
'librarian-librarian_retrieval': '检索知识',
'librarian-librarian_graph': '沉淀图谱',
'analyst-analyst_progress': '汇总进度',
'analyst-analyst_insights': '生成洞察',
}
export const COMMANDER_SKILLS: CommanderSkill[] = [
{
id: 'skill_orchestration',
label: 'CORE-01',
title: '战略编排',
description: '统一调度主控链路,决定由谁接管当前任务。',
relatedAgentIds: ['master'],
stateLabel: 'ROUTING',
},
{
id: 'skill_planner',
label: 'CORE-02',
title: '路径拆解',
description: '把复杂目标压缩成清晰路径、优先级与执行步骤。',
relatedAgentIds: ['planner', 'planner_scope', 'planner_steps'],
stateLabel: 'STRUCTURE',
},
{
id: 'skill_executor',
label: 'CORE-03',
title: '执行调度',
description: '驱动任务工具与交互动作,把计划转成实际推进。',
relatedAgentIds: ['executor', 'executor_tasks', 'executor_forum'],
stateLabel: 'ACTION',
},
{
id: 'skill_librarian',
label: 'CORE-04',
title: '知识检索',
description: '连接知识与上下文,让信息可以被实时调取和利用。',
relatedAgentIds: ['librarian', 'librarian_retrieval', 'librarian_graph'],
stateLabel: 'RETRIEVE',
},
{
id: 'skill_analyst',
label: 'CORE-05',
title: '洞察研判',
description: '从状态和数据中提炼风险、趋势与行动建议。',
relatedAgentIds: ['analyst', 'analyst_progress', 'analyst_insights'],
stateLabel: 'INSIGHT',
},
]
export const SUB_COMMANDERS: SubCommander[] = [
{
id: 'planner_scope',
parentId: 'planner',
name: 'SCOPE',
role: '目标收束官',
description: '负责澄清目标、识别边界与约束,让规划前提先变清楚。',
relationLabel: RELATION_LABELS['planner-planner_scope'],
toolScopeLabel: '约束/需求',
},
{
id: 'planner_steps',
parentId: 'planner',
name: 'STEPS',
role: '步骤拆解官',
description: '负责把目标拆成步骤、优先级与依赖关系,形成执行路径。',
relationLabel: RELATION_LABELS['planner-planner_steps'],
toolScopeLabel: '路径/排序',
},
{
id: 'executor_tasks',
parentId: 'executor',
name: 'TASK OPS',
role: '任务执行官',
description: '负责调用任务相关工具,推进创建、更新与状态执行。',
relationLabel: RELATION_LABELS['executor-executor_tasks'],
toolScopeLabel: 'Task Tools',
},
{
id: 'executor_forum',
parentId: 'executor',
name: 'FORUM OPS',
role: '论坛执行官',
description: '负责处理论坛发帖、扫描指令等交互型执行操作。',
relationLabel: RELATION_LABELS['executor-executor_forum'],
toolScopeLabel: 'Forum Tools',
},
{
id: 'librarian_retrieval',
parentId: 'librarian',
name: 'RETRIEVAL',
role: '检索问答官',
description: '负责检索知识库与上下文,快速给出证据范围内的回答。',
relationLabel: RELATION_LABELS['librarian-librarian_retrieval'],
toolScopeLabel: 'Search / Hybrid',
},
{
id: 'librarian_graph',
parentId: 'librarian',
name: 'GRAPH LINK',
role: '图谱沉淀官',
description: '负责知识图谱构建、关系串联与结构化沉淀。',
relationLabel: RELATION_LABELS['librarian-librarian_graph'],
toolScopeLabel: 'Graph Context',
},
{
id: 'analyst_progress',
parentId: 'analyst',
name: 'PROGRESS',
role: '进度研判官',
description: '负责汇总任务与讨论状态,判断阶段进展与卡点。',
relationLabel: RELATION_LABELS['analyst-analyst_progress'],
toolScopeLabel: 'Status / Tasks',
},
{
id: 'analyst_insights',
parentId: 'analyst',
name: 'INSIGHTS',
role: '洞察建议官',
description: '负责提炼趋势、风险与建议,把数据转成判断。',
relationLabel: RELATION_LABELS['analyst-analyst_insights'],
toolScopeLabel: 'Trends / Advice',
},
]
export const SUB_COMMANDERS_BY_PARENT: Record<Exclude<MainAgentId, 'master'>, SubCommander[]> = {
planner: SUB_COMMANDERS.filter((item) => item.parentId === 'planner'),
executor: SUB_COMMANDERS.filter((item) => item.parentId === 'executor'),
librarian: SUB_COMMANDERS.filter((item) => item.parentId === 'librarian'),
analyst: SUB_COMMANDERS.filter((item) => item.parentId === 'analyst'),
}

View File

@@ -0,0 +1,121 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { mount } from '@vue/test-utils'
const mocks = vi.hoisted(() => ({
getHierarchyStats: vi.fn(),
updateConfig: vi.fn(),
}))
vi.mock('@/api/agent', () => ({
agentApi: {
getHierarchyStats: mocks.getHierarchyStats,
updateConfig: mocks.updateConfig,
},
}))
import AgentsPage from './index.vue'
const hierarchyStats = {
main_agents: [
{
agent_id: 'planner',
call_count: 12,
current_task: null,
status: 'idle',
sub_commanders: [
{ agent_id: 'planner_scope', call_count: 4, current_task: null, status: 'idle' },
{ agent_id: 'planner_steps', call_count: 9, current_task: '拆解执行步骤', status: 'active' },
],
},
{
agent_id: 'executor',
call_count: 8,
current_task: null,
status: 'idle',
sub_commanders: [
{ agent_id: 'executor_tasks', call_count: 8, current_task: null, status: 'idle' },
{ agent_id: 'executor_forum', call_count: 4, current_task: null, status: 'idle' },
],
},
{
agent_id: 'librarian',
call_count: 5,
current_task: null,
status: 'idle',
sub_commanders: [
{ agent_id: 'librarian_retrieval', call_count: 5, current_task: null, status: 'idle' },
{ agent_id: 'librarian_graph', call_count: 2, current_task: null, status: 'idle' },
],
},
{
agent_id: 'analyst',
call_count: 3,
current_task: null,
status: 'idle',
sub_commanders: [
{ agent_id: 'analyst_progress', call_count: 2, current_task: null, status: 'idle' },
{ agent_id: 'analyst_insights', call_count: 3, current_task: null, status: 'idle' },
],
},
],
}
describe('agents page pcb command center', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
mocks.getHierarchyStats.mockResolvedValue(hierarchyStats)
mocks.updateConfig.mockResolvedValue({})
})
it('shows commander skills and active route telemetry for an active sub commander path', async () => {
const wrapper = mount(AgentsPage)
await Promise.resolve()
await Promise.resolve()
const skillsPanel = wrapper.get('[data-testid="commander-skills"]')
expect(skillsPanel.text()).toContain('指挥官技能')
expect(skillsPanel.text()).toContain('路径拆解')
const activeSkill = wrapper.get('[data-testid="commander-skill-skill_planner"]')
expect(activeSkill.classes()).toContain('active')
const plannerBus = wrapper.get('[data-testid="bus-link-planner"]')
expect(plannerBus.classes()).toContain('energized')
const plannerStepsBranch = wrapper.get('[data-testid="sub-link-planner_steps"]')
expect(plannerStepsBranch.classes()).toContain('energized')
const routeTelemetry = wrapper.get('[data-testid="route-telemetry"]')
expect(routeTelemetry.text()).toContain('ACTIVE ROUTE')
expect(routeTelemetry.text()).toContain('PLANNER')
expect(routeTelemetry.text()).toContain('STEPS')
})
it('keeps the configuration drawer available when selecting a main agent chip', async () => {
const wrapper = mount(AgentsPage)
await Promise.resolve()
await Promise.resolve()
await wrapper.get('[data-testid="agent-chip-planner"]').trigger('click')
expect(wrapper.text()).toContain('AGENT CONFIGURATION')
expect(wrapper.find('input').exists()).toBe(true)
})
it('cycles the energized demo path when hierarchy stats are offline', async () => {
mocks.getHierarchyStats.mockRejectedValue(new Error('offline'))
const wrapper = mount(AgentsPage)
await Promise.resolve()
await Promise.resolve()
expect(wrapper.get('[data-testid="bus-link-planner"]').classes()).toContain('energized')
expect(wrapper.get('[data-testid="sub-link-planner_scope"]').classes()).toContain('energized')
await vi.advanceTimersByTimeAsync(1700)
expect(wrapper.get('[data-testid="sub-link-planner_scope"]').classes()).not.toContain('energized')
expect(wrapper.get('[data-testid="sub-link-planner_steps"]').classes()).toContain('energized')
})
})

File diff suppressed because it is too large Load Diff