@llblab/pi-actors 0.38.1 → 0.40.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 +11 -4
- package/BACKLOG.md +2 -11
- package/CHANGELOG.md +51 -0
- package/README.md +120 -131
- package/dist/index.js +6 -4
- package/dist/lib/async-runs.d.ts +4 -0
- package/dist/lib/async-runs.js +112 -17
- package/dist/lib/command-templates.d.ts +9 -0
- package/dist/lib/command-templates.js +92 -11
- package/dist/lib/config.js +0 -5
- package/dist/lib/execution.d.ts +31 -0
- package/dist/lib/execution.js +145 -12
- package/dist/lib/file-state.d.ts +1 -0
- package/dist/lib/file-state.js +91 -3
- package/dist/lib/observability.d.ts +1 -1
- package/dist/lib/observability.js +7 -4
- package/dist/lib/pi.d.ts +1 -1
- package/dist/lib/pi.js +2 -2
- package/dist/lib/prompts.d.ts +1 -2
- package/dist/lib/prompts.js +2 -3
- package/dist/lib/recipes-context.js +17 -9
- package/dist/lib/recipes-discovery.js +11 -5
- package/dist/lib/recipes-references.d.ts +1 -1
- package/dist/lib/recipes-references.js +4 -5
- package/dist/lib/recipes-usage.d.ts +2 -0
- package/dist/lib/recipes-usage.js +35 -21
- package/dist/lib/registry.d.ts +0 -2
- package/dist/lib/registry.js +33 -10
- package/dist/lib/runs-ownership.d.ts +7 -0
- package/dist/lib/runs-ownership.js +82 -0
- package/dist/lib/runs-process.d.ts +17 -2
- package/dist/lib/runs-process.js +99 -11
- package/dist/lib/runs-retention.d.ts +3 -0
- package/dist/lib/runs-retention.js +18 -3
- package/dist/lib/runs-start.d.ts +2 -2
- package/dist/lib/runs-start.js +51 -17
- package/dist/lib/runs-status.d.ts +1 -1
- package/dist/lib/runs-status.js +8 -6
- package/dist/lib/runtime.js +69 -13
- package/dist/lib/tools-inspect.d.ts +2 -0
- package/dist/lib/tools-inspect.js +39 -2
- package/dist/lib/tools-register.js +0 -1
- package/dist/lib/tools-spawn.js +3 -2
- package/dist/lib/tools.d.ts +1 -0
- package/dist/lib/tools.js +3 -0
- package/dist/pi-actors/index.js +1 -0
- package/dist/recipes/subagent-judge.json +2 -1
- package/dist/recipes/subagent-merge.json +2 -1
- package/dist/recipes/subagent-normalize.json +2 -1
- package/dist/recipes/subagent-review-coordinator.json +1 -1
- package/dist/recipes/subagent-review.json +2 -1
- package/dist/recipes/subagent-verify.json +2 -1
- package/dist/scripts/async-runner.mjs +274 -6
- package/dist/scripts/build-dist.mjs +14 -1
- package/dist/skills/actors/SKILL.md +11 -7
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-messages.md +1 -1
- package/docs/async-runs.md +14 -5
- package/docs/command-templates.md +4 -2
- package/docs/recipe-library.md +1 -0
- package/docs/template-recipes.md +5 -7
- package/docs/tool-registry.md +4 -2
- package/index.ts +18 -7
- package/lib/async-runs.ts +138 -19
- package/lib/command-templates.ts +132 -13
- package/lib/config.ts +0 -4
- package/lib/execution.ts +198 -13
- package/lib/file-state.ts +106 -3
- package/lib/observability.ts +11 -5
- package/lib/pi.ts +3 -3
- package/lib/prompts.ts +2 -4
- package/lib/recipes-context.ts +17 -9
- package/lib/recipes-discovery.ts +10 -5
- package/lib/recipes-references.ts +5 -6
- package/lib/recipes-usage.ts +36 -20
- package/lib/registry.ts +43 -13
- package/lib/runs-ownership.ts +117 -0
- package/lib/runs-process.ts +138 -16
- package/lib/runs-retention.ts +22 -2
- package/lib/runs-start.ts +89 -31
- package/lib/runs-status.ts +15 -6
- package/lib/runtime.ts +64 -12
- package/lib/tools-inspect.ts +46 -4
- package/lib/tools-register.ts +0 -3
- package/lib/tools-spawn.ts +5 -5
- package/lib/tools.ts +8 -0
- package/package.json +2 -2
- package/recipes/subagent-judge.json +2 -1
- package/recipes/subagent-merge.json +2 -1
- package/recipes/subagent-normalize.json +2 -1
- package/recipes/subagent-review-coordinator.json +1 -1
- package/recipes/subagent-review.json +2 -1
- package/recipes/subagent-verify.json +2 -1
- package/scripts/async-runner.mjs +274 -6
- package/scripts/build-dist.mjs +14 -1
- package/skills/actors/SKILL.md +11 -7
- package/skills/swarm/SKILL.md +1 -1
package/lib/runs-start.ts
CHANGED
|
@@ -4,10 +4,21 @@
|
|
|
4
4
|
* preparation before a new runner process is spawned.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
8
14
|
import { join } from "node:path";
|
|
9
15
|
|
|
10
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
isAlive,
|
|
18
|
+
readProcessIdentity,
|
|
19
|
+
verifyRunProcessIdentity,
|
|
20
|
+
type RunProcessIdentity,
|
|
21
|
+
} from "./runs-process.ts";
|
|
11
22
|
|
|
12
23
|
const START_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
|
|
13
24
|
|
|
@@ -16,39 +27,79 @@ export type RunJsonReader = (path: string) => Record<string, unknown> | undefine
|
|
|
16
27
|
export function assertNoActiveRunState(
|
|
17
28
|
stateDir: string,
|
|
18
29
|
readJson: RunJsonReader,
|
|
19
|
-
|
|
30
|
+
_runnerPath: string,
|
|
20
31
|
): void {
|
|
21
32
|
const meta = readJson(join(stateDir, "run.json"));
|
|
22
33
|
if (!meta) return;
|
|
23
34
|
const pid = Number(meta.pid || 0);
|
|
24
35
|
const cwd = String(meta.cwd ?? "");
|
|
25
|
-
if (!pid || !isAlive(pid)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
if (!pid || !isAlive(pid)) return;
|
|
37
|
+
const identity = verifyRunProcessIdentity(
|
|
38
|
+
pid,
|
|
39
|
+
meta.process_identity as RunProcessIdentity | undefined,
|
|
29
40
|
);
|
|
41
|
+
if (identity.status === "owner_mismatch") {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Run state process identity does not match the live pid: ${String(meta.run ?? stateDir)}. Refusing to reuse the state directory while pid ${pid} is alive.`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (identity.status === "unsupported_proof") {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Run state process identity proof is unavailable: ${String(meta.run ?? stateDir)}. Refusing to reuse the state directory while pid ${pid} is alive.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (identity.valid) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Run state already has an active owned process: ${String(meta.run ?? stateDir)}. Stop it before reusing the same run_id or state_dir.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function writeStartLockOwner(lockDir: string, recovered = false): void {
|
|
59
|
+
const processIdentity = readProcessIdentity(process.pid);
|
|
60
|
+
writeFileSync(
|
|
61
|
+
join(lockDir, "owner.json"),
|
|
62
|
+
`${JSON.stringify({
|
|
63
|
+
pid: process.pid,
|
|
64
|
+
createdAt: new Date().toISOString(),
|
|
65
|
+
...(processIdentity ? { process_identity: processIdentity } : {}),
|
|
66
|
+
...(recovered ? { recovered: true } : {}),
|
|
67
|
+
})}\n`,
|
|
68
|
+
"utf8",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isStartLockOwnerProvenDead(lockDir: string): boolean {
|
|
73
|
+
try {
|
|
74
|
+
const owner = JSON.parse(
|
|
75
|
+
readFileSync(join(lockDir, "owner.json"), "utf8"),
|
|
76
|
+
) as Record<string, unknown>;
|
|
77
|
+
const pid = Number(owner.pid || 0);
|
|
78
|
+
if (!pid) return false;
|
|
79
|
+
return verifyRunProcessIdentity(
|
|
80
|
+
pid,
|
|
81
|
+
owner.process_identity as RunProcessIdentity | undefined,
|
|
82
|
+
).status === "dead_pid";
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
30
86
|
}
|
|
31
87
|
|
|
32
88
|
export function acquireStateStartLock(stateDir: string): () => void {
|
|
33
89
|
const lockDir = join(stateDir, ".start.lock");
|
|
34
90
|
try {
|
|
35
91
|
mkdirSync(lockDir);
|
|
36
|
-
|
|
37
|
-
join(lockDir, "owner.json"),
|
|
38
|
-
`${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })}\n`,
|
|
39
|
-
"utf8",
|
|
40
|
-
);
|
|
92
|
+
writeStartLockOwner(lockDir);
|
|
41
93
|
} catch (error) {
|
|
42
94
|
try {
|
|
43
95
|
const stat = statSync(lockDir);
|
|
44
|
-
if (
|
|
96
|
+
if (
|
|
97
|
+
Date.now() - stat.mtimeMs > START_LOCK_MAX_AGE_MS &&
|
|
98
|
+
isStartLockOwnerProvenDead(lockDir)
|
|
99
|
+
) {
|
|
45
100
|
rmSync(lockDir, { recursive: true, force: true });
|
|
46
101
|
mkdirSync(lockDir);
|
|
47
|
-
|
|
48
|
-
join(lockDir, "owner.json"),
|
|
49
|
-
`${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString(), recovered: true })}\n`,
|
|
50
|
-
"utf8",
|
|
51
|
-
);
|
|
102
|
+
writeStartLockOwner(lockDir, true);
|
|
52
103
|
return () => rmSync(lockDir, { recursive: true, force: true });
|
|
53
104
|
}
|
|
54
105
|
} catch {
|
|
@@ -65,23 +116,30 @@ export function acquireStateStartLock(stateDir: string): () => void {
|
|
|
65
116
|
export function prepareStateDirForStart(
|
|
66
117
|
stateDir: string,
|
|
67
118
|
readJson: RunJsonReader,
|
|
68
|
-
|
|
119
|
+
_runnerPath: string,
|
|
69
120
|
): void {
|
|
70
121
|
const existing = readJson(join(stateDir, "run.json"));
|
|
71
122
|
const existingPid = Number(existing?.pid || 0);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
!existingResult &&
|
|
77
|
-
existingPid &&
|
|
78
|
-
existingCwd &&
|
|
79
|
-
isAlive(existingPid) &&
|
|
80
|
-
pidMatchesRun(existingPid, existingCwd, stateDir, runnerPath)
|
|
81
|
-
) {
|
|
82
|
-
throw new Error(
|
|
83
|
-
`Run state already has an active owned process: ${String(existing?.run ?? stateDir)}. Stop it before restarting.`,
|
|
123
|
+
if (existingPid && isAlive(existingPid)) {
|
|
124
|
+
const identity = verifyRunProcessIdentity(
|
|
125
|
+
existingPid,
|
|
126
|
+
existing?.process_identity as RunProcessIdentity | undefined,
|
|
84
127
|
);
|
|
128
|
+
if (identity.status === "owner_mismatch") {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Run state process identity does not match the live pid: ${String(existing?.run ?? stateDir)}. Refusing to prepare the state directory while pid ${existingPid} is alive.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (identity.status === "unsupported_proof") {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Run state process identity proof is unavailable: ${String(existing?.run ?? stateDir)}. Refusing to prepare the state directory while pid ${existingPid} is alive.`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (identity.valid) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Run state already has an active owned process: ${String(existing?.run ?? stateDir)}. Stop it before restarting.`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
85
143
|
}
|
|
86
144
|
for (const file of [
|
|
87
145
|
"events.jsonl",
|
package/lib/runs-status.ts
CHANGED
|
@@ -7,7 +7,11 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
9
|
import type { AsyncRunStatus } from "./async-runs.ts";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
isWithinRunnerIdentityGrace,
|
|
12
|
+
verifyRunProcessIdentity,
|
|
13
|
+
type RunProcessIdentity,
|
|
14
|
+
} from "./runs-process.ts";
|
|
11
15
|
import { readJsonlFileResilient } from "./state-readers.ts";
|
|
12
16
|
|
|
13
17
|
export type RunJsonReader = (path: string) => Record<string, unknown> | undefined;
|
|
@@ -30,17 +34,20 @@ export function buildRunStatus(
|
|
|
30
34
|
runOrDir: string,
|
|
31
35
|
meta: Record<string, unknown>,
|
|
32
36
|
readJson: RunJsonReader,
|
|
33
|
-
|
|
37
|
+
_runnerPath: string,
|
|
34
38
|
runnerIdentityGraceMs: number,
|
|
35
39
|
): Record<string, unknown> {
|
|
36
40
|
const result = readJson(join(stateDir, "result.json"));
|
|
37
41
|
const pid = Number(meta.pid || 0);
|
|
42
|
+
const processIdentity = verifyRunProcessIdentity(
|
|
43
|
+
pid,
|
|
44
|
+
meta.process_identity as RunProcessIdentity | undefined,
|
|
45
|
+
);
|
|
38
46
|
const aliveOwnedRunner = Boolean(
|
|
39
47
|
pid &&
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
isWithinRunnerIdentityGrace(meta, runnerIdentityGraceMs)),
|
|
48
|
+
(processIdentity.valid ||
|
|
49
|
+
(processIdentity.status === "unsupported_proof" &&
|
|
50
|
+
isWithinRunnerIdentityGrace(meta, runnerIdentityGraceMs))),
|
|
44
51
|
);
|
|
45
52
|
const status: AsyncRunStatus = result
|
|
46
53
|
? Number(result.code ?? 0) === 0
|
|
@@ -53,8 +60,10 @@ export function buildRunStatus(
|
|
|
53
60
|
return {
|
|
54
61
|
...meta,
|
|
55
62
|
eventsFile: join(stateDir, "events.jsonl"),
|
|
63
|
+
evidenceFile: join(stateDir, "review-evidence.json"),
|
|
56
64
|
inboxFile: join(stateDir, "inbox.jsonl"),
|
|
57
65
|
outboxFile: join(stateDir, "outbox.jsonl"),
|
|
66
|
+
process_identity_status: processIdentity.status,
|
|
58
67
|
progress: readJson(join(stateDir, "progress.json")) || null,
|
|
59
68
|
result: result || null,
|
|
60
69
|
...(terminalHandled ? { terminal_handled: terminalHandled } : {}),
|
package/lib/runtime.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { existsSync, watch, type FSWatcher } from "node:fs";
|
|
8
|
+
import { basename, dirname } from "node:path";
|
|
8
9
|
|
|
9
10
|
import * as Config from "./config.ts";
|
|
10
11
|
import type { RegisteredToolExec } from "./execution.ts";
|
|
@@ -186,10 +187,13 @@ export function createRecipeToolReloadWatcher(
|
|
|
186
187
|
): RecipeToolReloadWatcher {
|
|
187
188
|
let reloadTimeout: NodeJS.Timeout | undefined;
|
|
188
189
|
let rootWatcher: FSWatcher | undefined;
|
|
190
|
+
let parentWatcher: FSWatcher | undefined;
|
|
189
191
|
let failureNotified = false;
|
|
190
192
|
const close = (): void => {
|
|
191
193
|
rootWatcher?.close();
|
|
192
194
|
rootWatcher = undefined;
|
|
195
|
+
parentWatcher?.close();
|
|
196
|
+
parentWatcher = undefined;
|
|
193
197
|
if (reloadTimeout) clearTimeout(reloadTimeout);
|
|
194
198
|
reloadTimeout = undefined;
|
|
195
199
|
};
|
|
@@ -210,21 +214,69 @@ export function createRecipeToolReloadWatcher(
|
|
|
210
214
|
}, 150);
|
|
211
215
|
reloadTimeout.unref?.();
|
|
212
216
|
};
|
|
217
|
+
const watchParent = (ctx: RuntimeContext, recipeRoot: string): void => {
|
|
218
|
+
if (parentWatcher || rootWatcher) return;
|
|
219
|
+
const parent = dirname(recipeRoot);
|
|
220
|
+
if (!existsSync(parent)) {
|
|
221
|
+
notifyFailure(ctx);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
parentWatcher = watch(parent, (_event, changedFile) => {
|
|
226
|
+
if (
|
|
227
|
+
changedFile &&
|
|
228
|
+
String(changedFile) !== basename(recipeRoot) &&
|
|
229
|
+
!existsSync(recipeRoot)
|
|
230
|
+
) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!existsSync(recipeRoot)) return;
|
|
234
|
+
parentWatcher?.close();
|
|
235
|
+
parentWatcher = undefined;
|
|
236
|
+
watchRoot(ctx, recipeRoot);
|
|
237
|
+
scheduleReload(ctx);
|
|
238
|
+
});
|
|
239
|
+
parentWatcher.on("error", () => {
|
|
240
|
+
parentWatcher?.close();
|
|
241
|
+
parentWatcher = undefined;
|
|
242
|
+
notifyFailure(ctx);
|
|
243
|
+
});
|
|
244
|
+
} catch {
|
|
245
|
+
notifyFailure(ctx);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const watchRoot = (ctx: RuntimeContext, recipeRoot: string): void => {
|
|
249
|
+
if (rootWatcher) return;
|
|
250
|
+
if (!existsSync(recipeRoot)) {
|
|
251
|
+
watchParent(ctx, recipeRoot);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
rootWatcher = watch(recipeRoot, () => {
|
|
256
|
+
if (!existsSync(recipeRoot)) {
|
|
257
|
+
rootWatcher?.close();
|
|
258
|
+
rootWatcher = undefined;
|
|
259
|
+
scheduleReload(ctx);
|
|
260
|
+
watchParent(ctx, recipeRoot);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
scheduleReload(ctx);
|
|
264
|
+
});
|
|
265
|
+
rootWatcher.on("error", () => {
|
|
266
|
+
rootWatcher?.close();
|
|
267
|
+
rootWatcher = undefined;
|
|
268
|
+
if (existsSync(recipeRoot)) notifyFailure(ctx);
|
|
269
|
+
else watchParent(ctx, recipeRoot);
|
|
270
|
+
});
|
|
271
|
+
} catch {
|
|
272
|
+
notifyFailure(ctx);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
213
275
|
return {
|
|
214
276
|
close,
|
|
215
277
|
watch(ctx: RuntimeContext): void {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
rootWatcher = watch(recipeRoot, () => scheduleReload(ctx));
|
|
220
|
-
rootWatcher.on("error", () => {
|
|
221
|
-
rootWatcher?.close();
|
|
222
|
-
rootWatcher = undefined;
|
|
223
|
-
notifyFailure(ctx);
|
|
224
|
-
});
|
|
225
|
-
} catch {
|
|
226
|
-
notifyFailure(ctx);
|
|
227
|
-
}
|
|
278
|
+
if (rootWatcher || parentWatcher) return;
|
|
279
|
+
watchRoot(ctx, Paths.getRecipeRoot());
|
|
228
280
|
},
|
|
229
281
|
};
|
|
230
282
|
}
|
package/lib/tools-inspect.ts
CHANGED
|
@@ -139,6 +139,31 @@ function compactArtifactPath(value: unknown): string {
|
|
|
139
139
|
return String(record.path ?? "<missing>");
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function readBoundedReviewEvidence(
|
|
143
|
+
status: Record<string, unknown>,
|
|
144
|
+
limit: number,
|
|
145
|
+
): Record<string, unknown> | undefined {
|
|
146
|
+
const evidenceFile = status.evidenceFile;
|
|
147
|
+
if (typeof evidenceFile !== "string" || !existsSync(evidenceFile))
|
|
148
|
+
return undefined;
|
|
149
|
+
try {
|
|
150
|
+
const manifest = asRecord(JSON.parse(readFileSync(evidenceFile, "utf8")));
|
|
151
|
+
const commands = Array.isArray(manifest.commands) ? manifest.commands : [];
|
|
152
|
+
const bounded = commands.slice(-Math.max(1, limit));
|
|
153
|
+
return {
|
|
154
|
+
...manifest,
|
|
155
|
+
commands: bounded,
|
|
156
|
+
commands_total: commands.length,
|
|
157
|
+
...(bounded.length < commands.length ? { commands_truncated: true } : {}),
|
|
158
|
+
};
|
|
159
|
+
} catch {
|
|
160
|
+
return {
|
|
161
|
+
status: "invalid",
|
|
162
|
+
error: "review evidence manifest is unreadable",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
142
167
|
function compactActorFiles(status: Record<string, unknown>): string {
|
|
143
168
|
const run = String(status.run ?? "<unknown>");
|
|
144
169
|
const artifacts = asRecord(status.artifacts);
|
|
@@ -147,6 +172,7 @@ function compactActorFiles(status: Record<string, unknown>): string {
|
|
|
147
172
|
status.stderrLog,
|
|
148
173
|
status.eventsFile,
|
|
149
174
|
status.outboxFile,
|
|
175
|
+
status.evidenceFile,
|
|
150
176
|
status.state_dir
|
|
151
177
|
? `${String(status.state_dir)}/communication.json`
|
|
152
178
|
: undefined,
|
|
@@ -305,9 +331,15 @@ function getPiActorsTriage(
|
|
|
305
331
|
): Record<string, unknown> {
|
|
306
332
|
const runtime = getPiActorsRuntimeStatus();
|
|
307
333
|
const currentSession = ToolsAccess.getContextSessionId(ctx);
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
)
|
|
334
|
+
const listRuns = deps.listRuns ?? AsyncRuns.listRuns;
|
|
335
|
+
const getRunStatus = deps.getRunStatus ?? AsyncRuns.getRunStatus;
|
|
336
|
+
const allRuns = listRuns().flatMap((run) => {
|
|
337
|
+
try {
|
|
338
|
+
return [getRunStatus(String(run.state_dir))];
|
|
339
|
+
} catch {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
});
|
|
311
343
|
const visibleRuns = currentSession
|
|
312
344
|
? allRuns.filter(
|
|
313
345
|
(run) => !run.ownerId || run.ownerId === currentSession,
|
|
@@ -433,7 +465,9 @@ function compactToolActor(name: string, tool: Record<string, unknown>): string {
|
|
|
433
465
|
}
|
|
434
466
|
|
|
435
467
|
export interface InspectToolDeps<TContext = unknown> {
|
|
468
|
+
getRunStatus?: (runOrDir: string) => Record<string, any>;
|
|
436
469
|
getTool?: (name: string) => any | undefined;
|
|
470
|
+
listRuns?: () => Array<Record<string, any>>;
|
|
437
471
|
packagedRecipeRoot?: string;
|
|
438
472
|
recipeRoot?: string;
|
|
439
473
|
}
|
|
@@ -832,16 +866,24 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
832
866
|
| Record<string, AsyncRuns.RunArtifactDeclaration>
|
|
833
867
|
| undefined,
|
|
834
868
|
);
|
|
869
|
+
const reviewEvidence = readBoundedReviewEvidence(
|
|
870
|
+
status,
|
|
871
|
+
Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
|
|
872
|
+
);
|
|
835
873
|
const details = artifactManifest
|
|
836
874
|
? {
|
|
837
875
|
...status,
|
|
876
|
+
...(reviewEvidence ? { review_evidence: reviewEvidence } : {}),
|
|
838
877
|
artifact_manifest: artifactManifest,
|
|
839
878
|
next_actions: ToolsResponse.artifactNextActions(
|
|
840
879
|
status.run ?? runId,
|
|
841
880
|
asRecord(status.artifacts),
|
|
842
881
|
),
|
|
843
882
|
}
|
|
844
|
-
:
|
|
883
|
+
: {
|
|
884
|
+
...status,
|
|
885
|
+
...(reviewEvidence ? { review_evidence: reviewEvidence } : {}),
|
|
886
|
+
};
|
|
845
887
|
return {
|
|
846
888
|
content: [
|
|
847
889
|
{
|
package/lib/tools-register.ts
CHANGED
|
@@ -38,9 +38,6 @@ export function createRegisterToolDefinition<TContext>(
|
|
|
38
38
|
),
|
|
39
39
|
draft: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.draft),
|
|
40
40
|
name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
|
|
41
|
-
state_dir: stringSchema(
|
|
42
|
-
Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.state_dir,
|
|
43
|
-
),
|
|
44
41
|
template: unionSchema([
|
|
45
42
|
stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
|
|
46
43
|
looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
|
package/lib/tools-spawn.ts
CHANGED
|
@@ -143,9 +143,6 @@ export function createSpawnToolDefinition<
|
|
|
143
143
|
recipe: Schema.stringSchema(
|
|
144
144
|
"Alias for file; template recipe JSON file/name to spawn.",
|
|
145
145
|
),
|
|
146
|
-
state_dir: Schema.stringSchema(
|
|
147
|
-
"Optional explicit run state directory.",
|
|
148
|
-
),
|
|
149
146
|
template: Schema.unionSchema([
|
|
150
147
|
Schema.stringSchema("Inline command template string"),
|
|
151
148
|
Schema.arraySchema(
|
|
@@ -172,6 +169,11 @@ export function createSpawnToolDefinition<
|
|
|
172
169
|
ctx: TContext,
|
|
173
170
|
) {
|
|
174
171
|
const input = asRecord(params);
|
|
172
|
+
if (input.state_dir !== undefined) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"spawn.state_dir is not supported; run state is runtime-owned so run:<id> remains addressable and retention-safe.",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
175
177
|
const runId = runIdFromActorAddress(
|
|
176
178
|
typeof input.as === "string" ? input.as : undefined,
|
|
177
179
|
);
|
|
@@ -189,8 +191,6 @@ export function createSpawnToolDefinition<
|
|
|
189
191
|
launch_source: "spawn",
|
|
190
192
|
ownerId: getRunOwnerId(ctx),
|
|
191
193
|
run_id: runId,
|
|
192
|
-
state_dir:
|
|
193
|
-
typeof input.state_dir === "string" ? input.state_dir : undefined,
|
|
194
194
|
...(input.template !== undefined
|
|
195
195
|
? {
|
|
196
196
|
template:
|
package/lib/tools.ts
CHANGED
|
@@ -32,6 +32,14 @@ export interface CoreActorToolDefinitionDeps<
|
|
|
32
32
|
setActiveTools: (toolNames: string[]) => void;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export function resolveActiveRuntimeTool(
|
|
36
|
+
name: string,
|
|
37
|
+
activeTools: Pick<Map<string, unknown>, "has">,
|
|
38
|
+
getDefinition: (name: string) => unknown,
|
|
39
|
+
): unknown {
|
|
40
|
+
return activeTools.has(name) ? getDefinition(name) : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
export const RESERVED_TOOL_NAMES = new Set([
|
|
36
44
|
"read",
|
|
37
45
|
"write",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-actors",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Local Actor Kernel for Pi",
|
|
6
6
|
"keywords": [
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
],
|
|
52
52
|
"pi": {
|
|
53
53
|
"extensions": [
|
|
54
|
-
"./dist/index.js"
|
|
54
|
+
"./dist/pi-actors/index.js"
|
|
55
55
|
],
|
|
56
56
|
"sourceExtensions": [
|
|
57
57
|
"./index.ts"
|
|
@@ -28,5 +28,6 @@
|
|
|
28
28
|
"run.failed"
|
|
29
29
|
]
|
|
30
30
|
},
|
|
31
|
-
"
|
|
31
|
+
"accept_output": "review_evidence",
|
|
32
|
+
"template": "pi -p --model {model} --thinking {thinking} {tools?--tools:--no-tools} {tools} Judge this report: {report}. Rubric: {rubric}. Evidence: {evidence}. Begin the response with ACTOR_REVIEW_RESULT on its own line. Output format: {output_format}"
|
|
32
33
|
}
|
|
@@ -30,5 +30,6 @@
|
|
|
30
30
|
"run.failed"
|
|
31
31
|
]
|
|
32
32
|
},
|
|
33
|
-
"
|
|
33
|
+
"accept_output": "review_evidence",
|
|
34
|
+
"template": "pi -p --model {model} --thinking {thinking} {tools?--tools:--no-tools} {tools} Merge these subagent outputs using mode {mode}: {inputs}. Merge policy: {merge_policy}. Risk policy: {risk_policy}. Begin the response with ACTOR_REVIEW_RESULT on its own line. Output format: {output_format}"
|
|
34
35
|
}
|
|
@@ -26,5 +26,6 @@
|
|
|
26
26
|
"run.failed"
|
|
27
27
|
]
|
|
28
28
|
},
|
|
29
|
-
"
|
|
29
|
+
"accept_output": "review_evidence",
|
|
30
|
+
"template": "pi -p --model {model} --thinking {thinking} {tools?--tools:--no-tools} {tools} Normalize this subagent output. Begin the response with ACTOR_REVIEW_RESULT on its own line. Target format: {format}. Preservation policy: {preservation_policy}. Input: {input}"
|
|
30
31
|
}
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
"values": {
|
|
175
175
|
"input": "Use merged and judged output from stdin.",
|
|
176
176
|
"format": "{output_format}",
|
|
177
|
-
"preservation_policy": "Preserve branch status and explicitly mark Status as complete, degraded, or insufficient_data.",
|
|
177
|
+
"preservation_policy": "Preserve branch status and explicitly mark Status as complete, degraded, or insufficient_data. Include stable ACTOR_EVIDENCE_REF values from stdin in the Evidence section for every reviewer, verifier, merger, and judge source used. Do not claim complete when any required evidence reference is absent or inconsistent.",
|
|
178
178
|
"model": "{merger_model}",
|
|
179
179
|
"thinking": "{thinking}",
|
|
180
180
|
"tools": "{tools}"
|
|
@@ -32,5 +32,6 @@
|
|
|
32
32
|
"run.failed"
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
|
-
"
|
|
35
|
+
"accept_output": "review_evidence",
|
|
36
|
+
"template": "pi -p --model {model} --thinking {thinking} {tools?--tools:--no-tools} {tools} Review {scope} through this lens: {lens}. Instructions: {instructions}. Evidence policy: {evidence_policy}. Risk policy: {risk_policy}. Begin the response with ACTOR_REVIEW_RESULT on its own line. Output format: {output_format}"
|
|
36
37
|
}
|
|
@@ -29,5 +29,6 @@
|
|
|
29
29
|
"run.failed"
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
|
-
"
|
|
32
|
+
"accept_output": "review_evidence",
|
|
33
|
+
"template": "pi -p --model {model} --thinking {thinking} {tools?--tools:--no-tools} {tools} Verify this claim against the provided evidence. Claim: {claim}. Evidence or artifact paths: {evidence}. Acceptance: {acceptance}. Evidence policy: {evidence_policy}. Begin the response with ACTOR_REVIEW_RESULT on its own line. Output format: {output_format}"
|
|
33
34
|
}
|