import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import fs from 'fs'; /** * Phase 4 集成验证 — 模拟完整编排流程 * * 通过注入 mock AgentManager 到真实 Orchestrator, * 验证修复循环、并行调度、超时处理、Checkpoint 恢复等场景。 */ const { Orchestrator } = require('../../core/orchestrator'); // 禁止 waitForGlobalLogManager 阻塞 vi.mock('../../core/logging', () => ({ getGlobalLogManager: () => ({ initialized: false, getOrCreateSession: () => null, }), waitForGlobalLogManager: () => Promise.resolve(), })); const testConfig = { agents: { master: { type: 'executor' }, coder: { type: 'executor' }, verifier: { type: 'validator', dependsOn: ['coder'] }, tester: { type: 'validator', dependsOn: ['coder'] }, }, }; function createOrchestrator(mockCallAgent) { const orch = new Orchestrator(testConfig, '/tmp/test-e2e', { logManager: { initialized: false, getOrCreateSession: () => null }, }); // 注入 mock manager orch.manager = { callAgent: mockCallAgent, closeAllSessions: vi.fn(), }; return orch; } describe('Phase 4: 集成验证', () => { beforeEach(() => { vi.spyOn(fs, 'readdirSync').mockReturnValue([]); vi.spyOn(fs, 'existsSync').mockReturnValue(true); }); afterEach(() => { vi.restoreAllMocks(); }); // ── 4.1 修复循环:validator FAIL → coder 修 → validator PASS ── describe('修复循环 (repair loop)', () => { it('should complete after coder fixes failing validator', async () => { let callCount = 0; const events = []; const mockCallAgent = vi.fn(async (agentName, prompt) => { callCount++; // 第 1 轮 master: 先调 coder if (agentName === 'master' && callCount === 1) { return { output: '[CALL: coder]\n写一个 add 函数\n[/CALL]' }; } // coder 第一次执行 if (agentName === 'coder' && callCount === 2) { return { output: '[RESULT: DONE]\n创建了 add.js(含 bug)\n[/RESULT]' }; } // 第 2 轮 master: 调 verifier if (agentName === 'master' && callCount === 3) { return { output: '[CALL: verifier]\n验证 add.js\n[/CALL]' }; } // verifier 首次 FAIL if (agentName === 'verifier' && callCount === 4) { return { output: '[RESULT: FAIL]\nadd(1,2) 返回 null,期望 3\n[/RESULT]' }; } // 第 3 轮 master: 再调 coder 修复 if (agentName === 'master' && callCount === 5) { return { output: '[CALL: coder]\n修复 add 函数的 bug\n[/CALL]' }; } // coder 修复 if (agentName === 'coder' && callCount === 6) { return { output: '[RESULT: DONE]\n修复完成\n[/RESULT]' }; } // 第 4 轮 master: 再调 verifier + tester if (agentName === 'master' && callCount === 7) { return { output: '[CALL: verifier]\n重新验证\n[/CALL]' }; } // verifier PASS if (agentName === 'verifier' && callCount === 8) { return { output: '[RESULT: PASS]\n所有测试通过\n[/RESULT]' }; } // 第 5 轮 master: 调 tester if (agentName === 'master' && callCount === 9) { return { output: '[CALL: tester]\n运行 e2e 测试\n[/CALL]' }; } // tester PASS if (agentName === 'tester' && callCount === 10) { return { output: '[RESULT: PASS]\ne2e 通过\n[/RESULT]' }; } return { output: '完成' }; }); const orch = createOrchestrator(mockCallAgent); orch.on('progress', (data) => events.push({ type: 'progress', ...data })); orch.on('completed', (data) => events.push({ type: 'completed', ...data })); const result = await orch.execute('写一个 add 函数并验证'); // 验证结果 expect(result.completed).toBe(true); expect(result.repairs).toBeGreaterThanOrEqual(1); // validator FAIL 触发修复 // 验证事件 const completedEvent = events.find(e => e.type === 'completed'); expect(completedEvent).toBeDefined(); expect(completedEvent.results.length).toBeGreaterThanOrEqual(3); // 验证修复链路 const progressEvents = events.filter(e => e.type === 'progress'); expect(progressEvents.length).toBeGreaterThanOrEqual(2); }); }); // ── 4.2 maxRepairs=1 极限 ── describe('修复次数耗尽 (maxRepairs limit)', () => { it('should stop when maxRepairs is exhausted', async () => { let callCount = 0; const mockCallAgent = vi.fn(async (agentName) => { callCount++; // master 始终调 coder if (agentName === 'master') { // 偶数轮调 verifier if (callCount % 3 === 0) { return { output: '[CALL: verifier]\n验证\n[/CALL]' }; } return { output: '[CALL: coder]\n写代码\n[/CALL]' }; } if (agentName === 'coder') { return { output: '[RESULT: DONE]\nok\n[/RESULT]' }; } // verifier 始终 FAIL if (agentName === 'verifier') { return { output: '[RESULT: FAIL]\n始终失败\n[/RESULT]' }; } return { output: '' }; }); const orch = createOrchestrator(mockCallAgent); const result = await orch.execute('任务', { maxIterations: 1 }); expect(result.completed).toBe(false); expect(result.repairs).toBe(1); expect(result.output).toContain('修复次数耗尽'); }); }); // ── 4.3 Agent 超时降级 ── describe('Agent 超时降级 (graceful degradation)', () => { it('should continue when one agent times out but another succeeds', async () => { let callCount = 0; const { FailoverError } = require('../../engine'); const mockCallAgent = vi.fn(async (agentName) => { callCount++; if (agentName === 'master' && callCount === 1) { return { output: '[CALL: coder]\n写代码\n[/CALL]' }; } if (agentName === 'coder' && callCount === 2) { return { output: '[RESULT: DONE]\n完成\n[/RESULT]' }; } // 第二轮:master 调 verifier if (agentName === 'master' && callCount === 3) { return { output: '[CALL: verifier]\n验证\n[/CALL]' }; } // verifier 超时 if (agentName === 'verifier' && callCount === 4) { const err = new Error('Agent timeout'); err.code = 'IDLE_TIMEOUT'; throw err; } // 第三轮:master 重试 verifier if (agentName === 'master' && callCount === 5) { return { output: '[CALL: verifier]\n重新验证\n[/CALL]' }; } // verifier 这次成功 if (agentName === 'verifier' && callCount === 6) { return { output: '[RESULT: PASS]\n通过\n[/RESULT]' }; } // tester if (agentName === 'master' && callCount === 7) { return { output: '[CALL: tester]\n测试\n[/CALL]' }; } if (agentName === 'tester' && callCount === 8) { return { output: '[RESULT: PASS]\n通过\n[/RESULT]' }; } return { output: '完成' }; }); const orch = createOrchestrator(mockCallAgent); const result = await orch.execute('任务'); // 超时被降级为 ERROR,编排继续 expect(result.completed).toBe(true); }); }); // ── 4.4 Checkpoint 保存与恢复 ── describe('Checkpoint 保存', () => { it('should save checkpoints during execution', async () => { let callCount = 0; const checkpoints = []; const mockCallAgent = vi.fn(async (agentName) => { callCount++; if (agentName === 'master' && callCount === 1) { return { output: '[CALL: coder]\n写代码\n[/CALL]' }; } if (agentName === 'coder' && callCount === 2) { return { output: '[RESULT: DONE]\n完成\n[/RESULT]' }; } if (agentName === 'master' && callCount === 3) { return { output: '[CALL: verifier]\n验证\n[/CALL]' }; } if (agentName === 'verifier' && callCount === 4) { return { output: '[RESULT: PASS]\n通过\n[/RESULT]' }; } if (agentName === 'master' && callCount === 5) { return { output: '[CALL: tester]\n测试\n[/CALL]' }; } if (agentName === 'tester' && callCount === 6) { return { output: '[RESULT: PASS]\n通过\n[/RESULT]' }; } return { output: '完成' }; }); const orch = createOrchestrator(mockCallAgent); await orch.execute('任务', { onCheckpoint: (cp) => checkpoints.push(cp), }); // 每轮完成后都应保存 checkpoint expect(checkpoints.length).toBeGreaterThanOrEqual(1); // checkpoint 应包含 requirement for (const cp of checkpoints) { expect(cp.version).toBe(1); expect(cp.requirement).toBe('任务'); expect(cp.loopCount).toBeGreaterThanOrEqual(1); } }); it('should save checkpoint on exception (1.3 fix)', async () => { let callCount = 0; const checkpoints = []; const mockCallAgent = vi.fn(async (agentName) => { callCount++; // master 抛不可重试错误 if (agentName === 'master' && callCount === 1) { const err = new Error('Auth failed'); err.code = 'AUTH_ERROR'; throw err; } return { output: '' }; }); const orch = createOrchestrator(mockCallAgent); await expect(orch.execute('任务', { onCheckpoint: (cp) => checkpoints.push(cp), })).rejects.toThrow(); // 即使异常,也应保存 checkpoint expect(checkpoints.length).toBeGreaterThanOrEqual(1); expect(checkpoints[0].error).toBeDefined(); }); }); // ── 4.5 EventEmitter 事件完整性 ── describe('事件完整性 (event lifecycle)', () => { it('should emit correct event sequence for full lifecycle', async () => { let callCount = 0; const events = []; const mockCallAgent = vi.fn(async (agentName) => { callCount++; if (agentName === 'master' && callCount === 1) { return { output: '[CALL: coder]\n写代码\n[/CALL]' }; } if (agentName === 'coder' && callCount === 2) { return { output: '[RESULT: DONE]\n完成\n[/RESULT]' }; } if (agentName === 'master' && callCount === 3) { return { output: '[CALL: verifier]\n验证\n[/CALL]' }; } if (agentName === 'verifier' && callCount === 4) { return { output: '[RESULT: PASS]\n通过\n[/RESULT]' }; } if (agentName === 'master' && callCount === 5) { return { output: '[CALL: tester]\n测试\n[/CALL]' }; } if (agentName === 'tester' && callCount === 6) { return { output: '[RESULT: PASS]\n通过\n[/RESULT]' }; } return { output: '完成' }; }); const orch = createOrchestrator(mockCallAgent); orch.on('progress', (data) => events.push({ type: 'progress', loop: data.loop })); orch.on('completed', (data) => events.push({ type: 'completed', loops: data.loops })); await orch.execute('任务'); // 应该有 progress 事件(前两轮未完成)+ completed 事件(最后一轮完成) const progressCount = events.filter(e => e.type === 'progress').length; const completedCount = events.filter(e => e.type === 'completed').length; expect(progressCount).toBeGreaterThanOrEqual(2); expect(completedCount).toBe(1); // completed 应在最后 expect(events[events.length - 1].type).toBe('completed'); }); }); // ── 4.6 ResultTracker 状态机完整验证 ── describe('状态机完整链路', () => { it('should track FAIL → repair → PASS state transitions', async () => { let callCount = 0; const mockCallAgent = vi.fn(async (agentName) => { callCount++; if (agentName === 'master' && callCount === 1) { return { output: '[CALL: coder]\n写\n[/CALL]' }; } if (agentName === 'coder' && callCount === 2) { return { output: '[RESULT: DONE]\nok\n[/RESULT]' }; } if (agentName === 'master' && callCount === 3) { return { output: '[CALL: verifier]\n验\n[/CALL]' }; } // verifier FAIL if (agentName === 'verifier' && callCount === 4) { return { output: '[RESULT: FAIL]\nbug\n[/RESULT]' }; } if (agentName === 'master' && callCount === 5) { return { output: '[CALL: coder]\n修\n[/CALL]' }; } if (agentName === 'coder' && callCount === 6) { return { output: '[RESULT: DONE]\n修好了\n[/RESULT]' }; } if (agentName === 'master' && callCount === 7) { return { output: '[CALL: verifier]\n验\n[/CALL]' }; } // verifier PASS if (agentName === 'verifier' && callCount === 8) { return { output: '[RESULT: PASS]\nok\n[/RESULT]' }; } if (agentName === 'master' && callCount === 9) { return { output: '[CALL: tester]\n测\n[/CALL]' }; } if (agentName === 'tester' && callCount === 10) { return { output: '[RESULT: PASS]\nok\n[/RESULT]' }; } return { output: '完成' }; }); const orch = createOrchestrator(mockCallAgent); const result = await orch.execute('任务'); expect(result.completed).toBe(true); // verifier 经历了 FAIL → PASS 转换 const verifierResult = result.results.find(r => r.agent === 'verifier'); expect(verifierResult.status).toBe('PASS'); // 修复次数 >= 1 expect(result.repairs).toBeGreaterThanOrEqual(1); }); }); }); // ── Phase 5: 递归编排 (边界逻辑) ── describe('Phase 5: 递归编排边界', () => { const subOrchConfig = { agents: { master: { type: 'master' }, 'sub-orch': { type: 'orchestrator', agentGroup: 'sub-dev', }, 'sub-coder': { type: 'executor' }, 'sub-verifier': { type: 'validator' }, }, agentGroups: { 'sub-dev': { agents: ['sub-coder', 'sub-verifier'], maxRepairs: 2, }, }, }; it('should return ERROR when depth exceeds limit', async () => { const orch = new Orchestrator(subOrchConfig, '/tmp/test-depth', { logManager: { initialized: false, getOrCreateSession: () => null }, depth: 2, }); orch.manager = { callAgent: vi.fn(), closeAllSessions: vi.fn() }; const result = await orch.handleOrchestratorCall( { agent: 'sub-orch', task: '测试' }, 1 ); expect(result.status).toBe('ERROR'); expect(result.message).toContain('Max orchestrator depth'); }); it('should return ERROR for unknown agentGroup', async () => { const badConfig = { agents: { master: { type: 'master' }, 'bad-orch': { type: 'orchestrator', agentGroup: 'nonexistent' }, }, }; const orch = new Orchestrator(badConfig, '/tmp/test-bad', { logManager: { initialized: false, getOrCreateSession: () => null }, }); orch.manager = { callAgent: vi.fn(), closeAllSessions: vi.fn() }; const result = await orch.handleOrchestratorCall( { agent: 'bad-orch', task: '测试' }, 1 ); expect(result.status).toBe('ERROR'); expect(result.message).toContain('unknown agentGroup'); }); it('should build sub-config with only group agents + master', () => { const orch = new Orchestrator(subOrchConfig, '/tmp/test', { logManager: { initialized: false, getOrCreateSession: () => null }, }); const sub = orch.buildSubConfig(subOrchConfig.agentGroups['sub-dev']); expect(Object.keys(sub.agents)).toEqual(['master', 'sub-coder', 'sub-verifier']); expect(sub.agents['sub-orch']).toBeUndefined(); expect(sub.agentGroups).toBeDefined(); }); });