@llblab/pi-actors 0.33.0 → 0.34.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 +22 -11
- package/BACKLOG.md +3 -34
- package/CHANGELOG.md +14 -0
- package/dist/index.js +10 -10
- package/dist/lib/async-runs.d.ts +23 -75
- package/dist/lib/async-runs.js +49 -801
- package/dist/lib/command-templates.d.ts +3 -3
- package/dist/lib/command-templates.js +3 -3
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/config.js +5 -5
- package/dist/lib/{output.d.ts → execution-output.d.ts} +3 -3
- package/dist/lib/{output.js → execution-output.js} +3 -3
- package/dist/lib/execution.js +1 -1
- package/dist/lib/{actor-inspector-tui.d.ts → inspector.d.ts} +3 -2
- package/dist/lib/{actor-inspector-tui.js → inspector.js} +3 -2
- package/dist/lib/mailbox-loop.d.ts +3 -3
- package/dist/lib/mailbox-loop.js +3 -3
- package/dist/lib/{actor-messages.d.ts → messages.d.ts} +3 -3
- package/dist/lib/{actor-messages.js → messages.js} +3 -3
- package/dist/lib/{actor-recipe-context.d.ts → recipes-context.d.ts} +3 -2
- package/dist/lib/{actor-recipe-context.js → recipes-context.js} +2 -1
- package/dist/lib/{recipe-discovery.d.ts → recipes-discovery.d.ts} +8 -8
- package/dist/lib/{recipe-discovery.js → recipes-discovery.js} +6 -6
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +22 -22
- package/dist/lib/{actor-rooms.d.ts → rooms.d.ts} +4 -4
- package/dist/lib/{actor-rooms.js → rooms.js} +3 -3
- package/dist/lib/runs-artifacts.d.ts +21 -0
- package/dist/lib/runs-artifacts.js +68 -0
- package/dist/lib/runs-control.d.ts +13 -0
- package/dist/lib/runs-control.js +59 -0
- package/dist/lib/runs-identity.d.ts +5 -0
- package/dist/lib/runs-identity.js +11 -0
- package/dist/lib/runs-index.d.ts +18 -0
- package/dist/lib/runs-index.js +119 -0
- package/dist/lib/runs-mailbox.d.ts +25 -0
- package/dist/lib/runs-mailbox.js +146 -0
- package/dist/lib/runs-messages.d.ts +15 -0
- package/dist/lib/runs-messages.js +179 -0
- package/dist/lib/runs-outbox.d.ts +41 -0
- package/dist/lib/runs-outbox.js +87 -0
- package/dist/lib/runs-process.d.ts +7 -0
- package/dist/lib/runs-process.js +36 -0
- package/dist/lib/runs-retention.d.ts +8 -0
- package/dist/lib/runs-retention.js +56 -0
- package/dist/lib/runs-start.d.ts +9 -0
- package/dist/lib/runs-start.js +67 -0
- package/dist/lib/runs-status.d.ts +8 -0
- package/dist/lib/runs-status.js +60 -0
- package/dist/lib/runtime.d.ts +2 -2
- package/dist/lib/runtime.js +5 -5
- package/dist/lib/schema.d.ts +9 -0
- package/dist/lib/schema.js +40 -0
- package/dist/lib/tools-access.d.ts +19 -0
- package/dist/lib/tools-access.js +44 -0
- package/dist/lib/tools-inspect.d.ts +11 -0
- package/dist/lib/tools-inspect.js +534 -0
- package/dist/lib/tools-local.d.ts +14 -0
- package/dist/lib/tools-local.js +156 -0
- package/dist/lib/tools-mailbox.d.ts +8 -0
- package/dist/lib/tools-mailbox.js +48 -0
- package/dist/lib/tools-message.d.ts +9 -0
- package/dist/lib/tools-message.js +428 -0
- package/dist/lib/tools-register.d.ts +18 -0
- package/dist/lib/tools-register.js +40 -0
- package/dist/lib/tools-response.d.ts +21 -0
- package/dist/lib/tools-response.js +219 -0
- package/dist/lib/tools-spawn.d.ts +12 -0
- package/dist/lib/tools-spawn.js +159 -0
- package/dist/lib/tools.d.ts +8 -39
- package/dist/lib/tools.js +11 -1490
- package/dist/recipes/utility-coordinator-lock-snapshot.json +1 -1
- package/dist/scripts/actor-worker.mjs +190 -7
- package/dist/scripts/async-runner.mjs +171 -8
- package/dist/scripts/build-dist.mjs +1 -1
- package/dist/scripts/conformance.mjs +3 -3
- package/dist/scripts/coordinator.mjs +733 -11
- package/dist/scripts/locker.mjs +345 -16
- package/dist/scripts/recipe-utils.mjs +478 -14
- package/dist/scripts/validate-recipe.mjs +110 -10
- package/dist/skills/actors/SKILL.md +1 -1
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/recipe-library.md +1 -1
- package/docs/template-recipes.md +1 -1
- package/index.ts +11 -18
- package/lib/async-runs.ts +124 -1016
- package/lib/command-templates.ts +3 -3
- package/lib/config.ts +7 -7
- package/lib/{output.ts → execution-output.ts} +3 -3
- package/lib/execution.ts +5 -1
- package/lib/{actor-inspector-tui.ts → inspector.ts} +4 -3
- package/lib/mailbox-loop.ts +3 -3
- package/lib/{actor-messages.ts → messages.ts} +3 -3
- package/lib/{actor-recipe-context.ts → recipes-context.ts} +3 -2
- package/lib/{recipe-discovery.ts → recipes-discovery.ts} +19 -19
- package/lib/{recipe-usage.ts → recipes-usage.ts} +2 -2
- package/lib/registry.ts +37 -24
- package/lib/{actor-rooms.ts → rooms.ts} +4 -4
- package/lib/runs-artifacts.ts +96 -0
- package/lib/runs-control.ts +85 -0
- package/lib/runs-identity.ts +14 -0
- package/lib/runs-index.ts +156 -0
- package/lib/runs-mailbox.ts +208 -0
- package/lib/runs-messages.ts +252 -0
- package/lib/runs-outbox.ts +144 -0
- package/lib/runs-process.ts +51 -0
- package/lib/runs-retention.ts +77 -0
- package/lib/runs-start.ts +98 -0
- package/lib/runs-status.ts +78 -0
- package/lib/runtime.ts +6 -6
- package/lib/schema.ts +55 -0
- package/lib/tools-access.ts +68 -0
- package/lib/tools-inspect.ts +735 -0
- package/lib/tools-local.ts +234 -0
- package/lib/tools-mailbox.ts +56 -0
- package/lib/tools-message.ts +569 -0
- package/lib/tools-register.ts +64 -0
- package/lib/tools-response.ts +267 -0
- package/lib/tools-spawn.ts +240 -0
- package/lib/tools.ts +20 -2040
- package/package.json +1 -1
- package/recipes/utility-coordinator-lock-snapshot.json +1 -1
- package/scripts/actor-worker.mjs +190 -7
- package/scripts/async-runner.mjs +171 -8
- package/scripts/build-dist.mjs +1 -1
- package/scripts/conformance.mjs +3 -3
- package/scripts/coordinator.mjs +733 -11
- package/scripts/locker.mjs +345 -16
- package/scripts/recipe-utils.mjs +478 -14
- package/scripts/validate-recipe.mjs +110 -10
- package/skills/actors/SKILL.md +1 -1
- package/skills/swarm/SKILL.md +1 -1
- package/dist/lib/actor-worker.d.ts +0 -16
- package/dist/lib/actor-worker.js +0 -134
- package/dist/lib/async-runner.d.ts +0 -5
- package/dist/lib/async-runner.js +0 -134
- package/dist/lib/coordinator.d.ts +0 -5
- package/dist/lib/coordinator.js +0 -660
- package/dist/lib/locker.d.ts +0 -5
- package/dist/lib/locker.js +0 -310
- package/dist/lib/recipe-utils.d.ts +0 -5
- package/dist/lib/recipe-utils.js +0 -385
- package/dist/lib/validate-recipe.d.ts +0 -6
- package/dist/lib/validate-recipe.js +0 -104
- package/lib/actor-worker.ts +0 -174
- package/lib/async-runner.ts +0 -173
- package/lib/coordinator.ts +0 -742
- package/lib/locker.ts +0 -340
- package/lib/recipe-utils.ts +0 -486
- package/lib/validate-recipe.ts +0 -110
- /package/dist/lib/{recipe-references.d.ts → recipes-references.d.ts} +0 -0
- /package/dist/lib/{recipe-references.js → recipes-references.js} +0 -0
- /package/dist/lib/{recipe-usage.d.ts → recipes-usage.d.ts} +0 -0
- /package/dist/lib/{recipe-usage.js → recipes-usage.js} +0 -0
- /package/lib/{recipe-references.ts → recipes-references.ts} +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run outbox events.
|
|
3
|
+
* Owns outbox event normalization, parsing, and append payload formatting.
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeRunOutboxDelivery(value) {
|
|
6
|
+
return value === "notify" || value === "followup" ? value : "log";
|
|
7
|
+
}
|
|
8
|
+
export function normalizeRunOutboxLevel(value) {
|
|
9
|
+
return value === "warning" || value === "error" ? value : "info";
|
|
10
|
+
}
|
|
11
|
+
function normalizeRunOutboxEvent(raw, run, stateDir, index) {
|
|
12
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
13
|
+
return undefined;
|
|
14
|
+
const record = raw;
|
|
15
|
+
const event = typeof record.event === "string" && record.event.trim()
|
|
16
|
+
? record.event.trim()
|
|
17
|
+
: "run.event";
|
|
18
|
+
const summary = typeof record.summary === "string" && record.summary.trim()
|
|
19
|
+
? record.summary.trim()
|
|
20
|
+
: event;
|
|
21
|
+
const ts = typeof record.ts === "string" && record.ts.trim()
|
|
22
|
+
? record.ts.trim()
|
|
23
|
+
: new Date(0).toISOString();
|
|
24
|
+
const id = typeof record.id === "string" && record.id.trim()
|
|
25
|
+
? record.id.trim()
|
|
26
|
+
: `${run}:${index}`;
|
|
27
|
+
return {
|
|
28
|
+
...(record.body !== undefined ? { body: record.body } : {}),
|
|
29
|
+
...(typeof record.correlation_id === "string"
|
|
30
|
+
? { correlation_id: record.correlation_id }
|
|
31
|
+
: {}),
|
|
32
|
+
...(record.data !== undefined ? { data: record.data } : {}),
|
|
33
|
+
delivery: normalizeRunOutboxDelivery(record.delivery),
|
|
34
|
+
...(record.metadata &&
|
|
35
|
+
typeof record.metadata === "object" &&
|
|
36
|
+
!Array.isArray(record.metadata)
|
|
37
|
+
? { metadata: record.metadata }
|
|
38
|
+
: {}),
|
|
39
|
+
event,
|
|
40
|
+
...(typeof record.from === "string" ? { from: record.from } : {}),
|
|
41
|
+
id,
|
|
42
|
+
level: normalizeRunOutboxLevel(record.level),
|
|
43
|
+
...(typeof record.reply_to === "string"
|
|
44
|
+
? { reply_to: record.reply_to }
|
|
45
|
+
: {}),
|
|
46
|
+
run,
|
|
47
|
+
state_dir: stateDir,
|
|
48
|
+
summary,
|
|
49
|
+
...(typeof record.to === "string" ? { to: record.to } : {}),
|
|
50
|
+
ts,
|
|
51
|
+
...(typeof record.type === "string" ? { type: record.type } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function parseRunOutboxEventLine(line, run, stateDir, index) {
|
|
55
|
+
try {
|
|
56
|
+
return normalizeRunOutboxEvent(JSON.parse(line), run, stateDir, index);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function buildRunOutboxEventPayload(run, event) {
|
|
63
|
+
const type = event.type || event.event || "run.message";
|
|
64
|
+
const to = event.to || "coordinator";
|
|
65
|
+
const metadata = event.metadata ?? {};
|
|
66
|
+
const requiresResponse = metadata.requires_response === true;
|
|
67
|
+
return {
|
|
68
|
+
...(event.body !== undefined ? { body: event.body } : {}),
|
|
69
|
+
...(event.correlation_id ? { correlation_id: event.correlation_id } : {}),
|
|
70
|
+
...(event.data !== undefined ? { data: event.data } : {}),
|
|
71
|
+
delivery: normalizeRunOutboxDelivery(event.delivery ??
|
|
72
|
+
(requiresResponse
|
|
73
|
+
? "followup"
|
|
74
|
+
: to === "coordinator"
|
|
75
|
+
? "notify"
|
|
76
|
+
: "log")),
|
|
77
|
+
event: type,
|
|
78
|
+
from: event.from || `run:${run}`,
|
|
79
|
+
level: normalizeRunOutboxLevel(event.level),
|
|
80
|
+
...(event.metadata ? { metadata: event.metadata } : {}),
|
|
81
|
+
...(event.reply_to ? { reply_to: event.reply_to } : {}),
|
|
82
|
+
summary: event.summary || type,
|
|
83
|
+
to,
|
|
84
|
+
ts: new Date().toISOString(),
|
|
85
|
+
type,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run process identity helpers.
|
|
3
|
+
* Owns liveness checks and Linux runner identity matching for run-owned processes.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isAlive(pid: number): boolean;
|
|
6
|
+
export declare function pidMatchesRun(pid: number, cwd: string, stateDir: string, runnerPath: string): boolean;
|
|
7
|
+
export declare function isWithinRunnerIdentityGrace(meta: Record<string, unknown>, graceMs: number): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run process identity helpers.
|
|
3
|
+
* Owns liveness checks and Linux runner identity matching for run-owned processes.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, readlinkSync } from "node:fs";
|
|
6
|
+
import { platform } from "node:os";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
export function isAlive(pid) {
|
|
9
|
+
try {
|
|
10
|
+
process.kill(pid, 0);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function pidMatchesRun(pid, cwd, stateDir, runnerPath) {
|
|
18
|
+
if (platform() !== "linux" || !existsSync(`/proc/${pid}`))
|
|
19
|
+
return isAlive(pid);
|
|
20
|
+
try {
|
|
21
|
+
const procCwd = readlinkSync(`/proc/${pid}/cwd`);
|
|
22
|
+
const cmdline = readFileSync(`/proc/${pid}/cmdline`, "utf8");
|
|
23
|
+
return (procCwd === resolve(cwd) &&
|
|
24
|
+
cmdline.includes(runnerPath) &&
|
|
25
|
+
cmdline.includes(stateDir));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function isWithinRunnerIdentityGrace(meta, graceMs) {
|
|
32
|
+
const createdAt = typeof meta.createdAt === "string" ? Date.parse(meta.createdAt) : NaN;
|
|
33
|
+
return (Number.isFinite(createdAt) &&
|
|
34
|
+
Date.now() - createdAt >= 0 &&
|
|
35
|
+
Date.now() - createdAt <= graceMs);
|
|
36
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run retention operations.
|
|
3
|
+
* Owns terminal-run archive and prune filesystem behavior.
|
|
4
|
+
*/
|
|
5
|
+
export declare function archiveTerminalRun(status: Record<string, unknown>): Record<string, unknown>;
|
|
6
|
+
export declare function pruneTerminalRun(status: Record<string, unknown>, options?: {
|
|
7
|
+
preserveArtifacts?: boolean;
|
|
8
|
+
}): Record<string, unknown>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run retention operations.
|
|
3
|
+
* Owns terminal-run archive and prune filesystem behavior.
|
|
4
|
+
*/
|
|
5
|
+
import { cpSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, join } from "node:path";
|
|
7
|
+
import { writeJsonAtomic } from "./file-state.js";
|
|
8
|
+
import { resolveArtifactManifest, } from "./runs-artifacts.js";
|
|
9
|
+
import { safeRunId } from "./runs-identity.js";
|
|
10
|
+
function archivePathFor(run, stateDir) {
|
|
11
|
+
const archiveRoot = join(dirname(stateDir), "archived");
|
|
12
|
+
mkdirSync(archiveRoot, { recursive: true });
|
|
13
|
+
return join(archiveRoot, `${safeRunId(run)}-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
14
|
+
}
|
|
15
|
+
export function archiveTerminalRun(status) {
|
|
16
|
+
const stateDir = String(status.state_dir);
|
|
17
|
+
const run = String(status.run ?? basename(stateDir));
|
|
18
|
+
const archiveDir = archivePathFor(run, stateDir);
|
|
19
|
+
renameSync(stateDir, archiveDir);
|
|
20
|
+
mkdirSync(stateDir, { recursive: true });
|
|
21
|
+
const tombstone = {
|
|
22
|
+
archived: true,
|
|
23
|
+
archive_dir: archiveDir,
|
|
24
|
+
original_state_dir: stateDir,
|
|
25
|
+
run,
|
|
26
|
+
status: status.status,
|
|
27
|
+
ts: new Date().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
writeJsonAtomic(join(stateDir, "archive-tombstone.json"), tombstone);
|
|
30
|
+
return tombstone;
|
|
31
|
+
}
|
|
32
|
+
export function pruneTerminalRun(status, options = {}) {
|
|
33
|
+
const stateDir = String(status.state_dir);
|
|
34
|
+
const run = String(status.run ?? basename(stateDir));
|
|
35
|
+
const manifest = resolveArtifactManifest(status.artifacts);
|
|
36
|
+
const preserved = {};
|
|
37
|
+
if (options.preserveArtifacts && manifest) {
|
|
38
|
+
const preserveRoot = join(dirname(stateDir), "preserved-artifacts", safeRunId(run));
|
|
39
|
+
mkdirSync(preserveRoot, { recursive: true });
|
|
40
|
+
for (const [name, artifact] of Object.entries(manifest)) {
|
|
41
|
+
if (!artifact.exists)
|
|
42
|
+
continue;
|
|
43
|
+
const target = join(preserveRoot, basename(artifact.path));
|
|
44
|
+
cpSync(artifact.path, target, { force: true });
|
|
45
|
+
preserved[name] = target;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
rmSync(stateDir, { recursive: true, force: true });
|
|
49
|
+
return {
|
|
50
|
+
pruned: true,
|
|
51
|
+
preserved_artifacts: preserved,
|
|
52
|
+
run,
|
|
53
|
+
state_dir: stateDir,
|
|
54
|
+
ts: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run start guards.
|
|
3
|
+
* Owns active-run reuse checks, start lock acquisition, and safe state-dir
|
|
4
|
+
* preparation before a new runner process is spawned.
|
|
5
|
+
*/
|
|
6
|
+
export type RunJsonReader = (path: string) => Record<string, unknown> | undefined;
|
|
7
|
+
export declare function assertNoActiveRunState(stateDir: string, readJson: RunJsonReader, runnerPath: string): void;
|
|
8
|
+
export declare function acquireStateStartLock(stateDir: string): () => void;
|
|
9
|
+
export declare function prepareStateDirForStart(stateDir: string, readJson: RunJsonReader, runnerPath: string): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run start guards.
|
|
3
|
+
* Owns active-run reuse checks, start lock acquisition, and safe state-dir
|
|
4
|
+
* preparation before a new runner process is spawned.
|
|
5
|
+
*/
|
|
6
|
+
import { mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { isAlive, pidMatchesRun } from "./runs-process.js";
|
|
9
|
+
const START_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
|
|
10
|
+
export function assertNoActiveRunState(stateDir, readJson, runnerPath) {
|
|
11
|
+
const meta = readJson(join(stateDir, "run.json"));
|
|
12
|
+
if (!meta)
|
|
13
|
+
return;
|
|
14
|
+
const pid = Number(meta.pid || 0);
|
|
15
|
+
const cwd = String(meta.cwd ?? "");
|
|
16
|
+
if (!pid || !isAlive(pid) || !pidMatchesRun(pid, cwd, stateDir, runnerPath))
|
|
17
|
+
return;
|
|
18
|
+
throw new Error(`Run state already has an active owned process: ${String(meta.run ?? stateDir)}. Stop it before reusing the same run_id or state_dir.`);
|
|
19
|
+
}
|
|
20
|
+
export function acquireStateStartLock(stateDir) {
|
|
21
|
+
const lockDir = join(stateDir, ".start.lock");
|
|
22
|
+
try {
|
|
23
|
+
mkdirSync(lockDir);
|
|
24
|
+
writeFileSync(join(lockDir, "owner.json"), `${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })}\n`, "utf8");
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
try {
|
|
28
|
+
const stat = statSync(lockDir);
|
|
29
|
+
if (Date.now() - stat.mtimeMs > START_LOCK_MAX_AGE_MS) {
|
|
30
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
31
|
+
mkdirSync(lockDir);
|
|
32
|
+
writeFileSync(join(lockDir, "owner.json"), `${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString(), recovered: true })}\n`, "utf8");
|
|
33
|
+
return () => rmSync(lockDir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Keep the original lock acquisition error below.
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Run state is already being started: ${stateDir}. Retry after the current start finishes.`, { cause: error });
|
|
40
|
+
}
|
|
41
|
+
return () => rmSync(lockDir, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
export function prepareStateDirForStart(stateDir, readJson, runnerPath) {
|
|
44
|
+
const existing = readJson(join(stateDir, "run.json"));
|
|
45
|
+
const existingPid = Number(existing?.pid || 0);
|
|
46
|
+
const existingCwd = typeof existing?.cwd === "string" ? existing.cwd : undefined;
|
|
47
|
+
const existingResult = readJson(join(stateDir, "result.json"));
|
|
48
|
+
if (!existingResult &&
|
|
49
|
+
existingPid &&
|
|
50
|
+
existingCwd &&
|
|
51
|
+
isAlive(existingPid) &&
|
|
52
|
+
pidMatchesRun(existingPid, existingCwd, stateDir, runnerPath)) {
|
|
53
|
+
throw new Error(`Run state already has an active owned process: ${String(existing?.run ?? stateDir)}. Stop it before restarting.`);
|
|
54
|
+
}
|
|
55
|
+
for (const file of [
|
|
56
|
+
"events.jsonl",
|
|
57
|
+
"inbox.jsonl",
|
|
58
|
+
"outbox.jsonl",
|
|
59
|
+
"progress.json",
|
|
60
|
+
"result.json",
|
|
61
|
+
"stderr.log",
|
|
62
|
+
"stdout.log",
|
|
63
|
+
"terminal-handled.json",
|
|
64
|
+
]) {
|
|
65
|
+
rmSync(join(stateDir, file), { force: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run status and log readers.
|
|
3
|
+
* Owns status derivation from run state files plus bounded log/event tails.
|
|
4
|
+
*/
|
|
5
|
+
export type RunJsonReader = (path: string) => Record<string, unknown> | undefined;
|
|
6
|
+
export declare function buildRunStatus(stateDir: string, runOrDir: string, meta: Record<string, unknown>, readJson: RunJsonReader, runnerPath: string, runnerIdentityGraceMs: number): Record<string, unknown>;
|
|
7
|
+
export declare function tailFile(path: string, lines: number): string;
|
|
8
|
+
export declare function tailLines(path: string, lines: number): string[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async run status and log readers.
|
|
3
|
+
* Owns status derivation from run state files plus bounded log/event tails.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { isAlive, isWithinRunnerIdentityGrace, pidMatchesRun } from "./runs-process.js";
|
|
8
|
+
import { readJsonlFileResilient } from "./state-readers.js";
|
|
9
|
+
function getInterruptedRunStatus(stateDir) {
|
|
10
|
+
const events = readJsonlFileResilient(join(stateDir, "events.jsonl")).records.slice(-200);
|
|
11
|
+
for (const event of events.reverse()) {
|
|
12
|
+
if (event.event === "run.kill")
|
|
13
|
+
return "killed";
|
|
14
|
+
if (event.event === "run.cancel")
|
|
15
|
+
return "cancelled";
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
export function buildRunStatus(stateDir, runOrDir, meta, readJson, runnerPath, runnerIdentityGraceMs) {
|
|
20
|
+
const result = readJson(join(stateDir, "result.json"));
|
|
21
|
+
const pid = Number(meta.pid || 0);
|
|
22
|
+
const aliveOwnedRunner = Boolean(pid &&
|
|
23
|
+
isAlive(pid) &&
|
|
24
|
+
(!Array.isArray(meta.argv) ||
|
|
25
|
+
pidMatchesRun(pid, String(meta.cwd ?? ""), stateDir, runnerPath) ||
|
|
26
|
+
isWithinRunnerIdentityGrace(meta, runnerIdentityGraceMs)));
|
|
27
|
+
const status = result
|
|
28
|
+
? Number(result.code ?? 0) === 0
|
|
29
|
+
? "done"
|
|
30
|
+
: "failed"
|
|
31
|
+
: aliveOwnedRunner
|
|
32
|
+
? "running"
|
|
33
|
+
: (getInterruptedRunStatus(stateDir) ?? "exited");
|
|
34
|
+
const terminalHandled = readJson(join(stateDir, "terminal-handled.json"));
|
|
35
|
+
return {
|
|
36
|
+
...meta,
|
|
37
|
+
eventsFile: join(stateDir, "events.jsonl"),
|
|
38
|
+
inboxFile: join(stateDir, "inbox.jsonl"),
|
|
39
|
+
outboxFile: join(stateDir, "outbox.jsonl"),
|
|
40
|
+
progress: readJson(join(stateDir, "progress.json")) || null,
|
|
41
|
+
result: result || null,
|
|
42
|
+
...(terminalHandled ? { terminal_handled: terminalHandled } : {}),
|
|
43
|
+
state_dir: String(meta.state_dir ?? stateDir),
|
|
44
|
+
stderrLog: join(stateDir, "stderr.log"),
|
|
45
|
+
stdoutLog: join(stateDir, "stdout.log"),
|
|
46
|
+
status,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function tailFile(path, lines) {
|
|
50
|
+
if (!existsSync(path))
|
|
51
|
+
return "";
|
|
52
|
+
const content = readFileSync(path, "utf8").trimEnd();
|
|
53
|
+
if (!content)
|
|
54
|
+
return "";
|
|
55
|
+
return content.split("\n").slice(-lines).join("\n");
|
|
56
|
+
}
|
|
57
|
+
export function tailLines(path, lines) {
|
|
58
|
+
const content = tailFile(path, lines);
|
|
59
|
+
return content ? content.split("\n") : [];
|
|
60
|
+
}
|
package/dist/lib/runtime.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as Config from "./config.ts";
|
|
7
7
|
import type { RegisteredToolExec } from "./execution.ts";
|
|
8
|
-
import * as
|
|
8
|
+
import * as ToolsLocal from "./tools-local.ts";
|
|
9
9
|
export interface RuntimeContext {
|
|
10
10
|
hasUI: boolean;
|
|
11
11
|
ui: {
|
|
@@ -22,7 +22,7 @@ export interface ToolRegistryRuntimeDeps {
|
|
|
22
22
|
recipeRoot?: string;
|
|
23
23
|
getActiveTools?: () => string[];
|
|
24
24
|
getAllTools: () => ToolInfoLike[];
|
|
25
|
-
registerTool: (definition: ReturnType<typeof
|
|
25
|
+
registerTool: (definition: ReturnType<typeof ToolsLocal.createRuntimeToolDefinition>) => void;
|
|
26
26
|
reservedToolNames: Set<string>;
|
|
27
27
|
setActiveTools?: (toolNames: string[]) => void;
|
|
28
28
|
}
|
package/dist/lib/runtime.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, watch } from "node:fs";
|
|
7
7
|
import * as Paths from "./paths.js";
|
|
8
|
-
import * as
|
|
9
|
-
import * as
|
|
8
|
+
import * as RecipesDiscovery from "./recipes-discovery.js";
|
|
9
|
+
import * as ToolsLocal from "./tools-local.js";
|
|
10
10
|
export function createAutoToolsRuntime(deps) {
|
|
11
11
|
const tools = new Map();
|
|
12
12
|
const runtimeToolFingerprints = new Map();
|
|
@@ -50,7 +50,7 @@ export function createAutoToolsRuntime(deps) {
|
|
|
50
50
|
const fingerprint = getToolFingerprint(cfg);
|
|
51
51
|
if (runtimeToolFingerprints.get(cfg.name) === fingerprint)
|
|
52
52
|
return;
|
|
53
|
-
deps.registerTool(
|
|
53
|
+
deps.registerTool(ToolsLocal.createRuntimeToolDefinition(cfg, deps.exec));
|
|
54
54
|
runtimeTools.add(cfg.name);
|
|
55
55
|
runtimeToolFingerprints.set(cfg.name, fingerprint);
|
|
56
56
|
}
|
|
@@ -86,7 +86,7 @@ export function createAutoToolsRuntime(deps) {
|
|
|
86
86
|
const warnings = [];
|
|
87
87
|
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
88
88
|
const packagedRecipeRoot = deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot();
|
|
89
|
-
const discovered =
|
|
89
|
+
const discovered = RecipesDiscovery.discoverRecipeSources([
|
|
90
90
|
{ root: recipeRoot, defaultTool: true, mutableUsage: true },
|
|
91
91
|
{ root: packagedRecipeRoot },
|
|
92
92
|
]);
|
|
@@ -94,7 +94,7 @@ export function createAutoToolsRuntime(deps) {
|
|
|
94
94
|
tools.clear();
|
|
95
95
|
for (const entry of discovered.active.values()) {
|
|
96
96
|
try {
|
|
97
|
-
const cfg =
|
|
97
|
+
const cfg = RecipesDiscovery.toRegisteredTool(entry);
|
|
98
98
|
if (cfg)
|
|
99
99
|
tools.set(cfg.name, cfg);
|
|
100
100
|
}
|
package/dist/lib/schema.d.ts
CHANGED
|
@@ -33,6 +33,15 @@ export interface ToolArgSpec {
|
|
|
33
33
|
defaults: Record<string, string>;
|
|
34
34
|
error?: string;
|
|
35
35
|
}
|
|
36
|
+
export type JsonSchema = Record<string, unknown>;
|
|
37
|
+
export declare function stringSchema(description: string): JsonSchema;
|
|
38
|
+
export declare function typedArgSchema(description: string, type: ToolArgType | undefined): JsonSchema;
|
|
39
|
+
export declare function booleanSchema(description: string): JsonSchema;
|
|
40
|
+
export declare function nullSchema(description: string): JsonSchema;
|
|
41
|
+
export declare function arraySchema(description: string): JsonSchema;
|
|
42
|
+
export declare function unionSchema(anyOf: JsonSchema[]): JsonSchema;
|
|
43
|
+
export declare function objectSchema(properties: Record<string, JsonSchema>, required?: string[]): JsonSchema;
|
|
44
|
+
export declare function looseObjectSchema(description: string): JsonSchema;
|
|
36
45
|
export declare function parseToolArgToken(value: string): ParsedToolArgToken;
|
|
37
46
|
export declare function parseToolArgDeclarations(value: string): ToolArgSpec;
|
|
38
47
|
export declare function normalizeStoredToolArgDeclarations(argsValue: unknown, defaultsValue: unknown): ToolArgSpec & {
|
package/dist/lib/schema.js
CHANGED
|
@@ -4,6 +4,46 @@
|
|
|
4
4
|
* Owns tool argument declarations, placeholder-derived tool schemas, and persisted registry normalization
|
|
5
5
|
*/
|
|
6
6
|
import * as CommandTemplates from "./command-templates.js";
|
|
7
|
+
export function stringSchema(description) {
|
|
8
|
+
return { description, type: "string" };
|
|
9
|
+
}
|
|
10
|
+
export function typedArgSchema(description, type) {
|
|
11
|
+
if (!type)
|
|
12
|
+
return stringSchema(description);
|
|
13
|
+
switch (type.kind) {
|
|
14
|
+
case "int":
|
|
15
|
+
return { description, type: "integer" };
|
|
16
|
+
case "number":
|
|
17
|
+
return { description, type: "number" };
|
|
18
|
+
case "bool":
|
|
19
|
+
return { description, type: "boolean" };
|
|
20
|
+
case "array":
|
|
21
|
+
return { description, items: {}, type: "array" };
|
|
22
|
+
case "enum":
|
|
23
|
+
return { description, enum: type.values, type: "string" };
|
|
24
|
+
case "path":
|
|
25
|
+
case "string":
|
|
26
|
+
return stringSchema(description);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function booleanSchema(description) {
|
|
30
|
+
return { description, type: "boolean" };
|
|
31
|
+
}
|
|
32
|
+
export function nullSchema(description) {
|
|
33
|
+
return { description, type: "null" };
|
|
34
|
+
}
|
|
35
|
+
export function arraySchema(description) {
|
|
36
|
+
return { description, items: {}, type: "array" };
|
|
37
|
+
}
|
|
38
|
+
export function unionSchema(anyOf) {
|
|
39
|
+
return { anyOf };
|
|
40
|
+
}
|
|
41
|
+
export function objectSchema(properties, required = []) {
|
|
42
|
+
return { additionalProperties: false, properties, required, type: "object" };
|
|
43
|
+
}
|
|
44
|
+
export function looseObjectSchema(description) {
|
|
45
|
+
return { additionalProperties: true, description, type: "object" };
|
|
46
|
+
}
|
|
7
47
|
function mergeUnique(items) {
|
|
8
48
|
return [...new Set(items.filter(Boolean))];
|
|
9
49
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public tool access and ownership checks
|
|
3
|
+
* Zones: session ownership, run visibility guards, tool authorization errors
|
|
4
|
+
* Owns consistent session-mismatch diagnostics for public tool execution paths
|
|
5
|
+
*/
|
|
6
|
+
export interface SessionContext {
|
|
7
|
+
sessionManager?: {
|
|
8
|
+
getSessionId?: () => string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare function getContextSessionId(ctx: unknown): string | undefined;
|
|
12
|
+
export declare function requireContextSessionId(ctx: unknown, actor: string): string;
|
|
13
|
+
export declare function sessionMismatchError(input: {
|
|
14
|
+
currentSession?: string;
|
|
15
|
+
expectedSession?: string;
|
|
16
|
+
run?: string;
|
|
17
|
+
target?: string;
|
|
18
|
+
}): Error;
|
|
19
|
+
export declare function assertRunAccessibleToContext(runId: string, ctx: unknown): Record<string, unknown>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public tool access and ownership checks
|
|
3
|
+
* Zones: session ownership, run visibility guards, tool authorization errors
|
|
4
|
+
* Owns consistent session-mismatch diagnostics for public tool execution paths
|
|
5
|
+
*/
|
|
6
|
+
import * as AsyncRuns from "./async-runs.js";
|
|
7
|
+
export function getContextSessionId(ctx) {
|
|
8
|
+
return ctx?.sessionManager?.getSessionId?.();
|
|
9
|
+
}
|
|
10
|
+
export function requireContextSessionId(ctx, actor) {
|
|
11
|
+
const sessionId = getContextSessionId(ctx);
|
|
12
|
+
if (!sessionId) {
|
|
13
|
+
throw new Error(`${actor} requires a current coordinator session; use session:<id> or session:all for explicit session inventory.`);
|
|
14
|
+
}
|
|
15
|
+
return sessionId;
|
|
16
|
+
}
|
|
17
|
+
export function sessionMismatchError(input) {
|
|
18
|
+
const ownerSession = input.expectedSession ?? "none";
|
|
19
|
+
const currentSession = input.currentSession ?? "none";
|
|
20
|
+
const actor = input.run ? `run:${input.run}` : (input.target ?? "session");
|
|
21
|
+
const hintTarget = input.expectedSession
|
|
22
|
+
? `session:${input.expectedSession}`
|
|
23
|
+
: "session:all";
|
|
24
|
+
return Object.assign(new Error(`${actor} reason=session_mismatch owner_session=${ownerSession} current_session=${currentSession} hint=inspect_session:${input.expectedSession ?? "all"}`), {
|
|
25
|
+
current_session: input.currentSession,
|
|
26
|
+
hint: `inspect target=${hintTarget} view=status`,
|
|
27
|
+
owner_session: input.expectedSession,
|
|
28
|
+
reason: "session_mismatch",
|
|
29
|
+
run: input.run,
|
|
30
|
+
target: input.target,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function assertRunAccessibleToContext(runId, ctx) {
|
|
34
|
+
const status = AsyncRuns.getRunStatus(runId);
|
|
35
|
+
const sessionId = getContextSessionId(ctx);
|
|
36
|
+
if (sessionId && status.ownerId && status.ownerId !== sessionId) {
|
|
37
|
+
throw sessionMismatchError({
|
|
38
|
+
currentSession: sessionId,
|
|
39
|
+
expectedSession: String(status.ownerId),
|
|
40
|
+
run: runId,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return status;
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public inspect tool behavior
|
|
3
|
+
* Zones: actor observation, recipe registry inspection, room/session/tool/run views
|
|
4
|
+
* Owns the public inspect execution path and compact observation responses
|
|
5
|
+
*/
|
|
6
|
+
export interface InspectToolDeps<TContext = unknown> {
|
|
7
|
+
getTool?: (name: string) => any | undefined;
|
|
8
|
+
packagedRecipeRoot?: string;
|
|
9
|
+
recipeRoot?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function createInspectToolDefinition<TContext = unknown>(deps?: InspectToolDeps<TContext>): any;
|