@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.
- package/AGENTS.md +125 -60
- package/BACKLOG.md +133 -176
- package/CHANGELOG.md +27 -0
- package/README.md +13 -0
- package/dist/fixtures/protocol/actor-message-branch.json +13 -0
- package/dist/fixtures/protocol/artifact-manifest.json +9 -0
- package/dist/fixtures/protocol/mailbox-contract.json +15 -0
- package/dist/fixtures/protocol/recipe-summary.json +16 -0
- package/dist/fixtures/protocol/room-message.json +11 -0
- package/dist/fixtures/protocol/room-roster.json +11 -0
- package/dist/fixtures/protocol/run-inbox-message.json +9 -0
- package/dist/fixtures/protocol/run-outbox-event.json +9 -0
- package/dist/fixtures/protocol/run-state.json +10 -0
- package/dist/index.js +3 -2
- package/dist/lib/actor-inspector-tui.js +5 -32
- package/dist/lib/actor-rooms.d.ts +8 -0
- package/dist/lib/actor-rooms.js +64 -20
- package/dist/lib/actor-worker.d.ts +13 -0
- package/dist/lib/actor-worker.js +86 -0
- package/dist/lib/async-runner.d.ts +5 -0
- package/dist/lib/async-runner.js +134 -0
- package/dist/lib/async-runs.js +9 -28
- package/dist/lib/conformance.d.ts +12 -0
- package/dist/lib/conformance.js +28 -0
- package/dist/lib/coordinator.d.ts +5 -0
- package/dist/lib/coordinator.js +574 -0
- package/dist/lib/locker.d.ts +5 -0
- package/dist/lib/locker.js +310 -0
- package/dist/lib/mailbox-loop.d.ts +41 -0
- package/dist/lib/mailbox-loop.js +62 -0
- package/dist/lib/observability.d.ts +2 -2
- package/dist/lib/observability.js +51 -53
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/recipe-references.js +25 -2
- package/dist/lib/recipe-utils.d.ts +5 -0
- package/dist/lib/recipe-utils.js +385 -0
- package/dist/lib/runtime-notifier.js +3 -7
- package/dist/lib/state-readers.d.ts +21 -0
- package/dist/lib/state-readers.js +74 -0
- package/dist/lib/tools.js +1 -1
- package/dist/lib/validate-recipe.d.ts +6 -0
- package/dist/lib/validate-recipe.js +104 -0
- package/dist/recipes/actor-worker.json +35 -0
- package/dist/recipes/coordinator-locker.json +45 -0
- package/dist/recipes/lens-swarm.json +66 -0
- package/dist/recipes/locker.json +45 -0
- package/dist/recipes/music-player.json +38 -0
- package/dist/recipes/pipeline-architect-coordinator.json +95 -0
- package/dist/recipes/pipeline-artifact-bundle.json +100 -0
- package/dist/recipes/pipeline-artifact-report.json +58 -0
- package/dist/recipes/pipeline-artifact-write.json +72 -0
- package/dist/recipes/pipeline-async-run-ops.json +70 -0
- package/dist/recipes/pipeline-checkpoint-continuation.json +67 -0
- package/dist/recipes/pipeline-development-tasking.json +81 -0
- package/dist/recipes/pipeline-docs-maintenance.json +80 -0
- package/dist/recipes/pipeline-media-library.json +59 -0
- package/dist/recipes/pipeline-quorum-review.json +79 -0
- package/dist/recipes/pipeline-release-readiness.json +110 -0
- package/dist/recipes/pipeline-release-summary.json +88 -0
- package/dist/recipes/pipeline-repo-health.json +89 -0
- package/dist/recipes/pipeline-research-synthesis.json +94 -0
- package/dist/recipes/pipeline-review-readiness.json +54 -0
- package/dist/recipes/pipeline-room-swarm.json +50 -0
- package/dist/recipes/subagent-artifact.json +32 -0
- package/dist/recipes/subagent-checkpoint.json +33 -0
- package/dist/recipes/subagent-conflict-report.json +32 -0
- package/dist/recipes/subagent-contradiction-map.json +33 -0
- package/dist/recipes/subagent-critic.json +35 -0
- package/dist/recipes/subagent-evidence-map.json +33 -0
- package/dist/recipes/subagent-followup.json +33 -0
- package/dist/recipes/subagent-judge.json +33 -0
- package/dist/recipes/subagent-merge.json +33 -0
- package/dist/recipes/subagent-message.json +34 -0
- package/dist/recipes/subagent-normalize.json +31 -0
- package/dist/recipes/subagent-plan.json +33 -0
- package/dist/recipes/subagent-prompt.json +28 -0
- package/dist/recipes/subagent-quorum.json +43 -0
- package/dist/recipes/subagent-review-coordinator.json +114 -0
- package/dist/recipes/subagent-review.json +37 -0
- package/dist/recipes/subagent-task-card.json +35 -0
- package/dist/recipes/subagent-tools.json +27 -0
- package/dist/recipes/subagent-verify.json +34 -0
- package/dist/recipes/subagents-prompts.json +51 -0
- package/dist/recipes/utility-actor-message.json +23 -0
- package/dist/recipes/utility-artifact-manifest.json +16 -0
- package/dist/recipes/utility-artifact-write.json +16 -0
- package/dist/recipes/utility-changelog-head.json +11 -0
- package/dist/recipes/utility-changelog-section.json +13 -0
- package/dist/recipes/utility-coordinator-lock-snapshot.json +13 -0
- package/dist/recipes/utility-git-log.json +11 -0
- package/dist/recipes/utility-git-status.json +9 -0
- package/dist/recipes/utility-jsonl-tail.json +10 -0
- package/dist/recipes/utility-markdown-index.json +14 -0
- package/dist/recipes/utility-package-summary.json +11 -0
- package/dist/recipes/utility-playlist-build.json +17 -0
- package/dist/recipes/utility-playlist-scan.json +11 -0
- package/dist/recipes/utility-run-ops-snapshot.json +17 -0
- package/dist/recipes/utility-run-state-files.json +13 -0
- package/dist/recipes/utility-run-summary.json +11 -0
- package/dist/recipes/utility-skill-summary.json +13 -0
- package/dist/recipes/utility-validate-recipe.json +13 -0
- package/dist/recipes/utility-validation-wrapper.json +13 -0
- package/dist/scripts/actor-worker.mjs +31 -0
- package/dist/scripts/async-runner.mjs +31 -0
- package/dist/scripts/build-dist.mjs +33 -0
- package/dist/scripts/conformance.mjs +33 -0
- package/dist/scripts/coordinator.mjs +31 -0
- package/dist/scripts/locker.mjs +33 -0
- package/dist/scripts/music-player.mjs +964 -0
- package/dist/scripts/recipe-utils.mjs +31 -0
- package/dist/scripts/validate-recipe.mjs +34 -0
- package/dist/skills/actors/SKILL.md +377 -0
- package/dist/skills/swarm/SKILL.md +467 -0
- package/dist/skills/swarm/references/development-swarm.md +596 -0
- package/docs/actor-messages.md +2 -2
- package/docs/async-runs.md +11 -0
- package/docs/template-recipes.md +1 -1
- package/fixtures/protocol/actor-message-branch.json +13 -0
- package/fixtures/protocol/artifact-manifest.json +9 -0
- package/fixtures/protocol/mailbox-contract.json +15 -0
- package/fixtures/protocol/recipe-summary.json +16 -0
- package/fixtures/protocol/room-message.json +11 -0
- package/fixtures/protocol/room-roster.json +11 -0
- package/fixtures/protocol/run-inbox-message.json +9 -0
- package/fixtures/protocol/run-outbox-event.json +9 -0
- package/fixtures/protocol/run-state.json +10 -0
- package/index.ts +3 -0
- package/lib/actor-inspector-tui.ts +11 -34
- package/lib/actor-rooms.ts +88 -18
- package/lib/actor-worker.ts +118 -0
- package/lib/async-runner.ts +173 -0
- package/lib/async-runs.ts +12 -22
- package/lib/conformance.ts +46 -0
- package/lib/coordinator.ts +664 -0
- package/lib/locker.ts +340 -0
- package/lib/mailbox-loop.ts +148 -0
- package/lib/observability.ts +24 -19
- package/lib/prompts.ts +1 -1
- package/lib/recipe-references.ts +37 -2
- package/lib/recipe-utils.ts +486 -0
- package/lib/runtime-notifier.ts +4 -6
- package/lib/state-readers.ts +93 -0
- package/lib/tools.ts +1 -1
- package/lib/validate-recipe.ts +110 -0
- package/package.json +10 -2
- package/recipes/actor-worker.json +35 -0
- package/recipes/pipeline-quorum-review.json +12 -7
- package/scripts/actor-worker.mjs +31 -0
- package/scripts/async-runner.mjs +11 -201
- package/scripts/build-dist.mjs +33 -0
- package/scripts/conformance.mjs +21 -35
- package/scripts/coordinator.mjs +15 -625
- package/scripts/locker.mjs +20 -332
- package/scripts/recipe-utils.mjs +17 -477
- package/scripts/validate-recipe.mjs +18 -121
- package/skills/actors/SKILL.md +8 -3
- 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.
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
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
|
+
}
|
package/scripts/async-runner.mjs
CHANGED
|
@@ -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
|
-
*
|
|
7
|
-
*
|
|
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 {
|
|
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(
|
|
15
|
+
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
30
16
|
}
|
|
31
17
|
|
|
32
|
-
function
|
|
18
|
+
function mainModulePath() {
|
|
33
19
|
const root = packageRoot();
|
|
34
|
-
const compiled = join(root, "dist", "lib",
|
|
35
|
-
return existsSync(compiled) ? compiled : join(root, "lib",
|
|
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 {
|
|
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
|
-
|
|
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]);
|
package/scripts/conformance.mjs
CHANGED
|
@@ -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
|
-
*
|
|
7
|
-
*
|
|
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 {
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
34
|
-
const
|
|
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
|
|
43
|
-
if (summary) console.log(summary);
|
|
44
|
-
if (
|
|
45
|
-
console.error(output.trimEnd());
|
|
46
|
-
process.exit(
|
|
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
|
}
|