@llblab/pi-actors 0.19.10 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/AGENTS.md +1 -1
  2. package/BACKLOG.md +40 -1
  3. package/CHANGELOG.md +15 -0
  4. package/dist/lib/actor-inspector-tui.d.ts +55 -0
  5. package/dist/lib/actor-inspector-tui.js +559 -0
  6. package/dist/lib/actor-messages.d.ts +25 -0
  7. package/dist/lib/actor-messages.js +122 -0
  8. package/dist/lib/actor-recipe-context.d.ts +14 -0
  9. package/dist/lib/actor-recipe-context.js +79 -0
  10. package/dist/lib/actor-rooms.d.ts +81 -0
  11. package/dist/lib/actor-rooms.js +468 -0
  12. package/dist/lib/async-runs.d.ts +101 -0
  13. package/dist/lib/async-runs.js +612 -0
  14. package/dist/lib/command-templates.d.ts +70 -0
  15. package/dist/lib/command-templates.js +592 -0
  16. package/dist/lib/config.d.ts +34 -0
  17. package/dist/lib/config.js +226 -0
  18. package/dist/lib/execution.d.ts +63 -0
  19. package/dist/lib/execution.js +450 -0
  20. package/dist/lib/file-state.d.ts +6 -0
  21. package/dist/lib/file-state.js +25 -0
  22. package/dist/lib/identity.d.ts +9 -0
  23. package/dist/lib/identity.js +27 -0
  24. package/dist/lib/observability.d.ts +86 -0
  25. package/dist/lib/observability.js +534 -0
  26. package/dist/lib/output.d.ts +25 -0
  27. package/dist/lib/output.js +89 -0
  28. package/dist/lib/paths.d.ts +11 -0
  29. package/dist/lib/paths.js +28 -0
  30. package/dist/lib/prompts.d.ts +23 -0
  31. package/dist/lib/prompts.js +50 -0
  32. package/dist/lib/recipe-discovery.d.ts +50 -0
  33. package/dist/lib/recipe-discovery.js +317 -0
  34. package/dist/lib/recipe-migration.d.ts +21 -0
  35. package/dist/lib/recipe-migration.js +90 -0
  36. package/dist/lib/recipe-references.d.ts +67 -0
  37. package/dist/lib/recipe-references.js +542 -0
  38. package/dist/lib/recipe-usage.d.ts +6 -0
  39. package/dist/lib/recipe-usage.js +57 -0
  40. package/dist/lib/registry.d.ts +47 -0
  41. package/dist/lib/registry.js +222 -0
  42. package/dist/lib/runtime.d.ts +36 -0
  43. package/dist/lib/runtime.js +126 -0
  44. package/dist/lib/schema.d.ts +48 -0
  45. package/dist/lib/schema.js +355 -0
  46. package/dist/lib/temp.d.ts +10 -0
  47. package/dist/lib/temp.js +90 -0
  48. package/dist/lib/tools.d.ts +39 -0
  49. package/dist/lib/tools.js +982 -0
  50. package/lib/async-runs.ts +20 -4
  51. package/package.json +6 -3
  52. package/scripts/async-runner.mjs +26 -6
  53. package/scripts/validate-recipe.mjs +19 -2
  54. package/skills/actors/SKILL.md +1 -1
  55. package/skills/swarm/SKILL.md +1 -1
@@ -0,0 +1,982 @@
1
+ /**
2
+ * Pi-facing tool definition helpers
3
+ * Zones: pi tools, registry tools, async run launchers
4
+ * Owns generated runtime tool schemas and the register_tool management tool schema
5
+ */
6
+ import * as ActorMessages from "./actor-messages.js";
7
+ import * as ActorRooms from "./actor-rooms.js";
8
+ import * as AsyncRuns from "./async-runs.js";
9
+ import * as Execution from "./execution.js";
10
+ import * as Paths from "./paths.js";
11
+ import * as Prompts from "./prompts.js";
12
+ import * as RecipeDiscovery from "./recipe-discovery.js";
13
+ import * as RecipeReferences from "./recipe-references.js";
14
+ import * as RecipeUsage from "./recipe-usage.js";
15
+ import * as Registry from "./registry.js";
16
+ import * as Schema from "./schema.js";
17
+ function stringSchema(description) {
18
+ return { description, type: "string" };
19
+ }
20
+ function typedArgSchema(arg, type) {
21
+ if (!type || type.kind === "string")
22
+ return stringSchema(`Argument: ${arg}`);
23
+ if (type.kind === "path")
24
+ return stringSchema(`Path argument: ${arg}`);
25
+ if (type.kind === "int")
26
+ return { description: `Integer argument: ${arg}`, type: "integer" };
27
+ if (type.kind === "number")
28
+ return { description: `Number argument: ${arg}`, type: "number" };
29
+ if (type.kind === "bool")
30
+ return { description: `Boolean argument: ${arg}`, type: "boolean" };
31
+ if (type.kind === "array")
32
+ return { description: `Array argument: ${arg}`, items: {}, type: "array" };
33
+ return {
34
+ description: `Enum argument: ${arg}`,
35
+ enum: type.values,
36
+ type: "string",
37
+ };
38
+ }
39
+ function booleanSchema(description) {
40
+ return { description, type: "boolean" };
41
+ }
42
+ function nullSchema(description) {
43
+ return { description, type: "null" };
44
+ }
45
+ function arraySchema(description) {
46
+ return { description, items: {}, type: "array" };
47
+ }
48
+ function unionSchema(anyOf) {
49
+ return { anyOf };
50
+ }
51
+ function objectSchema(properties, required) {
52
+ return { additionalProperties: false, properties, required, type: "object" };
53
+ }
54
+ function sampleValueForArg(arg, type, defaults) {
55
+ if (Object.hasOwn(defaults, arg))
56
+ return defaults[arg];
57
+ if (!type || type.kind === "string")
58
+ return `<${arg}>`;
59
+ if (type.kind === "path")
60
+ return `./${arg}`;
61
+ if (type.kind === "int")
62
+ return 1;
63
+ if (type.kind === "number")
64
+ return 1.5;
65
+ if (type.kind === "bool")
66
+ return true;
67
+ if (type.kind === "array")
68
+ return [`<${arg}>`];
69
+ return type.values[0] ?? `<${arg}>`;
70
+ }
71
+ function shouldAddRuntimeToolUsageHint(error) {
72
+ const message = error instanceof Error ? error.message : String(error);
73
+ return (/^Argument \S+ must /.test(message) || /^Missing .* value: /.test(message));
74
+ }
75
+ function formatRuntimeToolUsageHint(cfg, required, includeRunId) {
76
+ const optional = cfg.args.filter((arg) => !required.includes(arg));
77
+ const example = {};
78
+ for (const arg of required)
79
+ example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], cfg.defaults);
80
+ for (const arg of optional)
81
+ example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], cfg.defaults);
82
+ if (includeRunId)
83
+ example.run_id = `${cfg.name}-1`;
84
+ const lines = [
85
+ `Expected call shape for ${cfg.name}:`,
86
+ `${cfg.name}(${JSON.stringify(example, null, 2)})`,
87
+ ];
88
+ if (required.length)
89
+ lines.push(`Required: ${required.join(", ")}`);
90
+ if (optional.length || includeRunId)
91
+ lines.push(`Optional: ${[...optional, ...(includeRunId ? ["run_id"] : [])].join(", ")}`);
92
+ return lines.join("\n");
93
+ }
94
+ function formatRuntimeToolArgumentError(cfg, error, required, includeRunId) {
95
+ const message = error instanceof Error ? error.message : String(error);
96
+ if (!shouldAddRuntimeToolUsageHint(error))
97
+ return error instanceof Error ? error : new Error(message);
98
+ return new Error(`Invalid arguments for tool "${cfg.name}": ${message}\n\n${formatRuntimeToolUsageHint(cfg, required, includeRunId)}`);
99
+ }
100
+ function looseObjectSchema(description) {
101
+ return { additionalProperties: true, description, type: "object" };
102
+ }
103
+ function jsonText(value) {
104
+ return `\n${JSON.stringify(value, null, 2)}`;
105
+ }
106
+ function asRecord(value) {
107
+ return value && typeof value === "object" && !Array.isArray(value)
108
+ ? value
109
+ : {};
110
+ }
111
+ function formatFailureCount(value) {
112
+ return Array.isArray(value) ? value.length : undefined;
113
+ }
114
+ function compactAsyncRunStatus(value) {
115
+ const status = asRecord(value);
116
+ const progress = asRecord(status.progress);
117
+ const result = asRecord(status.result);
118
+ const tokens = [
119
+ `run=${String(status.run ?? "<unknown>")}`,
120
+ `status=${String(status.status ?? "unknown")}`,
121
+ ];
122
+ if (status.tool)
123
+ tokens.push(`tool=${String(status.tool)}`);
124
+ if (status.recipe)
125
+ tokens.push(`recipe=${String(status.recipe)}`);
126
+ if (status.retire_when)
127
+ tokens.push(`retire_when=${String(status.retire_when)}`);
128
+ if (Number(status.pid) > 0)
129
+ tokens.push(`pid=${Number(status.pid)}`);
130
+ if (progress.phase && progress.phase !== status.status)
131
+ tokens.push(`phase=${String(progress.phase)}`);
132
+ if (Number(progress.activeSubagents) > 0)
133
+ tokens.push(`active=${Number(progress.activeSubagents)}`);
134
+ if (Number(progress.completed) > 0)
135
+ tokens.push(`completed=${Number(progress.completed)}`);
136
+ const failures = formatFailureCount(progress.failures);
137
+ if (failures !== undefined && failures > 0)
138
+ tokens.push(`failures=${failures}`);
139
+ if (result.code !== undefined)
140
+ tokens.push(`code=${String(result.code)}`);
141
+ if (result.killed === true)
142
+ tokens.push("killed=true");
143
+ return `\n${tokens.join(" ")}`;
144
+ }
145
+ function compactRunMessages(messages) {
146
+ if (messages.length === 0)
147
+ return "\n(no actor messages)";
148
+ return `\n${messages
149
+ .map((message) => [
150
+ `run=${message.run}`,
151
+ `type=${message.event}`,
152
+ `level=${message.level}`,
153
+ `summary=${message.summary.replaceAll(/\s+/g, "_")}`,
154
+ ].join(" "))
155
+ .join("\n")}`;
156
+ }
157
+ function compactPreview(value, maxLength = 80) {
158
+ if (value === undefined)
159
+ return undefined;
160
+ const text = typeof value === "string" ? value : JSON.stringify(value, undefined, 0);
161
+ const compact = text.replaceAll(/\s+/g, "_");
162
+ return compact.length > maxLength
163
+ ? `${compact.slice(0, Math.max(0, maxLength - 1))}…`
164
+ : compact;
165
+ }
166
+ function compactRoomPreviews(previews) {
167
+ if (previews.length === 0)
168
+ return "\n(no room message previews)";
169
+ return `\n${previews
170
+ .map((preview) => [
171
+ `ts=${preview.timestamp}`,
172
+ preview.from ? `from=${preview.from}` : "",
173
+ `to=${preview.to}`,
174
+ `type=${preview.type}`,
175
+ preview.summary ? `summary=${compactPreview(preview.summary)}` : "",
176
+ preview.body_preview ? `body=${compactPreview(preview.body_preview)}` : "",
177
+ ]
178
+ .filter(Boolean)
179
+ .join(" "))
180
+ .join("\n")}`;
181
+ }
182
+ function compactRoomMessages(messages) {
183
+ if (messages.length === 0)
184
+ return "\n(no room messages)";
185
+ return `\n${messages
186
+ .map((message) => [
187
+ `ts=${message.received_at}`,
188
+ `from=${String(message.from ?? "<unknown>")}`,
189
+ `to=${message.to}`,
190
+ `type=${message.type}`,
191
+ `summary=${String(message.summary ?? "").replaceAll(/\s+/g, "_")}`,
192
+ compactPreview(message.body) ? `body=${compactPreview(message.body)}` : "",
193
+ ]
194
+ .filter(Boolean)
195
+ .join(" "))
196
+ .join("\n")}`;
197
+ }
198
+ function compactRoomContacts(contacts) {
199
+ if (contacts.length === 0)
200
+ return "\n(no room contacts)";
201
+ return `\n${contacts
202
+ .map((contact) => [
203
+ `address=${contact.address}`,
204
+ contact.role !== undefined ? `role=${String(contact.role)}` : "",
205
+ contact.parent !== undefined ? `parent=${String(contact.parent)}` : "",
206
+ contact.caps !== undefined ? `caps=${Array.isArray(contact.caps) ? contact.caps.join(",") : String(contact.caps)}` : "",
207
+ contact.claim !== undefined ? `claim=${String(contact.claim).replaceAll(/\s+/g, "_")}` : "",
208
+ contact.status !== undefined ? `status=${String(contact.status)}` : "",
209
+ ]
210
+ .filter(Boolean)
211
+ .join(" "))
212
+ .join("\n")}`;
213
+ }
214
+ function compactRoomRoster(roster) {
215
+ const members = Object.values(roster);
216
+ if (members.length === 0)
217
+ return "\n(no room members)";
218
+ return `\n${members
219
+ .map((member) => [
220
+ `address=${member.address}`,
221
+ `role=${String(member.role ?? "")}`,
222
+ member.parent !== undefined ? `parent=${String(member.parent)}` : "",
223
+ member.caps !== undefined ? `caps=${Array.isArray(member.caps) ? member.caps.join(",") : String(member.caps)}` : "",
224
+ member.claim !== undefined ? `claim=${String(member.claim).replaceAll(/\s+/g, "_")}` : "",
225
+ `status=${String(member.status ?? "")}`,
226
+ `last_seen=${member.last_seen}`,
227
+ ].join(" "))
228
+ .join("\n")}`;
229
+ }
230
+ function compactRoomStatus(status) {
231
+ return `\nroom=${status.room} messages=${status.message_count} roster=${status.roster_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)}` : ""}`;
232
+ }
233
+ function compactCommunicationSnapshot(snapshot) {
234
+ if (!snapshot)
235
+ return "\n(no communication snapshot)";
236
+ return `\nself=${snapshot.self} root=${snapshot.root} rooms=${snapshot.rooms.length} updated_at=${snapshot.updated_at}`;
237
+ }
238
+ function compactBranchInbox(messages) {
239
+ if (messages.length === 0)
240
+ return "\n(no branch inbox messages)";
241
+ return `\n${messages
242
+ .map((message) => [
243
+ ...(message.id ? [`id=${String(message.id)}`] : []),
244
+ `status=${String(message.status ?? "")}`,
245
+ `type=${String(message.type ?? "")}`,
246
+ `from=${String(message.from ?? "")}`,
247
+ `to=${String(message.to ?? "")}`,
248
+ ...(message.queued_at ? [`queued_at=${String(message.queued_at)}`] : []),
249
+ ...(message.claimed_at ? [`claimed_at=${String(message.claimed_at)}`] : []),
250
+ ...(message.handled_at ? [`handled_at=${String(message.handled_at)}`] : []),
251
+ ...(message.failed_at ? [`failed_at=${String(message.failed_at)}`] : []),
252
+ ].join(" "))
253
+ .join("\n")}`;
254
+ }
255
+ function compactActorFiles(status) {
256
+ const run = String(status.run ?? "<unknown>");
257
+ const artifacts = asRecord(status.artifacts);
258
+ const files = [
259
+ status.stdoutLog,
260
+ status.stderrLog,
261
+ status.eventsFile,
262
+ status.outboxFile,
263
+ status.state_dir ? `${String(status.state_dir)}/communication.json` : undefined,
264
+ status.state_dir ? `${String(status.state_dir)}/result.json` : undefined,
265
+ ].filter((file) => typeof file === "string");
266
+ const artifactText = Object.keys(artifacts).length
267
+ ? ` artifacts=${Object.entries(artifacts)
268
+ .map(([key, value]) => `${key}:${String(value)}`)
269
+ .join(",")}`
270
+ : "";
271
+ return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}`;
272
+ }
273
+ function compactSessionRuns(session, runs) {
274
+ if (runs.length === 0)
275
+ return `\nsession=${session} runs=0`;
276
+ return `\nsession=${session} runs=${runs.length}\n${runs
277
+ .map((run) => {
278
+ const tokens = [
279
+ `run=${String(run.run ?? "")}`,
280
+ `status=${String(run.status ?? "")}`,
281
+ ];
282
+ if (run.recipe)
283
+ tokens.push(`recipe=${String(run.recipe)}`);
284
+ if (run.retire_when)
285
+ tokens.push(`retire_when=${String(run.retire_when)}`);
286
+ return tokens.join(" ");
287
+ })
288
+ .join("\n")}`;
289
+ }
290
+ function compactToolActor(name, tool) {
291
+ const parameters = asRecord(tool.parameters);
292
+ const required = Array.isArray(parameters.required)
293
+ ? parameters.required.join(",")
294
+ : "";
295
+ const properties = asRecord(parameters.properties);
296
+ return `\ntool=${name} description=${String(tool.description ?? "").replaceAll(/\s+/g, "_")} args=${Object.keys(properties).join(",")} required=${required}`;
297
+ }
298
+ function compactRecipeRegistry(summary) {
299
+ const active = Array.isArray(summary.active) ? summary.active.length : 0;
300
+ const shadowed = Array.isArray(summary.shadowed) ? summary.shadowed.length : 0;
301
+ const invalid = Array.isArray(summary.invalid) ? summary.invalid.length : 0;
302
+ const disabled = Array.isArray(summary.disabled) ? summary.disabled.length : 0;
303
+ const diagnostics = Array.isArray(summary.diagnostics)
304
+ ? summary.diagnostics.length
305
+ : 0;
306
+ const recommendations = Array.isArray(summary.recommendations)
307
+ ? summary.recommendations.length
308
+ : 0;
309
+ return `\nrecipes active=${active} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}`;
310
+ }
311
+ function compactActorMessageResult(message, result) {
312
+ const tokens = [
313
+ `to=${message.to}`,
314
+ `type=${message.type}`,
315
+ `message=${result.sent === true || result.stopped === true ? "sent" : "not_sent"}`,
316
+ ];
317
+ if (result.bytes !== undefined)
318
+ tokens.push(`bytes=${String(result.bytes)}`);
319
+ if (result.control)
320
+ tokens.push(`control=${String(result.control)}`);
321
+ if (result.outbox)
322
+ tokens.push(`messages=${String(result.outbox)}`);
323
+ if (result.message_count !== undefined)
324
+ tokens.push(`messages=${String(result.message_count)}`);
325
+ if (result.roster_count !== undefined)
326
+ tokens.push(`roster=${String(result.roster_count)}`);
327
+ if (result.room)
328
+ tokens.push(`room=${String(result.room)}`);
329
+ if (result.tool)
330
+ tokens.push(`tool=${String(result.tool)}`);
331
+ if (result.stopped === true)
332
+ tokens.push("stopped=true");
333
+ if (result.signal)
334
+ tokens.push(`signal=${String(result.signal)}`);
335
+ if (result.invoked === true)
336
+ tokens.push("invoked=true");
337
+ return `\n${tokens.join(" ")}`;
338
+ }
339
+ function maybeJsonText(value, verbose, compact) {
340
+ return verbose ? jsonText(value) : compact;
341
+ }
342
+ export function createRegisterToolDefinition(deps) {
343
+ return {
344
+ name: "register_tool",
345
+ label: "Register Tool",
346
+ description: Prompts.REGISTER_TOOL_DESCRIPTION,
347
+ promptSnippet: Prompts.REGISTER_TOOL_PROMPT_SNIPPET,
348
+ promptGuidelines: Prompts.REGISTER_TOOL_GUIDELINES,
349
+ parameters: objectSchema({
350
+ args: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.args),
351
+ async: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.async),
352
+ description: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description),
353
+ name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
354
+ state_dir: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.state_dir),
355
+ template: unionSchema([
356
+ stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
357
+ arraySchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateArray),
358
+ nullSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateNull),
359
+ ]),
360
+ update: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.update),
361
+ values: looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.values),
362
+ }, []),
363
+ execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => Registry.executeRegisterTool(params, ctx, deps),
364
+ };
365
+ }
366
+ function getRunOwnerId(ctx) {
367
+ return ctx.sessionManager?.getSessionId?.();
368
+ }
369
+ function messageBodyToRunLine(message) {
370
+ if (typeof message.body === "string")
371
+ return message.body;
372
+ if (message.body === undefined)
373
+ return message.type;
374
+ return JSON.stringify(message.body);
375
+ }
376
+ function messageBodyToToolParams(message) {
377
+ if (message.body &&
378
+ typeof message.body === "object" &&
379
+ !Array.isArray(message.body)) {
380
+ return message.body;
381
+ }
382
+ if (message.body === undefined)
383
+ return {};
384
+ return { input: message.body };
385
+ }
386
+ function runIdFromActorAddress(address) {
387
+ if (!address)
388
+ return undefined;
389
+ const parsed = ActorMessages.parseActorAddress(address);
390
+ if (parsed.kind !== "run" || !parsed.value) {
391
+ throw new Error(`Expected run:<id> actor address, received: ${address}`);
392
+ }
393
+ return parsed.value;
394
+ }
395
+ function assertMessageSenderBelongsToRun(message, run, routeLabel) {
396
+ if (!message.from) {
397
+ throw new Error(`message to ${message.to} requires from=<actor address>.`);
398
+ }
399
+ const sender = ActorMessages.parseActorAddress(message.from);
400
+ if ((sender.kind !== "run" && sender.kind !== "branch") ||
401
+ sender.value !== run) {
402
+ throw new Error(`message to ${routeLabel} requires from=run:${run} or branch:${run}/<branch>; got ${message.from}.`);
403
+ }
404
+ }
405
+ function getRoomMulticastRecipients(message, run) {
406
+ const raw = message.metadata?.recipients;
407
+ if (raw === undefined)
408
+ return [];
409
+ if (!Array.isArray(raw)) {
410
+ throw new Error("room multicast metadata.recipients must be an array.");
411
+ }
412
+ return raw.map((recipient) => {
413
+ if (typeof recipient !== "string") {
414
+ throw new Error("room multicast recipients must be actor addresses.");
415
+ }
416
+ const parsed = ActorMessages.parseActorAddress(recipient);
417
+ if (parsed.kind !== "branch" || parsed.value !== run) {
418
+ throw new Error(`room multicast recipient must be branch:${run}/<branch>; got ${recipient}.`);
419
+ }
420
+ return ActorMessages.formatActorAddress(parsed);
421
+ });
422
+ }
423
+ export function createSpawnToolDefinition() {
424
+ return {
425
+ name: "spawn",
426
+ label: "Spawn",
427
+ description: "Create an addressable actor from a recipe file or inline command template. Currently spawns run:<id> actors backed by async runs.",
428
+ parameters: objectSchema({
429
+ artifacts: looseObjectSchema("Optional named artifact paths for the spawned actor."),
430
+ as: stringSchema("Optional actor address for the spawned run, e.g. run:<id>."),
431
+ file: stringSchema("Optional template recipe JSON file. Bare names resolve under ~/.pi/agent/recipes."),
432
+ recipe: stringSchema("Alias for file; template recipe JSON file/name to spawn."),
433
+ state_dir: stringSchema("Optional explicit run state directory."),
434
+ template: unionSchema([
435
+ stringSchema("Inline command template string"),
436
+ arraySchema("Inline command-template sequence or parallel tree"),
437
+ looseObjectSchema("Inline command-template object with flags such as parallel, repeat, retry, failure, and nested template."),
438
+ ]),
439
+ values: looseObjectSchema("Runtime placeholder values passed to the actor."),
440
+ verbose: booleanSchema("Return full JSON instead of compact text."),
441
+ }, []),
442
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
443
+ const input = asRecord(params);
444
+ const runId = runIdFromActorAddress(typeof input.as === "string" ? input.as : undefined);
445
+ const meta = AsyncRuns.startRun({
446
+ file: typeof input.file === "string"
447
+ ? input.file
448
+ : typeof input.recipe === "string"
449
+ ? input.recipe
450
+ : undefined,
451
+ launch_source: "spawn",
452
+ ownerId: getRunOwnerId(ctx),
453
+ run_id: runId,
454
+ state_dir: typeof input.state_dir === "string" ? input.state_dir : undefined,
455
+ ...(input.template !== undefined
456
+ ? { template: input.template }
457
+ : {}),
458
+ values: asRecord(input.values),
459
+ ...(input.artifacts &&
460
+ typeof input.artifacts === "object" &&
461
+ !Array.isArray(input.artifacts)
462
+ ? { artifacts: input.artifacts }
463
+ : {}),
464
+ }, ctx.cwd);
465
+ ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
466
+ ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
467
+ return {
468
+ content: [
469
+ {
470
+ type: "text",
471
+ text: maybeJsonText(meta, input.verbose === true, compactAsyncRunStatus(meta)),
472
+ },
473
+ ],
474
+ details: meta,
475
+ };
476
+ },
477
+ };
478
+ }
479
+ function getContextSessionId(ctx) {
480
+ return ctx?.sessionManager?.getSessionId?.();
481
+ }
482
+ function requireContextSessionId(ctx, actor) {
483
+ const sessionId = getContextSessionId(ctx);
484
+ if (!sessionId) {
485
+ throw new Error(`${actor} requires a current coordinator session; use session:<id> or session:all for explicit session inventory.`);
486
+ }
487
+ return sessionId;
488
+ }
489
+ function assertRunAccessibleToContext(runId, ctx) {
490
+ const status = AsyncRuns.getRunStatus(runId);
491
+ const sessionId = getContextSessionId(ctx);
492
+ if (sessionId && status.ownerId && status.ownerId !== sessionId) {
493
+ throw new Error(`run:${runId} is owned by session:${status.ownerId}; current session is ${sessionId}.`);
494
+ }
495
+ return status;
496
+ }
497
+ function assertRunExistsForActorMessage(runId) {
498
+ return AsyncRuns.getRunStatus(runId);
499
+ }
500
+ export function createInspectToolDefinition(deps = {}) {
501
+ return {
502
+ name: "inspect",
503
+ label: "Inspect",
504
+ description: "Intentionally inspect an actor. Supports run:<id> views: status, tail, messages, artifacts, files, mailbox, communication; room:<run> status/messages/previews/roster/contacts; coordinator/session status; and tool:<name> status/schema.",
505
+ parameters: objectSchema({
506
+ lines: stringSchema("Line count for tail/messages views. Default 40."),
507
+ status: stringSchema("Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited."),
508
+ target: stringSchema("Actor address to inspect, e.g. run:<id>, room:<run>, coordinator, session:<id>, session:all, or tool:<name>."),
509
+ verbose: booleanSchema("Return full JSON instead of compact text where available."),
510
+ view: stringSchema("Inspection view: status, tail, messages, artifacts, files, mailbox, communication, roster, or contacts."),
511
+ }, ["target", "view"]),
512
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
513
+ const input = asRecord(params);
514
+ const target = String(input.target ?? "");
515
+ const view = String(input.view ?? "");
516
+ if (target === "recipes" || target === "recipe-registry") {
517
+ if (view !== "status" && view !== "summary") {
518
+ throw new Error("inspect recipes supports view=status or view=summary.");
519
+ }
520
+ const discovered = RecipeDiscovery.discoverRecipeSources([
521
+ { root: deps.recipeRoot ?? Paths.getRecipeRoot(), defaultTool: true, mutableUsage: true },
522
+ { root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
523
+ ]);
524
+ const summary = RecipeDiscovery.summarizeDiscovery(discovered);
525
+ return {
526
+ content: [
527
+ {
528
+ type: "text",
529
+ text: maybeJsonText(summary, input.verbose === true, compactRecipeRegistry(summary)),
530
+ },
531
+ ],
532
+ details: summary,
533
+ };
534
+ }
535
+ const address = ActorMessages.parseActorAddress(target);
536
+ if (address.kind === "coordinator") {
537
+ if (view !== "status" && view !== "runs") {
538
+ throw new Error("inspect coordinator supports view=status or view=runs.");
539
+ }
540
+ const session = requireContextSessionId(ctx, "inspect coordinator");
541
+ const runs = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined)
542
+ .map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
543
+ .filter((run) => run.ownerId === session);
544
+ return {
545
+ content: [
546
+ {
547
+ type: "text",
548
+ text: maybeJsonText({ session, runs }, input.verbose === true, compactSessionRuns(session, runs)),
549
+ },
550
+ ],
551
+ details: { session, runs },
552
+ };
553
+ }
554
+ if (address.kind === "session") {
555
+ if (view !== "status" && view !== "runs") {
556
+ throw new Error("inspect session:<id> supports view=status or view=runs.");
557
+ }
558
+ const runs = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined)
559
+ .map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
560
+ .filter((run) => address.value === "all" || run.ownerId === address.value);
561
+ return {
562
+ content: [
563
+ {
564
+ type: "text",
565
+ text: maybeJsonText({ session: address.value, runs }, input.verbose === true, compactSessionRuns(address.value || "", runs)),
566
+ },
567
+ ],
568
+ details: { session: address.value, runs },
569
+ };
570
+ }
571
+ if (address.kind === "tool" && address.value) {
572
+ if (view !== "status" && view !== "schema") {
573
+ throw new Error("inspect tool:<name> supports view=status or view=schema.");
574
+ }
575
+ const tool = deps.getTool?.(address.value);
576
+ if (!tool)
577
+ throw new Error(`tool actor not found: ${address.value}`);
578
+ const details = {
579
+ name: address.value,
580
+ description: tool.description,
581
+ parameters: tool.parameters,
582
+ promptSnippet: tool.promptSnippet,
583
+ };
584
+ return {
585
+ content: [
586
+ {
587
+ type: "text",
588
+ text: maybeJsonText(details, input.verbose === true || view === "schema", compactToolActor(address.value, details)),
589
+ },
590
+ ],
591
+ details,
592
+ };
593
+ }
594
+ if (address.kind === "room" && address.value && address.room) {
595
+ const status = assertRunAccessibleToContext(address.value, ctx);
596
+ const stateDir = String(status.state_dir ?? "");
597
+ if (!stateDir)
598
+ throw new Error(`room:${address.value} has no run state directory.`);
599
+ if (view === "status") {
600
+ const status = ActorRooms.getRoomStatus(stateDir, address.room);
601
+ return {
602
+ content: [
603
+ {
604
+ type: "text",
605
+ text: maybeJsonText(status, input.verbose === true, compactRoomStatus(status)),
606
+ },
607
+ ],
608
+ details: status,
609
+ };
610
+ }
611
+ if (view === "previews") {
612
+ const previews = ActorRooms.readRoomMessagePreviews(stateDir, address.room, Number(input.lines || 40));
613
+ return {
614
+ content: [
615
+ {
616
+ type: "text",
617
+ text: maybeJsonText(previews, input.verbose === true, compactRoomPreviews(previews)),
618
+ },
619
+ ],
620
+ details: { previews },
621
+ };
622
+ }
623
+ if (view === "messages") {
624
+ const messages = ActorRooms.readRoomMessages(stateDir, address.room, Number(input.lines || 40));
625
+ return {
626
+ content: [
627
+ {
628
+ type: "text",
629
+ text: maybeJsonText(messages, input.verbose === true, compactRoomMessages(messages)),
630
+ },
631
+ ],
632
+ details: { messages },
633
+ };
634
+ }
635
+ if (view === "contacts") {
636
+ const contacts = ActorRooms.readRoomContacts(stateDir, address.room);
637
+ return {
638
+ content: [
639
+ {
640
+ type: "text",
641
+ text: maybeJsonText(contacts, input.verbose === true, compactRoomContacts(contacts)),
642
+ },
643
+ ],
644
+ details: { contacts },
645
+ };
646
+ }
647
+ if (view === "roster") {
648
+ const roster = ActorRooms.readRoomRoster(stateDir, address.room);
649
+ return {
650
+ content: [
651
+ {
652
+ type: "text",
653
+ text: maybeJsonText(roster, input.verbose === true, compactRoomRoster(roster)),
654
+ },
655
+ ],
656
+ details: { roster },
657
+ };
658
+ }
659
+ throw new Error("inspect room:<run> supports view=status, view=messages, view=previews, view=roster, or view=contacts.");
660
+ }
661
+ const runId = address.kind === "run" || address.kind === "branch" ? address.value : undefined;
662
+ if (!runId)
663
+ throw new Error("inspect target must be run:<id>, branch:<run>/<branch>, coordinator, session:<id>, or tool:<name>.");
664
+ if (address.kind === "branch") {
665
+ if (view !== "mailbox")
666
+ throw new Error("inspect branch:<run>/<branch> supports view=mailbox.");
667
+ const status = assertRunAccessibleToContext(runId, ctx);
668
+ const messages = ActorRooms.readBranchInboxMessages(String(status.state_dir ?? ""), runId, target, Number(input.lines || 40));
669
+ return {
670
+ content: [
671
+ {
672
+ type: "text",
673
+ text: maybeJsonText(messages, input.verbose === true, compactBranchInbox(messages.map((message) => ({ ...message })))),
674
+ },
675
+ ],
676
+ details: { messages },
677
+ };
678
+ }
679
+ switch (view) {
680
+ case "status": {
681
+ const status = assertRunAccessibleToContext(runId, ctx);
682
+ return {
683
+ content: [
684
+ {
685
+ type: "text",
686
+ text: maybeJsonText(status, input.verbose === true, compactAsyncRunStatus(status)),
687
+ },
688
+ ],
689
+ details: status,
690
+ };
691
+ }
692
+ case "tail": {
693
+ assertRunAccessibleToContext(runId, ctx);
694
+ const text = AsyncRuns.tailRun(runId, Number(input.lines || 40));
695
+ return {
696
+ content: [{ type: "text", text: `\n${text}` }],
697
+ details: {},
698
+ };
699
+ }
700
+ case "messages": {
701
+ assertRunAccessibleToContext(runId, ctx);
702
+ const messages = AsyncRuns.readRunEvents(runId, Number(input.lines || 40));
703
+ return {
704
+ content: [
705
+ {
706
+ type: "text",
707
+ text: maybeJsonText(messages, input.verbose === true, compactRunMessages(messages)),
708
+ },
709
+ ],
710
+ details: { messages },
711
+ };
712
+ }
713
+ case "artifacts":
714
+ case "files": {
715
+ const status = assertRunAccessibleToContext(runId, ctx);
716
+ return {
717
+ content: [
718
+ {
719
+ type: "text",
720
+ text: maybeJsonText(status, input.verbose === true, compactActorFiles(status)),
721
+ },
722
+ ],
723
+ details: status,
724
+ };
725
+ }
726
+ case "mailbox": {
727
+ const status = assertRunAccessibleToContext(runId, ctx);
728
+ const mailbox = asRecord(status.mailbox);
729
+ return {
730
+ content: [
731
+ {
732
+ type: "text",
733
+ text: maybeJsonText(mailbox, input.verbose === true, `\nrun=${String(status.run ?? runId)} accepts=${Array.isArray(mailbox.accepts) ? mailbox.accepts.join(",") : ""} emits=${Array.isArray(mailbox.emits) ? mailbox.emits.join(",") : ""}`),
734
+ },
735
+ ],
736
+ details: { mailbox },
737
+ };
738
+ }
739
+ case "communication": {
740
+ const status = assertRunAccessibleToContext(runId, ctx);
741
+ const snapshot = ActorRooms.readCommunicationSnapshot(String(status.state_dir ?? ""));
742
+ return {
743
+ content: [
744
+ {
745
+ type: "text",
746
+ text: maybeJsonText(snapshot ?? {}, input.verbose === true, compactCommunicationSnapshot(snapshot)),
747
+ },
748
+ ],
749
+ details: { communication: snapshot },
750
+ };
751
+ }
752
+ default:
753
+ throw new Error("inspect view must be one of: status, tail, messages, artifacts, files, mailbox, communication; branch targets support mailbox.");
754
+ }
755
+ },
756
+ };
757
+ }
758
+ export function createActorMessageToolDefinition(deps = {}) {
759
+ return {
760
+ name: "message",
761
+ label: "Message",
762
+ description: "Send one typed addressed message. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, room:<run> timelines/rosters, tool:<name> calls, and coordinator/session-bound run messages.",
763
+ parameters: objectSchema({
764
+ body: unionSchema([
765
+ stringSchema("Message body. For run:<id>, this is the run-local command line."),
766
+ looseObjectSchema("Structured JSON message body."),
767
+ arraySchema("Structured JSON message body array."),
768
+ ]),
769
+ correlation_id: stringSchema("Optional correlation id for workflow/task linkage."),
770
+ from: stringSchema("Optional sender address, such as coordinator or run:<id>."),
771
+ metadata: looseObjectSchema("Optional structured metadata for routing or domain hints."),
772
+ reply_to: stringSchema("Optional message id this message replies to."),
773
+ summary: stringSchema("Optional short human-facing summary."),
774
+ to: stringSchema("Destination actor address, e.g. run:<id>, branch:<run>/<branch>, room:<run>, coordinator, session:<id>, or tool:<name>."),
775
+ type: stringSchema("Semantic message type, e.g. control.approve or checkpoint.needs_scope."),
776
+ verbose: booleanSchema("Return full JSON instead of compact text."),
777
+ }, ["to", "type"]),
778
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
779
+ const input = asRecord(params);
780
+ const message = ActorMessages.normalizeActorMessage(input);
781
+ const address = ActorMessages.parseActorAddress(message.to);
782
+ let result;
783
+ if (address.kind === "run" && address.value) {
784
+ assertRunAccessibleToContext(address.value, ctx);
785
+ if (message.type === "control.stop" ||
786
+ message.type === "control.cancel") {
787
+ result = AsyncRuns.cancelRun(address.value);
788
+ }
789
+ else if (message.type === "control.kill") {
790
+ result = AsyncRuns.killRun(address.value);
791
+ }
792
+ else {
793
+ result = AsyncRuns.sendRunMessage(address.value, messageBodyToRunLine(message));
794
+ }
795
+ }
796
+ else if (address.kind === "branch" && address.value) {
797
+ const runId = address.value;
798
+ if (message.from)
799
+ assertMessageSenderBelongsToRun(message, runId, `branch:${runId}/<branch>`);
800
+ const status = message.from
801
+ ? assertRunExistsForActorMessage(runId)
802
+ : assertRunAccessibleToContext(runId, ctx);
803
+ const stateDir = String(status.state_dir ?? "");
804
+ if (stateDir && address.branch) {
805
+ const ensureBranchMember = (actorAddress) => {
806
+ ActorRooms.ensureRoomMember(stateDir, runId, "main", actorAddress, {
807
+ parent: `run:${runId}`,
808
+ role: "branch",
809
+ status: "present",
810
+ }, "Branch joined default room");
811
+ ActorRooms.writeBranchCommunicationSnapshot(stateDir, runId, actorAddress);
812
+ };
813
+ ensureBranchMember(message.to);
814
+ if (message.from) {
815
+ const sender = ActorMessages.parseActorAddress(message.from);
816
+ if (sender.kind === "branch" && sender.value === runId) {
817
+ ensureBranchMember(message.from);
818
+ }
819
+ }
820
+ ActorRooms.writeCommunicationSnapshot(stateDir, runId);
821
+ ActorRooms.appendBranchInboxMessage(stateDir, runId, message.to, message);
822
+ }
823
+ result = AsyncRuns.sendRunMessage(address.value, JSON.stringify(message));
824
+ }
825
+ else if (address.kind === "room" && address.value && address.room) {
826
+ const runId = address.value;
827
+ assertMessageSenderBelongsToRun(message, runId, `room:${runId}`);
828
+ const status = assertRunExistsForActorMessage(runId);
829
+ const stateDir = String(status.state_dir ?? "");
830
+ if (!stateDir)
831
+ throw new Error(`${message.to} has no run state directory.`);
832
+ const recipients = getRoomMulticastRecipients(message, runId);
833
+ const roomResult = ActorRooms.appendRoomMessage(stateDir, address.room, message);
834
+ const multicast = recipients.map((recipient) => AsyncRuns.sendRunMessage(runId, JSON.stringify({ ...message, to: recipient })));
835
+ result = {
836
+ ...roomResult,
837
+ ...(multicast.length > 0
838
+ ? { multicast: recipients, multicast_count: multicast.length }
839
+ : {}),
840
+ };
841
+ }
842
+ else if (address.kind === "tool" && address.value) {
843
+ const tool = deps.getTool?.(address.value);
844
+ if (!tool || typeof tool.execute !== "function") {
845
+ throw new Error(`tool actor not found or not executable: ${address.value}`);
846
+ }
847
+ const toolResult = await tool.execute(`message:${message.type}`, messageBodyToToolParams(message), _signal, _onUpdate, ctx);
848
+ result = {
849
+ invoked: true,
850
+ sent: true,
851
+ tool: address.value,
852
+ tool_result: toolResult,
853
+ };
854
+ }
855
+ else if (address.kind === "coordinator" || address.kind === "session") {
856
+ if (!message.from) {
857
+ throw new Error(`message to ${address.kind} requires from=run:<id>.`);
858
+ }
859
+ const sender = ActorMessages.parseActorAddress(message.from);
860
+ if (sender.kind !== "run" || !sender.value) {
861
+ throw new Error(`message to ${address.kind} currently requires from=run:<id>.`);
862
+ }
863
+ const senderStatus = assertRunAccessibleToContext(sender.value, ctx);
864
+ if (address.kind === "session") {
865
+ if (!senderStatus.ownerId) {
866
+ throw new Error(`message to session:${address.value} requires sender run owner ${address.value}; got no owner.`);
867
+ }
868
+ if (senderStatus.ownerId !== address.value) {
869
+ throw new Error(`message to session:${address.value} requires sender run owner ${address.value}; got ${senderStatus.ownerId}.`);
870
+ }
871
+ }
872
+ result = AsyncRuns.appendRunOutboxEvent(sender.value, {
873
+ body: message.body,
874
+ correlation_id: message.correlation_id,
875
+ delivery: address.kind === "session" ? "followup" : undefined,
876
+ event: message.type,
877
+ from: message.from,
878
+ metadata: message.metadata,
879
+ reply_to: message.reply_to,
880
+ summary: message.summary,
881
+ to: message.to,
882
+ type: message.type,
883
+ });
884
+ }
885
+ else {
886
+ throw new Error(`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`);
887
+ }
888
+ return {
889
+ content: [
890
+ {
891
+ type: "text",
892
+ text: maybeJsonText({ message, result }, input.verbose === true, compactActorMessageResult(message, result)),
893
+ },
894
+ ],
895
+ details: { message, result },
896
+ };
897
+ },
898
+ };
899
+ }
900
+ export function createRuntimeToolDefinition(cfg, exec) {
901
+ const paramSchema = {};
902
+ const required = [];
903
+ const isRecipe = RecipeReferences.isRecipeTool(cfg.template, cfg.recipe);
904
+ const isAsyncRecipe = cfg.recipe?.async === true ||
905
+ RecipeReferences.isAsyncRecipeReference(cfg.template);
906
+ const recipeTemplate = cfg.recipe?.template ?? RecipeReferences.getRecipeTemplate(cfg.template);
907
+ const requiredTemplate = recipeTemplate ?? cfg.template;
908
+ const requiredTemplateConfig = typeof requiredTemplate === "object" && !Array.isArray(requiredTemplate)
909
+ ? {
910
+ ...requiredTemplate,
911
+ args: cfg.args,
912
+ defaults: { ...(requiredTemplate.defaults ?? {}), ...cfg.defaults },
913
+ }
914
+ : {
915
+ args: cfg.args,
916
+ defaults: cfg.defaults,
917
+ template: requiredTemplate,
918
+ };
919
+ const requiredArgs = isRecipe && cfg.storedArgs !== undefined
920
+ ? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
921
+ : RecipeReferences.isRecipeReference(cfg.template) && !recipeTemplate
922
+ ? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
923
+ : Schema.getRequiredToolArgNames(requiredTemplateConfig);
924
+ for (const arg of cfg.args) {
925
+ paramSchema[arg] = typedArgSchema(arg, cfg.argTypes?.[arg]);
926
+ if (requiredArgs.has(arg))
927
+ required.push(arg);
928
+ }
929
+ if (isAsyncRecipe)
930
+ paramSchema.run_id = stringSchema("Optional run id override for this async template recipe invocation.");
931
+ return {
932
+ name: cfg.name,
933
+ label: cfg.name,
934
+ description: cfg.description,
935
+ parameters: objectSchema(paramSchema, required),
936
+ promptSnippet: isRecipe
937
+ ? Prompts.formatRecipeToolPromptSnippet(cfg.recipe?.name ?? String(cfg.template), isAsyncRecipe)
938
+ : Prompts.formatRegisteredToolPromptSnippet(cfg.template),
939
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
940
+ try {
941
+ if (cfg.sourcePath)
942
+ RecipeUsage.recordRecipeLaunch(cfg.sourcePath);
943
+ if (isAsyncRecipe) {
944
+ const input = params;
945
+ const { run_id, ...values } = input;
946
+ const base = cfg.recipe ? cfg.recipe : { file: String(cfg.template) };
947
+ const runId = typeof run_id === "string" && run_id.trim()
948
+ ? run_id.trim()
949
+ : `${cfg.name}-${Date.now()}`;
950
+ const meta = AsyncRuns.startRun({
951
+ ...base,
952
+ launch_source: "tool",
953
+ ownerId: getRunOwnerId(ctx),
954
+ run_id: runId,
955
+ tool: cfg.name,
956
+ values: Schema.normalizeRuntimeValues({ ...(cfg.recipe?.values ?? {}), ...cfg.defaults, ...values }, cfg.argTypes),
957
+ }, ctx.cwd);
958
+ ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
959
+ ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
960
+ return {
961
+ content: [
962
+ { type: "text", text: compactAsyncRunStatus(meta) },
963
+ ],
964
+ details: meta,
965
+ };
966
+ }
967
+ if (isRecipe && recipeTemplate) {
968
+ const paramsWithDefaults = {
969
+ ...(cfg.recipe?.values ?? {}),
970
+ ...cfg.defaults,
971
+ ...params,
972
+ };
973
+ return await Execution.executeRegisteredTool({ ...cfg, template: recipeTemplate }, Schema.normalizeRuntimeValues(paramsWithDefaults, cfg.argTypes), exec, ctx.cwd, signal);
974
+ }
975
+ return await Execution.executeRegisteredTool(cfg, Schema.normalizeRuntimeValues(params, cfg.argTypes), exec, ctx.cwd, signal);
976
+ }
977
+ catch (error) {
978
+ throw formatRuntimeToolArgumentError(cfg, error, required, isAsyncRecipe);
979
+ }
980
+ },
981
+ };
982
+ }