@llblab/pi-actors 0.32.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.
Files changed (162) hide show
  1. package/AGENTS.md +23 -11
  2. package/BACKLOG.md +2 -30
  3. package/CHANGELOG.md +30 -1
  4. package/dist/index.js +10 -10
  5. package/dist/lib/async-runs.d.ts +23 -75
  6. package/dist/lib/async-runs.js +49 -801
  7. package/dist/lib/command-templates.d.ts +3 -3
  8. package/dist/lib/command-templates.js +3 -3
  9. package/dist/lib/config.d.ts +2 -2
  10. package/dist/lib/config.js +5 -5
  11. package/dist/lib/{output.d.ts → execution-output.d.ts} +3 -3
  12. package/dist/lib/{output.js → execution-output.js} +3 -3
  13. package/dist/lib/execution.js +1 -1
  14. package/dist/lib/{actor-inspector-tui.d.ts → inspector.d.ts} +3 -2
  15. package/dist/lib/{actor-inspector-tui.js → inspector.js} +3 -2
  16. package/dist/lib/mailbox-loop.d.ts +3 -3
  17. package/dist/lib/mailbox-loop.js +3 -3
  18. package/dist/lib/{actor-messages.d.ts → messages.d.ts} +3 -3
  19. package/dist/lib/{actor-messages.js → messages.js} +3 -3
  20. package/dist/lib/paths.d.ts +1 -1
  21. package/dist/lib/paths.js +3 -3
  22. package/dist/lib/{actor-recipe-context.d.ts → recipes-context.d.ts} +3 -2
  23. package/dist/lib/{actor-recipe-context.js → recipes-context.js} +2 -1
  24. package/dist/lib/{recipe-discovery.d.ts → recipes-discovery.d.ts} +9 -9
  25. package/dist/lib/{recipe-discovery.js → recipes-discovery.js} +17 -17
  26. package/dist/lib/registry.d.ts +1 -1
  27. package/dist/lib/registry.js +22 -22
  28. package/dist/lib/{actor-rooms.d.ts → rooms.d.ts} +4 -4
  29. package/dist/lib/{actor-rooms.js → rooms.js} +3 -3
  30. package/dist/lib/runs-artifacts.d.ts +21 -0
  31. package/dist/lib/runs-artifacts.js +68 -0
  32. package/dist/lib/runs-control.d.ts +13 -0
  33. package/dist/lib/runs-control.js +59 -0
  34. package/dist/lib/runs-identity.d.ts +5 -0
  35. package/dist/lib/runs-identity.js +11 -0
  36. package/dist/lib/runs-index.d.ts +18 -0
  37. package/dist/lib/runs-index.js +119 -0
  38. package/dist/lib/runs-mailbox.d.ts +25 -0
  39. package/dist/lib/runs-mailbox.js +146 -0
  40. package/dist/lib/runs-messages.d.ts +15 -0
  41. package/dist/lib/runs-messages.js +179 -0
  42. package/dist/lib/runs-outbox.d.ts +41 -0
  43. package/dist/lib/runs-outbox.js +87 -0
  44. package/dist/lib/runs-process.d.ts +7 -0
  45. package/dist/lib/runs-process.js +36 -0
  46. package/dist/lib/runs-retention.d.ts +8 -0
  47. package/dist/lib/runs-retention.js +56 -0
  48. package/dist/lib/runs-start.d.ts +9 -0
  49. package/dist/lib/runs-start.js +67 -0
  50. package/dist/lib/runs-status.d.ts +8 -0
  51. package/dist/lib/runs-status.js +60 -0
  52. package/dist/lib/runtime.d.ts +2 -2
  53. package/dist/lib/runtime.js +5 -5
  54. package/dist/lib/schema.d.ts +9 -0
  55. package/dist/lib/schema.js +40 -0
  56. package/dist/lib/tools-access.d.ts +19 -0
  57. package/dist/lib/tools-access.js +44 -0
  58. package/dist/lib/tools-inspect.d.ts +11 -0
  59. package/dist/lib/tools-inspect.js +534 -0
  60. package/dist/lib/tools-local.d.ts +14 -0
  61. package/dist/lib/tools-local.js +156 -0
  62. package/dist/lib/tools-mailbox.d.ts +8 -0
  63. package/dist/lib/tools-mailbox.js +48 -0
  64. package/dist/lib/tools-message.d.ts +9 -0
  65. package/dist/lib/tools-message.js +346 -0
  66. package/dist/lib/tools-register.d.ts +18 -0
  67. package/dist/lib/tools-register.js +40 -0
  68. package/dist/lib/tools-response.d.ts +21 -0
  69. package/dist/lib/tools-response.js +219 -0
  70. package/dist/lib/tools-spawn.d.ts +12 -0
  71. package/dist/lib/tools-spawn.js +159 -0
  72. package/dist/lib/tools.d.ts +13 -39
  73. package/dist/lib/tools.js +11 -1497
  74. package/dist/recipes/utility-coordinator-lock-snapshot.json +1 -1
  75. package/dist/scripts/actor-worker.mjs +186 -7
  76. package/dist/scripts/async-runner.mjs +171 -8
  77. package/dist/scripts/build-dist.mjs +1 -1
  78. package/dist/scripts/conformance.mjs +34 -18
  79. package/dist/scripts/coordinator.mjs +733 -11
  80. package/dist/scripts/locker.mjs +345 -16
  81. package/dist/scripts/recipe-utils.mjs +478 -14
  82. package/dist/scripts/validate-recipe.mjs +110 -10
  83. package/dist/skills/actors/SKILL.md +2 -2
  84. package/dist/skills/swarm/SKILL.md +2 -2
  85. package/docs/recipe-library.md +1 -1
  86. package/docs/template-recipes.md +1 -1
  87. package/docs/tool-registry.md +3 -3
  88. package/index.ts +12 -19
  89. package/lib/async-runs.ts +124 -1016
  90. package/lib/command-templates.ts +3 -3
  91. package/lib/config.ts +7 -7
  92. package/lib/{output.ts → execution-output.ts} +3 -3
  93. package/lib/execution.ts +5 -1
  94. package/lib/{actor-inspector-tui.ts → inspector.ts} +4 -3
  95. package/lib/mailbox-loop.ts +3 -3
  96. package/lib/{actor-messages.ts → messages.ts} +3 -3
  97. package/lib/paths.ts +3 -3
  98. package/lib/{actor-recipe-context.ts → recipes-context.ts} +3 -2
  99. package/lib/{recipe-discovery.ts → recipes-discovery.ts} +30 -30
  100. package/lib/{recipe-usage.ts → recipes-usage.ts} +2 -2
  101. package/lib/registry.ts +37 -24
  102. package/lib/{actor-rooms.ts → rooms.ts} +4 -4
  103. package/lib/runs-artifacts.ts +96 -0
  104. package/lib/runs-control.ts +85 -0
  105. package/lib/runs-identity.ts +14 -0
  106. package/lib/runs-index.ts +156 -0
  107. package/lib/runs-mailbox.ts +208 -0
  108. package/lib/runs-messages.ts +252 -0
  109. package/lib/runs-outbox.ts +144 -0
  110. package/lib/runs-process.ts +51 -0
  111. package/lib/runs-retention.ts +77 -0
  112. package/lib/runs-start.ts +98 -0
  113. package/lib/runs-status.ts +78 -0
  114. package/lib/runtime.ts +6 -6
  115. package/lib/schema.ts +55 -0
  116. package/lib/tools-access.ts +68 -0
  117. package/lib/tools-inspect.ts +735 -0
  118. package/lib/tools-local.ts +234 -0
  119. package/lib/tools-mailbox.ts +56 -0
  120. package/lib/tools-message.ts +483 -0
  121. package/lib/tools-register.ts +64 -0
  122. package/lib/tools-response.ts +267 -0
  123. package/lib/tools-spawn.ts +240 -0
  124. package/lib/tools.ts +26 -2051
  125. package/package.json +1 -1
  126. package/recipes/utility-coordinator-lock-snapshot.json +1 -1
  127. package/scripts/actor-worker.mjs +186 -7
  128. package/scripts/async-runner.mjs +171 -8
  129. package/scripts/build-dist.mjs +1 -1
  130. package/scripts/conformance.mjs +34 -18
  131. package/scripts/coordinator.mjs +733 -11
  132. package/scripts/locker.mjs +345 -16
  133. package/scripts/recipe-utils.mjs +478 -14
  134. package/scripts/validate-recipe.mjs +110 -10
  135. package/skills/actors/SKILL.md +2 -2
  136. package/skills/swarm/SKILL.md +2 -2
  137. package/dist/lib/actor-worker.d.ts +0 -16
  138. package/dist/lib/actor-worker.js +0 -134
  139. package/dist/lib/async-runner.d.ts +0 -5
  140. package/dist/lib/async-runner.js +0 -134
  141. package/dist/lib/conformance.d.ts +0 -12
  142. package/dist/lib/conformance.js +0 -28
  143. package/dist/lib/coordinator.d.ts +0 -5
  144. package/dist/lib/coordinator.js +0 -660
  145. package/dist/lib/locker.d.ts +0 -5
  146. package/dist/lib/locker.js +0 -310
  147. package/dist/lib/recipe-utils.d.ts +0 -5
  148. package/dist/lib/recipe-utils.js +0 -385
  149. package/dist/lib/validate-recipe.d.ts +0 -6
  150. package/dist/lib/validate-recipe.js +0 -104
  151. package/lib/actor-worker.ts +0 -174
  152. package/lib/async-runner.ts +0 -173
  153. package/lib/conformance.ts +0 -46
  154. package/lib/coordinator.ts +0 -742
  155. package/lib/locker.ts +0 -340
  156. package/lib/recipe-utils.ts +0 -486
  157. package/lib/validate-recipe.ts +0 -110
  158. /package/dist/lib/{recipe-references.d.ts → recipes-references.d.ts} +0 -0
  159. /package/dist/lib/{recipe-references.js → recipes-references.js} +0 -0
  160. /package/dist/lib/{recipe-usage.d.ts → recipes-usage.d.ts} +0 -0
  161. /package/dist/lib/{recipe-usage.js → recipes-usage.js} +0 -0
  162. /package/lib/{recipe-references.ts → recipes-references.ts} +0 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Public tool response formatting
3
+ * Zones: compact text summaries, verbose JSON switching, next-action rendering
4
+ * Owns model-facing response helpers shared by public tool execution paths
5
+ */
6
+ import * as Limits from "./limits.js";
7
+ import * as ToolsMailbox from "./tools-mailbox.js";
8
+ export function asRecord(value) {
9
+ return value && typeof value === "object" && !Array.isArray(value)
10
+ ? value
11
+ : {};
12
+ }
13
+ export function jsonText(value) {
14
+ return `\n${JSON.stringify(value, null, 2)}`;
15
+ }
16
+ export function compactPreview(value, maxLength = Limits.COMPACT_PREVIEW_CHARS) {
17
+ if (value === undefined)
18
+ return undefined;
19
+ const text = typeof value === "string" ? value : JSON.stringify(value, undefined, 0);
20
+ const compact = text.replaceAll(/\s+/g, "_");
21
+ return compact.length > maxLength
22
+ ? `${compact.slice(0, Math.max(0, maxLength - 1))}…`
23
+ : compact;
24
+ }
25
+ export function compactNextActions(actions) {
26
+ return actions.length
27
+ ? ` next=${actions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
28
+ : "";
29
+ }
30
+ function formatFailureCount(value) {
31
+ return Array.isArray(value) ? value.length : undefined;
32
+ }
33
+ export function actorRunNextActions(run) {
34
+ const id = String(run ?? "").trim();
35
+ if (!id)
36
+ return [];
37
+ return [
38
+ `inspect target=run:${id} view=status`,
39
+ `inspect target=run:${id} view=messages`,
40
+ `message to=run:${id} type=<actor.action>`,
41
+ ];
42
+ }
43
+ export function compactAsyncRunStatus(value) {
44
+ const status = asRecord(value);
45
+ const progress = asRecord(status.progress);
46
+ const result = asRecord(status.result);
47
+ const run = String(status.run ?? "<unknown>");
48
+ const tokens = [`run=${run}`, `status=${String(status.status ?? "unknown")}`];
49
+ if (status.tool)
50
+ tokens.push(`tool=${String(status.tool)}`);
51
+ if (status.recipe)
52
+ tokens.push(`recipe=${String(status.recipe)}`);
53
+ if (status.retire_when)
54
+ tokens.push(`retire_when=${String(status.retire_when)}`);
55
+ if (Number(status.pid) > 0)
56
+ tokens.push(`pid=${Number(status.pid)}`);
57
+ if (progress.phase && progress.phase !== status.status)
58
+ tokens.push(`phase=${String(progress.phase)}`);
59
+ if (Number(progress.activeSubagents) > 0)
60
+ tokens.push(`active=${Number(progress.activeSubagents)}`);
61
+ if (Number(progress.completed) > 0)
62
+ tokens.push(`completed=${Number(progress.completed)}`);
63
+ const failures = formatFailureCount(progress.failures);
64
+ if (failures !== undefined && failures > 0)
65
+ tokens.push(`failures=${failures}`);
66
+ if (result.code !== undefined)
67
+ tokens.push(`code=${String(result.code)}`);
68
+ if (result.killed === true)
69
+ tokens.push("killed=true");
70
+ const draftRecipe = status.draft_recipe;
71
+ if (draftRecipe)
72
+ tokens.push(`draft_recipe=${String(draftRecipe)}`);
73
+ const nextActions = actorRunNextActions(run);
74
+ if (nextActions.length > 0)
75
+ tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
76
+ return `\n${tokens.join(" ")}`;
77
+ }
78
+ export function compactInboxMessages(messages, emptyLabel) {
79
+ if (messages.length === 0)
80
+ return `\n(no ${emptyLabel} messages)`;
81
+ return `\n${messages
82
+ .map((message) => [
83
+ ...(message.id ? [`id=${String(message.id)}`] : []),
84
+ `status=${String(message.status ?? "")}`,
85
+ `type=${String(message.type ?? "")}`,
86
+ `from=${String(message.from ?? "")}`,
87
+ `to=${String(message.to ?? "")}`,
88
+ ...(message.queued_at
89
+ ? [`queued_at=${String(message.queued_at)}`]
90
+ : []),
91
+ ...(message.sent_at ? [`sent_at=${String(message.sent_at)}`] : []),
92
+ ...(message.claimed_at
93
+ ? [`claimed_at=${String(message.claimed_at)}`]
94
+ : []),
95
+ ...(message.handled_at
96
+ ? [`handled_at=${String(message.handled_at)}`]
97
+ : []),
98
+ ...(message.failed_at
99
+ ? [`failed_at=${String(message.failed_at)}`]
100
+ : []),
101
+ ].join(" "))
102
+ .join("\n")}`;
103
+ }
104
+ export function compactBranchInbox(messages) {
105
+ return compactInboxMessages(messages, "branch inbox");
106
+ }
107
+ export function compactRunMailbox(run, mailbox, messages) {
108
+ const normalized = ToolsMailbox.normalizeMailboxContracts(mailbox);
109
+ return `\nrun=${run} accepts=${ToolsMailbox.mailboxTypes(normalized.accepts).join(",")} emits=${ToolsMailbox.mailboxTypes(normalized.emits).join(",")}${compactInboxMessages(messages, "run inbox")}`;
110
+ }
111
+ export function artifactNextActions(run, artifacts) {
112
+ const id = String(run ?? "").trim();
113
+ if (!id || Object.keys(artifacts).length === 0)
114
+ return [];
115
+ return [
116
+ `inspect target=run:${id} view=artifacts verbose=true`,
117
+ `inspect target=run:${id} view=messages`,
118
+ ];
119
+ }
120
+ export function maybeJsonText(value, verbose, compact) {
121
+ return verbose ? jsonText(value) : compact;
122
+ }
123
+ export function compactRecipeImports(summary) {
124
+ const active = Array.isArray(summary.active)
125
+ ? summary.active
126
+ : [];
127
+ const lines = active.flatMap((entry) => {
128
+ const imports = asRecord(entry.imports);
129
+ return Object.entries(imports).map(([alias, value]) => {
130
+ const binding = typeof value === "string" ? { from: value } : asRecord(value);
131
+ return `recipe=${String(entry.id ?? "<unknown>")} alias=${alias} from=${String(binding.from ?? value)}`;
132
+ });
133
+ });
134
+ return lines.length ? `\n${lines.join("\n")}` : "\n(no recipe imports)";
135
+ }
136
+ export function compactRecipeDoctor(summary) {
137
+ const details = Array.isArray(summary.diagnostic_details)
138
+ ? summary.diagnostic_details
139
+ : [];
140
+ const remediations = Array.isArray(summary.remediations)
141
+ ? summary.remediations
142
+ : [];
143
+ const recommendations = Array.isArray(summary.recommendations)
144
+ ? summary.recommendations
145
+ : [];
146
+ const counts = { error: 0, info: 0, warning: 0 };
147
+ for (const detail of details) {
148
+ const severity = String(detail.severity ?? "info");
149
+ if (severity === "error" || severity === "warning" || severity === "info")
150
+ counts[severity] += 1;
151
+ }
152
+ const topAction = asRecord(summary.top_action);
153
+ const lines = [
154
+ `recipes doctor errors=${counts.error} warnings=${counts.warning} info=${counts.info} actions=${remediations.length} recommendations=${recommendations.length}`,
155
+ ];
156
+ if (Object.keys(topAction).length > 0) {
157
+ const action = compactPreview(topAction.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
158
+ lines.push(`top severity=${String(topAction.severity ?? "info")} kind=${String(topAction.kind ?? "inspect")} id=${String(topAction.id ?? "root")} action=${action ?? "inspect"}`);
159
+ }
160
+ for (const item of remediations.slice(0, 8)) {
161
+ const action = compactPreview(item.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
162
+ const blocked = item.blocked_fallback
163
+ ? ` blocked=${compactPreview(item.blocked_fallback, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
164
+ : "";
165
+ lines.push(`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`);
166
+ }
167
+ const nextActions = Array.isArray(summary.next_actions)
168
+ ? summary.next_actions
169
+ : [];
170
+ if (nextActions.length > 0)
171
+ lines[0] = `${lines[0]}${compactNextActions(nextActions)}`;
172
+ return `\n${lines.join("\n")}`;
173
+ }
174
+ export function recipeRegistryNextActions(summary, view) {
175
+ const actions = [];
176
+ const drafts = Array.isArray(summary.drafts)
177
+ ? summary.drafts
178
+ : [];
179
+ const invalid = Array.isArray(summary.invalid) ? summary.invalid.length : 0;
180
+ const diagnostics = Array.isArray(summary.diagnostics)
181
+ ? summary.diagnostics.length
182
+ : 0;
183
+ const topAction = asRecord(summary.top_action);
184
+ if (view !== "doctor" && (invalid > 0 || diagnostics > 0)) {
185
+ actions.push("inspect target=recipes view=doctor");
186
+ }
187
+ if (view === "doctor" && typeof topAction.action === "string") {
188
+ actions.push(String(topAction.action));
189
+ }
190
+ if (drafts.length > 0) {
191
+ actions.push("inspect target=recipes view=summary verbose=true");
192
+ const firstPath = typeof drafts[0]?.path === "string" ? drafts[0].path : undefined;
193
+ if (firstPath)
194
+ actions.push(`spawn file=${firstPath}`);
195
+ }
196
+ return [...new Set(actions)].slice(0, 4);
197
+ }
198
+ export function compactRecipeRegistry(summary) {
199
+ const active = Array.isArray(summary.active) ? summary.active.length : 0;
200
+ const shadowed = Array.isArray(summary.shadowed)
201
+ ? summary.shadowed.length
202
+ : 0;
203
+ const invalid = Array.isArray(summary.invalid) ? summary.invalid.length : 0;
204
+ const disabled = Array.isArray(summary.disabled)
205
+ ? summary.disabled.length
206
+ : 0;
207
+ const diagnostics = Array.isArray(summary.diagnostics)
208
+ ? summary.diagnostics.length
209
+ : 0;
210
+ const drafts = Array.isArray(summary.drafts) ? summary.drafts.length : 0;
211
+ const recommendations = Array.isArray(summary.recommendations)
212
+ ? summary.recommendations.length
213
+ : 0;
214
+ const nextActions = Array.isArray(summary.next_actions)
215
+ ? summary.next_actions
216
+ : [];
217
+ return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
218
+ }
219
+ export const DEFAULT_INSPECT_LINES = Limits.DEFAULT_INSPECT_LINES;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Public spawn tool behavior
3
+ * Zones: actor launch, draft recipe capture, launch diagnostics
4
+ * Owns the public spawn execution path for run-backed actors
5
+ */
6
+ export interface SpawnToolContext {
7
+ cwd: string;
8
+ sessionManager?: {
9
+ getSessionId?: () => string;
10
+ };
11
+ }
12
+ export declare function createSpawnToolDefinition<TContext extends SpawnToolContext>(): any;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Public spawn tool behavior
3
+ * Zones: actor launch, draft recipe capture, launch diagnostics
4
+ * Owns the public spawn execution path for run-backed actors
5
+ */
6
+ import { mkdirSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import * as AsyncRuns from "./async-runs.js";
9
+ import * as Messages from "./messages.js";
10
+ import * as Paths from "./paths.js";
11
+ import * as RecipesDiscovery from "./recipes-discovery.js";
12
+ import * as Rooms from "./rooms.js";
13
+ import * as Schema from "./schema.js";
14
+ import * as ToolsResponse from "./tools-response.js";
15
+ const asRecord = ToolsResponse.asRecord;
16
+ const maybeJsonText = ToolsResponse.maybeJsonText;
17
+ function getRunOwnerId(ctx) {
18
+ return ctx.sessionManager?.getSessionId?.();
19
+ }
20
+ function shadowedRecipeLaunchDiagnostic(recipe) {
21
+ if (typeof recipe !== "string" ||
22
+ recipe.includes("/") ||
23
+ recipe.includes("~"))
24
+ return undefined;
25
+ const discovery = RecipesDiscovery.discoverRecipeSources([
26
+ { root: Paths.getRecipeRoot(), defaultTool: true, mutableUsage: true },
27
+ { root: Paths.getPackagedRecipeRoot(), defaultTool: false },
28
+ ]);
29
+ return RecipesDiscovery.getShadowedLaunchDiagnostic(discovery, recipe);
30
+ }
31
+ function draftRecipeName(run) {
32
+ return `${run.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "spawn"}.json`;
33
+ }
34
+ function draftRecipeDefaults(values) {
35
+ const ignored = new Set([
36
+ "actor_address",
37
+ "communication_file",
38
+ "default_room",
39
+ "run_id",
40
+ "state_dir",
41
+ ]);
42
+ const defaults = Object.fromEntries(Object.entries(values).filter(([key]) => !ignored.has(key)));
43
+ return Object.keys(defaults).length > 0 ? defaults : undefined;
44
+ }
45
+ function writeSpawnDraftRecipe(input, meta) {
46
+ if (process.env.NODE_TEST_CONTEXT &&
47
+ process.env.PI_ACTORS_ENABLE_SPAWN_DRAFTS_IN_TEST !== "1")
48
+ return undefined;
49
+ if (input.template === undefined ||
50
+ input.file !== undefined ||
51
+ input.recipe !== undefined)
52
+ return undefined;
53
+ const root = Paths.getRecipeDraftRoot();
54
+ mkdirSync(root, { recursive: true });
55
+ const path = join(root, draftRecipeName(String(meta.run)));
56
+ const defaults = draftRecipeDefaults(meta.values);
57
+ const recipe = {
58
+ async: true,
59
+ description: `Draft recipe captured from spawn run ${String(meta.run)}`,
60
+ ...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
61
+ ...(defaults ? { defaults } : {}),
62
+ template: input.template,
63
+ };
64
+ writeFileSync(path, `${JSON.stringify(recipe, null, 2)}\n`, { flag: "wx" });
65
+ return path;
66
+ }
67
+ function enhanceSpawnRecipeError(error, recipe) {
68
+ const diagnostic = shadowedRecipeLaunchDiagnostic(recipe);
69
+ if (!diagnostic)
70
+ return error instanceof Error ? error : new Error(String(error));
71
+ const original = error instanceof Error ? error.message : String(error);
72
+ return Object.assign(new Error(`${original} reason=${diagnostic.reason} active_path=${diagnostic.active_path} blocked_fallback=${diagnostic.blocked_fallback} hint=${diagnostic.hint}`), {
73
+ ...diagnostic,
74
+ original_error: original,
75
+ });
76
+ }
77
+ function runIdFromActorAddress(address) {
78
+ if (!address)
79
+ return undefined;
80
+ const parsed = Messages.parseActorAddress(address);
81
+ if (parsed.kind !== "run" || !parsed.value) {
82
+ throw new Error(`Expected run:<id> actor address, received: ${address}`);
83
+ }
84
+ return parsed.value;
85
+ }
86
+ export function createSpawnToolDefinition() {
87
+ return {
88
+ name: "spawn",
89
+ label: "Spawn",
90
+ 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.",
91
+ parameters: Schema.objectSchema({
92
+ artifacts: Schema.looseObjectSchema("Optional named artifact paths for the spawned actor."),
93
+ as: Schema.stringSchema("Optional actor address for the spawned run, e.g. run:<id>."),
94
+ file: Schema.stringSchema("Optional template recipe JSON file. Bare names resolve under ~/.pi/agent/recipes."),
95
+ recipe: Schema.stringSchema("Alias for file; template recipe JSON file/name to spawn."),
96
+ state_dir: Schema.stringSchema("Optional explicit run state directory."),
97
+ template: Schema.unionSchema([
98
+ Schema.stringSchema("Inline command template string"),
99
+ Schema.arraySchema("Inline command-template sequence or parallel tree"),
100
+ Schema.looseObjectSchema("Inline command-template object with flags such as parallel, repeat, retry, failure, and nested template."),
101
+ ]),
102
+ values: Schema.looseObjectSchema("Runtime placeholder values passed to the actor."),
103
+ verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
104
+ }, []),
105
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
106
+ const input = asRecord(params);
107
+ const runId = runIdFromActorAddress(typeof input.as === "string" ? input.as : undefined);
108
+ const recipe = typeof input.file === "string"
109
+ ? input.file
110
+ : typeof input.recipe === "string"
111
+ ? input.recipe
112
+ : undefined;
113
+ let meta;
114
+ try {
115
+ meta = AsyncRuns.startRun({
116
+ file: recipe,
117
+ launch_source: "spawn",
118
+ ownerId: getRunOwnerId(ctx),
119
+ run_id: runId,
120
+ state_dir: typeof input.state_dir === "string" ? input.state_dir : undefined,
121
+ ...(input.template !== undefined
122
+ ? {
123
+ template: input.template,
124
+ }
125
+ : {}),
126
+ values: asRecord(input.values),
127
+ ...(input.artifacts &&
128
+ typeof input.artifacts === "object" &&
129
+ !Array.isArray(input.artifacts)
130
+ ? {
131
+ artifacts: input.artifacts,
132
+ }
133
+ : {}),
134
+ }, ctx.cwd);
135
+ }
136
+ catch (error) {
137
+ throw enhanceSpawnRecipeError(error, recipe);
138
+ }
139
+ const draftRecipe = writeSpawnDraftRecipe(input, meta);
140
+ const nextActions = ToolsResponse.actorRunNextActions(meta.run);
141
+ const details = {
142
+ ...meta,
143
+ ...(draftRecipe ? { draft_recipe: draftRecipe } : {}),
144
+ next_actions: nextActions,
145
+ };
146
+ Rooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
147
+ Rooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
148
+ return {
149
+ content: [
150
+ {
151
+ type: "text",
152
+ text: maybeJsonText(details, input.verbose === true, ToolsResponse.compactAsyncRunStatus(details)),
153
+ },
154
+ ],
155
+ details,
156
+ };
157
+ },
158
+ };
159
+ }
@@ -1,14 +1,17 @@
1
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
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 type { RegisteredTool } from "./config.ts";
7
- import * as Execution from "./execution.ts";
8
- import * as Registry from "./registry.ts";
9
- export type RegisterToolInput = Registry.RegisterToolInput;
10
- export type RegisterToolRuntimeDeps<TContext> = Registry.RegisterToolRuntimeDeps<TContext>;
11
- export interface CoreActorToolDefinitionDeps<TContext extends AsyncRunToolContext> {
6
+ import type { RuntimeToolContext } from "./tools-local.ts";
7
+ import * as ToolsRegister from "./tools-register.ts";
8
+ export type RegisterToolRuntimeDeps<TContext> = ToolsRegister.RegisterToolRuntimeDeps<TContext>;
9
+ export interface ActorToolDefinition {
10
+ name: string;
11
+ execute?: (...args: never[]) => unknown;
12
+ [key: string]: unknown;
13
+ }
14
+ export interface CoreActorToolDefinitionDeps<TContext extends RuntimeToolContext> {
12
15
  configPath: string;
13
16
  getActiveTools: () => string[];
14
17
  getRuntimeTool: (name: string) => unknown;
@@ -16,33 +19,4 @@ export interface CoreActorToolDefinitionDeps<TContext extends AsyncRunToolContex
16
19
  setActiveTools: (toolNames: string[]) => void;
17
20
  }
18
21
  export declare const RESERVED_TOOL_NAMES: Set<string>;
19
- type JsonSchema = Record<string, unknown>;
20
- export declare function createRegisterToolDefinition<TContext>(deps: RegisterToolRuntimeDeps<TContext>): {
21
- name: string;
22
- label: string;
23
- description: string;
24
- promptSnippet: string;
25
- promptGuidelines: string[];
26
- parameters: JsonSchema;
27
- execute: (_toolCallId: string, params: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: TContext) => Promise<Registry.RegisterToolResult>;
28
- };
29
- export interface AsyncRunToolContext {
30
- cwd: string;
31
- sessionManager?: {
32
- getSessionId?: () => string;
33
- };
34
- }
35
- export declare function createSpawnToolDefinition<TContext extends AsyncRunToolContext>(): any;
36
- export interface InspectToolDeps<TContext = unknown> {
37
- getTool?: (name: string) => any | undefined;
38
- packagedRecipeRoot?: string;
39
- recipeRoot?: string;
40
- }
41
- export declare function createInspectToolDefinition<TContext = unknown>(deps?: InspectToolDeps<TContext>): any;
42
- export interface ActorMessageToolDeps<TContext = unknown> {
43
- getTool?: (name: string) => any | undefined;
44
- }
45
- export declare function createActorMessageToolDefinition<TContext = unknown>(deps?: ActorMessageToolDeps<TContext>): any;
46
- export declare function createCoreActorToolDefinitions<TContext extends AsyncRunToolContext>(deps: CoreActorToolDefinitionDeps<TContext>): any[];
47
- export declare function createRuntimeToolDefinition(cfg: RegisteredTool, exec: Execution.RegisteredToolExec): any;
48
- export {};
22
+ export declare function createCoreActorToolDefinitions<TContext extends RuntimeToolContext>(deps: CoreActorToolDefinitionDeps<TContext>): ActorToolDefinition[];