test-innovator-pipeline.js
9.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#!/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);
});
}