@llblab/pi-actors 0.42.1 → 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/CHANGELOG.md +5 -0
- package/README.md +1 -3
- package/dist/lib/async-runs.js +7 -7
- package/dist/lib/inspector-overlay.d.ts +1 -0
- package/dist/lib/inspector-overlay.js +35 -23
- package/dist/lib/observability.d.ts +0 -1
- package/dist/lib/observability.js +24 -89
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/scripts/async-runner.mjs +15 -15
- package/dist/skills/actors/SKILL.md +3 -3
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-inspector.md +2 -2
- package/docs/async-runs.md +4 -4
- package/docs/recipe-library.md +1 -1
- package/lib/async-runs.ts +7 -7
- package/lib/inspector-overlay.ts +64 -23
- package/lib/observability.ts +28 -86
- package/lib/prompts.ts +1 -1
- package/package.json +1 -1
- package/scripts/async-runner.mjs +15 -15
- package/skills/actors/SKILL.md +3 -3
- package/skills/swarm/SKILL.md +1 -1
package/AGENTS.md
CHANGED
|
@@ -108,7 +108,7 @@ Pi host
|
|
|
108
108
|
- Preserve node controls: `when`, positive `timeout`, `delay`, bounded `retry`, `failure`, and `recover` cleanup.
|
|
109
109
|
- Persist every async command's complete byte-exact stdout/stderr under command- and retry-specific run-state paths while keeping returned tails bounded and pipeline stdin complete.
|
|
110
110
|
- Keep async run state under `~/.pi/agent/tmp/pi-actors/runs` with injected `{run_id}` and `{state_dir}` values.
|
|
111
|
-
- Preserve event-driven observability with bounded reconciliation: file watchers accelerate durable retrying terminal follow-up notifications, while a conservative terminal-only interval recovers missed watcher activity, rearms degraded watchers, and never replays outbox traffic. Queue coordinator
|
|
111
|
+
- Preserve event-driven observability with bounded reconciliation: file watchers accelerate durable retrying terminal follow-up notifications, while a conservative terminal-only interval recovers missed watcher activity, rearms degraded watchers, and never replays outbox traffic. Queue compact terminal coordinator notices through Pi follow-up delivery rather than steering so current work finishes before async results arrive and host follow-up batching policy can combine concurrent completions; LLM content contains only run id, status, one base path, and relative artifact names, while semantic output/error/correlation stays in non-LLM details and run state. Terminal delivery is at-least-once across the unavoidable send/handled-marker crash window; watch-triggered and periodic delivery share one live-runtime in-flight guard.
|
|
112
112
|
- 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 on operator request, meaningful actor event, or diagnosis of an overdue/stuck run.
|
|
113
113
|
- Do not restore busy-polling examples, duplicate terminal notifications, or duplicate notifications for handled `cancel`, `kill`, or control-stop actions.
|
|
114
114
|
|
|
@@ -142,7 +142,7 @@ Pi host
|
|
|
142
142
|
- Keep tail truncation, full-output temp files, failure formatting, and centralized limits intact.
|
|
143
143
|
- Published docs must not include machine-local absolute paths.
|
|
144
144
|
- Any view scanning run directories must apply coordinator/session ownership filters before exposing summaries or previews.
|
|
145
|
-
- Actor Inspector remains evidence-first; its `Kill` action is available only for a focused owned running run, requires in-overlay confirmation, revalidates exact ownership/status at action time, routes through canonical `control.kill`, and renders bounded success/failure feedback without direct process signaling.
|
|
145
|
+
- Actor Inspector remains evidence-first; its `Kill` action is available only for a focused owned running run, requires in-overlay confirmation, revalidates exact ownership/status at action time, routes through canonical `control.kill`, and renders bounded success/failure feedback without direct process signaling. Keep active key hints in the bottom border as one blue-key/border-accent-description rail joined by border-accent `─`, never as a dedicated body row or bullet-separated footer.
|
|
146
146
|
- Direct branch messages are active inbox queues; guard branch-local append/status rewrites with the branch inbox lock and keep claim/handled/failed transitions tested.
|
|
147
147
|
- Run-state launch and destructive retention require the runtime ownership marker bound to the canonical directory and run id; reject non-run directories, missing/mismatched markers, and symlink aliases rather than trusting `run.json`.
|
|
148
148
|
- Runner lifecycle and destructive process controls require the persisted cross-platform process identity proof (start time, command, and cwd where available), revalidated at authorization and immediately before signaling; dead, mismatched, or unsupported proofs stay distinct and fail closed rather than degrading to pid liveness. On Unix, only process-group `ESRCH` followed by another matching identity proof permits exact-pid fallback. Keep the residual post-read OS PID/PGID reuse window explicit because Node lacks one portable identity-stable group handle.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.42.2: Inspector Key Rail and Terminal Follow-up Context
|
|
6
|
+
|
|
7
|
+
- `[Inspector Key Rail]` Moved every Inspector hotkey hint onto the bottom border, replacing the dedicated two-row footer with a border-connected rail. Blue key labels remain, while descriptions and `─` connectors use the border accent instead of bullet separators; the main viewport cap rises from 16 to 24 rows. Impact: the Inspector gains two content rows without losing keyboard discoverability, and the kill confirmation dialog now shares the same visual grammar.
|
|
8
|
+
- `[Terminal Follow-up Context]` Kept terminal delivery on Pi's `followUp` queue but reduced LLM content to run id, status, one base path, and relative artifact names. Bounded semantic stdout/error payloads, correlation, and adapter transport metadata remain available only in non-LLM details and run state; the launcher writes initial progress before spawning its runner, and runners write terminal progress and review evidence before the `result.json` completion marker. Impact: completed background actors no longer inject raw output or unsolicited workflow prompts into coordinator context, and result readers never see an incomplete terminal state.
|
|
9
|
+
|
|
5
10
|
## 0.42.1: Terminal Delivery and Cross-platform Validation
|
|
6
11
|
|
|
7
12
|
- `[Terminal Delivery]` Added one bounded semantic terminal result with durable launch/tool-call correlation and bounded adapter-provided transport context. Explicit advertised semantic envelopes win; successful accepted reviews that advertise `review.completed` deterministically synthesize it when absent, while failed runs include their terminal error. Watcher and reconciliation delivery share the existing live in-flight dedupe guard, failed sends persist bounded retry evidence without writing the handled marker, and status exposes the latest failure. Impact: coordinators and Telegram-style chat/thread adapters can retain the exact launch/result relationship across detached completion instead of receiving only run-file metadata.
|
package/README.md
CHANGED
|
@@ -293,9 +293,7 @@ Packaged recipes are building blocks. Use `spawn file=<recipe>` for maintained p
|
|
|
293
293
|
| A useful output that should survive context compression | Artifacts |
|
|
294
294
|
| A repeated local workflow | Recipe/tool memory |
|
|
295
295
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
Terminal completion returns one bounded semantic result to the originating coordinator, not only run-state paths. `spawn` and saved async tools preserve their tool-call correlation automatically; callers may add `correlation_id` plus bounded `transport_context` when an adapter must retain an exact detached route, including Telegram `chat_id`/`thread_id`. Recipes that advertise `review.completed` either emit that envelope explicitly or receive one synthesized from successfully accepted review output. Watcher and periodic reconciliation share one in-flight guard, while failed sends remain retryable and visible in run status.
|
|
296
|
+
Terminal completion queues a minimal follow-up with run id, status, one base path, and relative artifact names only. Bounded semantic output, launch/tool-call correlation, and optional transport context remain in non-LLM follow-up details and run state, so adapters retain the exact launch/result relationship without injecting actor output into coordinator context. Inspect the run before deciding whether a successful pattern deserves recipe persistence; never auto-save without the operator's confirmation.
|
|
299
297
|
|
|
300
298
|
## Platform support
|
|
301
299
|
|
package/dist/lib/async-runs.js
CHANGED
|
@@ -321,6 +321,13 @@ export function startRun(params, cwd) {
|
|
|
321
321
|
? { transport_context: transportContext } : {}),
|
|
322
322
|
};
|
|
323
323
|
writeJsonAtomic(join(stateDir, "run.json"), meta);
|
|
324
|
+
writeJsonAtomic(join(stateDir, "progress.json"), {
|
|
325
|
+
completed: 0,
|
|
326
|
+
failures: [],
|
|
327
|
+
model_policy: modelPolicy,
|
|
328
|
+
phase: "starting",
|
|
329
|
+
updatedAt: new Date().toISOString(),
|
|
330
|
+
});
|
|
324
331
|
const child = spawn(process.execPath, argv, {
|
|
325
332
|
cwd,
|
|
326
333
|
detached: true,
|
|
@@ -333,13 +340,6 @@ export function startRun(params, cwd) {
|
|
|
333
340
|
if (processIdentity)
|
|
334
341
|
meta.process_identity = processIdentity;
|
|
335
342
|
writeJsonAtomic(join(stateDir, "run.json"), meta);
|
|
336
|
-
writeJsonAtomic(join(stateDir, "progress.json"), {
|
|
337
|
-
completed: 0,
|
|
338
|
-
failures: [],
|
|
339
|
-
model_policy: modelPolicy,
|
|
340
|
-
phase: "starting",
|
|
341
|
-
updatedAt: new Date().toISOString(),
|
|
342
|
-
});
|
|
343
343
|
writeFileSync(join(stateDir, "events.jsonl"), `${JSON.stringify({ event: "run.start", run, run_instance_id: meta.run_instance_id, pid: meta.pid, ts: new Date().toISOString() })}\n`, { flag: "a" });
|
|
344
344
|
child.unref();
|
|
345
345
|
return meta;
|
|
@@ -239,7 +239,7 @@ export class ActorInspectorOverlay {
|
|
|
239
239
|
if (selectorTop) {
|
|
240
240
|
const leadingBorder = "─".repeat(selectorAnchor);
|
|
241
241
|
const remainingBorder = "─".repeat(Math.max(0, innerWidth - visibleWidth(selectorTop) - selectorAnchor));
|
|
242
|
-
lines.push(`${this.theme.fg("
|
|
242
|
+
lines.push(`${this.theme.fg("borderAccent", `├${leadingBorder}`)}${selectorTop}${this.theme.fg("borderAccent", `${remainingBorder}┤`)}`);
|
|
243
243
|
}
|
|
244
244
|
else
|
|
245
245
|
lines.push(this.border("├", "", "┤", innerWidth));
|
|
@@ -280,9 +280,7 @@ export class ActorInspectorOverlay {
|
|
|
280
280
|
const preservedBase = this.stripeBackground(this.fit(this.dropVisiblePrefix(base, popupWidth + popupAnchor), baseWidth), stripeIndex);
|
|
281
281
|
lines.push(this.row(`${leadingBase}${visiblePopup}${preservedBase}`, innerWidth, true));
|
|
282
282
|
}
|
|
283
|
-
lines.push(this.
|
|
284
|
-
lines.push(this.row(this.renderKeyHints(), innerWidth));
|
|
285
|
-
lines.push(this.border("╰", "", "╯", innerWidth));
|
|
283
|
+
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
286
284
|
return lines;
|
|
287
285
|
}
|
|
288
286
|
invalidate() { }
|
|
@@ -298,7 +296,7 @@ export class ActorInspectorOverlay {
|
|
|
298
296
|
}
|
|
299
297
|
contentViewportRows() {
|
|
300
298
|
const overlayRows = Math.floor(this.tui.terminal.rows * 0.94);
|
|
301
|
-
return Math.max(4, Math.min(
|
|
299
|
+
return Math.max(4, Math.min(24, overlayRows - 5));
|
|
302
300
|
}
|
|
303
301
|
selectRun(run, index) {
|
|
304
302
|
this.selectedRun = run;
|
|
@@ -380,7 +378,7 @@ export class ActorInspectorOverlay {
|
|
|
380
378
|
}
|
|
381
379
|
renderKillDialog(_width, innerWidth) {
|
|
382
380
|
const confirmation = this.killConfirmation;
|
|
383
|
-
const totalRows = this.contentViewportRows() +
|
|
381
|
+
const totalRows = this.contentViewportRows() + 5;
|
|
384
382
|
const cancel = this.killDialogChoice === "cancel"
|
|
385
383
|
? this.theme.bg("selectedBg", this.theme.fg("accent", " Cancel "))
|
|
386
384
|
: this.theme.fg("muted", " Cancel ");
|
|
@@ -411,7 +409,7 @@ export class ActorInspectorOverlay {
|
|
|
411
409
|
dialogRows.push("");
|
|
412
410
|
for (const line of dialogRows)
|
|
413
411
|
lines.push(this.row(this.center(line, innerWidth), innerWidth));
|
|
414
|
-
lines.push(this.
|
|
412
|
+
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
415
413
|
return lines;
|
|
416
414
|
}
|
|
417
415
|
listItemCount() {
|
|
@@ -435,27 +433,34 @@ export class ActorInspectorOverlay {
|
|
|
435
433
|
return Math.max(0, tabs.indexOf(label) - 2);
|
|
436
434
|
}
|
|
437
435
|
renderKeyHints() {
|
|
438
|
-
const hint = (keys, description) => `${this.theme.fg("accent", keys)}${this.theme.fg("
|
|
436
|
+
const hint = (keys, description) => `${this.theme.fg("accent", keys)}${this.theme.fg("borderAccent", ` ${description}`)}`;
|
|
437
|
+
const divider = this.theme.fg("borderAccent", " ─ ");
|
|
438
|
+
const hints = (...items) => items.map(([keys, description]) => hint(keys, description)).join(divider);
|
|
439
439
|
if (this.killConfirmation)
|
|
440
|
-
return
|
|
440
|
+
return hints(["←→/tab", "choose"], ["enter/y", "confirm"], ["esc/n", "cancel"]);
|
|
441
441
|
if (this.focus === "select")
|
|
442
442
|
return this.menuLevel === "value"
|
|
443
|
-
?
|
|
444
|
-
:
|
|
443
|
+
? hints(["↑↓", "option"], ["enter", "apply"], ["←/esc", "back"])
|
|
444
|
+
: hints(["↑↓", "option"], ["→/enter", "open"], ["←/esc", "back"]);
|
|
445
445
|
if (this.focus === "recipe")
|
|
446
|
-
return
|
|
446
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["↑ at top", "tabs"], ["←/esc", "tabs"]);
|
|
447
447
|
if (this.focus === "detail")
|
|
448
|
-
return
|
|
448
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["←/esc", "back"]);
|
|
449
449
|
if (this.focus === "list")
|
|
450
|
-
return
|
|
450
|
+
return hints(["↑↓/pgup/pgdn", "row"], ["→/enter", "open"], ["←", "tabs"], ["esc", "close"]);
|
|
451
451
|
if (this.focus === "runs") {
|
|
452
|
+
const items = [
|
|
453
|
+
["←→", "run"],
|
|
454
|
+
["↓", "tabs"],
|
|
455
|
+
["enter", "list"],
|
|
456
|
+
];
|
|
452
457
|
const run = this.runs().find((item) => item.run === this.selectedRun);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
return
|
|
458
|
+
if (this.killRun && run?.status === "running" && run.runInstanceId)
|
|
459
|
+
items.push(["k", "kill"]);
|
|
460
|
+
items.push(["esc", "close"]);
|
|
461
|
+
return hints(...items);
|
|
457
462
|
}
|
|
458
|
-
return
|
|
463
|
+
return hints(["←→", "navigate"], ["↑↓", "change row"], ["enter", "select"], ["esc", "close"]);
|
|
459
464
|
}
|
|
460
465
|
renderRunControl() {
|
|
461
466
|
const runs = this.runs();
|
|
@@ -1013,7 +1018,7 @@ export class ActorInspectorOverlay {
|
|
|
1013
1018
|
const hiddenAbove = start > 0;
|
|
1014
1019
|
const hiddenBelow = start + visibleOptions.length < options.length;
|
|
1015
1020
|
const contentWidth = Math.max(4, ...options.map((option) => visibleWidth(option) + 4));
|
|
1016
|
-
const border = (left, right, marker = "") => this.theme.fg("
|
|
1021
|
+
const border = (left, right, marker = "") => this.theme.fg("borderAccent", `${omitLeftBorder ? "" : left}${marker}${"─".repeat(Math.max(0, contentWidth - visibleWidth(marker)))}${omitRightBorder ? "" : right}`);
|
|
1017
1022
|
return [
|
|
1018
1023
|
border("╭", "╮", hiddenAbove ? "↑" : ""),
|
|
1019
1024
|
...visibleOptions.map((option, localIndex) => {
|
|
@@ -1026,7 +1031,7 @@ export class ActorInspectorOverlay {
|
|
|
1026
1031
|
const styled = focused || index === parentIndex
|
|
1027
1032
|
? this.theme.bg("selectedBg", colored)
|
|
1028
1033
|
: colored;
|
|
1029
|
-
return `${omitLeftBorder ? "" : this.theme.fg("
|
|
1034
|
+
return `${omitLeftBorder ? "" : this.theme.fg("borderAccent", "│")}${styled}${omitRightBorder ? "" : this.theme.fg("borderAccent", "│")}`;
|
|
1030
1035
|
}),
|
|
1031
1036
|
border("╰", "╯", hiddenBelow ? "↓" : ""),
|
|
1032
1037
|
];
|
|
@@ -1068,10 +1073,17 @@ export class ActorInspectorOverlay {
|
|
|
1068
1073
|
this.detailScroll = 0;
|
|
1069
1074
|
this.focus = "list";
|
|
1070
1075
|
}
|
|
1076
|
+
footerBorder(hints, width) {
|
|
1077
|
+
const prefix = " ";
|
|
1078
|
+
const suffix = " ─";
|
|
1079
|
+
const content = truncateToWidth(hints, Math.max(0, width - visibleWidth(prefix) - visibleWidth(suffix)), "");
|
|
1080
|
+
const fill = "─".repeat(Math.max(0, width - visibleWidth(prefix) - visibleWidth(suffix) - visibleWidth(content)));
|
|
1081
|
+
return `${this.theme.fg("borderAccent", `╰${prefix}`)}${content}${this.theme.fg("borderAccent", `${suffix}${fill}╯`)}`;
|
|
1082
|
+
}
|
|
1071
1083
|
border(left, title, right, width) {
|
|
1072
1084
|
const titleText = truncateToWidth(title, width, "");
|
|
1073
1085
|
const fill = "─".repeat(Math.max(0, width - visibleWidth(titleText)));
|
|
1074
|
-
return this.theme.fg("
|
|
1086
|
+
return this.theme.fg("borderAccent", `${left}${titleText}${fill}${right}`);
|
|
1075
1087
|
}
|
|
1076
1088
|
stripeBackground(content, index) {
|
|
1077
1089
|
if (index % 2 === 0)
|
|
@@ -1086,7 +1098,7 @@ export class ActorInspectorOverlay {
|
|
|
1086
1098
|
}
|
|
1087
1099
|
row(content, width, fitted = false) {
|
|
1088
1100
|
const body = fitted ? content : this.fit(content, width);
|
|
1089
|
-
return `${this.theme.fg("
|
|
1101
|
+
return `${this.theme.fg("borderAccent", "│")}${body}${this.theme.fg("borderAccent", "│")}`;
|
|
1090
1102
|
}
|
|
1091
1103
|
takeVisiblePrefix(content, width) {
|
|
1092
1104
|
const plain = stripVTControlCharacters(content);
|
|
@@ -181,5 +181,4 @@ export declare function formatRunOutboxMessage(event: RunOutboxEvent): string;
|
|
|
181
181
|
export declare function getRunTransitionNotificationType(transition: RunTransition): RunTransitionNotificationType;
|
|
182
182
|
export declare function shouldNotifyRunTransition(transition: RunTransition): boolean;
|
|
183
183
|
export declare function shouldSendRunTransitionFollowUp(transition: RunTransition): boolean;
|
|
184
|
-
export declare function shouldSuggestRecipePersistence(transition: RunTransition): boolean;
|
|
185
184
|
export declare function formatRunTransitionMessage(transition: RunTransition): string;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns ambient summaries, terminal events, and run outbox delivery for detached command-template runs
|
|
5
5
|
*/
|
|
6
6
|
import { closeSync, existsSync, fstatSync, openSync, readdirSync, readFileSync, readSync, watch, } from "node:fs";
|
|
7
|
-
import { basename, dirname, isAbsolute, join, relative,
|
|
7
|
+
import { basename, dirname, isAbsolute, join, relative, } from "node:path";
|
|
8
8
|
import * as AsyncRuns from "./async-runs.js";
|
|
9
9
|
import * as Paths from "./paths.js";
|
|
10
10
|
import { readJsonlFileResilient } from "./state-readers.js";
|
|
@@ -958,94 +958,29 @@ export function shouldNotifyRunTransition(transition) {
|
|
|
958
958
|
export function shouldSendRunTransitionFollowUp(transition) {
|
|
959
959
|
return shouldNotifyRunTransition(transition);
|
|
960
960
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
if (
|
|
974
|
-
return
|
|
975
|
-
const
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
return false;
|
|
983
|
-
if (isUserRecipeFile(transition.recipeFile))
|
|
984
|
-
return false;
|
|
985
|
-
return Boolean(transition.recipeFile) || transition.launchSource === "spawn";
|
|
986
|
-
}
|
|
987
|
-
function formatRecipePersistenceSuggestion(transition) {
|
|
988
|
-
if (!shouldSuggestRecipePersistence(transition))
|
|
989
|
-
return "";
|
|
990
|
-
if (transition.recipeFile) {
|
|
991
|
-
return `\nAgent note: this actor completed successfully from recipe ${transition.recipeFile}. If this recipe fits this machine's recurring workflow, ask the operator whether to copy or register it as a durable tool recipe under ~/.pi/agent/recipes. Do not auto-save without confirmation.`;
|
|
992
|
-
}
|
|
993
|
-
return `\nAgent note: this actor was spawned directly and completed successfully. If this pattern fits this machine's recurring workflow, ask the operator whether to save it as a durable recipe/tool under ~/.pi/agent/recipes with register_tool. Do not auto-save without confirmation.`;
|
|
994
|
-
}
|
|
995
|
-
function formatTransitionPolicy(transition) {
|
|
996
|
-
if (!transition.modelPolicy)
|
|
997
|
-
return "";
|
|
998
|
-
const axis = (key, label) => {
|
|
999
|
-
const value = transition.modelPolicy?.[key];
|
|
1000
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1001
|
-
return undefined;
|
|
1002
|
-
const record = value;
|
|
1003
|
-
const source = typeof record.source === "string" ? record.source : "unused";
|
|
1004
|
-
if (source === "unused")
|
|
1005
|
-
return undefined;
|
|
1006
|
-
const renderedValue = typeof record.value === "string" && record.value.trim()
|
|
1007
|
-
? ` (${record.value.trim()})`
|
|
1008
|
-
: "";
|
|
1009
|
-
return `${label}: ${source}${renderedValue}`;
|
|
1010
|
-
};
|
|
1011
|
-
const lines = [axis("model", "Model"), axis("thinking", "Thinking")].filter((line) => Boolean(line));
|
|
1012
|
-
return lines.length ? `\nPolicy:\n- ${lines.join("\n- ")}` : "";
|
|
1013
|
-
}
|
|
1014
|
-
function formatTransitionNextActions(transition) {
|
|
1015
|
-
const actions = [
|
|
1016
|
-
`inspect target=run:${transition.run} view=status`,
|
|
1017
|
-
transition.to === "done" && Object.keys(transition.artifacts ?? {}).length > 0
|
|
1018
|
-
? `inspect target=run:${transition.run} view=artifacts`
|
|
1019
|
-
: `inspect target=run:${transition.run} view=tail`,
|
|
1020
|
-
`inspect target=run:${transition.run} view=messages`,
|
|
1021
|
-
].filter(Boolean);
|
|
1022
|
-
return `\nNext actions: ${actions.join(" | ")}`;
|
|
1023
|
-
}
|
|
1024
|
-
function formatTransitionSemanticResult(transition) {
|
|
1025
|
-
const result = transition.semanticResult;
|
|
1026
|
-
if (!result)
|
|
1027
|
-
return "";
|
|
1028
|
-
const correlation = result.correlationId
|
|
1029
|
-
? `\nCorrelation: ${result.correlationId}` : "";
|
|
1030
|
-
const body = result.body ? `\nResult:\n${result.body}` : "";
|
|
1031
|
-
return `\nSemantic result: ${result.type} — ${result.summary}${correlation}${body}`;
|
|
961
|
+
const TERMINAL_FOLLOW_UP_ARTIFACT_LIMIT = 4;
|
|
962
|
+
const TERMINAL_FOLLOW_UP_IDENTIFIER_CHARS = 120;
|
|
963
|
+
const TERMINAL_FOLLOW_UP_PATH_CHARS = 320;
|
|
964
|
+
function compactTerminalText(value, limit) {
|
|
965
|
+
const compact = value.replaceAll(/\s+/g, " ").trim();
|
|
966
|
+
return compact.length > limit ? `${compact.slice(0, limit - 1)}…` : compact;
|
|
967
|
+
}
|
|
968
|
+
function formatTerminalPath(path) {
|
|
969
|
+
return `\`${compactTerminalText(path, TERMINAL_FOLLOW_UP_PATH_CHARS)}\``;
|
|
970
|
+
}
|
|
971
|
+
function formatTerminalResultLocations(transition) {
|
|
972
|
+
const artifactPaths = [...new Set(Object.values(transition.artifacts ?? {}).filter((path) => typeof path === "string" && path.length > 0))];
|
|
973
|
+
if (artifactPaths.length === 0)
|
|
974
|
+
return transition.stateDir ? `\nBase: ${formatTerminalPath(transition.stateDir)}` : "";
|
|
975
|
+
const base = commonDirectory(artifactPaths);
|
|
976
|
+
const artifactPreview = artifactPaths
|
|
977
|
+
.slice(0, TERMINAL_FOLLOW_UP_ARTIFACT_LIMIT)
|
|
978
|
+
.map((path) => formatTerminalPath(base ? relativeName(base, path) : path));
|
|
979
|
+
const omitted = artifactPaths.length - artifactPreview.length;
|
|
980
|
+
const artifacts = `${artifactPreview.join(", ")}${omitted > 0 ? ` (+${omitted} more)` : ""}`;
|
|
981
|
+
return `${base ? `\nBase: ${formatTerminalPath(base)}` : ""}\nArtifacts: ${artifacts}`;
|
|
1032
982
|
}
|
|
1033
983
|
export function formatRunTransitionMessage(transition) {
|
|
1034
|
-
const
|
|
1035
|
-
|
|
1036
|
-
const persistenceSuggestion = formatRecipePersistenceSuggestion(transition);
|
|
1037
|
-
const policy = formatTransitionPolicy(transition);
|
|
1038
|
-
const nextActions = formatTransitionNextActions(transition);
|
|
1039
|
-
const semanticResult = formatTransitionSemanticResult(transition);
|
|
1040
|
-
if (transition.to === "done")
|
|
1041
|
-
return `Run ${transition.run} completed successfully.${semanticResult}${policy}${artifacts}${runFiles}${nextActions}${persistenceSuggestion}`;
|
|
1042
|
-
if (transition.to === "failed")
|
|
1043
|
-
return `Run ${transition.run} failed.${semanticResult}${policy}${artifacts}${runFiles}${nextActions}`;
|
|
1044
|
-
if (transition.to === "cancelled")
|
|
1045
|
-
return `Run ${transition.run} was cancelled.${policy}${nextActions}`;
|
|
1046
|
-
if (transition.to === "killed")
|
|
1047
|
-
return `Run ${transition.run} was force-killed.${policy}${nextActions}`;
|
|
1048
|
-
if (transition.to === "exited")
|
|
1049
|
-
return `Run ${transition.run} exited before writing a result.${policy}${nextActions}`;
|
|
1050
|
-
return `Run ${transition.run} finished with status ${transition.to}.${policy}${nextActions}`;
|
|
984
|
+
const run = compactTerminalText(transition.run, TERMINAL_FOLLOW_UP_IDENTIFIER_CHARS);
|
|
985
|
+
return `Run: \`${run}\`\nStatus: \`${transition.to}\`${formatTerminalResultLocations(transition)}`;
|
|
1051
986
|
}
|
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 and shell-free: string leaves split into executable + argv, so operators such as && are literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, 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- 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.\n- 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.\n- 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.\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- 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.\n- 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.";
|
|
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 and shell-free: string leaves split into executable + argv, so operators such as && are literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, 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- 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.\n- 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.\n- 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.\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- 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.\n- 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.";
|
|
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
|
- 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.
|
|
25
25
|
- 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.
|
|
26
|
-
- 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.
|
|
26
|
+
- 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.
|
|
27
27
|
- 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.
|
|
28
28
|
- 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.
|
|
29
29
|
- 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.`;
|
|
@@ -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 } : {}),
|
|
@@ -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
|
|
|
@@ -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
|
package/docs/actor-inspector.md
CHANGED
|
@@ -31,7 +31,7 @@ Navigation stays bounded by available actions. `↑` on Run does nothing because
|
|
|
31
31
|
|
|
32
32
|
`K` appears only while Run is focused and the selected owned run reports `running`. It replaces the Inspector with a dedicated responsive `Confirm Actor Kill` overlay that names the exact `run:<id>`, shows its current status, and states that canonical `control.kill` is destructive and irreversible. Cancel owns initial focus; ←/→/Tab moves between Cancel and Kill actor, Enter activates the focused choice, `Y` confirms directly, and `N`/Escape cancels. Confirmation captures the immutable run generation and routes expected owner/generation through canonical `control.kill`; control compares owner, generation, and running status while serialized against same-directory restart, so terminal, ownership, or replacement-generation races reject without signaling. After the dialog closes, success, cancellation, rejection, and failure remain bounded in the Inspector content area; terminal runs expose no Kill hint and reject a stale keypress.
|
|
33
33
|
|
|
34
|
-
Selection and focus remain separate visual states. Accent-blue text marks the current tab, active filter popup, and applied option. The Run control uses `← … →` markers plus a light neutral background to show both focus and horizontal cycling; menus and timeline rows retain the single `▶` focus marker, while selected tabs retain brackets. Opening a popup keeps its parent filter blue so the relationship remains visible.
|
|
34
|
+
Selection and focus remain separate visual states. Accent-blue text marks the current tab, active filter popup, and applied option. The Run control uses `← … →` markers plus a light neutral background to show both focus and horizontal cycling; menus and timeline rows retain the single `▶` focus marker, while selected tabs retain brackets. Opening a popup keeps its parent filter blue so the relationship remains visible. Key hints live directly in the bottom overlay border rather than a dedicated body row: border-accent `─` connectors run through and between them instead of bullet glyphs, while key names and arrows retain blue accent color and descriptions use the border accent.
|
|
35
35
|
|
|
36
36
|
The top Run control aligns vertically with the tab labels, names the selected owned run, and colors its textual lifecycle status semantically. ←/→ cycles owned runs directly with wraparound, while Enter opens the complete owned-run list immediately beneath the control. That run list starts one cell farther left than the filter menus so its border aligns with the Run control rather than the tab/filter grid. It still overlays the tab row rather than leaving a detached gap. The timeline no longer renders run metadata as a data row.
|
|
37
37
|
|
|
@@ -39,7 +39,7 @@ Filters live behind their tab rather than occupying a permanent row. Non-default
|
|
|
39
39
|
|
|
40
40
|
Nested menus overlay rather than replace the timeline. Only rows and columns containing menu borders or values occlude underlying cells. When adjacent menus have different heights, the unused corner remains transparent and preserves the separator, striped background, and timeline data beneath it. Every run, filter, and nested value menu is viewport-bounded: ↑/↓ moves through the complete option set, the visible window follows focus, and `↑`/`↓` border markers disclose hidden options above or below without growing past the available inspector rows.
|
|
41
41
|
|
|
42
|
-
The overlay uses most of the available terminal width and height and reduces its content/menu viewport on shorter terminals. The bordered header keeps all three tabs visible, while the body shows the selected run and its current status above the active document or evidence rows. Run, Message, and Turn lists place the newest retained item directly below their control; a newly opened Inspector therefore selects the latest owned run, and ↓ moves backward in time toward older entries. Run options, Messages, and Turns all use compact descending `#N` labels, providing one timestamp-free time axis without repeating type words on every row. Evidence rows retain stable alternating backgrounds based on their absolute timeline position, including while scrolling: even rows keep the dark overlay background, while odd rows use the neutral `customMessageBg` stripe. Unused viewport padding stays on the plain overlay background instead of drawing fake striped rows beneath the last item. The
|
|
42
|
+
The overlay uses most of the available terminal width and height and reduces its content/menu viewport on shorter terminals. Its border-embedded key rail replaces the former three-row footer, returning two rows to a viewport that now caps at 24 rows. The bordered header keeps all three tabs visible, while the body shows the selected run and its current status above the active document or evidence rows. Run, Message, and Turn lists place the newest retained item directly below their control; a newly opened Inspector therefore selects the latest owned run, and ↓ moves backward in time toward older entries. Run options, Messages, and Turns all use compact descending `#N` labels, providing one timestamp-free time axis without repeating type words on every row. Evidence rows retain stable alternating backgrounds based on their absolute timeline position, including while scrolling: even rows keep the dark overlay background, while odd rows use the neutral `customMessageBg` stripe. Unused viewport padding stays on the plain overlay background instead of drawing fake striped rows beneath the last item. The bottom frame exposes the active keys. Messages retain attention markers and unread filtering and open into bounded detail without leaving the overlay. The overlay refreshes while visible and distinguishes true empty timelines from filtered-empty results; filtered-empty copy points back to Enter on the active tab without moving focus.
|
|
43
43
|
|
|
44
44
|
## Recipe Document
|
|
45
45
|
|
package/docs/async-runs.md
CHANGED
|
@@ -83,7 +83,7 @@ Use `run_id` on async recipe tools or `as: "run:<id>"` on `spawn` when the calle
|
|
|
83
83
|
|
|
84
84
|
Review commands that require semantic evidence apply marker acceptance before command completion accounting. Rejected code-zero output is reported consistently as a failed command in events, progress, evidence, and outbox delivery; it cannot emit a success-level completion notification. Evidence records are written before command launch and lifecycle cancellation or kill finalizes any running record with its interrupted state, effective exit code, and attempt capture paths. Async attempt stdout/stderr files exist from attempt start, so even small partial streams remain auditable when a command never returns.
|
|
85
85
|
|
|
86
|
-
Terminal follow-
|
|
86
|
+
Terminal follow-up content stays deliberately minimal: run id, status, one base path, and relative artifact names only. With declared artifacts, `Base` names their common directory and `Artifacts` lists bounded relative names; without them, `Base` names the run state directory. It never embeds stdout, stderr, semantic body, terminal error, model policy, persistence advice, completion type, or an inspect command into LLM context. The follow-up's non-LLM details retain one bounded semantic result, launch/tool-call correlation, and optional bounded scalar `transport_context`; a transport adapter can preserve an exact route such as `{ "transport": "telegram", "chat_id": 123456, "thread_id": 77 }`. When a recipe advertises `review.completed`, an explicit matching outbox envelope wins; otherwise a successful accepted review result deterministically synthesizes one from the bounded beginning of `stdout.log`. Failed runs retain their bounded terminal error as `run.failed` details.
|
|
87
87
|
|
|
88
88
|
Watcher acceleration and periodic reconciliation share one live in-flight guard. Delivery remains at-least-once across the send/handled-marker crash window, but reentrant watcher/reconciliation races do not create parallel sends. A send failure leaves the run unhandled for retry, notifies the active operator, and persists bounded attempts/error/status evidence in `terminal-delivery-failure.json`; `getRunStatus` exposes the latest record as `terminal_delivery_failure`.
|
|
89
89
|
|
|
@@ -96,12 +96,12 @@ Use ordinary files under the extension temp directory so status tools stay simpl
|
|
|
96
96
|
- `communication.json`: compact actor communication snapshot with self/root/parent, default-room, member, and contact hints for room-aware scripts and agents.
|
|
97
97
|
- `progress.json`: phase, active command count, completed count, failures, updated time, and optional `model_policy` provenance for inherited/explicit model and thinking values.
|
|
98
98
|
- `events.jsonl`: append-only implementation lifecycle log.
|
|
99
|
-
- `outbox.jsonl`: implementation storage for actor-message envelopes used by `inspect view=messages`, coordinator notifications, or follow-up context.
|
|
99
|
+
- `outbox.jsonl`: implementation storage for actor-message envelopes used by `inspect view=messages`, coordinator notifications, or follow-up context. Script-authored decision-point follow-ups may preserve bounded `body` previews plus message metadata; automatic terminal follow-ups stay limited to run id, status, one base path, and relative artifact names.
|
|
100
100
|
- `stdout.log` and `stderr.log`: detached process output.
|
|
101
101
|
- `prompts/command-NNN.md`: state-owned prompt files that collapse child `pi -p` natural-language positional fragments and appended recipe context into one authoritative `@file` prompt while preserving intentional file/image arguments.
|
|
102
102
|
- `captures/command-NNN/attempt-NNN/{stdout,stderr}.log`: complete byte-exact command streams, retained even below the bounded in-memory capture limit and separated across retries.
|
|
103
103
|
- `review-evidence.json`: stable command/stage manifest linking prompts, repeated branches, capture attempts, byte counts, exit state, semantic marker acceptance, recipe context, and model/thinking policy; terminal status aligns with the run. Review pipelines inject prior-stage `ACTOR_EVIDENCE_REF` values into downstream prompts, record cited/missing report sources, and fail closed if a normalized report claims `complete` without every required reviewer, verifier, merger, and judge reference.
|
|
104
|
-
- `result.json`: final code, killed flag, output selector, and optional full-output path.
|
|
104
|
+
- `result.json`: final code, killed flag, output selector, and optional full-output path. It publishes only after terminal `progress.json` and `review-evidence.json`, so readers never observe a result before its terminal state.
|
|
105
105
|
- `terminal-delivery-failure.json`: latest bounded failed follow-up attempt count, status, error, and timestamp; a later successful retry writes `terminal-handled.json`.
|
|
106
106
|
- `terminal-handled.json`: durable proof that terminal follow-up delivery or an explicit terminal control completed; notification delivery writes it only after the follow-up send returns successfully.
|
|
107
107
|
|
|
@@ -143,7 +143,7 @@ The core loop is:
|
|
|
143
143
|
{ "recipe": "music-player.json", "as": "run:music" }
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
2. Let terminal completion, `command.done`, and script-authored follow-up messages reach the launching coordinator automatically.
|
|
146
|
+
2. Let terminal completion, `command.done`, and script-authored follow-up messages reach the launching coordinator automatically. Terminal completion gives the coordinator only run id, status, a base path, and relative artifact names; inspect the run when result content changes the next decision. Decide whether a successful pattern deserves recipe persistence only after inspection and operator confirmation.
|
|
147
147
|
|
|
148
148
|
3. Respond with explicit run-local messages when needed:
|
|
149
149
|
|
package/docs/recipe-library.md
CHANGED
|
@@ -135,7 +135,7 @@ The repeatable smoke surface is the normal validation suite:
|
|
|
135
135
|
npm test
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
The scenario coverage is intentionally local-first and bounded: shared room coordination and roster snapshots (`rooms` / `tools` tests), direct branch delivery and claim/handle transitions (`tools` and coordinator tests), inspector navigation (`inspector` tests), recipe context injection (`recipes-context` / async-runs tests),
|
|
138
|
+
The scenario coverage is intentionally local-first and bounded: shared room coordination and roster snapshots (`rooms` / `tools` tests), direct branch delivery and claim/handle transitions (`tools` and coordinator tests), inspector navigation (`inspector` tests), recipe context injection (`recipes-context` / async-runs tests), compact terminal follow-up delivery (`observability` tests), and opt-in retirement candidate/execution smoke (`observability` / async-runs tests). These scenarios exercise public `spawn` / `message` / `inspect` behavior or the packaged script surfaces rather than relying on manual swarm demos.
|
|
139
139
|
|
|
140
140
|
## Music Player
|
|
141
141
|
|
package/lib/async-runs.ts
CHANGED
|
@@ -573,6 +573,13 @@ export function startRun(
|
|
|
573
573
|
? { transport_context: transportContext } : {}),
|
|
574
574
|
};
|
|
575
575
|
writeJsonAtomic(join(stateDir, "run.json"), meta);
|
|
576
|
+
writeJsonAtomic(join(stateDir, "progress.json"), {
|
|
577
|
+
completed: 0,
|
|
578
|
+
failures: [],
|
|
579
|
+
model_policy: modelPolicy,
|
|
580
|
+
phase: "starting",
|
|
581
|
+
updatedAt: new Date().toISOString(),
|
|
582
|
+
});
|
|
576
583
|
const child = spawn(process.execPath, argv, {
|
|
577
584
|
cwd,
|
|
578
585
|
detached: true,
|
|
@@ -589,13 +596,6 @@ export function startRun(
|
|
|
589
596
|
);
|
|
590
597
|
if (processIdentity) meta.process_identity = processIdentity;
|
|
591
598
|
writeJsonAtomic(join(stateDir, "run.json"), meta);
|
|
592
|
-
writeJsonAtomic(join(stateDir, "progress.json"), {
|
|
593
|
-
completed: 0,
|
|
594
|
-
failures: [],
|
|
595
|
-
model_policy: modelPolicy,
|
|
596
|
-
phase: "starting",
|
|
597
|
-
updatedAt: new Date().toISOString(),
|
|
598
|
-
});
|
|
599
599
|
writeFileSync(
|
|
600
600
|
join(stateDir, "events.jsonl"),
|
|
601
601
|
`${JSON.stringify({ event: "run.start", run, run_instance_id: meta.run_instance_id, pid: meta.pid, ts: new Date().toISOString() })}\n`,
|
package/lib/inspector-overlay.ts
CHANGED
|
@@ -270,7 +270,7 @@ export class ActorInspectorOverlay {
|
|
|
270
270
|
Math.max(0, innerWidth - visibleWidth(selectorTop) - selectorAnchor),
|
|
271
271
|
);
|
|
272
272
|
lines.push(
|
|
273
|
-
`${this.theme.fg("
|
|
273
|
+
`${this.theme.fg("borderAccent", `├${leadingBorder}`)}${selectorTop}${this.theme.fg("borderAccent", `${remainingBorder}┤`)}`,
|
|
274
274
|
);
|
|
275
275
|
} else lines.push(this.border("├", "", "┤", innerWidth));
|
|
276
276
|
this.contentStripeIndices = [];
|
|
@@ -322,9 +322,7 @@ export class ActorInspectorOverlay {
|
|
|
322
322
|
);
|
|
323
323
|
lines.push(this.row(`${leadingBase}${visiblePopup}${preservedBase}`, innerWidth, true));
|
|
324
324
|
}
|
|
325
|
-
lines.push(this.
|
|
326
|
-
lines.push(this.row(this.renderKeyHints(), innerWidth));
|
|
327
|
-
lines.push(this.border("╰", "", "╯", innerWidth));
|
|
325
|
+
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
328
326
|
return lines;
|
|
329
327
|
}
|
|
330
328
|
|
|
@@ -344,7 +342,7 @@ export class ActorInspectorOverlay {
|
|
|
344
342
|
|
|
345
343
|
private contentViewportRows(): number {
|
|
346
344
|
const overlayRows = Math.floor(this.tui.terminal.rows * 0.94);
|
|
347
|
-
return Math.max(4, Math.min(
|
|
345
|
+
return Math.max(4, Math.min(24, overlayRows - 5));
|
|
348
346
|
}
|
|
349
347
|
|
|
350
348
|
private selectRun(run: string, index: number): void {
|
|
@@ -438,7 +436,7 @@ export class ActorInspectorOverlay {
|
|
|
438
436
|
|
|
439
437
|
private renderKillDialog(_width: number, innerWidth: number): string[] {
|
|
440
438
|
const confirmation = this.killConfirmation!;
|
|
441
|
-
const totalRows = this.contentViewportRows() +
|
|
439
|
+
const totalRows = this.contentViewportRows() + 5;
|
|
442
440
|
const cancel = this.killDialogChoice === "cancel"
|
|
443
441
|
? this.theme.bg("selectedBg", this.theme.fg("accent", " Cancel "))
|
|
444
442
|
: this.theme.fg("muted", " Cancel ");
|
|
@@ -468,7 +466,7 @@ export class ActorInspectorOverlay {
|
|
|
468
466
|
while (dialogRows.length < availableRows) dialogRows.push("");
|
|
469
467
|
for (const line of dialogRows)
|
|
470
468
|
lines.push(this.row(this.center(line, innerWidth), innerWidth));
|
|
471
|
-
lines.push(this.
|
|
469
|
+
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
472
470
|
return lines;
|
|
473
471
|
}
|
|
474
472
|
|
|
@@ -495,27 +493,53 @@ export class ActorInspectorOverlay {
|
|
|
495
493
|
|
|
496
494
|
private renderKeyHints(): string {
|
|
497
495
|
const hint = (keys: string, description: string) =>
|
|
498
|
-
`${this.theme.fg("accent", keys)}${this.theme.fg("
|
|
496
|
+
`${this.theme.fg("accent", keys)}${this.theme.fg("borderAccent", ` ${description}`)}`;
|
|
497
|
+
const divider = this.theme.fg("borderAccent", " ─ ");
|
|
498
|
+
const hints = (...items: Array<[string, string]>) =>
|
|
499
|
+
items.map(([keys, description]) => hint(keys, description)).join(divider);
|
|
499
500
|
if (this.killConfirmation)
|
|
500
|
-
return
|
|
501
|
+
return hints(
|
|
502
|
+
["←→/tab", "choose"],
|
|
503
|
+
["enter/y", "confirm"],
|
|
504
|
+
["esc/n", "cancel"],
|
|
505
|
+
);
|
|
501
506
|
if (this.focus === "select")
|
|
502
507
|
return this.menuLevel === "value"
|
|
503
|
-
?
|
|
504
|
-
:
|
|
508
|
+
? hints(["↑↓", "option"], ["enter", "apply"], ["←/esc", "back"])
|
|
509
|
+
: hints(["↑↓", "option"], ["→/enter", "open"], ["←/esc", "back"]);
|
|
505
510
|
if (this.focus === "recipe")
|
|
506
|
-
return
|
|
511
|
+
return hints(
|
|
512
|
+
["↑↓/pgup/pgdn", "scroll"],
|
|
513
|
+
["↑ at top", "tabs"],
|
|
514
|
+
["←/esc", "tabs"],
|
|
515
|
+
);
|
|
507
516
|
if (this.focus === "detail")
|
|
508
|
-
return
|
|
517
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["←/esc", "back"]);
|
|
509
518
|
if (this.focus === "list")
|
|
510
|
-
return
|
|
519
|
+
return hints(
|
|
520
|
+
["↑↓/pgup/pgdn", "row"],
|
|
521
|
+
["→/enter", "open"],
|
|
522
|
+
["←", "tabs"],
|
|
523
|
+
["esc", "close"],
|
|
524
|
+
);
|
|
511
525
|
if (this.focus === "runs") {
|
|
526
|
+
const items: Array<[string, string]> = [
|
|
527
|
+
["←→", "run"],
|
|
528
|
+
["↓", "tabs"],
|
|
529
|
+
["enter", "list"],
|
|
530
|
+
];
|
|
512
531
|
const run = this.runs().find((item) => item.run === this.selectedRun);
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
return
|
|
532
|
+
if (this.killRun && run?.status === "running" && run.runInstanceId)
|
|
533
|
+
items.push(["k", "kill"]);
|
|
534
|
+
items.push(["esc", "close"]);
|
|
535
|
+
return hints(...items);
|
|
517
536
|
}
|
|
518
|
-
return
|
|
537
|
+
return hints(
|
|
538
|
+
["←→", "navigate"],
|
|
539
|
+
["↑↓", "change row"],
|
|
540
|
+
["enter", "select"],
|
|
541
|
+
["esc", "close"],
|
|
542
|
+
);
|
|
519
543
|
}
|
|
520
544
|
|
|
521
545
|
private renderRunControl(): string {
|
|
@@ -1200,7 +1224,7 @@ export class ActorInspectorOverlay {
|
|
|
1200
1224
|
);
|
|
1201
1225
|
const border = (left: string, right: string, marker = "") =>
|
|
1202
1226
|
this.theme.fg(
|
|
1203
|
-
"
|
|
1227
|
+
"borderAccent",
|
|
1204
1228
|
`${omitLeftBorder ? "" : left}${marker}${"─".repeat(Math.max(0, contentWidth - visibleWidth(marker)))}${omitRightBorder ? "" : right}`,
|
|
1205
1229
|
);
|
|
1206
1230
|
return [
|
|
@@ -1215,7 +1239,7 @@ export class ActorInspectorOverlay {
|
|
|
1215
1239
|
const styled = focused || index === parentIndex
|
|
1216
1240
|
? this.theme.bg("selectedBg", colored)
|
|
1217
1241
|
: colored;
|
|
1218
|
-
return `${omitLeftBorder ? "" : this.theme.fg("
|
|
1242
|
+
return `${omitLeftBorder ? "" : this.theme.fg("borderAccent", "│")}${styled}${omitRightBorder ? "" : this.theme.fg("borderAccent", "│")}`;
|
|
1219
1243
|
}),
|
|
1220
1244
|
border("╰", "╯", hiddenBelow ? "↓" : ""),
|
|
1221
1245
|
];
|
|
@@ -1285,10 +1309,27 @@ export class ActorInspectorOverlay {
|
|
|
1285
1309
|
this.focus = "list";
|
|
1286
1310
|
}
|
|
1287
1311
|
|
|
1312
|
+
private footerBorder(hints: string, width: number): string {
|
|
1313
|
+
const prefix = " ";
|
|
1314
|
+
const suffix = " ─";
|
|
1315
|
+
const content = truncateToWidth(
|
|
1316
|
+
hints,
|
|
1317
|
+
Math.max(0, width - visibleWidth(prefix) - visibleWidth(suffix)),
|
|
1318
|
+
"",
|
|
1319
|
+
);
|
|
1320
|
+
const fill = "─".repeat(
|
|
1321
|
+
Math.max(
|
|
1322
|
+
0,
|
|
1323
|
+
width - visibleWidth(prefix) - visibleWidth(suffix) - visibleWidth(content),
|
|
1324
|
+
),
|
|
1325
|
+
);
|
|
1326
|
+
return `${this.theme.fg("borderAccent", `╰${prefix}`)}${content}${this.theme.fg("borderAccent", `${suffix}${fill}╯`)}`;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1288
1329
|
private border(left: string, title: string, right: string, width: number): string {
|
|
1289
1330
|
const titleText = truncateToWidth(title, width, "");
|
|
1290
1331
|
const fill = "─".repeat(Math.max(0, width - visibleWidth(titleText)));
|
|
1291
|
-
return this.theme.fg("
|
|
1332
|
+
return this.theme.fg("borderAccent", `${left}${titleText}${fill}${right}`);
|
|
1292
1333
|
}
|
|
1293
1334
|
|
|
1294
1335
|
private stripeBackground(content: string, index: number): string {
|
|
@@ -1311,7 +1352,7 @@ export class ActorInspectorOverlay {
|
|
|
1311
1352
|
|
|
1312
1353
|
private row(content: string, width: number, fitted = false): string {
|
|
1313
1354
|
const body = fitted ? content : this.fit(content, width);
|
|
1314
|
-
return `${this.theme.fg("
|
|
1355
|
+
return `${this.theme.fg("borderAccent", "│")}${body}${this.theme.fg("borderAccent", "│")}`;
|
|
1315
1356
|
}
|
|
1316
1357
|
|
|
1317
1358
|
private takeVisiblePrefix(content: string, width: number): string {
|
package/lib/observability.ts
CHANGED
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
isAbsolute,
|
|
22
22
|
join,
|
|
23
23
|
relative,
|
|
24
|
-
resolve,
|
|
25
24
|
} from "node:path";
|
|
26
25
|
|
|
27
26
|
import * as AsyncRuns from "./async-runs.ts";
|
|
@@ -1345,97 +1344,40 @@ export function shouldSendRunTransitionFollowUp(
|
|
|
1345
1344
|
return shouldNotifyRunTransition(transition);
|
|
1346
1345
|
}
|
|
1347
1346
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
join(transition.stateDir, "stdout.log"),
|
|
1352
|
-
join(transition.stateDir, "stderr.log"),
|
|
1353
|
-
join(transition.stateDir, "result.json"),
|
|
1354
|
-
join(transition.stateDir, "events.jsonl"),
|
|
1355
|
-
join(transition.stateDir, "outbox.jsonl"),
|
|
1356
|
-
];
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
function isUserRecipeFile(file: string | undefined): boolean {
|
|
1360
|
-
if (!file) return false;
|
|
1361
|
-
const recipeRoot = resolve(Paths.getRecipeRoot());
|
|
1362
|
-
const path = resolve(file);
|
|
1363
|
-
const relation = relative(recipeRoot, path);
|
|
1364
|
-
return relation === "" || (!relation.startsWith("..") && !isAbsolute(relation));
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
export function shouldSuggestRecipePersistence(
|
|
1368
|
-
transition: RunTransition,
|
|
1369
|
-
): boolean {
|
|
1370
|
-
if (transition.to !== "done") return false;
|
|
1371
|
-
if (isUserRecipeFile(transition.recipeFile)) return false;
|
|
1372
|
-
return Boolean(transition.recipeFile) || transition.launchSource === "spawn";
|
|
1373
|
-
}
|
|
1347
|
+
const TERMINAL_FOLLOW_UP_ARTIFACT_LIMIT = 4;
|
|
1348
|
+
const TERMINAL_FOLLOW_UP_IDENTIFIER_CHARS = 120;
|
|
1349
|
+
const TERMINAL_FOLLOW_UP_PATH_CHARS = 320;
|
|
1374
1350
|
|
|
1375
|
-
function
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
return `\nAgent note: this actor completed successfully from recipe ${transition.recipeFile}. If this recipe fits this machine's recurring workflow, ask the operator whether to copy or register it as a durable tool recipe under ~/.pi/agent/recipes. Do not auto-save without confirmation.`;
|
|
1379
|
-
}
|
|
1380
|
-
return `\nAgent note: this actor was spawned directly and completed successfully. If this pattern fits this machine's recurring workflow, ask the operator whether to save it as a durable recipe/tool under ~/.pi/agent/recipes with register_tool. Do not auto-save without confirmation.`;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
function formatTransitionPolicy(transition: RunTransition): string {
|
|
1384
|
-
if (!transition.modelPolicy) return "";
|
|
1385
|
-
const axis = (key: "model" | "thinking", label: string): string | undefined => {
|
|
1386
|
-
const value = transition.modelPolicy?.[key];
|
|
1387
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
1388
|
-
const record = value as Record<string, unknown>;
|
|
1389
|
-
const source = typeof record.source === "string" ? record.source : "unused";
|
|
1390
|
-
if (source === "unused") return undefined;
|
|
1391
|
-
const renderedValue =
|
|
1392
|
-
typeof record.value === "string" && record.value.trim()
|
|
1393
|
-
? ` (${record.value.trim()})`
|
|
1394
|
-
: "";
|
|
1395
|
-
return `${label}: ${source}${renderedValue}`;
|
|
1396
|
-
};
|
|
1397
|
-
const lines = [axis("model", "Model"), axis("thinking", "Thinking")].filter(
|
|
1398
|
-
(line): line is string => Boolean(line),
|
|
1399
|
-
);
|
|
1400
|
-
return lines.length ? `\nPolicy:\n- ${lines.join("\n- ")}` : "";
|
|
1351
|
+
function compactTerminalText(value: string, limit: number): string {
|
|
1352
|
+
const compact = value.replaceAll(/\s+/g, " ").trim();
|
|
1353
|
+
return compact.length > limit ? `${compact.slice(0, limit - 1)}…` : compact;
|
|
1401
1354
|
}
|
|
1402
1355
|
|
|
1403
|
-
function
|
|
1404
|
-
|
|
1405
|
-
`inspect target=run:${transition.run} view=status`,
|
|
1406
|
-
transition.to === "done" && Object.keys(transition.artifacts ?? {}).length > 0
|
|
1407
|
-
? `inspect target=run:${transition.run} view=artifacts`
|
|
1408
|
-
: `inspect target=run:${transition.run} view=tail`,
|
|
1409
|
-
`inspect target=run:${transition.run} view=messages`,
|
|
1410
|
-
].filter(Boolean);
|
|
1411
|
-
return `\nNext actions: ${actions.join(" | ")}`;
|
|
1356
|
+
function formatTerminalPath(path: string): string {
|
|
1357
|
+
return `\`${compactTerminalText(path, TERMINAL_FOLLOW_UP_PATH_CHARS)}\``;
|
|
1412
1358
|
}
|
|
1413
1359
|
|
|
1414
|
-
function
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1360
|
+
function formatTerminalResultLocations(transition: RunTransition): string {
|
|
1361
|
+
const artifactPaths = [...new Set(
|
|
1362
|
+
Object.values(transition.artifacts ?? {}).filter(
|
|
1363
|
+
(path): path is string => typeof path === "string" && path.length > 0,
|
|
1364
|
+
),
|
|
1365
|
+
)];
|
|
1366
|
+
if (artifactPaths.length === 0)
|
|
1367
|
+
return transition.stateDir ? `\nBase: ${formatTerminalPath(transition.stateDir)}` : "";
|
|
1368
|
+
const base = commonDirectory(artifactPaths);
|
|
1369
|
+
const artifactPreview = artifactPaths
|
|
1370
|
+
.slice(0, TERMINAL_FOLLOW_UP_ARTIFACT_LIMIT)
|
|
1371
|
+
.map((path) => formatTerminalPath(base ? relativeName(base, path) : path));
|
|
1372
|
+
const omitted = artifactPaths.length - artifactPreview.length;
|
|
1373
|
+
const artifacts = `${artifactPreview.join(", ")}${omitted > 0 ? ` (+${omitted} more)` : ""}`;
|
|
1374
|
+
return `${base ? `\nBase: ${formatTerminalPath(base)}` : ""}\nArtifacts: ${artifacts}`;
|
|
1421
1375
|
}
|
|
1422
1376
|
|
|
1423
1377
|
export function formatRunTransitionMessage(transition: RunTransition): string {
|
|
1424
|
-
const
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
const semanticResult = formatTransitionSemanticResult(transition);
|
|
1430
|
-
if (transition.to === "done")
|
|
1431
|
-
return `Run ${transition.run} completed successfully.${semanticResult}${policy}${artifacts}${runFiles}${nextActions}${persistenceSuggestion}`;
|
|
1432
|
-
if (transition.to === "failed")
|
|
1433
|
-
return `Run ${transition.run} failed.${semanticResult}${policy}${artifacts}${runFiles}${nextActions}`;
|
|
1434
|
-
if (transition.to === "cancelled")
|
|
1435
|
-
return `Run ${transition.run} was cancelled.${policy}${nextActions}`;
|
|
1436
|
-
if (transition.to === "killed")
|
|
1437
|
-
return `Run ${transition.run} was force-killed.${policy}${nextActions}`;
|
|
1438
|
-
if (transition.to === "exited")
|
|
1439
|
-
return `Run ${transition.run} exited before writing a result.${policy}${nextActions}`;
|
|
1440
|
-
return `Run ${transition.run} finished with status ${transition.to}.${policy}${nextActions}`;
|
|
1378
|
+
const run = compactTerminalText(
|
|
1379
|
+
transition.run,
|
|
1380
|
+
TERMINAL_FOLLOW_UP_IDENTIFIER_CHARS,
|
|
1381
|
+
);
|
|
1382
|
+
return `Run: \`${run}\`\nStatus: \`${transition.to}\`${formatTerminalResultLocations(transition)}`;
|
|
1441
1383
|
}
|
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/package.json
CHANGED
package/scripts/async-runner.mjs
CHANGED
|
@@ -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/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
|