- 零第三方依赖,Node >=20 原生 ESM - 团队式编排引擎:拆解/路由/执行/审查/合并全真实 LLM - 阶段心跳、单一权威清单守卫、all-keys-failed 如实上报 - H4 会话视图/amend/watchdog 有界重试/产物区 artifacts.json - H5 零依赖三栏控制台
90 lines
4.6 KiB
JavaScript
90 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* R6 H4-2 现场演示:中途改任务 amend(真实 LLM 编排)。
|
||
* run 启动后并发轮询 plan.json:一旦计划落盘,定位 README 写作子任务(按 expectedFile 匹配,
|
||
* 不硬编码 st 编号——planner 每次编号可能不同),在该子任务被派发前投递 amend;
|
||
* 引擎在派发它之前由真实 LLM 把改动合并成修正指令再派发。
|
||
* 结束校验:①收件箱状态 applied ②会话流含 amend 消息 ③该子任务最终产物确实反映改动。
|
||
* 用法:npm run amend-demo
|
||
*/
|
||
import { mkdirSync, readFileSync, readdirSync, existsSync } from "node:fs";
|
||
import { resolve, dirname, join } from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
import { TeamOrchestrator } from "../src/team/orchestrate.js";
|
||
import { HandoffInbox } from "../src/team/conversation.js";
|
||
|
||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||
const GOAL = [
|
||
"用 Node.js(零第三方依赖)做一个 Markdown 词频统计命令行小工具,团队分工完成三件事:",
|
||
"①编写可运行的实现脚本 wordcount.mjs:读取 .md 文件,按词统计中文/英文词频,支持 --top=N 参数输出前 N 个高频词,并能对 samples 目录里的样例真实跑出结果;",
|
||
"②编写 README.md:含功能说明、用法示例(node wordcount.mjs --top=5 samples/sample.txt)、输出格式说明;",
|
||
"③准备 samples/sample.txt:一份用于演示的 Markdown 样例文本(含若干重复词)。",
|
||
].join("");
|
||
const AMEND = "请在 README.md 中额外增加一个『故障排查(FAQ)』小节,至少包含:遇到 429 限流时应如何指数退避重试。";
|
||
|
||
const orch = new TeamOrchestrator({ root: ROOT });
|
||
const runId = `run-amend-${Date.now().toString(36)}`;
|
||
const evDir = resolve(ROOT, "evidence", "team-orch", runId);
|
||
mkdirSync(evDir, { recursive: true });
|
||
|
||
// 并发:一边跑编排,一边等 plan.json 落盘后精准投递 amend 到 README 子任务
|
||
let amendReq = null;
|
||
const watcher = (async () => {
|
||
const planFile = join(evDir, "plan.json");
|
||
for (let i = 0; i < 600; i++) {
|
||
if (existsSync(planFile)) {
|
||
try {
|
||
const plan = JSON.parse(readFileSync(planFile, "utf8"));
|
||
const readmeSub = plan.subtasks.find((s) => /readme/i.test(String(s.expectedFile)) || /README/.test(String(s.expectedArtifact)));
|
||
if (readmeSub) {
|
||
const inbox = new HandoffInbox(evDir);
|
||
amendReq = inbox.add({ kind: "amend", subtaskId: readmeSub.id, change: AMEND, from: "amend-demo" });
|
||
console.log(`[amend-demo] plan 落盘,定位 README 子任务 ${readmeSub.id},已投递 ${amendReq.id}:${AMEND}`);
|
||
return;
|
||
}
|
||
} catch { /* plan 可能写一半,下轮再试 */ }
|
||
}
|
||
await sleep(1000);
|
||
}
|
||
console.log("[amend-demo] 警告:未能在时限内定位 README 子任务投递 amend");
|
||
})();
|
||
|
||
const t0 = Date.now();
|
||
const result = await orch.run(GOAL, { runId });
|
||
await watcher;
|
||
const wall = Date.now() - t0;
|
||
|
||
// ---- 验收 ----
|
||
const problems = [];
|
||
const targetSid = amendReq?.subtaskId;
|
||
if (!amendReq) problems.push("amend 未投递(未定位到 README 子任务)");
|
||
const inboxAfter = new HandoffInbox(evDir);
|
||
const rec = inboxAfter.items.find((x) => x.id === amendReq?.id);
|
||
if (rec?.status !== "applied") problems.push(`amend ${amendReq?.id} 未被消费(status=${rec?.status})`);
|
||
const conv = JSON.parse(readFileSync(join(evDir, "conversation.json"), "utf8"));
|
||
if (!conv.some((m) => m.kind === "amend")) problems.push("会话流缺少 amend 消息");
|
||
let reflected = false;
|
||
const scanned = [];
|
||
if (targetSid) {
|
||
const dir = resolve(ROOT, "out", runId, targetSid);
|
||
if (existsSync(dir)) {
|
||
for (const f of readdirSync(dir)) {
|
||
scanned.push(`${targetSid}/${f}`);
|
||
if (/故障排查|FAQ|429/.test(readFileSync(join(dir, f), "utf8"))) reflected = true;
|
||
}
|
||
}
|
||
}
|
||
if (!reflected) problems.push(`README 子任务产物未反映 amend(扫描:${scanned.join(",") || "无"})`);
|
||
|
||
console.log("\n===== amend-demo 验收 =====");
|
||
console.log(`runId=${runId} verdict=${result.verdict}/${result.finalScore} 耗时=${wall}ms;amend 目标=${targetSid}`);
|
||
console.log(`amend 状态=${rec?.status};LLM 修正指令=${rec?.correctedInstruction || rec?.summary || "(无)"}`);
|
||
console.log(`扫描产物:${scanned.join(", ") || "无"};改动已反映=${reflected}`);
|
||
if (problems.length) {
|
||
console.error("[amend-demo] 未闭环:\n - " + problems.join("\n - "));
|
||
process.exit(2);
|
||
}
|
||
console.log("[amend-demo] 闭环成功 EXIT=0(amend 被真实接收并反映到最终产物)");
|
||
process.exit(0);
|