cli-blueprint 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -22
- package/skills/blueprint/SKILL.md +1 -1
- package/skills/blueprint/skill.json +2 -2
- package/skills/calctool/SKILL.md +23 -17
- package/skills/calctool/skill.json +3 -3
- package/skills/swarm/SKILL.md +120 -0
- package/skills/swarm/install-meta.json +7 -0
- package/skills/swarm/references/ops-heartbeat.md +45 -0
- package/skills/swarm/references/org-chart.md +50 -0
- package/skills/swarm/references/security-guard.md +56 -0
- package/skills/swarm/references/task-lifecycle.md +53 -0
- package/skills/swarm/references/traffic-light.md +52 -0
- package/skills/swarm/skill.json +10 -0
- package/skills/swarm/swarm-runtime.mjs +600 -0
- package/sources.json +8 -1
- package/README.md +0 -147
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
// swarm runtime v0.2.0 — 智能体蜂群编排的确定性运行时(可选 Blueprint 协同规划)
|
|
2
|
+
// 自包含、无外部依赖。通过组织架构规则调度 N 个子智能体,
|
|
3
|
+
// 围绕项目 JSON 完成 派单/认领/回传,红绿灯状态 + 进度/错误汇报,
|
|
4
|
+
// 固定 运维智能体(心跳/回收/接替/继承)与 安全守卫智能体(注入/危险指令检测)。
|
|
5
|
+
const REQUEST_SCHEMA = "swarm.skill.request/1.0";
|
|
6
|
+
const RESPONSE_SCHEMA = "swarm.skill.response/1.0";
|
|
7
|
+
const ERROR_SCHEMA = "swarm.skill.error/1.0";
|
|
8
|
+
const ORG_SCHEMA = "swarm.org-chart/1.0";
|
|
9
|
+
const TASK_SCHEMA = "swarm.tasks/1.0";
|
|
10
|
+
const COMPILER_NAME = "swarm";
|
|
11
|
+
const COMPILER_VERSION = "0.2.0";
|
|
12
|
+
|
|
13
|
+
const PURE_OPERATIONS = new Set([
|
|
14
|
+
"capabilities", "help", "intake", "org-chart", "blueprint-bridge", "dispatch", "claim",
|
|
15
|
+
"report", "swarm-status", "traffic-light", "security-check", "validate-json",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
// ---------- 工具函数 ----------
|
|
19
|
+
function text(value) { return String(value ?? ""); }
|
|
20
|
+
|
|
21
|
+
function okResponse(requestId, payload) {
|
|
22
|
+
return { schemaVersion: RESPONSE_SCHEMA, requestId, status: "succeeded", ...payload };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function blockedResponse(requestId, request, findings) {
|
|
26
|
+
return {
|
|
27
|
+
schemaVersion: RESPONSE_SCHEMA,
|
|
28
|
+
requestId,
|
|
29
|
+
status: "blocked",
|
|
30
|
+
brainMode: null,
|
|
31
|
+
requestedBrainMode: request?.requestedBrainMode ?? "ide",
|
|
32
|
+
brainUsed: false,
|
|
33
|
+
revision: null,
|
|
34
|
+
validation: { valid: false, guarantee: "blocked", findings },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function finding(severity, ruleId, entityRef, message, evidence = {}) {
|
|
39
|
+
return { severity, ruleId, entityRef, message, evidence };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isObject(value) {
|
|
43
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function validId(value) {
|
|
47
|
+
return typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(value);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------- 组织架构(org-chart/1.0) ----------
|
|
51
|
+
const ORG_LAYERS = ["board", "management", "execution"];
|
|
52
|
+
const ORG_ROLES = ["board", "dispatcher", "ops", "security-guard", "worker"];
|
|
53
|
+
const ORG_FIXED_ROLES = new Set(["board", "dispatcher", "ops", "security-guard"]);
|
|
54
|
+
|
|
55
|
+
const ORG_PERMISSIONS = {
|
|
56
|
+
board: ["dispatch", "accept", "reject", "stop", "reclaim", "replace"],
|
|
57
|
+
dispatcher: ["dispatch", "reassign", "prioritize"],
|
|
58
|
+
ops: ["heartbeat", "reclaim", "replace"],
|
|
59
|
+
"security-guard": ["block", "alert", "quarantine"],
|
|
60
|
+
worker: ["claim", "report", "request-help"],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** 生成企业级组织架构(三层):决策层 + 管理层(dispatcher/ops/security-guard)+ 执行层(N 个 worker) */
|
|
64
|
+
function buildOrgChart(input = {}) {
|
|
65
|
+
const workerCount = Math.min(50, Math.max(1, Math.floor(Number(input.workerCount) || 4)));
|
|
66
|
+
const projectName = text(input.projectName || "swarm-run");
|
|
67
|
+
const org = {
|
|
68
|
+
schemaVersion: ORG_SCHEMA,
|
|
69
|
+
projectName,
|
|
70
|
+
layers: {
|
|
71
|
+
board: [{ agentId: "board", role: "board", title: "决策层·老板/主智能体" }],
|
|
72
|
+
management: [
|
|
73
|
+
{ agentId: "dispatcher", role: "dispatcher", title: "管理层·调度智能体", fixed: true },
|
|
74
|
+
{ agentId: "ops", role: "ops", title: "管理层·运维智能体", fixed: true },
|
|
75
|
+
{ agentId: "security-guard", role: "security-guard", title: "管理层·安全守卫智能体", fixed: true },
|
|
76
|
+
],
|
|
77
|
+
execution: Array.from({ length: workerCount }, (_, index) => ({
|
|
78
|
+
agentId: `worker-${String(index + 1).padStart(3, "0")}`,
|
|
79
|
+
role: "worker",
|
|
80
|
+
title: `执行层·子智能体 ${index + 1}`,
|
|
81
|
+
fixed: false,
|
|
82
|
+
})),
|
|
83
|
+
},
|
|
84
|
+
permissions: ORG_PERMISSIONS,
|
|
85
|
+
};
|
|
86
|
+
return org;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 校验组织架构 */
|
|
90
|
+
function validateOrgChart(org) {
|
|
91
|
+
const findings = [];
|
|
92
|
+
if (!isObject(org)) return [finding("P0", "ORG_OBJECT", "org", "org must be an object")];
|
|
93
|
+
if (org.schemaVersion !== ORG_SCHEMA) {
|
|
94
|
+
findings.push(finding("P0", "ORG_SCHEMA_VERSION", "org.schemaVersion", `Expected ${ORG_SCHEMA}`));
|
|
95
|
+
}
|
|
96
|
+
for (const layer of ORG_LAYERS) {
|
|
97
|
+
if (!Array.isArray(org.layers?.[layer])) {
|
|
98
|
+
findings.push(finding("P0", "ORG_LAYER_ARRAY", `org.layers.${layer}`, "must be an array"));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return findings;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------- 安全守卫(注入 / 危险指令 / 异常检测) ----------
|
|
105
|
+
const INJECTION_PATTERNS = [
|
|
106
|
+
/ignore\s+(all\s+)?previous\s+instructions/i,
|
|
107
|
+
/忽略\s*(之前|此前|先前)\s*(的)?(所有)?指令/i,
|
|
108
|
+
/you\s+are\s+now|act\s+as\s+an?\s+(admin|system|root)/i,
|
|
109
|
+
/现在你(是|要|必须)/,
|
|
110
|
+
/(?:系统|system)\s*(提示词|提示|指令)\s*[::]/,
|
|
111
|
+
];
|
|
112
|
+
const DANGEROUS_PATTERNS = [
|
|
113
|
+
/\brm\s+-rf\b|\bDROP\s+TABLE\b|\bDELETE\s+FROM\b/i,
|
|
114
|
+
/\bsudo\b|\bchmod\s+777\b|提权|越权/i,
|
|
115
|
+
/(?:导出|输出|给出|返回|泄露|外发|发送|上传|回传|读取|获取)[^。\n]{0,24}?(?:api[_-]?\s*key|token|password|secret|密钥|密码|凭据)/i,
|
|
116
|
+
/(?:api[_-]?\s*key|token|password|secret|密钥|密码|凭据)\s*(?:[::=]\s*[A-Za-z0-9_\-]{6,}|请\s*(?:输出|给出|返回|提供))/i,
|
|
117
|
+
/(?:发送|上传|回传|外发)\s*(?:到|至)?\s*https?:\/\//i,
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
/** 安全守卫检测:返回拦截/警报结果 */
|
|
121
|
+
function securityCheck(content, context = {}) {
|
|
122
|
+
const input = text(content);
|
|
123
|
+
const agentId = text(context.agentId || "unknown");
|
|
124
|
+
const alerts = [];
|
|
125
|
+
let blocked = false;
|
|
126
|
+
const injectionHits = INJECTION_PATTERNS
|
|
127
|
+
.filter((pattern) => pattern.test(input))
|
|
128
|
+
.map((pattern) => pattern.source);
|
|
129
|
+
if (injectionHits.length) {
|
|
130
|
+
blocked = true;
|
|
131
|
+
alerts.push({
|
|
132
|
+
alertId: `sec-${Math.random().toString(36).slice(2, 8)}`,
|
|
133
|
+
severity: "high", rule: "prompt-injection", agentId,
|
|
134
|
+
source: "task-input", matched: injectionHits, action: "block",
|
|
135
|
+
at: new Date().toISOString(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const dangerHits = DANGEROUS_PATTERNS
|
|
139
|
+
.filter((pattern) => pattern.test(input))
|
|
140
|
+
.map((pattern) => pattern.source);
|
|
141
|
+
if (dangerHits.length) {
|
|
142
|
+
blocked = true;
|
|
143
|
+
alerts.push({
|
|
144
|
+
alertId: `sec-${Math.random().toString(36).slice(2, 8)}`,
|
|
145
|
+
severity: "high", rule: "dangerous-command", agentId,
|
|
146
|
+
source: "task-input", matched: dangerHits, action: "block",
|
|
147
|
+
at: new Date().toISOString(),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return { allowed: !blocked, blocked, alerts };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------- 任务编排(tasks/1.0) ----------
|
|
154
|
+
const TASK_STATUSES = new Set([
|
|
155
|
+
"backlog", "assigned", "claimed", "running", "reported", "accepted",
|
|
156
|
+
"failed", "blocked", "cancelled",
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
function validateProjectJson(project) {
|
|
160
|
+
const findings = [];
|
|
161
|
+
if (!isObject(project)) return [finding("P0", "PROJECT_OBJECT", "project", "project must be an object")];
|
|
162
|
+
if (!Array.isArray(project.tasks) || project.tasks.length === 0) {
|
|
163
|
+
findings.push(finding("P0", "PROJECT_TASKS", "project.tasks", "tasks must be a non-empty array"));
|
|
164
|
+
}
|
|
165
|
+
return findings;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** 项目 JSON → 任务包(backlog 列表) */
|
|
169
|
+
function buildTasks(project, org) {
|
|
170
|
+
const tasks = (project.tasks ?? []).map((task, index) => ({
|
|
171
|
+
taskId: validId(task.taskId) ? task.taskId : `task-${String(index + 1).padStart(4, "0")}`,
|
|
172
|
+
title: text(task.title || task.name || `任务 ${index + 1}`),
|
|
173
|
+
owner: null,
|
|
174
|
+
status: "backlog",
|
|
175
|
+
priority: text(task.priority || "normal"),
|
|
176
|
+
dependsOn: Array.isArray(task.dependsOn) ? task.dependsOn : [],
|
|
177
|
+
assignedBy: null,
|
|
178
|
+
claimedAt: null,
|
|
179
|
+
reportedAt: null,
|
|
180
|
+
report: null,
|
|
181
|
+
progressPercent: 0,
|
|
182
|
+
progressNote: "",
|
|
183
|
+
inheritedFrom: null,
|
|
184
|
+
}));
|
|
185
|
+
return tasks;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 派单:把 backlog 任务派给指定 worker(只有 dispatcher/board 可派) */
|
|
189
|
+
function dispatchTask(tasks, taskId, workerId, actorRole) {
|
|
190
|
+
if (!ORG_PERMISSIONS[actorRole]?.includes("dispatch")) {
|
|
191
|
+
return { ok: false, error: `role ${actorRole} cannot dispatch` };
|
|
192
|
+
}
|
|
193
|
+
const task = tasks.find((t) => t.taskId === taskId);
|
|
194
|
+
if (!task) return { ok: false, error: `task ${taskId} not found` };
|
|
195
|
+
if (task.status !== "backlog") return { ok: false, error: `task ${taskId} is ${task.status}, not backlog` };
|
|
196
|
+
task.status = "assigned";
|
|
197
|
+
task.owner = workerId;
|
|
198
|
+
task.assignedBy = actorRole;
|
|
199
|
+
return { ok: true, task };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** 认领:worker 认领 assigned 任务 */
|
|
203
|
+
function claimTask(tasks, taskId, workerId) {
|
|
204
|
+
const task = tasks.find((t) => t.taskId === taskId);
|
|
205
|
+
if (!task) return { ok: false, error: `task ${taskId} not found` };
|
|
206
|
+
if (task.status !== "assigned") return { ok: false, error: `task ${taskId} is ${task.status}, not assigned` };
|
|
207
|
+
if (task.owner && task.owner !== workerId) return { ok: false, error: `task ${taskId} claimed by another worker` };
|
|
208
|
+
task.status = "claimed";
|
|
209
|
+
task.owner = workerId;
|
|
210
|
+
task.claimedAt = new Date().toISOString();
|
|
211
|
+
return { ok: true, task };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 回传:worker 回传结果 */
|
|
215
|
+
function reportTask(tasks, taskId, workerId, report) {
|
|
216
|
+
const task = tasks.find((t) => t.taskId === taskId);
|
|
217
|
+
if (!task) return { ok: false, error: `task ${taskId} not found` };
|
|
218
|
+
if (task.owner && task.owner !== workerId) return { ok: false, error: `task ${taskId} not owned by ${workerId}` };
|
|
219
|
+
if (!["claimed", "running"].includes(task.status)) {
|
|
220
|
+
return { ok: false, error: `task ${taskId} is ${task.status}, cannot report` };
|
|
221
|
+
}
|
|
222
|
+
task.status = "reported";
|
|
223
|
+
task.report = isObject(report) ? report : { output: text(report) };
|
|
224
|
+
task.reportedAt = new Date().toISOString();
|
|
225
|
+
return { ok: true, task };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** 验收:board 验收 */
|
|
229
|
+
function acceptTask(tasks, taskId, accept) {
|
|
230
|
+
const task = tasks.find((t) => t.taskId === taskId);
|
|
231
|
+
if (!task) return { ok: false, error: `task ${taskId} not found` };
|
|
232
|
+
if (task.status !== "reported") return { ok: false, error: `task ${taskId} is ${task.status}, not reported` };
|
|
233
|
+
task.status = accept ? "accepted" : "failed";
|
|
234
|
+
return { ok: true, task };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ---------- 红绿灯 ----------
|
|
238
|
+
function taskTrafficLight(task) {
|
|
239
|
+
if (["accepted", "reported"].includes(task.status)) return "green";
|
|
240
|
+
if (task.status === "failed" || task.status === "blocked" || task.status === "cancelled") return "red";
|
|
241
|
+
if (task.status === "running" || task.status === "claimed") {
|
|
242
|
+
return task.progressPercent >= 50 ? "green" : "yellow";
|
|
243
|
+
}
|
|
244
|
+
return "yellow"; // backlog/assigned 视为待启动
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---------- 运维(心跳 / 回收 / 接替 / 继承) ----------
|
|
248
|
+
const HEARTBEAT_MISS_LIMIT = 3;
|
|
249
|
+
|
|
250
|
+
function buildAgents(org, nowIso = null) {
|
|
251
|
+
const now = nowIso || new Date().toISOString();
|
|
252
|
+
const agents = [];
|
|
253
|
+
for (const layer of ORG_LAYERS) {
|
|
254
|
+
for (const member of org.layers?.[layer] ?? []) {
|
|
255
|
+
agents.push({
|
|
256
|
+
agentId: member.agentId,
|
|
257
|
+
role: member.role,
|
|
258
|
+
title: member.title,
|
|
259
|
+
fixed: Boolean(member.fixed),
|
|
260
|
+
status: "green",
|
|
261
|
+
lastHeartbeatAt: now,
|
|
262
|
+
heartbeatMisses: 0,
|
|
263
|
+
currentTaskId: null,
|
|
264
|
+
progressPercent: 0,
|
|
265
|
+
progressNote: "",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return agents;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function recordHeartbeat(agents, agentId) {
|
|
273
|
+
const agent = agents.find((a) => a.agentId === agentId);
|
|
274
|
+
if (!agent) return { ok: false, error: `agent ${agentId} not found` };
|
|
275
|
+
agent.lastHeartbeatAt = new Date().toISOString();
|
|
276
|
+
agent.heartbeatMisses = 0;
|
|
277
|
+
if (agent.status === "dead" || agent.status === "red") {
|
|
278
|
+
agent.status = "green";
|
|
279
|
+
}
|
|
280
|
+
return { ok: true, agent };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** 运维心跳扫描:标记心跳停止的 worker 为 red/dead */
|
|
284
|
+
function scanHeartbeats(agents, nowIso = null) {
|
|
285
|
+
const now = nowIso ? new Date(nowIso).getTime() : Date.now();
|
|
286
|
+
const dead = [];
|
|
287
|
+
for (const agent of agents) {
|
|
288
|
+
if (agent.role !== "worker") continue;
|
|
289
|
+
const last = new Date(agent.lastHeartbeatAt).getTime();
|
|
290
|
+
const missedSeconds = (now - last) / 1000;
|
|
291
|
+
const misses = Math.floor(missedSeconds / 30);
|
|
292
|
+
agent.heartbeatMisses = Math.max(agent.heartbeatMisses, Math.min(misses, 99));
|
|
293
|
+
if (agent.heartbeatMisses >= HEARTBEAT_MISS_LIMIT) {
|
|
294
|
+
if (agent.status !== "dead") {
|
|
295
|
+
agent.status = "dead";
|
|
296
|
+
dead.push(agent.agentId);
|
|
297
|
+
}
|
|
298
|
+
} else if (agent.heartbeatMisses >= 1) {
|
|
299
|
+
agent.status = "yellow";
|
|
300
|
+
} else {
|
|
301
|
+
agent.status = "green";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return dead;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** 运维回收:收回死亡 worker 名下任务(保留回传历史供继承) */
|
|
308
|
+
function reclaimTasks(tasks, workerId) {
|
|
309
|
+
const reclaimed = [];
|
|
310
|
+
for (const task of tasks) {
|
|
311
|
+
if (task.owner === workerId && ["assigned", "claimed", "running"].includes(task.status)) {
|
|
312
|
+
task.status = "backlog";
|
|
313
|
+
task.owner = null;
|
|
314
|
+
task.inheritedFrom = workerId;
|
|
315
|
+
reclaimed.push(task.taskId);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return reclaimed;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** 运维接替:派遣新 worker 继承任务继续执行 */
|
|
322
|
+
function replaceWorker(org, agents, tasks, deadWorkerId, newWorkerId = null) {
|
|
323
|
+
const dead = agents.find((a) => a.agentId === deadWorkerId);
|
|
324
|
+
if (!dead) return { ok: false, error: `dead worker ${deadWorkerId} not found` };
|
|
325
|
+
const replacement = newWorkerId
|
|
326
|
+
? agents.find((a) => a.agentId === newWorkerId && a.role === "worker")
|
|
327
|
+
: agents.find((a) => a.role === "worker" && a.status === "green" && a.agentId !== deadWorkerId);
|
|
328
|
+
if (!replacement) return { ok: false, error: "no healthy replacement worker available" };
|
|
329
|
+
const inheritedTasks = reclaimTasks(tasks, deadWorkerId);
|
|
330
|
+
for (const taskId of inheritedTasks) {
|
|
331
|
+
const task = tasks.find((t) => t.taskId === taskId);
|
|
332
|
+
if (task) {
|
|
333
|
+
task.owner = replacement.agentId;
|
|
334
|
+
task.status = "assigned";
|
|
335
|
+
task.assignedBy = "ops";
|
|
336
|
+
task.inheritedFrom = deadWorkerId;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
dead.status = "dead";
|
|
340
|
+
replacement.currentTaskId = inheritedTasks[0] ?? null;
|
|
341
|
+
return { ok: true, replacement: replacement.agentId, inheritedTasks };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ---------- 主入口 ----------
|
|
345
|
+
const INTAKE_QUESTIONS = [
|
|
346
|
+
{
|
|
347
|
+
id: "goal",
|
|
348
|
+
prompt: "What must the swarm accomplish? List the parallel/ordered work items or point to the project JSON.",
|
|
349
|
+
required: true,
|
|
350
|
+
example: "12 个模块迁移:A1..A12,依赖 A1→A2→A3,其余并行",
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
id: "workerCount",
|
|
354
|
+
prompt: "How many worker sub-agents should the brain create?",
|
|
355
|
+
required: false,
|
|
356
|
+
example: "6",
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
id: "orgTier",
|
|
360
|
+
prompt: "Any org-chart constraints? (default: board → dispatcher/ops/security-guard → workers)",
|
|
361
|
+
required: false,
|
|
362
|
+
example: "默认三层即可",
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: "securityPolicy",
|
|
366
|
+
prompt: "Security policy: strict (block injections) or observe (alert only)?",
|
|
367
|
+
required: false,
|
|
368
|
+
example: "strict",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: "blueprintEnabled",
|
|
372
|
+
prompt: "Use Blueprint to plan tasks before dispatch? (yes: tasks are planned by the Blueprint skill for traceable acceptance; no: direct dispatch)",
|
|
373
|
+
required: false,
|
|
374
|
+
example: "no",
|
|
375
|
+
},
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
const OPERATION_CATALOG = Object.freeze([
|
|
379
|
+
{ operation: "capabilities", summary: "Discover swarm capabilities and operations.", input: {}, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-1", operation: "capabilities", input: {} } },
|
|
380
|
+
{ operation: "help", summary: "Return the usage guide and operation catalog.", input: {}, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-2", operation: "help", input: {} } },
|
|
381
|
+
{ operation: "intake", summary: "Return the intake questions before orchestrating a swarm.", input: {}, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-3", operation: "intake", input: {} } },
|
|
382
|
+
{ operation: "org-chart", summary: "Build the enterprise org-chart (board/management/execution).", input: { workerCount: "number of workers", projectName: "string", blueprintEnabled: "use Blueprint to plan tasks" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-4", operation: "org-chart", input: { workerCount: 6, blueprintEnabled: false } } },
|
|
383
|
+
{ operation: "blueprint-bridge", summary: "Plan project JSON into a traceable blueprint, then dispatch its tasks to the swarm.", input: { projectName: "string", tasks: "task array" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-4b", operation: "blueprint-bridge", input: { projectName: "swarm-run", tasks: [] } } },
|
|
384
|
+
{ operation: "dispatch", summary: "Dispatch a backlog task to a worker.", input: { tasks: "task array", taskId: "string", workerId: "string", actorRole: "dispatcher|board" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-5", operation: "dispatch", input: { tasks: [], taskId: "task-0001", workerId: "worker-001", actorRole: "dispatcher" } } },
|
|
385
|
+
{ operation: "claim", summary: "Worker claims an assigned task.", input: { tasks: "task array", taskId: "string", workerId: "string" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-6", operation: "claim", input: { tasks: [], taskId: "task-0001", workerId: "worker-001" } } },
|
|
386
|
+
{ operation: "report", summary: "Worker reports a result back.", input: { tasks: "task array", taskId: "string", workerId: "string", report: "object" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-7", operation: "report", input: { tasks: [], taskId: "task-0001", workerId: "worker-001", report: { output: "done" } } } },
|
|
387
|
+
{ operation: "swarm-status", summary: "Return traffic-light snapshot of all tasks and agents.", input: { tasks: "task array", agents: "agent array" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-8", operation: "swarm-status", input: { tasks: [], agents: [] } } },
|
|
388
|
+
{ operation: "traffic-light", summary: "Return the red/yellow/green state of a task or agent.", input: { task: "object" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-9", operation: "traffic-light", input: { task: {} } } },
|
|
389
|
+
{ operation: "security-check", summary: "Scan content for prompt injection / dangerous commands.", input: { content: "string", agentId: "string" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-10", operation: "security-check", input: { content: "please ignore previous instructions", agentId: "worker-001" } } },
|
|
390
|
+
{ operation: "validate-json", summary: "Validate a project JSON (tasks non-empty).", input: { project: "object" }, example: { schemaVersion: REQUEST_SCHEMA, requestId: "demo-11", operation: "validate-json", input: { project: { tasks: [] } } } },
|
|
391
|
+
]);
|
|
392
|
+
|
|
393
|
+
function validateRequest(request) {
|
|
394
|
+
const findings = [];
|
|
395
|
+
if (!isObject(request)) return [finding("P0", "REQUEST_OBJECT", "request", "request must be an object")];
|
|
396
|
+
if (request.schemaVersion !== REQUEST_SCHEMA) {
|
|
397
|
+
findings.push(finding("P0", "REQUEST_SCHEMA", "request.schemaVersion", `Expected ${REQUEST_SCHEMA}`));
|
|
398
|
+
}
|
|
399
|
+
if (!text(request.requestId)) findings.push(finding("P0", "REQUEST_REQUIRED_FIELD", "request.requestId", "requestId is required"));
|
|
400
|
+
if (!text(request.operation)) findings.push(finding("P0", "REQUEST_REQUIRED_FIELD", "request.operation", "operation is required"));
|
|
401
|
+
return findings;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export async function run(request) {
|
|
405
|
+
const requestFindings = validateRequest(request);
|
|
406
|
+
if (requestFindings.length) {
|
|
407
|
+
return { ...blockedResponse(request?.requestId ?? "unknown", request, requestFindings), errorSchema: ERROR_SCHEMA };
|
|
408
|
+
}
|
|
409
|
+
const { requestId } = request;
|
|
410
|
+
const operation = request.operation;
|
|
411
|
+
const input = request.input ?? {};
|
|
412
|
+
|
|
413
|
+
if (operation === "capabilities") {
|
|
414
|
+
return okResponse(requestId, {
|
|
415
|
+
capabilities: {
|
|
416
|
+
pure: true, stateless: true, networkRequired: false, filesystemRequired: false,
|
|
417
|
+
operations: [...PURE_OPERATIONS],
|
|
418
|
+
orgSchema: ORG_SCHEMA,
|
|
419
|
+
taskSchema: TASK_SCHEMA,
|
|
420
|
+
fixedAgents: ["board", "dispatcher", "ops", "security-guard"],
|
|
421
|
+
trafficLights: ["green", "yellow", "red"],
|
|
422
|
+
},
|
|
423
|
+
skill: { name: COMPILER_NAME, version: COMPILER_VERSION },
|
|
424
|
+
nextStep: { operation: "intake", instruction: "Ask the intake questions, then build the org-chart and dispatch tasks." },
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (operation === "help") {
|
|
429
|
+
return okResponse(requestId, {
|
|
430
|
+
help: { name: COMPILER_NAME, version: COMPILER_VERSION, operations: OPERATION_CATALOG },
|
|
431
|
+
nextStep: { operation: "intake", instruction: "Ask the intake questions one at a time." },
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (operation === "intake") {
|
|
436
|
+
return okResponse(requestId, {
|
|
437
|
+
questions: INTAKE_QUESTIONS,
|
|
438
|
+
nextStep: { operation: "org-chart", instruction: "Turn the answers into an org-chart; optionally plan tasks with Blueprint (input.blueprintEnabled), then dispatch the project tasks." },
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (operation === "org-chart") {
|
|
443
|
+
const org = buildOrgChart(input);
|
|
444
|
+
const findings = validateOrgChart(org);
|
|
445
|
+
if (findings.length) return blockedResponse(requestId, request, findings);
|
|
446
|
+
const blueprintEnabled = input.blueprintEnabled === true || text(input.blueprintEnabled).toLowerCase() === "yes";
|
|
447
|
+
return okResponse(requestId, {
|
|
448
|
+
org,
|
|
449
|
+
blueprintEnabled,
|
|
450
|
+
fixedAgents: ["board", "dispatcher", "ops", "security-guard"],
|
|
451
|
+
workerCount: org.layers.execution.length,
|
|
452
|
+
nextStep: blueprintEnabled
|
|
453
|
+
? { operation: "blueprint-bridge", instruction: "Blueprint is enabled: call blueprint-bridge to plan the project JSON into a traceable blueprint, then dispatch its tasks to the swarm." }
|
|
454
|
+
: { operation: "dispatch", instruction: "Feed the project JSON; dispatch backlog tasks to workers by dependency order." },
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// blueprint 协同桥:把项目 JSON 转为 blueprint 请求(与 calctool 的 blueprint-orchestrate 对称)。
|
|
459
|
+
// 用户选择 blueprintEnabled 后调用,由 blueprint 技能规划可追溯任务,再回到 swarm 派单执行。
|
|
460
|
+
if (operation === "blueprint-bridge") {
|
|
461
|
+
const projectName = text(input.projectName || "swarm-run");
|
|
462
|
+
const tasks = Array.isArray(input.tasks) ? input.tasks : [];
|
|
463
|
+
const blueprintRequest = {
|
|
464
|
+
schemaVersion: REQUEST_SCHEMA,
|
|
465
|
+
requestId: text(input.requestId || "swarm-bp-1"),
|
|
466
|
+
operation: "blueprint-bridge",
|
|
467
|
+
input: {
|
|
468
|
+
schemaVersion: "blueprint.skill.request/1.0",
|
|
469
|
+
operation: "compile-inline",
|
|
470
|
+
input: {
|
|
471
|
+
goal: projectName,
|
|
472
|
+
blueprint: {
|
|
473
|
+
schemaVersion: "blueprint.ir/1.0",
|
|
474
|
+
blueprintId: `swarm-${projectName}`,
|
|
475
|
+
requirements: [
|
|
476
|
+
{ id: "fact-goal", status: "confirmed", statement: `蜂群目标:${projectName}` },
|
|
477
|
+
{ id: "fact-tasks", status: "confirmed", statement: `任务清单:${tasks.length} 项` },
|
|
478
|
+
],
|
|
479
|
+
modules: [],
|
|
480
|
+
nodes: tasks.map((task, index) => ({
|
|
481
|
+
id: `task-${index + 1}`,
|
|
482
|
+
moduleId: "swarm-tasks",
|
|
483
|
+
title: text(task.title || task.taskId || `任务 ${index + 1}`),
|
|
484
|
+
inputs: [],
|
|
485
|
+
outputs: [{ name: "done", exposed: true }],
|
|
486
|
+
requirementRefs: ["fact-goal", "fact-tasks"],
|
|
487
|
+
})),
|
|
488
|
+
edges: [],
|
|
489
|
+
acceptance: tasks.map((task, index) => ({ id: `acc-${index + 1}`, text: `任务 ${index + 1} 回传并被验收`, nodeRef: `task-${index + 1}` })),
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
return okResponse(requestId, {
|
|
495
|
+
status: "planned",
|
|
496
|
+
blueprintEnabled: true,
|
|
497
|
+
blueprintEndpoint: "https://cli.tax/wvz6zmRWmX",
|
|
498
|
+
blueprintRequest,
|
|
499
|
+
nextStep: { operation: "dispatch", instruction: "POST the blueprintRequest to https://cli.tax/wvz6zmRWmX (operation compile-inline) to get the traceable blueprint, then dispatch its tasks to the swarm workers." },
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (operation === "dispatch") {
|
|
504
|
+
const tasks = Array.isArray(input.tasks) ? input.tasks : [];
|
|
505
|
+
const result = dispatchTask(tasks, text(input.taskId), text(input.workerId), text(input.actorRole || "dispatcher"));
|
|
506
|
+
if (!result.ok) return blockedResponse(requestId, request, [finding("P0", "DISPATCH_FAILED", input.taskId, result.error)]);
|
|
507
|
+
return okResponse(requestId, { task: result.task, nextStep: { operation: "claim", instruction: "The worker can now claim the task." } });
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (operation === "claim") {
|
|
511
|
+
const tasks = Array.isArray(input.tasks) ? input.tasks : [];
|
|
512
|
+
const result = claimTask(tasks, text(input.taskId), text(input.workerId));
|
|
513
|
+
if (!result.ok) return blockedResponse(requestId, request, [finding("P0", "CLAIM_FAILED", input.taskId, result.error)]);
|
|
514
|
+
return okResponse(requestId, { task: result.task, trafficLight: taskTrafficLight(result.task), nextStep: { operation: "report", instruction: "Execute and report the result." } });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (operation === "report") {
|
|
518
|
+
const tasks = Array.isArray(input.tasks) ? input.tasks : [];
|
|
519
|
+
const result = reportTask(tasks, text(input.taskId), text(input.workerId), input.report);
|
|
520
|
+
if (!result.ok) return blockedResponse(requestId, request, [finding("P0", "REPORT_FAILED", input.taskId, result.error)]);
|
|
521
|
+
return okResponse(requestId, { task: result.task, trafficLight: taskTrafficLight(result.task), nextStep: { operation: "swarm-status", instruction: "Board can accept the reported task." } });
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (operation === "swarm-status") {
|
|
525
|
+
const tasks = Array.isArray(input.tasks) ? input.tasks : [];
|
|
526
|
+
const agents = Array.isArray(input.agents) ? input.agents : [];
|
|
527
|
+
return okResponse(requestId, {
|
|
528
|
+
tasks: tasks.map((task) => ({ ...task, trafficLight: taskTrafficLight(task) })),
|
|
529
|
+
agents: agents.map((agent) => ({ ...agent })),
|
|
530
|
+
summary: {
|
|
531
|
+
tasks: tasks.length,
|
|
532
|
+
green: tasks.filter((t) => taskTrafficLight(t) === "green").length,
|
|
533
|
+
yellow: tasks.filter((t) => taskTrafficLight(t) === "yellow").length,
|
|
534
|
+
red: tasks.filter((t) => taskTrafficLight(t) === "red").length,
|
|
535
|
+
workersDead: agents.filter((a) => a.role === "worker" && a.status === "dead").length,
|
|
536
|
+
},
|
|
537
|
+
nextStep: { operation: "ops", instruction: "Ops monitors heartbeats; security-guard scans inputs." },
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (operation === "traffic-light") {
|
|
542
|
+
const task = input.task ?? {};
|
|
543
|
+
return okResponse(requestId, { trafficLight: taskTrafficLight(task) });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (operation === "security-check") {
|
|
547
|
+
const result = securityCheck(input.content, { agentId: input.agentId });
|
|
548
|
+
return okResponse(requestId, {
|
|
549
|
+
...result,
|
|
550
|
+
nextStep: result.allowed
|
|
551
|
+
? { operation: "claim", instruction: "Input is safe; proceed with the task." }
|
|
552
|
+
: { operation: "security-alert", instruction: "Input blocked; review security-audit.json." },
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (operation === "validate-json") {
|
|
557
|
+
const findings = validateProjectJson(input.project);
|
|
558
|
+
if (findings.length) return blockedResponse(requestId, request, findings);
|
|
559
|
+
return okResponse(requestId, {
|
|
560
|
+
valid: true,
|
|
561
|
+
taskCount: (input.project?.tasks ?? []).length,
|
|
562
|
+
nextStep: { operation: "org-chart", instruction: "Project valid; build the org-chart and dispatch." },
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
schemaVersion: RESPONSE_SCHEMA,
|
|
568
|
+
requestId,
|
|
569
|
+
status: "failed",
|
|
570
|
+
errorSchema: ERROR_SCHEMA,
|
|
571
|
+
error: { code: "UNSUPPORTED_OPERATION", message: `Unsupported operation: ${operation}` },
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export {
|
|
576
|
+
COMPILER_VERSION,
|
|
577
|
+
ORG_SCHEMA,
|
|
578
|
+
TASK_SCHEMA,
|
|
579
|
+
PURE_OPERATIONS,
|
|
580
|
+
OPERATION_CATALOG,
|
|
581
|
+
INTAKE_QUESTIONS,
|
|
582
|
+
ORG_PERMISSIONS,
|
|
583
|
+
buildOrgChart,
|
|
584
|
+
validateOrgChart,
|
|
585
|
+
securityCheck,
|
|
586
|
+
buildTasks,
|
|
587
|
+
dispatchTask,
|
|
588
|
+
claimTask,
|
|
589
|
+
reportTask,
|
|
590
|
+
acceptTask,
|
|
591
|
+
taskTrafficLight,
|
|
592
|
+
buildAgents,
|
|
593
|
+
recordHeartbeat,
|
|
594
|
+
scanHeartbeats,
|
|
595
|
+
reclaimTasks,
|
|
596
|
+
replaceWorker,
|
|
597
|
+
okResponse,
|
|
598
|
+
blockedResponse,
|
|
599
|
+
finding,
|
|
600
|
+
};
|
package/sources.json
CHANGED
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
"endpoint": "https://cli.tax/api/public/skills/{code}",
|
|
14
14
|
"doc": "https://cli.tax/KKyA6xljUX",
|
|
15
15
|
"description": "calctool 万能计算工具生成器(CLI.Tax 发布)"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"code": "zj7fTPVh4p",
|
|
19
|
+
"slug": "swarm",
|
|
20
|
+
"endpoint": "https://cli.tax/api/public/skills/{code}",
|
|
21
|
+
"doc": "https://cli.tax/zj7fTPVh4p",
|
|
22
|
+
"description": "swarm 智能体蜂群编排(CLI.Tax 发布)"
|
|
16
23
|
}
|
|
17
24
|
]
|
|
18
|
-
}
|
|
25
|
+
}
|