@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.
Files changed (41) hide show
  1. package/BACKLOG.md +34 -80
  2. package/CHANGELOG.md +29 -0
  3. package/README.md +7 -1
  4. package/dist/index.js +11 -0
  5. package/dist/lib/actor-rooms.d.ts +1 -0
  6. package/dist/lib/actor-rooms.js +33 -1
  7. package/dist/lib/async-runs.d.ts +35 -1
  8. package/dist/lib/async-runs.js +318 -36
  9. package/dist/lib/command-templates.js +8 -1
  10. package/dist/lib/observability.d.ts +15 -0
  11. package/dist/lib/observability.js +103 -18
  12. package/dist/lib/recipe-discovery.js +13 -5
  13. package/dist/lib/recipe-references.js +137 -11
  14. package/dist/lib/runtime-notifier.d.ts +48 -0
  15. package/dist/lib/runtime-notifier.js +137 -0
  16. package/dist/lib/tools.js +18 -9
  17. package/docs/README.md +1 -1
  18. package/docs/actor-messages.md +8 -3
  19. package/docs/async-runs.md +7 -5
  20. package/docs/recipe-library.md +25 -8
  21. package/docs/template-recipes.md +37 -7
  22. package/docs/tool-registry.md +4 -3
  23. package/index.ts +14 -0
  24. package/lib/actor-rooms.ts +46 -1
  25. package/lib/async-runs.ts +433 -49
  26. package/lib/command-templates.ts +8 -1
  27. package/lib/observability.ts +133 -20
  28. package/lib/recipe-discovery.ts +21 -6
  29. package/lib/recipe-references.ts +141 -17
  30. package/lib/runtime-notifier.ts +207 -0
  31. package/lib/tools.ts +36 -13
  32. package/package.json +2 -3
  33. package/recipes/music-player.json +1 -1
  34. package/recipes/pipeline-room-swarm.json +1 -1
  35. package/scripts/coordinator.mjs +276 -135
  36. package/scripts/locker.mjs +87 -28
  37. package/scripts/music-player.mjs +401 -94
  38. package/scripts/validate-recipe.mjs +2 -2
  39. package/skills/actors/SKILL.md +10 -9
  40. package/skills/swarm/SKILL.md +1 -1
  41. package/index.js +0 -19
@@ -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("lock.denied", `Lock renew denied ${resource}`, { resource, owner, reason: "missing" }, "warning");
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("lock.denied", `Lock renew denied ${resource}`, { resource, owner, current }, "warning");
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 (type === "lock.complete" || type === "lock.fail" || type === "coord.complete" || type === "coord.fail") {
226
- const eventType = type.startsWith("coord.") ? type.replace("coord.", "lock.") : type;
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
- if (!existsSync(controlPath)) {
248
- const result = spawnSync("mkfifo", [controlPath]);
249
- if (result.status !== 0)
250
- throw new Error(`mkfifo failed: ${result.stderr?.toString?.() ?? ""}`);
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
- while (true) {
258
- const stream = await import("node:fs").then((fs) =>
259
- fs.createReadStream(controlPath, { encoding: "utf8" }),
260
- );
261
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
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);