feat(coordinator): 添加团队节点默认最大工具迭代次数配置
添加 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): 修复节点证据评估中需求验证逻辑 更新节点证据评估逻辑,跳过自然语言证据需求的确定性验证, 只执行机器可读的需求验证,避免因自然语言需求导致的节点失败。
This commit is contained in:
304
app-instance/frontend/lib/task-ui-model.test.ts
Normal file
304
app-instance/frontend/lib/task-ui-model.test.ts
Normal file
@ -0,0 +1,304 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user