demo-harness-company.js
12.2 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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#!/usr/bin/env node
/**
* demo-harness-company.js — 创建一家 AI 工具公司来演练 Harness 四阶段流转
*
* 前置条件: 服务器已在 3000 端口运行 (node server/index.js)
*
* 本脚本会完成以下动作:
* Step 1: 创建公司 "aitoolbox"(跳过默认 Bootstrap 自举,我们手动控制)
* Step 2: 招聘一个 base 模板的"代码矿工" (feature-dev)
* Step 3: 验证该员工的 SKILL.md 是否被挂载为 meta-research(初始阶段)
* Step 4: 派发一个 P1 任务 (不触发审批拦截, 但会触发四阶段流转)
* Step 5: 手动模拟 transition research -> plan -> execute -> verify
* Step 6: 检查各阶段产出与日志
*
* 用法:
* node scripts/demo-harness-company.js
*/
const http = require('http');
const fs = require('fs');
const path = require('path');
const API = 'http://localhost:3000/api';
const ROOT = path.resolve(__dirname, '..');
const COMPANY_ID = 'aitoolbox';
const WORKER_ROLE = 'feature-dev';
const WORKER_ID = `${COMPANY_ID}:${WORKER_ROLE}`;
function post(urlPath, body) {
return new Promise((resolve, reject) => {
const data = JSON.stringify(body);
const url = new URL(API + urlPath);
const req = http.request({
hostname: url.hostname, port: url.port, path: url.pathname,
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
}, (res) => {
let buf = '';
res.on('data', c => buf += c);
res.on('end', () => {
try { resolve({ status: res.statusCode, data: JSON.parse(buf) }); }
catch { resolve({ status: res.statusCode, data: buf }); }
});
});
req.on('error', reject);
req.write(data);
req.end();
});
}
function get(urlPath) {
return new Promise((resolve, reject) => {
const url = new URL(API + urlPath);
http.get(url.href, (res) => {
let buf = '';
res.on('data', c => buf += c);
res.on('end', () => {
try { resolve({ status: res.statusCode, data: JSON.parse(buf) }); }
catch { resolve({ status: res.statusCode, data: buf }); }
});
}).on('error', reject);
});
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
function banner(msg) { console.log(`\n${'═'.repeat(60)}\n ${msg}\n${'═'.repeat(60)}`); }
function step(n, msg) { console.log(`\n🔹 Step ${n}: ${msg}`); }
function ok(msg) { console.log(` ✅ ${msg}`); }
function warn(msg) { console.log(` ⚠️ ${msg}`); }
function fail(msg) { console.log(` ❌ ${msg}`); }
async function main() {
banner('🏢 AI ToolBox 公司 — Harness Engineering 端到端演练');
// ──────────────────────────────────────
// Step 1: 创建公司 (跳过 Bootstrap)
// ──────────────────────────────────────
step(1, '创建公司 aitoolbox (跳过默认 Bootstrap)');
const createRes = await post('/company/create', {
id: COMPANY_ID,
name: 'AI ToolBox',
description: '专注于 AI 开发者工具的极客公司',
skipBootstrap: true,
});
if (createRes.status === 200 || createRes.status === 201) {
ok(`公司 "${COMPANY_ID}" 创建成功`);
} else if (createRes.data?.error?.includes('已存在')) {
warn(`公司 "${COMPANY_ID}" 已存在, 继续执行`);
} else {
fail(`创建失败: ${JSON.stringify(createRes.data)}`);
}
// ──────────────────────────────────────
// Step 2: 招聘一个 Base 模板的 Agent
// ──────────────────────────────────────
step(2, `招聘 ${WORKER_ROLE} (使用 base 模板)`);
const hireRes = await post(`/company/${COMPANY_ID}/hire`, {
role: WORKER_ROLE,
type: 'base',
name: '代码矿工',
description: '负责功能开发、代码编写、Bug 修复',
});
if (hireRes.status === 200) {
ok(`${WORKER_ID} 招聘成功: ${JSON.stringify(hireRes.data?.status || 'ok')}`);
} else {
warn(`招聘结果: ${JSON.stringify(hireRes.data)}`);
}
// 等一下让脚手架完成文件写入
await sleep(1000);
// ──────────────────────────────────────
// Step 3: 验证初始状态
// ──────────────────────────────────────
step(3, '验证 Agent 初始状态');
const agentDir = path.join(ROOT, 'data/companies', COMPANY_ID, 'agents', WORKER_ROLE);
// 3a: 手动复制 base template 到这个 agent(模拟四阶段范式挂载)
const baseAgentMd = path.join(ROOT, 'system/agents/_base/agent.md');
const targetAgentMd = path.join(agentDir, 'agent.md');
if (fs.existsSync(baseAgentMd) && fs.existsSync(agentDir)) {
const currentContent = fs.existsSync(targetAgentMd) ? fs.readFileSync(targetAgentMd, 'utf-8') : '';
if (!currentContent.includes('四段式法则')) {
fs.copyFileSync(baseAgentMd, targetAgentMd);
ok('已将 Base Agent 四阶段规范注入到 agent.md');
} else {
ok('agent.md 已包含四阶段规范');
}
}
// 3b: 挂载初始 meta-research 技能
const researchSkill = path.join(ROOT, 'system/skills/meta-research/SKILL.md');
const mainSkillDir = path.join(agentDir, '.claude', 'skills', 'main_skill');
const mainSkillPath = path.join(mainSkillDir, 'SKILL.md');
if (fs.existsSync(researchSkill) && fs.existsSync(agentDir)) {
if (!fs.existsSync(mainSkillDir)) fs.mkdirSync(mainSkillDir, { recursive: true });
fs.copyFileSync(researchSkill, mainSkillPath);
ok('已挂载 meta-research 作为初始技能');
}
// 3c: 复制 base config.json (restricted 工具策略)
const baseConfig = path.join(ROOT, 'system/agents/_base/config.json');
const agentConfig = path.join(agentDir, 'config.json');
if (fs.existsSync(baseConfig) && fs.existsSync(agentDir)) {
const existingConfig = fs.existsSync(agentConfig) ? JSON.parse(fs.readFileSync(agentConfig, 'utf-8')) : {};
const baseData = JSON.parse(fs.readFileSync(baseConfig, 'utf-8'));
const merged = { ...existingConfig, ...baseData, id: WORKER_ROLE };
fs.writeFileSync(agentConfig, JSON.stringify(merged, null, 2), 'utf-8');
ok(`config.json 已合并 restricted 工具策略: ${JSON.stringify(merged.cli?.toolPolicy?.mode)}`);
}
// ──────────────────────────────────────
// Step 4: 派发 P1 开发任务
// ──────────────────────────────────────
step(4, '派发 P1 开发任务');
const taskRes = await post('/agent/task/dispatch', {
from: `${COMPANY_ID}:ceo`,
to: WORKER_ID,
task: '请帮我调研一下项目 server/routes/ 目录下所有的 API 路由文件,统计有多少个 GET/POST/PUT/DELETE 端点,然后写一个简洁的 API 清单文档。',
priority: 'P1',
autoExecute: false, // 不自动执行, 我们手动控制流转
});
if (taskRes.status === 200 && taskRes.data?.taskId) {
ok(`任务已派发: ${taskRes.data.taskId}`);
} else {
warn(`派发结果: ${JSON.stringify(taskRes.data)}`);
}
// ──────────────────────────────────────
// Step 5: 模拟四阶段流转
// ──────────────────────────────────────
step(5, '模拟四阶段流转 (Research → Plan → Execute → Verify)');
const phases = ['plan', 'execute', 'verify'];
const phaseNames = { plan: '规划', execute: '执行', verify: '验证' };
const skillNames = { plan: 'meta-plan', execute: 'meta-execute', verify: 'meta-verify' };
// 当前处于 Research 阶段, 模拟产出 _research.md
const wsDir = path.join(agentDir, 'workspace');
if (!fs.existsSync(wsDir)) fs.mkdirSync(wsDir, { recursive: true });
fs.writeFileSync(path.join(wsDir, '_research.md'), `# 调研结果
- server/routes/ 中有约 10 个 API 路由文件
- 主要模块: agent-chat.js, company.js, goals.js, evolution.js 等
- 估计有 60+ 个 REST endpoint
`, 'utf-8');
ok('Research 阶段: 已产出 workspace/_research.md');
for (const phase of phases) {
console.log(`\n 🔄 触发 transition → ${phase} (${phaseNames[phase]})`);
const trRes = await post(`/agent/${WORKER_ID}/task/transition`, {
phase,
artifact: phase === 'plan' ? 'workspace/_research.md' : (phase === 'execute' ? 'workspace/_plan.md' : ''),
});
if (trRes.status === 200 && trRes.data?.success) {
ok(`Transition → ${phase} 成功: ${trRes.data.message}`);
} else {
fail(`Transition → ${phase} 失败: ${JSON.stringify(trRes.data)}`);
continue;
}
// 验证 SKILL.md 被替换
await sleep(500);
if (fs.existsSync(mainSkillPath)) {
const content = fs.readFileSync(mainSkillPath, 'utf-8');
if (content.includes(skillNames[phase])) {
ok(`SKILL.md 已切换为 ${skillNames[phase]}`);
} else {
warn(`SKILL.md 内容未包含 ${skillNames[phase]} 关键词`);
}
}
// 模拟该阶段的产出 (只有 plan 阶段写 _plan.md)
if (phase === 'plan') {
fs.writeFileSync(path.join(wsDir, '_plan.md'), `# 执行计划
1. 遍历 server/routes/*.js 文件
2. 用正则提取 router.get/post/put/delete 端点
3. 聚合为 Markdown 表格输出到 workspace/api-inventory.md
`, 'utf-8');
ok('Plan 阶段: 已产出 workspace/_plan.md');
}
if (phase === 'execute') {
fs.writeFileSync(path.join(wsDir, 'api-inventory.md'), `# API 清单
| 方法 | 路径 | 模块 |
|------|------|------|
| GET | /api/agent/list | agent-chat.js |
| POST | /api/agent/:id/chat | agent-chat.js |
| POST | /api/company/create | company.js |
| ... | ... | ... |
`, 'utf-8');
ok('Execute 阶段: 已产出 workspace/api-inventory.md');
}
}
// ──────────────────────────────────────
// Step 6: 最终检查
// ──────────────────────────────────────
step(6, '最终检查');
// 检查所有产出文件
const expectedFiles = ['_research.md', '_plan.md', 'api-inventory.md'];
for (const f of expectedFiles) {
const fp = path.join(wsDir, f);
if (fs.existsSync(fp)) {
const size = fs.statSync(fp).size;
ok(`${f} 存在 (${size} bytes)`);
} else {
fail(`${f} 缺失`);
}
}
// 检查当前挂载的技能是 meta-verify
if (fs.existsSync(mainSkillPath)) {
const finalSkill = fs.readFileSync(mainSkillPath, 'utf-8');
if (finalSkill.includes('meta-verify')) {
ok('最终技能树: meta-verify ✓ (验证阶段)');
} else {
warn('最终技能树不是 meta-verify');
}
}
// 检查 billing.log
const billingPath = path.join(ROOT, 'data/logs/billing.log');
if (fs.existsSync(billingPath)) {
ok(`billing.log 存在 (${fs.statSync(billingPath).size} bytes)`);
} else {
warn('billing.log 尚未产生 (需要真实 Agent 对话才会写入)');
}
banner('🎉 演练完成!AI ToolBox 四阶段流转验证通过');
console.log(`
📋 验证清单:
✅ 公司创建 + 员工招聘 (aitoolbox:feature-dev)
✅ Base Agent 规范注入 (agent.md + config.json restricted)
✅ 初始技能挂载 (meta-research)
✅ Transition API: research → plan → execute → verify
✅ 每次 Transition 的 Hot Swap 换脑验证
✅ 各阶段 Artifact 产出 (_research.md / _plan.md / api-inventory.md)
📂 公司目录: data/companies/${COMPANY_ID}/
📂 员工目录: data/companies/${COMPANY_ID}/agents/${WORKER_ROLE}/
💡 接下来你可以:
1. 用 UI 打开 ${WORKER_ID} 跟它对话,观察它的受限行为
2. 派发一个 P0 任务测试审批拦截:
node bin/lemon.js task dispatch --from ${COMPANY_ID}:ceo --to ${WORKER_ID} --task "重构核心架构" --priority P0
3. 查看 billing.log: cat data/logs/billing.log
`);
}
main().catch(err => {
console.error('❌ 脚本执行错误:', err.message);
process.exit(1);
});