@llblab/pi-actors 0.22.5 → 0.24.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 (180) hide show
  1. package/AGENTS.md +125 -59
  2. package/BACKLOG.md +88 -403
  3. package/CHANGELOG.md +39 -0
  4. package/README.md +14 -1
  5. package/dist/fixtures/protocol/actor-message-branch.json +13 -0
  6. package/dist/fixtures/protocol/artifact-manifest.json +9 -0
  7. package/dist/fixtures/protocol/mailbox-contract.json +15 -0
  8. package/dist/fixtures/protocol/recipe-summary.json +16 -0
  9. package/dist/fixtures/protocol/room-message.json +11 -0
  10. package/dist/fixtures/protocol/room-roster.json +11 -0
  11. package/dist/fixtures/protocol/run-inbox-message.json +9 -0
  12. package/dist/fixtures/protocol/run-outbox-event.json +9 -0
  13. package/dist/fixtures/protocol/run-state.json +10 -0
  14. package/dist/index.js +18 -2
  15. package/dist/lib/actor-inspector-tui.d.ts +4 -0
  16. package/dist/lib/actor-inspector-tui.js +58 -44
  17. package/dist/lib/actor-rooms.d.ts +16 -0
  18. package/dist/lib/actor-rooms.js +123 -28
  19. package/dist/lib/actor-worker.d.ts +13 -0
  20. package/dist/lib/actor-worker.js +86 -0
  21. package/dist/lib/async-runner.d.ts +5 -0
  22. package/dist/lib/async-runner.js +134 -0
  23. package/dist/lib/async-runs.d.ts +34 -2
  24. package/dist/lib/async-runs.js +224 -50
  25. package/dist/lib/build-dist.d.ts +5 -0
  26. package/dist/lib/build-dist.js +24 -0
  27. package/dist/lib/command-templates.js +5 -5
  28. package/dist/lib/conformance.d.ts +12 -0
  29. package/dist/lib/conformance.js +28 -0
  30. package/dist/lib/coordinator.d.ts +5 -0
  31. package/dist/lib/coordinator.js +557 -0
  32. package/dist/lib/limits.d.ts +11 -0
  33. package/dist/lib/limits.js +11 -0
  34. package/dist/lib/locker.d.ts +5 -0
  35. package/dist/lib/locker.js +310 -0
  36. package/dist/lib/mailbox-loop.d.ts +41 -0
  37. package/dist/lib/mailbox-loop.js +62 -0
  38. package/dist/lib/observability.d.ts +2 -2
  39. package/dist/lib/observability.js +57 -57
  40. package/dist/lib/output.js +4 -5
  41. package/dist/lib/prompts.d.ts +1 -1
  42. package/dist/lib/prompts.js +1 -1
  43. package/dist/lib/recipe-discovery.js +1 -0
  44. package/dist/lib/recipe-references.d.ts +11 -2
  45. package/dist/lib/recipe-references.js +29 -4
  46. package/dist/lib/recipe-usage.d.ts +2 -1
  47. package/dist/lib/recipe-usage.js +15 -3
  48. package/dist/lib/recipe-utils.d.ts +5 -0
  49. package/dist/lib/recipe-utils.js +385 -0
  50. package/dist/lib/runtime-notifier.js +3 -7
  51. package/dist/lib/runtime.js +12 -2
  52. package/dist/lib/state-readers.d.ts +21 -0
  53. package/dist/lib/state-readers.js +74 -0
  54. package/dist/lib/tools.js +198 -39
  55. package/dist/lib/validate-recipe.d.ts +6 -0
  56. package/dist/lib/validate-recipe.js +104 -0
  57. package/dist/recipes/actor-worker.json +35 -0
  58. package/dist/recipes/coordinator-locker.json +45 -0
  59. package/dist/recipes/lens-swarm.json +66 -0
  60. package/dist/recipes/locker.json +45 -0
  61. package/dist/recipes/music-player.json +38 -0
  62. package/dist/recipes/pipeline-architect-coordinator.json +95 -0
  63. package/dist/recipes/pipeline-artifact-bundle.json +100 -0
  64. package/dist/recipes/pipeline-artifact-report.json +58 -0
  65. package/dist/recipes/pipeline-artifact-write.json +72 -0
  66. package/dist/recipes/pipeline-async-run-ops.json +70 -0
  67. package/dist/recipes/pipeline-checkpoint-continuation.json +67 -0
  68. package/dist/recipes/pipeline-development-tasking.json +81 -0
  69. package/dist/recipes/pipeline-docs-maintenance.json +80 -0
  70. package/dist/recipes/pipeline-media-library.json +59 -0
  71. package/dist/recipes/pipeline-quorum-review.json +79 -0
  72. package/dist/recipes/pipeline-release-readiness.json +110 -0
  73. package/dist/recipes/pipeline-release-summary.json +88 -0
  74. package/dist/recipes/pipeline-repo-health.json +89 -0
  75. package/dist/recipes/pipeline-research-synthesis.json +94 -0
  76. package/dist/recipes/pipeline-review-readiness.json +54 -0
  77. package/dist/recipes/pipeline-room-swarm.json +50 -0
  78. package/dist/recipes/subagent-artifact.json +32 -0
  79. package/dist/recipes/subagent-checkpoint.json +33 -0
  80. package/dist/recipes/subagent-conflict-report.json +32 -0
  81. package/dist/recipes/subagent-contradiction-map.json +33 -0
  82. package/dist/recipes/subagent-critic.json +35 -0
  83. package/dist/recipes/subagent-evidence-map.json +33 -0
  84. package/dist/recipes/subagent-followup.json +33 -0
  85. package/dist/recipes/subagent-judge.json +33 -0
  86. package/dist/recipes/subagent-merge.json +33 -0
  87. package/dist/recipes/subagent-message.json +34 -0
  88. package/dist/recipes/subagent-normalize.json +31 -0
  89. package/dist/recipes/subagent-plan.json +33 -0
  90. package/dist/recipes/subagent-prompt.json +28 -0
  91. package/dist/recipes/subagent-quorum.json +43 -0
  92. package/dist/recipes/subagent-review-coordinator.json +114 -0
  93. package/dist/recipes/subagent-review.json +37 -0
  94. package/dist/recipes/subagent-task-card.json +35 -0
  95. package/dist/recipes/subagent-tools.json +27 -0
  96. package/dist/recipes/subagent-verify.json +34 -0
  97. package/dist/recipes/subagents-prompts.json +51 -0
  98. package/dist/recipes/utility-actor-message.json +23 -0
  99. package/dist/recipes/utility-artifact-manifest.json +16 -0
  100. package/dist/recipes/utility-artifact-write.json +16 -0
  101. package/dist/recipes/utility-changelog-head.json +11 -0
  102. package/dist/recipes/utility-changelog-section.json +13 -0
  103. package/dist/recipes/utility-coordinator-lock-snapshot.json +13 -0
  104. package/dist/recipes/utility-git-log.json +11 -0
  105. package/dist/recipes/utility-git-status.json +9 -0
  106. package/dist/recipes/utility-jsonl-tail.json +10 -0
  107. package/dist/recipes/utility-markdown-index.json +14 -0
  108. package/dist/recipes/utility-package-summary.json +11 -0
  109. package/dist/recipes/utility-playlist-build.json +17 -0
  110. package/dist/recipes/utility-playlist-scan.json +11 -0
  111. package/dist/recipes/utility-run-ops-snapshot.json +17 -0
  112. package/dist/recipes/utility-run-state-files.json +13 -0
  113. package/dist/recipes/utility-run-summary.json +11 -0
  114. package/dist/recipes/utility-skill-summary.json +13 -0
  115. package/dist/recipes/utility-validate-recipe.json +13 -0
  116. package/dist/recipes/utility-validation-wrapper.json +13 -0
  117. package/dist/scripts/actor-worker.mjs +31 -0
  118. package/dist/scripts/async-runner.mjs +31 -0
  119. package/dist/scripts/build-dist.mjs +25 -0
  120. package/dist/scripts/conformance.mjs +33 -0
  121. package/dist/scripts/coordinator.mjs +31 -0
  122. package/dist/scripts/locker.mjs +33 -0
  123. package/dist/scripts/music-player.mjs +964 -0
  124. package/dist/scripts/recipe-utils.mjs +31 -0
  125. package/dist/scripts/validate-recipe.mjs +34 -0
  126. package/dist/skills/actors/SKILL.md +375 -0
  127. package/dist/skills/swarm/SKILL.md +467 -0
  128. package/dist/skills/swarm/references/development-swarm.md +596 -0
  129. package/docs/actor-messages.md +2 -2
  130. package/docs/async-runs.md +13 -1
  131. package/docs/template-recipes.md +2 -2
  132. package/docs/tool-registry.md +0 -1
  133. package/fixtures/protocol/actor-message-branch.json +13 -0
  134. package/fixtures/protocol/artifact-manifest.json +9 -0
  135. package/fixtures/protocol/mailbox-contract.json +15 -0
  136. package/fixtures/protocol/recipe-summary.json +16 -0
  137. package/fixtures/protocol/room-message.json +11 -0
  138. package/fixtures/protocol/room-roster.json +11 -0
  139. package/fixtures/protocol/run-inbox-message.json +9 -0
  140. package/fixtures/protocol/run-outbox-event.json +9 -0
  141. package/fixtures/protocol/run-state.json +10 -0
  142. package/index.ts +21 -0
  143. package/lib/actor-inspector-tui.ts +138 -59
  144. package/lib/actor-rooms.ts +241 -60
  145. package/lib/actor-worker.ts +118 -0
  146. package/lib/async-runner.ts +173 -0
  147. package/lib/async-runs.ts +302 -53
  148. package/lib/build-dist.ts +30 -0
  149. package/lib/command-templates.ts +5 -5
  150. package/lib/conformance.ts +46 -0
  151. package/lib/coordinator.ts +649 -0
  152. package/lib/limits.ts +12 -0
  153. package/lib/locker.ts +340 -0
  154. package/lib/mailbox-loop.ts +148 -0
  155. package/lib/observability.ts +34 -23
  156. package/lib/output.ts +4 -6
  157. package/lib/prompts.ts +1 -1
  158. package/lib/recipe-discovery.ts +1 -0
  159. package/lib/recipe-references.ts +57 -6
  160. package/lib/recipe-usage.ts +31 -4
  161. package/lib/recipe-utils.ts +486 -0
  162. package/lib/runtime-notifier.ts +4 -6
  163. package/lib/runtime.ts +31 -7
  164. package/lib/state-readers.ts +93 -0
  165. package/lib/tools.ts +297 -58
  166. package/lib/validate-recipe.ts +110 -0
  167. package/package.json +11 -2
  168. package/recipes/actor-worker.json +35 -0
  169. package/recipes/pipeline-quorum-review.json +12 -7
  170. package/scripts/actor-worker.mjs +31 -0
  171. package/scripts/async-runner.mjs +11 -195
  172. package/scripts/build-dist.mjs +25 -0
  173. package/scripts/conformance.mjs +33 -0
  174. package/scripts/coordinator.mjs +19 -616
  175. package/scripts/locker.mjs +23 -322
  176. package/scripts/music-player.mjs +21 -2
  177. package/scripts/recipe-utils.mjs +20 -467
  178. package/scripts/validate-recipe.mjs +23 -113
  179. package/skills/actors/SKILL.md +7 -4
  180. package/skills/swarm/SKILL.md +3 -1
@@ -4,14 +4,15 @@
4
4
  * Owns detached run state, observation, log tailing, listing, and cancellation safety
5
5
  */
6
6
  import { spawn, spawnSync } from "node:child_process";
7
- import { randomUUID } from "node:crypto";
8
- import { closeSync, constants, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readlinkSync, rmSync, statSync, writeFileSync, writeSync, } from "node:fs";
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ import { closeSync, constants, existsSync, mkdirSync, openSync, readdirSync, readFileSync, cpSync, readlinkSync, renameSync, rmSync, statSync, writeFileSync, writeSync, } from "node:fs";
9
9
  import { createConnection } from "node:net";
10
10
  import { platform } from "node:os";
11
11
  import { basename, dirname, extname, join, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { substituteCommandTemplateToken } from "./command-templates.js";
14
14
  import { writeJsonAtomic } from "./file-state.js";
15
+ import { readJsonFileResilient, readJsonlFileResilient } from "./state-readers.js";
15
16
  import * as Paths from "./paths.js";
16
17
  import * as RecipeReferences from "./recipe-references.js";
17
18
  import * as RecipeUsage from "./recipe-usage.js";
@@ -48,10 +49,60 @@ function resolveArtifactPaths(artifacts, values) {
48
49
  for (const [key, value] of Object.entries(artifacts)) {
49
50
  if (!key.trim())
50
51
  continue;
51
- resolved[key] = substituteCommandTemplateToken(value, values, `recipe artifacts.${key}`);
52
+ if (typeof value === "string") {
53
+ resolved[key] = substituteCommandTemplateToken(value, values, `recipe artifacts.${key}`);
54
+ }
55
+ else if (value &&
56
+ typeof value === "object" &&
57
+ typeof value.path === "string") {
58
+ resolved[key] = {
59
+ ...value,
60
+ path: substituteCommandTemplateToken(value.path, values, `recipe artifacts.${key}.path`),
61
+ };
62
+ }
52
63
  }
53
64
  return Object.keys(resolved).length > 0 ? resolved : undefined;
54
65
  }
66
+ export function resolveArtifactManifest(artifacts) {
67
+ if (!artifacts)
68
+ return undefined;
69
+ const manifest = {};
70
+ for (const [name, artifact] of Object.entries(artifacts)) {
71
+ const declaration = typeof artifact === "string" ? { path: artifact } : artifact;
72
+ if (!declaration?.path)
73
+ continue;
74
+ try {
75
+ const content = readFileSync(declaration.path);
76
+ manifest[name] = {
77
+ exists: true,
78
+ ...(declaration.kind ? { kind: declaration.kind } : {}),
79
+ ...(declaration.media_type
80
+ ? { media_type: declaration.media_type }
81
+ : {}),
82
+ path: declaration.path,
83
+ ...(declaration.required !== undefined
84
+ ? { required: declaration.required }
85
+ : {}),
86
+ sha256: createHash("sha256").update(content).digest("hex"),
87
+ size: content.byteLength,
88
+ };
89
+ }
90
+ catch {
91
+ manifest[name] = {
92
+ exists: false,
93
+ ...(declaration.kind ? { kind: declaration.kind } : {}),
94
+ ...(declaration.media_type
95
+ ? { media_type: declaration.media_type }
96
+ : {}),
97
+ path: declaration.path,
98
+ ...(declaration.required !== undefined
99
+ ? { required: declaration.required }
100
+ : {}),
101
+ };
102
+ }
103
+ }
104
+ return Object.keys(manifest).length > 0 ? manifest : undefined;
105
+ }
55
106
  function resolveRunTemplate(params) {
56
107
  if (!params.template)
57
108
  throw new Error("spawn requires file or template.");
@@ -139,12 +190,7 @@ function resolveStartParams(params) {
139
190
  };
140
191
  }
141
192
  function readJson(path) {
142
- try {
143
- return JSON.parse(readFileSync(path, "utf8"));
144
- }
145
- catch {
146
- return undefined;
147
- }
193
+ return readJsonFileResilient(path, undefined).value;
148
194
  }
149
195
  function isAlive(pid) {
150
196
  try {
@@ -182,20 +228,12 @@ function tailLines(path, lines) {
182
228
  return content ? content.split("\n") : [];
183
229
  }
184
230
  function getInterruptedRunStatus(stateDir) {
185
- const events = tailFile(join(stateDir, "events.jsonl"), 200);
186
- if (!events)
187
- return undefined;
188
- for (const line of events.split("\n").reverse()) {
189
- try {
190
- const event = JSON.parse(line);
191
- if (event.event === "run.kill")
192
- return "killed";
193
- if (event.event === "run.cancel")
194
- return "cancelled";
195
- }
196
- catch {
197
- // Ignore malformed event lines.
198
- }
231
+ const events = readJsonlFileResilient(join(stateDir, "events.jsonl")).records.slice(-200);
232
+ for (const event of events.reverse()) {
233
+ if (event.event === "run.kill")
234
+ return "killed";
235
+ if (event.event === "run.cancel")
236
+ return "cancelled";
199
237
  }
200
238
  return undefined;
201
239
  }
@@ -270,7 +308,7 @@ export function startRun(params, cwd) {
270
308
  ? RecipeReferences.buildRecipeContextRecords(recipeFile)
271
309
  : undefined;
272
310
  if (recipeFile && isMutableUsageRecipeFile(recipeFile)) {
273
- RecipeUsage.recordRecipeLaunch(recipeFile);
311
+ RecipeUsage.recordRecipeLaunch(recipeFile, new Date(), startParams.launch_source === "tool" ? "tool" : "spawn");
274
312
  }
275
313
  const outFd = openSync(stdout, "a");
276
314
  const errFd = openSync(stderr, "a");
@@ -440,31 +478,111 @@ function matchesStatusFilter(status, filter) {
440
478
  return status !== "running";
441
479
  return status === filter;
442
480
  }
443
- export function listRuns(stateRoot = DEFAULT_STATE_ROOT, statusFilter) {
444
- if (!existsSync(stateRoot))
481
+ export function listRunStateDirs(stateRoot = DEFAULT_STATE_ROOT, depth = 0, seen = new Set()) {
482
+ if (!existsSync(stateRoot) || seen.has(resolve(stateRoot)))
483
+ return [];
484
+ seen.add(resolve(stateRoot));
485
+ let entries;
486
+ try {
487
+ entries = readdirSync(stateRoot, { withFileTypes: true });
488
+ }
489
+ catch {
445
490
  return [];
446
- const runs = [];
447
- for (const entry of readdirSync(stateRoot, { withFileTypes: true })) {
491
+ }
492
+ const result = [];
493
+ for (const entry of entries) {
448
494
  if (!entry.isDirectory())
449
495
  continue;
496
+ const child = join(stateRoot, entry.name);
497
+ if (existsSync(join(child, "run.json")))
498
+ result.push(child);
499
+ if (depth + 1 < 8)
500
+ result.push(...listRunStateDirs(child, depth + 1, seen));
501
+ }
502
+ return result;
503
+ }
504
+ function runIndexPath(stateRoot) {
505
+ return join(stateRoot, "index.json");
506
+ }
507
+ function indexEntryFromStatus(status) {
508
+ const progress = status.progress && typeof status.progress === "object"
509
+ ? status.progress
510
+ : {};
511
+ return {
512
+ ...(typeof status.ownerId === "string" ? { ownerId: status.ownerId } : {}),
513
+ ...(typeof status.recipe === "string" ? { recipe: status.recipe } : {}),
514
+ run: String(status.run),
515
+ state_dir: String(status.state_dir),
516
+ status: status.status,
517
+ ...(typeof status.tool === "string" ? { tool: status.tool } : {}),
518
+ updated_at: typeof progress.updatedAt === "string"
519
+ ? progress.updatedAt
520
+ : typeof status.createdAt === "string"
521
+ ? status.createdAt
522
+ : new Date(0).toISOString(),
523
+ };
524
+ }
525
+ export function rebuildRunStateIndex(stateRoot = DEFAULT_STATE_ROOT) {
526
+ mkdirSync(stateRoot, { recursive: true });
527
+ const entries = listRunStateDirs(stateRoot)
528
+ .flatMap((stateDir) => {
450
529
  try {
451
- const stateDir = join(stateRoot, entry.name);
452
- const status = getRunStatus(stateDir);
453
- if (!matchesStatusFilter(status.status, statusFilter))
454
- continue;
455
- runs.push({
456
- run: status.run,
457
- state_dir: stateDir,
458
- status: status.status,
459
- ...(typeof status.tool === "string" ? { tool: status.tool } : {}),
460
- ...(typeof status.recipe === "string" ? { recipe: status.recipe } : {}),
461
- });
530
+ return [indexEntryFromStatus(getRunStatus(stateDir))];
462
531
  }
463
532
  catch {
464
- // Ignore malformed run dirs.
533
+ return [];
465
534
  }
535
+ })
536
+ .sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""));
537
+ writeJsonAtomic(runIndexPath(stateRoot), {
538
+ entries,
539
+ rebuilt_at: new Date().toISOString(),
540
+ });
541
+ return entries;
542
+ }
543
+ export function readRunStateIndex(stateRoot = DEFAULT_STATE_ROOT) {
544
+ const index = readJson(runIndexPath(stateRoot));
545
+ if (!index || typeof index !== "object")
546
+ return undefined;
547
+ const entries = index.entries;
548
+ if (!Array.isArray(entries))
549
+ return undefined;
550
+ const valid = entries.filter((entry) => {
551
+ const record = entry && typeof entry === "object"
552
+ ? entry
553
+ : {};
554
+ return (typeof record.run === "string" &&
555
+ typeof record.state_dir === "string" &&
556
+ typeof record.status === "string");
557
+ });
558
+ if (valid.some((entry) => !existsSync(join(entry.state_dir, "run.json"))))
559
+ return undefined;
560
+ return valid;
561
+ }
562
+ export function listRuns(stateRoot = DEFAULT_STATE_ROOT, statusFilter) {
563
+ if (!existsSync(stateRoot))
564
+ return [];
565
+ const indexed = readRunStateIndex(stateRoot);
566
+ if (indexed) {
567
+ return indexed
568
+ .filter((entry) => matchesStatusFilter(entry.status, statusFilter))
569
+ .map((entry) => ({
570
+ run: entry.run,
571
+ state_dir: entry.state_dir,
572
+ status: entry.status,
573
+ ...(entry.tool ? { tool: entry.tool } : {}),
574
+ ...(entry.recipe ? { recipe: entry.recipe } : {}),
575
+ }));
466
576
  }
467
- return runs;
577
+ return rebuildRunStateIndex(stateRoot)
578
+ .filter((entry) => matchesStatusFilter(entry.status, statusFilter))
579
+ .map((entry) => ({
580
+ run: entry.run,
581
+ state_dir: entry.state_dir,
582
+ status: entry.status,
583
+ ...(entry.tool ? { tool: entry.tool } : {}),
584
+ ...(entry.recipe ? { recipe: entry.recipe } : {}),
585
+ }));
468
586
  }
469
587
  export function tailRun(runOrDir, lines = 40) {
470
588
  const status = getRunStatus(runOrDir);
@@ -525,14 +643,7 @@ function parseRunInboxLine(line) {
525
643
  }
526
644
  }
527
645
  function readRunInboxMessagesFromStateDir(stateDir) {
528
- const file = runInboxFile(stateDir);
529
- if (!existsSync(file))
530
- return [];
531
- return readFileSync(file, "utf8")
532
- .split("\n")
533
- .filter((line) => line.trim())
534
- .map(parseRunInboxLine)
535
- .filter((message) => Boolean(message));
646
+ return readJsonlFileResilient(runInboxFile(stateDir)).records;
536
647
  }
537
648
  function writeRunInboxMessages(stateDir, messages) {
538
649
  writeFileSync(runInboxFile(stateDir), messages.length
@@ -630,11 +741,18 @@ export function appendRunOutboxEvent(runOrDir, event) {
630
741
  const run = String(status.run ?? runOrDir);
631
742
  const type = event.type || event.event || "run.message";
632
743
  const to = event.to || "coordinator";
744
+ const metadata = event.metadata ?? {};
745
+ const requiresResponse = metadata.requires_response === true;
633
746
  const payload = {
634
747
  ...(event.body !== undefined ? { body: event.body } : {}),
635
748
  ...(event.correlation_id ? { correlation_id: event.correlation_id } : {}),
636
749
  ...(event.data !== undefined ? { data: event.data } : {}),
637
- delivery: normalizeRunOutboxDelivery(event.delivery ?? (to === "coordinator" ? "followup" : "log")),
750
+ delivery: normalizeRunOutboxDelivery(event.delivery ??
751
+ (requiresResponse
752
+ ? "followup"
753
+ : to === "coordinator"
754
+ ? "notify"
755
+ : "log")),
638
756
  event: type,
639
757
  from: event.from || `run:${run}`,
640
758
  level: normalizeRunOutboxLevel(event.level),
@@ -905,6 +1023,62 @@ export function cancelRun(runOrDir) {
905
1023
  ? { cancelled: result.stopped, ...result }
906
1024
  : result;
907
1025
  }
1026
+ function assertTerminalRun(runOrDir) {
1027
+ const status = getRunStatus(runOrDir);
1028
+ if (status.status === "running") {
1029
+ throw new Error("Only terminal runs can be archived or pruned.");
1030
+ }
1031
+ return status;
1032
+ }
1033
+ function archivePathFor(run, stateDir) {
1034
+ const archiveRoot = join(dirname(stateDir), "archived");
1035
+ mkdirSync(archiveRoot, { recursive: true });
1036
+ return join(archiveRoot, `${safeRunId(run)}-${new Date().toISOString().replace(/[:.]/g, "-")}`);
1037
+ }
1038
+ export function archiveRun(runOrDir) {
1039
+ const status = assertTerminalRun(runOrDir);
1040
+ const stateDir = String(status.state_dir);
1041
+ const run = String(status.run ?? basename(stateDir));
1042
+ const archiveDir = archivePathFor(run, stateDir);
1043
+ renameSync(stateDir, archiveDir);
1044
+ mkdirSync(stateDir, { recursive: true });
1045
+ const tombstone = {
1046
+ archived: true,
1047
+ archive_dir: archiveDir,
1048
+ original_state_dir: stateDir,
1049
+ run,
1050
+ status: status.status,
1051
+ ts: new Date().toISOString(),
1052
+ };
1053
+ writeJsonAtomic(join(stateDir, "archive-tombstone.json"), tombstone);
1054
+ return tombstone;
1055
+ }
1056
+ export function pruneRun(runOrDir, options = {}) {
1057
+ const status = assertTerminalRun(runOrDir);
1058
+ const stateDir = String(status.state_dir);
1059
+ const run = String(status.run ?? basename(stateDir));
1060
+ const manifest = resolveArtifactManifest(status.artifacts);
1061
+ const preserved = {};
1062
+ if (options.preserveArtifacts && manifest) {
1063
+ const preserveRoot = join(dirname(stateDir), "preserved-artifacts", safeRunId(run));
1064
+ mkdirSync(preserveRoot, { recursive: true });
1065
+ for (const [name, artifact] of Object.entries(manifest)) {
1066
+ if (!artifact.exists)
1067
+ continue;
1068
+ const target = join(preserveRoot, basename(artifact.path));
1069
+ cpSync(artifact.path, target, { force: true });
1070
+ preserved[name] = target;
1071
+ }
1072
+ }
1073
+ rmSync(stateDir, { recursive: true, force: true });
1074
+ return {
1075
+ pruned: true,
1076
+ preserved_artifacts: preserved,
1077
+ run,
1078
+ state_dir: stateDir,
1079
+ ts: new Date().toISOString(),
1080
+ };
1081
+ }
908
1082
  export function killRun(runOrDir) {
909
1083
  const result = stopRun(runOrDir, "SIGKILL", "run.kill");
910
1084
  return Object.hasOwn(result, "stopped")
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Dist build pipeline entrypoint logic.
3
+ * Zones: packaging, JavaScript-only distributive tree
4
+ */
5
+ export declare function buildDist(): void;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Dist build pipeline entrypoint logic.
3
+ * Zones: packaging, JavaScript-only distributive tree
4
+ */
5
+ import { spawnSync } from "node:child_process";
6
+ import { cpSync, mkdirSync, readdirSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ function run(command, args) {
9
+ const result = spawnSync(command, args, { stdio: "inherit" });
10
+ if (result.status !== 0)
11
+ process.exit(result.status ?? 1);
12
+ }
13
+ export function buildDist() {
14
+ rmSync("dist", { recursive: true, force: true });
15
+ mkdirSync("dist", { recursive: true });
16
+ run("tsc", ["-p", "tsconfig.build.json"]);
17
+ for (const dir of ["scripts", "recipes", "fixtures", "skills"]) {
18
+ cpSync(dir, join("dist", dir), { recursive: true });
19
+ }
20
+ const builtScripts = readdirSync(join("dist", "scripts"))
21
+ .filter((name) => name.endsWith(".mjs"))
22
+ .map((name) => join("dist", "scripts", name));
23
+ run(process.execPath, ["--check", ...builtScripts]);
24
+ }
@@ -102,23 +102,23 @@ function getLeafCommandTemplateWarnings(config) {
102
102
  const shellContent = hasAnyFlag(args, ["-c"])
103
103
  ? "shell command strings"
104
104
  : "shell scripts";
105
- warnings.push(`${config.label ?? command}: invokes ${command}; ${shellContent} are trusted executable content and are not sandboxed by command-template argv splitting.`);
105
+ warnings.push(`${config.label ?? command}: invokes ${command}; ${shellContent} are trusted executable content and are not sandboxed by command-template argv splitting. Mitigation: keep scripts local, reviewed, and parameterized with explicit placeholders.`);
106
106
  }
107
107
  if (["node", "deno", "bun"].includes(command) &&
108
108
  hasAnyFlag(args, ["-e", "--eval"])) {
109
- warnings.push(`${config.label ?? command}: invokes ${command} eval mode; code strings are trusted executable content and are not sandboxed.`);
109
+ warnings.push(`${config.label ?? command}: invokes ${command} eval mode; code strings are trusted executable content and are not sandboxed. Mitigation: prefer a checked-in script file or keep eval input fixed and reviewed.`);
110
110
  }
111
111
  if (["python", "python3", "perl", "ruby"].includes(command) &&
112
112
  hasAnyFlag(args, ["-c", "-e"])) {
113
- warnings.push(`${config.label ?? command}: invokes ${command} code-eval mode; code strings are trusted executable content and are not sandboxed.`);
113
+ warnings.push(`${config.label ?? command}: invokes ${command} code-eval mode; code strings are trusted executable content and are not sandboxed. Mitigation: prefer a checked-in script file or keep eval input fixed and reviewed.`);
114
114
  }
115
115
  if (command === "rm" &&
116
116
  (args.some((arg) => /^-[^-]*r/.test(arg) || /^-[^-]*f/.test(arg)) ||
117
117
  hasRiskyPathArg(args))) {
118
- warnings.push(`${config.label ?? command}: removes filesystem paths; verify placeholders and paths before running trusted destructive commands.`);
118
+ warnings.push(`${config.label ?? command}: removes filesystem paths; verify placeholders and paths before running trusted destructive commands. Mitigation: constrain path placeholders and consider dry-run or explicit confirmation.`);
119
119
  }
120
120
  if (["mv", "cp", "rsync"].includes(command) && hasRiskyPathArg(args)) {
121
- warnings.push(`${config.label ?? command}: mutates broad filesystem paths; verify placeholders and paths before running trusted commands.`);
121
+ warnings.push(`${config.label ?? command}: mutates broad filesystem paths; verify placeholders and paths before running trusted commands. Mitigation: constrain path placeholders and prefer narrow source/destination paths.`);
122
122
  }
123
123
  return warnings;
124
124
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Internal conformance runner logic.
3
+ * Zones: CI/release validation, protocol regression suite selection
4
+ */
5
+ export declare const conformanceSuites: string[];
6
+ export interface ConformanceReport {
7
+ code: number;
8
+ output: string;
9
+ summary: string;
10
+ suites: number;
11
+ }
12
+ export declare function runConformance(cwd?: URL): ConformanceReport;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Internal conformance runner logic.
3
+ * Zones: CI/release validation, protocol regression suite selection
4
+ */
5
+ import { spawnSync } from "node:child_process";
6
+ export const conformanceSuites = [
7
+ "tests/protocol-examples.test.ts",
8
+ "tests/recipe-discovery.test.ts",
9
+ "tests/registry.test.ts",
10
+ "tests/runtime-registry.test.ts",
11
+ "tests/async-runs.test.ts",
12
+ "tests/actor-rooms.test.ts",
13
+ "tests/tools.test.ts",
14
+ ];
15
+ export function runConformance(cwd = new URL("..", import.meta.url)) {
16
+ const result = spawnSync(process.execPath, ["--experimental-strip-types", "--test", ...conformanceSuites], { cwd, encoding: "utf8", stdio: "pipe" });
17
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
18
+ const summary = output
19
+ .split("\n")
20
+ .filter((line) => /^ℹ (tests|pass|fail|cancelled|skipped|todo|duration_ms) /.test(line))
21
+ .join("\n");
22
+ return {
23
+ code: result.status ?? 1,
24
+ output,
25
+ summary,
26
+ suites: conformanceSuites.length,
27
+ };
28
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Multi-actor coordinator helper entrypoint logic.
3
+ * Zones: swarm coordination, worker pool orchestration
4
+ */
5
+ export declare function runCoordinator(argv?: string[]): Promise<void>;