@llblab/pi-actors 0.23.0 → 0.24.1

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 (158) hide show
  1. package/AGENTS.md +125 -60
  2. package/BACKLOG.md +133 -176
  3. package/CHANGELOG.md +27 -0
  4. package/README.md +13 -0
  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 +3 -2
  15. package/dist/lib/actor-inspector-tui.js +5 -32
  16. package/dist/lib/actor-rooms.d.ts +8 -0
  17. package/dist/lib/actor-rooms.js +64 -20
  18. package/dist/lib/actor-worker.d.ts +13 -0
  19. package/dist/lib/actor-worker.js +86 -0
  20. package/dist/lib/async-runner.d.ts +5 -0
  21. package/dist/lib/async-runner.js +134 -0
  22. package/dist/lib/async-runs.js +9 -28
  23. package/dist/lib/conformance.d.ts +12 -0
  24. package/dist/lib/conformance.js +28 -0
  25. package/dist/lib/coordinator.d.ts +5 -0
  26. package/dist/lib/coordinator.js +574 -0
  27. package/dist/lib/locker.d.ts +5 -0
  28. package/dist/lib/locker.js +310 -0
  29. package/dist/lib/mailbox-loop.d.ts +41 -0
  30. package/dist/lib/mailbox-loop.js +62 -0
  31. package/dist/lib/observability.d.ts +2 -2
  32. package/dist/lib/observability.js +51 -53
  33. package/dist/lib/prompts.d.ts +1 -1
  34. package/dist/lib/prompts.js +1 -1
  35. package/dist/lib/recipe-references.js +25 -2
  36. package/dist/lib/recipe-utils.d.ts +5 -0
  37. package/dist/lib/recipe-utils.js +385 -0
  38. package/dist/lib/runtime-notifier.js +3 -7
  39. package/dist/lib/state-readers.d.ts +21 -0
  40. package/dist/lib/state-readers.js +74 -0
  41. package/dist/lib/tools.js +1 -1
  42. package/dist/lib/validate-recipe.d.ts +6 -0
  43. package/dist/lib/validate-recipe.js +104 -0
  44. package/dist/recipes/actor-worker.json +35 -0
  45. package/dist/recipes/coordinator-locker.json +45 -0
  46. package/dist/recipes/lens-swarm.json +66 -0
  47. package/dist/recipes/locker.json +45 -0
  48. package/dist/recipes/music-player.json +38 -0
  49. package/dist/recipes/pipeline-architect-coordinator.json +95 -0
  50. package/dist/recipes/pipeline-artifact-bundle.json +100 -0
  51. package/dist/recipes/pipeline-artifact-report.json +58 -0
  52. package/dist/recipes/pipeline-artifact-write.json +72 -0
  53. package/dist/recipes/pipeline-async-run-ops.json +70 -0
  54. package/dist/recipes/pipeline-checkpoint-continuation.json +67 -0
  55. package/dist/recipes/pipeline-development-tasking.json +81 -0
  56. package/dist/recipes/pipeline-docs-maintenance.json +80 -0
  57. package/dist/recipes/pipeline-media-library.json +59 -0
  58. package/dist/recipes/pipeline-quorum-review.json +79 -0
  59. package/dist/recipes/pipeline-release-readiness.json +110 -0
  60. package/dist/recipes/pipeline-release-summary.json +88 -0
  61. package/dist/recipes/pipeline-repo-health.json +89 -0
  62. package/dist/recipes/pipeline-research-synthesis.json +94 -0
  63. package/dist/recipes/pipeline-review-readiness.json +54 -0
  64. package/dist/recipes/pipeline-room-swarm.json +50 -0
  65. package/dist/recipes/subagent-artifact.json +32 -0
  66. package/dist/recipes/subagent-checkpoint.json +33 -0
  67. package/dist/recipes/subagent-conflict-report.json +32 -0
  68. package/dist/recipes/subagent-contradiction-map.json +33 -0
  69. package/dist/recipes/subagent-critic.json +35 -0
  70. package/dist/recipes/subagent-evidence-map.json +33 -0
  71. package/dist/recipes/subagent-followup.json +33 -0
  72. package/dist/recipes/subagent-judge.json +33 -0
  73. package/dist/recipes/subagent-merge.json +33 -0
  74. package/dist/recipes/subagent-message.json +34 -0
  75. package/dist/recipes/subagent-normalize.json +31 -0
  76. package/dist/recipes/subagent-plan.json +33 -0
  77. package/dist/recipes/subagent-prompt.json +28 -0
  78. package/dist/recipes/subagent-quorum.json +43 -0
  79. package/dist/recipes/subagent-review-coordinator.json +114 -0
  80. package/dist/recipes/subagent-review.json +37 -0
  81. package/dist/recipes/subagent-task-card.json +35 -0
  82. package/dist/recipes/subagent-tools.json +27 -0
  83. package/dist/recipes/subagent-verify.json +34 -0
  84. package/dist/recipes/subagents-prompts.json +51 -0
  85. package/dist/recipes/utility-actor-message.json +23 -0
  86. package/dist/recipes/utility-artifact-manifest.json +16 -0
  87. package/dist/recipes/utility-artifact-write.json +16 -0
  88. package/dist/recipes/utility-changelog-head.json +11 -0
  89. package/dist/recipes/utility-changelog-section.json +13 -0
  90. package/dist/recipes/utility-coordinator-lock-snapshot.json +13 -0
  91. package/dist/recipes/utility-git-log.json +11 -0
  92. package/dist/recipes/utility-git-status.json +9 -0
  93. package/dist/recipes/utility-jsonl-tail.json +10 -0
  94. package/dist/recipes/utility-markdown-index.json +14 -0
  95. package/dist/recipes/utility-package-summary.json +11 -0
  96. package/dist/recipes/utility-playlist-build.json +17 -0
  97. package/dist/recipes/utility-playlist-scan.json +11 -0
  98. package/dist/recipes/utility-run-ops-snapshot.json +17 -0
  99. package/dist/recipes/utility-run-state-files.json +13 -0
  100. package/dist/recipes/utility-run-summary.json +11 -0
  101. package/dist/recipes/utility-skill-summary.json +13 -0
  102. package/dist/recipes/utility-validate-recipe.json +13 -0
  103. package/dist/recipes/utility-validation-wrapper.json +13 -0
  104. package/dist/scripts/actor-worker.mjs +31 -0
  105. package/dist/scripts/async-runner.mjs +31 -0
  106. package/dist/scripts/build-dist.mjs +33 -0
  107. package/dist/scripts/conformance.mjs +33 -0
  108. package/dist/scripts/coordinator.mjs +31 -0
  109. package/dist/scripts/locker.mjs +33 -0
  110. package/dist/scripts/music-player.mjs +964 -0
  111. package/dist/scripts/recipe-utils.mjs +31 -0
  112. package/dist/scripts/validate-recipe.mjs +34 -0
  113. package/dist/skills/actors/SKILL.md +377 -0
  114. package/dist/skills/swarm/SKILL.md +467 -0
  115. package/dist/skills/swarm/references/development-swarm.md +596 -0
  116. package/docs/actor-messages.md +2 -2
  117. package/docs/async-runs.md +11 -0
  118. package/docs/template-recipes.md +1 -1
  119. package/fixtures/protocol/actor-message-branch.json +13 -0
  120. package/fixtures/protocol/artifact-manifest.json +9 -0
  121. package/fixtures/protocol/mailbox-contract.json +15 -0
  122. package/fixtures/protocol/recipe-summary.json +16 -0
  123. package/fixtures/protocol/room-message.json +11 -0
  124. package/fixtures/protocol/room-roster.json +11 -0
  125. package/fixtures/protocol/run-inbox-message.json +9 -0
  126. package/fixtures/protocol/run-outbox-event.json +9 -0
  127. package/fixtures/protocol/run-state.json +10 -0
  128. package/index.ts +3 -0
  129. package/lib/actor-inspector-tui.ts +11 -34
  130. package/lib/actor-rooms.ts +88 -18
  131. package/lib/actor-worker.ts +118 -0
  132. package/lib/async-runner.ts +173 -0
  133. package/lib/async-runs.ts +12 -22
  134. package/lib/conformance.ts +46 -0
  135. package/lib/coordinator.ts +664 -0
  136. package/lib/locker.ts +340 -0
  137. package/lib/mailbox-loop.ts +148 -0
  138. package/lib/observability.ts +24 -19
  139. package/lib/prompts.ts +1 -1
  140. package/lib/recipe-references.ts +37 -2
  141. package/lib/recipe-utils.ts +486 -0
  142. package/lib/runtime-notifier.ts +4 -6
  143. package/lib/state-readers.ts +93 -0
  144. package/lib/tools.ts +1 -1
  145. package/lib/validate-recipe.ts +110 -0
  146. package/package.json +10 -2
  147. package/recipes/actor-worker.json +35 -0
  148. package/recipes/pipeline-quorum-review.json +12 -7
  149. package/scripts/actor-worker.mjs +31 -0
  150. package/scripts/async-runner.mjs +11 -201
  151. package/scripts/build-dist.mjs +33 -0
  152. package/scripts/conformance.mjs +21 -35
  153. package/scripts/coordinator.mjs +15 -625
  154. package/scripts/locker.mjs +20 -332
  155. package/scripts/recipe-utils.mjs +17 -477
  156. package/scripts/validate-recipe.mjs +18 -121
  157. package/skills/actors/SKILL.md +8 -3
  158. package/skills/swarm/SKILL.md +3 -1
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Template recipe validator CLI logic.
3
+ * Zones: recipe validation, installed script entrypoint
4
+ */
5
+
6
+ import { existsSync, readdirSync, statSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { resolve } from "node:path";
9
+
10
+ import { readResolvedRecipeConfig } from "./recipe-references.ts";
11
+
12
+ export function validateRecipeUsage(): string {
13
+ return `Usage:
14
+ validate-recipe.mjs <recipe-file-or-dir> [--all]
15
+
16
+ Validates one template recipe file, or all *.json/*.md files in a directory when --all is set.`;
17
+ }
18
+
19
+ function expandPath(value: string): string {
20
+ return resolve(
21
+ String(value).replace(/^~(?=\/|$)/, process.env.HOME ?? homedir()),
22
+ );
23
+ }
24
+
25
+ function templateKind(template: unknown): string {
26
+ if (typeof template === "string") return "leaf";
27
+ if (Array.isArray(template)) return "sequence";
28
+ if (template && typeof template === "object") {
29
+ const node = template as Record<string, unknown>;
30
+ if (typeof node.template === "string") return "leaf";
31
+ if (Array.isArray(node.template))
32
+ return node.parallel === true ? "parallel" : "sequence";
33
+ if (node.parallel === true) return "parallel";
34
+ return "object";
35
+ }
36
+ return "unknown";
37
+ }
38
+
39
+ function recipeFiles(target: string, all: boolean): string[] {
40
+ if (!existsSync(target)) throw new Error(`Recipe path not found: ${target}`);
41
+ const stat = statSync(target);
42
+ if (stat.isFile()) return [target];
43
+ if (!stat.isDirectory())
44
+ throw new Error(`Recipe path is not a file or directory: ${target}`);
45
+ if (!all) throw new Error("Directory validation requires --all.");
46
+ return readdirSync(target)
47
+ .filter((file) => file.endsWith(".json") || file.endsWith(".md"))
48
+ .sort((a, b) => a.localeCompare(b))
49
+ .map((file) => resolve(target, file));
50
+ }
51
+
52
+ function validateFile(file: string): Record<string, unknown> {
53
+ try {
54
+ const config = readResolvedRecipeConfig(file);
55
+ if (!config?.template)
56
+ throw new Error("Recipe must define a non-empty template.");
57
+ return {
58
+ file,
59
+ ok: true,
60
+ name: config.name ?? "",
61
+ async: Boolean(config.async),
62
+ args: Array.isArray(config.args) ? config.args : [],
63
+ defaults:
64
+ config.defaults && typeof config.defaults === "object"
65
+ ? Object.keys(config.defaults).sort()
66
+ : [],
67
+ imports:
68
+ config.imports && typeof config.imports === "object"
69
+ ? Object.keys(config.imports).sort()
70
+ : [],
71
+ mailbox:
72
+ config.mailbox && typeof config.mailbox === "object"
73
+ ? {
74
+ accepts: Array.isArray(config.mailbox.accepts)
75
+ ? config.mailbox.accepts
76
+ : [],
77
+ emits: Array.isArray(config.mailbox.emits)
78
+ ? config.mailbox.emits
79
+ : [],
80
+ }
81
+ : undefined,
82
+ template: templateKind(config.template),
83
+ };
84
+ } catch (error) {
85
+ return {
86
+ file,
87
+ ok: false,
88
+ error: error instanceof Error ? error.message : String(error),
89
+ };
90
+ }
91
+ }
92
+
93
+ export function validateRecipes(argv: string[]): Record<string, unknown> {
94
+ const targetArg = argv.find((arg) => !arg.startsWith("-"));
95
+ const all = argv.includes("--all");
96
+ if (!targetArg || argv.includes("--help") || argv.includes("-h")) {
97
+ return { help: true, ok: Boolean(targetArg), usage: validateRecipeUsage() };
98
+ }
99
+
100
+ const files = recipeFiles(expandPath(targetArg), all);
101
+ const results = files.map(validateFile);
102
+ const failed = results.filter((result) => !result.ok).length;
103
+ return {
104
+ ok: failed === 0,
105
+ total: results.length,
106
+ passed: results.length - failed,
107
+ failed,
108
+ results,
109
+ };
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -30,7 +30,7 @@
30
30
  "test": "node --experimental-strip-types --test tests/*.test.ts",
31
31
  "pack:dry": "npm pack --dry-run",
32
32
  "validate": "npx tsc --noEmit && npm run build && npm run check && npm test && npm run pack:dry",
33
- "build": "tsc -p tsconfig.build.json",
33
+ "build": "node scripts/build-dist.mjs",
34
34
  "prepack": "npm run build"
35
35
  },
36
36
  "files": [
@@ -45,13 +45,21 @@
45
45
  "BACKLOG.md",
46
46
  "CHANGELOG.md",
47
47
  "docs",
48
+ "fixtures",
48
49
  "banner.jpg"
49
50
  ],
50
51
  "pi": {
51
52
  "extensions": [
52
53
  "./dist/index.js"
53
54
  ],
55
+ "sourceExtensions": [
56
+ "./index.ts"
57
+ ],
54
58
  "skills": [
59
+ "./dist/skills/actors/SKILL.md",
60
+ "./dist/skills/swarm/SKILL.md"
61
+ ],
62
+ "sourceSkills": [
55
63
  "./skills/actors/SKILL.md",
56
64
  "./skills/swarm/SKILL.md"
57
65
  ],
@@ -0,0 +1,35 @@
1
+ {
2
+ "async": true,
3
+ "description": "Start a canonical mailbox-backed branch worker demo actor.",
4
+ "args": [
5
+ "repo:path",
6
+ "branch:string",
7
+ "poll_ms:int"
8
+ ],
9
+ "defaults": {
10
+ "repo": "~/.pi/agent/extensions/pi-actors",
11
+ "branch": "worker",
12
+ "poll_ms": "1000"
13
+ },
14
+ "mailbox": {
15
+ "accepts": [
16
+ "task.assign",
17
+ "control.stop",
18
+ "control.cancel",
19
+ "control.kill"
20
+ ],
21
+ "emits": [
22
+ "actor.join",
23
+ "task.claim",
24
+ "task.result",
25
+ "awaiting_assignment",
26
+ "actor.leave",
27
+ "run.done",
28
+ "run.failed"
29
+ ]
30
+ },
31
+ "artifacts": {
32
+ "journal": "{state_dir}/worker-events.jsonl"
33
+ },
34
+ "template": "{repo}/scripts/actor-worker.mjs --state-dir {state_dir} --run {run_id} --branch {branch} --poll-ms {poll_ms}"
35
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "async": true,
3
3
  "imports": {
4
- "quorum": "subagent-quorum.json",
4
+ "subagent": "subagent-prompt.json",
5
5
  "merger": "subagent-merge.json",
6
6
  "judge": "subagent-judge.json",
7
7
  "normalizer": "subagent-normalize.json"
@@ -34,12 +34,17 @@
34
34
  },
35
35
  "template": [
36
36
  {
37
- "name": "quorum",
38
- "values": {
39
- "prompt": "{prompt}",
40
- "models": "{models}",
41
- "tools": "{tools}",
42
- "output_format": "Vote-style Markdown: Verdict, Rationale, Evidence, Confidence."
37
+ "parallel": true,
38
+ "repeat": "{models.length}",
39
+ "failure": "branch",
40
+ "template": {
41
+ "name": "subagent",
42
+ "values": {
43
+ "prompt": "{prompt}",
44
+ "model": "{models[index]}",
45
+ "tools": "{tools}",
46
+ "output_format": "Vote-style Markdown: Verdict, Rationale, Evidence, Confidence."
47
+ }
43
48
  }
44
49
  },
45
50
  {
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Canonical mailbox-backed actor worker demo.
5
+ *
6
+ * Thin process shim. Runtime logic lives in lib/actor-worker.ts and is
7
+ * compiled to dist/lib/actor-worker.js for installed JS-only packages.
8
+ */
9
+
10
+ import { existsSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { fileURLToPath, pathToFileURL } from "node:url";
13
+
14
+ function packageRoot() {
15
+ return dirname(dirname(fileURLToPath(import.meta.url)));
16
+ }
17
+
18
+ function mainModulePath() {
19
+ const root = packageRoot();
20
+ const compiled = join(root, "dist", "lib", "actor-worker.js");
21
+ return existsSync(compiled) ? compiled : join(root, "lib", "actor-worker.ts");
22
+ }
23
+
24
+ const { runActorWorker } = await import(pathToFileURL(mainModulePath()).href);
25
+
26
+ try {
27
+ await runActorWorker(process.argv.slice(2));
28
+ } catch (error) {
29
+ console.error(error instanceof Error ? error.message : String(error));
30
+ process.exit(2);
31
+ }
@@ -1,221 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Detached async-runner.
4
+ * Detached async-runner shim.
5
5
  *
6
- * This process is intentionally thin: the parent tool prepares state files and
7
- * starts this script in the background; this script loads the recorded run,
8
- * executes its command template, and writes ordinary files that status/tail/list
9
- * tools can inspect later.
10
- *
11
- * Keep orchestration policy out of this file.
6
+ * Runtime logic lives in lib/async-runner.ts and is compiled to
7
+ * dist/lib/async-runner.js for installed JS-only packages.
12
8
  */
13
9
 
14
- import { appendFileSync, existsSync, readFileSync } from "node:fs";
10
+ import { existsSync } from "node:fs";
15
11
  import { dirname, join } from "node:path";
16
12
  import { fileURLToPath, pathToFileURL } from "node:url";
17
13
 
18
- const stateDir = process.argv[2];
19
- if (!stateDir) {
20
- console.error("missing state dir");
21
- process.exit(1);
22
- }
23
-
24
- function scriptFile() {
25
- return fileURLToPath(import.meta.url);
26
- }
27
-
28
14
  function packageRoot() {
29
- return dirname(dirname(scriptFile()));
15
+ return dirname(dirname(fileURLToPath(import.meta.url)));
30
16
  }
31
17
 
32
- function libModulePath(name) {
18
+ function mainModulePath() {
33
19
  const root = packageRoot();
34
- const compiled = join(root, "dist", "lib", `${name}.js`);
35
- return existsSync(compiled) ? compiled : join(root, "lib", `${name}.ts`);
36
- }
37
-
38
- async function importLib(name) {
39
- return import(pathToFileURL(libModulePath(name)).href);
20
+ const compiled = join(root, "dist", "lib", "async-runner.js");
21
+ return existsSync(compiled) ? compiled : join(root, "lib", "async-runner.ts");
40
22
  }
41
23
 
42
- const { executeRegisteredTool } = await importLib("execution");
43
- const { execCommandTemplate } = await importLib("command-templates");
44
- const { appendRecipeContextToPiArgs } = await importLib("actor-recipe-context");
45
- const { writeJsonAtomic } = await importLib("file-state");
46
- const runPath = join(stateDir, "run.json");
47
- const progressPath = join(stateDir, "progress.json");
48
- const resultPath = join(stateDir, "result.json");
49
- const eventsPath = join(stateDir, "events.jsonl");
50
- const outboxPath = join(stateDir, "outbox.jsonl");
51
- const stdoutPath = join(stateDir, "stdout.log");
52
- const stderrPath = join(stateDir, "stderr.log");
53
- const meta = JSON.parse(readFileSync(runPath, "utf8"));
54
-
55
- /**
56
- * Appends one lifecycle event.
57
- *
58
- * events.jsonl is append-only so observers can tail transitions even while the
59
- * run is still running.
60
- */
61
- function event(name, data = {}) {
62
- appendFileSync(
63
- eventsPath,
64
- `${JSON.stringify({ event: name, ts: new Date().toISOString(), ...data })}\n`,
65
- );
66
- }
67
-
68
- function quoteCommandDetailPart(value) {
69
- if (value === "") return "''";
70
- if (/^[A-Za-z0-9_/:=.,@%+\-]+$/.test(value)) return value;
71
- return `'${String(value).replaceAll("'", "'\\''")}'`;
72
- }
24
+ const { runAsyncRunner } = await import(pathToFileURL(mainModulePath()).href);
73
25
 
74
- function formatCommandDetail(command, args) {
75
- return [command, ...args].map(quoteCommandDetailPart).join(" ");
76
- }
77
-
78
- function summarizeCommandDetail(commandDetail) {
79
- return commandDetail.length > 160
80
- ? `${commandDetail.slice(0, 157)}...`
81
- : commandDetail;
82
- }
83
-
84
- function getCommandDoneDelivery(result) {
85
- return result.code !== 0 || activeSubagents > 0 ? "followup" : "log";
86
- }
87
-
88
- function outbox(name, summary, data = {}, delivery = "log", level = "info") {
89
- appendFileSync(
90
- outboxPath,
91
- `${JSON.stringify({ body: data, data, delivery, event: name, from: `run:${meta.run}`, level, summary, to: "coordinator", ts: new Date().toISOString(), type: name })}\n`,
92
- );
93
- }
94
- /**
95
- * Writes the compact status snapshot.
96
- *
97
- * progress.json is rewritten atomically because observers may read it
98
- * concurrently.
99
- */
100
- function progress(phase, extra = {}) {
101
- writeJsonAtomic(progressPath, {
102
- phase,
103
- updatedAt: new Date().toISOString(),
104
- ...extra,
105
- });
106
- }
107
- let activeSubagents = 0;
108
- let completedSubagents = 0;
109
- const subagentFailures = [];
110
-
111
- function progressRunning() {
112
- progress("running", {
113
- activeSubagents,
114
- completed: completedSubagents,
115
- failures: subagentFailures,
116
- });
117
- }
118
- async function observedExec(command, args, options) {
119
- const commandDetail = formatCommandDetail(command, args);
120
- const execArgs = appendRecipeContextToPiArgs(
121
- command,
122
- args,
123
- meta.recipe_context_records,
124
- options?.actorRecipeContext,
125
- );
126
- activeSubagents += 1;
127
- event("command.start", { activeSubagents, command: commandDetail });
128
- progressRunning();
129
- const result = await execCommandTemplate(command, execArgs, options);
130
- activeSubagents = Math.max(0, activeSubagents - 1);
131
- completedSubagents += 1;
132
- if (result.code !== 0) {
133
- subagentFailures.push({
134
- code: result.code,
135
- command: commandDetail,
136
- killed: result.killed,
137
- });
138
- }
139
- event("command.done", {
140
- activeSubagents,
141
- code: result.code,
142
- command: commandDetail,
143
- killed: result.killed,
144
- });
145
- outbox(
146
- "command.done",
147
- `Command ${summarizeCommandDetail(commandDetail)} completed with code ${result.code}`,
148
- {
149
- activeSubagents,
150
- ...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
151
- run_files: [stdoutPath, stderrPath, resultPath, eventsPath, outboxPath],
152
- code: result.code,
153
- command: commandDetail,
154
- killed: result.killed,
155
- },
156
- getCommandDoneDelivery(result),
157
- result.code === 0 ? "info" : "error",
158
- );
159
- progressRunning();
160
- return result;
161
- }
162
26
  try {
163
- event("run.runner.start", { pid: process.pid });
164
- progressRunning();
165
- /**
166
- * Reuse the same registered-tool execution path as foreground tools.
167
- *
168
- * The synthetic tool name is only for reporting; meta.template is the real
169
- * work.
170
- */
171
- const result = await executeRegisteredTool(
172
- {
173
- name: "run_actor",
174
- description: "Detached command-template run actor",
175
- template: meta.template,
176
- args: [],
177
- defaults: {},
178
- },
179
- meta.values || {},
180
- observedExec,
181
- meta.cwd,
182
- );
183
- /**
184
- * Tool output is already formatted/truncated by the shared execution layer.
185
- * The runner persists that agent-facing text for later tail/status analysis.
186
- */
187
- const text = result.content?.[0]?.text || "";
188
- appendFileSync(stdoutPath, text);
189
- writeJsonAtomic(resultPath, {
190
- code: result.details.code,
191
- command: result.details.command,
192
- killed: result.details.killed,
193
- truncated: result.details.truncated,
194
- completedAt: new Date().toISOString(),
195
- });
196
- progress("done", {
197
- completed: 1,
198
- failures: result.details.nonCriticalFailures || [],
199
- });
200
- event("run.done", { code: result.details.code });
27
+ await runAsyncRunner(process.argv[2]);
201
28
  } catch (error) {
202
- /**
203
- * Represent failure in every observable channel:
204
- *
205
- * - stderr.log for humans
206
- * - result.json for structured readers
207
- * - progress.json for status
208
- * - events.jsonl for tail/watch flows
209
- */
210
- const message = error instanceof Error ? error.message : String(error);
211
- appendFileSync(stderrPath, `${message}\n`);
212
- writeJsonAtomic(resultPath, {
213
- code: 1,
214
- error: message,
215
- killed: false,
216
- completedAt: new Date().toISOString(),
217
- });
218
- progress("failed", { completed: 0, failures: [{ message }] });
219
- event("run.failed", { error: message });
29
+ console.error(error instanceof Error ? error.message : String(error));
220
30
  process.exit(1);
221
31
  }
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Build the JavaScript-only distributive tree.
5
+ *
6
+ * This is intentionally standalone: it is package/build glue, not reusable
7
+ * actor-domain behavior. It cleans dist, compiles TypeScript, mirrors runtime
8
+ * assets, and syntax-checks packaged script shims.
9
+ */
10
+
11
+ import { spawnSync } from "node:child_process";
12
+ import { cpSync, mkdirSync, readdirSync, rmSync } from "node:fs";
13
+ import { join } from "node:path";
14
+
15
+ function run(command, args) {
16
+ const result = spawnSync(command, args, { stdio: "inherit" });
17
+ if (result.status !== 0) process.exit(result.status ?? 1);
18
+ }
19
+
20
+ rmSync("dist", { recursive: true, force: true });
21
+ mkdirSync("dist", { recursive: true });
22
+
23
+ run("tsc", ["-p", "tsconfig.build.json"]);
24
+
25
+ for (const dir of ["scripts", "recipes", "fixtures", "skills"]) {
26
+ cpSync(dir, join("dist", dir), { recursive: true });
27
+ }
28
+
29
+ const builtScripts = readdirSync(join("dist", "scripts"))
30
+ .filter((name) => name.endsWith(".mjs"))
31
+ .map((name) => join("dist", "scripts", name));
32
+
33
+ run(process.execPath, ["--check", ...builtScripts]);
@@ -1,47 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Internal conformance runner.
4
+ * Internal conformance runner shim.
5
5
  *
6
- * This script runs the protocol-facing regression suites that exercise recipe
7
- * discovery, registry mutation, spawn lifecycle, message routing, room/branch
8
- * state, ownership checks, artifacts, and attention semantics without requiring
9
- * the Pi UI.
10
- *
11
- * Keep output compact for CI and release preflight use; detailed failures are
12
- * printed only when the underlying Node test run fails.
6
+ * Runtime logic lives in lib/conformance.ts and is compiled to
7
+ * dist/lib/conformance.js for installed JS-only packages.
13
8
  */
14
9
 
15
- import { spawnSync } from "node:child_process";
10
+ import { existsSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
13
 
17
- const suites = [
18
- "tests/protocol-examples.test.ts",
19
- "tests/recipe-discovery.test.ts",
20
- "tests/registry.test.ts",
21
- "tests/runtime-registry.test.ts",
22
- "tests/async-runs.test.ts",
23
- "tests/actor-rooms.test.ts",
24
- "tests/tools.test.ts",
25
- ];
14
+ function packageRoot() {
15
+ return dirname(dirname(fileURLToPath(import.meta.url)));
16
+ }
26
17
 
27
- const result = spawnSync(
28
- process.execPath,
29
- ["--experimental-strip-types", "--test", ...suites],
30
- { cwd: new URL("..", import.meta.url), encoding: "utf8", stdio: "pipe" },
31
- );
18
+ function mainModulePath() {
19
+ const root = packageRoot();
20
+ const compiled = join(root, "dist", "lib", "conformance.js");
21
+ return existsSync(compiled) ? compiled : join(root, "lib", "conformance.ts");
22
+ }
32
23
 
33
- const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
34
- const summary = output
35
- .split("\n")
36
- .filter((line) =>
37
- /^ℹ (tests|pass|fail|cancelled|skipped|todo|duration_ms) /.test(line),
38
- )
39
- .join("\n");
24
+ const { runConformance } = await import(pathToFileURL(mainModulePath()).href);
25
+ const report = runConformance(packageRoot());
40
26
 
41
27
  console.log("pi-actors conformance");
42
- console.log(`suites ${suites.length}`);
43
- if (summary) console.log(summary);
44
- if (result.status !== 0) {
45
- console.error(output.trimEnd());
46
- process.exit(result.status ?? 1);
28
+ console.log(`suites ${report.suites}`);
29
+ if (report.summary) console.log(report.summary);
30
+ if (report.code !== 0) {
31
+ console.error(report.output.trimEnd());
32
+ process.exit(report.code);
47
33
  }