p3-demo-setup.js
6.73 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
#!/usr/bin/env node
/**
* P3 执行控制台 · 手工测试准备脚本
*
* node scripts/p3-demo-setup.js <companyId> [--task "自然语言任务"] [--auto]
*
* 做三件事,顺序不能换:
* 1. 雇齐 agent —— 先 org 节点、再 work graph 节点本体
* 2. 写 org graph(三节点 + 两条 fail 边)
* 3. 起一个 run(默认 manual,便于在界面上一步步点)
*
* 跑完打开 http://localhost:5173/<companyId>/graph-console 即可开始点。
*
* ── 为什么需要这个脚本(两个已知缺口的绕行)────────────────────────────
* ① work graph 节点本体不会被自动雇:graph-engine.resolveAgentName 只检查
* data/companies/{cid}/agents/{id} 存不存在,**不做按需 hire**(同文件的
* resolvePlanningAgentName 对 workgraph-planning 倒是做了)。本体不在
* system/agents/ 下,节点就会在 2ms 内 fail 且不起任何子进程。
* ② 顺序不能换:company-service.hire() 会把**任何**被雇的 agent upsert 成一行
* org_graph_nodes,而 POST /org-graph 会把不在节点列表里的行标 removed。
* 先写图再雇人 → 左栏 org graph 会混进 prd-planning 这些 work 层本体。
*
* 两个缺口修好后本脚本的第 1 步就可以删掉,只留第 2、3 步。
*/
const PORT = process.env.PORT || 3010;
const BASE = `http://127.0.0.1:${PORT}`;
const ORG_NODES = ['org-planner', 'org-coder', 'org-validator'];
// 与 fixtures/devloop-mock/workgraph-planning.json 规划出的图对应
const WORK_BODIES = ['prd-planning', 'sdd-planning', 'coder-validator'];
const ORG_GRAPH = {
nodes: ORG_NODES.map((roleName) => ({ roleName })),
edges: [
{ from: 'org-planner', to: 'org-coder', condition: 'success' },
{ from: 'org-coder', to: 'org-validator', condition: 'success' },
// 归因为 input_underspecified 时退回规划;其余一律走这条无 failReason 的兜底边
// (§三.7 第五项 fail-fallback-edge 要求每个有 fail 出边的节点必须留一条兜底)
{ from: 'org-validator', to: 'org-planner', condition: 'fail', failReason: 'input_underspecified', edgeKind: 'backward' },
{ from: 'org-validator', to: 'org-coder', condition: 'fail', edgeKind: 'backward' },
],
};
async function post(path, body) {
const res = await fetch(BASE + path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const text = await res.text();
let json = null;
try { json = JSON.parse(text); } catch { /* 非 JSON 原样回报 */ }
return { status: res.status, json, text };
}
async function main() {
const args = process.argv.slice(2);
const companyId = args.find((a) => !a.startsWith('--'));
if (!companyId) {
console.error('用法: node scripts/p3-demo-setup.js <companyId> [--task "..."] [--auto]');
process.exit(1);
}
const taskIdx = args.indexOf('--task');
const task = taskIdx >= 0 ? args[taskIdx + 1] : '实现一个最小可运行的接口,错误输入要有明确响应';
const mode = args.includes('--auto') ? 'auto' : 'manual';
// 0. server 活着吗
try {
const h = await fetch(`${BASE}/api/health`);
if (!h.ok) throw new Error(`health ${h.status}`);
} catch (e) {
console.error(`❌ 连不上 ${BASE} —— 先起服务:`);
console.error(' LEMONBOSS_CLAUDE_MODE=mock PORT=3010 node server/index.js');
process.exit(1);
}
console.log(`\n公司 ${companyId} · 模式 ${mode}\n`);
// 1. 雇齐(先 org 节点、再 work 本体;顺序见文件头说明)
console.log('① 雇 agent');
for (const role of [...ORG_NODES, ...WORK_BODIES]) {
const r = await post(`/api/company/${companyId}/hire`, { role });
const s = r.json?.status || r.json?.error || r.text.slice(0, 60);
console.log(` ${role.padEnd(18)} ${s}`);
}
// 2. 写 org graph(放在雇人之后:hire 会 upsert org_graph_nodes,
// 这一步顺带把 work 层本体那几行标 removed,左栏才干净)
console.log('\n② 写 org graph');
const g = await post(`/api/company/${companyId}/org-graph`, ORG_GRAPH);
if (!g.json?.ok) {
console.error(' ❌ 写入失败:', g.json?.errors || g.text.slice(0, 300));
process.exit(1);
}
console.log(` ✅ ${g.json.nodeCount} 节点 / ${g.json.edgeCount} 边`);
// 3. 起 run
// §一非目标定的 v1 约束:一家公司同时只有一个 active root run
// (idx_graph_runs_one_active_root 按 company 建的部分唯一索引)。
// 所以重复跑本脚本会被拒——除非先把上一个 run 停掉。--fresh 就是干这个的。
console.log('\n③ 起 run');
if (args.includes('--fresh')) {
const list = await fetch(`${BASE}/api/company/${companyId}/graph-runs`).then((r) => r.json()).catch(() => null);
const active = (list?.runs || []).filter(
(r) => !r.parent_run_id && ['ready', 'running', 'paused', 'blocked'].includes(r.run_status)
);
for (const r of active) {
await post(`/api/graph-runs/${r.id}/stop`, {});
console.log(` ⏹ 已停掉旧 run ${r.id}(原状态 ${r.run_status})`);
}
}
const t = await post(`/api/company/${companyId}/tasks`, { task, mode });
if (!t.json?.runId) {
const msg = t.json?.error || t.text.slice(0, 300);
console.error(' ❌ 失败:', msg);
if (/已有进行中的任务|active root run/.test(msg)) {
const list = await fetch(`${BASE}/api/company/${companyId}/graph-runs`).then((r) => r.json()).catch(() => null);
const active = (list?.runs || []).find((r) => !r.parent_run_id
&& ['ready', 'running', 'paused', 'blocked'].includes(r.run_status));
if (active) {
console.error(`\n 现有 run: ${active.id}(${active.run_status})`);
console.error(' 直接接着点它,或者加 --fresh 重开一个:');
console.error(` node scripts/p3-demo-setup.js ${companyId} --fresh`);
console.error(`\n 控制台:http://localhost:5173/${companyId}/graph-console`);
}
}
process.exit(1);
}
console.log(` ✅ runId ${t.json.runId} · ${t.json.executionMode}`);
console.log(`\n打开控制台:http://localhost:5173/${companyId}/graph-console`);
if (mode === 'manual') {
console.log('\n点法:');
console.log(' 左栏「下一步」→ org-planner 起子图');
console.log(' 点 org-planner 节点 → 下钻,中栏出现它的 work graph');
console.log(' 中栏「下一步」×3 → 第 3 次触发回退,徽标 0/3 → 1/3');
console.log(' 点 wg-check → 右栏「概览」看回退记录与 remediation_note');
console.log(' 中栏「下一步」×2 → work graph 收敛,父节点结算,左栏前移\n');
}
}
main().catch((e) => { console.error(e); process.exit(1); });