添加 DEFAULT_TEAM_NODE_MAX_TOOL_ITERATIONS 配置项以控制团队节点的最大工具迭代次数, 并修改 LocalAgentRunner 中的逻辑来使用此默认值当 envelope 中未指定时。 fix(runtime): 修复团队节点运行成功判断逻辑 更新运行成功判断条件,将 finish_reason 为 "max_tool_iterations_finalized" 的情况 视为运行失败,并添加对原始工具调用输出的检测,避免将其误判为成功完成。 feat(mcp): 添加团队工作流MCP工具类别支持 增加新的本地MCP工具类别 "team_workflow" 及其对应的工具创建功能, 为团队工作流提供本地工具支持。 refactor(engine): 调整AgentLoop最大工具迭代次数设置 将 AgentProfile 中的默认 max_tool_iterations 从 30 增加到 100, 同时移除 TaskExecutionPlanner 构造函数中的重复参数传递。 perf(mcp): 优化MCP连接管理避免重复连接 添加 mcp_connected 标志来跟踪MCP连接状态,确保 connect_all 只执行一次, 提高性能并避免不必要的重复连接。 refactor(skills): 移除技能团队模板相关功能 移除与技能团队模板相关的代码,包括解析、存储和处理逻辑, 简化技能记录结构和加载流程。 feat(process): 增强会话过程投影器功能 添加技能激活快照事件处理,改进团队运行完成消息显示, 并增强技能激活事件的时间戳记录功能。 refactor(tasks): 简化任务尝试编排器团队执行逻辑 移除团队执行相关代码,将所有任务统一按单步执行处理, 简化任务编排器的复杂度并提升执行效率。 fix(evidence): 修复节点证据评估中需求验证逻辑 更新节点证据评估逻辑,跳过自然语言证据需求的确定性验证, 只执行机器可读的需求验证,避免因自然语言需求导致的节点失败。
305 lines
9.1 KiB
TypeScript
305 lines
9.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { buildTaskUiModel } from '@/lib/task-ui-model';
|
|
import type { BackendTask, ProcessEvent, ProcessRun, SessionProcessProjection, TaskTimelineCard } from '@/types';
|
|
|
|
function task(overrides: Partial<BackendTask> = {}): BackendTask {
|
|
return {
|
|
task_id: 'task-1',
|
|
session_id: 'web:default',
|
|
description: 'Build MGM report',
|
|
goal: 'Build MGM report',
|
|
constraints: [],
|
|
priority: 0,
|
|
status: 'awaiting_acceptance',
|
|
creator: 'user',
|
|
created_at: '2026-06-24T00:00:00.000Z',
|
|
updated_at: '2026-06-24T00:01:00.000Z',
|
|
run_ids: ['main-run'],
|
|
skill_names: ['mgm-galaxy-financial-chart-report-safe'],
|
|
feedback: [],
|
|
metadata: {},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function process(overrides: Partial<SessionProcessProjection> = {}): SessionProcessProjection {
|
|
return {
|
|
runs: [],
|
|
events: [],
|
|
artifacts: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('buildTaskUiModel', () => {
|
|
it('keeps single-agent tasks out of the Agent Team tree and summarizes tool results', () => {
|
|
const cards: TaskTimelineCard[] = [
|
|
{
|
|
id: 'tool-start',
|
|
taskId: 'task-1',
|
|
runId: 'main-run',
|
|
type: 'tool_call',
|
|
title: 'Calling tool: web_search',
|
|
actorName: 'web_search',
|
|
status: 'running',
|
|
createdAt: '2026-06-24T00:00:10.000Z',
|
|
details: {
|
|
tool_name: 'web_search',
|
|
tool_call_id: 'call-1',
|
|
arguments: '{"query":"MGM China annual results"}',
|
|
},
|
|
},
|
|
{
|
|
id: 'tool-result',
|
|
taskId: 'task-1',
|
|
runId: 'main-run',
|
|
type: 'tool_result',
|
|
title: 'Tool result: web_search',
|
|
actorName: 'web_search',
|
|
status: 'done',
|
|
createdAt: '2026-06-24T00:00:20.000Z',
|
|
summary: '{"success":true,"query":"MGM China annual results","quality":"low","results":[{"title":"bad"}]}',
|
|
details: {
|
|
tool_name: 'web_search',
|
|
tool_call_id: 'call-1',
|
|
result_summary: '{"success":true,"query":"MGM China annual results","quality":"low","results":[{"title":"bad"}]}',
|
|
},
|
|
},
|
|
];
|
|
|
|
const model = buildTaskUiModel({
|
|
task: task(),
|
|
process: process({
|
|
runs: [
|
|
{
|
|
run_id: 'main-run',
|
|
parent_run_id: 'task:task-1:attempt:1',
|
|
session_id: 'web:default',
|
|
actor_type: 'agent',
|
|
actor_id: 'main-agent',
|
|
actor_name: 'Main Agent',
|
|
title: 'Final synthesis',
|
|
source: 'task_synthesis',
|
|
status: 'done',
|
|
started_at: '2026-06-24T00:00:00.000Z',
|
|
},
|
|
],
|
|
}),
|
|
cards,
|
|
locale: 'zh-Hans',
|
|
});
|
|
|
|
expect(model.executionMode).toBe('single');
|
|
expect(model.team.hasTeam).toBe(false);
|
|
expect(model.agentTree).toEqual([]);
|
|
expect(model.tools).toHaveLength(1);
|
|
expect(model.tools[0].status).toBe('done');
|
|
expect(model.tools[0].quality).toBe('low');
|
|
expect(model.tools[0].summary).toContain('MGM China annual results');
|
|
});
|
|
|
|
it('models real task team nodes separately from the main agent', () => {
|
|
const cards: TaskTimelineCard[] = [
|
|
{
|
|
id: 'team',
|
|
taskId: 'task-1',
|
|
runId: 'task:task-1:attempt:1',
|
|
type: 'agent_team',
|
|
title: 'Agent Team',
|
|
actorName: 'Task Team',
|
|
status: 'error',
|
|
createdAt: '2026-06-24T00:00:10.000Z',
|
|
details: {
|
|
task_outcome: 'incomplete',
|
|
node_ids: ['collect', 'report'],
|
|
incomplete_node_ids: ['collect'],
|
|
},
|
|
},
|
|
];
|
|
|
|
const runs: ProcessRun[] = [
|
|
{
|
|
run_id: 'main-run',
|
|
parent_run_id: 'task:task-1:attempt:1',
|
|
session_id: 'web:default',
|
|
actor_type: 'agent',
|
|
actor_id: 'main-agent',
|
|
actor_name: 'Main Agent',
|
|
title: 'Final synthesis',
|
|
source: 'task_synthesis',
|
|
status: 'done',
|
|
started_at: '2026-06-24T00:00:00.000Z',
|
|
},
|
|
{
|
|
run_id: 'collect-run',
|
|
parent_run_id: 'task:task-1:attempt:1',
|
|
session_id: 'web:default:team:collect',
|
|
actor_type: 'agent',
|
|
actor_id: 'collect',
|
|
actor_name: 'collect',
|
|
title: 'collect',
|
|
source: 'task_team',
|
|
status: 'error',
|
|
started_at: '2026-06-24T00:00:10.000Z',
|
|
metadata: { node_id: 'collect' },
|
|
},
|
|
];
|
|
|
|
const model = buildTaskUiModel({
|
|
task: task(),
|
|
process: process({ runs, events: [] as ProcessEvent[] }),
|
|
cards,
|
|
locale: 'zh-Hans',
|
|
});
|
|
|
|
expect(model.executionMode).toBe('team');
|
|
expect(model.team.hasTeam).toBe(true);
|
|
expect(model.team.outcome).toBe('incomplete');
|
|
expect(model.agentTree.map((node) => node.name)).toEqual(['collect']);
|
|
});
|
|
|
|
it('groups tools and result versions by task attempt', () => {
|
|
const cards: TaskTimelineCard[] = [
|
|
{
|
|
id: 'run-1-tool-call',
|
|
taskId: 'task-1',
|
|
runId: 'main-run-1',
|
|
type: 'tool_call',
|
|
title: 'Calling tool: web_fetch',
|
|
actorName: 'web_fetch',
|
|
status: 'running',
|
|
createdAt: '2026-06-24T00:00:10.000Z',
|
|
details: { tool_name: 'web_fetch', tool_call_id: 'call-1' },
|
|
},
|
|
{
|
|
id: 'run-1-tool-result',
|
|
taskId: 'task-1',
|
|
runId: 'main-run-1',
|
|
type: 'tool_result',
|
|
title: 'Tool result: web_fetch',
|
|
actorName: 'web_fetch',
|
|
status: 'error',
|
|
createdAt: '2026-06-24T00:00:15.000Z',
|
|
details: { tool_name: 'web_fetch', tool_call_id: 'call-1' },
|
|
},
|
|
{
|
|
id: 'run-1-result',
|
|
taskId: 'task-1',
|
|
runId: 'main-run-1',
|
|
type: 'result',
|
|
title: 'Result ready',
|
|
actorName: 'Main Agent',
|
|
status: 'error',
|
|
createdAt: '2026-06-24T00:00:20.000Z',
|
|
summary: 'First result',
|
|
},
|
|
{
|
|
id: 'run-2-tool-call',
|
|
taskId: 'task-1',
|
|
runId: 'main-run-2',
|
|
type: 'tool_call',
|
|
title: 'Calling tool: web_search',
|
|
actorName: 'web_search',
|
|
status: 'running',
|
|
createdAt: '2026-06-24T00:02:10.000Z',
|
|
details: { tool_name: 'web_search', tool_call_id: 'call-2' },
|
|
},
|
|
{
|
|
id: 'run-2-tool-result',
|
|
taskId: 'task-1',
|
|
runId: 'main-run-2',
|
|
type: 'tool_result',
|
|
title: 'Tool result: web_search',
|
|
actorName: 'web_search',
|
|
status: 'done',
|
|
createdAt: '2026-06-24T00:02:30.000Z',
|
|
details: { tool_name: 'web_search', tool_call_id: 'call-2' },
|
|
},
|
|
{
|
|
id: 'run-2-result',
|
|
taskId: 'task-1',
|
|
runId: 'main-run-2',
|
|
type: 'result',
|
|
title: 'Result ready',
|
|
actorName: 'Main Agent',
|
|
status: 'done',
|
|
createdAt: '2026-06-24T00:03:00.000Z',
|
|
summary: 'Second result',
|
|
},
|
|
];
|
|
|
|
const runs: ProcessRun[] = [
|
|
{
|
|
run_id: 'task:task-1:attempt:1',
|
|
session_id: 'web:default',
|
|
actor_type: 'system',
|
|
actor_id: 'task',
|
|
actor_name: 'Task Planner',
|
|
title: 'single plan',
|
|
source: 'task_mode',
|
|
status: 'error',
|
|
started_at: '2026-06-24T00:00:00.000Z',
|
|
metadata: { attempt_index: 1 },
|
|
},
|
|
{
|
|
run_id: 'main-run-1',
|
|
parent_run_id: 'task:task-1:attempt:1',
|
|
session_id: 'web:default',
|
|
actor_type: 'agent',
|
|
actor_id: 'main-agent',
|
|
actor_name: 'Main Agent',
|
|
title: 'Final synthesis',
|
|
source: 'task_synthesis',
|
|
status: 'error',
|
|
started_at: '2026-06-24T00:00:05.000Z',
|
|
finished_at: '2026-06-24T00:00:20.000Z',
|
|
metadata: { attempt_index: 1 },
|
|
},
|
|
{
|
|
run_id: 'task:task-1:attempt:2',
|
|
session_id: 'web:default',
|
|
actor_type: 'system',
|
|
actor_id: 'task',
|
|
actor_name: 'Task Planner',
|
|
title: 'single plan',
|
|
source: 'task_mode',
|
|
status: 'done',
|
|
started_at: '2026-06-24T00:02:00.000Z',
|
|
finished_at: '2026-06-24T00:03:00.000Z',
|
|
metadata: { attempt_index: 2 },
|
|
},
|
|
{
|
|
run_id: 'main-run-2',
|
|
parent_run_id: 'task:task-1:attempt:2',
|
|
session_id: 'web:default',
|
|
actor_type: 'agent',
|
|
actor_id: 'main-agent',
|
|
actor_name: 'Main Agent',
|
|
title: 'Final synthesis',
|
|
source: 'task_synthesis',
|
|
status: 'done',
|
|
started_at: '2026-06-24T00:02:05.000Z',
|
|
finished_at: '2026-06-24T00:03:00.000Z',
|
|
metadata: { attempt_index: 2 },
|
|
},
|
|
];
|
|
|
|
const model = buildTaskUiModel({
|
|
task: task({ run_ids: ['main-run-1', 'main-run-2'] }),
|
|
process: process({ runs }),
|
|
cards,
|
|
locale: 'zh-Hans',
|
|
});
|
|
|
|
expect(model.tools).toHaveLength(2);
|
|
expect(model.attempts).toHaveLength(2);
|
|
expect(model.attempts[0].index).toBe(1);
|
|
expect(model.attempts[0].tools.map((tool) => tool.toolName)).toEqual(['web_fetch']);
|
|
expect(model.attempts[0].result?.summary).toBe('First result');
|
|
expect(model.attempts[1].index).toBe(2);
|
|
expect(model.attempts[1].tools.map((tool) => tool.toolName)).toEqual(['web_search']);
|
|
expect(model.attempts[1].result?.summary).toBe('Second result');
|
|
});
|
|
});
|