@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.
Files changed (162) hide show
  1. package/AGENTS.md +23 -11
  2. package/BACKLOG.md +2 -30
  3. package/CHANGELOG.md +30 -1
  4. package/dist/index.js +10 -10
  5. package/dist/lib/async-runs.d.ts +23 -75
  6. package/dist/lib/async-runs.js +49 -801
  7. package/dist/lib/command-templates.d.ts +3 -3
  8. package/dist/lib/command-templates.js +3 -3
  9. package/dist/lib/config.d.ts +2 -2
  10. package/dist/lib/config.js +5 -5
  11. package/dist/lib/{output.d.ts → execution-output.d.ts} +3 -3
  12. package/dist/lib/{output.js → execution-output.js} +3 -3
  13. package/dist/lib/execution.js +1 -1
  14. package/dist/lib/{actor-inspector-tui.d.ts → inspector.d.ts} +3 -2
  15. package/dist/lib/{actor-inspector-tui.js → inspector.js} +3 -2
  16. package/dist/lib/mailbox-loop.d.ts +3 -3
  17. package/dist/lib/mailbox-loop.js +3 -3
  18. package/dist/lib/{actor-messages.d.ts → messages.d.ts} +3 -3
  19. package/dist/lib/{actor-messages.js → messages.js} +3 -3
  20. package/dist/lib/paths.d.ts +1 -1
  21. package/dist/lib/paths.js +3 -3
  22. package/dist/lib/{actor-recipe-context.d.ts → recipes-context.d.ts} +3 -2
  23. package/dist/lib/{actor-recipe-context.js → recipes-context.js} +2 -1
  24. package/dist/lib/{recipe-discovery.d.ts → recipes-discovery.d.ts} +9 -9
  25. package/dist/lib/{recipe-discovery.js → recipes-discovery.js} +17 -17
  26. package/dist/lib/registry.d.ts +1 -1
  27. package/dist/lib/registry.js +22 -22
  28. package/dist/lib/{actor-rooms.d.ts → rooms.d.ts} +4 -4
  29. package/dist/lib/{actor-rooms.js → rooms.js} +3 -3
  30. package/dist/lib/runs-artifacts.d.ts +21 -0
  31. package/dist/lib/runs-artifacts.js +68 -0
  32. package/dist/lib/runs-control.d.ts +13 -0
  33. package/dist/lib/runs-control.js +59 -0
  34. package/dist/lib/runs-identity.d.ts +5 -0
  35. package/dist/lib/runs-identity.js +11 -0
  36. package/dist/lib/runs-index.d.ts +18 -0
  37. package/dist/lib/runs-index.js +119 -0
  38. package/dist/lib/runs-mailbox.d.ts +25 -0
  39. package/dist/lib/runs-mailbox.js +146 -0
  40. package/dist/lib/runs-messages.d.ts +15 -0
  41. package/dist/lib/runs-messages.js +179 -0
  42. package/dist/lib/runs-outbox.d.ts +41 -0
  43. package/dist/lib/runs-outbox.js +87 -0
  44. package/dist/lib/runs-process.d.ts +7 -0
  45. package/dist/lib/runs-process.js +36 -0
  46. package/dist/lib/runs-retention.d.ts +8 -0
  47. package/dist/lib/runs-retention.js +56 -0
  48. package/dist/lib/runs-start.d.ts +9 -0
  49. package/dist/lib/runs-start.js +67 -0
  50. package/dist/lib/runs-status.d.ts +8 -0
  51. package/dist/lib/runs-status.js +60 -0
  52. package/dist/lib/runtime.d.ts +2 -2
  53. package/dist/lib/runtime.js +5 -5
  54. package/dist/lib/schema.d.ts +9 -0
  55. package/dist/lib/schema.js +40 -0
  56. package/dist/lib/tools-access.d.ts +19 -0
  57. package/dist/lib/tools-access.js +44 -0
  58. package/dist/lib/tools-inspect.d.ts +11 -0
  59. package/dist/lib/tools-inspect.js +534 -0
  60. package/dist/lib/tools-local.d.ts +14 -0
  61. package/dist/lib/tools-local.js +156 -0
  62. package/dist/lib/tools-mailbox.d.ts +8 -0
  63. package/dist/lib/tools-mailbox.js +48 -0
  64. package/dist/lib/tools-message.d.ts +9 -0
  65. package/dist/lib/tools-message.js +346 -0
  66. package/dist/lib/tools-register.d.ts +18 -0
  67. package/dist/lib/tools-register.js +40 -0
  68. package/dist/lib/tools-response.d.ts +21 -0
  69. package/dist/lib/tools-response.js +219 -0
  70. package/dist/lib/tools-spawn.d.ts +12 -0
  71. package/dist/lib/tools-spawn.js +159 -0
  72. package/dist/lib/tools.d.ts +13 -39
  73. package/dist/lib/tools.js +11 -1497
  74. package/dist/recipes/utility-coordinator-lock-snapshot.json +1 -1
  75. package/dist/scripts/actor-worker.mjs +186 -7
  76. package/dist/scripts/async-runner.mjs +171 -8
  77. package/dist/scripts/build-dist.mjs +1 -1
  78. package/dist/scripts/conformance.mjs +34 -18
  79. package/dist/scripts/coordinator.mjs +733 -11
  80. package/dist/scripts/locker.mjs +345 -16
  81. package/dist/scripts/recipe-utils.mjs +478 -14
  82. package/dist/scripts/validate-recipe.mjs +110 -10
  83. package/dist/skills/actors/SKILL.md +2 -2
  84. package/dist/skills/swarm/SKILL.md +2 -2
  85. package/docs/recipe-library.md +1 -1
  86. package/docs/template-recipes.md +1 -1
  87. package/docs/tool-registry.md +3 -3
  88. package/index.ts +12 -19
  89. package/lib/async-runs.ts +124 -1016
  90. package/lib/command-templates.ts +3 -3
  91. package/lib/config.ts +7 -7
  92. package/lib/{output.ts → execution-output.ts} +3 -3
  93. package/lib/execution.ts +5 -1
  94. package/lib/{actor-inspector-tui.ts → inspector.ts} +4 -3
  95. package/lib/mailbox-loop.ts +3 -3
  96. package/lib/{actor-messages.ts → messages.ts} +3 -3
  97. package/lib/paths.ts +3 -3
  98. package/lib/{actor-recipe-context.ts → recipes-context.ts} +3 -2
  99. package/lib/{recipe-discovery.ts → recipes-discovery.ts} +30 -30
  100. package/lib/{recipe-usage.ts → recipes-usage.ts} +2 -2
  101. package/lib/registry.ts +37 -24
  102. package/lib/{actor-rooms.ts → rooms.ts} +4 -4
  103. package/lib/runs-artifacts.ts +96 -0
  104. package/lib/runs-control.ts +85 -0
  105. package/lib/runs-identity.ts +14 -0
  106. package/lib/runs-index.ts +156 -0
  107. package/lib/runs-mailbox.ts +208 -0
  108. package/lib/runs-messages.ts +252 -0
  109. package/lib/runs-outbox.ts +144 -0
  110. package/lib/runs-process.ts +51 -0
  111. package/lib/runs-retention.ts +77 -0
  112. package/lib/runs-start.ts +98 -0
  113. package/lib/runs-status.ts +78 -0
  114. package/lib/runtime.ts +6 -6
  115. package/lib/schema.ts +55 -0
  116. package/lib/tools-access.ts +68 -0
  117. package/lib/tools-inspect.ts +735 -0
  118. package/lib/tools-local.ts +234 -0
  119. package/lib/tools-mailbox.ts +56 -0
  120. package/lib/tools-message.ts +483 -0
  121. package/lib/tools-register.ts +64 -0
  122. package/lib/tools-response.ts +267 -0
  123. package/lib/tools-spawn.ts +240 -0
  124. package/lib/tools.ts +26 -2051
  125. package/package.json +1 -1
  126. package/recipes/utility-coordinator-lock-snapshot.json +1 -1
  127. package/scripts/actor-worker.mjs +186 -7
  128. package/scripts/async-runner.mjs +171 -8
  129. package/scripts/build-dist.mjs +1 -1
  130. package/scripts/conformance.mjs +34 -18
  131. package/scripts/coordinator.mjs +733 -11
  132. package/scripts/locker.mjs +345 -16
  133. package/scripts/recipe-utils.mjs +478 -14
  134. package/scripts/validate-recipe.mjs +110 -10
  135. package/skills/actors/SKILL.md +2 -2
  136. package/skills/swarm/SKILL.md +2 -2
  137. package/dist/lib/actor-worker.d.ts +0 -16
  138. package/dist/lib/actor-worker.js +0 -134
  139. package/dist/lib/async-runner.d.ts +0 -5
  140. package/dist/lib/async-runner.js +0 -134
  141. package/dist/lib/conformance.d.ts +0 -12
  142. package/dist/lib/conformance.js +0 -28
  143. package/dist/lib/coordinator.d.ts +0 -5
  144. package/dist/lib/coordinator.js +0 -660
  145. package/dist/lib/locker.d.ts +0 -5
  146. package/dist/lib/locker.js +0 -310
  147. package/dist/lib/recipe-utils.d.ts +0 -5
  148. package/dist/lib/recipe-utils.js +0 -385
  149. package/dist/lib/validate-recipe.d.ts +0 -6
  150. package/dist/lib/validate-recipe.js +0 -104
  151. package/lib/actor-worker.ts +0 -174
  152. package/lib/async-runner.ts +0 -173
  153. package/lib/conformance.ts +0 -46
  154. package/lib/coordinator.ts +0 -742
  155. package/lib/locker.ts +0 -340
  156. package/lib/recipe-utils.ts +0 -486
  157. package/lib/validate-recipe.ts +0 -110
  158. /package/dist/lib/{recipe-references.d.ts → recipes-references.d.ts} +0 -0
  159. /package/dist/lib/{recipe-references.js → recipes-references.js} +0 -0
  160. /package/dist/lib/{recipe-usage.d.ts → recipes-usage.d.ts} +0 -0
  161. /package/dist/lib/{recipe-usage.js → recipes-usage.js} +0 -0
  162. /package/lib/{recipe-references.ts → recipes-references.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -9,5 +9,5 @@
9
9
  "state_dir": "~/.pi/agent/tmp/pi-actors/runs/coordinator-locker",
10
10
  "lines": "20"
11
11
  },
12
- "template": "{repo}/scripts/coordinator-locker.mjs snapshot --state-dir {state_dir} --lines {lines}"
12
+ "template": "{repo}/scripts/locker.mjs snapshot --state-dir {state_dir} --lines {lines}"
13
13
  }
@@ -3,11 +3,11 @@
3
3
  /**
4
4
  * Canonical mailbox-backed actor worker demo.
5
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.
6
+ * Owns the worker argument parsing and control loop directly. Reusable room and
7
+ * mailbox primitives stay in lib/; this script remains the public executable.
8
8
  */
9
9
 
10
- import { existsSync } from "node:fs";
10
+ import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
11
11
  import { dirname, join } from "node:path";
12
12
  import { fileURLToPath, pathToFileURL } from "node:url";
13
13
 
@@ -15,13 +15,192 @@ function packageRoot() {
15
15
  return dirname(dirname(fileURLToPath(import.meta.url)));
16
16
  }
17
17
 
18
- function mainModulePath() {
18
+ async function importRuntimeModule(name) {
19
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");
20
+ const compiled = join(root, "dist", "lib", `${name}.js`);
21
+ const source = join(root, "lib", `${name}.ts`);
22
+ return await import(
23
+ pathToFileURL(existsSync(compiled) ? compiled : source).href
24
+ );
22
25
  }
23
26
 
24
- const { runActorWorker } = await import(pathToFileURL(mainModulePath()).href);
27
+ const { appendRoomMessage, ensureRoomMember, readBranchInboxMessages } =
28
+ await importRuntimeModule("rooms");
29
+ const { handleMailboxLoopOnce, isMailboxLoopStopMessage } =
30
+ await importRuntimeModule("mailbox-loop");
31
+
32
+ export function parseActorWorkerArgs(argv) {
33
+ const args = {};
34
+ for (let index = 0; index < argv.length; index += 1) {
35
+ const token = argv[index];
36
+ if (!token.startsWith("--")) continue;
37
+ const key = token.slice(2).replaceAll("-", "_");
38
+ const next = argv[index + 1];
39
+ if (!next || next.startsWith("--")) {
40
+ args[key] = true;
41
+ continue;
42
+ }
43
+ args[key] = next;
44
+ index += 1;
45
+ }
46
+ return args;
47
+ }
48
+
49
+ function sleep(ms) {
50
+ return new Promise((resolve) => setTimeout(resolve, ms));
51
+ }
52
+
53
+ function text(value) {
54
+ return typeof value === "string" ? value : JSON.stringify(value);
55
+ }
56
+
57
+ function enabled(value) {
58
+ return value !== false && value !== "false" && value !== "0";
59
+ }
60
+
61
+ function safeName(value) {
62
+ return String(value ?? "unknown").replaceAll(/[^A-Za-z0-9_.-]+/g, "_");
63
+ }
64
+
65
+ export async function runActorWorker(argv = process.argv.slice(2)) {
66
+ const args = parseActorWorkerArgs(argv);
67
+ const stateDir = String(args.state_dir ?? "");
68
+ const run = String(args.run ?? "");
69
+ const branch = String(args.branch ?? "worker");
70
+ const pollMs = Math.max(50, Number(args.poll_ms ?? 1000));
71
+ const staleClaimMs = Math.max(0, Number(args.stale_claim_ms ?? 0));
72
+ const artifactDir = String(
73
+ args.artifact_dir ?? join(stateDir, "worker-artifacts"),
74
+ );
75
+ const writeArtifacts = enabled(args.write_artifacts ?? true);
76
+
77
+ if (!stateDir || !run) {
78
+ throw new Error(
79
+ "usage: actor-worker.mjs --state-dir <dir> --run <id> [--branch worker] [--poll-ms 1000]",
80
+ );
81
+ }
82
+
83
+ const branchAddress = `branch:${run}/${branch}`;
84
+ const roomAddress = `room:${run}`;
85
+ const journalPath = join(stateDir, "worker-events.jsonl");
86
+ const statusPath = join(stateDir, "worker-status.json");
87
+ if (writeArtifacts) mkdirSync(artifactDir, { recursive: true });
88
+
89
+ function journal(event, data = {}) {
90
+ appendFileSync(
91
+ journalPath,
92
+ `${JSON.stringify({ event, ts: new Date().toISOString(), ...data })}\n`,
93
+ );
94
+ }
95
+
96
+ function staleClaims() {
97
+ if (staleClaimMs <= 0) return 0;
98
+ const cutoff = Date.now() - staleClaimMs;
99
+ return readBranchInboxMessages(stateDir, run, branchAddress).filter(
100
+ (record) => {
101
+ if (record.status !== "claimed" || !record.claimed_at) return false;
102
+ const claimedAt = Date.parse(record.claimed_at);
103
+ return Number.isFinite(claimedAt) && claimedAt < cutoff;
104
+ },
105
+ ).length;
106
+ }
107
+
108
+ function status(state, data = {}) {
109
+ writeFileSync(
110
+ statusPath,
111
+ `${JSON.stringify({ artifact_dir: artifactDir, branch, run, stale_claims: staleClaims(), state, ts: new Date().toISOString(), ...data }, null, 2)}\n`,
112
+ );
113
+ }
114
+
115
+ function writeResultArtifact(id, result) {
116
+ if (!writeArtifacts) return undefined;
117
+ const path = join(artifactDir, `${safeName(id)}.txt`);
118
+ writeFileSync(path, `${result}\n`);
119
+ return path;
120
+ }
121
+
122
+ function room(type, summary, body = {}) {
123
+ appendRoomMessage(stateDir, "main", {
124
+ body,
125
+ from: branchAddress,
126
+ summary,
127
+ to: roomAddress,
128
+ type,
129
+ });
130
+ }
131
+
132
+ ensureRoomMember(
133
+ stateDir,
134
+ run,
135
+ "main",
136
+ branchAddress,
137
+ { display: branch, role: "worker", status: "present" },
138
+ `${branch} joined as mailbox worker`,
139
+ );
140
+ room("awaiting_assignment", `${branch} awaiting assignment`, { branch });
141
+ journal("worker.started", {
142
+ artifact_dir: artifactDir,
143
+ branch,
144
+ run,
145
+ stale_claim_ms: staleClaimMs,
146
+ });
147
+ status("awaiting_assignment", { handled: 0, stale_claim_ms: staleClaimMs });
148
+
149
+ let handled = 0;
150
+ let stopping = false;
151
+ while (!stopping) {
152
+ const result = await handleMailboxLoopOnce(
153
+ { address: branchAddress, kind: "branch", run, stateDir },
154
+ (message) => {
155
+ if (isMailboxLoopStopMessage(message)) {
156
+ stopping = true;
157
+ room("actor.leave", `${branch} stopping`, {
158
+ branch,
159
+ reason: message.type,
160
+ });
161
+ journal("worker.stopping", { id: message.id, type: message.type });
162
+ status("stopping", { handled, reason: message.type });
163
+ return;
164
+ }
165
+ room("task.claim", `${branch} claimed ${message.type}`, {
166
+ branch,
167
+ id: message.id,
168
+ type: message.type,
169
+ });
170
+ const result = text(message.body);
171
+ const artifact = writeResultArtifact(message.id, result);
172
+ handled += 1;
173
+ room("task.result", `${branch} handled ${message.type}`, {
174
+ artifact,
175
+ branch,
176
+ id: message.id,
177
+ result,
178
+ type: message.type,
179
+ });
180
+ room("awaiting_assignment", `${branch} awaiting assignment`, {
181
+ branch,
182
+ });
183
+ journal("task.handled", {
184
+ artifact,
185
+ id: message.id,
186
+ type: message.type,
187
+ });
188
+ status("awaiting_assignment", {
189
+ handled,
190
+ last_artifact: artifact,
191
+ last_id: message.id,
192
+ last_type: message.type,
193
+ stale_claim_ms: staleClaimMs,
194
+ });
195
+ },
196
+ { owner: branchAddress },
197
+ );
198
+ if (!result.handled) await sleep(pollMs);
199
+ }
200
+
201
+ journal("worker.done", { branch, handled, run });
202
+ status("done", { handled });
203
+ }
25
204
 
26
205
  try {
27
206
  await runActorWorker(process.argv.slice(2));
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Detached async-runner shim.
4
+ * Detached async-runner executable.
5
5
  *
6
- * Runtime logic lives in lib/async-runner.ts and is compiled to
7
- * dist/lib/async-runner.js for installed JS-only packages.
6
+ * Owns the run child-process control loop directly. Reusable runtime
7
+ * primitives stay in lib/; this script should remain understandable without
8
+ * chasing a one-off lib entrypoint domain.
8
9
  */
9
10
 
10
- import { existsSync } from "node:fs";
11
+ import { appendFileSync, existsSync, readFileSync } from "node:fs";
11
12
  import { dirname, join } from "node:path";
12
13
  import { fileURLToPath, pathToFileURL } from "node:url";
13
14
 
@@ -15,13 +16,175 @@ function packageRoot() {
15
16
  return dirname(dirname(fileURLToPath(import.meta.url)));
16
17
  }
17
18
 
18
- function mainModulePath() {
19
+ async function importRuntimeModule(name) {
19
20
  const root = packageRoot();
20
- const compiled = join(root, "dist", "lib", "async-runner.js");
21
- return existsSync(compiled) ? compiled : join(root, "lib", "async-runner.ts");
21
+ const compiled = join(root, "dist", "lib", `${name}.js`);
22
+ const source = join(root, "lib", `${name}.ts`);
23
+ return await import(
24
+ pathToFileURL(existsSync(compiled) ? compiled : source).href
25
+ );
22
26
  }
23
27
 
24
- const { runAsyncRunner } = await import(pathToFileURL(mainModulePath()).href);
28
+ const { appendRecipeContextToPiArgs } =
29
+ await importRuntimeModule("recipes-context");
30
+ const { execCommandTemplate } = await importRuntimeModule("command-templates");
31
+ const { executeRegisteredTool } = await importRuntimeModule("execution");
32
+ const { writeJsonAtomic } = await importRuntimeModule("file-state");
33
+
34
+ function quoteCommandDetailPart(value) {
35
+ if (value === "") return "''";
36
+ if (/^[A-Za-z0-9_/:=.,@%+\-]+$/.test(value)) return value;
37
+ return `'${String(value).replaceAll("'", "'\\''")}'`;
38
+ }
39
+
40
+ function formatCommandDetail(command, args) {
41
+ return [command, ...args].map(quoteCommandDetailPart).join(" ");
42
+ }
43
+
44
+ function summarizeCommandDetail(commandDetail) {
45
+ return commandDetail.length > 160
46
+ ? `${commandDetail.slice(0, 157)}...`
47
+ : commandDetail;
48
+ }
49
+
50
+ export async function runAsyncRunner(stateDir = process.argv[2]) {
51
+ if (!stateDir) throw new Error("missing state dir");
52
+
53
+ const runPath = join(stateDir, "run.json");
54
+ const progressPath = join(stateDir, "progress.json");
55
+ const resultPath = join(stateDir, "result.json");
56
+ const eventsPath = join(stateDir, "events.jsonl");
57
+ const outboxPath = join(stateDir, "outbox.jsonl");
58
+ const stdoutPath = join(stateDir, "stdout.log");
59
+ const stderrPath = join(stateDir, "stderr.log");
60
+ const meta = JSON.parse(readFileSync(runPath, "utf8"));
61
+
62
+ function event(name, data = {}) {
63
+ appendFileSync(
64
+ eventsPath,
65
+ `${JSON.stringify({ event: name, ts: new Date().toISOString(), ...data })}\n`,
66
+ );
67
+ }
68
+
69
+ function outbox(name, summary, data = {}, delivery = "log", level = "info") {
70
+ appendFileSync(
71
+ outboxPath,
72
+ `${JSON.stringify({ body: data, data, delivery, event: name, from: `run:${meta.run}`, level, summary, to: "coordinator", ts: new Date().toISOString(), type: name })}\n`,
73
+ );
74
+ }
75
+
76
+ function progress(phase, extra = {}) {
77
+ writeJsonAtomic(progressPath, {
78
+ phase,
79
+ updatedAt: new Date().toISOString(),
80
+ ...extra,
81
+ });
82
+ }
83
+
84
+ let activeSubagents = 0;
85
+ let completedSubagents = 0;
86
+ const subagentFailures = [];
87
+
88
+ function getCommandDoneDelivery(result) {
89
+ return result.code !== 0 || activeSubagents > 0 ? "followup" : "log";
90
+ }
91
+
92
+ function progressRunning() {
93
+ progress("running", {
94
+ activeSubagents,
95
+ completed: completedSubagents,
96
+ failures: subagentFailures,
97
+ });
98
+ }
99
+
100
+ async function observedExec(command, args, options) {
101
+ const commandDetail = formatCommandDetail(command, args);
102
+ const execArgs = appendRecipeContextToPiArgs(
103
+ command,
104
+ args,
105
+ meta.recipe_context_records,
106
+ options?.actorRecipeContext,
107
+ );
108
+ activeSubagents += 1;
109
+ event("command.start", { activeSubagents, command: commandDetail });
110
+ progressRunning();
111
+ const result = await execCommandTemplate(command, execArgs, options);
112
+ activeSubagents = Math.max(0, activeSubagents - 1);
113
+ completedSubagents += 1;
114
+ if (result.code !== 0) {
115
+ subagentFailures.push({
116
+ code: result.code,
117
+ command: commandDetail,
118
+ killed: result.killed,
119
+ });
120
+ }
121
+ event("command.done", {
122
+ activeSubagents,
123
+ code: result.code,
124
+ command: commandDetail,
125
+ killed: result.killed,
126
+ });
127
+ outbox(
128
+ "command.done",
129
+ `Command ${summarizeCommandDetail(commandDetail)} completed with code ${result.code}`,
130
+ {
131
+ activeSubagents,
132
+ ...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
133
+ run_files: [stdoutPath, stderrPath, resultPath, eventsPath, outboxPath],
134
+ code: result.code,
135
+ command: commandDetail,
136
+ killed: result.killed,
137
+ },
138
+ getCommandDoneDelivery(result),
139
+ result.code === 0 ? "info" : "error",
140
+ );
141
+ progressRunning();
142
+ return result;
143
+ }
144
+
145
+ try {
146
+ event("run.runner.start", { pid: process.pid });
147
+ progressRunning();
148
+ const result = await executeRegisteredTool(
149
+ {
150
+ name: "run_actor",
151
+ description: "Detached command-template run actor",
152
+ template: meta.template,
153
+ args: [],
154
+ defaults: {},
155
+ },
156
+ meta.values || {},
157
+ observedExec,
158
+ meta.cwd,
159
+ );
160
+ const text = result.content?.[0]?.text || "";
161
+ appendFileSync(stdoutPath, text);
162
+ writeJsonAtomic(resultPath, {
163
+ code: result.details.code,
164
+ command: result.details.command,
165
+ killed: result.details.killed,
166
+ truncated: result.details.truncated,
167
+ completedAt: new Date().toISOString(),
168
+ });
169
+ progress("done", {
170
+ completed: 1,
171
+ failures: result.details.nonCriticalFailures || [],
172
+ });
173
+ event("run.done", { code: result.details.code });
174
+ } catch (error) {
175
+ const message = error instanceof Error ? error.message : String(error);
176
+ appendFileSync(stderrPath, `${message}\n`);
177
+ writeJsonAtomic(resultPath, {
178
+ code: 1,
179
+ error: message,
180
+ killed: false,
181
+ completedAt: new Date().toISOString(),
182
+ });
183
+ progress("failed", { completed: 0, failures: [{ message }] });
184
+ event("run.failed", { error: message });
185
+ throw error;
186
+ }
187
+ }
25
188
 
26
189
  try {
27
190
  await runAsyncRunner(process.argv[2]);
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * This is intentionally standalone: it is package/build glue, not reusable
7
7
  * actor-domain behavior. It cleans dist, compiles TypeScript, mirrors runtime
8
- * assets, and syntax-checks packaged script shims.
8
+ * assets, and syntax-checks packaged scripts.
9
9
  */
10
10
 
11
11
  import { spawnSync } from "node:child_process";
@@ -1,33 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Internal conformance runner shim.
4
+ * Internal conformance runner.
5
5
  *
6
- * Runtime logic lives in lib/conformance.ts and is compiled to
7
- * dist/lib/conformance.js for installed JS-only packages.
6
+ * This script is intentionally standalone package/release glue rather than a
7
+ * lib domain: it only selects regression suites and formats their summary.
8
8
  */
9
9
 
10
- import { existsSync } from "node:fs";
11
- import { dirname, join } from "node:path";
12
- import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { spawnSync } from "node:child_process";
11
+ import { dirname } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ const conformanceSuites = [
15
+ "tests/protocol-examples.test.ts",
16
+ "tests/recipes-discovery.test.ts",
17
+ "tests/registry.test.ts",
18
+ "tests/runtime.test.ts",
19
+ "tests/async-runs.test.ts",
20
+ "tests/rooms.test.ts",
21
+ "tests/tools.test.ts",
22
+ ];
13
23
 
14
24
  function packageRoot() {
15
25
  return dirname(dirname(fileURLToPath(import.meta.url)));
16
26
  }
17
27
 
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
- }
28
+ const result = spawnSync(
29
+ process.execPath,
30
+ ["--experimental-strip-types", "--test", ...conformanceSuites],
31
+ { cwd: packageRoot(), encoding: "utf8", stdio: "pipe" },
32
+ );
23
33
 
24
- const { runConformance } = await import(pathToFileURL(mainModulePath()).href);
25
- const report = runConformance(packageRoot());
34
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
35
+ const summary = output
36
+ .split("\n")
37
+ .filter((line) =>
38
+ /^ℹ (tests|pass|fail|cancelled|skipped|todo|duration_ms) /.test(line),
39
+ )
40
+ .join("\n");
26
41
 
27
42
  console.log("pi-actors conformance");
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);
43
+ console.log(`suites ${conformanceSuites.length}`);
44
+
45
+ if (summary) console.log(summary);
46
+ if ((result.status ?? 1) !== 0) {
47
+ console.error(output.trimEnd());
48
+ process.exit(result.status ?? 1);
33
49
  }