@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.
- package/AGENTS.md +3 -3
- package/BACKLOG.md +22 -1
- package/CHANGELOG.md +31 -0
- package/README.md +5 -4
- package/dist/index.js +30 -51
- package/dist/lib/inspector-overlay.d.ts +84 -0
- package/dist/lib/inspector-overlay.js +627 -0
- package/dist/lib/inspector.d.ts +17 -33
- package/dist/lib/inspector.js +98 -151
- package/dist/lib/limits.d.ts +3 -0
- package/dist/lib/limits.js +3 -0
- package/dist/lib/observability.d.ts +1 -1
- package/dist/lib/observability.js +2 -2
- package/dist/lib/pi.d.ts +1 -1
- package/dist/lib/pi.js +2 -2
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +3 -3
- package/dist/lib/recipes-context.d.ts +5 -0
- package/dist/lib/recipes-context.js +15 -0
- package/dist/lib/session-evidence.d.ts +45 -0
- package/dist/lib/session-evidence.js +204 -0
- package/dist/lib/tools-response.d.ts +3 -0
- package/dist/lib/tools-response.js +31 -0
- package/dist/scripts/async-runner.mjs +40 -5
- package/dist/scripts/coordinator.mjs +55 -9
- package/dist/skills/actors/SKILL.md +10 -9
- package/dist/skills/swarm/SKILL.md +4 -2
- package/docs/README.md +1 -0
- package/docs/actor-inspector.md +72 -0
- package/docs/async-runs.md +2 -2
- package/index.ts +32 -63
- package/lib/inspector-overlay.ts +735 -0
- package/lib/inspector.ts +132 -204
- package/lib/limits.ts +3 -0
- package/lib/observability.ts +3 -3
- package/lib/pi.ts +3 -3
- package/lib/prompts.ts +3 -3
- package/lib/recipes-context.ts +25 -0
- package/lib/session-evidence.ts +302 -0
- package/lib/tools-response.ts +33 -0
- package/package.json +1 -1
- package/scripts/async-runner.mjs +40 -5
- package/scripts/coordinator.mjs +55 -9
- package/skills/actors/SKILL.md +10 -9
- package/skills/swarm/SKILL.md +4 -2
package/lib/inspector.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { visibleWidth } from "@earendil-works/pi-tui";
|
|
|
12
12
|
import type { ActorMessage } from "./messages.ts";
|
|
13
13
|
import * as Limits from "./limits.ts";
|
|
14
14
|
import * as Paths from "./paths.ts";
|
|
15
|
+
import * as SessionEvidence from "./session-evidence.ts";
|
|
15
16
|
import {
|
|
16
17
|
readJsonFileResilient,
|
|
17
18
|
readJsonlFileResilient,
|
|
@@ -63,6 +64,7 @@ export interface ActorInspectorPreviewReadOptions {
|
|
|
63
64
|
ownerId?: string;
|
|
64
65
|
branch?: string;
|
|
65
66
|
currentRunOnly?: boolean;
|
|
67
|
+
run?: string;
|
|
66
68
|
channels?: ActorInspectorPreview["channel"][];
|
|
67
69
|
mention?: string;
|
|
68
70
|
readKeys?: Iterable<string>;
|
|
@@ -70,221 +72,144 @@ export interface ActorInspectorPreviewReadOptions {
|
|
|
70
72
|
unreadOnly?: boolean;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
export interface
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
readKeys: Set<string>;
|
|
78
|
-
roomLimitPerRun: number;
|
|
79
|
-
rows: number;
|
|
80
|
-
selectedSequence?: number;
|
|
81
|
-
unreadOnly: boolean;
|
|
82
|
-
visible: boolean;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface ActorInspectorCommandResult {
|
|
86
|
-
notify: string;
|
|
87
|
-
type: "info" | "warning";
|
|
88
|
-
update: boolean;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const DEFAULT_ACTOR_INSPECTOR_ROWS = 12;
|
|
92
|
-
|
|
93
|
-
export const ACTOR_INSPECTOR_COMMAND_DESCRIPTIONS = {
|
|
94
|
-
filter:
|
|
95
|
-
"Filter actor inspector rows: all, room, direct, broadcast, unread, branch <name>, mention <text>",
|
|
96
|
-
inspect: "Inspect actor message by visible number",
|
|
97
|
-
toggle: "Toggle actor inspector widget; optional row count",
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export function createActorInspectorControllerState(): ActorInspectorControllerState {
|
|
101
|
-
return {
|
|
102
|
-
readKeys: new Set<string>(),
|
|
103
|
-
roomLimitPerRun: DEFAULT_ACTOR_INSPECTOR_ROWS,
|
|
104
|
-
rows: DEFAULT_ACTOR_INSPECTOR_ROWS,
|
|
105
|
-
unreadOnly: false,
|
|
106
|
-
visible: false,
|
|
107
|
-
};
|
|
75
|
+
export interface ActorInspectorRunItem {
|
|
76
|
+
run: string;
|
|
77
|
+
status: string;
|
|
78
|
+
updatedAt?: string;
|
|
108
79
|
}
|
|
109
80
|
|
|
110
|
-
export function
|
|
111
|
-
|
|
81
|
+
export function readActorInspectorRuns(
|
|
82
|
+
stateRoot: string,
|
|
112
83
|
ownerId: string,
|
|
113
|
-
):
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return
|
|
145
|
-
notify: `Actor inspector rows ${rows}`,
|
|
146
|
-
type: "info",
|
|
147
|
-
update: true,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
if (state.selectedSequence !== undefined) {
|
|
151
|
-
state.selectedSequence = undefined;
|
|
152
|
-
state.visible = true;
|
|
153
|
-
return { notify: "Actor inspector table", type: "info", update: true };
|
|
154
|
-
}
|
|
155
|
-
if (state.visible) state.visible = false;
|
|
156
|
-
else {
|
|
157
|
-
state.rows = DEFAULT_ACTOR_INSPECTOR_ROWS;
|
|
158
|
-
state.roomLimitPerRun = DEFAULT_ACTOR_INSPECTOR_ROWS;
|
|
159
|
-
state.visible = true;
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
notify: `Actor inspector ${state.visible ? "shown" : "hidden"}`,
|
|
163
|
-
type: "info",
|
|
164
|
-
update: true,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function handleActorInspectorFilter(
|
|
169
|
-
state: ActorInspectorControllerState,
|
|
170
|
-
args: unknown,
|
|
171
|
-
): ActorInspectorCommandResult {
|
|
172
|
-
const parts = Array.isArray(args)
|
|
173
|
-
? args.map(String)
|
|
174
|
-
: String(args ?? "").split(/\s+/);
|
|
175
|
-
const mode = (parts[0] ?? "").trim().toLowerCase();
|
|
176
|
-
if (!mode || mode === "all" || mode === "clear") {
|
|
177
|
-
state.channels = undefined;
|
|
178
|
-
state.mention = undefined;
|
|
179
|
-
state.branch = undefined;
|
|
180
|
-
state.unreadOnly = false;
|
|
181
|
-
} else if (mode === "room" || mode === "direct" || mode === "broadcast") {
|
|
182
|
-
state.channels = [mode];
|
|
183
|
-
state.mention = undefined;
|
|
184
|
-
} else if (mode === "unread") {
|
|
185
|
-
state.unreadOnly = true;
|
|
186
|
-
} else if (mode === "branch" || mode === "current-branch") {
|
|
187
|
-
const branch = parts.slice(1).join(" ").trim();
|
|
188
|
-
if (!branch) {
|
|
189
|
-
return {
|
|
190
|
-
notify: `Usage: /actors-inspector-filter ${mode} <branch-name>`,
|
|
191
|
-
type: "warning",
|
|
192
|
-
update: false,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
state.branch = branch;
|
|
196
|
-
} else if (mode === "mention") {
|
|
197
|
-
const mention = parts.slice(1).join(" ").trim();
|
|
198
|
-
if (!mention) {
|
|
199
|
-
return {
|
|
200
|
-
notify: "Usage: /actors-inspector-filter mention <text>",
|
|
201
|
-
type: "warning",
|
|
202
|
-
update: false,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
state.channels = undefined;
|
|
206
|
-
state.mention = mention;
|
|
207
|
-
} else {
|
|
208
|
-
return {
|
|
209
|
-
notify:
|
|
210
|
-
"Usage: /actors-inspector-filter all|room|direct|broadcast|unread|branch <name>|mention <text>",
|
|
211
|
-
type: "warning",
|
|
212
|
-
update: false,
|
|
213
|
-
};
|
|
84
|
+
): ActorInspectorRunItem[] {
|
|
85
|
+
try {
|
|
86
|
+
return fs
|
|
87
|
+
.readdirSync(stateRoot, { withFileTypes: true })
|
|
88
|
+
.filter((entry) => entry.isDirectory())
|
|
89
|
+
.flatMap((entry) => {
|
|
90
|
+
const stateDir = path.join(stateRoot, entry.name);
|
|
91
|
+
if (!matchesOwner(stateDir, ownerId)) return [];
|
|
92
|
+
const progress = readJsonFileResilient<Record<string, unknown>>(
|
|
93
|
+
path.join(stateDir, "progress.json"),
|
|
94
|
+
{},
|
|
95
|
+
).value;
|
|
96
|
+
const result = readJsonFileResilient<Record<string, unknown>>(
|
|
97
|
+
path.join(stateDir, "result.json"),
|
|
98
|
+
{},
|
|
99
|
+
).value;
|
|
100
|
+
return [{
|
|
101
|
+
run: entry.name,
|
|
102
|
+
status: String(progress.phase ?? (Object.keys(result).length ? "terminal" : "unknown")),
|
|
103
|
+
...(typeof progress.updatedAt === "string"
|
|
104
|
+
? { updatedAt: progress.updatedAt }
|
|
105
|
+
: typeof result.completedAt === "string"
|
|
106
|
+
? { updatedAt: result.completedAt }
|
|
107
|
+
: {}),
|
|
108
|
+
}];
|
|
109
|
+
})
|
|
110
|
+
.sort((left, right) =>
|
|
111
|
+
String(left.updatedAt ?? "").localeCompare(String(right.updatedAt ?? "")),
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
115
|
+
return [];
|
|
214
116
|
}
|
|
215
|
-
state.selectedSequence = undefined;
|
|
216
|
-
state.visible = true;
|
|
217
|
-
return {
|
|
218
|
-
notify: `Actor inspector filter ${mode || "all"}`,
|
|
219
|
-
type: "info",
|
|
220
|
-
update: true,
|
|
221
|
-
};
|
|
222
117
|
}
|
|
223
118
|
|
|
224
|
-
export
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
previews,
|
|
234
|
-
);
|
|
119
|
+
export interface ActorInspectorTurnItem extends SessionEvidence.SessionEvidenceTurn {
|
|
120
|
+
commandId: string;
|
|
121
|
+
diagnostics: string[];
|
|
122
|
+
promptBytes?: number;
|
|
123
|
+
promptFile?: string;
|
|
124
|
+
recipeContext?: unknown;
|
|
125
|
+
sessionFile: string;
|
|
126
|
+
sessionTruncated: boolean;
|
|
127
|
+
stage?: string;
|
|
235
128
|
}
|
|
236
129
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
130
|
+
function ownedSessionPath(
|
|
131
|
+
stateDir: string,
|
|
132
|
+
sessionFile: string,
|
|
133
|
+
): string | undefined {
|
|
134
|
+
if (path.isAbsolute(sessionFile)) return undefined;
|
|
135
|
+
const sessionsRoot = path.resolve(stateDir, "sessions");
|
|
136
|
+
const resolved = path.resolve(stateDir, sessionFile);
|
|
137
|
+
if (!resolved.startsWith(`${sessionsRoot}${path.sep}`)) return undefined;
|
|
138
|
+
try {
|
|
139
|
+
const canonicalRoot = fs.realpathSync(sessionsRoot);
|
|
140
|
+
const canonicalFile = fs.realpathSync(resolved);
|
|
141
|
+
return canonicalFile.startsWith(`${canonicalRoot}${path.sep}`)
|
|
142
|
+
? canonicalFile
|
|
143
|
+
: undefined;
|
|
144
|
+
} catch {
|
|
145
|
+
return undefined;
|
|
248
146
|
}
|
|
249
|
-
const preview = previews.find((item) => item.sequence === sequence);
|
|
250
|
-
if (preview) state.readKeys.add(inspectorPreviewReadKey(preview));
|
|
251
|
-
state.selectedSequence = sequence;
|
|
252
|
-
state.visible = true;
|
|
253
|
-
return {
|
|
254
|
-
notify: `Actor inspect item ${sequence}`,
|
|
255
|
-
type: "info",
|
|
256
|
-
update: true,
|
|
257
|
-
};
|
|
258
147
|
}
|
|
259
148
|
|
|
260
|
-
export function
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
149
|
+
export function readActorInspectorTurns(stateDir: string): ActorInspectorTurnItem[] {
|
|
150
|
+
const evidence = readJsonFileResilient<Record<string, unknown>>(
|
|
151
|
+
path.join(stateDir, "review-evidence.json"),
|
|
152
|
+
{},
|
|
153
|
+
).value;
|
|
154
|
+
const commands = Array.isArray(evidence.commands)
|
|
155
|
+
? (evidence.commands as Array<Record<string, unknown>>)
|
|
156
|
+
: [];
|
|
157
|
+
const recordedFiles = new Set(
|
|
158
|
+
commands.flatMap((command) =>
|
|
159
|
+
(Array.isArray(command.session_files) ? command.session_files : []).filter(
|
|
160
|
+
(file): file is string => typeof file === "string",
|
|
161
|
+
),
|
|
162
|
+
),
|
|
271
163
|
);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
164
|
+
let discoveredCommands: Array<Record<string, unknown>> = [];
|
|
165
|
+
try {
|
|
166
|
+
const sessionsDir = path.join(stateDir, "sessions");
|
|
167
|
+
discoveredCommands = fs
|
|
168
|
+
.readdirSync(sessionsDir, { withFileTypes: true })
|
|
169
|
+
.filter((entry) => entry.isDirectory())
|
|
170
|
+
.flatMap((entry) => {
|
|
171
|
+
const files = fs
|
|
172
|
+
.readdirSync(path.join(sessionsDir, entry.name), {
|
|
173
|
+
withFileTypes: true,
|
|
174
|
+
})
|
|
175
|
+
.filter((file) => file.isFile() && file.name.endsWith(".jsonl"))
|
|
176
|
+
.map((file) => path.join("sessions", entry.name, file.name))
|
|
177
|
+
.filter((file) => !recordedFiles.has(file));
|
|
178
|
+
return files.length > 0
|
|
179
|
+
? [{ id: entry.name, session_files: files, stage: "subagent" }]
|
|
180
|
+
: [];
|
|
181
|
+
});
|
|
182
|
+
} catch {
|
|
183
|
+
discoveredCommands = [];
|
|
184
|
+
}
|
|
185
|
+
return [...commands, ...discoveredCommands].flatMap((command) =>
|
|
186
|
+
(Array.isArray(command.session_files) ? command.session_files : [])
|
|
187
|
+
.filter((file): file is string => typeof file === "string")
|
|
188
|
+
.flatMap((file) => {
|
|
189
|
+
const sessionPath = ownedSessionPath(stateDir, file);
|
|
190
|
+
if (!sessionPath) return [];
|
|
191
|
+
const session = SessionEvidence.readSessionEvidence(sessionPath);
|
|
192
|
+
return session.turns.map((turn) => ({
|
|
193
|
+
...turn,
|
|
194
|
+
commandId: String(command.id ?? "unknown"),
|
|
195
|
+
diagnostics: session.diagnostics.map((item) =>
|
|
196
|
+
`${item.line === undefined ? "" : `line ${item.line}: `}${item.message}`,
|
|
197
|
+
),
|
|
198
|
+
...(typeof command.prompt_bytes === "number"
|
|
199
|
+
? { promptBytes: command.prompt_bytes }
|
|
200
|
+
: {}),
|
|
201
|
+
...(typeof command.prompt_file === "string"
|
|
202
|
+
? { promptFile: command.prompt_file }
|
|
203
|
+
: {}),
|
|
204
|
+
...(command.recipe_context !== undefined
|
|
205
|
+
? { recipeContext: command.recipe_context }
|
|
206
|
+
: {}),
|
|
207
|
+
sessionFile: file,
|
|
208
|
+
sessionTruncated: session.truncated,
|
|
209
|
+
...(typeof command.stage === "string" ? { stage: command.stage } : {}),
|
|
210
|
+
}));
|
|
211
|
+
}),
|
|
212
|
+
).map((turn, index) => ({ ...turn, index: index + 1 }));
|
|
288
213
|
}
|
|
289
214
|
|
|
290
215
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
@@ -302,7 +227,9 @@ function previewValue(
|
|
|
302
227
|
maxLength = Limits.INSPECTOR_BODY_PREVIEW_CHARS,
|
|
303
228
|
): string | undefined {
|
|
304
229
|
if (value === undefined) return undefined;
|
|
305
|
-
const
|
|
230
|
+
const redacted = SessionEvidence.redactSessionEvidenceValue(value);
|
|
231
|
+
const text =
|
|
232
|
+
typeof redacted === "string" ? redacted : JSON.stringify(redacted);
|
|
306
233
|
const compact = text.replaceAll(/\s+/g, " ").trim();
|
|
307
234
|
if (!compact) return undefined;
|
|
308
235
|
return compact.length > maxLength
|
|
@@ -606,6 +533,7 @@ export function readActorInspectorPreviews(
|
|
|
606
533
|
.flatMap((entry) => {
|
|
607
534
|
const stateDir = path.join(stateRoot, entry.name);
|
|
608
535
|
if (!matchesOwner(stateDir, options.ownerId)) return [];
|
|
536
|
+
if (options.run && entry.name !== options.run) return [];
|
|
609
537
|
return [
|
|
610
538
|
...readRoomPreviews(entry.name, stateDir),
|
|
611
539
|
...readInboxPreviews(entry.name, stateDir),
|
package/lib/limits.ts
CHANGED
|
@@ -10,3 +10,6 @@ export const COMPACT_PREVIEW_CHARS = 160;
|
|
|
10
10
|
export const INSPECTOR_BODY_PREVIEW_CHARS = 320;
|
|
11
11
|
export const ROOM_MESSAGE_PREVIEW_CHARS = 320;
|
|
12
12
|
export const DOCTOR_ACTION_PREVIEW_CHARS = 72;
|
|
13
|
+
export const SESSION_EVIDENCE_MAX_TURNS = 100;
|
|
14
|
+
export const SESSION_EVIDENCE_TEXT_CHARS = 4_000;
|
|
15
|
+
export const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
|
package/lib/observability.ts
CHANGED
|
@@ -81,7 +81,7 @@ export interface RunUiSnapshot {
|
|
|
81
81
|
|
|
82
82
|
export interface RunUiNotificationSink {
|
|
83
83
|
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
84
|
-
|
|
84
|
+
sendFollowUp(message: {
|
|
85
85
|
customType: string;
|
|
86
86
|
content: string;
|
|
87
87
|
display: true;
|
|
@@ -140,7 +140,7 @@ export function deliverRunTransitionNotifications(
|
|
|
140
140
|
const text = formatRunTransitionMessage(transition);
|
|
141
141
|
sink.notify(text, getRunTransitionNotificationType(transition));
|
|
142
142
|
if (!shouldSendRunTransitionFollowUp(transition)) continue;
|
|
143
|
-
sink.
|
|
143
|
+
sink.sendFollowUp({
|
|
144
144
|
customType: "pi-actors-run",
|
|
145
145
|
content: text,
|
|
146
146
|
display: true,
|
|
@@ -164,7 +164,7 @@ export function deliverRunOutboxNotifications(
|
|
|
164
164
|
const text = formatRunOutboxMessage(event);
|
|
165
165
|
sink.notify(text, getRunOutboxNotificationType(event));
|
|
166
166
|
if (!shouldSendRunOutboxFollowUp(event)) continue;
|
|
167
|
-
sink.
|
|
167
|
+
sink.sendFollowUp({
|
|
168
168
|
customType: "pi-actors-run-message",
|
|
169
169
|
content: text,
|
|
170
170
|
display: true,
|
package/lib/pi.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type { ExtensionAPI, ExtensionContext };
|
|
|
13
13
|
|
|
14
14
|
export interface PiNotificationSink {
|
|
15
15
|
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
16
|
-
|
|
16
|
+
sendFollowUp(message: {
|
|
17
17
|
customType: string;
|
|
18
18
|
content: string;
|
|
19
19
|
display: true;
|
|
@@ -31,9 +31,9 @@ export function createNotificationSink(
|
|
|
31
31
|
): PiNotificationSink {
|
|
32
32
|
return {
|
|
33
33
|
notify: (message, level) => ctx.ui.notify(message, level),
|
|
34
|
-
|
|
34
|
+
sendFollowUp: (message) =>
|
|
35
35
|
pi.sendMessage(message, {
|
|
36
|
-
deliverAs: "
|
|
36
|
+
deliverAs: "followUp",
|
|
37
37
|
triggerTurn: true,
|
|
38
38
|
}),
|
|
39
39
|
};
|
package/lib/prompts.ts
CHANGED
|
@@ -22,17 +22,17 @@ export const REGISTER_TOOL_GUIDELINES = [
|
|
|
22
22
|
export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
23
23
|
- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.
|
|
24
24
|
- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.
|
|
25
|
-
- Command templates stay sync: string
|
|
25
|
+
- 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.
|
|
26
26
|
- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.
|
|
27
27
|
- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
|
|
28
28
|
- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.
|
|
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. When a deferred actor result gates the next step, wait for its terminal
|
|
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.
|
|
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
|
-
- For any non-trivial actor use or pi-actors change, read the bundled actors skill first
|
|
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.`;
|
|
36
36
|
|
|
37
37
|
export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
|
|
38
38
|
name: "Tool name in snake_case (e.g., 'transcribe')",
|
package/lib/recipes-context.ts
CHANGED
|
@@ -167,6 +167,31 @@ export function appendRecipeContextToPiArgs(
|
|
|
167
167
|
return next;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
export interface ManagedPiSessionArgs {
|
|
171
|
+
args: string[];
|
|
172
|
+
sessionDir?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const PI_SESSION_POLICY_FLAGS = new Set([
|
|
176
|
+
"--fork",
|
|
177
|
+
"--no-session",
|
|
178
|
+
"--session",
|
|
179
|
+
"--session-dir",
|
|
180
|
+
"--session-id",
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
export function attachPiSessionDir(
|
|
184
|
+
command: string,
|
|
185
|
+
args: string[],
|
|
186
|
+
sessionDir: string,
|
|
187
|
+
): ManagedPiSessionArgs {
|
|
188
|
+
if (!isPiCommand(command) || findPiPrintPromptIndex(args) === undefined) {
|
|
189
|
+
return { args };
|
|
190
|
+
}
|
|
191
|
+
if (args.some((arg) => PI_SESSION_POLICY_FLAGS.has(arg))) return { args };
|
|
192
|
+
return { args: ["--session-dir", sessionDir, ...args], sessionDir };
|
|
193
|
+
}
|
|
194
|
+
|
|
170
195
|
export interface MaterializedPiPrintPromptArgs {
|
|
171
196
|
args: string[];
|
|
172
197
|
promptBytes?: number;
|