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