@llblab/pi-actors 0.20.2 → 0.22.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/BACKLOG.md +34 -80
- package/CHANGELOG.md +29 -0
- package/README.md +7 -1
- package/dist/index.js +11 -0
- package/dist/lib/actor-rooms.d.ts +1 -0
- package/dist/lib/actor-rooms.js +33 -1
- package/dist/lib/async-runs.d.ts +35 -1
- package/dist/lib/async-runs.js +318 -36
- package/dist/lib/command-templates.js +8 -1
- package/dist/lib/observability.d.ts +15 -0
- package/dist/lib/observability.js +103 -18
- package/dist/lib/recipe-discovery.js +13 -5
- package/dist/lib/recipe-references.js +137 -11
- package/dist/lib/runtime-notifier.d.ts +48 -0
- package/dist/lib/runtime-notifier.js +137 -0
- package/dist/lib/tools.js +18 -9
- package/docs/README.md +1 -1
- package/docs/actor-messages.md +8 -3
- package/docs/async-runs.md +7 -5
- package/docs/recipe-library.md +25 -8
- package/docs/template-recipes.md +37 -7
- package/docs/tool-registry.md +4 -3
- package/index.ts +14 -0
- package/lib/actor-rooms.ts +46 -1
- package/lib/async-runs.ts +433 -49
- package/lib/command-templates.ts +8 -1
- package/lib/observability.ts +133 -20
- package/lib/recipe-discovery.ts +21 -6
- package/lib/recipe-references.ts +141 -17
- package/lib/runtime-notifier.ts +207 -0
- package/lib/tools.ts +36 -13
- package/package.json +2 -3
- package/recipes/music-player.json +1 -1
- package/recipes/pipeline-room-swarm.json +1 -1
- package/scripts/coordinator.mjs +276 -135
- package/scripts/locker.mjs +87 -28
- package/scripts/music-player.mjs +401 -94
- package/scripts/validate-recipe.mjs +2 -2
- package/skills/actors/SKILL.md +10 -9
- package/skills/swarm/SKILL.md +1 -1
- package/index.js +0 -19
package/scripts/locker.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
2
4
|
import {
|
|
3
5
|
appendFileSync,
|
|
4
6
|
existsSync,
|
|
@@ -7,8 +9,8 @@ import {
|
|
|
7
9
|
rmSync,
|
|
8
10
|
writeFileSync,
|
|
9
11
|
} from "node:fs";
|
|
12
|
+
import { createServer } from "node:net";
|
|
10
13
|
import { dirname, join, resolve } from "node:path";
|
|
11
|
-
import { spawnSync } from "node:child_process";
|
|
12
14
|
import readline from "node:readline";
|
|
13
15
|
|
|
14
16
|
function parseArgs(argv) {
|
|
@@ -49,6 +51,21 @@ function writeJson(path, value) {
|
|
|
49
51
|
mkdirSync(dirname(path), { recursive: true });
|
|
50
52
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
51
53
|
}
|
|
54
|
+
function getControlEndpoint() {
|
|
55
|
+
if (process.platform !== "win32") return { path: controlPath, type: "fifo" };
|
|
56
|
+
const hash = createHash("sha256")
|
|
57
|
+
.update(resolve(stateDir))
|
|
58
|
+
.digest("hex")
|
|
59
|
+
.slice(0, 20);
|
|
60
|
+
return { path: `\\\\.\\pipe\\pi-actors-locker-${hash}`, type: "named-pipe" };
|
|
61
|
+
}
|
|
62
|
+
function writeControlEndpoint(endpoint) {
|
|
63
|
+
writeJson(join(stateDir, "control.json"), endpoint);
|
|
64
|
+
const runPath = join(stateDir, "run.json");
|
|
65
|
+
const run = readJson(runPath, undefined);
|
|
66
|
+
if (run && typeof run === "object")
|
|
67
|
+
writeJson(runPath, { ...run, control: endpoint });
|
|
68
|
+
}
|
|
52
69
|
function journal(event, data = {}) {
|
|
53
70
|
appendFileSync(
|
|
54
71
|
journalPath,
|
|
@@ -205,9 +222,19 @@ function handle(message) {
|
|
|
205
222
|
if (!resource) throw new Error("lock.renew body.resource is required");
|
|
206
223
|
const current = locks[resource];
|
|
207
224
|
if (!current) {
|
|
208
|
-
outbox(
|
|
225
|
+
outbox(
|
|
226
|
+
"lock.denied",
|
|
227
|
+
`Lock renew denied ${resource}`,
|
|
228
|
+
{ resource, owner, reason: "missing" },
|
|
229
|
+
"warning",
|
|
230
|
+
);
|
|
209
231
|
} else if (current.owner !== owner) {
|
|
210
|
-
outbox(
|
|
232
|
+
outbox(
|
|
233
|
+
"lock.denied",
|
|
234
|
+
`Lock renew denied ${resource}`,
|
|
235
|
+
{ resource, owner, current },
|
|
236
|
+
"warning",
|
|
237
|
+
);
|
|
211
238
|
} else {
|
|
212
239
|
locks[resource] = { ...current, expiresAt: now() + leaseMs };
|
|
213
240
|
outbox("lock.renewed", `Lock renewed ${resource}`, { resource, owner });
|
|
@@ -222,8 +249,15 @@ function handle(message) {
|
|
|
222
249
|
outbox("lock.released", `Lock released ${resource}`, { resource });
|
|
223
250
|
return;
|
|
224
251
|
}
|
|
225
|
-
if (
|
|
226
|
-
|
|
252
|
+
if (
|
|
253
|
+
type === "lock.complete" ||
|
|
254
|
+
type === "lock.fail" ||
|
|
255
|
+
type === "coord.complete" ||
|
|
256
|
+
type === "coord.fail"
|
|
257
|
+
) {
|
|
258
|
+
const eventType = type.startsWith("coord.")
|
|
259
|
+
? type.replace("coord.", "lock.")
|
|
260
|
+
: type;
|
|
227
261
|
journal(eventType, body);
|
|
228
262
|
outbox(
|
|
229
263
|
eventType,
|
|
@@ -244,30 +278,55 @@ if (mode === "snapshot") {
|
|
|
244
278
|
process.exit(0);
|
|
245
279
|
}
|
|
246
280
|
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
250
|
-
|
|
281
|
+
function handleLine(line) {
|
|
282
|
+
const message = normalizeMessage(line);
|
|
283
|
+
if (!message) return;
|
|
284
|
+
try {
|
|
285
|
+
handle(message);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
288
|
+
journal("lock.error", { error: text });
|
|
289
|
+
outbox("lock.error", text, { error: text }, "error");
|
|
290
|
+
}
|
|
251
291
|
}
|
|
252
|
-
writeJson(queuePath, readJson(queuePath, { items: [] }));
|
|
253
|
-
writeJson(locksPath, cleanExpiredLocks(readJson(locksPath, {})));
|
|
254
|
-
journal("lock.started", { leaseMs });
|
|
255
|
-
outbox("lock.started", "Locker ready", { leaseMs });
|
|
256
292
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
for await (const line of rl) {
|
|
263
|
-
const message = normalizeMessage(line);
|
|
264
|
-
if (!message) continue;
|
|
265
|
-
try {
|
|
266
|
-
handle(message);
|
|
267
|
-
} catch (error) {
|
|
268
|
-
const text = error instanceof Error ? error.message : String(error);
|
|
269
|
-
journal("lock.error", { error: text });
|
|
270
|
-
outbox("lock.error", text, { error: text }, "error");
|
|
271
|
-
}
|
|
293
|
+
async function serveFifo(endpoint) {
|
|
294
|
+
if (!existsSync(endpoint.path)) {
|
|
295
|
+
const result = spawnSync("mkfifo", [endpoint.path]);
|
|
296
|
+
if (result.status !== 0)
|
|
297
|
+
throw new Error(`mkfifo failed: ${result.stderr?.toString?.() ?? ""}`);
|
|
272
298
|
}
|
|
299
|
+
writeControlEndpoint(endpoint);
|
|
300
|
+
while (true) {
|
|
301
|
+
const stream = await import("node:fs").then((fs) =>
|
|
302
|
+
fs.createReadStream(endpoint.path, { encoding: "utf8" }),
|
|
303
|
+
);
|
|
304
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
305
|
+
for await (const line of rl) handleLine(line);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function serveNamedPipe(endpoint) {
|
|
310
|
+
rmSync(endpoint.path, { force: true });
|
|
311
|
+
const server = createServer((socket) => {
|
|
312
|
+
const rl = readline.createInterface({ input: socket, crlfDelay: Infinity });
|
|
313
|
+
rl.on("line", handleLine);
|
|
314
|
+
});
|
|
315
|
+
await new Promise((resolveReady, rejectReady) => {
|
|
316
|
+
server.once("error", rejectReady);
|
|
317
|
+
server.listen(endpoint.path, () => {
|
|
318
|
+
server.off("error", rejectReady);
|
|
319
|
+
writeControlEndpoint(endpoint);
|
|
320
|
+
resolveReady();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
await new Promise(() => {});
|
|
273
324
|
}
|
|
325
|
+
|
|
326
|
+
const endpoint = getControlEndpoint();
|
|
327
|
+
writeJson(queuePath, readJson(queuePath, { items: [] }));
|
|
328
|
+
writeJson(locksPath, cleanExpiredLocks(readJson(locksPath, {})));
|
|
329
|
+
journal("lock.started", { leaseMs, control: endpoint.type });
|
|
330
|
+
outbox("lock.started", "Locker ready", { leaseMs, control: endpoint.type });
|
|
331
|
+
if (endpoint.type === "named-pipe") await serveNamedPipe(endpoint);
|
|
332
|
+
else await serveFifo(endpoint);
|