@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
@@ -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.22.5",
3
+ "version": "0.24.0",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -26,10 +26,11 @@
26
26
  },
27
27
  "scripts": {
28
28
  "check": "node --experimental-strip-types -e \"await import('./index.ts'); console.log('pi-actors: extension import ok')\"",
29
+ "conformance": "node scripts/conformance.mjs",
29
30
  "test": "node --experimental-strip-types --test tests/*.test.ts",
30
31
  "pack:dry": "npm pack --dry-run",
31
32
  "validate": "npx tsc --noEmit && npm run build && npm run check && npm test && npm run pack:dry",
32
- "build": "tsc -p tsconfig.build.json",
33
+ "build": "node scripts/build-dist.mjs",
33
34
  "prepack": "npm run build"
34
35
  },
35
36
  "files": [
@@ -44,13 +45,21 @@
44
45
  "BACKLOG.md",
45
46
  "CHANGELOG.md",
46
47
  "docs",
48
+ "fixtures",
47
49
  "banner.jpg"
48
50
  ],
49
51
  "pi": {
50
52
  "extensions": [
51
53
  "./dist/index.js"
52
54
  ],
55
+ "sourceExtensions": [
56
+ "./index.ts"
57
+ ],
53
58
  "skills": [
59
+ "./dist/skills/actors/SKILL.md",
60
+ "./dist/skills/swarm/SKILL.md"
61
+ ],
62
+ "sourceSkills": [
54
63
  "./skills/actors/SKILL.md",
55
64
  "./skills/swarm/SKILL.md"
56
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,215 +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`);
20
+ const compiled = join(root, "dist", "lib", "async-runner.js");
21
+ return existsSync(compiled) ? compiled : join(root, "lib", "async-runner.ts");
36
22
  }
37
23
 
38
- async function importLib(name) {
39
- return import(pathToFileURL(libModulePath(name)).href);
40
- }
41
-
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"));
24
+ const { runAsyncRunner } = await import(pathToFileURL(mainModulePath()).href);
54
25
 
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
- function quoteCommandDetailPart(value) {
68
- if (value === "") return "''";
69
- if (/^[A-Za-z0-9_/:=.,@%+\-]+$/.test(value)) return value;
70
- return `'${String(value).replaceAll("'", "'\\''")}'`;
71
- }
72
- function formatCommandDetail(command, args) {
73
- return [command, ...args].map(quoteCommandDetailPart).join(" ");
74
- }
75
- function summarizeCommandDetail(commandDetail) {
76
- return commandDetail.length > 160
77
- ? `${commandDetail.slice(0, 157)}...`
78
- : commandDetail;
79
- }
80
- function getCommandDoneDelivery(result) {
81
- return result.code !== 0 || activeSubagents > 0 ? "followup" : "log";
82
- }
83
- function outbox(name, summary, data = {}, delivery = "log", level = "info") {
84
- appendFileSync(
85
- outboxPath,
86
- `${JSON.stringify({ body: data, data, delivery, event: name, from: `run:${meta.run}`, level, summary, to: "coordinator", ts: new Date().toISOString(), type: name })}\n`,
87
- );
88
- }
89
- /**
90
- * Writes the compact status snapshot.
91
- *
92
- * progress.json is rewritten atomically because observers may read it
93
- * concurrently.
94
- */
95
- function progress(phase, extra = {}) {
96
- writeJsonAtomic(progressPath, {
97
- phase,
98
- updatedAt: new Date().toISOString(),
99
- ...extra,
100
- });
101
- }
102
- let activeSubagents = 0;
103
- let completedSubagents = 0;
104
- const subagentFailures = [];
105
- function progressRunning() {
106
- progress("running", {
107
- activeSubagents,
108
- completed: completedSubagents,
109
- failures: subagentFailures,
110
- });
111
- }
112
- async function observedExec(command, args, options) {
113
- const commandDetail = formatCommandDetail(command, args);
114
- const execArgs = appendRecipeContextToPiArgs(
115
- command,
116
- args,
117
- meta.recipe_context_records,
118
- options?.actorRecipeContext,
119
- );
120
- activeSubagents += 1;
121
- event("command.start", { activeSubagents, command: commandDetail });
122
- progressRunning();
123
- const result = await execCommandTemplate(command, execArgs, options);
124
- activeSubagents = Math.max(0, activeSubagents - 1);
125
- completedSubagents += 1;
126
- if (result.code !== 0) {
127
- subagentFailures.push({
128
- code: result.code,
129
- command: commandDetail,
130
- killed: result.killed,
131
- });
132
- }
133
- event("command.done", {
134
- activeSubagents,
135
- code: result.code,
136
- command: commandDetail,
137
- killed: result.killed,
138
- });
139
- outbox(
140
- "command.done",
141
- `Command ${summarizeCommandDetail(commandDetail)} completed with code ${result.code}`,
142
- {
143
- activeSubagents,
144
- ...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
145
- run_files: [stdoutPath, stderrPath, resultPath, eventsPath, outboxPath],
146
- code: result.code,
147
- command: commandDetail,
148
- killed: result.killed,
149
- },
150
- getCommandDoneDelivery(result),
151
- result.code === 0 ? "info" : "error",
152
- );
153
- progressRunning();
154
- return result;
155
- }
156
26
  try {
157
- event("run.runner.start", { pid: process.pid });
158
- progressRunning();
159
- /**
160
- * Reuse the same registered-tool execution path as foreground tools.
161
- *
162
- * The synthetic tool name is only for reporting; meta.template is the real
163
- * work.
164
- */
165
- const result = await executeRegisteredTool(
166
- {
167
- name: "run_actor",
168
- description: "Detached command-template run actor",
169
- template: meta.template,
170
- args: [],
171
- defaults: {},
172
- },
173
- meta.values || {},
174
- observedExec,
175
- meta.cwd,
176
- );
177
- /**
178
- * Tool output is already formatted/truncated by the shared execution layer.
179
- * The runner persists that agent-facing text for later tail/status analysis.
180
- */
181
- const text = result.content?.[0]?.text || "";
182
- appendFileSync(stdoutPath, text);
183
- writeJsonAtomic(resultPath, {
184
- code: result.details.code,
185
- command: result.details.command,
186
- killed: result.details.killed,
187
- truncated: result.details.truncated,
188
- completedAt: new Date().toISOString(),
189
- });
190
- progress("done", {
191
- completed: 1,
192
- failures: result.details.nonCriticalFailures || [],
193
- });
194
- event("run.done", { code: result.details.code });
27
+ await runAsyncRunner(process.argv[2]);
195
28
  } catch (error) {
196
- /**
197
- * Represent failure in every observable channel:
198
- *
199
- * - stderr.log for humans
200
- * - result.json for structured readers
201
- * - progress.json for status
202
- * - events.jsonl for tail/watch flows
203
- */
204
- const message = error instanceof Error ? error.message : String(error);
205
- appendFileSync(stderrPath, `${message}\n`);
206
- writeJsonAtomic(resultPath, {
207
- code: 1,
208
- error: message,
209
- killed: false,
210
- completedAt: new Date().toISOString(),
211
- });
212
- progress("failed", { completed: 0, failures: [{ message }] });
213
- event("run.failed", { error: message });
29
+ console.error(error instanceof Error ? error.message : String(error));
214
30
  process.exit(1);
215
31
  }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Build the JavaScript-only distributive tree shim.
5
+ *
6
+ * Runtime logic lives in lib/build-dist.ts and is compiled to
7
+ * dist/lib/build-dist.js when the package is built.
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", "build-dist.js");
21
+ return existsSync(compiled) ? compiled : join(root, "lib", "build-dist.ts");
22
+ }
23
+
24
+ const { buildDist } = await import(pathToFileURL(mainModulePath()).href);
25
+ buildDist();
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Internal conformance runner shim.
5
+ *
6
+ * Runtime logic lives in lib/conformance.ts and is compiled to
7
+ * dist/lib/conformance.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", "conformance.js");
21
+ return existsSync(compiled) ? compiled : join(root, "lib", "conformance.ts");
22
+ }
23
+
24
+ const { runConformance } = await import(pathToFileURL(mainModulePath()).href);
25
+ const report = runConformance(packageRoot());
26
+
27
+ 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);
33
+ }