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:
@@ -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'),
|
||||
}
|
||||
|
||||
121
frontend/src/pages/agents/agentsPage.test.ts
Normal file
121
frontend/src/pages/agents/agentsPage.test.ts
Normal 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
Reference in New Issue
Block a user