@llblab/pi-actors 0.42.0 → 0.42.2
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 +2 -2
- package/BACKLOG.md +1 -5
- package/CHANGELOG.md +15 -0
- package/README.md +1 -1
- package/dist/lib/async-runs.d.ts +12 -0
- package/dist/lib/async-runs.js +53 -12
- package/dist/lib/command-templates.js +1 -1
- package/dist/lib/inspector-overlay.d.ts +5 -1
- package/dist/lib/inspector-overlay.js +104 -36
- package/dist/lib/inspector.js +1 -1
- package/dist/lib/observability.d.ts +12 -1
- package/dist/lib/observability.js +159 -80
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/runs-control.d.ts +2 -0
- package/dist/lib/runs-control.js +14 -1
- package/dist/lib/runs-ownership.js +17 -3
- package/dist/lib/runs-process.js +4 -3
- package/dist/lib/runs-start.js +1 -0
- package/dist/lib/runs-status.js +3 -0
- package/dist/lib/tools-inspect.js +2 -1
- package/dist/lib/tools-local.js +17 -2
- package/dist/lib/tools-spawn.js +10 -1
- package/dist/scripts/async-runner.mjs +24 -24
- package/dist/scripts/build-dist.mjs +6 -1
- package/dist/scripts/conformance.mjs +6 -1
- package/dist/scripts/recipe-utils.mjs +3 -3
- package/dist/skills/actors/SKILL.md +3 -3
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-inspector.md +3 -3
- package/docs/async-runs.md +10 -4
- package/docs/recipe-library.md +1 -1
- package/docs/tool-registry.md +2 -0
- package/lib/async-runs.ts +72 -12
- package/lib/command-templates.ts +1 -1
- package/lib/inspector-overlay.ts +129 -36
- package/lib/inspector.ts +1 -1
- package/lib/observability.ts +194 -76
- package/lib/prompts.ts +1 -1
- package/lib/runs-control.ts +20 -1
- package/lib/runs-ownership.ts +22 -3
- package/lib/runs-process.ts +4 -3
- package/lib/runs-start.ts +1 -0
- package/lib/runs-status.ts +5 -0
- package/lib/tools-inspect.ts +2 -1
- package/lib/tools-local.ts +21 -2
- package/lib/tools-spawn.ts +14 -1
- package/package.json +4 -3
- package/scripts/async-runner.mjs +24 -24
- package/scripts/build-dist.mjs +6 -1
- package/scripts/conformance.mjs +6 -1
- package/scripts/recipe-utils.mjs +3 -3
- package/skills/actors/SKILL.md +3 -3
- package/skills/swarm/SKILL.md +1 -1
package/lib/prompts.ts
CHANGED
|
@@ -29,7 +29,7 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
|
29
29
|
- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
|
|
30
30
|
- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.
|
|
31
31
|
- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.
|
|
32
|
-
- Run state lives under ~/.pi/agent/tmp/pi-actors/runs. Inspect intentionally and avoid busy-polling. Terminal and coordinator-bound notifications queue as Pi follow-ups so concurrently completed actors can reach the coordinator after current work instead of steering between tool calls. When a deferred actor result gates the next step, wait for its terminal follow-up; do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue/stuck run.
|
|
32
|
+
- Run state lives under ~/.pi/agent/tmp/pi-actors/runs. Inspect intentionally and avoid busy-polling. Terminal and coordinator-bound notifications queue as Pi follow-ups so concurrently completed actors can reach the coordinator after current work instead of steering between tool calls. Terminal follow-up content stays minimal: run, status, one base path, and relative artifact names only; semantic output stays in non-LLM details and run state. When a deferred actor result gates the next step, wait for its terminal follow-up; do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue/stuck run.
|
|
33
33
|
- 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.
|
|
34
34
|
- Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.
|
|
35
35
|
- For any non-trivial actor use or pi-actors change, read the bundled actors skill first. Before launching multiple actors/subagents for parallel implementation, independent artifact generation, delegated audit, or review, also read the bundled swarm skill; the coordinator owns decomposition, disjoint scopes, launch correctness, integration, and final validation. For deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.`;
|
package/lib/runs-control.ts
CHANGED
|
@@ -42,6 +42,7 @@ export function getRunProcessSignalPlan(
|
|
|
42
42
|
export interface RunProcessSignalDeps {
|
|
43
43
|
killProcess?: typeof process.kill;
|
|
44
44
|
runtimePlatform?: NodeJS.Platform;
|
|
45
|
+
spawnProcess?: typeof spawnSync;
|
|
45
46
|
verifyIdentity?: (
|
|
46
47
|
pid: number,
|
|
47
48
|
expected: RunProcessIdentity,
|
|
@@ -70,8 +71,26 @@ export function signalOwnedRunProcess(
|
|
|
70
71
|
}
|
|
71
72
|
const plan = getRunProcessSignalPlan(pid, signal, runtimePlatform);
|
|
72
73
|
if (plan.command && plan.args) {
|
|
73
|
-
const
|
|
74
|
+
const spawnProcess = deps.spawnProcess ?? spawnSync;
|
|
75
|
+
let result = spawnProcess(plan.command, plan.args, { encoding: "utf8" });
|
|
76
|
+
if (
|
|
77
|
+
runtimePlatform === "win32" &&
|
|
78
|
+
result.status !== 0 &&
|
|
79
|
+
!plan.args.includes("/F")
|
|
80
|
+
) {
|
|
81
|
+
result = spawnProcess(plan.command, [...plan.args, "/F"], {
|
|
82
|
+
encoding: "utf8",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
74
85
|
if (result.status !== 0) {
|
|
86
|
+
if (expectedIdentity) {
|
|
87
|
+
const finalProof = (deps.verifyIdentity ?? verifyRunProcessIdentity)(
|
|
88
|
+
pid,
|
|
89
|
+
expectedIdentity,
|
|
90
|
+
runtimePlatform,
|
|
91
|
+
);
|
|
92
|
+
if (finalProof.status === "dead_pid") return plan;
|
|
93
|
+
}
|
|
75
94
|
throw new Error(
|
|
76
95
|
result.stderr?.trim() ||
|
|
77
96
|
result.stdout?.trim() ||
|
package/lib/runs-ownership.ts
CHANGED
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
realpathSync,
|
|
13
13
|
} from "node:fs";
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
|
-
import {
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
16
17
|
|
|
17
18
|
import { writeJsonAtomic } from "./file-state.ts";
|
|
18
19
|
|
|
@@ -35,13 +36,31 @@ function comparablePath(path: string): string {
|
|
|
35
36
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
function isSystemTempRootAlias(resolved: string, canonical: string): boolean {
|
|
40
|
+
const tempRoot = resolve(tmpdir());
|
|
41
|
+
const relativeStateDir = relative(tempRoot, resolved);
|
|
42
|
+
if (
|
|
43
|
+
relativeStateDir === ".." ||
|
|
44
|
+
relativeStateDir.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) ||
|
|
45
|
+
isAbsolute(relativeStateDir)
|
|
46
|
+
) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const canonicalTempRoot = realpathSync.native(tempRoot);
|
|
50
|
+
const expectedCanonical = resolve(canonicalTempRoot, relativeStateDir);
|
|
51
|
+
return comparablePath(canonical) === comparablePath(expectedCanonical);
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
function assertCanonicalDirectory(stateDir: string): string {
|
|
39
55
|
const resolved = resolve(stateDir);
|
|
40
56
|
if (lstatSync(resolved).isSymbolicLink()) {
|
|
41
57
|
throw new Error(`Run state directory cannot be a symlink: ${resolved}`);
|
|
42
58
|
}
|
|
43
|
-
const canonical = realpathSync(resolved);
|
|
44
|
-
if (
|
|
59
|
+
const canonical = realpathSync.native(resolved);
|
|
60
|
+
if (
|
|
61
|
+
comparablePath(canonical) !== comparablePath(resolved) &&
|
|
62
|
+
!isSystemTempRootAlias(resolved, canonical)
|
|
63
|
+
) {
|
|
45
64
|
throw new Error(`Run state directory has an ambiguous symlink alias: ${resolved}`);
|
|
46
65
|
}
|
|
47
66
|
return resolved;
|
package/lib/runs-process.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { existsSync, readFileSync, readlinkSync, realpathSync } from "node:fs";
|
|
8
8
|
import { platform } from "node:os";
|
|
9
|
-
import {
|
|
9
|
+
import { posix, win32 } from "node:path";
|
|
10
10
|
|
|
11
11
|
export interface RunProcessIdentity {
|
|
12
12
|
command: string;
|
|
@@ -125,8 +125,9 @@ export function captureRunProcessIdentity(
|
|
|
125
125
|
if (!identity.command.includes(runnerPath) || !identity.command.includes(stateDir)) {
|
|
126
126
|
return undefined;
|
|
127
127
|
}
|
|
128
|
-
const
|
|
129
|
-
const
|
|
128
|
+
const pathApi = runtimePlatform === "win32" ? win32 : posix;
|
|
129
|
+
const resolvedCwd = pathApi.resolve(cwd);
|
|
130
|
+
const canonicalCwd = runtimePlatform === platform() && existsSync(resolvedCwd)
|
|
130
131
|
? realpathSync.native(resolvedCwd)
|
|
131
132
|
: resolvedCwd;
|
|
132
133
|
const expectedCwd =
|
package/lib/runs-start.ts
CHANGED
package/lib/runs-status.ts
CHANGED
|
@@ -61,6 +61,9 @@ export function buildRunStatus(
|
|
|
61
61
|
? "running"
|
|
62
62
|
: (getInterruptedRunStatus(stateDir) ?? "exited");
|
|
63
63
|
const terminalHandled = readJson(join(stateDir, "terminal-handled.json"));
|
|
64
|
+
const terminalDeliveryFailure = readJson(
|
|
65
|
+
join(stateDir, "terminal-delivery-failure.json"),
|
|
66
|
+
);
|
|
64
67
|
return {
|
|
65
68
|
...meta,
|
|
66
69
|
eventsFile: join(stateDir, "events.jsonl"),
|
|
@@ -70,6 +73,8 @@ export function buildRunStatus(
|
|
|
70
73
|
process_identity_status: processIdentity.status,
|
|
71
74
|
progress: readJson(join(stateDir, "progress.json")) || null,
|
|
72
75
|
result: result || null,
|
|
76
|
+
...(terminalDeliveryFailure
|
|
77
|
+
? { terminal_delivery_failure: terminalDeliveryFailure } : {}),
|
|
73
78
|
...(terminalHandled ? { terminal_handled: terminalHandled } : {}),
|
|
74
79
|
state_dir: String(meta.state_dir ?? stateDir),
|
|
75
80
|
stderrLog: join(stateDir, "stderr.log"),
|
package/lib/tools-inspect.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
10
11
|
|
|
11
12
|
import * as AsyncRuns from "./async-runs.ts";
|
|
12
13
|
import * as Limits from "./limits.ts";
|
|
@@ -269,7 +270,7 @@ function getPiActorsRuntimeStatus(): Record<string, unknown> {
|
|
|
269
270
|
} catch {
|
|
270
271
|
git_commit = undefined;
|
|
271
272
|
}
|
|
272
|
-
const entrypoint =
|
|
273
|
+
const entrypoint = fileURLToPath(import.meta.url);
|
|
273
274
|
return {
|
|
274
275
|
automatic_recipe_review: Paths.isAutomaticRecipeReviewEnabled(),
|
|
275
276
|
entrypoint,
|
package/lib/tools-local.ts
CHANGED
|
@@ -143,6 +143,14 @@ export function createRuntimeToolDefinition(
|
|
|
143
143
|
paramSchema.run_id = Schema.stringSchema(
|
|
144
144
|
"Optional run id override for this async template recipe invocation.",
|
|
145
145
|
);
|
|
146
|
+
if (isAsyncRecipe) {
|
|
147
|
+
paramSchema.correlation_id = Schema.stringSchema(
|
|
148
|
+
"Optional workflow correlation id preserved in terminal follow-up delivery.",
|
|
149
|
+
);
|
|
150
|
+
paramSchema.transport_context = Schema.looseObjectSchema(
|
|
151
|
+
"Optional originating transport route preserved for detached terminal follow-up.",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
146
154
|
return {
|
|
147
155
|
name: cfg.name,
|
|
148
156
|
label: cfg.name,
|
|
@@ -155,7 +163,7 @@ export function createRuntimeToolDefinition(
|
|
|
155
163
|
)
|
|
156
164
|
: Prompts.formatRegisteredToolPromptSnippet(cfg.template),
|
|
157
165
|
async execute(
|
|
158
|
-
|
|
166
|
+
toolCallId: string,
|
|
159
167
|
params: unknown,
|
|
160
168
|
signal: AbortSignal | undefined,
|
|
161
169
|
_onUpdate: unknown,
|
|
@@ -172,7 +180,7 @@ export function createRuntimeToolDefinition(
|
|
|
172
180
|
}
|
|
173
181
|
if (isAsyncRecipe) {
|
|
174
182
|
const input = params as Record<string, unknown>;
|
|
175
|
-
const { run_id, ...values } = input;
|
|
183
|
+
const { correlation_id, run_id, transport_context, ...values } = input;
|
|
176
184
|
const base = cfg.recipe ? cfg.recipe : { file: String(cfg.template) };
|
|
177
185
|
const runId =
|
|
178
186
|
typeof run_id === "string" && run_id.trim()
|
|
@@ -182,6 +190,17 @@ export function createRuntimeToolDefinition(
|
|
|
182
190
|
{
|
|
183
191
|
...base,
|
|
184
192
|
launch_source: "tool",
|
|
193
|
+
launch_correlation: {
|
|
194
|
+
...(typeof correlation_id === "string"
|
|
195
|
+
? { correlation_id } : {}),
|
|
196
|
+
tool_call_id: toolCallId,
|
|
197
|
+
},
|
|
198
|
+
...(transport_context &&
|
|
199
|
+
typeof transport_context === "object" &&
|
|
200
|
+
!Array.isArray(transport_context)
|
|
201
|
+
? {
|
|
202
|
+
transport_context: transport_context as Record<string, unknown>,
|
|
203
|
+
} : {}),
|
|
185
204
|
ownerId: getRunOwnerId(ctx),
|
|
186
205
|
run_id: runId,
|
|
187
206
|
tool: cfg.name,
|
package/lib/tools-spawn.ts
CHANGED
|
@@ -144,6 +144,9 @@ export function createSpawnToolDefinition<
|
|
|
144
144
|
as: Schema.stringSchema(
|
|
145
145
|
"Optional actor address for the spawned run, e.g. run:<id>.",
|
|
146
146
|
),
|
|
147
|
+
correlation_id: Schema.stringSchema(
|
|
148
|
+
"Optional workflow correlation id preserved in terminal follow-up delivery.",
|
|
149
|
+
),
|
|
147
150
|
file: Schema.stringSchema(
|
|
148
151
|
"Optional template recipe JSON file. Bare names resolve under ~/.pi/agent/recipes.",
|
|
149
152
|
),
|
|
@@ -162,6 +165,9 @@ export function createSpawnToolDefinition<
|
|
|
162
165
|
values: Schema.looseObjectSchema(
|
|
163
166
|
"Runtime placeholder values passed to the actor.",
|
|
164
167
|
),
|
|
168
|
+
transport_context: Schema.looseObjectSchema(
|
|
169
|
+
"Optional originating transport route preserved for detached terminal follow-up, e.g. Telegram chat_id and thread_id.",
|
|
170
|
+
),
|
|
165
171
|
verbose: Schema.booleanSchema(
|
|
166
172
|
"Return full JSON instead of compact text.",
|
|
167
173
|
),
|
|
@@ -169,7 +175,7 @@ export function createSpawnToolDefinition<
|
|
|
169
175
|
[],
|
|
170
176
|
),
|
|
171
177
|
async execute(
|
|
172
|
-
|
|
178
|
+
toolCallId: string,
|
|
173
179
|
params: unknown,
|
|
174
180
|
_signal: AbortSignal | undefined,
|
|
175
181
|
_onUpdate: unknown,
|
|
@@ -196,7 +202,14 @@ export function createSpawnToolDefinition<
|
|
|
196
202
|
{
|
|
197
203
|
file: recipe,
|
|
198
204
|
launch_source: "spawn",
|
|
205
|
+
launch_correlation: {
|
|
206
|
+
...(typeof input.correlation_id === "string"
|
|
207
|
+
? { correlation_id: input.correlation_id } : {}),
|
|
208
|
+
tool_call_id: toolCallId,
|
|
209
|
+
},
|
|
199
210
|
ownerId: getRunOwnerId(ctx),
|
|
211
|
+
...(input.transport_context
|
|
212
|
+
? { transport_context: asRecord(input.transport_context) } : {}),
|
|
200
213
|
run_id: runId,
|
|
201
214
|
...(input.template !== undefined
|
|
202
215
|
? {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-actors",
|
|
3
|
-
"version": "0.42.
|
|
3
|
+
"version": "0.42.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Local Actor Kernel for Pi",
|
|
6
6
|
"keywords": [
|
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"check": "node --experimental-strip-types -e \"await import('./index.ts'); console.log('pi-actors: extension import ok')\"",
|
|
29
29
|
"conformance": "node scripts/conformance.mjs",
|
|
30
|
-
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
30
|
+
"test": "node --experimental-strip-types --test --test-concurrency=1 tests/*.test.ts",
|
|
31
31
|
"pack:dry": "npm pack --dry-run",
|
|
32
32
|
"recipes:qa": "node scripts/validate-recipe.mjs recipes --all --qa --summary",
|
|
33
33
|
"release:validate": "npm run validate && node scripts/release-gates.mjs",
|
|
34
|
-
"validate": "npx tsc --noEmit && npm run recipes:qa && npm run build && npm run check && npm test && npm run conformance && npm
|
|
34
|
+
"validate": "npx tsc --noEmit && npm run recipes:qa && npm run build && npm run check && npm test && npm run conformance && npm run pack:dry",
|
|
35
|
+
"audit:dependencies": "npm audit --audit-level=high --omit=peer",
|
|
35
36
|
"build": "node scripts/build-dist.mjs",
|
|
36
37
|
"prepack": "npm run build"
|
|
37
38
|
},
|
package/scripts/async-runner.mjs
CHANGED
|
@@ -135,7 +135,7 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
135
135
|
try {
|
|
136
136
|
return readdirSync(sessionDir, { withFileTypes: true })
|
|
137
137
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
|
|
138
|
-
.map((entry) => relative(stateDir, join(sessionDir, entry.name)))
|
|
138
|
+
.map((entry) => relative(stateDir, join(sessionDir, entry.name)).replaceAll("\\", "/"))
|
|
139
139
|
.sort();
|
|
140
140
|
} catch {
|
|
141
141
|
return [];
|
|
@@ -167,12 +167,12 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
167
167
|
...(recipeContext ? { recipe_context: recipeContext } : {}),
|
|
168
168
|
command: commandDetail,
|
|
169
169
|
...(materialized.promptFile
|
|
170
|
-
? { prompt_file: relative(stateDir, materialized.promptFile) }
|
|
170
|
+
? { prompt_file: relative(stateDir, materialized.promptFile).replaceAll("\\", "/") }
|
|
171
171
|
: {}),
|
|
172
172
|
...(materialized.promptBytes
|
|
173
173
|
? { prompt_bytes: materialized.promptBytes }
|
|
174
174
|
: {}),
|
|
175
|
-
...(sessionDir ? { session_dir: relative(stateDir, sessionDir) } : {}),
|
|
175
|
+
...(sessionDir ? { session_dir: relative(stateDir, sessionDir).replaceAll("\\", "/") } : {}),
|
|
176
176
|
attempts: [],
|
|
177
177
|
semantic_acceptance:
|
|
178
178
|
options?.evidenceContext?.acceptOutput === "review_evidence" ||
|
|
@@ -209,11 +209,11 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
209
209
|
attempts.push({
|
|
210
210
|
attempt,
|
|
211
211
|
stdout: {
|
|
212
|
-
path: relative(stateDir, stdoutFile),
|
|
212
|
+
path: relative(stateDir, stdoutFile).replaceAll("\\", "/"),
|
|
213
213
|
bytes: existsSync(stdoutFile) ? statSync(stdoutFile).size : 0,
|
|
214
214
|
},
|
|
215
215
|
stderr: {
|
|
216
|
-
path: relative(stateDir, stderrFile),
|
|
216
|
+
path: relative(stateDir, stderrFile).replaceAll("\\", "/"),
|
|
217
217
|
bytes: existsSync(stderrFile) ? statSync(stderrFile).size : 0,
|
|
218
218
|
},
|
|
219
219
|
});
|
|
@@ -248,12 +248,12 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
248
248
|
...(recipeContext ? { recipe_context: recipeContext } : {}),
|
|
249
249
|
command: commandDetail,
|
|
250
250
|
...(materialized.promptFile
|
|
251
|
-
? { prompt_file: relative(stateDir, materialized.promptFile) }
|
|
251
|
+
? { prompt_file: relative(stateDir, materialized.promptFile).replaceAll("\\", "/") }
|
|
252
252
|
: {}),
|
|
253
253
|
...(materialized.promptBytes
|
|
254
254
|
? { prompt_bytes: materialized.promptBytes }
|
|
255
255
|
: {}),
|
|
256
|
-
...(sessionDir ? { session_dir: relative(stateDir, sessionDir) } : {}),
|
|
256
|
+
...(sessionDir ? { session_dir: relative(stateDir, sessionDir).replaceAll("\\", "/") } : {}),
|
|
257
257
|
...(commandSessionFiles(sessionDir).length > 0
|
|
258
258
|
? { session_files: commandSessionFiles(sessionDir) }
|
|
259
259
|
: {}),
|
|
@@ -386,7 +386,7 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
386
386
|
command: commandDetail,
|
|
387
387
|
...(materialized.promptFile ? { prompt_file: materialized.promptFile } : {}),
|
|
388
388
|
...(materialized.promptBytes ? { prompt_bytes: materialized.promptBytes } : {}),
|
|
389
|
-
...(session.sessionDir ? { session_dir: relative(stateDir, session.sessionDir) } : {}),
|
|
389
|
+
...(session.sessionDir ? { session_dir: relative(stateDir, session.sessionDir).replaceAll("\\", "/") } : {}),
|
|
390
390
|
});
|
|
391
391
|
progressRunning();
|
|
392
392
|
const captureDir = join(stateDir, "captures", commandId);
|
|
@@ -457,7 +457,7 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
457
457
|
...captureDetails(result),
|
|
458
458
|
...(materialized.promptFile ? { prompt_file: materialized.promptFile } : {}),
|
|
459
459
|
...(materialized.promptBytes ? { prompt_bytes: materialized.promptBytes } : {}),
|
|
460
|
-
...(session.sessionDir ? { session_dir: relative(stateDir, session.sessionDir) } : {}),
|
|
460
|
+
...(session.sessionDir ? { session_dir: relative(stateDir, session.sessionDir).replaceAll("\\", "/") } : {}),
|
|
461
461
|
...(commandSessionFiles(session.sessionDir).length > 0
|
|
462
462
|
? { session_files: commandSessionFiles(session.sessionDir) }
|
|
463
463
|
: {}),
|
|
@@ -517,6 +517,11 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
517
517
|
};
|
|
518
518
|
throw error;
|
|
519
519
|
}
|
|
520
|
+
writeEvidenceManifest("done");
|
|
521
|
+
progress("done", {
|
|
522
|
+
completed: 1,
|
|
523
|
+
failures: result.details.nonCriticalFailures || [],
|
|
524
|
+
});
|
|
520
525
|
writeJsonAtomic(resultPath, {
|
|
521
526
|
code: result.details.code,
|
|
522
527
|
command: result.details.command,
|
|
@@ -525,26 +530,11 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
525
530
|
truncated: result.details.truncated,
|
|
526
531
|
completedAt: new Date().toISOString(),
|
|
527
532
|
});
|
|
528
|
-
writeEvidenceManifest("done");
|
|
529
|
-
progress("done", {
|
|
530
|
-
completed: 1,
|
|
531
|
-
failures: result.details.nonCriticalFailures || [],
|
|
532
|
-
});
|
|
533
533
|
event("run.done", { code: result.details.code });
|
|
534
534
|
} catch (error) {
|
|
535
535
|
const message = error instanceof Error ? error.message : String(error);
|
|
536
536
|
const details = error && typeof error === "object" ? error.details : undefined;
|
|
537
537
|
appendFileSync(stderrPath, `${message}\n`);
|
|
538
|
-
writeJsonAtomic(resultPath, {
|
|
539
|
-
code: typeof details?.code === "number" ? details.code : 1,
|
|
540
|
-
error: message,
|
|
541
|
-
killed: Boolean(details?.killed),
|
|
542
|
-
...(Array.isArray(details?.branches) ? { branches: details.branches } : {}),
|
|
543
|
-
...(details?.failureReason ? { failure_reason: details.failureReason } : {}),
|
|
544
|
-
...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
|
|
545
|
-
...(details?.softQuorum ? { soft_quorum: details.softQuorum } : {}),
|
|
546
|
-
completedAt: new Date().toISOString(),
|
|
547
|
-
});
|
|
548
538
|
writeEvidenceManifest("failed");
|
|
549
539
|
progress("failed", {
|
|
550
540
|
completed: 0,
|
|
@@ -555,6 +545,16 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
555
545
|
: [{ message }],
|
|
556
546
|
...(details?.failureReason ? { failureReason: details.failureReason } : {}),
|
|
557
547
|
});
|
|
548
|
+
writeJsonAtomic(resultPath, {
|
|
549
|
+
code: typeof details?.code === "number" ? details.code : 1,
|
|
550
|
+
error: message,
|
|
551
|
+
killed: Boolean(details?.killed),
|
|
552
|
+
...(Array.isArray(details?.branches) ? { branches: details.branches } : {}),
|
|
553
|
+
...(details?.failureReason ? { failure_reason: details.failureReason } : {}),
|
|
554
|
+
...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
|
|
555
|
+
...(details?.softQuorum ? { soft_quorum: details.softQuorum } : {}),
|
|
556
|
+
completedAt: new Date().toISOString(),
|
|
557
|
+
});
|
|
558
558
|
event("run.failed", {
|
|
559
559
|
error: message,
|
|
560
560
|
...(details?.failureReason ? { failure_reason: details.failureReason } : {}),
|
package/scripts/build-dist.mjs
CHANGED
|
@@ -20,13 +20,18 @@ import { join } from "node:path";
|
|
|
20
20
|
|
|
21
21
|
function run(command, args) {
|
|
22
22
|
const result = spawnSync(command, args, { stdio: "inherit" });
|
|
23
|
+
if (result.error) throw result.error;
|
|
23
24
|
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
rmSync("dist", { recursive: true, force: true });
|
|
27
28
|
mkdirSync("dist", { recursive: true });
|
|
28
29
|
|
|
29
|
-
run(
|
|
30
|
+
run(process.execPath, [
|
|
31
|
+
join("node_modules", "typescript", "bin", "tsc"),
|
|
32
|
+
"-p",
|
|
33
|
+
"tsconfig.build.json",
|
|
34
|
+
]);
|
|
30
35
|
|
|
31
36
|
mkdirSync(join("dist", "pi-actors"), { recursive: true });
|
|
32
37
|
writeFileSync(
|
package/scripts/conformance.mjs
CHANGED
|
@@ -28,7 +28,12 @@ function packageRoot() {
|
|
|
28
28
|
|
|
29
29
|
const result = spawnSync(
|
|
30
30
|
process.execPath,
|
|
31
|
-
[
|
|
31
|
+
[
|
|
32
|
+
"--experimental-strip-types",
|
|
33
|
+
"--test",
|
|
34
|
+
"--test-concurrency=1",
|
|
35
|
+
...conformanceSuites,
|
|
36
|
+
],
|
|
32
37
|
{ cwd: packageRoot(), encoding: "utf8", stdio: "pipe" },
|
|
33
38
|
);
|
|
34
39
|
|
package/scripts/recipe-utils.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
statSync,
|
|
17
17
|
writeFileSync,
|
|
18
18
|
} from "node:fs";
|
|
19
|
-
import { dirname, extname, join, relative, resolve } from "node:path";
|
|
19
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
20
20
|
|
|
21
21
|
function usage() {
|
|
22
22
|
console.error(`Usage:
|
|
@@ -101,7 +101,7 @@ function collectRunSummary(rootValue) {
|
|
|
101
101
|
const root = resolve(
|
|
102
102
|
rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
103
103
|
);
|
|
104
|
-
const files = walkFiles(root, 2).filter((file) => file
|
|
104
|
+
const files = walkFiles(root, 2).filter((file) => basename(file) === "run.json");
|
|
105
105
|
const rows = [];
|
|
106
106
|
for (const file of files) {
|
|
107
107
|
const run = readJson(file);
|
|
@@ -118,7 +118,7 @@ function collectRunSummary(rootValue) {
|
|
|
118
118
|
const progress = readJson(join(runDir, "progress.json"));
|
|
119
119
|
const result = readJson(join(runDir, "result.json"));
|
|
120
120
|
rows.push({
|
|
121
|
-
run: run.run_id ?? run.run ?? relative(root, file).split(
|
|
121
|
+
run: run.run_id ?? run.run ?? relative(root, file).split(sep)[0],
|
|
122
122
|
status: getRunStatus(run, progress, result),
|
|
123
123
|
recipe: run.recipe ?? run.recipe_file ?? "",
|
|
124
124
|
updated:
|
package/skills/actors/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Required practical guide for non-trivial pi-actors use, including parallel actor launches, subagent fanout, and autonomous coordinator workflows. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.42.
|
|
5
|
+
version: 0.42.2
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
|
@@ -69,7 +69,7 @@ Rules:
|
|
|
69
69
|
- Command-template strings execute directly without a shell. Operators such as `&&`, `||`, pipes, redirects, and `cd` remain literal argv unless an explicit trusted shell is the executable. Prefer absolute paths or template arrays for sequencing; put non-trivial shell behavior in a reviewed script.
|
|
70
70
|
- Use `file`/`recipe` for saved recipes; bare names resolve under `~/.pi/agent/recipes`.
|
|
71
71
|
- Use inline `template` for one-off experiments; promote useful repeats to recipes.
|
|
72
|
-
-
|
|
72
|
+
- Terminal follow-up context contains only run id, status, one base path, and relative artifact names. Inspect the run for contents; semantic output and correlation remain in non-LLM details and state. Decide whether a successful pattern deserves durable tool memory only after inspection, and ask before writing the user recipe root.
|
|
73
73
|
- Use stable `as` names when you will inspect or message the actor later.
|
|
74
74
|
- Public run state is runtime-owned; do not pass custom `state_dir` paths. This keeps `run:<id>` addressability and retention on one boundary.
|
|
75
75
|
- `async: true` on the recipe is the detached run switch.
|
|
@@ -126,7 +126,7 @@ Views:
|
|
|
126
126
|
- `artifacts`: declared artifact paths/status plus the same bounded owned review-evidence manifest when present.
|
|
127
127
|
- `recipes` target: registry summary for active, shadowed, invalid, disabled, and diagnostic recipe entries.
|
|
128
128
|
|
|
129
|
-
Let terminal notifications arrive. They queue through Pi's follow-up delivery mode, so a busy coordinator finishes its current work before receiving concurrently completed actor results; the host's `followUpMode` controls whether queued results arrive together or one at a time. File watching accelerates delivery, while a bounded ten-second terminal-only reconciliation pass recovers missed or failed watcher activity without replaying outbox traffic; watcher degradation and rearm remain visible diagnostics. When a deferred actor result gates the next step, wait for that terminal follow-up instead of scheduling continuation loops, repeatedly inspecting, or mutating the actor's reviewed scope. Idle coordinators still start a normal turn through `triggerTurn: true`. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue or stuck run.
|
|
129
|
+
Let terminal notifications arrive. They queue through Pi's follow-up delivery mode, so a busy coordinator finishes its current work before receiving concurrently completed actor results; the host's `followUpMode` controls whether queued results arrive together or one at a time. Their LLM context content stays limited to run id, status, one base path, and relative artifact names; inspect state for raw output while correlation and semantic details remain outside LLM context. File watching accelerates delivery, while a bounded ten-second terminal-only reconciliation pass recovers missed or failed watcher activity without replaying outbox traffic; watcher degradation and rearm remain visible diagnostics. When a deferred actor result gates the next step, wait for that terminal follow-up instead of scheduling continuation loops, repeatedly inspecting, or mutating the actor's reviewed scope. Idle coordinators still start a normal turn through `triggerTurn: true`. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue or stuck run.
|
|
130
130
|
|
|
131
131
|
## Runtime Communication Rules
|
|
132
132
|
|
package/skills/swarm/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: swarm
|
|
3
3
|
description: Subagent and actor orchestration with scoped locks, fanout, and quorum consensus. Use before launching multiple parallel actors or subagents for independent implementation, artifact generation, review, delegated audit, coordinated execution, or any workflow that needs autonomous coordinator decomposition and integration.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.42.
|
|
5
|
+
version: 0.42.2
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Swarm
|