@llblab/pi-actors 0.14.2 → 0.15.0
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/AGENTS.md +5 -1
- package/BACKLOG.md +18 -32
- package/CHANGELOG.md +25 -0
- package/README.md +24 -20
- package/docs/actor-messages.md +1 -1
- package/docs/async-runs.md +4 -4
- package/docs/command-templates.md +11 -11
- package/docs/recipe-library.md +7 -3
- package/docs/task-first-recipes.md +44 -43
- package/docs/template-recipes.md +7 -2
- package/docs/tool-registry.md +7 -5
- package/lib/actor-messages.ts +20 -7
- package/lib/async-runs.ts +25 -12
- package/lib/command-templates.ts +6 -1
- package/lib/config.ts +2 -2
- package/lib/execution.ts +9 -5
- package/lib/observability.ts +20 -10
- package/lib/prompts.ts +13 -20
- package/lib/tools.ts +196 -64
- package/package.json +17 -9
- package/recipes/coordinator-locker.json +46 -0
- package/recipes/music-player.json +16 -2
- package/recipes/pipeline-architect-coordinator.json +11 -3
- package/recipes/pipeline-artifact-bundle.json +12 -3
- package/recipes/pipeline-artifact-report.json +9 -3
- package/recipes/pipeline-artifact-write.json +9 -3
- package/recipes/pipeline-async-run-ops.json +18 -9
- package/recipes/pipeline-checkpoint-continuation.json +14 -3
- package/recipes/pipeline-development-tasking.json +12 -3
- package/recipes/pipeline-docs-maintenance.json +12 -3
- package/recipes/pipeline-media-library.json +12 -3
- package/recipes/pipeline-quorum-review.json +12 -9
- package/recipes/pipeline-release-readiness.json +27 -9
- package/recipes/pipeline-release-summary.json +89 -0
- package/recipes/pipeline-repo-health.json +12 -3
- package/recipes/pipeline-research-synthesis.json +11 -3
- package/recipes/pipeline-review-readiness.json +12 -6
- package/recipes/subagent-artifact.json +9 -3
- package/recipes/subagent-checkpoint.json +10 -3
- package/recipes/subagent-conflict-report.json +11 -3
- package/recipes/subagent-contradiction-map.json +11 -3
- package/recipes/subagent-critic.json +11 -3
- package/recipes/subagent-evidence-map.json +11 -3
- package/recipes/subagent-followup.json +10 -3
- package/recipes/subagent-judge.json +11 -3
- package/recipes/subagent-merge.json +11 -3
- package/recipes/subagent-message.json +8 -3
- package/recipes/subagent-normalize.json +11 -3
- package/recipes/subagent-plan.json +11 -3
- package/recipes/subagent-prompt.json +10 -3
- package/recipes/subagent-quorum.json +10 -7
- package/recipes/subagent-review-coordinator.json +14 -6
- package/recipes/subagent-review.json +11 -3
- package/recipes/subagent-task-card.json +11 -3
- package/recipes/subagent-tools.json +10 -3
- package/recipes/subagent-verify.json +11 -3
- package/recipes/subagents-prompts.json +10 -3
- package/recipes/utility-coordinator-lock-snapshot.json +14 -0
- package/recipes/utility-run-ops-snapshot.json +3 -3
- package/recipes/utility-skill-summary.json +14 -0
- package/scripts/coordinator-locker.mjs +272 -0
- package/scripts/music-player.mjs +2 -1
- package/scripts/recipe-utils.mjs +239 -81
- package/scripts/validate-recipe.mjs +28 -10
- package/skills/actors/SKILL.md +283 -0
- package/skills/swarm/SKILL.md +451 -0
- package/skills/swarm/references/development-swarm.md +596 -0
package/scripts/recipe-utils.mjs
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
appendFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
statSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
3
11
|
import { dirname, extname, join, relative, resolve } from "node:path";
|
|
4
12
|
|
|
5
13
|
function usage() {
|
|
6
14
|
console.error(`Usage:
|
|
7
15
|
recipe-utils.mjs run-summary <state-root>
|
|
8
|
-
recipe-utils.mjs run-ops-snapshot <state-root> <
|
|
16
|
+
recipe-utils.mjs run-ops-snapshot <state-root> <run-id> [lines] [stale-minutes]
|
|
9
17
|
recipe-utils.mjs playlist <source-dir> [extensions] [max-depth] [paths|m3u|inline]
|
|
10
18
|
recipe-utils.mjs changelog-section <file> <version>
|
|
11
19
|
recipe-utils.mjs artifact-manifest <artifact-path> <title> <status> [summary]
|
|
12
20
|
recipe-utils.mjs artifact-write <artifact-path> [create|overwrite|append]
|
|
13
21
|
recipe-utils.mjs actor-message <type> [to] [from] [summary] [metadata-json] [correlation-id] [reply-to]
|
|
14
|
-
recipe-utils.mjs package-summary <package-json
|
|
22
|
+
recipe-utils.mjs package-summary <package-json>
|
|
23
|
+
recipe-utils.mjs skill-summary <skill-md> [package-json]`);
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
function fail(message) {
|
|
@@ -24,7 +33,8 @@ const MESSAGE_TYPE_PATTERN = /^[A-Za-z][A-Za-z0-9_.:-]*$/;
|
|
|
24
33
|
function assertToken(value, label) {
|
|
25
34
|
const normalized = String(value ?? "").trim();
|
|
26
35
|
if (!normalized) fail(`${label} is required`);
|
|
27
|
-
if (!ADDRESS_PATTERN.test(normalized))
|
|
36
|
+
if (!ADDRESS_PATTERN.test(normalized))
|
|
37
|
+
fail(`${label} contains unsupported characters: ${value}`);
|
|
28
38
|
return normalized;
|
|
29
39
|
}
|
|
30
40
|
|
|
@@ -37,16 +47,19 @@ function validateActorAddress(address, label) {
|
|
|
37
47
|
const rest = value.slice(separator + 1);
|
|
38
48
|
if (kind === "branch") {
|
|
39
49
|
const [run, branch, ...extra] = rest.split("/");
|
|
40
|
-
if (extra.length > 0)
|
|
50
|
+
if (extra.length > 0)
|
|
51
|
+
fail(`${label} branch address has too many parts: ${address}`);
|
|
41
52
|
return `branch:${assertToken(run, `${label} branch run`)}/${assertToken(branch, `${label} branch id`)}`;
|
|
42
53
|
}
|
|
43
|
-
if (["run", "session", "tool"].includes(kind))
|
|
54
|
+
if (["run", "session", "tool"].includes(kind))
|
|
55
|
+
return `${kind}:${assertToken(rest, label)}`;
|
|
44
56
|
fail(`${label} has unsupported actor kind: ${kind}`);
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
function validateMessageType(type) {
|
|
48
60
|
const value = String(type ?? "").trim();
|
|
49
|
-
if (!MESSAGE_TYPE_PATTERN.test(value))
|
|
61
|
+
if (!MESSAGE_TYPE_PATTERN.test(value))
|
|
62
|
+
fail(`Invalid actor message type: ${type}`);
|
|
50
63
|
return value;
|
|
51
64
|
}
|
|
52
65
|
|
|
@@ -100,7 +113,13 @@ function collectRunSummary(rootValue) {
|
|
|
100
113
|
run: run.run_id ?? run.run ?? relative(root, file).split("/")[0],
|
|
101
114
|
status: getRunStatus(run, progress, result),
|
|
102
115
|
recipe: run.recipe ?? run.recipe_file ?? "",
|
|
103
|
-
updated:
|
|
116
|
+
updated:
|
|
117
|
+
progress?.updatedAt ??
|
|
118
|
+
result?.completedAt ??
|
|
119
|
+
run.updated_at ??
|
|
120
|
+
run.completed_at ??
|
|
121
|
+
run.started_at ??
|
|
122
|
+
"",
|
|
104
123
|
});
|
|
105
124
|
}
|
|
106
125
|
rows.sort((a, b) =>
|
|
@@ -114,43 +133,82 @@ function runSummary(rootValue) {
|
|
|
114
133
|
}
|
|
115
134
|
|
|
116
135
|
function tailJsonl(fileValue, linesValue = "80") {
|
|
117
|
-
const file = resolve(
|
|
136
|
+
const file = resolve(
|
|
137
|
+
fileValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
138
|
+
);
|
|
118
139
|
if (!existsSync(file)) return [];
|
|
119
140
|
const lines = Number.parseInt(linesValue, 10);
|
|
120
141
|
const count = Number.isFinite(lines) && lines > 0 ? lines : 80;
|
|
121
|
-
return readFileSync(file, "utf8")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
142
|
+
return readFileSync(file, "utf8")
|
|
143
|
+
.trimEnd()
|
|
144
|
+
.split("\n")
|
|
145
|
+
.filter(Boolean)
|
|
146
|
+
.slice(-count)
|
|
147
|
+
.map((line) => {
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(line);
|
|
150
|
+
} catch {
|
|
151
|
+
return { raw: line };
|
|
152
|
+
}
|
|
153
|
+
});
|
|
128
154
|
}
|
|
129
155
|
|
|
130
|
-
function runOpsSnapshot(
|
|
156
|
+
function runOpsSnapshot(
|
|
157
|
+
rootValue,
|
|
158
|
+
runIdValue = "music",
|
|
159
|
+
linesValue = "80",
|
|
160
|
+
staleMinutesValue = "60",
|
|
161
|
+
) {
|
|
131
162
|
const runs = collectRunSummary(rootValue);
|
|
163
|
+
const root = resolve(
|
|
164
|
+
rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
165
|
+
);
|
|
166
|
+
const inspectedRun = String(runIdValue || "music");
|
|
167
|
+
const messageFile = join(root, inspectedRun, "outbox.jsonl");
|
|
132
168
|
const staleMs = Number(staleMinutesValue) * 60 * 1000;
|
|
133
169
|
const now = Date.now();
|
|
134
170
|
const recommendations = runs.flatMap((run) => {
|
|
135
171
|
const updatedMs = Date.parse(run.updated || "");
|
|
136
|
-
const stale =
|
|
172
|
+
const stale =
|
|
173
|
+
Number.isFinite(updatedMs) &&
|
|
174
|
+
Number.isFinite(staleMs) &&
|
|
175
|
+
now - updatedMs > staleMs;
|
|
137
176
|
if (run.status === "running" && stale) {
|
|
138
|
-
return [
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
run: run.run,
|
|
180
|
+
reason: "running-stale",
|
|
181
|
+
suggested_message: {
|
|
182
|
+
to: `run:${run.run}`,
|
|
183
|
+
type: "control.stop",
|
|
184
|
+
body: "stop",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
];
|
|
143
188
|
}
|
|
144
189
|
if (["failed", "exited", "killed"].includes(run.status)) {
|
|
145
|
-
return [
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
190
|
+
return [
|
|
191
|
+
{
|
|
192
|
+
run: run.run,
|
|
193
|
+
reason: `terminal-${run.status}`,
|
|
194
|
+
suggested_inspect: { target: `run:${run.run}`, view: "tail" },
|
|
195
|
+
},
|
|
196
|
+
];
|
|
150
197
|
}
|
|
151
198
|
return [];
|
|
152
199
|
});
|
|
153
|
-
console.log(
|
|
200
|
+
console.log(
|
|
201
|
+
JSON.stringify(
|
|
202
|
+
{
|
|
203
|
+
runs,
|
|
204
|
+
inspectedRun,
|
|
205
|
+
messages: tailJsonl(messageFile, linesValue),
|
|
206
|
+
recommendations,
|
|
207
|
+
},
|
|
208
|
+
null,
|
|
209
|
+
2,
|
|
210
|
+
),
|
|
211
|
+
);
|
|
154
212
|
}
|
|
155
213
|
|
|
156
214
|
function playlist(
|
|
@@ -177,23 +235,38 @@ function playlist(
|
|
|
177
235
|
else console.log(files.join("\n"));
|
|
178
236
|
}
|
|
179
237
|
|
|
180
|
-
function artifactManifest(
|
|
181
|
-
|
|
238
|
+
function artifactManifest(
|
|
239
|
+
pathValue,
|
|
240
|
+
title = "Artifact",
|
|
241
|
+
status = "draft",
|
|
242
|
+
summary = "",
|
|
243
|
+
) {
|
|
244
|
+
const path = resolve(
|
|
245
|
+
pathValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
246
|
+
);
|
|
182
247
|
const exists = existsSync(path);
|
|
183
248
|
const stat = exists ? statSync(path) : undefined;
|
|
184
|
-
console.log(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
249
|
+
console.log(
|
|
250
|
+
JSON.stringify(
|
|
251
|
+
{
|
|
252
|
+
title,
|
|
253
|
+
status,
|
|
254
|
+
path,
|
|
255
|
+
exists,
|
|
256
|
+
bytes: stat?.size ?? 0,
|
|
257
|
+
modified: stat?.mtime?.toISOString?.() ?? null,
|
|
258
|
+
summary,
|
|
259
|
+
},
|
|
260
|
+
null,
|
|
261
|
+
2,
|
|
262
|
+
),
|
|
263
|
+
);
|
|
193
264
|
}
|
|
194
265
|
|
|
195
266
|
function artifactWrite(pathValue, mode = "create") {
|
|
196
|
-
const path = resolve(
|
|
267
|
+
const path = resolve(
|
|
268
|
+
pathValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
269
|
+
);
|
|
197
270
|
if (!["create", "overwrite", "append"].includes(mode)) {
|
|
198
271
|
fail(`Invalid artifact write mode: ${mode}`);
|
|
199
272
|
}
|
|
@@ -205,10 +278,20 @@ function artifactWrite(pathValue, mode = "create") {
|
|
|
205
278
|
if (mode === "append") appendFileSync(path, content);
|
|
206
279
|
else writeFileSync(path, content, "utf8");
|
|
207
280
|
const stat = statSync(path);
|
|
208
|
-
console.log(
|
|
281
|
+
console.log(
|
|
282
|
+
JSON.stringify({ path, mode, bytes: stat.size, written: true }, null, 2),
|
|
283
|
+
);
|
|
209
284
|
}
|
|
210
285
|
|
|
211
|
-
function actorMessage(
|
|
286
|
+
function actorMessage(
|
|
287
|
+
type = "event",
|
|
288
|
+
to = "coordinator",
|
|
289
|
+
from = "run:{run_id}",
|
|
290
|
+
summary = "",
|
|
291
|
+
metadataValue = "",
|
|
292
|
+
correlationId = "",
|
|
293
|
+
replyTo = "",
|
|
294
|
+
) {
|
|
212
295
|
const messageType = validateMessageType(type);
|
|
213
296
|
const messageTo = validateActorAddress(to, "message.to");
|
|
214
297
|
const messageFrom = validateActorAddress(from, "message.from");
|
|
@@ -216,7 +299,8 @@ function actorMessage(type = "event", to = "coordinator", from = "run:{run_id}",
|
|
|
216
299
|
if (metadataValue) {
|
|
217
300
|
try {
|
|
218
301
|
const parsed = JSON.parse(metadataValue);
|
|
219
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
302
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
303
|
+
metadata = parsed;
|
|
220
304
|
else fail("Actor message metadata must be a JSON object");
|
|
221
305
|
} catch (error) {
|
|
222
306
|
fail(`Invalid actor message metadata JSON: ${error.message}`);
|
|
@@ -232,47 +316,114 @@ function actorMessage(type = "event", to = "coordinator", from = "run:{run_id}",
|
|
|
232
316
|
body = bodyText;
|
|
233
317
|
}
|
|
234
318
|
}
|
|
235
|
-
console.log(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
319
|
+
console.log(
|
|
320
|
+
JSON.stringify(
|
|
321
|
+
{
|
|
322
|
+
to: messageTo,
|
|
323
|
+
from: messageFrom,
|
|
324
|
+
type: messageType,
|
|
325
|
+
summary: summary || messageType,
|
|
326
|
+
body,
|
|
327
|
+
...(correlationId ? { correlation_id: correlationId } : {}),
|
|
328
|
+
...(replyTo ? { reply_to: replyTo } : {}),
|
|
329
|
+
metadata,
|
|
330
|
+
},
|
|
331
|
+
null,
|
|
332
|
+
2,
|
|
333
|
+
),
|
|
334
|
+
);
|
|
245
335
|
}
|
|
246
336
|
|
|
247
337
|
function packageSummary(fileValue = "package.json") {
|
|
248
|
-
const file = resolve(
|
|
338
|
+
const file = resolve(
|
|
339
|
+
fileValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
340
|
+
);
|
|
249
341
|
const pkg = readJson(file);
|
|
250
342
|
if (!pkg) fail(`Package JSON not found or invalid: ${fileValue}`);
|
|
251
|
-
const scripts =
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
343
|
+
const scripts =
|
|
344
|
+
pkg.scripts && typeof pkg.scripts === "object"
|
|
345
|
+
? Object.keys(pkg.scripts).sort()
|
|
346
|
+
: [];
|
|
347
|
+
const dependencies =
|
|
348
|
+
pkg.dependencies && typeof pkg.dependencies === "object"
|
|
349
|
+
? Object.keys(pkg.dependencies).sort()
|
|
350
|
+
: [];
|
|
351
|
+
const devDependencies =
|
|
352
|
+
pkg.devDependencies && typeof pkg.devDependencies === "object"
|
|
353
|
+
? Object.keys(pkg.devDependencies).sort()
|
|
354
|
+
: [];
|
|
355
|
+
console.log(
|
|
356
|
+
JSON.stringify(
|
|
357
|
+
{
|
|
358
|
+
name: pkg.name ?? "",
|
|
359
|
+
version: pkg.version ?? "",
|
|
360
|
+
type: pkg.type ?? "",
|
|
361
|
+
private: Boolean(pkg.private),
|
|
362
|
+
packageManager: pkg.packageManager ?? "",
|
|
363
|
+
files: Array.isArray(pkg.files) ? pkg.files : [],
|
|
364
|
+
bin: pkg.bin ?? null,
|
|
365
|
+
main: pkg.main ?? "",
|
|
366
|
+
exports: pkg.exports ?? null,
|
|
367
|
+
scripts,
|
|
368
|
+
dependencyCount: dependencies.length,
|
|
369
|
+
devDependencyCount: devDependencies.length,
|
|
370
|
+
dependencies,
|
|
371
|
+
devDependencies,
|
|
372
|
+
},
|
|
373
|
+
null,
|
|
374
|
+
2,
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function parseSkillFrontmatter(content) {
|
|
380
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? "";
|
|
381
|
+
const fields = {};
|
|
382
|
+
for (const line of frontmatter.split(/\r?\n/)) {
|
|
383
|
+
const match = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
384
|
+
if (match) fields[match[1]] = match[2].trim();
|
|
385
|
+
}
|
|
386
|
+
return { frontmatter, fields };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function skillSummary(skillValue, packageValue = "package.json") {
|
|
390
|
+
const skillFile = resolve(
|
|
391
|
+
skillValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
392
|
+
);
|
|
393
|
+
const packageFile = resolve(
|
|
394
|
+
packageValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
395
|
+
);
|
|
396
|
+
const content = readFileSync(skillFile, "utf8");
|
|
397
|
+
const pkg = readJson(packageFile) ?? {};
|
|
398
|
+
const { frontmatter, fields } = parseSkillFrontmatter(content);
|
|
399
|
+
const scalarLines = frontmatter
|
|
400
|
+
.split(/\r?\n/)
|
|
401
|
+
.filter((line) => /^\w+:\s*\S/.test(line));
|
|
402
|
+
const extraColonLines = scalarLines.filter(
|
|
403
|
+
(line) => (line.match(/:/g) ?? []).length > 1,
|
|
404
|
+
);
|
|
405
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
406
|
+
const version =
|
|
407
|
+
fields.version ??
|
|
408
|
+
frontmatter.match(/^\s+version:\s*([^\n]+)\s*$/m)?.[1]?.trim() ??
|
|
409
|
+
"";
|
|
410
|
+
console.log(
|
|
411
|
+
JSON.stringify(
|
|
412
|
+
{
|
|
413
|
+
path: skillValue,
|
|
414
|
+
name: fields.name ?? "",
|
|
415
|
+
description: fields.description ?? "",
|
|
416
|
+
version,
|
|
417
|
+
packageVersion: pkg.version ?? "",
|
|
418
|
+
versionMatchesPackage: version === pkg.version,
|
|
419
|
+
frontmatterExtraColonLines: extraColonLines,
|
|
420
|
+
bodyLineCount: body.split(/\r?\n/).length,
|
|
421
|
+
headings: body.split(/\r?\n/).filter((line) => /^#{1,6}\s/.test(line)),
|
|
422
|
+
},
|
|
423
|
+
null,
|
|
424
|
+
2,
|
|
425
|
+
),
|
|
426
|
+
);
|
|
276
427
|
}
|
|
277
428
|
|
|
278
429
|
function changelogSection(fileValue, version) {
|
|
@@ -301,7 +452,12 @@ if (!command) {
|
|
|
301
452
|
if (command === "run-summary")
|
|
302
453
|
runSummary(args[0] ?? "~/.pi/agent/tmp/pi-actors/runs");
|
|
303
454
|
else if (command === "run-ops-snapshot")
|
|
304
|
-
runOpsSnapshot(
|
|
455
|
+
runOpsSnapshot(
|
|
456
|
+
args[0] ?? "~/.pi/agent/tmp/pi-actors/runs",
|
|
457
|
+
args[1] ?? "music",
|
|
458
|
+
args[2],
|
|
459
|
+
args[3],
|
|
460
|
+
);
|
|
305
461
|
else if (command === "playlist")
|
|
306
462
|
playlist(args[0] ?? "~/Music", args[1], args[2], args[3]);
|
|
307
463
|
else if (command === "changelog-section")
|
|
@@ -314,6 +470,8 @@ else if (command === "actor-message")
|
|
|
314
470
|
actorMessage(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
|
|
315
471
|
else if (command === "package-summary")
|
|
316
472
|
packageSummary(args[0] ?? "package.json");
|
|
473
|
+
else if (command === "skill-summary")
|
|
474
|
+
skillSummary(args[0] ?? "skills/actors/SKILL.md", args[1] ?? "package.json");
|
|
317
475
|
else {
|
|
318
476
|
usage();
|
|
319
477
|
fail(`Unknown command: ${command}`);
|
|
@@ -13,7 +13,9 @@ Validates one template recipe file, or all *.json files in a directory when --al
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function expandPath(value) {
|
|
16
|
-
return resolve(
|
|
16
|
+
return resolve(
|
|
17
|
+
String(value).replace(/^~(?=\/|$)/, process.env.HOME ?? homedir()),
|
|
18
|
+
);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
function templateKind(template) {
|
|
@@ -21,7 +23,8 @@ function templateKind(template) {
|
|
|
21
23
|
if (Array.isArray(template)) return "sequence";
|
|
22
24
|
if (template && typeof template === "object") {
|
|
23
25
|
if (typeof template.template === "string") return "leaf";
|
|
24
|
-
if (Array.isArray(template.template))
|
|
26
|
+
if (Array.isArray(template.template))
|
|
27
|
+
return template.parallel === true ? "parallel" : "sequence";
|
|
25
28
|
if (template.parallel === true) return "parallel";
|
|
26
29
|
return "object";
|
|
27
30
|
}
|
|
@@ -32,7 +35,8 @@ function recipeFiles(target, all) {
|
|
|
32
35
|
if (!existsSync(target)) throw new Error(`Recipe path not found: ${target}`);
|
|
33
36
|
const stat = statSync(target);
|
|
34
37
|
if (stat.isFile()) return [target];
|
|
35
|
-
if (!stat.isDirectory())
|
|
38
|
+
if (!stat.isDirectory())
|
|
39
|
+
throw new Error(`Recipe path is not a file or directory: ${target}`);
|
|
36
40
|
if (!all) throw new Error("Directory validation requires --all.");
|
|
37
41
|
return readdirSync(target)
|
|
38
42
|
.filter((file) => file.endsWith(".json"))
|
|
@@ -43,19 +47,33 @@ function recipeFiles(target, all) {
|
|
|
43
47
|
function validateFile(file) {
|
|
44
48
|
try {
|
|
45
49
|
const config = readResolvedRecipeConfig(file);
|
|
46
|
-
if (!config?.template)
|
|
50
|
+
if (!config?.template)
|
|
51
|
+
throw new Error("Recipe must define a non-empty template.");
|
|
47
52
|
return {
|
|
48
53
|
file,
|
|
49
54
|
ok: true,
|
|
50
55
|
name: config.name ?? "",
|
|
51
56
|
async: Boolean(config.async),
|
|
52
57
|
args: Array.isArray(config.args) ? config.args : [],
|
|
53
|
-
defaults:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
defaults:
|
|
59
|
+
config.defaults && typeof config.defaults === "object"
|
|
60
|
+
? Object.keys(config.defaults).sort()
|
|
61
|
+
: [],
|
|
62
|
+
imports:
|
|
63
|
+
config.imports && typeof config.imports === "object"
|
|
64
|
+
? Object.keys(config.imports).sort()
|
|
65
|
+
: [],
|
|
66
|
+
mailbox:
|
|
67
|
+
config.mailbox && typeof config.mailbox === "object"
|
|
68
|
+
? {
|
|
69
|
+
accepts: Array.isArray(config.mailbox.accepts)
|
|
70
|
+
? config.mailbox.accepts
|
|
71
|
+
: [],
|
|
72
|
+
emits: Array.isArray(config.mailbox.emits)
|
|
73
|
+
? config.mailbox.emits
|
|
74
|
+
: [],
|
|
75
|
+
}
|
|
76
|
+
: undefined,
|
|
59
77
|
template: templateKind(config.template),
|
|
60
78
|
};
|
|
61
79
|
} catch (error) {
|