@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
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local coordination locker service entrypoint logic.
|
|
3
|
+
* Zones: coordination locks, task leases, platform control endpoint
|
|
4
|
+
*/
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
9
|
+
import { createServer } from "node:net";
|
|
10
|
+
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import readline from "node:readline";
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
const args = { mode: "serve", stateDir: "", leaseMs: 600000, lines: 20 };
|
|
14
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
15
|
+
const arg = argv[index];
|
|
16
|
+
if (arg === "serve" || arg === "snapshot")
|
|
17
|
+
args.mode = arg;
|
|
18
|
+
else if (arg === "--state-dir")
|
|
19
|
+
args.stateDir = argv[++index] ?? "";
|
|
20
|
+
else if (arg === "--lease-ms")
|
|
21
|
+
args.leaseMs = Number(argv[++index] ?? args.leaseMs);
|
|
22
|
+
else if (arg === "--lines")
|
|
23
|
+
args.lines = Number(argv[++index] ?? args.lines);
|
|
24
|
+
}
|
|
25
|
+
if (!args.stateDir)
|
|
26
|
+
throw new Error("--state-dir is required");
|
|
27
|
+
if (!Number.isFinite(args.leaseMs) || args.leaseMs <= 0)
|
|
28
|
+
args.leaseMs = 600000;
|
|
29
|
+
if (!Number.isFinite(args.lines) || args.lines <= 0)
|
|
30
|
+
args.lines = 20;
|
|
31
|
+
return args;
|
|
32
|
+
}
|
|
33
|
+
export async function runLocker(argv = process.argv.slice(2)) {
|
|
34
|
+
const { mode, stateDir, leaseMs, lines } = parseArgs(argv);
|
|
35
|
+
const queuePath = join(stateDir, "queue.json");
|
|
36
|
+
const locksPath = join(stateDir, "locks.json");
|
|
37
|
+
const journalPath = join(stateDir, "journal.jsonl");
|
|
38
|
+
const outboxPath = join(stateDir, "outbox.jsonl");
|
|
39
|
+
const controlPath = join(stateDir, "control.fifo");
|
|
40
|
+
mkdirSync(stateDir, { recursive: true });
|
|
41
|
+
function readJson(path, fallback) {
|
|
42
|
+
if (!existsSync(path))
|
|
43
|
+
return fallback;
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return fallback;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function writeJson(path, value) {
|
|
52
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
53
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
54
|
+
}
|
|
55
|
+
function getControlEndpoint() {
|
|
56
|
+
if (process.platform !== "win32")
|
|
57
|
+
return { path: controlPath, type: "fifo" };
|
|
58
|
+
const hash = createHash("sha256")
|
|
59
|
+
.update(resolve(stateDir))
|
|
60
|
+
.digest("hex")
|
|
61
|
+
.slice(0, 20);
|
|
62
|
+
return { path: `\\\\.\\pipe\\pi-actors-locker-${hash}`, type: "named-pipe" };
|
|
63
|
+
}
|
|
64
|
+
function writeControlEndpoint(endpoint) {
|
|
65
|
+
writeJson(join(stateDir, "control.json"), endpoint);
|
|
66
|
+
const runPath = join(stateDir, "run.json");
|
|
67
|
+
const run = readJson(runPath, undefined);
|
|
68
|
+
if (run && typeof run === "object")
|
|
69
|
+
writeJson(runPath, { ...run, control: endpoint });
|
|
70
|
+
}
|
|
71
|
+
function journal(event, data = {}) {
|
|
72
|
+
appendFileSync(journalPath, `${JSON.stringify({ event, ts: new Date().toISOString(), ...data })}\n`);
|
|
73
|
+
}
|
|
74
|
+
function outbox(type, summary, body = {}, level = "info") {
|
|
75
|
+
appendFileSync(outboxPath, `${JSON.stringify({ to: "coordinator", from: `run:${process.env.run_id ?? "locker"}`, type, event: type, summary, body, data: body, delivery: "followup", level, ts: new Date().toISOString() })}\n`);
|
|
76
|
+
}
|
|
77
|
+
function now() {
|
|
78
|
+
return Date.now();
|
|
79
|
+
}
|
|
80
|
+
function cleanExpiredLocks(locks) {
|
|
81
|
+
const current = now();
|
|
82
|
+
const kept = {};
|
|
83
|
+
for (const [key, lock] of Object.entries(locks)) {
|
|
84
|
+
if (Number(lock.expiresAt) > current)
|
|
85
|
+
kept[key] = lock;
|
|
86
|
+
else
|
|
87
|
+
journal("lock.expired", { resource: key, owner: lock.owner });
|
|
88
|
+
}
|
|
89
|
+
return kept;
|
|
90
|
+
}
|
|
91
|
+
function normalizeMessage(line) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
return undefined;
|
|
95
|
+
if (["stop", "quit", "exit", "cancel"].includes(trimmed.toLowerCase())) {
|
|
96
|
+
return { type: "control.stop", body: {} };
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(trimmed);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return { type: "lock.enqueue", body: { task: trimmed } };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function tailJournal(count) {
|
|
106
|
+
if (!existsSync(journalPath))
|
|
107
|
+
return [];
|
|
108
|
+
return readFileSync(journalPath, "utf8")
|
|
109
|
+
.trimEnd()
|
|
110
|
+
.split("\n")
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.slice(-count)
|
|
113
|
+
.map((line) => {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(line);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return { raw: line };
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function printSnapshot() {
|
|
123
|
+
const locks = cleanExpiredLocks(readJson(locksPath, {}));
|
|
124
|
+
writeJson(locksPath, locks);
|
|
125
|
+
const queue = readJson(queuePath, { items: [] });
|
|
126
|
+
console.log(JSON.stringify({
|
|
127
|
+
queueDepth: Array.isArray(queue.items) ? queue.items.length : 0,
|
|
128
|
+
queue,
|
|
129
|
+
locks,
|
|
130
|
+
journal: tailJournal(lines),
|
|
131
|
+
}, null, 2));
|
|
132
|
+
}
|
|
133
|
+
function nextTask(queue, locks) {
|
|
134
|
+
const items = Array.isArray(queue.items) ? queue.items : [];
|
|
135
|
+
const index = items.findIndex((item) => {
|
|
136
|
+
const resources = Array.isArray(item.resources) ? item.resources : [];
|
|
137
|
+
return resources.every((resource) => !locks[resource]);
|
|
138
|
+
});
|
|
139
|
+
if (index < 0)
|
|
140
|
+
return undefined;
|
|
141
|
+
return items.splice(index, 1)[0];
|
|
142
|
+
}
|
|
143
|
+
function handle(message) {
|
|
144
|
+
const type = message.type || message.event || "lock.enqueue";
|
|
145
|
+
const body = message.body && typeof message.body === "object" ? message.body : message;
|
|
146
|
+
let queue = readJson(queuePath, { items: [] });
|
|
147
|
+
let locks = cleanExpiredLocks(readJson(locksPath, {}));
|
|
148
|
+
if (type === "control.stop" || type === "control.cancel") {
|
|
149
|
+
writeJson(locksPath, locks);
|
|
150
|
+
journal("control.stop", {});
|
|
151
|
+
outbox("lock.stopped", "Locker stopped", {
|
|
152
|
+
queueDepth: queue.items?.length ?? 0,
|
|
153
|
+
});
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
if (type === "lock.enqueue" || type === "coord.enqueue") {
|
|
157
|
+
const item = {
|
|
158
|
+
id: body.id || `task-${Date.now()}`,
|
|
159
|
+
task: body.task ?? body,
|
|
160
|
+
resources: body.resources ?? [],
|
|
161
|
+
enqueuedAt: new Date().toISOString(),
|
|
162
|
+
};
|
|
163
|
+
queue.items = [...(queue.items ?? []), item];
|
|
164
|
+
writeJson(queuePath, queue);
|
|
165
|
+
writeJson(locksPath, locks);
|
|
166
|
+
journal("lock.enqueued", { id: item.id, resources: item.resources });
|
|
167
|
+
outbox("lock.enqueued", `Queued task ${item.id}`, {
|
|
168
|
+
id: item.id,
|
|
169
|
+
queueDepth: queue.items.length,
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (type === "lock.claim" || type === "coord.claim") {
|
|
174
|
+
const owner = body.owner || message.from || "worker";
|
|
175
|
+
const item = nextTask(queue, locks);
|
|
176
|
+
if (!item) {
|
|
177
|
+
writeJson(queuePath, queue);
|
|
178
|
+
writeJson(locksPath, locks);
|
|
179
|
+
outbox("lock.empty", "No claimable task", {
|
|
180
|
+
owner,
|
|
181
|
+
queueDepth: queue.items?.length ?? 0,
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
for (const resource of item.resources ?? [])
|
|
186
|
+
locks[resource] = { owner, task: item.id, expiresAt: now() + leaseMs };
|
|
187
|
+
writeJson(queuePath, queue);
|
|
188
|
+
writeJson(locksPath, locks);
|
|
189
|
+
journal("lock.assigned", {
|
|
190
|
+
id: item.id,
|
|
191
|
+
owner,
|
|
192
|
+
resources: item.resources,
|
|
193
|
+
});
|
|
194
|
+
outbox("lock.assigned", `Assigned task ${item.id}`, { owner, task: item });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (type === "lock.acquire") {
|
|
198
|
+
const resource = body.resource;
|
|
199
|
+
const owner = body.owner || message.from || "worker";
|
|
200
|
+
if (!resource)
|
|
201
|
+
throw new Error("lock.acquire body.resource is required");
|
|
202
|
+
if (locks[resource])
|
|
203
|
+
outbox("lock.denied", `Lock denied ${resource}`, { resource, owner, current: locks[resource] }, "warning");
|
|
204
|
+
else {
|
|
205
|
+
locks[resource] = { owner, expiresAt: now() + leaseMs };
|
|
206
|
+
outbox("lock.granted", `Lock granted ${resource}`, { resource, owner });
|
|
207
|
+
}
|
|
208
|
+
writeJson(locksPath, locks);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (type === "lock.renew") {
|
|
212
|
+
const resource = body.resource;
|
|
213
|
+
const owner = body.owner || message.from || "worker";
|
|
214
|
+
if (!resource)
|
|
215
|
+
throw new Error("lock.renew body.resource is required");
|
|
216
|
+
const current = locks[resource];
|
|
217
|
+
if (!current) {
|
|
218
|
+
outbox("lock.denied", `Lock renew denied ${resource}`, { resource, owner, reason: "missing" }, "warning");
|
|
219
|
+
}
|
|
220
|
+
else if (current.owner !== owner) {
|
|
221
|
+
outbox("lock.denied", `Lock renew denied ${resource}`, { resource, owner, current }, "warning");
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
locks[resource] = { ...current, expiresAt: now() + leaseMs };
|
|
225
|
+
outbox("lock.renewed", `Lock renewed ${resource}`, { resource, owner });
|
|
226
|
+
}
|
|
227
|
+
writeJson(locksPath, locks);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (type === "lock.release") {
|
|
231
|
+
const resource = body.resource;
|
|
232
|
+
if (resource)
|
|
233
|
+
delete locks[resource];
|
|
234
|
+
writeJson(locksPath, locks);
|
|
235
|
+
outbox("lock.released", `Lock released ${resource}`, { resource });
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (type === "lock.complete" ||
|
|
239
|
+
type === "lock.fail" ||
|
|
240
|
+
type === "coord.complete" ||
|
|
241
|
+
type === "coord.fail") {
|
|
242
|
+
const eventType = type.startsWith("coord.")
|
|
243
|
+
? type.replace("coord.", "lock.")
|
|
244
|
+
: type;
|
|
245
|
+
journal(eventType, body);
|
|
246
|
+
outbox(eventType, `${eventType} ${body.id ?? ""}`.trim(), body, eventType === "lock.fail" ? "error" : "info");
|
|
247
|
+
writeJson(locksPath, locks);
|
|
248
|
+
writeJson(queuePath, queue);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
journal("lock.unknown", { type, body });
|
|
252
|
+
outbox("lock.unknown", `Unknown message ${type}`, { type, body }, "warning");
|
|
253
|
+
}
|
|
254
|
+
if (mode === "snapshot") {
|
|
255
|
+
printSnapshot();
|
|
256
|
+
process.exit(0);
|
|
257
|
+
}
|
|
258
|
+
function handleLine(line) {
|
|
259
|
+
const message = normalizeMessage(line);
|
|
260
|
+
if (!message)
|
|
261
|
+
return;
|
|
262
|
+
try {
|
|
263
|
+
handle(message);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
267
|
+
journal("lock.error", { error: text });
|
|
268
|
+
outbox("lock.error", text, { error: text }, "error");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async function serveFifo(endpoint) {
|
|
272
|
+
if (!existsSync(endpoint.path)) {
|
|
273
|
+
const result = spawnSync("mkfifo", [endpoint.path]);
|
|
274
|
+
if (result.status !== 0)
|
|
275
|
+
throw new Error(`mkfifo failed: ${result.stderr?.toString?.() ?? ""}`);
|
|
276
|
+
}
|
|
277
|
+
writeControlEndpoint(endpoint);
|
|
278
|
+
while (true) {
|
|
279
|
+
const stream = await import("node:fs").then((fs) => fs.createReadStream(endpoint.path, { encoding: "utf8" }));
|
|
280
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
281
|
+
for await (const line of rl)
|
|
282
|
+
handleLine(line);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async function serveNamedPipe(endpoint) {
|
|
286
|
+
rmSync(endpoint.path, { force: true });
|
|
287
|
+
const server = createServer((socket) => {
|
|
288
|
+
const rl = readline.createInterface({ input: socket, crlfDelay: Infinity });
|
|
289
|
+
rl.on("line", handleLine);
|
|
290
|
+
});
|
|
291
|
+
await new Promise((resolveReady, rejectReady) => {
|
|
292
|
+
server.once("error", rejectReady);
|
|
293
|
+
server.listen(endpoint.path, () => {
|
|
294
|
+
server.off("error", rejectReady);
|
|
295
|
+
writeControlEndpoint(endpoint);
|
|
296
|
+
resolveReady();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
await new Promise(() => { });
|
|
300
|
+
}
|
|
301
|
+
const endpoint = getControlEndpoint();
|
|
302
|
+
writeJson(queuePath, readJson(queuePath, { items: [] }));
|
|
303
|
+
writeJson(locksPath, cleanExpiredLocks(readJson(locksPath, {})));
|
|
304
|
+
journal("lock.started", { leaseMs, control: endpoint.type });
|
|
305
|
+
outbox("lock.started", "Locker ready", { leaseMs, control: endpoint.type });
|
|
306
|
+
if (endpoint.type === "named-pipe")
|
|
307
|
+
await serveNamedPipe(endpoint);
|
|
308
|
+
else
|
|
309
|
+
await serveFifo(endpoint);
|
|
310
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal mailbox loop helpers.
|
|
3
|
+
* Zones: mailbox-consuming actors, run/branch inbox claiming, handler status transitions
|
|
4
|
+
* Owns small reusable primitives for recipe authors; scheduling and task policy stay outside.
|
|
5
|
+
*/
|
|
6
|
+
import { type BranchInboxRecord } from "./actor-rooms.ts";
|
|
7
|
+
import { type RunInboxMessage } from "./async-runs.ts";
|
|
8
|
+
export type MailboxLoopMessage = RunInboxMessage | BranchInboxRecord;
|
|
9
|
+
export type MailboxLoopTarget = {
|
|
10
|
+
kind: "run";
|
|
11
|
+
runOrDir: string;
|
|
12
|
+
} | {
|
|
13
|
+
address: string;
|
|
14
|
+
kind: "branch";
|
|
15
|
+
run: string;
|
|
16
|
+
stateDir: string;
|
|
17
|
+
};
|
|
18
|
+
export interface MailboxLoopClaimOptions {
|
|
19
|
+
owner?: string;
|
|
20
|
+
statuses?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface MailboxLoopHandleResult {
|
|
23
|
+
handled: boolean;
|
|
24
|
+
id?: string;
|
|
25
|
+
message?: MailboxLoopMessage;
|
|
26
|
+
target: MailboxLoopTarget;
|
|
27
|
+
}
|
|
28
|
+
export interface MailboxLoopDrainOptions extends MailboxLoopClaimOptions {
|
|
29
|
+
maxMessages?: number;
|
|
30
|
+
stopOnControl?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface MailboxLoopDrainResult {
|
|
33
|
+
handled: number;
|
|
34
|
+
stopped: boolean;
|
|
35
|
+
target: MailboxLoopTarget;
|
|
36
|
+
}
|
|
37
|
+
export declare function isMailboxLoopStopMessage(message: unknown): boolean;
|
|
38
|
+
export declare function claimMailboxLoopMessage(target: MailboxLoopTarget, options?: MailboxLoopClaimOptions): MailboxLoopMessage | undefined;
|
|
39
|
+
export declare function updateMailboxLoopMessageStatus(target: MailboxLoopTarget, id: string, status: "claimed" | "handled" | "failed", metadata?: Record<string, unknown>): boolean;
|
|
40
|
+
export declare function handleMailboxLoopOnce(target: MailboxLoopTarget, handler: (message: MailboxLoopMessage) => Promise<void> | void, options?: MailboxLoopClaimOptions): Promise<MailboxLoopHandleResult>;
|
|
41
|
+
export declare function drainMailboxLoopMessages(target: MailboxLoopTarget, handler: (message: MailboxLoopMessage) => Promise<void> | void, options?: MailboxLoopDrainOptions): Promise<MailboxLoopDrainResult>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal mailbox loop helpers.
|
|
3
|
+
* Zones: mailbox-consuming actors, run/branch inbox claiming, handler status transitions
|
|
4
|
+
* Owns small reusable primitives for recipe authors; scheduling and task policy stay outside.
|
|
5
|
+
*/
|
|
6
|
+
import { claimBranchInboxMessage, updateBranchInboxMessageStatus, } from "./actor-rooms.js";
|
|
7
|
+
import { claimRunInboxMessage, updateRunInboxMessageStatus, } from "./async-runs.js";
|
|
8
|
+
export function isMailboxLoopStopMessage(message) {
|
|
9
|
+
const type = message && typeof message === "object" && "type" in message
|
|
10
|
+
? message.type
|
|
11
|
+
: undefined;
|
|
12
|
+
return (type === "control.stop" ||
|
|
13
|
+
type === "control.cancel" ||
|
|
14
|
+
type === "control.kill");
|
|
15
|
+
}
|
|
16
|
+
function messageId(message) {
|
|
17
|
+
return typeof message?.id === "string" ? message.id : undefined;
|
|
18
|
+
}
|
|
19
|
+
export function claimMailboxLoopMessage(target, options = {}) {
|
|
20
|
+
const owner = options.owner ?? "mailbox-loop";
|
|
21
|
+
const statuses = options.statuses ?? ["queued"];
|
|
22
|
+
return target.kind === "run"
|
|
23
|
+
? claimRunInboxMessage(target.runOrDir, owner, statuses)
|
|
24
|
+
: claimBranchInboxMessage(target.stateDir, target.run, target.address, owner, statuses);
|
|
25
|
+
}
|
|
26
|
+
export function updateMailboxLoopMessageStatus(target, id, status, metadata = {}) {
|
|
27
|
+
return target.kind === "run"
|
|
28
|
+
? updateRunInboxMessageStatus(target.runOrDir, id, status, metadata)
|
|
29
|
+
: updateBranchInboxMessageStatus(target.stateDir, target.run, target.address, id, status, metadata);
|
|
30
|
+
}
|
|
31
|
+
export async function handleMailboxLoopOnce(target, handler, options = {}) {
|
|
32
|
+
const message = claimMailboxLoopMessage(target, options);
|
|
33
|
+
const id = messageId(message);
|
|
34
|
+
if (!message || !id)
|
|
35
|
+
return { handled: false, target };
|
|
36
|
+
try {
|
|
37
|
+
await handler(message);
|
|
38
|
+
updateMailboxLoopMessageStatus(target, id, "handled");
|
|
39
|
+
return { handled: true, id, message, target };
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
updateMailboxLoopMessageStatus(target, id, "failed", {
|
|
43
|
+
error: error instanceof Error ? error.message : String(error),
|
|
44
|
+
});
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function drainMailboxLoopMessages(target, handler, options = {}) {
|
|
49
|
+
const maxMessages = Math.max(1, Math.floor(options.maxMessages ?? 100));
|
|
50
|
+
let handled = 0;
|
|
51
|
+
for (; handled < maxMessages; handled += 1) {
|
|
52
|
+
const result = await handleMailboxLoopOnce(target, handler, options);
|
|
53
|
+
if (!result.handled || !result.message) {
|
|
54
|
+
return { handled, stopped: false, target };
|
|
55
|
+
}
|
|
56
|
+
if (options.stopOnControl !== false &&
|
|
57
|
+
isMailboxLoopStopMessage(result.message)) {
|
|
58
|
+
return { handled: handled + 1, stopped: true, target };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { handled, stopped: false, target };
|
|
62
|
+
}
|
|
@@ -88,8 +88,8 @@ export declare function renderRunStatus(summary: RunSummary, frame?: number): st
|
|
|
88
88
|
export declare function findRunRetirementCandidates(summary: RunSummary): RunRetirementCandidate[];
|
|
89
89
|
export declare function executeRunRetirements(summary: RunSummary, options: RunRetirementExecutorOptions): Promise<RunRetirementExecution[]>;
|
|
90
90
|
export declare function detectRunTransitions(previous: Map<string, RunObservedStatus>, summary: RunSummary): RunTransition[];
|
|
91
|
-
export declare function pruneRunObservationState(previousStatuses: Map<string, RunObservedStatus>, previousLineCounts: Map<string, number>, summary: RunSummary, terminalRuns?: Iterable<string
|
|
92
|
-
export declare function detectRunOutboxEvents(previousLineCounts: Map<string, number>, summary: RunSummary): RunOutboxEvent[];
|
|
91
|
+
export declare function pruneRunObservationState(previousStatuses: Map<string, RunObservedStatus>, previousLineCounts: Map<string, number>, summary: RunSummary, terminalRuns?: Iterable<string>, seenEventIds?: Map<string, Set<string>>): void;
|
|
92
|
+
export declare function detectRunOutboxEvents(previousLineCounts: Map<string, number>, summary: RunSummary, seenEventIds?: Map<string, Set<string>>): RunOutboxEvent[];
|
|
93
93
|
export declare function getRunOutboxNotificationType(event: RunOutboxEvent): RunTransitionNotificationType;
|
|
94
94
|
export declare function shouldNotifyRunOutboxEvent(event: RunOutboxEvent): boolean;
|
|
95
95
|
export declare function shouldSendRunOutboxFollowUp(event: RunOutboxEvent): boolean;
|
|
@@ -7,6 +7,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
7
7
|
import { basename, dirname, isAbsolute, join, relative, resolve, } from "node:path";
|
|
8
8
|
import * as AsyncRuns from "./async-runs.js";
|
|
9
9
|
import * as Paths from "./paths.js";
|
|
10
|
+
import { readJsonlFileResilient } from "./state-readers.js";
|
|
10
11
|
const TERMINAL = new Set([
|
|
11
12
|
"done",
|
|
12
13
|
"failed",
|
|
@@ -35,7 +36,7 @@ function getUpdatedAt(status) {
|
|
|
35
36
|
? status.createdAt
|
|
36
37
|
: undefined;
|
|
37
38
|
}
|
|
38
|
-
function
|
|
39
|
+
function scanRunStateDirs(stateRoot, depth = 0, seen = new Set()) {
|
|
39
40
|
if (!existsSync(stateRoot) || seen.has(stateRoot))
|
|
40
41
|
return [];
|
|
41
42
|
seen.add(stateRoot);
|
|
@@ -54,7 +55,7 @@ function listRunStateDirs(stateRoot, depth = 0, seen = new Set()) {
|
|
|
54
55
|
if (existsSync(join(child, "run.json")))
|
|
55
56
|
result.push(child);
|
|
56
57
|
if (depth + 1 < RUN_STATE_DISCOVERY_MAX_DEPTH)
|
|
57
|
-
result.push(...
|
|
58
|
+
result.push(...scanRunStateDirs(child, depth + 1, seen));
|
|
58
59
|
}
|
|
59
60
|
return result;
|
|
60
61
|
}
|
|
@@ -114,7 +115,8 @@ export function summarizeRuns(stateRoot = Paths.getRunStateRoot(), ownerId) {
|
|
|
114
115
|
total: 0,
|
|
115
116
|
};
|
|
116
117
|
}
|
|
117
|
-
const runs =
|
|
118
|
+
const runs = (AsyncRuns.readRunStateIndex(stateRoot)?.map((entry) => entry.state_dir) ??
|
|
119
|
+
scanRunStateDirs(stateRoot))
|
|
118
120
|
.map((stateDir) => observeRun(stateDir))
|
|
119
121
|
.filter((run) => Boolean(run))
|
|
120
122
|
.filter((run) => ownerId === undefined || run.ownerId === ownerId)
|
|
@@ -190,7 +192,8 @@ function summarizeRunsWithoutSubagents(stateRoot, ownerId) {
|
|
|
190
192
|
runs: [],
|
|
191
193
|
total: 0,
|
|
192
194
|
};
|
|
193
|
-
const runs =
|
|
195
|
+
const runs = (AsyncRuns.readRunStateIndex(stateRoot)?.map((entry) => entry.state_dir) ??
|
|
196
|
+
scanRunStateDirs(stateRoot))
|
|
194
197
|
.map((stateDir) => observeRun(stateDir))
|
|
195
198
|
.filter((run) => Boolean(run))
|
|
196
199
|
.filter((run) => ownerId === undefined || run.ownerId === ownerId)
|
|
@@ -407,57 +410,45 @@ function normalizeOutboxDelivery(value) {
|
|
|
407
410
|
function normalizeOutboxLevel(value) {
|
|
408
411
|
return value === "warning" || value === "error" ? value : "info";
|
|
409
412
|
}
|
|
410
|
-
function
|
|
413
|
+
function parseOutboxRecord(raw, run, index) {
|
|
411
414
|
if (!run.stateDir)
|
|
412
415
|
return undefined;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
stateDir: run.stateDir,
|
|
443
|
-
summary,
|
|
444
|
-
ts,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
catch {
|
|
448
|
-
return undefined;
|
|
449
|
-
}
|
|
416
|
+
const event = typeof raw.event === "string" && raw.event.trim()
|
|
417
|
+
? raw.event.trim()
|
|
418
|
+
: "run.event";
|
|
419
|
+
const summary = typeof raw.summary === "string" && raw.summary.trim()
|
|
420
|
+
? raw.summary.trim()
|
|
421
|
+
: event;
|
|
422
|
+
const ts = typeof raw.ts === "string" && raw.ts.trim()
|
|
423
|
+
? raw.ts.trim()
|
|
424
|
+
: new Date(0).toISOString();
|
|
425
|
+
const id = typeof raw.id === "string" && raw.id.trim()
|
|
426
|
+
? raw.id.trim()
|
|
427
|
+
: `${run.run}:${index}`;
|
|
428
|
+
return {
|
|
429
|
+
...(raw.body !== undefined ? { body: raw.body } : {}),
|
|
430
|
+
...(raw.data !== undefined ? { data: raw.data } : {}),
|
|
431
|
+
delivery: normalizeOutboxDelivery(raw.delivery),
|
|
432
|
+
event,
|
|
433
|
+
id,
|
|
434
|
+
level: normalizeOutboxLevel(raw.level),
|
|
435
|
+
...(raw.metadata &&
|
|
436
|
+
typeof raw.metadata === "object" &&
|
|
437
|
+
!Array.isArray(raw.metadata)
|
|
438
|
+
? { metadata: raw.metadata }
|
|
439
|
+
: {}),
|
|
440
|
+
run: run.run,
|
|
441
|
+
stateDir: run.stateDir,
|
|
442
|
+
summary,
|
|
443
|
+
ts,
|
|
444
|
+
};
|
|
450
445
|
}
|
|
451
|
-
function
|
|
446
|
+
function readOutboxRecords(run) {
|
|
452
447
|
if (!run.stateDir)
|
|
453
448
|
return [];
|
|
454
|
-
|
|
455
|
-
if (!existsSync(path))
|
|
456
|
-
return [];
|
|
457
|
-
const content = readFileSync(path, "utf8").trimEnd();
|
|
458
|
-
return content ? content.split("\n") : [];
|
|
449
|
+
return readJsonlFileResilient(join(run.stateDir, "outbox.jsonl")).records;
|
|
459
450
|
}
|
|
460
|
-
export function pruneRunObservationState(previousStatuses, previousLineCounts, summary, terminalRuns = []) {
|
|
451
|
+
export function pruneRunObservationState(previousStatuses, previousLineCounts, summary, terminalRuns = [], seenEventIds = new Map()) {
|
|
461
452
|
const activeRuns = new Set(summary.runs.map((run) => runObservationKey(run)));
|
|
462
453
|
const terminalRunSet = new Set(terminalRuns);
|
|
463
454
|
const terminalLineKeys = new Set(summary.runs
|
|
@@ -475,20 +466,29 @@ export function pruneRunObservationState(previousStatuses, previousLineCounts, s
|
|
|
475
466
|
previousLineCounts.delete(key);
|
|
476
467
|
}
|
|
477
468
|
}
|
|
469
|
+
for (const key of seenEventIds.keys()) {
|
|
470
|
+
if (terminalLineKeys.has(key) || !activeLineKeys.has(key)) {
|
|
471
|
+
seenEventIds.delete(key);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
478
474
|
}
|
|
479
|
-
export function detectRunOutboxEvents(previousLineCounts, summary) {
|
|
475
|
+
export function detectRunOutboxEvents(previousLineCounts, summary, seenEventIds = new Map()) {
|
|
480
476
|
const events = [];
|
|
481
477
|
for (const run of summary.runs) {
|
|
482
478
|
const key = run.stateDir ?? run.run;
|
|
483
|
-
const
|
|
479
|
+
const records = readOutboxRecords(run);
|
|
484
480
|
const previousCount = previousLineCounts.get(key) ?? 0;
|
|
485
|
-
const start = Math.min(previousCount,
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
481
|
+
const start = Math.min(previousCount, records.length);
|
|
482
|
+
const seen = seenEventIds.get(key) ?? new Set();
|
|
483
|
+
for (let index = start; index < records.length; index += 1) {
|
|
484
|
+
const event = parseOutboxRecord(records[index], run, index);
|
|
485
|
+
if (!event || seen.has(event.id))
|
|
486
|
+
continue;
|
|
487
|
+
events.push(event);
|
|
488
|
+
seen.add(event.id);
|
|
490
489
|
}
|
|
491
|
-
previousLineCounts.set(key,
|
|
490
|
+
previousLineCounts.set(key, records.length);
|
|
491
|
+
seenEventIds.set(key, seen);
|
|
492
492
|
}
|
|
493
493
|
return events;
|
|
494
494
|
}
|
package/dist/lib/output.js
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { sanitizeFilePart } from "./identity.js";
|
|
9
|
+
import * as Limits from "./limits.js";
|
|
9
10
|
import * as Paths from "./paths.js";
|
|
10
|
-
const MAX_OUTPUT_BYTES = 50 * 1024;
|
|
11
|
-
const MAX_OUTPUT_LINES = 2_000;
|
|
12
11
|
export function writeFullOutput(toolName, stream, content) {
|
|
13
12
|
try {
|
|
14
13
|
const outputRoot = join(Paths.getExtensionTmpDir(), "outputs");
|
|
@@ -45,10 +44,10 @@ export function truncateTailContent(content) {
|
|
|
45
44
|
const totalBytes = byteLength(content);
|
|
46
45
|
const lines = content.split("\n");
|
|
47
46
|
const totalLines = lines.length;
|
|
48
|
-
let output = totalLines >
|
|
49
|
-
? lines.slice(-
|
|
47
|
+
let output = totalLines > Limits.TOOL_OUTPUT_MAX_LINES
|
|
48
|
+
? lines.slice(-Limits.TOOL_OUTPUT_MAX_LINES).join("\n")
|
|
50
49
|
: content;
|
|
51
|
-
output = trimToTailBytes(output,
|
|
50
|
+
output = trimToTailBytes(output, Limits.TOOL_OUTPUT_MAX_BYTES);
|
|
52
51
|
const outputBytes = byteLength(output);
|
|
53
52
|
const outputLines = output ? output.split("\n").length : 0;
|
|
54
53
|
return {
|
package/dist/lib/prompts.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export declare const REGISTER_TOOL_DESCRIPTION: string;
|
|
7
7
|
export declare const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent command templates as agent-callable tools";
|
|
8
8
|
export declare const REGISTER_TOOL_GUIDELINES: string[];
|
|
9
|
-
export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, when, timeout, delay, retry, failure, recover, repeat, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Use spawn/message/inspect for actor-level start/send/observe; avoid runtime/FIFO/outbox vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.\n- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.\n- Foreground tools/templates fit short work; async recipes/runs fit subagents, services, fanout, media, and long pipelines.\n- Long fanout = parent async recipe wrapping template(parallel:true) and imports; packaged fanout recipes bubble branch completion messages; grow recurring multi-agent workflows as packaged recipes/pipelines, not ad hoc external scripts.\n- For deeper pi-actors guidance, inspect installed extension sources/docs/recipes; README and docs are not automatically in context.";
|
|
9
|
+
export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, when, timeout, delay, retry, failure, recover, repeat, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Use spawn/message/inspect for actor-level start/send/observe; avoid runtime/FIFO/outbox vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.\n- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.\n- Foreground tools/templates fit short work; async recipes/runs fit subagents, services, fanout, media, and long pipelines.\n- Long fanout = parent async recipe wrapping template(parallel:true) and imports; packaged fanout recipes bubble branch completion messages; grow recurring multi-agent workflows as packaged recipes/pipelines, not ad hoc external scripts.\n- For deeper pi-actors guidance, inspect installed extension sources/docs/recipes; README and docs are not automatically in context.";
|
|
10
10
|
export declare const REGISTER_TOOL_PARAM_DESCRIPTIONS: {
|
|
11
11
|
readonly name: "Tool name in snake_case (e.g., 'transcribe')";
|
|
12
12
|
readonly description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.";
|
package/dist/lib/prompts.js
CHANGED
|
@@ -23,7 +23,7 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
|
23
23
|
- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
|
|
24
24
|
- Use spawn/message/inspect for actor-level start/send/observe; avoid runtime/FIFO/outbox vocabulary in public guidance.
|
|
25
25
|
- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.
|
|
26
|
-
- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.
|
|
26
|
+
- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.
|
|
27
27
|
- Foreground tools/templates fit short work; async recipes/runs fit subagents, services, fanout, media, and long pipelines.
|
|
28
28
|
- Long fanout = parent async recipe wrapping template(parallel:true) and imports; packaged fanout recipes bubble branch completion messages; grow recurring multi-agent workflows as packaged recipes/pipelines, not ad hoc external scripts.
|
|
29
29
|
- For deeper pi-actors guidance, inspect installed extension sources/docs/recipes; README and docs are not automatically in context.`;
|
|
@@ -351,6 +351,7 @@ export function summarizeDiscovery(result) {
|
|
|
351
351
|
disabled: entry.disabled,
|
|
352
352
|
invalid: entry.invalid,
|
|
353
353
|
shadows: entry.shadows,
|
|
354
|
+
...(entry.config?.imports ? { imports: entry.config.imports } : {}),
|
|
354
355
|
...(recipeUsage(entry.config)
|
|
355
356
|
? { usage: recipeUsage(entry.config) }
|
|
356
357
|
: {}),
|