@llblab/pi-actors 0.32.0 → 0.34.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 +23 -11
- package/BACKLOG.md +2 -30
- package/CHANGELOG.md +30 -1
- package/dist/index.js +10 -10
- package/dist/lib/async-runs.d.ts +23 -75
- package/dist/lib/async-runs.js +49 -801
- package/dist/lib/command-templates.d.ts +3 -3
- package/dist/lib/command-templates.js +3 -3
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/config.js +5 -5
- package/dist/lib/{output.d.ts → execution-output.d.ts} +3 -3
- package/dist/lib/{output.js → execution-output.js} +3 -3
- package/dist/lib/execution.js +1 -1
- package/dist/lib/{actor-inspector-tui.d.ts → inspector.d.ts} +3 -2
- package/dist/lib/{actor-inspector-tui.js → inspector.js} +3 -2
- package/dist/lib/mailbox-loop.d.ts +3 -3
- package/dist/lib/mailbox-loop.js +3 -3
- package/dist/lib/{actor-messages.d.ts → messages.d.ts} +3 -3
- package/dist/lib/{actor-messages.js → messages.js} +3 -3
- package/dist/lib/paths.d.ts +1 -1
- package/dist/lib/paths.js +3 -3
- package/dist/lib/{actor-recipe-context.d.ts → recipes-context.d.ts} +3 -2
- package/dist/lib/{actor-recipe-context.js → recipes-context.js} +2 -1
- package/dist/lib/{recipe-discovery.d.ts → recipes-discovery.d.ts} +9 -9
- package/dist/lib/{recipe-discovery.js → recipes-discovery.js} +17 -17
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +22 -22
- package/dist/lib/{actor-rooms.d.ts → rooms.d.ts} +4 -4
- package/dist/lib/{actor-rooms.js → rooms.js} +3 -3
- package/dist/lib/runs-artifacts.d.ts +21 -0
- package/dist/lib/runs-artifacts.js +68 -0
- package/dist/lib/runs-control.d.ts +13 -0
- package/dist/lib/runs-control.js +59 -0
- package/dist/lib/runs-identity.d.ts +5 -0
- package/dist/lib/runs-identity.js +11 -0
- package/dist/lib/runs-index.d.ts +18 -0
- package/dist/lib/runs-index.js +119 -0
- package/dist/lib/runs-mailbox.d.ts +25 -0
- package/dist/lib/runs-mailbox.js +146 -0
- package/dist/lib/runs-messages.d.ts +15 -0
- package/dist/lib/runs-messages.js +179 -0
- package/dist/lib/runs-outbox.d.ts +41 -0
- package/dist/lib/runs-outbox.js +87 -0
- package/dist/lib/runs-process.d.ts +7 -0
- package/dist/lib/runs-process.js +36 -0
- package/dist/lib/runs-retention.d.ts +8 -0
- package/dist/lib/runs-retention.js +56 -0
- package/dist/lib/runs-start.d.ts +9 -0
- package/dist/lib/runs-start.js +67 -0
- package/dist/lib/runs-status.d.ts +8 -0
- package/dist/lib/runs-status.js +60 -0
- package/dist/lib/runtime.d.ts +2 -2
- package/dist/lib/runtime.js +5 -5
- package/dist/lib/schema.d.ts +9 -0
- package/dist/lib/schema.js +40 -0
- package/dist/lib/tools-access.d.ts +19 -0
- package/dist/lib/tools-access.js +44 -0
- package/dist/lib/tools-inspect.d.ts +11 -0
- package/dist/lib/tools-inspect.js +534 -0
- package/dist/lib/tools-local.d.ts +14 -0
- package/dist/lib/tools-local.js +156 -0
- package/dist/lib/tools-mailbox.d.ts +8 -0
- package/dist/lib/tools-mailbox.js +48 -0
- package/dist/lib/tools-message.d.ts +9 -0
- package/dist/lib/tools-message.js +346 -0
- package/dist/lib/tools-register.d.ts +18 -0
- package/dist/lib/tools-register.js +40 -0
- package/dist/lib/tools-response.d.ts +21 -0
- package/dist/lib/tools-response.js +219 -0
- package/dist/lib/tools-spawn.d.ts +12 -0
- package/dist/lib/tools-spawn.js +159 -0
- package/dist/lib/tools.d.ts +13 -39
- package/dist/lib/tools.js +11 -1497
- package/dist/recipes/utility-coordinator-lock-snapshot.json +1 -1
- package/dist/scripts/actor-worker.mjs +186 -7
- package/dist/scripts/async-runner.mjs +171 -8
- package/dist/scripts/build-dist.mjs +1 -1
- package/dist/scripts/conformance.mjs +34 -18
- package/dist/scripts/coordinator.mjs +733 -11
- package/dist/scripts/locker.mjs +345 -16
- package/dist/scripts/recipe-utils.mjs +478 -14
- package/dist/scripts/validate-recipe.mjs +110 -10
- package/dist/skills/actors/SKILL.md +2 -2
- package/dist/skills/swarm/SKILL.md +2 -2
- package/docs/recipe-library.md +1 -1
- package/docs/template-recipes.md +1 -1
- package/docs/tool-registry.md +3 -3
- package/index.ts +12 -19
- package/lib/async-runs.ts +124 -1016
- package/lib/command-templates.ts +3 -3
- package/lib/config.ts +7 -7
- package/lib/{output.ts → execution-output.ts} +3 -3
- package/lib/execution.ts +5 -1
- package/lib/{actor-inspector-tui.ts → inspector.ts} +4 -3
- package/lib/mailbox-loop.ts +3 -3
- package/lib/{actor-messages.ts → messages.ts} +3 -3
- package/lib/paths.ts +3 -3
- package/lib/{actor-recipe-context.ts → recipes-context.ts} +3 -2
- package/lib/{recipe-discovery.ts → recipes-discovery.ts} +30 -30
- package/lib/{recipe-usage.ts → recipes-usage.ts} +2 -2
- package/lib/registry.ts +37 -24
- package/lib/{actor-rooms.ts → rooms.ts} +4 -4
- package/lib/runs-artifacts.ts +96 -0
- package/lib/runs-control.ts +85 -0
- package/lib/runs-identity.ts +14 -0
- package/lib/runs-index.ts +156 -0
- package/lib/runs-mailbox.ts +208 -0
- package/lib/runs-messages.ts +252 -0
- package/lib/runs-outbox.ts +144 -0
- package/lib/runs-process.ts +51 -0
- package/lib/runs-retention.ts +77 -0
- package/lib/runs-start.ts +98 -0
- package/lib/runs-status.ts +78 -0
- package/lib/runtime.ts +6 -6
- package/lib/schema.ts +55 -0
- package/lib/tools-access.ts +68 -0
- package/lib/tools-inspect.ts +735 -0
- package/lib/tools-local.ts +234 -0
- package/lib/tools-mailbox.ts +56 -0
- package/lib/tools-message.ts +483 -0
- package/lib/tools-register.ts +64 -0
- package/lib/tools-response.ts +267 -0
- package/lib/tools-spawn.ts +240 -0
- package/lib/tools.ts +26 -2051
- package/package.json +1 -1
- package/recipes/utility-coordinator-lock-snapshot.json +1 -1
- package/scripts/actor-worker.mjs +186 -7
- package/scripts/async-runner.mjs +171 -8
- package/scripts/build-dist.mjs +1 -1
- package/scripts/conformance.mjs +34 -18
- package/scripts/coordinator.mjs +733 -11
- package/scripts/locker.mjs +345 -16
- package/scripts/recipe-utils.mjs +478 -14
- package/scripts/validate-recipe.mjs +110 -10
- package/skills/actors/SKILL.md +2 -2
- package/skills/swarm/SKILL.md +2 -2
- package/dist/lib/actor-worker.d.ts +0 -16
- package/dist/lib/actor-worker.js +0 -134
- package/dist/lib/async-runner.d.ts +0 -5
- package/dist/lib/async-runner.js +0 -134
- package/dist/lib/conformance.d.ts +0 -12
- package/dist/lib/conformance.js +0 -28
- package/dist/lib/coordinator.d.ts +0 -5
- package/dist/lib/coordinator.js +0 -660
- package/dist/lib/locker.d.ts +0 -5
- package/dist/lib/locker.js +0 -310
- package/dist/lib/recipe-utils.d.ts +0 -5
- package/dist/lib/recipe-utils.js +0 -385
- package/dist/lib/validate-recipe.d.ts +0 -6
- package/dist/lib/validate-recipe.js +0 -104
- package/lib/actor-worker.ts +0 -174
- package/lib/async-runner.ts +0 -173
- package/lib/conformance.ts +0 -46
- package/lib/coordinator.ts +0 -742
- package/lib/locker.ts +0 -340
- package/lib/recipe-utils.ts +0 -486
- package/lib/validate-recipe.ts +0 -110
- /package/dist/lib/{recipe-references.d.ts → recipes-references.d.ts} +0 -0
- /package/dist/lib/{recipe-references.js → recipes-references.js} +0 -0
- /package/dist/lib/{recipe-usage.d.ts → recipes-usage.d.ts} +0 -0
- /package/dist/lib/{recipe-usage.js → recipes-usage.js} +0 -0
- /package/lib/{recipe-references.ts → recipes-references.ts} +0 -0
package/scripts/recipe-utils.mjs
CHANGED
|
@@ -1,27 +1,491 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Recipe utility command bundle
|
|
5
|
-
*
|
|
6
|
-
* Runtime logic lives in lib/recipe-utils.ts and is compiled to
|
|
7
|
-
* dist/lib/recipe-utils.js for installed JS-only packages.
|
|
4
|
+
* Recipe utility command bundle.
|
|
5
|
+
* Zones: recipe utilities, deterministic helper subcommands
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
import { dirname, join } from "node:path";
|
|
12
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
// @ts-nocheck
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import {
|
|
11
|
+
appendFileSync,
|
|
12
|
+
existsSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
readdirSync,
|
|
16
|
+
statSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { dirname, extname, join, relative, resolve } from "node:path";
|
|
20
|
+
|
|
21
|
+
function usage() {
|
|
22
|
+
console.error(`Usage:
|
|
23
|
+
recipe-utils.mjs run-summary <state-root>
|
|
24
|
+
recipe-utils.mjs run-ops-snapshot <state-root> <run-id> [lines] [stale-minutes]
|
|
25
|
+
recipe-utils.mjs playlist <source-dir> [extensions] [max-depth] [paths|m3u|inline]
|
|
26
|
+
recipe-utils.mjs changelog-section <file> <version>
|
|
27
|
+
recipe-utils.mjs artifact-manifest <artifact-path> <title> <status> [summary]
|
|
28
|
+
recipe-utils.mjs artifact-write <artifact-path> [create|overwrite|append]
|
|
29
|
+
recipe-utils.mjs actor-message <type> [to] [from] [summary] [metadata-json] [correlation-id] [reply-to]
|
|
30
|
+
recipe-utils.mjs package-summary <package-json>
|
|
31
|
+
recipe-utils.mjs skill-summary <skill-md> [package-json]`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fail(message) {
|
|
35
|
+
console.error(message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ADDRESS_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
40
|
+
const MESSAGE_TYPE_PATTERN = /^[A-Za-z][A-Za-z0-9_.:-]*$/;
|
|
41
|
+
function assertToken(value, label) {
|
|
42
|
+
const normalized = String(value ?? "").trim();
|
|
43
|
+
if (!normalized) fail(`${label} is required`);
|
|
44
|
+
if (!ADDRESS_PATTERN.test(normalized))
|
|
45
|
+
fail(`${label} contains unsupported characters: ${value}`);
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validateActorAddress(address, label) {
|
|
50
|
+
const value = String(address ?? "").trim();
|
|
51
|
+
if (value === "coordinator") return value;
|
|
52
|
+
const separator = value.indexOf(":");
|
|
53
|
+
if (separator < 0) fail(`${label} must include an actor kind: ${address}`);
|
|
54
|
+
const kind = value.slice(0, separator);
|
|
55
|
+
const rest = value.slice(separator + 1);
|
|
56
|
+
if (kind === "branch") {
|
|
57
|
+
const [run, branch, ...extra] = rest.split("/");
|
|
58
|
+
if (extra.length > 0)
|
|
59
|
+
fail(`${label} branch address has too many parts: ${address}`);
|
|
60
|
+
return `branch:${assertToken(run, `${label} branch run`)}/${assertToken(branch, `${label} branch id`)}`;
|
|
61
|
+
}
|
|
62
|
+
if (["run", "session", "tool"].includes(kind))
|
|
63
|
+
return `${kind}:${assertToken(rest, label)}`;
|
|
64
|
+
fail(`${label} has unsupported actor kind: ${kind}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function validateMessageType(type) {
|
|
68
|
+
const value = String(type ?? "").trim();
|
|
69
|
+
if (!MESSAGE_TYPE_PATTERN.test(value))
|
|
70
|
+
fail(`Invalid actor message type: ${type}`);
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function walkFiles(dir, maxDepth = 2, depth = 0, out = []) {
|
|
75
|
+
if (depth > maxDepth || !existsSync(dir)) return out;
|
|
76
|
+
for (const entry of readdirSync(dir)) {
|
|
77
|
+
const path = join(dir, entry);
|
|
78
|
+
const stat = statSync(path);
|
|
79
|
+
if (stat.isDirectory()) walkFiles(path, maxDepth, depth + 1, out);
|
|
80
|
+
else if (stat.isFile()) out.push(path);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readJson(file) {
|
|
86
|
+
if (!existsSync(file)) return undefined;
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
89
|
+
} catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getRunStatus(run, progress, result) {
|
|
95
|
+
if (progress?.phase) return progress.phase;
|
|
96
|
+
if (result?.code !== undefined) return result.code === 0 ? "done" : "failed";
|
|
97
|
+
return run.status ?? "unknown";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function collectRunSummary(rootValue) {
|
|
101
|
+
const root = resolve(
|
|
102
|
+
rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
103
|
+
);
|
|
104
|
+
const files = walkFiles(root, 2).filter((file) => file.endsWith("/run.json"));
|
|
105
|
+
const rows = [];
|
|
106
|
+
for (const file of files) {
|
|
107
|
+
const run = readJson(file);
|
|
108
|
+
if (!run) {
|
|
109
|
+
rows.push({
|
|
110
|
+
run: relative(root, file),
|
|
111
|
+
status: "invalid-json",
|
|
112
|
+
recipe: "",
|
|
113
|
+
updated: "",
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const runDir = dirname(file);
|
|
118
|
+
const progress = readJson(join(runDir, "progress.json"));
|
|
119
|
+
const result = readJson(join(runDir, "result.json"));
|
|
120
|
+
rows.push({
|
|
121
|
+
run: run.run_id ?? run.run ?? relative(root, file).split("/")[0],
|
|
122
|
+
status: getRunStatus(run, progress, result),
|
|
123
|
+
recipe: run.recipe ?? run.recipe_file ?? "",
|
|
124
|
+
updated:
|
|
125
|
+
progress?.updatedAt ??
|
|
126
|
+
result?.completedAt ??
|
|
127
|
+
run.updated_at ??
|
|
128
|
+
run.completed_at ??
|
|
129
|
+
run.started_at ??
|
|
130
|
+
"",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
rows.sort((a, b) =>
|
|
134
|
+
`${a.status}:${a.run}`.localeCompare(`${b.status}:${b.run}`),
|
|
135
|
+
);
|
|
136
|
+
return rows;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function runSummary(rootValue) {
|
|
140
|
+
console.log(JSON.stringify(collectRunSummary(rootValue), null, 2));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function tailJsonl(fileValue, linesValue = "80") {
|
|
144
|
+
const file = resolve(
|
|
145
|
+
fileValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
146
|
+
);
|
|
147
|
+
if (!existsSync(file)) return [];
|
|
148
|
+
const lines = Number.parseInt(linesValue, 10);
|
|
149
|
+
const count = Number.isFinite(lines) && lines > 0 ? lines : 80;
|
|
150
|
+
return readFileSync(file, "utf8")
|
|
151
|
+
.trimEnd()
|
|
152
|
+
.split("\n")
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.slice(-count)
|
|
155
|
+
.map((line) => {
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(line);
|
|
158
|
+
} catch {
|
|
159
|
+
return { raw: line };
|
|
160
|
+
}
|
|
161
|
+
});
|
|
16
162
|
}
|
|
17
163
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
164
|
+
function runOpsSnapshot(
|
|
165
|
+
rootValue,
|
|
166
|
+
runIdValue = "music",
|
|
167
|
+
linesValue = "80",
|
|
168
|
+
staleMinutesValue = "60",
|
|
169
|
+
) {
|
|
170
|
+
const runs = collectRunSummary(rootValue);
|
|
171
|
+
const root = resolve(
|
|
172
|
+
rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
173
|
+
);
|
|
174
|
+
const inspectedRun = String(runIdValue || "music");
|
|
175
|
+
const messageFile = join(root, inspectedRun, "outbox.jsonl");
|
|
176
|
+
const staleMs = Number(staleMinutesValue) * 60 * 1000;
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
const recommendations = runs.flatMap((run) => {
|
|
179
|
+
const updatedMs = Date.parse(run.updated || "");
|
|
180
|
+
const stale =
|
|
181
|
+
Number.isFinite(updatedMs) &&
|
|
182
|
+
Number.isFinite(staleMs) &&
|
|
183
|
+
now - updatedMs > staleMs;
|
|
184
|
+
if (run.status === "running" && stale) {
|
|
185
|
+
return [
|
|
186
|
+
{
|
|
187
|
+
run: run.run,
|
|
188
|
+
reason: "running-stale",
|
|
189
|
+
suggested_message: {
|
|
190
|
+
to: `run:${run.run}`,
|
|
191
|
+
type: "control.stop",
|
|
192
|
+
body: "stop",
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
if (["failed", "exited", "killed"].includes(run.status)) {
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
run: run.run,
|
|
201
|
+
reason: `terminal-${run.status}`,
|
|
202
|
+
suggested_inspect: { target: `run:${run.run}`, view: "tail" },
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
return [];
|
|
207
|
+
});
|
|
208
|
+
console.log(
|
|
209
|
+
JSON.stringify(
|
|
210
|
+
{
|
|
211
|
+
runs,
|
|
212
|
+
inspectedRun,
|
|
213
|
+
messages: tailJsonl(messageFile, linesValue),
|
|
214
|
+
recommendations,
|
|
215
|
+
},
|
|
216
|
+
null,
|
|
217
|
+
2,
|
|
218
|
+
),
|
|
219
|
+
);
|
|
22
220
|
}
|
|
23
221
|
|
|
24
|
-
|
|
222
|
+
function playlist(
|
|
223
|
+
sourceValue,
|
|
224
|
+
extensionsValue = ".mp3,.ogg,.wav,.flac,.m4a",
|
|
225
|
+
maxDepthValue = "2",
|
|
226
|
+
outputMode = "paths",
|
|
227
|
+
) {
|
|
228
|
+
const source = resolve(
|
|
229
|
+
sourceValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
230
|
+
);
|
|
231
|
+
const maxDepth = Number.parseInt(maxDepthValue, 10);
|
|
232
|
+
const extensions = new Set(
|
|
233
|
+
extensionsValue
|
|
234
|
+
.split(",")
|
|
235
|
+
.map((item) => item.trim().toLowerCase())
|
|
236
|
+
.filter(Boolean),
|
|
237
|
+
);
|
|
238
|
+
const files = walkFiles(source, Number.isFinite(maxDepth) ? maxDepth : 2)
|
|
239
|
+
.filter((file) => extensions.has(extname(file).toLowerCase()))
|
|
240
|
+
.sort((a, b) => a.localeCompare(b));
|
|
241
|
+
if (outputMode === "m3u") console.log(["#EXTM3U", ...files].join("\n"));
|
|
242
|
+
else if (outputMode === "inline") console.log(files.join("|"));
|
|
243
|
+
else console.log(files.join("\n"));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function artifactManifest(
|
|
247
|
+
pathValue,
|
|
248
|
+
title = "Artifact",
|
|
249
|
+
status = "draft",
|
|
250
|
+
summary = "",
|
|
251
|
+
) {
|
|
252
|
+
const path = resolve(
|
|
253
|
+
pathValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
254
|
+
);
|
|
255
|
+
const exists = existsSync(path);
|
|
256
|
+
const stat = exists ? statSync(path) : undefined;
|
|
257
|
+
console.log(
|
|
258
|
+
JSON.stringify(
|
|
259
|
+
{
|
|
260
|
+
title,
|
|
261
|
+
status,
|
|
262
|
+
path,
|
|
263
|
+
exists,
|
|
264
|
+
bytes: stat?.size ?? 0,
|
|
265
|
+
modified: stat?.mtime?.toISOString?.() ?? null,
|
|
266
|
+
summary,
|
|
267
|
+
},
|
|
268
|
+
null,
|
|
269
|
+
2,
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function artifactWrite(pathValue, mode = "create") {
|
|
275
|
+
const path = resolve(
|
|
276
|
+
pathValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
277
|
+
);
|
|
278
|
+
if (!["create", "overwrite", "append"].includes(mode)) {
|
|
279
|
+
fail(`Invalid artifact write mode: ${mode}`);
|
|
280
|
+
}
|
|
281
|
+
if (mode === "create" && existsSync(path)) {
|
|
282
|
+
fail(`Artifact already exists: ${path}`);
|
|
283
|
+
}
|
|
284
|
+
const content = readFileSync(0, "utf8");
|
|
285
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
286
|
+
if (mode === "append") appendFileSync(path, content);
|
|
287
|
+
else writeFileSync(path, content, "utf8");
|
|
288
|
+
const stat = statSync(path);
|
|
289
|
+
console.log(
|
|
290
|
+
JSON.stringify({ path, mode, bytes: stat.size, written: true }, null, 2),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function actorMessage(
|
|
295
|
+
type = "event",
|
|
296
|
+
to = "coordinator",
|
|
297
|
+
from = "run:{run_id}",
|
|
298
|
+
summary = "",
|
|
299
|
+
metadataValue = "",
|
|
300
|
+
correlationId = "",
|
|
301
|
+
replyTo = "",
|
|
302
|
+
) {
|
|
303
|
+
const messageType = validateMessageType(type);
|
|
304
|
+
const messageTo = validateActorAddress(to, "message.to");
|
|
305
|
+
const messageFrom = validateActorAddress(from, "message.from");
|
|
306
|
+
let metadata = {};
|
|
307
|
+
if (metadataValue) {
|
|
308
|
+
try {
|
|
309
|
+
const parsed = JSON.parse(metadataValue);
|
|
310
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
311
|
+
metadata = parsed;
|
|
312
|
+
else fail("Actor message metadata must be a JSON object");
|
|
313
|
+
} catch (error) {
|
|
314
|
+
fail(`Invalid actor message metadata JSON: ${error.message}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const bodyText = readFileSync(0, "utf8");
|
|
318
|
+
const trimmed = bodyText.trim();
|
|
319
|
+
let body = bodyText;
|
|
320
|
+
if (trimmed) {
|
|
321
|
+
try {
|
|
322
|
+
body = JSON.parse(trimmed);
|
|
323
|
+
} catch {
|
|
324
|
+
body = bodyText;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
console.log(
|
|
328
|
+
JSON.stringify(
|
|
329
|
+
{
|
|
330
|
+
to: messageTo,
|
|
331
|
+
from: messageFrom,
|
|
332
|
+
type: messageType,
|
|
333
|
+
summary: summary || messageType,
|
|
334
|
+
body,
|
|
335
|
+
...(correlationId ? { correlation_id: correlationId } : {}),
|
|
336
|
+
...(replyTo ? { reply_to: replyTo } : {}),
|
|
337
|
+
metadata,
|
|
338
|
+
},
|
|
339
|
+
null,
|
|
340
|
+
2,
|
|
341
|
+
),
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function packageSummary(fileValue = "package.json") {
|
|
346
|
+
const file = resolve(
|
|
347
|
+
fileValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
348
|
+
);
|
|
349
|
+
const pkg = readJson(file);
|
|
350
|
+
if (!pkg) fail(`Package JSON not found or invalid: ${fileValue}`);
|
|
351
|
+
const scripts =
|
|
352
|
+
pkg.scripts && typeof pkg.scripts === "object"
|
|
353
|
+
? Object.keys(pkg.scripts).sort()
|
|
354
|
+
: [];
|
|
355
|
+
const dependencies =
|
|
356
|
+
pkg.dependencies && typeof pkg.dependencies === "object"
|
|
357
|
+
? Object.keys(pkg.dependencies).sort()
|
|
358
|
+
: [];
|
|
359
|
+
const devDependencies =
|
|
360
|
+
pkg.devDependencies && typeof pkg.devDependencies === "object"
|
|
361
|
+
? Object.keys(pkg.devDependencies).sort()
|
|
362
|
+
: [];
|
|
363
|
+
console.log(
|
|
364
|
+
JSON.stringify(
|
|
365
|
+
{
|
|
366
|
+
name: pkg.name ?? "",
|
|
367
|
+
version: pkg.version ?? "",
|
|
368
|
+
type: pkg.type ?? "",
|
|
369
|
+
private: Boolean(pkg.private),
|
|
370
|
+
packageManager: pkg.packageManager ?? "",
|
|
371
|
+
files: Array.isArray(pkg.files) ? pkg.files : [],
|
|
372
|
+
bin: pkg.bin ?? null,
|
|
373
|
+
main: pkg.main ?? "",
|
|
374
|
+
exports: pkg.exports ?? null,
|
|
375
|
+
scripts,
|
|
376
|
+
dependencyCount: dependencies.length,
|
|
377
|
+
devDependencyCount: devDependencies.length,
|
|
378
|
+
dependencies,
|
|
379
|
+
devDependencies,
|
|
380
|
+
},
|
|
381
|
+
null,
|
|
382
|
+
2,
|
|
383
|
+
),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function parseSkillFrontmatter(content) {
|
|
388
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? "";
|
|
389
|
+
const fields = {};
|
|
390
|
+
for (const line of frontmatter.split(/\r?\n/)) {
|
|
391
|
+
const match = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
392
|
+
if (match) fields[match[1]] = match[2].trim();
|
|
393
|
+
}
|
|
394
|
+
return { frontmatter, fields };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function skillSummary(skillValue, packageValue = "package.json") {
|
|
398
|
+
const skillFile = resolve(
|
|
399
|
+
skillValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
400
|
+
);
|
|
401
|
+
const packageFile = resolve(
|
|
402
|
+
packageValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
403
|
+
);
|
|
404
|
+
const content = readFileSync(skillFile, "utf8");
|
|
405
|
+
const pkg = readJson(packageFile) ?? {};
|
|
406
|
+
const { frontmatter, fields } = parseSkillFrontmatter(content);
|
|
407
|
+
const scalarLines = frontmatter
|
|
408
|
+
.split(/\r?\n/)
|
|
409
|
+
.filter((line) => /^\w+:\s*\S/.test(line));
|
|
410
|
+
const extraColonLines = scalarLines.filter(
|
|
411
|
+
(line) => (line.match(/:/g) ?? []).length > 1,
|
|
412
|
+
);
|
|
413
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
414
|
+
const version =
|
|
415
|
+
fields.version ??
|
|
416
|
+
frontmatter.match(/^\s+version:\s*([^\n]+)\s*$/m)?.[1]?.trim() ??
|
|
417
|
+
"";
|
|
418
|
+
console.log(
|
|
419
|
+
JSON.stringify(
|
|
420
|
+
{
|
|
421
|
+
path: skillValue,
|
|
422
|
+
name: fields.name ?? "",
|
|
423
|
+
description: fields.description ?? "",
|
|
424
|
+
version,
|
|
425
|
+
packageVersion: pkg.version ?? "",
|
|
426
|
+
versionMatchesPackage: version === pkg.version,
|
|
427
|
+
frontmatterExtraColonLines: extraColonLines,
|
|
428
|
+
bodyLineCount: body.split(/\r?\n/).length,
|
|
429
|
+
headings: body.split(/\r?\n/).filter((line) => /^#{1,6}\s/.test(line)),
|
|
430
|
+
},
|
|
431
|
+
null,
|
|
432
|
+
2,
|
|
433
|
+
),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function changelogSection(fileValue, version) {
|
|
438
|
+
const file = resolve(fileValue);
|
|
439
|
+
const lines = readFileSync(file, "utf8").split(/\r?\n/);
|
|
440
|
+
const start = lines.findIndex(
|
|
441
|
+
(line) => line.startsWith("## ") && line.includes(version),
|
|
442
|
+
);
|
|
443
|
+
if (start < 0) fail(`Version section not found: ${version}`);
|
|
444
|
+
let end = lines.length;
|
|
445
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
446
|
+
if (lines[index].startsWith("## ")) {
|
|
447
|
+
end = index;
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
console.log(lines.slice(start, end).join("\n").trimEnd());
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function runRecipeUtils(argv = process.argv.slice(2)) {
|
|
455
|
+
const [command, ...args] = argv;
|
|
456
|
+
if (!command) {
|
|
457
|
+
usage();
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (command === "run-summary")
|
|
462
|
+
runSummary(args[0] ?? "~/.pi/agent/tmp/pi-actors/runs");
|
|
463
|
+
else if (command === "run-ops-snapshot")
|
|
464
|
+
runOpsSnapshot(
|
|
465
|
+
args[0] ?? "~/.pi/agent/tmp/pi-actors/runs",
|
|
466
|
+
args[1] ?? "music",
|
|
467
|
+
args[2],
|
|
468
|
+
args[3],
|
|
469
|
+
);
|
|
470
|
+
else if (command === "playlist")
|
|
471
|
+
playlist(args[0] ?? "~/Music", args[1], args[2], args[3]);
|
|
472
|
+
else if (command === "changelog-section")
|
|
473
|
+
changelogSection(args[0] ?? "CHANGELOG.md", args[1] ?? "Unreleased");
|
|
474
|
+
else if (command === "artifact-manifest")
|
|
475
|
+
artifactManifest(args[0] ?? "artifact.md", args[1], args[2], args[3]);
|
|
476
|
+
else if (command === "artifact-write")
|
|
477
|
+
artifactWrite(args[0] ?? "artifact.md", args[1] ?? "create");
|
|
478
|
+
else if (command === "actor-message")
|
|
479
|
+
actorMessage(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
|
|
480
|
+
else if (command === "package-summary")
|
|
481
|
+
packageSummary(args[0] ?? "package.json");
|
|
482
|
+
else if (command === "skill-summary")
|
|
483
|
+
skillSummary(args[0] ?? "skills/actors/SKILL.md", args[1] ?? "package.json");
|
|
484
|
+
else {
|
|
485
|
+
usage();
|
|
486
|
+
fail(`Unknown command: ${command}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
25
489
|
|
|
26
490
|
try {
|
|
27
491
|
runRecipeUtils(process.argv.slice(2));
|
|
@@ -1,27 +1,127 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Template recipe validator CLI
|
|
4
|
+
* Template recipe validator CLI.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Owns CLI parsing and report formatting directly. Recipe parsing remains in
|
|
7
|
+
* the reusable recipe-reference domain.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { existsSync } from "node:fs";
|
|
11
|
-
import {
|
|
10
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
13
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
14
|
|
|
14
15
|
function packageRoot() {
|
|
15
16
|
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
function
|
|
19
|
+
async function importRuntimeModule(name) {
|
|
19
20
|
const root = packageRoot();
|
|
20
|
-
const compiled = join(root, "dist", "lib",
|
|
21
|
-
|
|
21
|
+
const compiled = join(root, "dist", "lib", `${name}.js`);
|
|
22
|
+
const source = join(root, "lib", `${name}.ts`);
|
|
23
|
+
return await import(pathToFileURL(existsSync(compiled) ? compiled : source).href);
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
const {
|
|
26
|
+
const { readResolvedRecipeConfig } = await importRuntimeModule("recipes-references");
|
|
27
|
+
|
|
28
|
+
export function validateRecipeUsage() {
|
|
29
|
+
return `Usage:
|
|
30
|
+
validate-recipe.mjs <recipe-file-or-dir> [--all]
|
|
31
|
+
|
|
32
|
+
Validates one template recipe file, or all *.json/*.md files in a directory when --all is set.`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function expandPath(value) {
|
|
36
|
+
return resolve(String(value).replace(/^~(?=\/|$)/, process.env.HOME ?? homedir()));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function templateKind(template) {
|
|
40
|
+
if (typeof template === "string") return "leaf";
|
|
41
|
+
if (Array.isArray(template)) return "sequence";
|
|
42
|
+
if (template && typeof template === "object") {
|
|
43
|
+
const node = template;
|
|
44
|
+
if (typeof node.template === "string") return "leaf";
|
|
45
|
+
if (Array.isArray(node.template))
|
|
46
|
+
return node.parallel === true ? "parallel" : "sequence";
|
|
47
|
+
if (node.parallel === true) return "parallel";
|
|
48
|
+
return "object";
|
|
49
|
+
}
|
|
50
|
+
return "unknown";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function recipeFiles(target, all) {
|
|
54
|
+
if (!existsSync(target)) throw new Error(`Recipe path not found: ${target}`);
|
|
55
|
+
const stat = statSync(target);
|
|
56
|
+
if (stat.isFile()) return [target];
|
|
57
|
+
if (!stat.isDirectory())
|
|
58
|
+
throw new Error(`Recipe path is not a file or directory: ${target}`);
|
|
59
|
+
if (!all) throw new Error("Directory validation requires --all.");
|
|
60
|
+
return readdirSync(target)
|
|
61
|
+
.filter((file) => file.endsWith(".json") || file.endsWith(".md"))
|
|
62
|
+
.sort((a, b) => a.localeCompare(b))
|
|
63
|
+
.map((file) => resolve(target, file));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validateFile(file) {
|
|
67
|
+
try {
|
|
68
|
+
const config = readResolvedRecipeConfig(file);
|
|
69
|
+
if (!config?.template)
|
|
70
|
+
throw new Error("Recipe must define a non-empty template.");
|
|
71
|
+
return {
|
|
72
|
+
file,
|
|
73
|
+
ok: true,
|
|
74
|
+
name: config.name ?? "",
|
|
75
|
+
async: Boolean(config.async),
|
|
76
|
+
args: Array.isArray(config.args) ? config.args : [],
|
|
77
|
+
defaults:
|
|
78
|
+
config.defaults && typeof config.defaults === "object"
|
|
79
|
+
? Object.keys(config.defaults).sort()
|
|
80
|
+
: [],
|
|
81
|
+
imports:
|
|
82
|
+
config.imports && typeof config.imports === "object"
|
|
83
|
+
? Object.keys(config.imports).sort()
|
|
84
|
+
: [],
|
|
85
|
+
mailbox:
|
|
86
|
+
config.mailbox && typeof config.mailbox === "object"
|
|
87
|
+
? {
|
|
88
|
+
accepts: Array.isArray(config.mailbox.accepts)
|
|
89
|
+
? config.mailbox.accepts
|
|
90
|
+
: [],
|
|
91
|
+
emits: Array.isArray(config.mailbox.emits)
|
|
92
|
+
? config.mailbox.emits
|
|
93
|
+
: [],
|
|
94
|
+
}
|
|
95
|
+
: undefined,
|
|
96
|
+
template: templateKind(config.template),
|
|
97
|
+
};
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
file,
|
|
101
|
+
ok: false,
|
|
102
|
+
error: error instanceof Error ? error.message : String(error),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function validateRecipes(argv) {
|
|
108
|
+
const targetArg = argv.find((arg) => !arg.startsWith("-"));
|
|
109
|
+
const all = argv.includes("--all");
|
|
110
|
+
if (!targetArg || argv.includes("--help") || argv.includes("-h")) {
|
|
111
|
+
return { help: true, ok: Boolean(targetArg), usage: validateRecipeUsage() };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const files = recipeFiles(expandPath(targetArg), all);
|
|
115
|
+
const results = files.map(validateFile);
|
|
116
|
+
const failed = results.filter((result) => !result.ok).length;
|
|
117
|
+
return {
|
|
118
|
+
ok: failed === 0,
|
|
119
|
+
total: results.length,
|
|
120
|
+
passed: results.length - failed,
|
|
121
|
+
failed,
|
|
122
|
+
results,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
25
125
|
|
|
26
126
|
try {
|
|
27
127
|
const report = validateRecipes(process.argv.slice(2));
|
package/skills/actors/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.34.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
|
@@ -213,7 +213,7 @@ Only matching filename ids compete. Higher priority shadows lower priority; with
|
|
|
213
213
|
Muscle-memory lens: pi-actors has two durable executable-memory layers.
|
|
214
214
|
|
|
215
215
|
1. `~/.pi/agent/recipes/*.json` and `*.md` are the agent's active capability memory. Every recipe in that directory becomes an easy-to-call tool automatically and survives into later sessions. Descriptions matter here because they become the tool's operator-facing title/context.
|
|
216
|
-
2. `~/.pi/agent/recipes/
|
|
216
|
+
2. `~/.pi/agent/recipes/drafts/*.json` is draft memory captured from successful inline `spawn template=...` runs. Drafts do not enter the injected tool surface. They remain reusable by explicit path, e.g. `spawn file="~/.pi/agent/recipes/drafts/<name>.json"`, and can be promoted by moving or copying one level up into `~/.pi/agent/recipes`.
|
|
217
217
|
|
|
218
218
|
Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow draft memory by trying ad hoc actors successfully. Treat both as executable habits: drafts are the workbench/proving ground; root recipes are promoted muscle memory.
|
|
219
219
|
|
package/skills/swarm/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: swarm
|
|
3
3
|
description: Subagent orchestration with scoped locks and quorum consensus. Use for multi-model review, parallel scoped work, delegated audit, and coordinated subagent execution.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.34.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Swarm
|
|
@@ -31,7 +31,7 @@ Maintain this skill as a living orchestration standard. When real swarm work exp
|
|
|
31
31
|
- `Lens`: A deliberately narrow cognitive role assigned to one subagent, such as security, tests, architecture, economics, or operator UX.
|
|
32
32
|
- `Task Card`: A bounded implementation assignment with goal, allowed files, avoided files, expected output, and validation gates.
|
|
33
33
|
- `Component Capability`: An abstract adapter operation such as launcher, reviewer, verifier, merger, quorum, checkpoint, follow-up, judge, or normalizer. Swarm may target these capabilities, but local adapters bind them to concrete tools, recipes, draft recipes, command templates, async runs, or services.
|
|
34
|
-
- `Draft Recipe`: A reusable but non-registered recipe captured from a successful inline actor spawn
|
|
34
|
+
- `Draft Recipe`: A reusable but non-registered recipe captured from a successful inline actor spawn under `~/.pi/agent/recipes/drafts`. It can be replayed by explicit file path and later promoted into the active tool recipe root after enough dogfood.
|
|
35
35
|
- `Coordinator Checkpoint`: A deliberate subagent pause where the subagent preserves its working context, sends a bounded question or status to the orchestrator, receives a coordinator reply, and continues in the same subagent context.
|
|
36
36
|
- `Evidence Checkpoint`: A deliberate stop where a subagent records sources, assumptions, confidence, contradictions, or blocking evidence gaps before synthesis.
|
|
37
37
|
- `Integrator`: The human or agent that merges isolated branches/worktrees into the shared target and owns conflict resolution.
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical mailbox-backed actor worker entrypoint logic.
|
|
3
|
-
* Zones: worker demo, mailbox loop recipe runtime
|
|
4
|
-
*/
|
|
5
|
-
interface ActorWorkerArgs {
|
|
6
|
-
artifact_dir?: string;
|
|
7
|
-
branch?: string;
|
|
8
|
-
poll_ms?: string | number;
|
|
9
|
-
run?: string;
|
|
10
|
-
stale_claim_ms?: string | number;
|
|
11
|
-
state_dir?: string;
|
|
12
|
-
write_artifacts?: string | boolean;
|
|
13
|
-
}
|
|
14
|
-
export declare function parseActorWorkerArgs(argv: string[]): ActorWorkerArgs;
|
|
15
|
-
export declare function runActorWorker(argv?: string[]): Promise<void>;
|
|
16
|
-
export {};
|