@llblab/pi-actors 0.22.3 → 0.22.4
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/BACKLOG.md +456 -81
- package/CHANGELOG.md +16 -0
- package/dist/index.js +1 -1
- package/dist/lib/actor-rooms.d.ts +8 -2
- package/dist/lib/actor-rooms.js +58 -9
- package/dist/lib/async-runs.js +32 -10
- package/dist/lib/observability.js +28 -10
- package/dist/lib/recipe-discovery.js +121 -16
- package/dist/lib/recipe-references.d.ts +1 -0
- package/dist/lib/recipe-references.js +40 -1
- package/dist/lib/registry.js +5 -1
- package/dist/lib/tools.js +25 -8
- package/index.ts +1 -1
- package/lib/actor-rooms.ts +79 -11
- package/lib/async-runs.ts +124 -88
- package/lib/observability.ts +85 -24
- package/lib/recipe-discovery.ts +186 -41
- package/lib/recipe-references.ts +88 -14
- package/lib/registry.ts +7 -1
- package/lib/tools.ts +42 -15
- package/package.json +1 -1
- package/skills/actors/SKILL.md +1 -1
- package/skills/swarm/SKILL.md +1 -1
package/lib/recipe-references.ts
CHANGED
|
@@ -109,7 +109,12 @@ export function resolveRecipePath(
|
|
|
109
109
|
|
|
110
110
|
function isBareRecipeName(value: string): boolean {
|
|
111
111
|
const trimmed = value.trim();
|
|
112
|
-
return
|
|
112
|
+
return (
|
|
113
|
+
Boolean(trimmed) &&
|
|
114
|
+
!trimmed.includes("/") &&
|
|
115
|
+
!trimmed.startsWith("~") &&
|
|
116
|
+
!trimmed.includes("{")
|
|
117
|
+
);
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
function recipeNameFiles(value: string): string[] {
|
|
@@ -118,8 +123,12 @@ function recipeNameFiles(value: string): string[] {
|
|
|
118
123
|
return [`${trimmed}.json`, `${trimmed}.md`];
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
function resolveRecipeImportPath(
|
|
122
|
-
|
|
126
|
+
function resolveRecipeImportPath(
|
|
127
|
+
value: string,
|
|
128
|
+
currentRecipeRoot: string,
|
|
129
|
+
): string {
|
|
130
|
+
if (!isBareRecipeName(value))
|
|
131
|
+
return resolveRecipePath(value, currentRecipeRoot);
|
|
123
132
|
const roots = [
|
|
124
133
|
Paths.getRecipeRoot(),
|
|
125
134
|
currentRecipeRoot,
|
|
@@ -266,11 +275,15 @@ function parseMarkdownFrontmatterObject(
|
|
|
266
275
|
lines: string[],
|
|
267
276
|
): Record<string, unknown> | unknown[] {
|
|
268
277
|
if (lines.every((line) => /^\s*-\s+/.test(line))) {
|
|
269
|
-
return lines.map((line) =>
|
|
278
|
+
return lines.map((line) =>
|
|
279
|
+
parseMarkdownScalar(line.replace(/^\s*-\s+/, "")),
|
|
280
|
+
);
|
|
270
281
|
}
|
|
271
282
|
const result: Record<string, unknown> = {};
|
|
272
283
|
for (let index = 0; index < lines.length; index += 1) {
|
|
273
|
-
const match = lines[index].match(
|
|
284
|
+
const match = lines[index].match(
|
|
285
|
+
/^\s{2}([A-Za-z_][A-Za-z0-9_.-]*):\s*(.*)$/,
|
|
286
|
+
);
|
|
274
287
|
if (!match) continue;
|
|
275
288
|
if (match[2]) {
|
|
276
289
|
result[match[1]] = parseMarkdownScalar(match[2]);
|
|
@@ -331,11 +344,14 @@ function parseMarkdownRecipeConfig(
|
|
|
331
344
|
): Record<string, unknown> | undefined {
|
|
332
345
|
const lines = content.split(/\r?\n/);
|
|
333
346
|
if (lines[0]?.trim() !== "---") return undefined;
|
|
334
|
-
const end = lines.findIndex(
|
|
347
|
+
const end = lines.findIndex(
|
|
348
|
+
(line, index) => index > 0 && line.trim() === "---",
|
|
349
|
+
);
|
|
335
350
|
if (end === -1) return undefined;
|
|
336
351
|
const frontmatter = parseMarkdownFrontmatter(lines.slice(1, end).join("\n"));
|
|
337
352
|
const fence = findMarkdownRecipeFence(lines.slice(end + 1).join("\n"));
|
|
338
|
-
if (!fence)
|
|
353
|
+
if (!fence)
|
|
354
|
+
return Object.hasOwn(frontmatter, "template") ? frontmatter : undefined;
|
|
339
355
|
const text = fence.body.trim();
|
|
340
356
|
if (!text) return undefined;
|
|
341
357
|
if (
|
|
@@ -351,12 +367,50 @@ function parseMarkdownRecipeConfig(
|
|
|
351
367
|
return { ...frontmatter, ...parsed };
|
|
352
368
|
return { ...frontmatter, template: parsed };
|
|
353
369
|
} catch {
|
|
354
|
-
if (fence.info.includes("json") || fence.info.includes("recipe"))
|
|
370
|
+
if (fence.info.includes("json") || fence.info.includes("recipe"))
|
|
371
|
+
return undefined;
|
|
355
372
|
}
|
|
356
373
|
}
|
|
357
374
|
return { ...frontmatter, template: text };
|
|
358
375
|
}
|
|
359
376
|
|
|
377
|
+
export function diagnoseRawRecipeConfigFailure(
|
|
378
|
+
path: string,
|
|
379
|
+
): string | undefined {
|
|
380
|
+
if (!existsSync(path)) return "file does not exist";
|
|
381
|
+
const size = statSync(path).size;
|
|
382
|
+
if (size > MAX_RECIPE_FILE_BYTES) {
|
|
383
|
+
return `file exceeds size limit ${MAX_RECIPE_FILE_BYTES} bytes`;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const content = readFileSync(path, "utf8");
|
|
387
|
+
if (path.endsWith(".md")) {
|
|
388
|
+
const lines = content.split(/\r?\n/);
|
|
389
|
+
if (lines[0]?.trim() !== "---") {
|
|
390
|
+
return "Markdown recipe must start with frontmatter";
|
|
391
|
+
}
|
|
392
|
+
const end = lines.findIndex(
|
|
393
|
+
(line, index) => index > 0 && line.trim() === "---",
|
|
394
|
+
);
|
|
395
|
+
if (end === -1) return "Markdown recipe frontmatter is not closed";
|
|
396
|
+
const parsed = parseMarkdownRecipeConfig(content);
|
|
397
|
+
if (!parsed)
|
|
398
|
+
return "Markdown recipe has no executable recipe/template fence";
|
|
399
|
+
if (!Object.hasOwn(parsed, "template"))
|
|
400
|
+
return "recipe must define template";
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
const raw = JSON.parse(content) as unknown;
|
|
404
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
405
|
+
return "JSON recipe must be an object";
|
|
406
|
+
}
|
|
407
|
+
if (!Object.hasOwn(raw, "template")) return "recipe must define template";
|
|
408
|
+
return undefined;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
return error instanceof Error ? error.message : String(error);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
360
414
|
export function readRawRecipeConfig(
|
|
361
415
|
path: string,
|
|
362
416
|
): Record<string, unknown> | undefined {
|
|
@@ -593,7 +647,8 @@ function expandImportNodes(
|
|
|
593
647
|
if (typeof value === "string") return value;
|
|
594
648
|
if (Array.isArray(value)) {
|
|
595
649
|
return value.map(
|
|
596
|
-
(item) =>
|
|
650
|
+
(item) =>
|
|
651
|
+
expandImportNodes(item, imports, options) as CommandTemplateConfig,
|
|
597
652
|
);
|
|
598
653
|
}
|
|
599
654
|
const record = value as Record<string, unknown>;
|
|
@@ -614,7 +669,11 @@ function expandImportNodes(
|
|
|
614
669
|
nodeDefaults,
|
|
615
670
|
nodeValues,
|
|
616
671
|
);
|
|
617
|
-
const expanded = applyDefaultsToTemplate(
|
|
672
|
+
const expanded = applyDefaultsToTemplate(
|
|
673
|
+
imported.config.template,
|
|
674
|
+
defaults,
|
|
675
|
+
record,
|
|
676
|
+
);
|
|
618
677
|
return options.includeActorRecipeContext
|
|
619
678
|
? withActorRecipeContext(expanded, {
|
|
620
679
|
alias: imported.alias,
|
|
@@ -672,8 +731,15 @@ export function readResolvedRecipeConfig(
|
|
|
672
731
|
if (!raw || !Object.hasOwn(raw, "template")) return undefined;
|
|
673
732
|
const imports: Record<string, ImportedRecipe> = {};
|
|
674
733
|
for (const [alias, binding] of Object.entries(getRecipeImports(raw))) {
|
|
675
|
-
const importPath = resolveRecipeImportPath(
|
|
676
|
-
|
|
734
|
+
const importPath = resolveRecipeImportPath(
|
|
735
|
+
getImportFrom(binding),
|
|
736
|
+
dirname(path),
|
|
737
|
+
);
|
|
738
|
+
const config = readResolvedRecipeConfig(
|
|
739
|
+
importPath,
|
|
740
|
+
[...stack, path],
|
|
741
|
+
options,
|
|
742
|
+
);
|
|
677
743
|
if (!config) throw new Error(`Recipe import not found: ${alias}`);
|
|
678
744
|
const bindingDefaults =
|
|
679
745
|
typeof binding === "string" ? undefined : binding.defaults;
|
|
@@ -836,8 +902,16 @@ function collectRecipeContextRecords(
|
|
|
836
902
|
};
|
|
837
903
|
const imports = getRecipeImports(raw);
|
|
838
904
|
const children = Object.entries(imports).flatMap(([childAlias, binding]) => {
|
|
839
|
-
const importFile = resolveRecipeImportPath(
|
|
840
|
-
|
|
905
|
+
const importFile = resolveRecipeImportPath(
|
|
906
|
+
getImportFrom(binding),
|
|
907
|
+
dirname(path),
|
|
908
|
+
);
|
|
909
|
+
return collectRecipeContextRecords(
|
|
910
|
+
importFile,
|
|
911
|
+
[...stack, path],
|
|
912
|
+
[...importPath, childAlias],
|
|
913
|
+
childAlias,
|
|
914
|
+
);
|
|
841
915
|
});
|
|
842
916
|
return [record, ...children];
|
|
843
917
|
}
|
package/lib/registry.ts
CHANGED
|
@@ -161,8 +161,14 @@ function getInputTemplate(
|
|
|
161
161
|
);
|
|
162
162
|
return value;
|
|
163
163
|
}
|
|
164
|
+
if (value && typeof value === "object") {
|
|
165
|
+
CommandTemplates.expandCommandTemplateConfigs(
|
|
166
|
+
value as CommandTemplates.CommandTemplateConfig,
|
|
167
|
+
);
|
|
168
|
+
return value as CommandTemplates.CommandTemplateConfig;
|
|
169
|
+
}
|
|
164
170
|
throw new Error(
|
|
165
|
-
Output.formatToolText("Tool template must be a string or sequence."),
|
|
171
|
+
Output.formatToolText("Tool template must be a string, object, or sequence."),
|
|
166
172
|
);
|
|
167
173
|
}
|
|
168
174
|
|
package/lib/tools.ts
CHANGED
|
@@ -436,6 +436,7 @@ export function createRegisterToolDefinition<TContext>(
|
|
|
436
436
|
),
|
|
437
437
|
template: unionSchema([
|
|
438
438
|
stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
|
|
439
|
+
looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
|
|
439
440
|
arraySchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateArray),
|
|
440
441
|
nullSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateNull),
|
|
441
442
|
]),
|
|
@@ -485,6 +486,27 @@ function messageBodyToToolParams(
|
|
|
485
486
|
return { input: message.body };
|
|
486
487
|
}
|
|
487
488
|
|
|
489
|
+
function formatToolActorFailure(
|
|
490
|
+
tool: string,
|
|
491
|
+
message: ActorMessages.ActorMessage,
|
|
492
|
+
params: Record<string, unknown>,
|
|
493
|
+
error: unknown,
|
|
494
|
+
): Error {
|
|
495
|
+
const original = error instanceof Error ? error.message : String(error);
|
|
496
|
+
const paramsPreview = compactPreview(params, 240) ?? "{}";
|
|
497
|
+
return Object.assign(
|
|
498
|
+
new Error(
|
|
499
|
+
`tool actor ${tool} failed for message type ${message.type}: ${original}; params=${paramsPreview}`,
|
|
500
|
+
),
|
|
501
|
+
{
|
|
502
|
+
message_type: message.type,
|
|
503
|
+
original_error: original,
|
|
504
|
+
params_preview: paramsPreview,
|
|
505
|
+
tool,
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
488
510
|
function runIdFromActorAddress(
|
|
489
511
|
address: string | undefined,
|
|
490
512
|
): string | undefined {
|
|
@@ -915,24 +937,25 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
915
937
|
if (address.kind === "branch") {
|
|
916
938
|
if (view !== "mailbox") throw new Error("inspect branch:<run>/<branch> supports view=mailbox.");
|
|
917
939
|
const status = assertRunAccessibleToContext(runId, ctx);
|
|
918
|
-
const
|
|
940
|
+
const branchInbox = ActorRooms.readBranchInboxDiagnostics(
|
|
919
941
|
String(status.state_dir ?? ""),
|
|
920
942
|
runId,
|
|
921
943
|
target,
|
|
922
944
|
Number(input.lines || 40),
|
|
923
945
|
);
|
|
946
|
+
const messages = branchInbox.messages;
|
|
924
947
|
return {
|
|
925
948
|
content: [
|
|
926
949
|
{
|
|
927
950
|
type: "text" as const,
|
|
928
951
|
text: maybeJsonText(
|
|
929
|
-
messages,
|
|
952
|
+
input.verbose === true ? branchInbox : messages,
|
|
930
953
|
input.verbose === true,
|
|
931
|
-
compactBranchInbox(messages.map((message) => ({ ...message })))
|
|
954
|
+
`${compactBranchInbox(messages.map((message) => ({ ...message })))}${branchInbox.corrupted > 0 ? `\ncorrupted=${branchInbox.corrupted}` : ""}`,
|
|
932
955
|
),
|
|
933
956
|
},
|
|
934
957
|
],
|
|
935
|
-
details: { messages },
|
|
958
|
+
details: { corrupted: branchInbox.corrupted, messages },
|
|
936
959
|
};
|
|
937
960
|
}
|
|
938
961
|
switch (view) {
|
|
@@ -1118,9 +1141,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1118
1141
|
} else if (address.kind === "branch" && address.value) {
|
|
1119
1142
|
const runId = address.value;
|
|
1120
1143
|
if (message.from) assertMessageSenderBelongsToRun(message, runId, `branch:${runId}/<branch>`);
|
|
1121
|
-
const status =
|
|
1122
|
-
? assertRunExistsForActorMessage(runId)
|
|
1123
|
-
: assertRunAccessibleToContext(runId, ctx);
|
|
1144
|
+
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1124
1145
|
const stateDir = String(status.state_dir ?? "");
|
|
1125
1146
|
if (stateDir && address.branch) {
|
|
1126
1147
|
const ensureBranchMember = (actorAddress: string) => {
|
|
@@ -1159,7 +1180,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1159
1180
|
} else if (address.kind === "room" && address.value && address.room) {
|
|
1160
1181
|
const runId = address.value;
|
|
1161
1182
|
assertMessageSenderBelongsToRun(message, runId, `room:${runId}`);
|
|
1162
|
-
const status =
|
|
1183
|
+
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1163
1184
|
const stateDir = String(status.state_dir ?? "");
|
|
1164
1185
|
if (!stateDir) throw new Error(`${message.to} has no run state directory.`);
|
|
1165
1186
|
const recipients = getRoomMulticastRecipients(message, runId);
|
|
@@ -1185,13 +1206,19 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1185
1206
|
`tool actor not found or not executable: ${address.value}`,
|
|
1186
1207
|
);
|
|
1187
1208
|
}
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1209
|
+
const toolParams = messageBodyToToolParams(message);
|
|
1210
|
+
let toolResult: unknown;
|
|
1211
|
+
try {
|
|
1212
|
+
toolResult = await tool.execute(
|
|
1213
|
+
`message:${message.type}`,
|
|
1214
|
+
toolParams,
|
|
1215
|
+
_signal,
|
|
1216
|
+
_onUpdate,
|
|
1217
|
+
ctx,
|
|
1218
|
+
);
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
throw formatToolActorFailure(address.value, message, toolParams, error);
|
|
1221
|
+
}
|
|
1195
1222
|
result = {
|
|
1196
1223
|
invoked: true,
|
|
1197
1224
|
sent: true,
|
package/package.json
CHANGED
package/skills/actors/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.22.
|
|
5
|
+
version: 0.22.4
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
package/skills/swarm/SKILL.md
CHANGED