@llblab/pi-actors 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/AGENTS.md +3 -3
  2. package/BACKLOG.md +22 -1
  3. package/CHANGELOG.md +31 -0
  4. package/README.md +5 -4
  5. package/dist/index.js +30 -51
  6. package/dist/lib/inspector-overlay.d.ts +84 -0
  7. package/dist/lib/inspector-overlay.js +627 -0
  8. package/dist/lib/inspector.d.ts +17 -33
  9. package/dist/lib/inspector.js +98 -151
  10. package/dist/lib/limits.d.ts +3 -0
  11. package/dist/lib/limits.js +3 -0
  12. package/dist/lib/observability.d.ts +1 -1
  13. package/dist/lib/observability.js +2 -2
  14. package/dist/lib/pi.d.ts +1 -1
  15. package/dist/lib/pi.js +2 -2
  16. package/dist/lib/prompts.d.ts +1 -1
  17. package/dist/lib/prompts.js +3 -3
  18. package/dist/lib/recipes-context.d.ts +5 -0
  19. package/dist/lib/recipes-context.js +15 -0
  20. package/dist/lib/session-evidence.d.ts +45 -0
  21. package/dist/lib/session-evidence.js +204 -0
  22. package/dist/lib/tools-response.d.ts +3 -0
  23. package/dist/lib/tools-response.js +31 -0
  24. package/dist/scripts/async-runner.mjs +40 -5
  25. package/dist/scripts/coordinator.mjs +55 -9
  26. package/dist/skills/actors/SKILL.md +10 -9
  27. package/dist/skills/swarm/SKILL.md +4 -2
  28. package/docs/README.md +1 -0
  29. package/docs/actor-inspector.md +72 -0
  30. package/docs/async-runs.md +2 -2
  31. package/index.ts +32 -63
  32. package/lib/inspector-overlay.ts +735 -0
  33. package/lib/inspector.ts +132 -204
  34. package/lib/limits.ts +3 -0
  35. package/lib/observability.ts +3 -3
  36. package/lib/pi.ts +3 -3
  37. package/lib/prompts.ts +3 -3
  38. package/lib/recipes-context.ts +25 -0
  39. package/lib/session-evidence.ts +302 -0
  40. package/lib/tools-response.ts +33 -0
  41. package/package.json +1 -1
  42. package/scripts/async-runner.mjs +40 -5
  43. package/scripts/coordinator.mjs +55 -9
  44. package/skills/actors/SKILL.md +10 -9
  45. package/skills/swarm/SKILL.md +4 -2
@@ -3,6 +3,7 @@
3
3
  * Zones: terminal run inspection, room/direct message previews, no-dependency UI formatting
4
4
  * Owns read-only previews and TUI-ready summaries; actor routing stays outside.
5
5
  */
6
+ import * as SessionEvidence from "./session-evidence.ts";
6
7
  export interface ActorInspectorPreview {
7
8
  body_preview?: string;
8
9
  branch?: string;
@@ -45,47 +46,30 @@ export interface ActorInspectorPreviewReadOptions {
45
46
  ownerId?: string;
46
47
  branch?: string;
47
48
  currentRunOnly?: boolean;
49
+ run?: string;
48
50
  channels?: ActorInspectorPreview["channel"][];
49
51
  mention?: string;
50
52
  readKeys?: Iterable<string>;
51
53
  roomLimitPerRun?: number;
52
54
  unreadOnly?: boolean;
53
55
  }
54
- export interface ActorInspectorControllerState {
55
- branch?: string;
56
- channels?: ActorInspectorPreview["channel"][];
57
- mention?: string;
58
- readKeys: Set<string>;
59
- roomLimitPerRun: number;
60
- rows: number;
61
- selectedSequence?: number;
62
- unreadOnly: boolean;
63
- visible: boolean;
56
+ export interface ActorInspectorRunItem {
57
+ run: string;
58
+ status: string;
59
+ updatedAt?: string;
64
60
  }
65
- export interface ActorInspectorCommandResult {
66
- notify: string;
67
- type: "info" | "warning";
68
- update: boolean;
61
+ export declare function readActorInspectorRuns(stateRoot: string, ownerId: string): ActorInspectorRunItem[];
62
+ export interface ActorInspectorTurnItem extends SessionEvidence.SessionEvidenceTurn {
63
+ commandId: string;
64
+ diagnostics: string[];
65
+ promptBytes?: number;
66
+ promptFile?: string;
67
+ recipeContext?: unknown;
68
+ sessionFile: string;
69
+ sessionTruncated: boolean;
70
+ stage?: string;
69
71
  }
70
- export declare const DEFAULT_ACTOR_INSPECTOR_ROWS = 12;
71
- export declare const ACTOR_INSPECTOR_COMMAND_DESCRIPTIONS: {
72
- filter: string;
73
- inspect: string;
74
- toggle: string;
75
- };
76
- export declare function createActorInspectorControllerState(): ActorInspectorControllerState;
77
- export declare function getActorInspectorPreviewOptions(state: ActorInspectorControllerState, ownerId: string): ActorInspectorPreviewReadOptions;
78
- export declare function handleActorInspectorToggle(state: ActorInspectorControllerState, args: unknown): ActorInspectorCommandResult;
79
- export declare function handleActorInspectorFilter(state: ActorInspectorControllerState, args: unknown): ActorInspectorCommandResult;
80
- export declare function handleActorInspectorInspect(state: ActorInspectorControllerState, args: unknown, previews: ActorInspectorPreview[]): ActorInspectorCommandResult;
81
- export declare function selectActorInspectorSequence(state: ActorInspectorControllerState, sequence: number, previews: ActorInspectorPreview[]): ActorInspectorCommandResult;
82
- export declare function renderActorInspectorPanel(input: {
83
- stateRoot: string;
84
- state: ActorInspectorControllerState;
85
- ownerId: string;
86
- width: number;
87
- style: ActorInspectorWidgetStyle;
88
- }): string[];
72
+ export declare function readActorInspectorTurns(stateDir: string): ActorInspectorTurnItem[];
89
73
  export declare function inspectorPreviewReadKey(preview: ActorInspectorPreview): string;
90
74
  export declare function readActorInspectorPreviews(stateRoot?: string, limit?: number, options?: ActorInspectorPreviewReadOptions): ActorInspectorPreview[];
91
75
  export declare function readActorInspectorRoster(stateRoot: string | undefined, run: string, room?: string): ActorInspectorRosterMember[];
@@ -8,164 +8,108 @@ import * as path from "node:path";
8
8
  import { visibleWidth } from "@earendil-works/pi-tui";
9
9
  import * as Limits from "./limits.js";
10
10
  import * as Paths from "./paths.js";
11
+ import * as SessionEvidence from "./session-evidence.js";
11
12
  import { readJsonFileResilient, readJsonlFileResilient, } from "./state-readers.js";
12
- export const DEFAULT_ACTOR_INSPECTOR_ROWS = 12;
13
- export const ACTOR_INSPECTOR_COMMAND_DESCRIPTIONS = {
14
- filter: "Filter actor inspector rows: all, room, direct, broadcast, unread, branch <name>, mention <text>",
15
- inspect: "Inspect actor message by visible number",
16
- toggle: "Toggle actor inspector widget; optional row count",
17
- };
18
- export function createActorInspectorControllerState() {
19
- return {
20
- readKeys: new Set(),
21
- roomLimitPerRun: DEFAULT_ACTOR_INSPECTOR_ROWS,
22
- rows: DEFAULT_ACTOR_INSPECTOR_ROWS,
23
- unreadOnly: false,
24
- visible: false,
25
- };
26
- }
27
- export function getActorInspectorPreviewOptions(state, ownerId) {
28
- return {
29
- channels: state.channels,
30
- currentRunOnly: true,
31
- branch: state.branch,
32
- mention: state.mention,
33
- ownerId,
34
- readKeys: state.readKeys,
35
- roomLimitPerRun: state.roomLimitPerRun,
36
- unreadOnly: state.unreadOnly,
37
- };
38
- }
39
- export function handleActorInspectorToggle(state, args) {
40
- const raw = Array.isArray(args) ? args[0] : String(args ?? "");
41
- if (String(raw).trim()) {
42
- const rows = Number.parseInt(String(raw), 10);
43
- if (!Number.isFinite(rows) || rows <= 0) {
44
- return {
45
- notify: "Usage: /actors-inspector-toggle [rows] where rows > 0",
46
- type: "warning",
47
- update: false,
48
- };
49
- }
50
- state.rows = rows;
51
- state.roomLimitPerRun = rows;
52
- state.selectedSequence = undefined;
53
- state.visible = true;
54
- return {
55
- notify: `Actor inspector rows ${rows}`,
56
- type: "info",
57
- update: true,
58
- };
59
- }
60
- if (state.selectedSequence !== undefined) {
61
- state.selectedSequence = undefined;
62
- state.visible = true;
63
- return { notify: "Actor inspector table", type: "info", update: true };
13
+ export function readActorInspectorRuns(stateRoot, ownerId) {
14
+ try {
15
+ return fs
16
+ .readdirSync(stateRoot, { withFileTypes: true })
17
+ .filter((entry) => entry.isDirectory())
18
+ .flatMap((entry) => {
19
+ const stateDir = path.join(stateRoot, entry.name);
20
+ if (!matchesOwner(stateDir, ownerId))
21
+ return [];
22
+ const progress = readJsonFileResilient(path.join(stateDir, "progress.json"), {}).value;
23
+ const result = readJsonFileResilient(path.join(stateDir, "result.json"), {}).value;
24
+ return [{
25
+ run: entry.name,
26
+ status: String(progress.phase ?? (Object.keys(result).length ? "terminal" : "unknown")),
27
+ ...(typeof progress.updatedAt === "string"
28
+ ? { updatedAt: progress.updatedAt }
29
+ : typeof result.completedAt === "string"
30
+ ? { updatedAt: result.completedAt }
31
+ : {}),
32
+ }];
33
+ })
34
+ .sort((left, right) => String(left.updatedAt ?? "").localeCompare(String(right.updatedAt ?? "")));
64
35
  }
65
- if (state.visible)
66
- state.visible = false;
67
- else {
68
- state.rows = DEFAULT_ACTOR_INSPECTOR_ROWS;
69
- state.roomLimitPerRun = DEFAULT_ACTOR_INSPECTOR_ROWS;
70
- state.visible = true;
36
+ catch (error) {
37
+ if (error.code === "ENOENT")
38
+ return [];
39
+ return [];
71
40
  }
72
- return {
73
- notify: `Actor inspector ${state.visible ? "shown" : "hidden"}`,
74
- type: "info",
75
- update: true,
76
- };
77
41
  }
78
- export function handleActorInspectorFilter(state, args) {
79
- const parts = Array.isArray(args)
80
- ? args.map(String)
81
- : String(args ?? "").split(/\s+/);
82
- const mode = (parts[0] ?? "").trim().toLowerCase();
83
- if (!mode || mode === "all" || mode === "clear") {
84
- state.channels = undefined;
85
- state.mention = undefined;
86
- state.branch = undefined;
87
- state.unreadOnly = false;
88
- }
89
- else if (mode === "room" || mode === "direct" || mode === "broadcast") {
90
- state.channels = [mode];
91
- state.mention = undefined;
92
- }
93
- else if (mode === "unread") {
94
- state.unreadOnly = true;
95
- }
96
- else if (mode === "branch" || mode === "current-branch") {
97
- const branch = parts.slice(1).join(" ").trim();
98
- if (!branch) {
99
- return {
100
- notify: `Usage: /actors-inspector-filter ${mode} <branch-name>`,
101
- type: "warning",
102
- update: false,
103
- };
104
- }
105
- state.branch = branch;
106
- }
107
- else if (mode === "mention") {
108
- const mention = parts.slice(1).join(" ").trim();
109
- if (!mention) {
110
- return {
111
- notify: "Usage: /actors-inspector-filter mention <text>",
112
- type: "warning",
113
- update: false,
114
- };
115
- }
116
- state.channels = undefined;
117
- state.mention = mention;
42
+ function ownedSessionPath(stateDir, sessionFile) {
43
+ if (path.isAbsolute(sessionFile))
44
+ return undefined;
45
+ const sessionsRoot = path.resolve(stateDir, "sessions");
46
+ const resolved = path.resolve(stateDir, sessionFile);
47
+ if (!resolved.startsWith(`${sessionsRoot}${path.sep}`))
48
+ return undefined;
49
+ try {
50
+ const canonicalRoot = fs.realpathSync(sessionsRoot);
51
+ const canonicalFile = fs.realpathSync(resolved);
52
+ return canonicalFile.startsWith(`${canonicalRoot}${path.sep}`)
53
+ ? canonicalFile
54
+ : undefined;
118
55
  }
119
- else {
120
- return {
121
- notify: "Usage: /actors-inspector-filter all|room|direct|broadcast|unread|branch <name>|mention <text>",
122
- type: "warning",
123
- update: false,
124
- };
56
+ catch {
57
+ return undefined;
125
58
  }
126
- state.selectedSequence = undefined;
127
- state.visible = true;
128
- return {
129
- notify: `Actor inspector filter ${mode || "all"}`,
130
- type: "info",
131
- update: true,
132
- };
133
59
  }
134
- export function handleActorInspectorInspect(state, args, previews) {
135
- const raw = Array.isArray(args) ? args[0] : String(args ?? "");
136
- return selectActorInspectorSequence(state, Number.parseInt(String(raw), 10), previews);
137
- }
138
- export function selectActorInspectorSequence(state, sequence, previews) {
139
- if (!Number.isFinite(sequence) || sequence <= 0) {
140
- return {
141
- notify: "Usage: /actors-inspect <number>",
142
- type: "warning",
143
- update: false,
144
- };
60
+ export function readActorInspectorTurns(stateDir) {
61
+ const evidence = readJsonFileResilient(path.join(stateDir, "review-evidence.json"), {}).value;
62
+ const commands = Array.isArray(evidence.commands)
63
+ ? evidence.commands
64
+ : [];
65
+ const recordedFiles = new Set(commands.flatMap((command) => (Array.isArray(command.session_files) ? command.session_files : []).filter((file) => typeof file === "string")));
66
+ let discoveredCommands = [];
67
+ try {
68
+ const sessionsDir = path.join(stateDir, "sessions");
69
+ discoveredCommands = fs
70
+ .readdirSync(sessionsDir, { withFileTypes: true })
71
+ .filter((entry) => entry.isDirectory())
72
+ .flatMap((entry) => {
73
+ const files = fs
74
+ .readdirSync(path.join(sessionsDir, entry.name), {
75
+ withFileTypes: true,
76
+ })
77
+ .filter((file) => file.isFile() && file.name.endsWith(".jsonl"))
78
+ .map((file) => path.join("sessions", entry.name, file.name))
79
+ .filter((file) => !recordedFiles.has(file));
80
+ return files.length > 0
81
+ ? [{ id: entry.name, session_files: files, stage: "subagent" }]
82
+ : [];
83
+ });
145
84
  }
146
- const preview = previews.find((item) => item.sequence === sequence);
147
- if (preview)
148
- state.readKeys.add(inspectorPreviewReadKey(preview));
149
- state.selectedSequence = sequence;
150
- state.visible = true;
151
- return {
152
- notify: `Actor inspect item ${sequence}`,
153
- type: "info",
154
- update: true,
155
- };
156
- }
157
- export function renderActorInspectorPanel(input) {
158
- const previews = readActorInspectorPreviews(input.stateRoot, input.state.rows, getActorInspectorPreviewOptions(input.state, input.ownerId));
159
- const rows = (input.state.selectedSequence !== undefined
160
- ? renderInspectorItemView(previews, input.width, input.style, {
161
- sequence: input.state.selectedSequence,
162
- })
163
- : renderInspectorWidget(previews, input.width, input.style)) ?? [];
164
- const run = previews[0]?.run;
165
- const roster = input.state.selectedSequence === undefined && run
166
- ? renderInspectorRosterPanel(readActorInspectorRoster(input.stateRoot, run), input.width, input.style)
167
- : undefined;
168
- return roster ? [...roster, ...rows] : rows;
85
+ catch {
86
+ discoveredCommands = [];
87
+ }
88
+ return [...commands, ...discoveredCommands].flatMap((command) => (Array.isArray(command.session_files) ? command.session_files : [])
89
+ .filter((file) => typeof file === "string")
90
+ .flatMap((file) => {
91
+ const sessionPath = ownedSessionPath(stateDir, file);
92
+ if (!sessionPath)
93
+ return [];
94
+ const session = SessionEvidence.readSessionEvidence(sessionPath);
95
+ return session.turns.map((turn) => ({
96
+ ...turn,
97
+ commandId: String(command.id ?? "unknown"),
98
+ diagnostics: session.diagnostics.map((item) => `${item.line === undefined ? "" : `line ${item.line}: `}${item.message}`),
99
+ ...(typeof command.prompt_bytes === "number"
100
+ ? { promptBytes: command.prompt_bytes }
101
+ : {}),
102
+ ...(typeof command.prompt_file === "string"
103
+ ? { promptFile: command.prompt_file }
104
+ : {}),
105
+ ...(command.recipe_context !== undefined
106
+ ? { recipeContext: command.recipe_context }
107
+ : {}),
108
+ sessionFile: file,
109
+ sessionTruncated: session.truncated,
110
+ ...(typeof command.stage === "string" ? { stage: command.stage } : {}),
111
+ }));
112
+ })).map((turn, index) => ({ ...turn, index: index + 1 }));
169
113
  }
170
114
  function asRecord(value) {
171
115
  return value && typeof value === "object" && !Array.isArray(value)
@@ -178,7 +122,8 @@ function readJsonLines(file) {
178
122
  function previewValue(value, maxLength = Limits.INSPECTOR_BODY_PREVIEW_CHARS) {
179
123
  if (value === undefined)
180
124
  return undefined;
181
- const text = typeof value === "string" ? value : JSON.stringify(value);
125
+ const redacted = SessionEvidence.redactSessionEvidenceValue(value);
126
+ const text = typeof redacted === "string" ? redacted : JSON.stringify(redacted);
182
127
  const compact = text.replaceAll(/\s+/g, " ").trim();
183
128
  if (!compact)
184
129
  return undefined;
@@ -393,6 +338,8 @@ export function readActorInspectorPreviews(stateRoot = Paths.getRunStateRoot(),
393
338
  const stateDir = path.join(stateRoot, entry.name);
394
339
  if (!matchesOwner(stateDir, options.ownerId))
395
340
  return [];
341
+ if (options.run && entry.name !== options.run)
342
+ return [];
396
343
  return [
397
344
  ...readRoomPreviews(entry.name, stateDir),
398
345
  ...readInboxPreviews(entry.name, stateDir),
@@ -9,3 +9,6 @@ export declare const COMPACT_PREVIEW_CHARS = 160;
9
9
  export declare const INSPECTOR_BODY_PREVIEW_CHARS = 320;
10
10
  export declare const ROOM_MESSAGE_PREVIEW_CHARS = 320;
11
11
  export declare const DOCTOR_ACTION_PREVIEW_CHARS = 72;
12
+ export declare const SESSION_EVIDENCE_MAX_TURNS = 100;
13
+ export declare const SESSION_EVIDENCE_TEXT_CHARS = 4000;
14
+ export declare const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
@@ -9,3 +9,6 @@ export const COMPACT_PREVIEW_CHARS = 160;
9
9
  export const INSPECTOR_BODY_PREVIEW_CHARS = 320;
10
10
  export const ROOM_MESSAGE_PREVIEW_CHARS = 320;
11
11
  export const DOCTOR_ACTION_PREVIEW_CHARS = 72;
12
+ export const SESSION_EVIDENCE_MAX_TURNS = 100;
13
+ export const SESSION_EVIDENCE_TEXT_CHARS = 4_000;
14
+ export const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
@@ -50,7 +50,7 @@ export interface RunUiSnapshot {
50
50
  }
51
51
  export interface RunUiNotificationSink {
52
52
  notify(message: string, level: "info" | "warning" | "error"): void;
53
- sendSteering(message: {
53
+ sendFollowUp(message: {
54
54
  customType: string;
55
55
  content: string;
56
56
  display: true;
@@ -37,7 +37,7 @@ export function deliverRunTransitionNotifications(transitions, sink) {
37
37
  sink.notify(text, getRunTransitionNotificationType(transition));
38
38
  if (!shouldSendRunTransitionFollowUp(transition))
39
39
  continue;
40
- sink.sendSteering({
40
+ sink.sendFollowUp({
41
41
  customType: "pi-actors-run",
42
42
  content: text,
43
43
  display: true,
@@ -56,7 +56,7 @@ export function deliverRunOutboxNotifications(events, sink) {
56
56
  sink.notify(text, getRunOutboxNotificationType(event));
57
57
  if (!shouldSendRunOutboxFollowUp(event))
58
58
  continue;
59
- sink.sendSteering({
59
+ sink.sendFollowUp({
60
60
  customType: "pi-actors-run-message",
61
61
  content: text,
62
62
  display: true,
package/dist/lib/pi.d.ts CHANGED
@@ -7,7 +7,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
7
7
  export type { ExtensionAPI, ExtensionContext };
8
8
  export interface PiNotificationSink {
9
9
  notify(message: string, level: "info" | "warning" | "error"): void;
10
- sendSteering(message: {
10
+ sendFollowUp(message: {
11
11
  customType: string;
12
12
  content: string;
13
13
  display: true;
package/dist/lib/pi.js CHANGED
@@ -9,8 +9,8 @@ export function getSessionId(ctx) {
9
9
  export function createNotificationSink(pi, ctx) {
10
10
  return {
11
11
  notify: (message, level) => ctx.ui.notify(message, level),
12
- sendSteering: (message) => pi.sendMessage(message, {
13
- deliverAs: "steer",
12
+ sendFollowUp: (message) => pi.sendMessage(message, {
13
+ deliverAs: "followUp",
14
14
  triggerTurn: true,
15
15
  }),
16
16
  };
@@ -6,7 +6,7 @@
6
6
  export declare const REGISTER_TOOL_DESCRIPTION: string;
7
7
  export declare const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent command templates as agent-callable tools";
8
8
  export declare const REGISTER_TOOL_GUIDELINES: string[];
9
- export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, 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. When a deferred actor result gates the next step, wait for its terminal steering notification; 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; 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. 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.";
@@ -16,17 +16,17 @@ export const REGISTER_TOOL_GUIDELINES = [
16
16
  export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
17
17
  - Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.
18
18
  - Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.
19
- - Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.
19
+ - 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.
20
20
  - Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.
21
21
  - ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
22
22
  - Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.
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. When a deferred actor result gates the next step, wait for its terminal steering notification; 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. 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
- - For any non-trivial actor use or pi-actors change, read the bundled actors skill first; for deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.`;
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.`;
30
30
  export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
31
31
  name: "Tool name in snake_case (e.g., 'transcribe')",
32
32
  description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.",
@@ -14,6 +14,11 @@ export declare function markRecipeContextRecords(records: TemplateRecipeContextR
14
14
  export declare function formatRecipeContextJsonl(records: TemplateRecipeContextRecord[], context?: CommandTemplateActorRecipeContext): string;
15
15
  export declare function buildRecipeContextPromptBlock(records: TemplateRecipeContextRecord[], context?: CommandTemplateActorRecipeContext): string;
16
16
  export declare function appendRecipeContextToPiArgs(command: string, args: string[], records: TemplateRecipeContextRecord[] | undefined, context?: CommandTemplateActorRecipeContext): string[];
17
+ export interface ManagedPiSessionArgs {
18
+ args: string[];
19
+ sessionDir?: string;
20
+ }
21
+ export declare function attachPiSessionDir(command: string, args: string[], sessionDir: string): ManagedPiSessionArgs;
17
22
  export interface MaterializedPiPrintPromptArgs {
18
23
  args: string[];
19
24
  promptBytes?: number;
@@ -134,6 +134,21 @@ export function appendRecipeContextToPiArgs(command, args, records, context) {
134
134
  next[promptIndex] = `${next[promptIndex]}\n\n${block}`;
135
135
  return next;
136
136
  }
137
+ const PI_SESSION_POLICY_FLAGS = new Set([
138
+ "--fork",
139
+ "--no-session",
140
+ "--session",
141
+ "--session-dir",
142
+ "--session-id",
143
+ ]);
144
+ export function attachPiSessionDir(command, args, sessionDir) {
145
+ if (!isPiCommand(command) || findPiPrintPromptIndex(args) === undefined) {
146
+ return { args };
147
+ }
148
+ if (args.some((arg) => PI_SESSION_POLICY_FLAGS.has(arg)))
149
+ return { args };
150
+ return { args: ["--session-dir", sessionDir, ...args], sessionDir };
151
+ }
137
152
  export function materializePiPrintPromptArg(command, args, promptFile) {
138
153
  if (!isPiCommand(command))
139
154
  return { args };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Persisted Pi session evidence reader.
3
+ * Zones: subagent turns, active session branches, bounded/redacted previews
4
+ * Owns read-only normalization of session JSONL into inspector-ready turns.
5
+ */
6
+ import { type StateReadDiagnostic } from "./state-readers.ts";
7
+ export interface SessionEvidenceToolCall {
8
+ arguments?: unknown;
9
+ id: string;
10
+ name: string;
11
+ result?: unknown;
12
+ resultError?: boolean;
13
+ }
14
+ export interface SessionEvidenceTurn {
15
+ assistantEntryId?: string;
16
+ assistantText?: string;
17
+ error?: string;
18
+ index: number;
19
+ model?: string;
20
+ provider?: string;
21
+ stopReason?: string;
22
+ thinking?: string;
23
+ timestamp?: string;
24
+ toolCalls: SessionEvidenceToolCall[];
25
+ unmatchedToolResults: number;
26
+ usage?: unknown;
27
+ userEntryId?: string;
28
+ userText?: string;
29
+ }
30
+ export interface SessionEvidence {
31
+ activeLeafId?: string;
32
+ diagnostics: StateReadDiagnostic[];
33
+ path: string;
34
+ session?: Record<string, unknown>;
35
+ totalTurns: number;
36
+ truncated: boolean;
37
+ turns: SessionEvidenceTurn[];
38
+ }
39
+ export interface SessionEvidenceReadOptions {
40
+ maxTextChars?: number;
41
+ maxToolCalls?: number;
42
+ maxTurns?: number;
43
+ }
44
+ export declare function redactSessionEvidenceValue(value: unknown, maxTextChars?: number, seen?: WeakSet<object>): unknown;
45
+ export declare function readSessionEvidence(path: string, options?: SessionEvidenceReadOptions): SessionEvidence;