@llblab/pi-actors 0.33.0 → 0.34.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 +22 -11
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +22 -1
- package/dist/index.js +10 -10
- package/dist/lib/async-runs.d.ts +23 -75
- package/dist/lib/async-runs.js +49 -801
- package/dist/lib/command-templates.d.ts +3 -3
- package/dist/lib/command-templates.js +3 -3
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/config.js +5 -5
- package/dist/lib/{output.d.ts → execution-output.d.ts} +3 -3
- package/dist/lib/{output.js → execution-output.js} +3 -3
- package/dist/lib/execution.js +1 -1
- package/dist/lib/{actor-inspector-tui.d.ts → inspector.d.ts} +3 -2
- package/dist/lib/{actor-inspector-tui.js → inspector.js} +3 -2
- package/dist/lib/mailbox-loop.d.ts +3 -3
- package/dist/lib/mailbox-loop.js +3 -3
- package/dist/lib/{actor-messages.d.ts → messages.d.ts} +3 -3
- package/dist/lib/{actor-messages.js → messages.js} +3 -3
- package/dist/lib/{actor-recipe-context.d.ts → recipes-context.d.ts} +3 -2
- package/dist/lib/{actor-recipe-context.js → recipes-context.js} +2 -1
- package/dist/lib/{recipe-discovery.d.ts → recipes-discovery.d.ts} +8 -8
- package/dist/lib/{recipe-discovery.js → recipes-discovery.js} +6 -6
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +22 -22
- package/dist/lib/{actor-rooms.d.ts → rooms.d.ts} +4 -4
- package/dist/lib/{actor-rooms.js → rooms.js} +3 -3
- package/dist/lib/runs-artifacts.d.ts +21 -0
- package/dist/lib/runs-artifacts.js +68 -0
- package/dist/lib/runs-control.d.ts +13 -0
- package/dist/lib/runs-control.js +59 -0
- package/dist/lib/runs-identity.d.ts +5 -0
- package/dist/lib/runs-identity.js +11 -0
- package/dist/lib/runs-index.d.ts +18 -0
- package/dist/lib/runs-index.js +119 -0
- package/dist/lib/runs-mailbox.d.ts +25 -0
- package/dist/lib/runs-mailbox.js +146 -0
- package/dist/lib/runs-messages.d.ts +15 -0
- package/dist/lib/runs-messages.js +179 -0
- package/dist/lib/runs-outbox.d.ts +41 -0
- package/dist/lib/runs-outbox.js +87 -0
- package/dist/lib/runs-process.d.ts +7 -0
- package/dist/lib/runs-process.js +36 -0
- package/dist/lib/runs-retention.d.ts +8 -0
- package/dist/lib/runs-retention.js +56 -0
- package/dist/lib/runs-start.d.ts +9 -0
- package/dist/lib/runs-start.js +67 -0
- package/dist/lib/runs-status.d.ts +8 -0
- package/dist/lib/runs-status.js +60 -0
- package/dist/lib/runtime.d.ts +2 -2
- package/dist/lib/runtime.js +5 -5
- package/dist/lib/schema.d.ts +9 -0
- package/dist/lib/schema.js +40 -0
- package/dist/lib/tools-access.d.ts +19 -0
- package/dist/lib/tools-access.js +44 -0
- package/dist/lib/tools-inspect.d.ts +11 -0
- package/dist/lib/tools-inspect.js +534 -0
- package/dist/lib/tools-local.d.ts +14 -0
- package/dist/lib/tools-local.js +156 -0
- package/dist/lib/tools-mailbox.d.ts +8 -0
- package/dist/lib/tools-mailbox.js +48 -0
- package/dist/lib/tools-message.d.ts +9 -0
- package/dist/lib/tools-message.js +346 -0
- package/dist/lib/tools-register.d.ts +18 -0
- package/dist/lib/tools-register.js +40 -0
- package/dist/lib/tools-response.d.ts +21 -0
- package/dist/lib/tools-response.js +219 -0
- package/dist/lib/tools-spawn.d.ts +12 -0
- package/dist/lib/tools-spawn.js +159 -0
- package/dist/lib/tools.d.ts +8 -39
- package/dist/lib/tools.js +11 -1490
- package/dist/recipes/utility-coordinator-lock-snapshot.json +1 -1
- package/dist/scripts/actor-worker.mjs +186 -7
- package/dist/scripts/async-runner.mjs +171 -8
- package/dist/scripts/build-dist.mjs +1 -1
- package/dist/scripts/conformance.mjs +3 -3
- package/dist/scripts/coordinator.mjs +733 -11
- package/dist/scripts/locker.mjs +345 -16
- package/dist/scripts/recipe-utils.mjs +478 -14
- package/dist/scripts/validate-recipe.mjs +110 -10
- package/dist/skills/actors/SKILL.md +1 -1
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/recipe-library.md +1 -1
- package/docs/template-recipes.md +1 -1
- package/index.ts +11 -18
- package/lib/async-runs.ts +124 -1016
- package/lib/command-templates.ts +3 -3
- package/lib/config.ts +7 -7
- package/lib/{output.ts → execution-output.ts} +3 -3
- package/lib/execution.ts +5 -1
- package/lib/{actor-inspector-tui.ts → inspector.ts} +4 -3
- package/lib/mailbox-loop.ts +3 -3
- package/lib/{actor-messages.ts → messages.ts} +3 -3
- package/lib/{actor-recipe-context.ts → recipes-context.ts} +3 -2
- package/lib/{recipe-discovery.ts → recipes-discovery.ts} +19 -19
- package/lib/{recipe-usage.ts → recipes-usage.ts} +2 -2
- package/lib/registry.ts +37 -24
- package/lib/{actor-rooms.ts → rooms.ts} +4 -4
- package/lib/runs-artifacts.ts +96 -0
- package/lib/runs-control.ts +85 -0
- package/lib/runs-identity.ts +14 -0
- package/lib/runs-index.ts +156 -0
- package/lib/runs-mailbox.ts +208 -0
- package/lib/runs-messages.ts +252 -0
- package/lib/runs-outbox.ts +144 -0
- package/lib/runs-process.ts +51 -0
- package/lib/runs-retention.ts +77 -0
- package/lib/runs-start.ts +98 -0
- package/lib/runs-status.ts +78 -0
- package/lib/runtime.ts +6 -6
- package/lib/schema.ts +55 -0
- package/lib/tools-access.ts +68 -0
- package/lib/tools-inspect.ts +735 -0
- package/lib/tools-local.ts +234 -0
- package/lib/tools-mailbox.ts +56 -0
- package/lib/tools-message.ts +483 -0
- package/lib/tools-register.ts +64 -0
- package/lib/tools-response.ts +267 -0
- package/lib/tools-spawn.ts +240 -0
- package/lib/tools.ts +20 -2040
- package/package.json +1 -1
- package/recipes/utility-coordinator-lock-snapshot.json +1 -1
- package/scripts/actor-worker.mjs +186 -7
- package/scripts/async-runner.mjs +171 -8
- package/scripts/build-dist.mjs +1 -1
- package/scripts/conformance.mjs +3 -3
- package/scripts/coordinator.mjs +733 -11
- package/scripts/locker.mjs +345 -16
- package/scripts/recipe-utils.mjs +478 -14
- package/scripts/validate-recipe.mjs +110 -10
- package/skills/actors/SKILL.md +1 -1
- package/skills/swarm/SKILL.md +1 -1
- package/dist/lib/actor-worker.d.ts +0 -16
- package/dist/lib/actor-worker.js +0 -134
- package/dist/lib/async-runner.d.ts +0 -5
- package/dist/lib/async-runner.js +0 -134
- package/dist/lib/coordinator.d.ts +0 -5
- package/dist/lib/coordinator.js +0 -660
- package/dist/lib/locker.d.ts +0 -5
- package/dist/lib/locker.js +0 -310
- package/dist/lib/recipe-utils.d.ts +0 -5
- package/dist/lib/recipe-utils.js +0 -385
- package/dist/lib/validate-recipe.d.ts +0 -6
- package/dist/lib/validate-recipe.js +0 -104
- package/lib/actor-worker.ts +0 -174
- package/lib/async-runner.ts +0 -173
- package/lib/coordinator.ts +0 -742
- package/lib/locker.ts +0 -340
- package/lib/recipe-utils.ts +0 -486
- package/lib/validate-recipe.ts +0 -110
- /package/dist/lib/{recipe-references.d.ts → recipes-references.d.ts} +0 -0
- /package/dist/lib/{recipe-references.js → recipes-references.js} +0 -0
- /package/dist/lib/{recipe-usage.d.ts → recipes-usage.d.ts} +0 -0
- /package/dist/lib/{recipe-usage.js → recipes-usage.js} +0 -0
- /package/lib/{recipe-references.ts → recipes-references.ts} +0 -0
package/dist/lib/tools.js
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Zones:
|
|
4
|
-
* Owns
|
|
2
|
+
* Public tool family composition
|
|
3
|
+
* Zones: tool set, reserved tool names, pi-facing tool wiring
|
|
4
|
+
* Owns public tool-family composition without owning individual tool behavior
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import * as
|
|
10
|
-
import * as ActorRooms from "./actor-rooms.js";
|
|
11
|
-
import * as AsyncRuns from "./async-runs.js";
|
|
12
|
-
import * as Execution from "./execution.js";
|
|
13
|
-
import * as Limits from "./limits.js";
|
|
14
|
-
import * as Paths from "./paths.js";
|
|
15
|
-
import * as Prompts from "./prompts.js";
|
|
16
|
-
import * as RecipeDiscovery from "./recipe-discovery.js";
|
|
17
|
-
import * as RecipeReferences from "./recipe-references.js";
|
|
18
|
-
import * as RecipeUsage from "./recipe-usage.js";
|
|
19
|
-
import * as Registry from "./registry.js";
|
|
20
|
-
import * as Schema from "./schema.js";
|
|
6
|
+
import * as ToolsInspect from "./tools-inspect.js";
|
|
7
|
+
import * as ToolsMessage from "./tools-message.js";
|
|
8
|
+
import * as ToolsRegister from "./tools-register.js";
|
|
9
|
+
import * as ToolsSpawn from "./tools-spawn.js";
|
|
21
10
|
export const RESERVED_TOOL_NAMES = new Set([
|
|
22
11
|
"read",
|
|
23
12
|
"write",
|
|
@@ -31,1394 +20,9 @@ export const RESERVED_TOOL_NAMES = new Set([
|
|
|
31
20
|
"spawn",
|
|
32
21
|
"inspect",
|
|
33
22
|
]);
|
|
34
|
-
function stringSchema(description) {
|
|
35
|
-
return { description, type: "string" };
|
|
36
|
-
}
|
|
37
|
-
function typedArgSchema(arg, type) {
|
|
38
|
-
if (!type || type.kind === "string")
|
|
39
|
-
return stringSchema(`Argument: ${arg}`);
|
|
40
|
-
if (type.kind === "path")
|
|
41
|
-
return stringSchema(`Path argument: ${arg}`);
|
|
42
|
-
if (type.kind === "int")
|
|
43
|
-
return { description: `Integer argument: ${arg}`, type: "integer" };
|
|
44
|
-
if (type.kind === "number")
|
|
45
|
-
return { description: `Number argument: ${arg}`, type: "number" };
|
|
46
|
-
if (type.kind === "bool")
|
|
47
|
-
return { description: `Boolean argument: ${arg}`, type: "boolean" };
|
|
48
|
-
if (type.kind === "array")
|
|
49
|
-
return { description: `Array argument: ${arg}`, items: {}, type: "array" };
|
|
50
|
-
return {
|
|
51
|
-
description: `Enum argument: ${arg}`,
|
|
52
|
-
enum: type.values,
|
|
53
|
-
type: "string",
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function booleanSchema(description) {
|
|
57
|
-
return { description, type: "boolean" };
|
|
58
|
-
}
|
|
59
|
-
function nullSchema(description) {
|
|
60
|
-
return { description, type: "null" };
|
|
61
|
-
}
|
|
62
|
-
function arraySchema(description) {
|
|
63
|
-
return { description, items: {}, type: "array" };
|
|
64
|
-
}
|
|
65
|
-
function unionSchema(anyOf) {
|
|
66
|
-
return { anyOf };
|
|
67
|
-
}
|
|
68
|
-
function objectSchema(properties, required) {
|
|
69
|
-
return { additionalProperties: false, properties, required, type: "object" };
|
|
70
|
-
}
|
|
71
|
-
function sampleValueForArg(arg, type, defaults) {
|
|
72
|
-
if (Object.hasOwn(defaults, arg))
|
|
73
|
-
return defaults[arg];
|
|
74
|
-
if (!type || type.kind === "string")
|
|
75
|
-
return `<${arg}>`;
|
|
76
|
-
if (type.kind === "path")
|
|
77
|
-
return `./${arg}`;
|
|
78
|
-
if (type.kind === "int")
|
|
79
|
-
return 1;
|
|
80
|
-
if (type.kind === "number")
|
|
81
|
-
return 1.5;
|
|
82
|
-
if (type.kind === "bool")
|
|
83
|
-
return true;
|
|
84
|
-
if (type.kind === "array")
|
|
85
|
-
return [`<${arg}>`];
|
|
86
|
-
return type.values[0] ?? `<${arg}>`;
|
|
87
|
-
}
|
|
88
|
-
function shouldAddRuntimeToolUsageHint(error) {
|
|
89
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
-
return (/^Argument \S+ must /.test(message) || /^Missing .* value: /.test(message));
|
|
91
|
-
}
|
|
92
|
-
function formatRuntimeToolUsageHint(cfg, required, includeRunId) {
|
|
93
|
-
const optional = cfg.args.filter((arg) => !required.includes(arg));
|
|
94
|
-
const example = {};
|
|
95
|
-
for (const arg of required)
|
|
96
|
-
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], cfg.defaults);
|
|
97
|
-
for (const arg of optional)
|
|
98
|
-
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], cfg.defaults);
|
|
99
|
-
if (includeRunId)
|
|
100
|
-
example.run_id = `${cfg.name}-1`;
|
|
101
|
-
const lines = [
|
|
102
|
-
`Expected call shape for ${cfg.name}:`,
|
|
103
|
-
`${cfg.name}(${JSON.stringify(example, null, 2)})`,
|
|
104
|
-
];
|
|
105
|
-
if (required.length)
|
|
106
|
-
lines.push(`Required: ${required.join(", ")}`);
|
|
107
|
-
if (optional.length || includeRunId)
|
|
108
|
-
lines.push(`Optional: ${[...optional, ...(includeRunId ? ["run_id"] : [])].join(", ")}`);
|
|
109
|
-
return lines.join("\n");
|
|
110
|
-
}
|
|
111
|
-
function formatRuntimeToolArgumentError(cfg, error, required, includeRunId) {
|
|
112
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
-
if (!shouldAddRuntimeToolUsageHint(error))
|
|
114
|
-
return error instanceof Error ? error : new Error(message);
|
|
115
|
-
return new Error(`Invalid arguments for tool "${cfg.name}": ${message}\n\n${formatRuntimeToolUsageHint(cfg, required, includeRunId)}`);
|
|
116
|
-
}
|
|
117
|
-
function looseObjectSchema(description) {
|
|
118
|
-
return { additionalProperties: true, description, type: "object" };
|
|
119
|
-
}
|
|
120
|
-
function jsonText(value) {
|
|
121
|
-
return `\n${JSON.stringify(value, null, 2)}`;
|
|
122
|
-
}
|
|
123
|
-
function asRecord(value) {
|
|
124
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
125
|
-
? value
|
|
126
|
-
: {};
|
|
127
|
-
}
|
|
128
|
-
function formatFailureCount(value) {
|
|
129
|
-
return Array.isArray(value) ? value.length : undefined;
|
|
130
|
-
}
|
|
131
|
-
function actorRunNextActions(run) {
|
|
132
|
-
const id = String(run ?? "").trim();
|
|
133
|
-
if (!id)
|
|
134
|
-
return [];
|
|
135
|
-
return [
|
|
136
|
-
`inspect target=run:${id} view=status`,
|
|
137
|
-
`inspect target=run:${id} view=messages`,
|
|
138
|
-
`message to=run:${id} type=<actor.action>`,
|
|
139
|
-
];
|
|
140
|
-
}
|
|
141
|
-
function compactAsyncRunStatus(value) {
|
|
142
|
-
const status = asRecord(value);
|
|
143
|
-
const progress = asRecord(status.progress);
|
|
144
|
-
const result = asRecord(status.result);
|
|
145
|
-
const run = String(status.run ?? "<unknown>");
|
|
146
|
-
const tokens = [
|
|
147
|
-
`run=${run}`,
|
|
148
|
-
`status=${String(status.status ?? "unknown")}`,
|
|
149
|
-
];
|
|
150
|
-
if (status.tool)
|
|
151
|
-
tokens.push(`tool=${String(status.tool)}`);
|
|
152
|
-
if (status.recipe)
|
|
153
|
-
tokens.push(`recipe=${String(status.recipe)}`);
|
|
154
|
-
if (status.retire_when)
|
|
155
|
-
tokens.push(`retire_when=${String(status.retire_when)}`);
|
|
156
|
-
if (Number(status.pid) > 0)
|
|
157
|
-
tokens.push(`pid=${Number(status.pid)}`);
|
|
158
|
-
if (progress.phase && progress.phase !== status.status)
|
|
159
|
-
tokens.push(`phase=${String(progress.phase)}`);
|
|
160
|
-
if (Number(progress.activeSubagents) > 0)
|
|
161
|
-
tokens.push(`active=${Number(progress.activeSubagents)}`);
|
|
162
|
-
if (Number(progress.completed) > 0)
|
|
163
|
-
tokens.push(`completed=${Number(progress.completed)}`);
|
|
164
|
-
const failures = formatFailureCount(progress.failures);
|
|
165
|
-
if (failures !== undefined && failures > 0)
|
|
166
|
-
tokens.push(`failures=${failures}`);
|
|
167
|
-
if (result.code !== undefined)
|
|
168
|
-
tokens.push(`code=${String(result.code)}`);
|
|
169
|
-
if (result.killed === true)
|
|
170
|
-
tokens.push("killed=true");
|
|
171
|
-
const draftRecipe = status.draft_recipe;
|
|
172
|
-
if (draftRecipe)
|
|
173
|
-
tokens.push(`draft_recipe=${String(draftRecipe)}`);
|
|
174
|
-
const nextActions = actorRunNextActions(run);
|
|
175
|
-
if (nextActions.length > 0)
|
|
176
|
-
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
177
|
-
return `\n${tokens.join(" ")}`;
|
|
178
|
-
}
|
|
179
|
-
function compactRunMessages(messages) {
|
|
180
|
-
if (messages.length === 0)
|
|
181
|
-
return "\n(no actor messages)";
|
|
182
|
-
return `\n${messages
|
|
183
|
-
.map((message) => [
|
|
184
|
-
`run=${message.run}`,
|
|
185
|
-
`type=${message.event}`,
|
|
186
|
-
`level=${message.level}`,
|
|
187
|
-
`summary=${message.summary.replaceAll(/\s+/g, "_")}`,
|
|
188
|
-
].join(" "))
|
|
189
|
-
.join("\n")}`;
|
|
190
|
-
}
|
|
191
|
-
function compactPreview(value, maxLength = Limits.COMPACT_PREVIEW_CHARS) {
|
|
192
|
-
if (value === undefined)
|
|
193
|
-
return undefined;
|
|
194
|
-
const text = typeof value === "string" ? value : JSON.stringify(value, undefined, 0);
|
|
195
|
-
const compact = text.replaceAll(/\s+/g, "_");
|
|
196
|
-
return compact.length > maxLength
|
|
197
|
-
? `${compact.slice(0, Math.max(0, maxLength - 1))}…`
|
|
198
|
-
: compact;
|
|
199
|
-
}
|
|
200
|
-
function compactRoomPreviews(previews) {
|
|
201
|
-
if (previews.length === 0)
|
|
202
|
-
return "\n(no room message previews)";
|
|
203
|
-
return `\n${previews
|
|
204
|
-
.map((preview) => [
|
|
205
|
-
`ts=${preview.timestamp}`,
|
|
206
|
-
preview.from ? `from=${preview.from}` : "",
|
|
207
|
-
`to=${preview.to}`,
|
|
208
|
-
`type=${preview.type}`,
|
|
209
|
-
preview.summary ? `summary=${compactPreview(preview.summary)}` : "",
|
|
210
|
-
preview.body_preview
|
|
211
|
-
? `body=${compactPreview(preview.body_preview)}`
|
|
212
|
-
: "",
|
|
213
|
-
]
|
|
214
|
-
.filter(Boolean)
|
|
215
|
-
.join(" "))
|
|
216
|
-
.join("\n")}`;
|
|
217
|
-
}
|
|
218
|
-
function compactRoomMessages(messages) {
|
|
219
|
-
if (messages.length === 0)
|
|
220
|
-
return "\n(no room messages)";
|
|
221
|
-
return `\n${messages
|
|
222
|
-
.map((message) => [
|
|
223
|
-
`ts=${message.received_at}`,
|
|
224
|
-
`from=${String(message.from ?? "<unknown>")}`,
|
|
225
|
-
`to=${message.to}`,
|
|
226
|
-
`type=${message.type}`,
|
|
227
|
-
`summary=${String(message.summary ?? "").replaceAll(/\s+/g, "_")}`,
|
|
228
|
-
compactPreview(message.body)
|
|
229
|
-
? `body=${compactPreview(message.body)}`
|
|
230
|
-
: "",
|
|
231
|
-
]
|
|
232
|
-
.filter(Boolean)
|
|
233
|
-
.join(" "))
|
|
234
|
-
.join("\n")}`;
|
|
235
|
-
}
|
|
236
|
-
function compactRoomContacts(contacts) {
|
|
237
|
-
if (contacts.length === 0)
|
|
238
|
-
return "\n(no room contacts)";
|
|
239
|
-
return `\n${contacts
|
|
240
|
-
.map((contact) => [
|
|
241
|
-
`address=${contact.address}`,
|
|
242
|
-
contact.role !== undefined ? `role=${String(contact.role)}` : "",
|
|
243
|
-
contact.parent !== undefined ? `parent=${String(contact.parent)}` : "",
|
|
244
|
-
contact.caps !== undefined
|
|
245
|
-
? `caps=${Array.isArray(contact.caps) ? contact.caps.join(",") : String(contact.caps)}`
|
|
246
|
-
: "",
|
|
247
|
-
contact.claim !== undefined
|
|
248
|
-
? `claim=${String(contact.claim).replaceAll(/\s+/g, "_")}`
|
|
249
|
-
: "",
|
|
250
|
-
contact.status !== undefined ? `status=${String(contact.status)}` : "",
|
|
251
|
-
]
|
|
252
|
-
.filter(Boolean)
|
|
253
|
-
.join(" "))
|
|
254
|
-
.join("\n")}`;
|
|
255
|
-
}
|
|
256
|
-
function compactRoomRoster(roster) {
|
|
257
|
-
const members = Object.values(roster);
|
|
258
|
-
if (members.length === 0)
|
|
259
|
-
return "\n(no room members)";
|
|
260
|
-
return `\n${members
|
|
261
|
-
.map((member) => [
|
|
262
|
-
`address=${member.address}`,
|
|
263
|
-
`role=${String(member.role ?? "")}`,
|
|
264
|
-
member.parent !== undefined ? `parent=${String(member.parent)}` : "",
|
|
265
|
-
member.caps !== undefined
|
|
266
|
-
? `caps=${Array.isArray(member.caps) ? member.caps.join(",") : String(member.caps)}`
|
|
267
|
-
: "",
|
|
268
|
-
member.claim !== undefined
|
|
269
|
-
? `claim=${String(member.claim).replaceAll(/\s+/g, "_")}`
|
|
270
|
-
: "",
|
|
271
|
-
`status=${String(member.status ?? "")}`,
|
|
272
|
-
`last_seen=${member.last_seen}`,
|
|
273
|
-
].join(" "))
|
|
274
|
-
.join("\n")}`;
|
|
275
|
-
}
|
|
276
|
-
function compactRoomStatus(status) {
|
|
277
|
-
return `\nroom=${status.room} messages=${status.message_count} roster=${status.roster_count}${status.diagnostics_count ? ` diagnostics=${status.diagnostics_count}` : ""}${status.last_message_at ? ` last_message_at=${status.last_message_at}` : ""}${status.last_message_from ? ` last_from=${status.last_message_from}` : ""}${status.last_message_type ? ` last_type=${status.last_message_type}` : ""}${status.last_message_summary ? ` last_summary=${compactPreview(status.last_message_summary)}` : ""}`;
|
|
278
|
-
}
|
|
279
|
-
function compactCommunicationSnapshot(snapshot) {
|
|
280
|
-
if (!snapshot)
|
|
281
|
-
return "\n(no communication snapshot)";
|
|
282
|
-
return `\nself=${snapshot.self} root=${snapshot.root} rooms=${snapshot.rooms.length} updated_at=${snapshot.updated_at}`;
|
|
283
|
-
}
|
|
284
|
-
function compactInboxMessages(messages, emptyLabel) {
|
|
285
|
-
if (messages.length === 0)
|
|
286
|
-
return `\n(no ${emptyLabel} messages)`;
|
|
287
|
-
return `\n${messages
|
|
288
|
-
.map((message) => [
|
|
289
|
-
...(message.id ? [`id=${String(message.id)}`] : []),
|
|
290
|
-
`status=${String(message.status ?? "")}`,
|
|
291
|
-
`type=${String(message.type ?? "")}`,
|
|
292
|
-
`from=${String(message.from ?? "")}`,
|
|
293
|
-
`to=${String(message.to ?? "")}`,
|
|
294
|
-
...(message.queued_at
|
|
295
|
-
? [`queued_at=${String(message.queued_at)}`]
|
|
296
|
-
: []),
|
|
297
|
-
...(message.sent_at ? [`sent_at=${String(message.sent_at)}`] : []),
|
|
298
|
-
...(message.claimed_at
|
|
299
|
-
? [`claimed_at=${String(message.claimed_at)}`]
|
|
300
|
-
: []),
|
|
301
|
-
...(message.handled_at
|
|
302
|
-
? [`handled_at=${String(message.handled_at)}`]
|
|
303
|
-
: []),
|
|
304
|
-
...(message.failed_at
|
|
305
|
-
? [`failed_at=${String(message.failed_at)}`]
|
|
306
|
-
: []),
|
|
307
|
-
].join(" "))
|
|
308
|
-
.join("\n")}`;
|
|
309
|
-
}
|
|
310
|
-
function compactBranchInbox(messages) {
|
|
311
|
-
return compactInboxMessages(messages, "branch inbox");
|
|
312
|
-
}
|
|
313
|
-
function normalizeMailboxEntry(value) {
|
|
314
|
-
if (typeof value === "string")
|
|
315
|
-
return { type: value };
|
|
316
|
-
const record = asRecord(value);
|
|
317
|
-
if (typeof record.type !== "string" || !record.type.trim())
|
|
318
|
-
return undefined;
|
|
319
|
-
return {
|
|
320
|
-
type: record.type.trim(),
|
|
321
|
-
...(record.body_schema !== undefined
|
|
322
|
-
? { body_schema: record.body_schema }
|
|
323
|
-
: {}),
|
|
324
|
-
...(record.ack !== undefined ? { ack: record.ack } : {}),
|
|
325
|
-
...(typeof record.idempotency === "string"
|
|
326
|
-
? { idempotency: record.idempotency }
|
|
327
|
-
: {}),
|
|
328
|
-
...(typeof record.level === "string" ? { level: record.level } : {}),
|
|
329
|
-
...(record.requires_response === true ? { requires_response: true } : {}),
|
|
330
|
-
...(typeof record.summary === "string" ? { summary: record.summary } : {}),
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
function normalizeMailboxContracts(mailbox) {
|
|
334
|
-
const accepts = Array.isArray(mailbox.accepts)
|
|
335
|
-
? mailbox.accepts
|
|
336
|
-
.map(normalizeMailboxEntry)
|
|
337
|
-
.filter((entry) => Boolean(entry))
|
|
338
|
-
: [];
|
|
339
|
-
const emits = Array.isArray(mailbox.emits)
|
|
340
|
-
? mailbox.emits
|
|
341
|
-
.map(normalizeMailboxEntry)
|
|
342
|
-
.filter((entry) => Boolean(entry))
|
|
343
|
-
: [];
|
|
344
|
-
return { accepts, emits };
|
|
345
|
-
}
|
|
346
|
-
function mailboxTypes(entries) {
|
|
347
|
-
return entries.flatMap((entry) => typeof asRecord(entry).type === "string"
|
|
348
|
-
? [String(asRecord(entry).type)]
|
|
349
|
-
: []);
|
|
350
|
-
}
|
|
351
|
-
function compactRunMailbox(run, mailbox, messages) {
|
|
352
|
-
const normalized = normalizeMailboxContracts(mailbox);
|
|
353
|
-
return `\nrun=${run} accepts=${mailboxTypes(normalized.accepts).join(",")} emits=${mailboxTypes(normalized.emits).join(",")}${compactInboxMessages(messages, "run inbox")}`;
|
|
354
|
-
}
|
|
355
|
-
function compactArtifactPath(value) {
|
|
356
|
-
if (typeof value === "string")
|
|
357
|
-
return value;
|
|
358
|
-
const record = asRecord(value);
|
|
359
|
-
return String(record.path ?? "<missing>");
|
|
360
|
-
}
|
|
361
|
-
function artifactNextActions(run, artifacts) {
|
|
362
|
-
const id = String(run ?? "").trim();
|
|
363
|
-
if (!id || Object.keys(artifacts).length === 0)
|
|
364
|
-
return [];
|
|
365
|
-
return [
|
|
366
|
-
`inspect target=run:${id} view=artifacts verbose=true`,
|
|
367
|
-
`inspect target=run:${id} view=messages`,
|
|
368
|
-
];
|
|
369
|
-
}
|
|
370
|
-
function compactActorFiles(status) {
|
|
371
|
-
const run = String(status.run ?? "<unknown>");
|
|
372
|
-
const artifacts = asRecord(status.artifacts);
|
|
373
|
-
const files = [
|
|
374
|
-
status.stdoutLog,
|
|
375
|
-
status.stderrLog,
|
|
376
|
-
status.eventsFile,
|
|
377
|
-
status.outboxFile,
|
|
378
|
-
status.state_dir
|
|
379
|
-
? `${String(status.state_dir)}/communication.json`
|
|
380
|
-
: undefined,
|
|
381
|
-
status.state_dir ? `${String(status.state_dir)}/result.json` : undefined,
|
|
382
|
-
].filter((file) => typeof file === "string");
|
|
383
|
-
const artifactText = Object.keys(artifacts).length
|
|
384
|
-
? ` artifacts=${Object.entries(artifacts)
|
|
385
|
-
.map(([key, value]) => `${key}:${compactArtifactPath(value)}`)
|
|
386
|
-
.join(",")}`
|
|
387
|
-
: "";
|
|
388
|
-
const nextActions = artifactNextActions(run, artifacts);
|
|
389
|
-
const nextText = nextActions.length
|
|
390
|
-
? ` next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
|
|
391
|
-
: "";
|
|
392
|
-
return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}${nextText}`;
|
|
393
|
-
}
|
|
394
|
-
function summarizeOtherSessions(currentSession, allRuns) {
|
|
395
|
-
const otherRuns = allRuns.filter((run) => run.ownerId && run.ownerId !== currentSession);
|
|
396
|
-
return {
|
|
397
|
-
other_runs: otherRuns.length,
|
|
398
|
-
other_sessions: new Set(otherRuns.map((run) => run.ownerId)).size,
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
function compactSessionRuns(session, runs, summary = {}) {
|
|
402
|
-
const suffix = [
|
|
403
|
-
summary.other_sessions !== undefined
|
|
404
|
-
? `other_sessions=${String(summary.other_sessions)}`
|
|
405
|
-
: "",
|
|
406
|
-
summary.other_runs !== undefined
|
|
407
|
-
? `other_runs=${String(summary.other_runs)}`
|
|
408
|
-
: "",
|
|
409
|
-
]
|
|
410
|
-
.filter(Boolean)
|
|
411
|
-
.join(" ");
|
|
412
|
-
if (runs.length === 0)
|
|
413
|
-
return `\nsession=${session} runs=0${suffix ? ` ${suffix}` : ""}`;
|
|
414
|
-
return `\nsession=${session} runs=${runs.length}${suffix ? ` ${suffix}` : ""}\n${runs
|
|
415
|
-
.map((run) => {
|
|
416
|
-
const tokens = [
|
|
417
|
-
`run=${String(run.run ?? "")}`,
|
|
418
|
-
`status=${String(run.status ?? "")}`,
|
|
419
|
-
];
|
|
420
|
-
if (run.recipe)
|
|
421
|
-
tokens.push(`recipe=${String(run.recipe)}`);
|
|
422
|
-
if (run.retire_when)
|
|
423
|
-
tokens.push(`retire_when=${String(run.retire_when)}`);
|
|
424
|
-
return tokens.join(" ");
|
|
425
|
-
})
|
|
426
|
-
.join("\n")}`;
|
|
427
|
-
}
|
|
428
|
-
function getPiActorsRuntimeStatus() {
|
|
429
|
-
const packagedRecipeRoot = Paths.getPackagedRecipeRoot();
|
|
430
|
-
const packageRoot = dirname(packagedRecipeRoot);
|
|
431
|
-
const packageJsonPath = join(packageRoot, "package.json");
|
|
432
|
-
const packageJson = existsSync(packageJsonPath)
|
|
433
|
-
? JSON.parse(readFileSync(packageJsonPath, "utf8"))
|
|
434
|
-
: {};
|
|
435
|
-
let git_commit;
|
|
436
|
-
try {
|
|
437
|
-
git_commit = execFileSync("git", ["-C", packageRoot, "rev-parse", "--short", "HEAD"], {
|
|
438
|
-
encoding: "utf8",
|
|
439
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
440
|
-
}).trim();
|
|
441
|
-
}
|
|
442
|
-
catch {
|
|
443
|
-
git_commit = undefined;
|
|
444
|
-
}
|
|
445
|
-
const entrypoint = new URL(import.meta.url).pathname;
|
|
446
|
-
return {
|
|
447
|
-
entrypoint,
|
|
448
|
-
git_commit,
|
|
449
|
-
mode: entrypoint.includes("/dist/") ? "dist" : "source",
|
|
450
|
-
package_name: packageJson.name ?? "@llblab/pi-actors",
|
|
451
|
-
package_root: packageRoot,
|
|
452
|
-
recipe_root: Paths.getRecipeRoot(),
|
|
453
|
-
packaged_recipe_root: packagedRecipeRoot,
|
|
454
|
-
version: packageJson.version ?? "unknown",
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
function compactPiActorsRuntimeStatus(status) {
|
|
458
|
-
return `\npi-actors version=${String(status.version)} mode=${String(status.mode)} path=${String(status.package_root)} entrypoint=${String(status.entrypoint)}${status.git_commit ? ` git=${String(status.git_commit)}` : ""}`;
|
|
459
|
-
}
|
|
460
|
-
function compactToolActor(name, tool) {
|
|
461
|
-
const parameters = asRecord(tool.parameters);
|
|
462
|
-
const required = Array.isArray(parameters.required)
|
|
463
|
-
? parameters.required.join(",")
|
|
464
|
-
: "";
|
|
465
|
-
const properties = asRecord(parameters.properties);
|
|
466
|
-
return `\ntool=${name} description=${String(tool.description ?? "").replaceAll(/\s+/g, "_")} args=${Object.keys(properties).join(",")} required=${required}`;
|
|
467
|
-
}
|
|
468
|
-
function compactRecipeImports(summary) {
|
|
469
|
-
const active = Array.isArray(summary.active)
|
|
470
|
-
? summary.active
|
|
471
|
-
: [];
|
|
472
|
-
const lines = active.flatMap((entry) => {
|
|
473
|
-
const imports = asRecord(entry.imports);
|
|
474
|
-
return Object.entries(imports).map(([alias, value]) => {
|
|
475
|
-
const binding = typeof value === "string" ? { from: value } : asRecord(value);
|
|
476
|
-
return `recipe=${String(entry.id ?? "<unknown>")} alias=${alias} from=${String(binding.from ?? value)}`;
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
return lines.length ? `\n${lines.join("\n")}` : "\n(no recipe imports)";
|
|
480
|
-
}
|
|
481
|
-
function compactRecipeDoctor(summary) {
|
|
482
|
-
const details = Array.isArray(summary.diagnostic_details)
|
|
483
|
-
? summary.diagnostic_details
|
|
484
|
-
: [];
|
|
485
|
-
const remediations = Array.isArray(summary.remediations)
|
|
486
|
-
? summary.remediations
|
|
487
|
-
: [];
|
|
488
|
-
const recommendations = Array.isArray(summary.recommendations)
|
|
489
|
-
? summary.recommendations
|
|
490
|
-
: [];
|
|
491
|
-
const counts = { error: 0, info: 0, warning: 0 };
|
|
492
|
-
for (const detail of details) {
|
|
493
|
-
const severity = String(detail.severity ?? "info");
|
|
494
|
-
if (severity === "error" || severity === "warning" || severity === "info")
|
|
495
|
-
counts[severity] += 1;
|
|
496
|
-
}
|
|
497
|
-
const topAction = asRecord(summary.top_action);
|
|
498
|
-
const lines = [
|
|
499
|
-
`recipes doctor errors=${counts.error} warnings=${counts.warning} info=${counts.info} actions=${remediations.length} recommendations=${recommendations.length}`,
|
|
500
|
-
];
|
|
501
|
-
if (Object.keys(topAction).length > 0) {
|
|
502
|
-
const action = compactPreview(topAction.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
|
|
503
|
-
lines.push(`top severity=${String(topAction.severity ?? "info")} kind=${String(topAction.kind ?? "inspect")} id=${String(topAction.id ?? "root")} action=${action ?? "inspect"}`);
|
|
504
|
-
}
|
|
505
|
-
for (const item of remediations.slice(0, 8)) {
|
|
506
|
-
const action = compactPreview(item.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
|
|
507
|
-
const blocked = item.blocked_fallback
|
|
508
|
-
? ` blocked=${compactPreview(item.blocked_fallback, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
|
|
509
|
-
: "";
|
|
510
|
-
lines.push(`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`);
|
|
511
|
-
}
|
|
512
|
-
const nextActions = Array.isArray(summary.next_actions)
|
|
513
|
-
? summary.next_actions
|
|
514
|
-
: [];
|
|
515
|
-
if (nextActions.length > 0)
|
|
516
|
-
lines[0] = `${lines[0]}${compactNextActions(nextActions)}`;
|
|
517
|
-
return `\n${lines.join("\n")}`;
|
|
518
|
-
}
|
|
519
|
-
function recipeRegistryNextActions(summary, view) {
|
|
520
|
-
const actions = [];
|
|
521
|
-
const drafts = Array.isArray(summary.drafts)
|
|
522
|
-
? summary.drafts
|
|
523
|
-
: [];
|
|
524
|
-
const invalid = Array.isArray(summary.invalid) ? summary.invalid.length : 0;
|
|
525
|
-
const diagnostics = Array.isArray(summary.diagnostics)
|
|
526
|
-
? summary.diagnostics.length
|
|
527
|
-
: 0;
|
|
528
|
-
const topAction = asRecord(summary.top_action);
|
|
529
|
-
if (view !== "doctor" && (invalid > 0 || diagnostics > 0)) {
|
|
530
|
-
actions.push("inspect target=recipes view=doctor");
|
|
531
|
-
}
|
|
532
|
-
if (view === "doctor" && typeof topAction.action === "string") {
|
|
533
|
-
actions.push(String(topAction.action));
|
|
534
|
-
}
|
|
535
|
-
if (drafts.length > 0) {
|
|
536
|
-
actions.push("inspect target=recipes view=summary verbose=true");
|
|
537
|
-
const firstPath = typeof drafts[0]?.path === "string" ? drafts[0].path : undefined;
|
|
538
|
-
if (firstPath)
|
|
539
|
-
actions.push(`spawn file=${firstPath}`);
|
|
540
|
-
}
|
|
541
|
-
return [...new Set(actions)].slice(0, 4);
|
|
542
|
-
}
|
|
543
|
-
function compactNextActions(actions) {
|
|
544
|
-
return actions.length
|
|
545
|
-
? ` next=${actions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
|
|
546
|
-
: "";
|
|
547
|
-
}
|
|
548
|
-
function compactRecipeRegistry(summary) {
|
|
549
|
-
const active = Array.isArray(summary.active) ? summary.active.length : 0;
|
|
550
|
-
const shadowed = Array.isArray(summary.shadowed)
|
|
551
|
-
? summary.shadowed.length
|
|
552
|
-
: 0;
|
|
553
|
-
const invalid = Array.isArray(summary.invalid) ? summary.invalid.length : 0;
|
|
554
|
-
const disabled = Array.isArray(summary.disabled)
|
|
555
|
-
? summary.disabled.length
|
|
556
|
-
: 0;
|
|
557
|
-
const diagnostics = Array.isArray(summary.diagnostics)
|
|
558
|
-
? summary.diagnostics.length
|
|
559
|
-
: 0;
|
|
560
|
-
const drafts = Array.isArray(summary.drafts) ? summary.drafts.length : 0;
|
|
561
|
-
const recommendations = Array.isArray(summary.recommendations)
|
|
562
|
-
? summary.recommendations.length
|
|
563
|
-
: 0;
|
|
564
|
-
const nextActions = Array.isArray(summary.next_actions)
|
|
565
|
-
? summary.next_actions
|
|
566
|
-
: [];
|
|
567
|
-
return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
|
|
568
|
-
}
|
|
569
|
-
function actorMessageNextActions(message, result) {
|
|
570
|
-
const actions = [];
|
|
571
|
-
const address = ActorMessages.parseActorAddress(message.to);
|
|
572
|
-
if (result.delivery_error || result.sent === false) {
|
|
573
|
-
if (address.kind === "run" && address.value) {
|
|
574
|
-
actions.push(`inspect target=run:${address.value} view=status`);
|
|
575
|
-
actions.push(`inspect target=run:${address.value} view=mailbox`);
|
|
576
|
-
}
|
|
577
|
-
else if (address.kind === "branch" && address.value) {
|
|
578
|
-
actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
|
|
579
|
-
actions.push(`inspect target=run:${address.value} view=status`);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
if (result.queued === true) {
|
|
583
|
-
if (address.kind === "branch" && address.value) {
|
|
584
|
-
actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
|
|
585
|
-
}
|
|
586
|
-
else if (address.kind === "run" && address.value) {
|
|
587
|
-
actions.push(`inspect target=run:${address.value} view=mailbox`);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
return [...new Set(actions)].slice(0, 3);
|
|
591
|
-
}
|
|
592
|
-
function compactActorMessageResult(message, result) {
|
|
593
|
-
const tokens = [
|
|
594
|
-
`to=${message.to}`,
|
|
595
|
-
`type=${message.type}`,
|
|
596
|
-
`message=${result.sent === true || result.stopped === true ? "sent" : "not_sent"}`,
|
|
597
|
-
];
|
|
598
|
-
if (result.bytes !== undefined)
|
|
599
|
-
tokens.push(`bytes=${String(result.bytes)}`);
|
|
600
|
-
if (result.queued === true)
|
|
601
|
-
tokens.push("queued=true");
|
|
602
|
-
if (result.control)
|
|
603
|
-
tokens.push(`control=${String(result.control)}`);
|
|
604
|
-
if (result.outbox)
|
|
605
|
-
tokens.push(`messages=${String(result.outbox)}`);
|
|
606
|
-
if (result.message_count !== undefined)
|
|
607
|
-
tokens.push(`messages=${String(result.message_count)}`);
|
|
608
|
-
if (result.roster_count !== undefined)
|
|
609
|
-
tokens.push(`roster=${String(result.roster_count)}`);
|
|
610
|
-
if (result.room)
|
|
611
|
-
tokens.push(`room=${String(result.room)}`);
|
|
612
|
-
if (result.tool)
|
|
613
|
-
tokens.push(`tool=${String(result.tool)}`);
|
|
614
|
-
if (result.stopped === true)
|
|
615
|
-
tokens.push("stopped=true");
|
|
616
|
-
if (result.signal)
|
|
617
|
-
tokens.push(`signal=${String(result.signal)}`);
|
|
618
|
-
if (result.invoked === true)
|
|
619
|
-
tokens.push("invoked=true");
|
|
620
|
-
if (result.delivery_error) {
|
|
621
|
-
tokens.push(`delivery_error=${compactPreview(result.delivery_error, 96)}`);
|
|
622
|
-
}
|
|
623
|
-
const nextActions = Array.isArray(result.next_actions)
|
|
624
|
-
? result.next_actions
|
|
625
|
-
: actorMessageNextActions(message, result);
|
|
626
|
-
if (nextActions.length > 0)
|
|
627
|
-
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
628
|
-
return `\n${tokens.join(" ")}`;
|
|
629
|
-
}
|
|
630
|
-
function maybeJsonText(value, verbose, compact) {
|
|
631
|
-
return verbose ? jsonText(value) : compact;
|
|
632
|
-
}
|
|
633
|
-
export function createRegisterToolDefinition(deps) {
|
|
634
|
-
return {
|
|
635
|
-
name: "register_tool",
|
|
636
|
-
label: "Register Tool",
|
|
637
|
-
description: Prompts.REGISTER_TOOL_DESCRIPTION,
|
|
638
|
-
promptSnippet: Prompts.REGISTER_TOOL_PROMPT_SNIPPET,
|
|
639
|
-
promptGuidelines: Prompts.REGISTER_TOOL_GUIDELINES,
|
|
640
|
-
parameters: objectSchema({
|
|
641
|
-
args: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.args),
|
|
642
|
-
async: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.async),
|
|
643
|
-
description: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description),
|
|
644
|
-
name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
|
|
645
|
-
state_dir: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.state_dir),
|
|
646
|
-
template: unionSchema([
|
|
647
|
-
stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
|
|
648
|
-
looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
|
|
649
|
-
arraySchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateArray),
|
|
650
|
-
nullSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateNull),
|
|
651
|
-
]),
|
|
652
|
-
update: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.update),
|
|
653
|
-
values: looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.values),
|
|
654
|
-
}, []),
|
|
655
|
-
execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => Registry.executeRegisterTool(params, ctx, deps),
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
function getRunOwnerId(ctx) {
|
|
659
|
-
return ctx.sessionManager?.getSessionId?.();
|
|
660
|
-
}
|
|
661
|
-
function messageBodyToRunLine(message) {
|
|
662
|
-
if (message.type !== "run.message")
|
|
663
|
-
return JSON.stringify(message);
|
|
664
|
-
if (typeof message.body === "string")
|
|
665
|
-
return message.body;
|
|
666
|
-
if (message.body === undefined)
|
|
667
|
-
return message.type;
|
|
668
|
-
return JSON.stringify(message.body);
|
|
669
|
-
}
|
|
670
|
-
function messageBodyToToolParams(message) {
|
|
671
|
-
if (message.body &&
|
|
672
|
-
typeof message.body === "object" &&
|
|
673
|
-
!Array.isArray(message.body)) {
|
|
674
|
-
return message.body;
|
|
675
|
-
}
|
|
676
|
-
if (message.body === undefined)
|
|
677
|
-
return {};
|
|
678
|
-
return { input: message.body };
|
|
679
|
-
}
|
|
680
|
-
function formatToolActorFailure(tool, message, params, error) {
|
|
681
|
-
const original = error instanceof Error ? error.message : String(error);
|
|
682
|
-
const paramsPreview = compactPreview(params, 240) ?? "{}";
|
|
683
|
-
return Object.assign(new Error(`tool actor ${tool} failed for message type ${message.type}: ${original}; params=${paramsPreview}`), {
|
|
684
|
-
message_type: message.type,
|
|
685
|
-
original_error: original,
|
|
686
|
-
params_preview: paramsPreview,
|
|
687
|
-
tool,
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
function shadowedRecipeLaunchDiagnostic(recipe) {
|
|
691
|
-
if (typeof recipe !== "string" ||
|
|
692
|
-
recipe.includes("/") ||
|
|
693
|
-
recipe.includes("~"))
|
|
694
|
-
return undefined;
|
|
695
|
-
const discovery = RecipeDiscovery.discoverRecipeSources([
|
|
696
|
-
{ root: Paths.getRecipeRoot(), defaultTool: true, mutableUsage: true },
|
|
697
|
-
{ root: Paths.getPackagedRecipeRoot(), defaultTool: false },
|
|
698
|
-
]);
|
|
699
|
-
return RecipeDiscovery.getShadowedLaunchDiagnostic(discovery, recipe);
|
|
700
|
-
}
|
|
701
|
-
function draftRecipeName(run) {
|
|
702
|
-
return `${run.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "spawn"}.json`;
|
|
703
|
-
}
|
|
704
|
-
function draftRecipeDefaults(values) {
|
|
705
|
-
const ignored = new Set([
|
|
706
|
-
"actor_address",
|
|
707
|
-
"communication_file",
|
|
708
|
-
"default_room",
|
|
709
|
-
"run_id",
|
|
710
|
-
"state_dir",
|
|
711
|
-
]);
|
|
712
|
-
const defaults = Object.fromEntries(Object.entries(values).filter(([key]) => !ignored.has(key)));
|
|
713
|
-
return Object.keys(defaults).length > 0 ? defaults : undefined;
|
|
714
|
-
}
|
|
715
|
-
function writeSpawnDraftRecipe(input, meta) {
|
|
716
|
-
if (process.env.NODE_TEST_CONTEXT &&
|
|
717
|
-
process.env.PI_ACTORS_ENABLE_SPAWN_DRAFTS_IN_TEST !== "1")
|
|
718
|
-
return undefined;
|
|
719
|
-
if (input.template === undefined ||
|
|
720
|
-
input.file !== undefined ||
|
|
721
|
-
input.recipe !== undefined)
|
|
722
|
-
return undefined;
|
|
723
|
-
const root = Paths.getRecipeDraftRoot();
|
|
724
|
-
mkdirSync(root, { recursive: true });
|
|
725
|
-
const path = join(root, draftRecipeName(String(meta.run)));
|
|
726
|
-
const defaults = draftRecipeDefaults(meta.values);
|
|
727
|
-
const recipe = {
|
|
728
|
-
async: true,
|
|
729
|
-
description: `Draft recipe captured from spawn run ${String(meta.run)}`,
|
|
730
|
-
...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
|
|
731
|
-
...(defaults ? { defaults } : {}),
|
|
732
|
-
template: input.template,
|
|
733
|
-
};
|
|
734
|
-
writeFileSync(path, `${JSON.stringify(recipe, null, 2)}\n`, { flag: "wx" });
|
|
735
|
-
return path;
|
|
736
|
-
}
|
|
737
|
-
function enhanceSpawnRecipeError(error, recipe) {
|
|
738
|
-
const diagnostic = shadowedRecipeLaunchDiagnostic(recipe);
|
|
739
|
-
if (!diagnostic)
|
|
740
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
741
|
-
const original = error instanceof Error ? error.message : String(error);
|
|
742
|
-
return Object.assign(new Error(`${original} reason=${diagnostic.reason} active_path=${diagnostic.active_path} blocked_fallback=${diagnostic.blocked_fallback} hint=${diagnostic.hint}`), {
|
|
743
|
-
...diagnostic,
|
|
744
|
-
original_error: original,
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
function runIdFromActorAddress(address) {
|
|
748
|
-
if (!address)
|
|
749
|
-
return undefined;
|
|
750
|
-
const parsed = ActorMessages.parseActorAddress(address);
|
|
751
|
-
if (parsed.kind !== "run" || !parsed.value) {
|
|
752
|
-
throw new Error(`Expected run:<id> actor address, received: ${address}`);
|
|
753
|
-
}
|
|
754
|
-
return parsed.value;
|
|
755
|
-
}
|
|
756
|
-
function assertMessageSenderBelongsToRun(message, run, routeLabel) {
|
|
757
|
-
if (!message.from) {
|
|
758
|
-
throw new Error(`message to ${message.to} requires from=<actor address>.`);
|
|
759
|
-
}
|
|
760
|
-
const sender = ActorMessages.parseActorAddress(message.from);
|
|
761
|
-
if ((sender.kind !== "run" && sender.kind !== "branch") ||
|
|
762
|
-
sender.value !== run) {
|
|
763
|
-
throw new Error(`message to ${routeLabel} requires from=run:${run} or branch:${run}/<branch>; got ${message.from}.`);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
async function routeBranchEnvelope(stateDir, runId, recipient, message, _options) {
|
|
767
|
-
const branchMessage = { ...message, to: recipient };
|
|
768
|
-
ActorRooms.appendBranchInboxMessage(stateDir, runId, recipient, branchMessage);
|
|
769
|
-
try {
|
|
770
|
-
return await AsyncRuns.sendRunMessage(runId, JSON.stringify(branchMessage));
|
|
771
|
-
}
|
|
772
|
-
catch (error) {
|
|
773
|
-
const record = error && typeof error === "object"
|
|
774
|
-
? error
|
|
775
|
-
: {};
|
|
776
|
-
if (record.queued === true) {
|
|
777
|
-
return {
|
|
778
|
-
control_path: record.control_path,
|
|
779
|
-
control_type: record.control_type,
|
|
780
|
-
delivery_error: record.delivery_error ??
|
|
781
|
-
(error instanceof Error ? error.message : String(error)),
|
|
782
|
-
inbox_id: record.inbox_id,
|
|
783
|
-
queued: true,
|
|
784
|
-
run: runId,
|
|
785
|
-
sent: false,
|
|
786
|
-
state_dir: stateDir,
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
throw error;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
function getRoomMulticastRecipients(message, run) {
|
|
793
|
-
const raw = message.metadata?.recipients;
|
|
794
|
-
if (raw === undefined)
|
|
795
|
-
return [];
|
|
796
|
-
if (!Array.isArray(raw)) {
|
|
797
|
-
throw new Error("room multicast metadata.recipients must be an array.");
|
|
798
|
-
}
|
|
799
|
-
return raw.map((recipient) => {
|
|
800
|
-
if (typeof recipient !== "string") {
|
|
801
|
-
throw new Error("room multicast recipients must be actor addresses.");
|
|
802
|
-
}
|
|
803
|
-
const parsed = ActorMessages.parseActorAddress(recipient);
|
|
804
|
-
if (parsed.kind !== "branch" || parsed.value !== run) {
|
|
805
|
-
throw new Error(`room multicast recipient must be branch:${run}/<branch>; got ${recipient}.`);
|
|
806
|
-
}
|
|
807
|
-
return ActorMessages.formatActorAddress(parsed);
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
export function createSpawnToolDefinition() {
|
|
811
|
-
return {
|
|
812
|
-
name: "spawn",
|
|
813
|
-
label: "Spawn",
|
|
814
|
-
description: "Create an addressable actor from a recipe file or inline command template. Use instead of ad hoc shell backgrounding for work that may outlive this turn, needs steering/follow-up/artifacts, runs as a service, fans out, or should be inspected later. Currently spawns run:<id> actors backed by async runs.",
|
|
815
|
-
parameters: objectSchema({
|
|
816
|
-
artifacts: looseObjectSchema("Optional named artifact paths for the spawned actor."),
|
|
817
|
-
as: stringSchema("Optional actor address for the spawned run, e.g. run:<id>."),
|
|
818
|
-
file: stringSchema("Optional template recipe JSON file. Bare names resolve under ~/.pi/agent/recipes."),
|
|
819
|
-
recipe: stringSchema("Alias for file; template recipe JSON file/name to spawn."),
|
|
820
|
-
state_dir: stringSchema("Optional explicit run state directory."),
|
|
821
|
-
template: unionSchema([
|
|
822
|
-
stringSchema("Inline command template string"),
|
|
823
|
-
arraySchema("Inline command-template sequence or parallel tree"),
|
|
824
|
-
looseObjectSchema("Inline command-template object with flags such as parallel, repeat, retry, failure, and nested template."),
|
|
825
|
-
]),
|
|
826
|
-
values: looseObjectSchema("Runtime placeholder values passed to the actor."),
|
|
827
|
-
verbose: booleanSchema("Return full JSON instead of compact text."),
|
|
828
|
-
}, []),
|
|
829
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
830
|
-
const input = asRecord(params);
|
|
831
|
-
const runId = runIdFromActorAddress(typeof input.as === "string" ? input.as : undefined);
|
|
832
|
-
const recipe = typeof input.file === "string"
|
|
833
|
-
? input.file
|
|
834
|
-
: typeof input.recipe === "string"
|
|
835
|
-
? input.recipe
|
|
836
|
-
: undefined;
|
|
837
|
-
let meta;
|
|
838
|
-
try {
|
|
839
|
-
meta = AsyncRuns.startRun({
|
|
840
|
-
file: recipe,
|
|
841
|
-
launch_source: "spawn",
|
|
842
|
-
ownerId: getRunOwnerId(ctx),
|
|
843
|
-
run_id: runId,
|
|
844
|
-
state_dir: typeof input.state_dir === "string" ? input.state_dir : undefined,
|
|
845
|
-
...(input.template !== undefined
|
|
846
|
-
? {
|
|
847
|
-
template: input.template,
|
|
848
|
-
}
|
|
849
|
-
: {}),
|
|
850
|
-
values: asRecord(input.values),
|
|
851
|
-
...(input.artifacts &&
|
|
852
|
-
typeof input.artifacts === "object" &&
|
|
853
|
-
!Array.isArray(input.artifacts)
|
|
854
|
-
? {
|
|
855
|
-
artifacts: input.artifacts,
|
|
856
|
-
}
|
|
857
|
-
: {}),
|
|
858
|
-
}, ctx.cwd);
|
|
859
|
-
}
|
|
860
|
-
catch (error) {
|
|
861
|
-
throw enhanceSpawnRecipeError(error, recipe);
|
|
862
|
-
}
|
|
863
|
-
const draftRecipe = writeSpawnDraftRecipe(input, meta);
|
|
864
|
-
const nextActions = actorRunNextActions(meta.run);
|
|
865
|
-
const details = {
|
|
866
|
-
...meta,
|
|
867
|
-
...(draftRecipe ? { draft_recipe: draftRecipe } : {}),
|
|
868
|
-
next_actions: nextActions,
|
|
869
|
-
};
|
|
870
|
-
ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
|
|
871
|
-
ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
|
|
872
|
-
return {
|
|
873
|
-
content: [
|
|
874
|
-
{
|
|
875
|
-
type: "text",
|
|
876
|
-
text: maybeJsonText(details, input.verbose === true, compactAsyncRunStatus(details)),
|
|
877
|
-
},
|
|
878
|
-
],
|
|
879
|
-
details,
|
|
880
|
-
};
|
|
881
|
-
},
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
function getContextSessionId(ctx) {
|
|
885
|
-
return ctx?.sessionManager?.getSessionId?.();
|
|
886
|
-
}
|
|
887
|
-
function requireContextSessionId(ctx, actor) {
|
|
888
|
-
const sessionId = getContextSessionId(ctx);
|
|
889
|
-
if (!sessionId) {
|
|
890
|
-
throw new Error(`${actor} requires a current coordinator session; use session:<id> or session:all for explicit session inventory.`);
|
|
891
|
-
}
|
|
892
|
-
return sessionId;
|
|
893
|
-
}
|
|
894
|
-
function sessionMismatchError(input) {
|
|
895
|
-
const ownerSession = input.expectedSession ?? "none";
|
|
896
|
-
const currentSession = input.currentSession ?? "none";
|
|
897
|
-
const actor = input.run ? `run:${input.run}` : (input.target ?? "session");
|
|
898
|
-
const hintTarget = input.expectedSession
|
|
899
|
-
? `session:${input.expectedSession}`
|
|
900
|
-
: "session:all";
|
|
901
|
-
return Object.assign(new Error(`${actor} reason=session_mismatch owner_session=${ownerSession} current_session=${currentSession} hint=inspect_session:${input.expectedSession ?? "all"}`), {
|
|
902
|
-
current_session: input.currentSession,
|
|
903
|
-
hint: `inspect target=${hintTarget} view=status`,
|
|
904
|
-
owner_session: input.expectedSession,
|
|
905
|
-
reason: "session_mismatch",
|
|
906
|
-
run: input.run,
|
|
907
|
-
target: input.target,
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
function assertRunAccessibleToContext(runId, ctx) {
|
|
911
|
-
const status = AsyncRuns.getRunStatus(runId);
|
|
912
|
-
const sessionId = getContextSessionId(ctx);
|
|
913
|
-
if (sessionId && status.ownerId && status.ownerId !== sessionId) {
|
|
914
|
-
throw sessionMismatchError({
|
|
915
|
-
currentSession: sessionId,
|
|
916
|
-
expectedSession: String(status.ownerId),
|
|
917
|
-
run: runId,
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
return status;
|
|
921
|
-
}
|
|
922
|
-
function assertRunExistsForActorMessage(runId) {
|
|
923
|
-
return AsyncRuns.getRunStatus(runId);
|
|
924
|
-
}
|
|
925
|
-
export function createInspectToolDefinition(deps = {}) {
|
|
926
|
-
return {
|
|
927
|
-
name: "inspect",
|
|
928
|
-
label: "Inspect",
|
|
929
|
-
description: "Intentionally inspect actors at decision points, after follow-ups, or during diagnosis instead of polling. Core targets are run:<id> and tool:<name>; advanced targets include branch:<run>/<branch>, room:<run>, coordinator, session:<id>, and session:all.",
|
|
930
|
-
parameters: objectSchema({
|
|
931
|
-
lines: stringSchema("Line count for tail/messages views. Default 40."),
|
|
932
|
-
status: stringSchema("Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited."),
|
|
933
|
-
target: stringSchema("Actor address to inspect, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>, session:all."),
|
|
934
|
-
verbose: booleanSchema("Return full JSON instead of compact text where available."),
|
|
935
|
-
view: stringSchema("Inspection view. Core run views: status, tail, messages, artifacts, files, mailbox. Advanced views include communication, roster, and contacts."),
|
|
936
|
-
}, ["target", "view"]),
|
|
937
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
938
|
-
const input = asRecord(params);
|
|
939
|
-
const target = String(input.target ?? "");
|
|
940
|
-
const view = String(input.view ?? "");
|
|
941
|
-
if (target === "recipes" || target === "recipe-registry") {
|
|
942
|
-
if (view !== "status" &&
|
|
943
|
-
view !== "summary" &&
|
|
944
|
-
view !== "doctor" &&
|
|
945
|
-
view !== "imports") {
|
|
946
|
-
throw new Error("inspect recipes supports view=status, view=summary, view=doctor, or view=imports.");
|
|
947
|
-
}
|
|
948
|
-
const discovered = RecipeDiscovery.discoverRecipeSources([
|
|
949
|
-
{
|
|
950
|
-
root: deps.recipeRoot ?? Paths.getRecipeRoot(),
|
|
951
|
-
defaultTool: true,
|
|
952
|
-
mutableUsage: true,
|
|
953
|
-
},
|
|
954
|
-
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
955
|
-
]);
|
|
956
|
-
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
957
|
-
const summaryBase = {
|
|
958
|
-
...RecipeDiscovery.summarizeDiscovery(discovered),
|
|
959
|
-
drafts: RecipeDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
|
|
960
|
-
};
|
|
961
|
-
const summary = {
|
|
962
|
-
...summaryBase,
|
|
963
|
-
next_actions: recipeRegistryNextActions(summaryBase, view),
|
|
964
|
-
};
|
|
965
|
-
return {
|
|
966
|
-
content: [
|
|
967
|
-
{
|
|
968
|
-
type: "text",
|
|
969
|
-
text: maybeJsonText(summary, input.verbose === true, view === "doctor"
|
|
970
|
-
? compactRecipeDoctor(summary)
|
|
971
|
-
: view === "imports"
|
|
972
|
-
? compactRecipeImports(summary)
|
|
973
|
-
: compactRecipeRegistry(summary)),
|
|
974
|
-
},
|
|
975
|
-
],
|
|
976
|
-
details: summary,
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
const address = ActorMessages.parseActorAddress(target);
|
|
980
|
-
if (address.kind === "coordinator") {
|
|
981
|
-
if (view !== "status" && view !== "runs") {
|
|
982
|
-
throw new Error("inspect coordinator supports view=status or view=runs.");
|
|
983
|
-
}
|
|
984
|
-
const session = requireContextSessionId(ctx, "inspect coordinator");
|
|
985
|
-
const allRuns = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
|
|
986
|
-
const runs = allRuns.filter((run) => run.ownerId === session);
|
|
987
|
-
const sessionSummary = summarizeOtherSessions(session, allRuns);
|
|
988
|
-
return {
|
|
989
|
-
content: [
|
|
990
|
-
{
|
|
991
|
-
type: "text",
|
|
992
|
-
text: maybeJsonText({ session, runs, ...sessionSummary }, input.verbose === true, compactSessionRuns(session, runs, sessionSummary)),
|
|
993
|
-
},
|
|
994
|
-
],
|
|
995
|
-
details: { session, runs, ...sessionSummary },
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
if (address.kind === "session") {
|
|
999
|
-
if (view !== "status" && view !== "runs") {
|
|
1000
|
-
throw new Error("inspect session:<id> supports view=status or view=runs.");
|
|
1001
|
-
}
|
|
1002
|
-
const allRuns = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
|
|
1003
|
-
const runs = allRuns.filter((run) => address.value === "all" || run.ownerId === address.value);
|
|
1004
|
-
const sessionSummary = address.value === "all"
|
|
1005
|
-
? {}
|
|
1006
|
-
: summarizeOtherSessions(address.value || "", allRuns);
|
|
1007
|
-
return {
|
|
1008
|
-
content: [
|
|
1009
|
-
{
|
|
1010
|
-
type: "text",
|
|
1011
|
-
text: maybeJsonText({ session: address.value, runs, ...sessionSummary }, input.verbose === true, compactSessionRuns(address.value || "", runs, sessionSummary)),
|
|
1012
|
-
},
|
|
1013
|
-
],
|
|
1014
|
-
details: { session: address.value, runs, ...sessionSummary },
|
|
1015
|
-
};
|
|
1016
|
-
}
|
|
1017
|
-
if (address.kind === "tool" && address.value) {
|
|
1018
|
-
if (address.value === "pi-actors") {
|
|
1019
|
-
if (view !== "status") {
|
|
1020
|
-
throw new Error("inspect tool:pi-actors supports view=status.");
|
|
1021
|
-
}
|
|
1022
|
-
const details = getPiActorsRuntimeStatus();
|
|
1023
|
-
return {
|
|
1024
|
-
content: [
|
|
1025
|
-
{
|
|
1026
|
-
type: "text",
|
|
1027
|
-
text: maybeJsonText(details, input.verbose === true, compactPiActorsRuntimeStatus(details)),
|
|
1028
|
-
},
|
|
1029
|
-
],
|
|
1030
|
-
details,
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
if (view !== "status" && view !== "schema") {
|
|
1034
|
-
throw new Error("inspect tool:<name> supports view=status or view=schema.");
|
|
1035
|
-
}
|
|
1036
|
-
const tool = deps.getTool?.(address.value);
|
|
1037
|
-
if (!tool)
|
|
1038
|
-
throw new Error(`tool actor not found: ${address.value}`);
|
|
1039
|
-
const details = {
|
|
1040
|
-
name: address.value,
|
|
1041
|
-
description: tool.description,
|
|
1042
|
-
parameters: tool.parameters,
|
|
1043
|
-
promptSnippet: tool.promptSnippet,
|
|
1044
|
-
};
|
|
1045
|
-
return {
|
|
1046
|
-
content: [
|
|
1047
|
-
{
|
|
1048
|
-
type: "text",
|
|
1049
|
-
text: maybeJsonText(details, input.verbose === true || view === "schema", compactToolActor(address.value, details)),
|
|
1050
|
-
},
|
|
1051
|
-
],
|
|
1052
|
-
details,
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
if (address.kind === "room" && address.value && address.room) {
|
|
1056
|
-
const status = assertRunAccessibleToContext(address.value, ctx);
|
|
1057
|
-
const stateDir = String(status.state_dir ?? "");
|
|
1058
|
-
if (!stateDir)
|
|
1059
|
-
throw new Error(`room:${address.value} has no run state directory.`);
|
|
1060
|
-
if (view === "status") {
|
|
1061
|
-
const status = ActorRooms.getRoomStatus(stateDir, address.room);
|
|
1062
|
-
return {
|
|
1063
|
-
content: [
|
|
1064
|
-
{
|
|
1065
|
-
type: "text",
|
|
1066
|
-
text: maybeJsonText(status, input.verbose === true, compactRoomStatus(status)),
|
|
1067
|
-
},
|
|
1068
|
-
],
|
|
1069
|
-
details: status,
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
if (view === "previews") {
|
|
1073
|
-
const previews = ActorRooms.readRoomMessagePreviews(stateDir, address.room, Number(input.lines || Limits.DEFAULT_INSPECT_LINES));
|
|
1074
|
-
return {
|
|
1075
|
-
content: [
|
|
1076
|
-
{
|
|
1077
|
-
type: "text",
|
|
1078
|
-
text: maybeJsonText(previews, input.verbose === true, compactRoomPreviews(previews)),
|
|
1079
|
-
},
|
|
1080
|
-
],
|
|
1081
|
-
details: { previews },
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
if (view === "messages") {
|
|
1085
|
-
const messages = ActorRooms.readRoomMessages(stateDir, address.room, Number(input.lines || Limits.DEFAULT_INSPECT_LINES));
|
|
1086
|
-
return {
|
|
1087
|
-
content: [
|
|
1088
|
-
{
|
|
1089
|
-
type: "text",
|
|
1090
|
-
text: maybeJsonText(messages, input.verbose === true, compactRoomMessages(messages)),
|
|
1091
|
-
},
|
|
1092
|
-
],
|
|
1093
|
-
details: { messages },
|
|
1094
|
-
};
|
|
1095
|
-
}
|
|
1096
|
-
if (view === "contacts") {
|
|
1097
|
-
const contacts = ActorRooms.readRoomContacts(stateDir, address.room);
|
|
1098
|
-
return {
|
|
1099
|
-
content: [
|
|
1100
|
-
{
|
|
1101
|
-
type: "text",
|
|
1102
|
-
text: maybeJsonText(contacts, input.verbose === true, compactRoomContacts(contacts)),
|
|
1103
|
-
},
|
|
1104
|
-
],
|
|
1105
|
-
details: { contacts },
|
|
1106
|
-
};
|
|
1107
|
-
}
|
|
1108
|
-
if (view === "roster") {
|
|
1109
|
-
const roster = ActorRooms.readRoomRoster(stateDir, address.room);
|
|
1110
|
-
return {
|
|
1111
|
-
content: [
|
|
1112
|
-
{
|
|
1113
|
-
type: "text",
|
|
1114
|
-
text: maybeJsonText(roster, input.verbose === true, compactRoomRoster(roster)),
|
|
1115
|
-
},
|
|
1116
|
-
],
|
|
1117
|
-
details: { roster },
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
throw new Error("inspect room:<run> supports view=status, view=messages, view=previews, view=roster, or view=contacts.");
|
|
1121
|
-
}
|
|
1122
|
-
const runId = address.kind === "run" || address.kind === "branch"
|
|
1123
|
-
? address.value
|
|
1124
|
-
: undefined;
|
|
1125
|
-
if (!runId)
|
|
1126
|
-
throw new Error("inspect target must be run:<id>, branch:<run>/<branch>, coordinator, session:<id>, or tool:<name>.");
|
|
1127
|
-
if (address.kind === "branch") {
|
|
1128
|
-
if (view !== "mailbox")
|
|
1129
|
-
throw new Error("inspect branch:<run>/<branch> supports view=mailbox.");
|
|
1130
|
-
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1131
|
-
const branchInbox = ActorRooms.readBranchInboxDiagnostics(String(status.state_dir ?? ""), runId, target, Number(input.lines || Limits.DEFAULT_INSPECT_LINES));
|
|
1132
|
-
const messages = branchInbox.messages;
|
|
1133
|
-
return {
|
|
1134
|
-
content: [
|
|
1135
|
-
{
|
|
1136
|
-
type: "text",
|
|
1137
|
-
text: maybeJsonText(input.verbose === true ? branchInbox : messages, input.verbose === true, `${compactBranchInbox(messages.map((message) => ({ ...message })))}${branchInbox.corrupted > 0 ? `\ncorrupted=${branchInbox.corrupted}` : ""}`),
|
|
1138
|
-
},
|
|
1139
|
-
],
|
|
1140
|
-
details: { corrupted: branchInbox.corrupted, messages },
|
|
1141
|
-
};
|
|
1142
|
-
}
|
|
1143
|
-
switch (view) {
|
|
1144
|
-
case "status": {
|
|
1145
|
-
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1146
|
-
return {
|
|
1147
|
-
content: [
|
|
1148
|
-
{
|
|
1149
|
-
type: "text",
|
|
1150
|
-
text: maybeJsonText(status, input.verbose === true, compactAsyncRunStatus(status)),
|
|
1151
|
-
},
|
|
1152
|
-
],
|
|
1153
|
-
details: status,
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
case "tail": {
|
|
1157
|
-
assertRunAccessibleToContext(runId, ctx);
|
|
1158
|
-
const text = AsyncRuns.tailRun(runId, Number(input.lines || Limits.DEFAULT_INSPECT_LINES));
|
|
1159
|
-
return {
|
|
1160
|
-
content: [{ type: "text", text: `\n${text}` }],
|
|
1161
|
-
details: {},
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
case "messages": {
|
|
1165
|
-
assertRunAccessibleToContext(runId, ctx);
|
|
1166
|
-
const messages = AsyncRuns.readRunEvents(runId, Number(input.lines || Limits.DEFAULT_INSPECT_LINES));
|
|
1167
|
-
return {
|
|
1168
|
-
content: [
|
|
1169
|
-
{
|
|
1170
|
-
type: "text",
|
|
1171
|
-
text: maybeJsonText(messages, input.verbose === true, compactRunMessages(messages)),
|
|
1172
|
-
},
|
|
1173
|
-
],
|
|
1174
|
-
details: { messages },
|
|
1175
|
-
};
|
|
1176
|
-
}
|
|
1177
|
-
case "artifacts":
|
|
1178
|
-
case "files": {
|
|
1179
|
-
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1180
|
-
const artifactManifest = AsyncRuns.resolveArtifactManifest(status.artifacts);
|
|
1181
|
-
const details = artifactManifest
|
|
1182
|
-
? {
|
|
1183
|
-
...status,
|
|
1184
|
-
artifact_manifest: artifactManifest,
|
|
1185
|
-
next_actions: artifactNextActions(status.run ?? runId, asRecord(status.artifacts)),
|
|
1186
|
-
}
|
|
1187
|
-
: status;
|
|
1188
|
-
return {
|
|
1189
|
-
content: [
|
|
1190
|
-
{
|
|
1191
|
-
type: "text",
|
|
1192
|
-
text: maybeJsonText(details, input.verbose === true, compactActorFiles(status)),
|
|
1193
|
-
},
|
|
1194
|
-
],
|
|
1195
|
-
details,
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
case "mailbox": {
|
|
1199
|
-
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1200
|
-
const mailbox = asRecord(status.mailbox);
|
|
1201
|
-
const normalizedMailbox = normalizeMailboxContracts(mailbox);
|
|
1202
|
-
const messages = AsyncRuns.readRunInboxMessages(runId, Number(input.lines || Limits.DEFAULT_INSPECT_LINES));
|
|
1203
|
-
const details = {
|
|
1204
|
-
mailbox,
|
|
1205
|
-
normalized_mailbox: normalizedMailbox,
|
|
1206
|
-
messages,
|
|
1207
|
-
};
|
|
1208
|
-
return {
|
|
1209
|
-
content: [
|
|
1210
|
-
{
|
|
1211
|
-
type: "text",
|
|
1212
|
-
text: maybeJsonText(details, input.verbose === true, compactRunMailbox(String(status.run ?? runId), mailbox, messages)),
|
|
1213
|
-
},
|
|
1214
|
-
],
|
|
1215
|
-
details,
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
case "communication": {
|
|
1219
|
-
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1220
|
-
const snapshot = ActorRooms.readCommunicationSnapshot(String(status.state_dir ?? ""));
|
|
1221
|
-
return {
|
|
1222
|
-
content: [
|
|
1223
|
-
{
|
|
1224
|
-
type: "text",
|
|
1225
|
-
text: maybeJsonText(snapshot ?? {}, input.verbose === true, compactCommunicationSnapshot(snapshot)),
|
|
1226
|
-
},
|
|
1227
|
-
],
|
|
1228
|
-
details: { communication: snapshot },
|
|
1229
|
-
};
|
|
1230
|
-
}
|
|
1231
|
-
default:
|
|
1232
|
-
throw new Error("inspect view must be one of: status, tail, messages, artifacts, files, mailbox, communication; branch targets support mailbox.");
|
|
1233
|
-
}
|
|
1234
|
-
},
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
export function createActorMessageToolDefinition(deps = {}) {
|
|
1238
|
-
return {
|
|
1239
|
-
name: "message",
|
|
1240
|
-
label: "Message",
|
|
1241
|
-
description: "Send one typed addressed message to steer an existing actor instead of restarting it. Core routes are run:<id> and tool:<name>; advanced routes include branch:<run>/<branch>, room:<run> group timelines, coordinator, and session:<id>.",
|
|
1242
|
-
parameters: objectSchema({
|
|
1243
|
-
body: unionSchema([
|
|
1244
|
-
stringSchema("Message body. For run:<id>, this is the run-local command line."),
|
|
1245
|
-
looseObjectSchema("Structured JSON message body."),
|
|
1246
|
-
arraySchema("Structured JSON message body array."),
|
|
1247
|
-
]),
|
|
1248
|
-
correlation_id: stringSchema("Optional correlation id for workflow/task linkage."),
|
|
1249
|
-
from: stringSchema("Optional sender address, such as coordinator or run:<id>."),
|
|
1250
|
-
metadata: looseObjectSchema("Optional structured metadata for routing or domain hints."),
|
|
1251
|
-
reply_to: stringSchema("Optional message id this message replies to."),
|
|
1252
|
-
summary: stringSchema("Optional short human-facing summary."),
|
|
1253
|
-
to: stringSchema("Destination actor address, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>."),
|
|
1254
|
-
type: stringSchema("Semantic message type, e.g. control.approve or checkpoint.needs_scope."),
|
|
1255
|
-
verbose: booleanSchema("Return full JSON instead of compact text."),
|
|
1256
|
-
}, ["to", "type"]),
|
|
1257
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1258
|
-
const input = asRecord(params);
|
|
1259
|
-
const message = ActorMessages.normalizeActorMessage(input);
|
|
1260
|
-
const address = ActorMessages.parseActorAddress(message.to);
|
|
1261
|
-
let result;
|
|
1262
|
-
if (address.kind === "run" && address.value) {
|
|
1263
|
-
const status = assertRunAccessibleToContext(address.value, ctx);
|
|
1264
|
-
const normalizedMailbox = normalizeMailboxContracts(asRecord(status.mailbox));
|
|
1265
|
-
const acceptedTypes = new Set(mailboxTypes(normalizedMailbox.accepts));
|
|
1266
|
-
const advisoryWarnings = acceptedTypes.size > 0 && !acceptedTypes.has(message.type)
|
|
1267
|
-
? [
|
|
1268
|
-
`Message type ${message.type} is not declared in mailbox.accepts for run:${address.value}.`,
|
|
1269
|
-
]
|
|
1270
|
-
: [];
|
|
1271
|
-
if (message.type === "control.kill") {
|
|
1272
|
-
result = AsyncRuns.killRun(address.value);
|
|
1273
|
-
}
|
|
1274
|
-
else if (message.type === "control.archive") {
|
|
1275
|
-
result = AsyncRuns.archiveRun(address.value);
|
|
1276
|
-
}
|
|
1277
|
-
else if (message.type === "control.prune") {
|
|
1278
|
-
const body = asRecord(message.body);
|
|
1279
|
-
result = AsyncRuns.pruneRun(address.value, {
|
|
1280
|
-
preserveArtifacts: body.preserve_artifacts === true ||
|
|
1281
|
-
body.preserveArtifacts === true,
|
|
1282
|
-
});
|
|
1283
|
-
}
|
|
1284
|
-
else {
|
|
1285
|
-
result = await AsyncRuns.sendRunMessage(address.value, messageBodyToRunLine(message));
|
|
1286
|
-
}
|
|
1287
|
-
if (advisoryWarnings.length > 0)
|
|
1288
|
-
result = { ...result, warnings: advisoryWarnings };
|
|
1289
|
-
}
|
|
1290
|
-
else if (address.kind === "branch" && address.value) {
|
|
1291
|
-
const runId = address.value;
|
|
1292
|
-
if (message.from)
|
|
1293
|
-
assertMessageSenderBelongsToRun(message, runId, `branch:${runId}/<branch>`);
|
|
1294
|
-
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1295
|
-
const stateDir = String(status.state_dir ?? "");
|
|
1296
|
-
if (stateDir && address.branch) {
|
|
1297
|
-
const ensureBranchMember = (actorAddress) => {
|
|
1298
|
-
ActorRooms.ensureRoomMember(stateDir, runId, "main", actorAddress, {
|
|
1299
|
-
parent: `run:${runId}`,
|
|
1300
|
-
role: "branch",
|
|
1301
|
-
status: "present",
|
|
1302
|
-
}, "Branch joined default room");
|
|
1303
|
-
ActorRooms.writeBranchCommunicationSnapshot(stateDir, runId, actorAddress);
|
|
1304
|
-
};
|
|
1305
|
-
ensureBranchMember(message.to);
|
|
1306
|
-
if (message.from) {
|
|
1307
|
-
const sender = ActorMessages.parseActorAddress(message.from);
|
|
1308
|
-
if (sender.kind === "branch" && sender.value === runId) {
|
|
1309
|
-
ensureBranchMember(message.from);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
ActorRooms.writeCommunicationSnapshot(stateDir, runId);
|
|
1313
|
-
}
|
|
1314
|
-
result = await routeBranchEnvelope(stateDir, runId, message.to, message, {
|
|
1315
|
-
source: "direct",
|
|
1316
|
-
});
|
|
1317
|
-
}
|
|
1318
|
-
else if (address.kind === "room" && address.value && address.room) {
|
|
1319
|
-
const runId = address.value;
|
|
1320
|
-
assertMessageSenderBelongsToRun(message, runId, `room:${runId}`);
|
|
1321
|
-
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1322
|
-
const stateDir = String(status.state_dir ?? "");
|
|
1323
|
-
if (!stateDir)
|
|
1324
|
-
throw new Error(`${message.to} has no run state directory.`);
|
|
1325
|
-
const recipients = getRoomMulticastRecipients(message, runId);
|
|
1326
|
-
const roomResult = ActorRooms.appendRoomMessage(stateDir, address.room, message);
|
|
1327
|
-
await Promise.all(recipients.map((recipient) => routeBranchEnvelope(stateDir, runId, recipient, message, {
|
|
1328
|
-
source: "room-multicast",
|
|
1329
|
-
})));
|
|
1330
|
-
result = {
|
|
1331
|
-
...roomResult,
|
|
1332
|
-
...(recipients.length > 0
|
|
1333
|
-
? { multicast: recipients, multicast_count: recipients.length }
|
|
1334
|
-
: {}),
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
|
-
else if (address.kind === "tool" && address.value) {
|
|
1338
|
-
const tool = deps.getTool?.(address.value);
|
|
1339
|
-
if (!tool || typeof tool.execute !== "function") {
|
|
1340
|
-
throw new Error(`tool actor not found or not executable: ${address.value}`);
|
|
1341
|
-
}
|
|
1342
|
-
const toolParams = messageBodyToToolParams(message);
|
|
1343
|
-
let toolResult;
|
|
1344
|
-
try {
|
|
1345
|
-
toolResult = await tool.execute(`message:${message.type}`, toolParams, _signal, _onUpdate, ctx);
|
|
1346
|
-
}
|
|
1347
|
-
catch (error) {
|
|
1348
|
-
throw formatToolActorFailure(address.value, message, toolParams, error);
|
|
1349
|
-
}
|
|
1350
|
-
result = {
|
|
1351
|
-
invoked: true,
|
|
1352
|
-
sent: true,
|
|
1353
|
-
tool: address.value,
|
|
1354
|
-
tool_result: toolResult,
|
|
1355
|
-
};
|
|
1356
|
-
}
|
|
1357
|
-
else if (address.kind === "coordinator" || address.kind === "session") {
|
|
1358
|
-
if (!message.from) {
|
|
1359
|
-
throw new Error(`message to ${address.kind} requires from=run:<id>.`);
|
|
1360
|
-
}
|
|
1361
|
-
const sender = ActorMessages.parseActorAddress(message.from);
|
|
1362
|
-
if (sender.kind !== "run" || !sender.value) {
|
|
1363
|
-
throw new Error(`message to ${address.kind} currently requires from=run:<id>.`);
|
|
1364
|
-
}
|
|
1365
|
-
const senderStatus = assertRunAccessibleToContext(sender.value, ctx);
|
|
1366
|
-
if (address.kind === "session") {
|
|
1367
|
-
if (!senderStatus.ownerId) {
|
|
1368
|
-
throw sessionMismatchError({
|
|
1369
|
-
currentSession: undefined,
|
|
1370
|
-
expectedSession: address.value,
|
|
1371
|
-
run: sender.value,
|
|
1372
|
-
target: `session:${address.value}`,
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
if (senderStatus.ownerId !== address.value) {
|
|
1376
|
-
throw sessionMismatchError({
|
|
1377
|
-
currentSession: String(senderStatus.ownerId),
|
|
1378
|
-
expectedSession: address.value,
|
|
1379
|
-
run: sender.value,
|
|
1380
|
-
target: `session:${address.value}`,
|
|
1381
|
-
});
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
result = AsyncRuns.appendRunOutboxEvent(sender.value, {
|
|
1385
|
-
body: message.body,
|
|
1386
|
-
correlation_id: message.correlation_id,
|
|
1387
|
-
delivery: message.metadata?.requires_response === true ||
|
|
1388
|
-
address.kind === "session"
|
|
1389
|
-
? "followup"
|
|
1390
|
-
: undefined,
|
|
1391
|
-
event: message.type,
|
|
1392
|
-
from: message.from,
|
|
1393
|
-
metadata: message.metadata,
|
|
1394
|
-
reply_to: message.reply_to,
|
|
1395
|
-
summary: message.summary,
|
|
1396
|
-
to: message.to,
|
|
1397
|
-
type: message.type,
|
|
1398
|
-
});
|
|
1399
|
-
}
|
|
1400
|
-
else {
|
|
1401
|
-
throw new Error(`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`);
|
|
1402
|
-
}
|
|
1403
|
-
const nextActions = actorMessageNextActions(message, result);
|
|
1404
|
-
const resultWithNext = nextActions.length
|
|
1405
|
-
? { ...result, next_actions: nextActions }
|
|
1406
|
-
: result;
|
|
1407
|
-
return {
|
|
1408
|
-
content: [
|
|
1409
|
-
{
|
|
1410
|
-
type: "text",
|
|
1411
|
-
text: maybeJsonText({ message, result: resultWithNext }, input.verbose === true, compactActorMessageResult(message, resultWithNext)),
|
|
1412
|
-
},
|
|
1413
|
-
],
|
|
1414
|
-
details: { message, result: resultWithNext },
|
|
1415
|
-
};
|
|
1416
|
-
},
|
|
1417
|
-
};
|
|
1418
|
-
}
|
|
1419
23
|
export function createCoreActorToolDefinitions(deps) {
|
|
1420
24
|
return [
|
|
1421
|
-
createRegisterToolDefinition({
|
|
25
|
+
ToolsRegister.createRegisterToolDefinition({
|
|
1422
26
|
configPath: deps.configPath,
|
|
1423
27
|
getActiveTools: deps.getActiveTools,
|
|
1424
28
|
getExternalToolConflict: deps.registryRuntime.getExternalToolConflict,
|
|
@@ -1428,95 +32,12 @@ export function createCoreActorToolDefinitions(deps) {
|
|
|
1428
32
|
reservedToolNames: RESERVED_TOOL_NAMES,
|
|
1429
33
|
setActiveTools: deps.setActiveTools,
|
|
1430
34
|
}),
|
|
1431
|
-
createSpawnToolDefinition(),
|
|
1432
|
-
createActorMessageToolDefinition({
|
|
35
|
+
ToolsSpawn.createSpawnToolDefinition(),
|
|
36
|
+
ToolsMessage.createActorMessageToolDefinition({
|
|
1433
37
|
getTool: (name) => deps.getRuntimeTool(name),
|
|
1434
38
|
}),
|
|
1435
|
-
createInspectToolDefinition({
|
|
39
|
+
ToolsInspect.createInspectToolDefinition({
|
|
1436
40
|
getTool: (name) => deps.getRuntimeTool(name),
|
|
1437
41
|
}),
|
|
1438
42
|
];
|
|
1439
43
|
}
|
|
1440
|
-
export function createRuntimeToolDefinition(cfg, exec) {
|
|
1441
|
-
const paramSchema = {};
|
|
1442
|
-
const required = [];
|
|
1443
|
-
const isRecipe = RecipeReferences.isRecipeTool(cfg.template, cfg.recipe);
|
|
1444
|
-
const isAsyncRecipe = cfg.recipe?.async === true ||
|
|
1445
|
-
RecipeReferences.isAsyncRecipeReference(cfg.template);
|
|
1446
|
-
const recipeTemplate = cfg.recipe?.template ?? RecipeReferences.getRecipeTemplate(cfg.template);
|
|
1447
|
-
const requiredTemplate = recipeTemplate ?? cfg.template;
|
|
1448
|
-
const requiredTemplateConfig = typeof requiredTemplate === "object" && !Array.isArray(requiredTemplate)
|
|
1449
|
-
? {
|
|
1450
|
-
...requiredTemplate,
|
|
1451
|
-
args: cfg.args,
|
|
1452
|
-
defaults: { ...(requiredTemplate.defaults ?? {}), ...cfg.defaults },
|
|
1453
|
-
}
|
|
1454
|
-
: {
|
|
1455
|
-
args: cfg.args,
|
|
1456
|
-
defaults: cfg.defaults,
|
|
1457
|
-
template: requiredTemplate,
|
|
1458
|
-
};
|
|
1459
|
-
const requiredArgs = isRecipe && cfg.storedArgs !== undefined
|
|
1460
|
-
? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
|
|
1461
|
-
: RecipeReferences.isRecipeReference(cfg.template) && !recipeTemplate
|
|
1462
|
-
? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
|
|
1463
|
-
: Schema.getRequiredToolArgNames(requiredTemplateConfig);
|
|
1464
|
-
for (const arg of cfg.args) {
|
|
1465
|
-
paramSchema[arg] = typedArgSchema(arg, cfg.argTypes?.[arg]);
|
|
1466
|
-
if (requiredArgs.has(arg))
|
|
1467
|
-
required.push(arg);
|
|
1468
|
-
}
|
|
1469
|
-
if (isAsyncRecipe)
|
|
1470
|
-
paramSchema.run_id = stringSchema("Optional run id override for this async template recipe invocation.");
|
|
1471
|
-
return {
|
|
1472
|
-
name: cfg.name,
|
|
1473
|
-
label: cfg.name,
|
|
1474
|
-
description: cfg.description,
|
|
1475
|
-
parameters: objectSchema(paramSchema, required),
|
|
1476
|
-
promptSnippet: isRecipe
|
|
1477
|
-
? Prompts.formatRecipeToolPromptSnippet(cfg.recipe?.name ?? String(cfg.template), isAsyncRecipe)
|
|
1478
|
-
: Prompts.formatRegisteredToolPromptSnippet(cfg.template),
|
|
1479
|
-
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
1480
|
-
try {
|
|
1481
|
-
if (cfg.sourcePath)
|
|
1482
|
-
RecipeUsage.recordRecipeLaunch(cfg.sourcePath, new Date(), "tool");
|
|
1483
|
-
if (isAsyncRecipe) {
|
|
1484
|
-
const input = params;
|
|
1485
|
-
const { run_id, ...values } = input;
|
|
1486
|
-
const base = cfg.recipe ? cfg.recipe : { file: String(cfg.template) };
|
|
1487
|
-
const runId = typeof run_id === "string" && run_id.trim()
|
|
1488
|
-
? run_id.trim()
|
|
1489
|
-
: `${cfg.name}-${Date.now()}`;
|
|
1490
|
-
const meta = AsyncRuns.startRun({
|
|
1491
|
-
...base,
|
|
1492
|
-
launch_source: "tool",
|
|
1493
|
-
ownerId: getRunOwnerId(ctx),
|
|
1494
|
-
run_id: runId,
|
|
1495
|
-
tool: cfg.name,
|
|
1496
|
-
values: Schema.normalizeRuntimeValues({ ...(cfg.recipe?.values ?? {}), ...cfg.defaults, ...values }, cfg.argTypes),
|
|
1497
|
-
}, ctx.cwd);
|
|
1498
|
-
ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
|
|
1499
|
-
ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
|
|
1500
|
-
return {
|
|
1501
|
-
content: [
|
|
1502
|
-
{ type: "text", text: compactAsyncRunStatus(meta) },
|
|
1503
|
-
],
|
|
1504
|
-
details: meta,
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
if (isRecipe && recipeTemplate) {
|
|
1508
|
-
const paramsWithDefaults = {
|
|
1509
|
-
...(cfg.recipe?.values ?? {}),
|
|
1510
|
-
...cfg.defaults,
|
|
1511
|
-
...params,
|
|
1512
|
-
};
|
|
1513
|
-
return await Execution.executeRegisteredTool({ ...cfg, template: recipeTemplate }, Schema.normalizeRuntimeValues(paramsWithDefaults, cfg.argTypes), exec, ctx.cwd, signal);
|
|
1514
|
-
}
|
|
1515
|
-
return await Execution.executeRegisteredTool(cfg, Schema.normalizeRuntimeValues(params, cfg.argTypes), exec, ctx.cwd, signal);
|
|
1516
|
-
}
|
|
1517
|
-
catch (error) {
|
|
1518
|
-
throw formatRuntimeToolArgumentError(cfg, error, required, isAsyncRecipe);
|
|
1519
|
-
}
|
|
1520
|
-
},
|
|
1521
|
-
};
|
|
1522
|
-
}
|