import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; /** * Master-Innovator 集成测试 * * 验证 Master Session 的完整工具调用链路: * 1. Master 收到用户消息 → 解析为 orchestrate 工具调用 * 2. orchestrate 返回编排计划 * 3. Master 按计划调用 call_sub_agent * 4. 最终汇总结果回复用户 * * 通过 mock AgentManager.callAgent 模拟 Master 的 Claude CLI 输出。 */ const { MasterSession } = require('../../core/master-session'); const { ToolRegistry } = require('../../tools/tool-registry'); const { createOrchestrateTool } = require('../../tools/orchestrate-tool'); const { createCallAgentTool, createListAgentsTool } = require('../../tools/call-agent-tool'); // Mock logging vi.mock('../../core/logging', () => ({ getGlobalLogManager: () => ({ initialized: false, getOrCreateSession: () => null, }), waitForGlobalLogManager: () => Promise.resolve(), })); // ─── 测试配置 ─── const testConfig = { agents: { master: { type: 'master-cli', client: 'claude-local', description: 'Master Agent' }, coder: { type: 'executor', client: 'claude-local', description: '编写代码' }, writer: { type: 'executor', client: 'claude-local', description: '创意写作' }, verifier: { type: 'validator', client: 'claude-local', description: '代码验证' }, 'frontend-tester': { type: 'validator', client: 'claude-local', description: '前端测试' }, }, agentGroups: { development: { name: '软件开发', description: '编写和验证代码', default: true, agents: ['coder', 'verifier', 'frontend-tester'], }, writing: { name: '创意写作', description: '写作和编辑', agents: ['writer'], }, }, clients: { 'claude-local': { command: 'claude', args: [] }, }, }; // ─── Helper ─── function createMockSession(callResponses) { let callIndex = 0; const session = new MasterSession(testConfig, '/tmp/test-master'); // 直接 mock init 跳过真实 AgentManager 创建 session._initialized = true; // 手动注册工具 session.toolRegistry = new ToolRegistry(); session.toolRegistry.registerAll([ createOrchestrateTool(), createCallAgentTool(), createListAgentsTool(), ]); // Mock AgentManager const mockCallAgent = vi.fn().mockImplementation(async (agentName, prompt) => { if (agentName === 'master') { // Master 的模拟回复 — 按序返回预设响应 const response = callResponses[callIndex++] || { output: '任务完成!' }; return response; } // Sub Agent 的模拟回复 return { output: `[${agentName}] 执行完成: ${prompt.substring(0, 50)}...` }; }); session.manager = { callAgent: mockCallAgent, closeAllSessions: vi.fn(), getAgentConfig: vi.fn().mockImplementation((name) => { const cfg = testConfig.agents[name]; if (!cfg) throw new Error(`Agent "${name}" not found`); return cfg; }), listAgents: vi.fn().mockReturnValue( Object.entries(testConfig.agents).map(([name, cfg]) => ({ name, ...cfg })) ), }; return { session, mockCallAgent }; } // ─── 测试 ─── describe('Master Session — 工具调用集成测试', () => { afterEach(() => { vi.restoreAllMocks(); }); // ── 1. 直接回复(无工具调用) ── describe('直接回复', () => { it('should reply directly for simple questions without tool calls', async () => { const { session } = createMockSession([ { output: 'Docker 是一个容器化平台,用于打包和运行应用。' }, ]); const result = await session.handleMessage('Docker 是什么?'); expect(result.type).toBe('reply'); expect(result.output).toContain('Docker'); expect(result.toolCalls).toHaveLength(0); expect(result.rounds).toBe(1); }); }); // ── 2. 单 Agent 调用(call_sub_agent) ── describe('单 Agent 调用', () => { it('should call sub agent directly and return result', async () => { const { session, mockCallAgent } = createMockSession([ // Round 1: Master 调用 call_sub_agent { output: `好的,我来帮你写一首诗。 [TOOL_CALL: call_sub_agent] { "agent": "writer", "task": "写一首关于秋天的五言绝句" } [/TOOL_CALL]`, }, // Round 2: Master 收到结果后汇总回复 { output: '这是 writer 创作的秋天诗:\n\n秋风起天末,万叶落纷纷。\n孤雁向南去,寒山映夕云。', }, ]); const result = await session.handleMessage('帮我写一首关于秋天的诗'); expect(result.type).toBe('reply'); expect(result.rounds).toBe(2); expect(result.toolCalls).toHaveLength(1); expect(result.toolCalls[0].tool).toBe('call_sub_agent'); expect(result.toolCalls[0].params.agent).toBe('writer'); // 验证 Master 被调了 2 次(初始 + 工具结果反馈) expect(mockCallAgent).toHaveBeenCalledTimes(3); // 2 master + 1 writer // 验证 writer 确实被调用了 const writerCall = mockCallAgent.mock.calls.find(c => c[0] === 'writer'); expect(writerCall).toBeTruthy(); expect(writerCall[1]).toContain('秋天'); }); }); // ── 3. orchestrate + 多步骤 call_sub_agent ── describe('多 Agent 编排', () => { it('should orchestrate plan then call sub agents step by step', async () => { const { session, mockCallAgent } = createMockSession([ // Round 1: Master 调用 orchestrate 获取编排计划 { output: `这是一个需要多 Agent 协作的任务,让我先获取编排计划。 [TOOL_CALL: orchestrate] { "task": "开发一个 Python 命令行计算器", "agentGroup": "development" } [/TOOL_CALL]`, }, // Round 2: Master 收到 plan,调用 coder { output: `收到编排计划。按步骤执行,先调用 coder。 [TOOL_CALL: call_sub_agent] { "agent": "coder", "task": "开发一个 Python 命令行计算器,支持加减乘除,处理除零和非法输入" } [/TOOL_CALL]`, }, // Round 3: coder 完成,调用 verifier { output: `coder 已完成代码编写。现在调用 verifier 验证。 [TOOL_CALL: call_sub_agent] { "agent": "verifier", "task": "验证 Python 计算器:运行测试,检查除零处理和错误输入处理" } [/TOOL_CALL]`, }, // Round 4: 全部通过,汇总回复 { output: `✅ 任务完成! **计算器已开发并验证通过:** - coder 创建了 calc.py,支持加减乘除 - verifier 验证通过,除零和非法输入都有正确处理 所有步骤已完成。`, }, ]); const toolCalls = []; const result = await session.handleMessage('开发一个 Python 命令行计算器', { onToolCall: (tc) => toolCalls.push(tc), }); // 验证最终结果 expect(result.type).toBe('reply'); expect(result.rounds).toBe(4); expect(result.output).toContain('任务完成'); // 验证工具调用历史 expect(result.toolCalls).toHaveLength(3); // orchestrate + coder + verifier expect(result.toolCalls[0].tool).toBe('orchestrate'); expect(result.toolCalls[1].tool).toBe('call_sub_agent'); expect(result.toolCalls[1].params.agent).toBe('coder'); expect(result.toolCalls[2].tool).toBe('call_sub_agent'); expect(result.toolCalls[2].params.agent).toBe('verifier'); // 验证 orchestrate 返回了有效 plan const orchResult = result.toolCalls[0].result; expect(orchResult.status).toBe('success'); expect(orchResult.result.status).toBe('success'); expect(orchResult.result.plan.agentGroup).toBe('development'); expect(orchResult.result.plan.steps.length).toBeGreaterThan(0); // 验证 onToolCall 回调被触发 expect(toolCalls).toHaveLength(3); // 验证 Sub Agent 被实际调用 const agentCalls = mockCallAgent.mock.calls.filter(c => c[0] !== 'master'); expect(agentCalls.map(c => c[0])).toEqual(['coder', 'verifier']); }); }); // ── 4. 修复循环 ── describe('修复循环', () => { it('should handle validator failure → executor fix → re-validate', async () => { const { session, mockCallAgent } = createMockSession([ // Round 1: 直接调 coder { output: `[TOOL_CALL: call_sub_agent] { "agent": "coder", "task": "写一个 sort 函数" } [/TOOL_CALL]`, }, // Round 2: 调 verifier { output: `[TOOL_CALL: call_sub_agent] { "agent": "verifier", "task": "验证 sort 函数" } [/TOOL_CALL]`, }, // Round 3: verifier "失败"(通过模拟 Sub Agent 返回失败结果),Master 让 coder 修复 { output: `verifier 报告了问题,让 coder 修复。 [TOOL_CALL: call_sub_agent] { "agent": "coder", "task": "修复 sort 函数:空数组未处理" } [/TOOL_CALL]`, }, // Round 4: 再次验证 { output: `[TOOL_CALL: call_sub_agent] { "agent": "verifier", "task": "重新验证 sort 函数" } [/TOOL_CALL]`, }, // Round 5: 完成 { output: '✅ sort 函数已修复并验证通过。', }, ]); const result = await session.handleMessage('写一个排序函数'); expect(result.type).toBe('reply'); expect(result.rounds).toBe(5); // 验证工具调用:coder → verifier → coder(修复) → verifier(重验) const agents = result.toolCalls.map(tc => tc.params.agent); expect(agents).toEqual(['coder', 'verifier', 'coder', 'verifier']); }); }); // ── 5. list_agents 工具 ── describe('list_agents 工具', () => { it('should return available agents when Master calls list_agents', async () => { const { session } = createMockSession([ { output: `[TOOL_CALL: list_agents] {} [/TOOL_CALL]`, }, { output: '目前可用的 Agent 有:coder(编写代码)、writer(创意写作)、verifier(代码验证)。', }, ]); const result = await session.handleMessage('有哪些 Agent 可用?'); expect(result.type).toBe('reply'); expect(result.toolCalls).toHaveLength(1); expect(result.toolCalls[0].tool).toBe('list_agents'); const listResult = result.toolCalls[0].result.result; expect(listResult.agents.length).toBeGreaterThan(0); expect(listResult.agents.find(a => a.name === 'coder')).toBeTruthy(); expect(listResult.groups.length).toBeGreaterThan(0); }); }); // ── 6. 未知工具处理 ── describe('错误处理', () => { it('should handle unknown tool gracefully', async () => { const { session } = createMockSession([ { output: `[TOOL_CALL: nonexistent_tool] {"task": "test"} [/TOOL_CALL]`, }, { output: '工具不存在,我直接回答你的问题。', }, ]); const result = await session.handleMessage('测试'); expect(result.type).toBe('reply'); expect(result.toolCalls).toHaveLength(1); expect(result.toolCalls[0].result.status).toBe('error'); expect(result.toolCalls[0].result.error).toContain('Unknown tool'); }); it('should handle nonexistent agent in call_sub_agent', async () => { const { session } = createMockSession([ { output: `[TOOL_CALL: call_sub_agent] { "agent": "nonexistent_agent", "task": "test" } [/TOOL_CALL]`, }, { output: '该 Agent 不存在。', }, ]); const result = await session.handleMessage('调用不存在的 agent'); expect(result.type).toBe('reply'); expect(result.toolCalls).toHaveLength(1); expect(result.toolCalls[0].result.status).toBe('error'); expect(result.toolCalls[0].result.error).toContain('不存在'); }); it('should stop at max tool rounds', async () => { // 无限循环调用工具 const infiniteResponses = Array(15).fill({ output: `[TOOL_CALL: list_agents] {} [/TOOL_CALL]`, }); const { session } = createMockSession(infiniteResponses); const result = await session.handleMessage('无限循环', { maxToolRounds: 3, }); expect(result.type).toBe('max_rounds'); expect(result.rounds).toBe(3); }); }); // ── 7. ToolRegistry 单元测试 ── describe('ToolRegistry', () => { it('should parse multiple tool calls from output', () => { const output = ` 先做第一步: [TOOL_CALL: orchestrate] {"task": "test task"} [/TOOL_CALL] 还有第二步: [TOOL_CALL: call_sub_agent] {"agent": "coder", "task": "write code"} [/TOOL_CALL] `; const calls = ToolRegistry.parseToolCalls(output); expect(calls).toHaveLength(2); expect(calls[0].tool).toBe('orchestrate'); expect(calls[0].params.task).toBe('test task'); expect(calls[1].tool).toBe('call_sub_agent'); expect(calls[1].params.agent).toBe('coder'); }); it('should handle non-JSON params as raw task', () => { const output = `[TOOL_CALL: call_sub_agent] just a plain text task [/TOOL_CALL]`; const calls = ToolRegistry.parseToolCalls(output); expect(calls).toHaveLength(1); expect(calls[0].params.task).toBe('just a plain text task'); }); it('should return empty array for no tool calls', () => { const calls = ToolRegistry.parseToolCalls('Just a normal reply with no tools.'); expect(calls).toHaveLength(0); }); it('should generate tool prompt with all registered tools', () => { const registry = new ToolRegistry(); registry.registerAll([ createOrchestrateTool(), createCallAgentTool(), createListAgentsTool(), ]); const prompt = registry.generateToolPrompt(); expect(prompt).toContain('orchestrate'); expect(prompt).toContain('call_sub_agent'); expect(prompt).toContain('list_agents'); expect(prompt).toContain('[TOOL_CALL:'); }); }); // ── 8. orchestrate planning 验证 ── describe('orchestrate planning', () => { it('should generate valid plan for development group', async () => { const tool = createOrchestrateTool(); const result = await tool.execute( { task: '开发计算器', agentGroup: 'development' }, { config: testConfig } ); expect(result.status).toBe('success'); expect(result.plan.agentGroup).toBe('development'); expect(result.plan.steps.length).toBeGreaterThan(0); // executor 步骤 const executorSteps = result.plan.steps.filter(s => s.type === 'executor'); expect(executorSteps.length).toBe(1); // coder expect(executorSteps[0].agent).toBe('coder'); // validator 步骤 const validatorSteps = result.plan.steps.filter(s => s.type === 'validator'); expect(validatorSteps.length).toBe(2); // verifier + frontend-tester // conditional 修复步骤 const conditionalSteps = result.plan.steps.filter(s => s.action === 'conditional'); expect(conditionalSteps.length).toBe(1); }); it('should return error for nonexistent group', async () => { const tool = createOrchestrateTool(); const result = await tool.execute( { task: 'test', agentGroup: 'nonexistent' }, { config: testConfig } ); expect(result.status).toBe('error'); expect(result.message).toContain('不存在'); }); it('should use default group when not specified', async () => { const tool = createOrchestrateTool(); const result = await tool.execute( { task: '开发功能' }, { config: testConfig } ); expect(result.status).toBe('success'); expect(result.plan.agentGroup).toBe('development'); // default: true }); }); // ── 9. 事件发射验证 ── describe('事件发射', () => { it('should emit round and tool_call events', async () => { const { session } = createMockSession([ { output: `[TOOL_CALL: list_agents] {} [/TOOL_CALL]`, }, { output: '完成' }, ]); const events = { round: [], tool_call: [], tool_result: [] }; session.on('round', (e) => events.round.push(e)); session.on('tool_call', (e) => events.tool_call.push(e)); session.on('tool_result', (e) => events.tool_result.push(e)); await session.handleMessage('test'); expect(events.round.length).toBe(2); // 2 rounds expect(events.tool_call.length).toBe(1); expect(events.tool_call[0].tool).toBe('list_agents'); expect(events.tool_result.length).toBe(1); }); }); });