@llblab/pi-actors 0.33.0 → 0.34.1

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 (155) hide show
  1. package/AGENTS.md +22 -11
  2. package/BACKLOG.md +3 -34
  3. package/CHANGELOG.md +14 -0
  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/{actor-recipe-context.d.ts → recipes-context.d.ts} +3 -2
  21. package/dist/lib/{actor-recipe-context.js → recipes-context.js} +2 -1
  22. package/dist/lib/{recipe-discovery.d.ts → recipes-discovery.d.ts} +8 -8
  23. package/dist/lib/{recipe-discovery.js → recipes-discovery.js} +6 -6
  24. package/dist/lib/registry.d.ts +1 -1
  25. package/dist/lib/registry.js +22 -22
  26. package/dist/lib/{actor-rooms.d.ts → rooms.d.ts} +4 -4
  27. package/dist/lib/{actor-rooms.js → rooms.js} +3 -3
  28. package/dist/lib/runs-artifacts.d.ts +21 -0
  29. package/dist/lib/runs-artifacts.js +68 -0
  30. package/dist/lib/runs-control.d.ts +13 -0
  31. package/dist/lib/runs-control.js +59 -0
  32. package/dist/lib/runs-identity.d.ts +5 -0
  33. package/dist/lib/runs-identity.js +11 -0
  34. package/dist/lib/runs-index.d.ts +18 -0
  35. package/dist/lib/runs-index.js +119 -0
  36. package/dist/lib/runs-mailbox.d.ts +25 -0
  37. package/dist/lib/runs-mailbox.js +146 -0
  38. package/dist/lib/runs-messages.d.ts +15 -0
  39. package/dist/lib/runs-messages.js +179 -0
  40. package/dist/lib/runs-outbox.d.ts +41 -0
  41. package/dist/lib/runs-outbox.js +87 -0
  42. package/dist/lib/runs-process.d.ts +7 -0
  43. package/dist/lib/runs-process.js +36 -0
  44. package/dist/lib/runs-retention.d.ts +8 -0
  45. package/dist/lib/runs-retention.js +56 -0
  46. package/dist/lib/runs-start.d.ts +9 -0
  47. package/dist/lib/runs-start.js +67 -0
  48. package/dist/lib/runs-status.d.ts +8 -0
  49. package/dist/lib/runs-status.js +60 -0
  50. package/dist/lib/runtime.d.ts +2 -2
  51. package/dist/lib/runtime.js +5 -5
  52. package/dist/lib/schema.d.ts +9 -0
  53. package/dist/lib/schema.js +40 -0
  54. package/dist/lib/tools-access.d.ts +19 -0
  55. package/dist/lib/tools-access.js +44 -0
  56. package/dist/lib/tools-inspect.d.ts +11 -0
  57. package/dist/lib/tools-inspect.js +534 -0
  58. package/dist/lib/tools-local.d.ts +14 -0
  59. package/dist/lib/tools-local.js +156 -0
  60. package/dist/lib/tools-mailbox.d.ts +8 -0
  61. package/dist/lib/tools-mailbox.js +48 -0
  62. package/dist/lib/tools-message.d.ts +9 -0
  63. package/dist/lib/tools-message.js +428 -0
  64. package/dist/lib/tools-register.d.ts +18 -0
  65. package/dist/lib/tools-register.js +40 -0
  66. package/dist/lib/tools-response.d.ts +21 -0
  67. package/dist/lib/tools-response.js +219 -0
  68. package/dist/lib/tools-spawn.d.ts +12 -0
  69. package/dist/lib/tools-spawn.js +159 -0
  70. package/dist/lib/tools.d.ts +8 -39
  71. package/dist/lib/tools.js +11 -1490
  72. package/dist/recipes/utility-coordinator-lock-snapshot.json +1 -1
  73. package/dist/scripts/actor-worker.mjs +190 -7
  74. package/dist/scripts/async-runner.mjs +171 -8
  75. package/dist/scripts/build-dist.mjs +1 -1
  76. package/dist/scripts/conformance.mjs +3 -3
  77. package/dist/scripts/coordinator.mjs +733 -11
  78. package/dist/scripts/locker.mjs +345 -16
  79. package/dist/scripts/recipe-utils.mjs +478 -14
  80. package/dist/scripts/validate-recipe.mjs +110 -10
  81. package/dist/skills/actors/SKILL.md +1 -1
  82. package/dist/skills/swarm/SKILL.md +1 -1
  83. package/docs/recipe-library.md +1 -1
  84. package/docs/template-recipes.md +1 -1
  85. package/index.ts +11 -18
  86. package/lib/async-runs.ts +124 -1016
  87. package/lib/command-templates.ts +3 -3
  88. package/lib/config.ts +7 -7
  89. package/lib/{output.ts → execution-output.ts} +3 -3
  90. package/lib/execution.ts +5 -1
  91. package/lib/{actor-inspector-tui.ts → inspector.ts} +4 -3
  92. package/lib/mailbox-loop.ts +3 -3
  93. package/lib/{actor-messages.ts → messages.ts} +3 -3
  94. package/lib/{actor-recipe-context.ts → recipes-context.ts} +3 -2
  95. package/lib/{recipe-discovery.ts → recipes-discovery.ts} +19 -19
  96. package/lib/{recipe-usage.ts → recipes-usage.ts} +2 -2
  97. package/lib/registry.ts +37 -24
  98. package/lib/{actor-rooms.ts → rooms.ts} +4 -4
  99. package/lib/runs-artifacts.ts +96 -0
  100. package/lib/runs-control.ts +85 -0
  101. package/lib/runs-identity.ts +14 -0
  102. package/lib/runs-index.ts +156 -0
  103. package/lib/runs-mailbox.ts +208 -0
  104. package/lib/runs-messages.ts +252 -0
  105. package/lib/runs-outbox.ts +144 -0
  106. package/lib/runs-process.ts +51 -0
  107. package/lib/runs-retention.ts +77 -0
  108. package/lib/runs-start.ts +98 -0
  109. package/lib/runs-status.ts +78 -0
  110. package/lib/runtime.ts +6 -6
  111. package/lib/schema.ts +55 -0
  112. package/lib/tools-access.ts +68 -0
  113. package/lib/tools-inspect.ts +735 -0
  114. package/lib/tools-local.ts +234 -0
  115. package/lib/tools-mailbox.ts +56 -0
  116. package/lib/tools-message.ts +569 -0
  117. package/lib/tools-register.ts +64 -0
  118. package/lib/tools-response.ts +267 -0
  119. package/lib/tools-spawn.ts +240 -0
  120. package/lib/tools.ts +20 -2040
  121. package/package.json +1 -1
  122. package/recipes/utility-coordinator-lock-snapshot.json +1 -1
  123. package/scripts/actor-worker.mjs +190 -7
  124. package/scripts/async-runner.mjs +171 -8
  125. package/scripts/build-dist.mjs +1 -1
  126. package/scripts/conformance.mjs +3 -3
  127. package/scripts/coordinator.mjs +733 -11
  128. package/scripts/locker.mjs +345 -16
  129. package/scripts/recipe-utils.mjs +478 -14
  130. package/scripts/validate-recipe.mjs +110 -10
  131. package/skills/actors/SKILL.md +1 -1
  132. package/skills/swarm/SKILL.md +1 -1
  133. package/dist/lib/actor-worker.d.ts +0 -16
  134. package/dist/lib/actor-worker.js +0 -134
  135. package/dist/lib/async-runner.d.ts +0 -5
  136. package/dist/lib/async-runner.js +0 -134
  137. package/dist/lib/coordinator.d.ts +0 -5
  138. package/dist/lib/coordinator.js +0 -660
  139. package/dist/lib/locker.d.ts +0 -5
  140. package/dist/lib/locker.js +0 -310
  141. package/dist/lib/recipe-utils.d.ts +0 -5
  142. package/dist/lib/recipe-utils.js +0 -385
  143. package/dist/lib/validate-recipe.d.ts +0 -6
  144. package/dist/lib/validate-recipe.js +0 -104
  145. package/lib/actor-worker.ts +0 -174
  146. package/lib/async-runner.ts +0 -173
  147. package/lib/coordinator.ts +0 -742
  148. package/lib/locker.ts +0 -340
  149. package/lib/recipe-utils.ts +0 -486
  150. package/lib/validate-recipe.ts +0 -110
  151. /package/dist/lib/{recipe-references.d.ts → recipes-references.d.ts} +0 -0
  152. /package/dist/lib/{recipe-references.js → recipes-references.js} +0 -0
  153. /package/dist/lib/{recipe-usage.d.ts → recipes-usage.d.ts} +0 -0
  154. /package/dist/lib/{recipe-usage.js → recipes-usage.js} +0 -0
  155. /package/lib/{recipe-references.ts → recipes-references.ts} +0 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Public register_tool behavior
3
+ * Zones: runtime tool registration, tool registry mutation, register_tool schema
4
+ * Owns the public register_tool definition that persists local agent capabilities
5
+ */
6
+ import * as Prompts from "./prompts.js";
7
+ import * as Registry from "./registry.js";
8
+ import * as Schema from "./schema.js";
9
+ const stringSchema = Schema.stringSchema;
10
+ const booleanSchema = Schema.booleanSchema;
11
+ const nullSchema = Schema.nullSchema;
12
+ const arraySchema = Schema.arraySchema;
13
+ const unionSchema = Schema.unionSchema;
14
+ const objectSchema = Schema.objectSchema;
15
+ const looseObjectSchema = Schema.looseObjectSchema;
16
+ export function createRegisterToolDefinition(deps) {
17
+ return {
18
+ name: "register_tool",
19
+ label: "Register Tool",
20
+ description: Prompts.REGISTER_TOOL_DESCRIPTION,
21
+ promptSnippet: Prompts.REGISTER_TOOL_PROMPT_SNIPPET,
22
+ promptGuidelines: Prompts.REGISTER_TOOL_GUIDELINES,
23
+ parameters: objectSchema({
24
+ args: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.args),
25
+ async: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.async),
26
+ description: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description),
27
+ name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
28
+ state_dir: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.state_dir),
29
+ template: unionSchema([
30
+ stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
31
+ looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
32
+ arraySchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateArray),
33
+ nullSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateNull),
34
+ ]),
35
+ update: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.update),
36
+ values: looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.values),
37
+ }, []),
38
+ execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => Registry.executeRegisterTool(params, ctx, deps),
39
+ };
40
+ }
@@ -0,0 +1,21 @@
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
+ export declare function asRecord(value: unknown): Record<string, unknown>;
7
+ export declare function jsonText(value: unknown): string;
8
+ export declare function compactPreview(value: unknown, maxLength?: number): string | undefined;
9
+ export declare function compactNextActions(actions: string[]): string;
10
+ export declare function actorRunNextActions(run: unknown): string[];
11
+ export declare function compactAsyncRunStatus(value: unknown): string;
12
+ export declare function compactInboxMessages(messages: Array<Record<string, unknown>>, emptyLabel: string): string;
13
+ export declare function compactBranchInbox(messages: Array<Record<string, unknown>>): string;
14
+ export declare function compactRunMailbox(run: string, mailbox: Record<string, unknown>, messages: Array<Record<string, unknown>>): string;
15
+ export declare function artifactNextActions(run: unknown, artifacts: Record<string, unknown>): string[];
16
+ export declare function maybeJsonText(value: unknown, verbose: boolean | undefined, compact: string): string;
17
+ export declare function compactRecipeImports(summary: Record<string, unknown>): string;
18
+ export declare function compactRecipeDoctor(summary: Record<string, unknown>): string;
19
+ export declare function recipeRegistryNextActions(summary: Record<string, unknown>, view: string): string[];
20
+ export declare function compactRecipeRegistry(summary: Record<string, unknown>): string;
21
+ export declare const DEFAULT_INSPECT_LINES = 40;
@@ -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,19 +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>;
6
+ import type { RuntimeToolContext } from "./tools-local.ts";
7
+ import * as ToolsRegister from "./tools-register.ts";
8
+ export type RegisterToolRuntimeDeps<TContext> = ToolsRegister.RegisterToolRuntimeDeps<TContext>;
11
9
  export interface ActorToolDefinition {
12
10
  name: string;
13
11
  execute?: (...args: never[]) => unknown;
14
12
  [key: string]: unknown;
15
13
  }
16
- export interface CoreActorToolDefinitionDeps<TContext extends AsyncRunToolContext> {
14
+ export interface CoreActorToolDefinitionDeps<TContext extends RuntimeToolContext> {
17
15
  configPath: string;
18
16
  getActiveTools: () => string[];
19
17
  getRuntimeTool: (name: string) => unknown;
@@ -21,33 +19,4 @@ export interface CoreActorToolDefinitionDeps<TContext extends AsyncRunToolContex
21
19
  setActiveTools: (toolNames: string[]) => void;
22
20
  }
23
21
  export declare const RESERVED_TOOL_NAMES: Set<string>;
24
- type JsonSchema = Record<string, unknown>;
25
- export declare function createRegisterToolDefinition<TContext>(deps: RegisterToolRuntimeDeps<TContext>): {
26
- name: string;
27
- label: string;
28
- description: string;
29
- promptSnippet: string;
30
- promptGuidelines: string[];
31
- parameters: JsonSchema;
32
- execute: (_toolCallId: string, params: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: TContext) => Promise<Registry.RegisterToolResult>;
33
- };
34
- export interface AsyncRunToolContext {
35
- cwd: string;
36
- sessionManager?: {
37
- getSessionId?: () => string;
38
- };
39
- }
40
- export declare function createSpawnToolDefinition<TContext extends AsyncRunToolContext>(): any;
41
- export interface InspectToolDeps<TContext = unknown> {
42
- getTool?: (name: string) => any | undefined;
43
- packagedRecipeRoot?: string;
44
- recipeRoot?: string;
45
- }
46
- export declare function createInspectToolDefinition<TContext = unknown>(deps?: InspectToolDeps<TContext>): any;
47
- export interface ActorMessageToolDeps<TContext = unknown> {
48
- getTool?: (name: string) => any | undefined;
49
- }
50
- export declare function createActorMessageToolDefinition<TContext = unknown>(deps?: ActorMessageToolDeps<TContext>): any;
51
- export declare function createCoreActorToolDefinitions<TContext extends AsyncRunToolContext>(deps: CoreActorToolDefinitionDeps<TContext>): ActorToolDefinition[];
52
- export declare function createRuntimeToolDefinition(cfg: RegisteredTool, exec: Execution.RegisteredToolExec): any;
53
- export {};
22
+ export declare function createCoreActorToolDefinitions<TContext extends RuntimeToolContext>(deps: CoreActorToolDefinitionDeps<TContext>): ActorToolDefinition[];