@llblab/pi-actors 0.12.5 → 0.12.7
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/CHANGELOG.md +8 -0
- package/docs/async-runs.md +6 -9
- package/docs/template-recipes.md +5 -5
- package/docs/tool-registry.md +10 -10
- package/lib/tools.ts +116 -47
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.12.7: Tool Argument Usage Hints
|
|
6
|
+
|
|
7
|
+
- `[Tools]` Added compact usage hints to runtime actor-tool argument errors when typed normalization or placeholder resolution fails. Impact: if an agent supplies a wrong enum/type value or misses a required template value after schema validation, the error now shows the expected call shape with required and optional fields.
|
|
8
|
+
|
|
9
|
+
## 0.12.6: Documentation Example Alignment
|
|
10
|
+
|
|
11
|
+
- `[Docs]` Replaced remaining shader-ring recipe examples in registry and template-recipe docs with the concrete docs-review actor recipe example, aligned test fixtures, and changed async-run outbox docs to show actor message envelopes without public delivery knobs. Impact: public docs now consistently demonstrate useful actor wrapping and keep coordinator attention policy out of recipe-authored message examples.
|
|
12
|
+
|
|
5
13
|
## 0.12.5: README Actor Recipe Example
|
|
6
14
|
|
|
7
15
|
- `[Docs]` Replaced the placeholder shader-ring onboarding recipe with a concrete async docs-review actor recipe that includes typed args, mailbox metadata, and a real launch template. Impact: README onboarding now demonstrates actor wrapping instead of an abstract placeholder.
|
package/docs/async-runs.md
CHANGED
|
@@ -188,22 +188,19 @@ Shape:
|
|
|
188
188
|
|
|
189
189
|
```json
|
|
190
190
|
{
|
|
191
|
-
"
|
|
191
|
+
"type": "player.track",
|
|
192
|
+
"to": "coordinator",
|
|
193
|
+
"from": "run:music-player",
|
|
192
194
|
"summary": "Now playing: track.flac",
|
|
193
195
|
"level": "info",
|
|
194
|
-
"delivery": "log",
|
|
195
196
|
"ts": "2026-05-19T00:00:00.000Z",
|
|
196
|
-
"
|
|
197
|
+
"body": { "track": "/Music/track.flac", "index": 3, "count": 42 }
|
|
197
198
|
}
|
|
198
199
|
```
|
|
199
200
|
|
|
200
|
-
`level` is `info`, `warning`, or `error`.
|
|
201
|
+
`level` is `info`, `warning`, or `error`. The public message describes sender, receiver, type, summary, and body; it does not choose notification mechanics. Runtime attention policy infers whether a coordinator-bound message stays available for explicit `inspect`, becomes a UI notification, or re-enters the launching coordinator as compact follow-up context.
|
|
201
202
|
|
|
202
|
-
-
|
|
203
|
-
- `notify`: shown as a UI notification to the launching coordinator session.
|
|
204
|
-
- `followup`: notification plus compact follow-up context to the launching coordinator session.
|
|
205
|
-
|
|
206
|
-
Use `followup` for completion and decision-point messages that should reach the coordinator, not for every progress tick. Packaged multi-agent branch completion is a completion message and should bubble by default. Follow-up path lists use Markdown hierarchy: a section heading, `- Base: ...`, and `- Files: ...`, so repeated run-state prefixes do not flood agent context.
|
|
203
|
+
Use coordinator-bound messages for completion and decision points, not for every progress tick. Packaged multi-agent branch completion is a completion message and should bubble by default. Follow-up path lists use Markdown hierarchy: a section heading, `- Base: ...`, and `- Files: ...`, so repeated run-state prefixes do not flood agent context.
|
|
207
204
|
|
|
208
205
|
## Cancellation And Ownership
|
|
209
206
|
|
package/docs/template-recipes.md
CHANGED
|
@@ -170,15 +170,15 @@ A registered tool can point at an actor recipe by storing the recipe path or nam
|
|
|
170
170
|
|
|
171
171
|
```json
|
|
172
172
|
{
|
|
173
|
-
"
|
|
174
|
-
"description": "Start
|
|
175
|
-
"args": ["
|
|
176
|
-
"template": "
|
|
173
|
+
"docs_review": {
|
|
174
|
+
"description": "Start an async docs review actor",
|
|
175
|
+
"args": ["scope:path", "model:string=openai-codex/gpt-5.5"],
|
|
176
|
+
"template": "docs-review.json"
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
If `
|
|
181
|
+
If `docs-review.json` contains `async: true`, calling `docs_review` starts a detached actor run and returns metadata. If `async` is omitted or false, calling `docs_review` executes the recipe foreground and returns normal tool output.
|
|
182
182
|
|
|
183
183
|
A registered tool may also co-locate an actor recipe directly in `actors-tools.json`:
|
|
184
184
|
|
package/docs/tool-registry.md
CHANGED
|
@@ -47,16 +47,16 @@ Use `update=true` to overwrite an existing tool. Omit `template` and co-located
|
|
|
47
47
|
]
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
For reusable workflows, register a small tool whose `template` points to
|
|
50
|
+
For reusable actor workflows, register a small tool whose `template` points to an actor recipe instead of embedding the launch graph in the tool itself:
|
|
51
51
|
|
|
52
52
|
```text
|
|
53
|
-
register_tool name=
|
|
54
|
-
description="Start
|
|
55
|
-
template="
|
|
56
|
-
args="
|
|
53
|
+
register_tool name=docs_review \
|
|
54
|
+
description="Start an async docs review actor" \
|
|
55
|
+
template="docs-review.json" \
|
|
56
|
+
args="scope:path,model:string=openai-codex/gpt-5.5"
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
This stores the recipe path in the registry as `template`. If `~/.pi/agent/recipes/
|
|
59
|
+
This stores the recipe path in the registry as `template`. If `~/.pi/agent/recipes/docs-review.json` contains `async: true`, calling the tool starts a detached actor run and returns metadata immediately. If `async` is omitted or false, the same recipe runs foreground and returns normal tool output.
|
|
60
60
|
|
|
61
61
|
When co-location is clearer than a separate file, the registry entry may include recipe fields directly beside tool metadata:
|
|
62
62
|
|
|
@@ -93,10 +93,10 @@ Tool names come from the top-level registry keys. Tool entries define `template`
|
|
|
93
93
|
"description": "Run pi as a non-interactive sub-agent",
|
|
94
94
|
"template": "pi -p --model {model=openai-codex/gpt-5.5} --no-tools {prompt}"
|
|
95
95
|
},
|
|
96
|
-
"
|
|
97
|
-
"description": "Start
|
|
98
|
-
"args": ["
|
|
99
|
-
"template": "
|
|
96
|
+
"docs_review": {
|
|
97
|
+
"description": "Start an async docs review actor",
|
|
98
|
+
"args": ["scope:path", "model:string=openai-codex/gpt-5.5"],
|
|
99
|
+
"template": "docs-review.json"
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
```
|
package/lib/tools.ts
CHANGED
|
@@ -68,6 +68,71 @@ function objectSchema(
|
|
|
68
68
|
return { additionalProperties: false, properties, required, type: "object" };
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function sampleValueForArg(
|
|
72
|
+
arg: string,
|
|
73
|
+
type: Schema.ToolArgType | undefined,
|
|
74
|
+
defaults: Record<string, unknown>,
|
|
75
|
+
): unknown {
|
|
76
|
+
if (Object.hasOwn(defaults, arg)) return defaults[arg];
|
|
77
|
+
if (!type || type.kind === "string") return `<${arg}>`;
|
|
78
|
+
if (type.kind === "path") return `./${arg}`;
|
|
79
|
+
if (type.kind === "int") return 1;
|
|
80
|
+
if (type.kind === "number") return 1.5;
|
|
81
|
+
if (type.kind === "bool") return true;
|
|
82
|
+
if (type.kind === "array") return [`<${arg}>`];
|
|
83
|
+
return type.values[0] ?? `<${arg}>`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function shouldAddRuntimeToolUsageHint(error: unknown): boolean {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
return (
|
|
89
|
+
/^Argument \S+ must /.test(message) ||
|
|
90
|
+
/^Missing .* value: /.test(message)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function formatRuntimeToolUsageHint(
|
|
95
|
+
cfg: RegisteredTool,
|
|
96
|
+
required: string[],
|
|
97
|
+
includeRunId: boolean,
|
|
98
|
+
): string {
|
|
99
|
+
const optional = cfg.args.filter((arg) => !required.includes(arg));
|
|
100
|
+
const example: Record<string, unknown> = {};
|
|
101
|
+
for (const arg of required)
|
|
102
|
+
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], cfg.defaults);
|
|
103
|
+
for (const arg of optional)
|
|
104
|
+
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], cfg.defaults);
|
|
105
|
+
if (includeRunId) example.run_id = `${cfg.name}-1`;
|
|
106
|
+
const lines = [
|
|
107
|
+
`Expected call shape for ${cfg.name}:`,
|
|
108
|
+
`${cfg.name}(${JSON.stringify(example, null, 2)})`,
|
|
109
|
+
];
|
|
110
|
+
if (required.length) lines.push(`Required: ${required.join(", ")}`);
|
|
111
|
+
if (optional.length || includeRunId)
|
|
112
|
+
lines.push(
|
|
113
|
+
`Optional: ${[...optional, ...(includeRunId ? ["run_id"] : [])].join(", ")}`,
|
|
114
|
+
);
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatRuntimeToolArgumentError(
|
|
119
|
+
cfg: RegisteredTool,
|
|
120
|
+
error: unknown,
|
|
121
|
+
required: string[],
|
|
122
|
+
includeRunId: boolean,
|
|
123
|
+
): Error {
|
|
124
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
+
if (!shouldAddRuntimeToolUsageHint(error))
|
|
126
|
+
return error instanceof Error ? error : new Error(message);
|
|
127
|
+
return new Error(
|
|
128
|
+
`Invalid arguments for tool "${cfg.name}": ${message}\n\n${formatRuntimeToolUsageHint(
|
|
129
|
+
cfg,
|
|
130
|
+
required,
|
|
131
|
+
includeRunId,
|
|
132
|
+
)}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
71
136
|
function looseObjectSchema(description: string): JsonSchema {
|
|
72
137
|
return { additionalProperties: true, description, type: "object" };
|
|
73
138
|
}
|
|
@@ -594,58 +659,62 @@ export function createRuntimeToolDefinition(
|
|
|
594
659
|
_onUpdate: unknown,
|
|
595
660
|
ctx: AsyncRunToolContext,
|
|
596
661
|
) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
662
|
+
try {
|
|
663
|
+
if (isAsyncRecipe) {
|
|
664
|
+
const input = params as Record<string, unknown>;
|
|
665
|
+
const { run_id, ...values } = input;
|
|
666
|
+
const base = cfg.recipe ? cfg.recipe : { file: String(cfg.template) };
|
|
667
|
+
const runId =
|
|
668
|
+
typeof run_id === "string" && run_id.trim()
|
|
669
|
+
? run_id.trim()
|
|
670
|
+
: `${cfg.name}-${Date.now()}`;
|
|
671
|
+
const meta = AsyncRuns.startRun(
|
|
672
|
+
{
|
|
673
|
+
...base,
|
|
674
|
+
ownerId: getRunOwnerId(ctx),
|
|
675
|
+
run_id: runId,
|
|
676
|
+
tool: cfg.name,
|
|
677
|
+
values: Schema.normalizeRuntimeValues(
|
|
678
|
+
{ ...(cfg.recipe?.values ?? {}), ...cfg.defaults, ...values },
|
|
679
|
+
cfg.argTypes,
|
|
680
|
+
),
|
|
681
|
+
},
|
|
682
|
+
ctx.cwd,
|
|
683
|
+
);
|
|
684
|
+
return {
|
|
685
|
+
content: [
|
|
686
|
+
{ type: "text" as const, text: compactAsyncRunStatus(meta) },
|
|
687
|
+
],
|
|
688
|
+
details: meta,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
if (isRecipe && recipeTemplate) {
|
|
692
|
+
const paramsWithDefaults = {
|
|
693
|
+
...(cfg.recipe?.values ?? {}),
|
|
694
|
+
...cfg.defaults,
|
|
695
|
+
...(params as Record<string, unknown>),
|
|
696
|
+
};
|
|
697
|
+
return await Execution.executeRegisteredTool(
|
|
698
|
+
{ ...cfg, template: recipeTemplate },
|
|
699
|
+
Schema.normalizeRuntimeValues(paramsWithDefaults, cfg.argTypes),
|
|
700
|
+
exec,
|
|
701
|
+
ctx.cwd,
|
|
702
|
+
signal,
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
return await Execution.executeRegisteredTool(
|
|
706
|
+
cfg,
|
|
707
|
+
Schema.normalizeRuntimeValues(
|
|
708
|
+
params as Record<string, unknown>,
|
|
709
|
+
cfg.argTypes,
|
|
710
|
+
),
|
|
634
711
|
exec,
|
|
635
712
|
ctx.cwd,
|
|
636
713
|
signal,
|
|
637
714
|
);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
throw formatRuntimeToolArgumentError(cfg, error, required, isAsyncRecipe);
|
|
638
717
|
}
|
|
639
|
-
return Execution.executeRegisteredTool(
|
|
640
|
-
cfg,
|
|
641
|
-
Schema.normalizeRuntimeValues(
|
|
642
|
-
params as Record<string, unknown>,
|
|
643
|
-
cfg.argTypes,
|
|
644
|
-
),
|
|
645
|
-
exec,
|
|
646
|
-
ctx.cwd,
|
|
647
|
-
signal,
|
|
648
|
-
);
|
|
649
718
|
},
|
|
650
719
|
};
|
|
651
720
|
}
|