#!/usr/bin/env node /** * 集成测试: Innovator 8-Skill Pipeline — 创建"一品布衣"小说写作 Skill * * 测试场景: * 用户消息: "帮我写一部历史穿越小说《一品布衣》" * 预期流程: dispatch → hunt → benchmark → digest → skill-creator → evolving * 预期产出: skills/yipin_buyi/ 下的完整 skill 目录 * * 用法: * node scripts/test-innovator-pipeline.js # 完整 pipeline 测试 * node scripts/test-innovator-pipeline.js --dry-run # 仅测试配置和 skill 加载 * * 注意: 完整测试需要 CLI agent 可用 (kimi/claude),可能耗时较长 */ const path = require('path'); const fs = require('fs'); const ROOT = path.resolve(__dirname, '..'); const SKILLS_DIR = path.join(ROOT, 'skills'); // ─── 测试配置 ─── const TEST_MESSAGE = `帮我写一部历史穿越小说,小说名称:一品布衣,类型:历史古代、穿越、逆袭、家国情怀。\n小说概要:"牧哥儿,要老婆不?开金口就送来。" 现代工程师徐牧穿成市井无赖,遇逃难富家女。乱世之中,他刀弓仗马,誓要护家人活下去。`; const EXPECTED_SKILL_NAME = 'yipin_buyi'; // dispatch 应该推断出的 skill name const DRY_RUN = process.argv.includes('--dry-run'); // ─── Helpers ─── function log(step, msg) { const ts = new Date().toISOString().substring(11, 19); console.log(`[${ts}] [${step}] ${msg}`); } function checkFile(filePath, description) { if (fs.existsSync(filePath)) { const size = fs.statSync(filePath).size; log('CHECK', `✓ ${description} (${size} bytes)`); return true; } else { log('CHECK', `✗ ${description} — 不存在`); return false; } } // ─── Dry Run: 仅验证配置 ─── function dryRunTest() { log('DRY-RUN', '=== 配置验证模式 ===\n'); // 1. 检查 8 个 system skill const SYSTEM_SKILLS = [ 'dispatch', 'deep-research', 'hunt', 'benchmark', 'digest', 'skill-creator', 'benchmark-evaluator', 'evolving', ]; let allOk = true; log('DRY-RUN', '--- System Skills ---'); for (const s of SYSTEM_SKILLS) { const ok = checkFile(path.join(SKILLS_DIR, s, 'SKILL.md'), `skills/${s}/SKILL.md`); if (!ok) allOk = false; } // 2. 检查 InnovatorService 能否实例化 log('DRY-RUN', '\n--- InnovatorService ---'); try { const { InnovatorService } = require(path.join(ROOT, 'server/services/innovator-service')); const service = new InnovatorService({ skillsDir: SKILLS_DIR, agentManagerFactory: () => ({ callAgent: async () => ({ output: '{}' }) }), }); log('DRY-RUN', `✓ InnovatorService 实例化成功`); log('DRY-RUN', ` skillsDir: ${service.skillsDir}`); log('DRY-RUN', ` conversationsDir: ${service.conversationsDir}`); log('DRY-RUN', ` registry skills: ${service.registry.listSkills().length}`); } catch (err) { log('DRY-RUN', `✗ InnovatorService 实例化失败: ${err.message}`); allOk = false; } // 3. 检查 agents.json log('DRY-RUN', '\n--- Agent Config ---'); try { const agentConfig = JSON.parse(fs.readFileSync( path.join(ROOT, 'server/config/agents.json'), 'utf-8' )); const writerAgent = agentConfig.agents?.writer; if (writerAgent) { log('DRY-RUN', `✓ writer agent: ${writerAgent.launcher} (${writerAgent.protocol})`); } else { log('DRY-RUN', `✗ writer agent 未配置`); allOk = false; } } catch (err) { log('DRY-RUN', `✗ agents.json 读取失败: ${err.message}`); allOk = false; } console.log('\n' + (allOk ? '✅ Dry run 全部通过' : '❌ Dry run 有问题')); return allOk; } // ─── Full Pipeline Test ─── async function fullPipelineTest() { log('TEST', '=== Innovator 8-Skill Pipeline 完整测试 ==='); log('TEST', `用户消息: ${TEST_MESSAGE.substring(0, 60)}...`); log('TEST', ''); // 先做 dry run if (!dryRunTest()) { log('TEST', '\n❌ Dry run 失败,跳过完整测试'); process.exit(1); } console.log('\n' + '═'.repeat(60)); log('TEST', '开始完整 pipeline 测试...\n'); // 实例化 InnovatorService const { InnovatorService } = require(path.join(ROOT, 'server/services/innovator-service')); const AgentManager = require(path.join(ROOT, 'server/src/core/agent-manager')); function createAgentManager(workDir) { // 合并 agents.json + backends.json const agentsConfig = JSON.parse(fs.readFileSync( path.join(ROOT, 'server/config/agents.json'), 'utf-8' )); const backendsConfig = JSON.parse(fs.readFileSync( path.join(ROOT, 'server/config/backends.json'), 'utf-8' )); // AgentManager.getAgentConfig 需要 agent.client → config.clients[xxx] // 但静态配置用的是 agent.launcher → backends.launchers[xxx] // 将 launchers 转化为 clients 格式 const clients = {}; for (const [name, launcher] of Object.entries(backendsConfig.launchers || {})) { clients[name] = { name, command: launcher.command, acpArgs: launcher.args || [], acpProtocol: name.includes('claude') ? 'claude-stream' : 'acp', deploy: 'local', }; } // 将 agents 的 launcher 映射为 client const agents = {}; for (const [name, agent] of Object.entries(agentsConfig.agents || {})) { agents[name] = { ...agent, client: agent.launcher || agent.client, // launcher → client deploy: 'local', }; } const mergedConfig = { ...agentsConfig, agents, clients, ...backendsConfig, }; return new AgentManager(mergedConfig, workDir); } const innovator = new InnovatorService({ skillsDir: SKILLS_DIR, agentManagerFactory: createAgentManager, }); // 监听进度事件 innovator.on('progress', (event) => { log(event.step.toUpperCase(), event.message); }); // 运行 dispatch const startTime = Date.now(); try { log('TEST', '>>> 调用 innovator.dispatch()...'); const result = await innovator.dispatch(TEST_MESSAGE); const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); log('TEST', `\n>>> 完成!耗时 ${elapsed}s`); log('TEST', `action: ${result.action}`); log('TEST', `skillName: ${result.skillName}`); log('TEST', `runId: ${result.runId}`); if (result.output) { log('TEST', `output (前 200 字): ${result.output.substring(0, 200)}...`); } // 验证产物 console.log('\n' + '═'.repeat(60)); log('VERIFY', '=== 验证产物 ===\n'); const skillName = result.skillName; const skillDir = path.join(SKILLS_DIR, skillName); if (result.action === 'created') { // 验证新创建的 skill 目录 checkFile(path.join(skillDir, 'SKILL.md'), `${skillName}/SKILL.md`); checkFile(path.join(skillDir, 'benchmark.md'), `${skillName}/benchmark.md`); checkFile(path.join(skillDir, 'hunter', 'knowhow.md'), `${skillName}/hunter/knowhow.md`); checkFile(path.join(skillDir, 'hunter', 'toplist.md'), `${skillName}/hunter/toplist.md`); checkFile(path.join(skillDir, 'digest', 'knowhow.md'), `${skillName}/digest/knowhow.md`); checkFile(path.join(skillDir, 'evolve', 'evolve_log.md'), `${skillName}/evolve/evolve_log.md`); // 验证 conversation 日志 const convDir = path.join(ROOT, 'conversations', result.runId); checkFile(path.join(convDir, 'messages.log'), `conversations/${result.runId}/messages.log`); checkFile(path.join(convDir, 'dispatchResult.md'), `conversations/${result.runId}/dispatchResult.md`); checkFile(path.join(convDir, 'dispatch.log'), `conversations/${result.runId}/dispatch.log`); checkFile(path.join(convDir, 'hunt.log'), `conversations/${result.runId}/hunt.log`); // 验证 CLI 同步 checkFile(path.join(ROOT, '.claude', 'skills', skillName, 'SKILL.md'), `.claude/skills/${skillName}/SKILL.md`); // 检查 skill registry const registeredSkill = innovator.registry.getSkill(skillName); if (registeredSkill) { log('VERIFY', `\n✓ Skill 已注册:`); log('VERIFY', ` status: ${registeredSkill.status}`); log('VERIFY', ` version: ${registeredSkill.version}`); log('VERIFY', ` score: ${registeredSkill.currentScore}`); log('VERIFY', ` domain: ${registeredSkill.domain}`); } } else { log('VERIFY', `action=${result.action}, 使用了已有 Skill`); } console.log('\n✅ 完整 pipeline 测试完成'); } catch (err) { const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); log('ERROR', `\n❌ Pipeline 测试失败 (耗时 ${elapsed}s)`); log('ERROR', err.message); log('ERROR', err.stack); process.exit(1); } } // ─── Main ─── if (DRY_RUN) { const ok = dryRunTest(); process.exit(ok ? 0 : 1); } else { fullPipelineTest().catch(err => { console.error(err); process.exit(1); }); }