@llblab/pi-actors 0.55.0 → 0.56.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 (188) hide show
  1. package/AGENTS.md +108 -20
  2. package/BACKLOG.md +198 -4
  3. package/CHANGELOG.md +17 -0
  4. package/README.md +31 -1
  5. package/dist/index.js +1 -1
  6. package/dist/lib/async-runs.d.ts +37 -0
  7. package/dist/lib/async-runs.js +131 -62
  8. package/dist/lib/automatic-review-runtime.d.ts +2 -0
  9. package/dist/lib/automatic-review-runtime.js +3 -2
  10. package/dist/lib/command-templates.d.ts +37 -0
  11. package/dist/lib/command-templates.js +263 -129
  12. package/dist/lib/execution-files.d.ts +32 -0
  13. package/dist/lib/execution-files.js +128 -0
  14. package/dist/lib/execution-output.js +5 -4
  15. package/dist/lib/execution-sessions.d.ts +19 -1
  16. package/dist/lib/execution-sessions.js +62 -26
  17. package/dist/lib/execution.d.ts +4 -1
  18. package/dist/lib/execution.js +6 -1
  19. package/dist/lib/extension-runtime.d.ts +9 -2
  20. package/dist/lib/extension-runtime.js +92 -14
  21. package/dist/lib/file-state.js +21 -2
  22. package/dist/lib/inspector-command.js +2 -2
  23. package/dist/lib/inspector-overlay.d.ts +25 -7
  24. package/dist/lib/inspector-overlay.js +562 -262
  25. package/dist/lib/inspector.d.ts +20 -10
  26. package/dist/lib/inspector.js +80 -91
  27. package/dist/lib/limits.d.ts +1 -0
  28. package/dist/lib/limits.js +1 -0
  29. package/dist/lib/observability.d.ts +15 -1
  30. package/dist/lib/observability.js +216 -99
  31. package/dist/lib/pi.d.ts +5 -4
  32. package/dist/lib/pi.js +64 -10
  33. package/dist/lib/recipe-projection.d.ts +18 -0
  34. package/dist/lib/recipe-projection.js +220 -0
  35. package/dist/lib/recipes-context.d.ts +2 -0
  36. package/dist/lib/recipes-context.js +30 -6
  37. package/dist/lib/registry.js +280 -36
  38. package/dist/lib/run-delivery.d.ts +213 -5
  39. package/dist/lib/run-delivery.js +1909 -40
  40. package/dist/lib/run-supervision.d.ts +107 -0
  41. package/dist/lib/run-supervision.js +412 -0
  42. package/dist/lib/run-ui-runtime.d.ts +9 -2
  43. package/dist/lib/run-ui-runtime.js +283 -60
  44. package/dist/lib/runs-artifacts.d.ts +26 -1
  45. package/dist/lib/runs-artifacts.js +68 -2
  46. package/dist/lib/runs-context-exports.d.ts +451 -0
  47. package/dist/lib/runs-context-exports.js +2315 -0
  48. package/dist/lib/runs-context-index.d.ts +97 -0
  49. package/dist/lib/runs-context-index.js +368 -0
  50. package/dist/lib/runs-control-delivery.js +46 -7
  51. package/dist/lib/runs-control.d.ts +78 -2
  52. package/dist/lib/runs-control.js +334 -2
  53. package/dist/lib/runs-controls.js +4 -1
  54. package/dist/lib/runs-descendant-launches.d.ts +80 -0
  55. package/dist/lib/runs-descendant-launches.js +287 -0
  56. package/dist/lib/runs-execution-evidence.d.ts +110 -0
  57. package/dist/lib/runs-execution-evidence.js +282 -0
  58. package/dist/lib/runs-index.d.ts +66 -1
  59. package/dist/lib/runs-index.js +349 -13
  60. package/dist/lib/runs-native-launches.d.ts +73 -0
  61. package/dist/lib/runs-native-launches.js +227 -0
  62. package/dist/lib/runs-outcomes.d.ts +141 -0
  63. package/dist/lib/runs-outcomes.js +292 -0
  64. package/dist/lib/runs-ownership.d.ts +10 -1
  65. package/dist/lib/runs-ownership.js +36 -4
  66. package/dist/lib/runs-process-windows-mi.d.ts +46 -0
  67. package/dist/lib/runs-process-windows-mi.js +114 -0
  68. package/dist/lib/runs-process.d.ts +34 -1
  69. package/dist/lib/runs-process.js +158 -26
  70. package/dist/lib/runs-results.d.ts +116 -0
  71. package/dist/lib/runs-results.js +518 -0
  72. package/dist/lib/runs-retention.d.ts +15 -2
  73. package/dist/lib/runs-retention.js +148 -9
  74. package/dist/lib/runs-session-exports.d.ts +95 -0
  75. package/dist/lib/runs-session-exports.js +470 -0
  76. package/dist/lib/runs-snapshot.d.ts +58 -0
  77. package/dist/lib/runs-snapshot.js +92 -0
  78. package/dist/lib/runs-start.d.ts +2 -0
  79. package/dist/lib/runs-start.js +44 -3
  80. package/dist/lib/runs-status.d.ts +15 -1
  81. package/dist/lib/runs-status.js +337 -13
  82. package/dist/lib/runs-supervision.d.ts +105 -0
  83. package/dist/lib/runs-supervision.js +501 -0
  84. package/dist/lib/runs-trace.d.ts +1 -0
  85. package/dist/lib/runs-trace.js +14 -3
  86. package/dist/lib/runtime.js +22 -3
  87. package/dist/lib/session-evidence.d.ts +37 -0
  88. package/dist/lib/session-evidence.js +59 -3
  89. package/dist/lib/state-readers.d.ts +4 -1
  90. package/dist/lib/state-readers.js +14 -8
  91. package/dist/lib/temp.d.ts +2 -4
  92. package/dist/lib/temp.js +4 -56
  93. package/dist/lib/tools-access.d.ts +3 -0
  94. package/dist/lib/tools-access.js +13 -0
  95. package/dist/lib/tools-inspect.d.ts +1 -0
  96. package/dist/lib/tools-inspect.js +110 -43
  97. package/dist/lib/tools-local.d.ts +3 -0
  98. package/dist/lib/tools-local.js +18 -6
  99. package/dist/lib/tools-message.d.ts +4 -1
  100. package/dist/lib/tools-message.js +126 -31
  101. package/dist/lib/tools-response.d.ts +31 -3
  102. package/dist/lib/tools-response.js +311 -47
  103. package/dist/lib/tools-spawn.d.ts +3 -0
  104. package/dist/lib/tools-spawn.js +29 -7
  105. package/dist/lib/trace-projection.d.ts +20 -0
  106. package/dist/lib/trace-projection.js +192 -43
  107. package/dist/scripts/async-runner.mjs +272 -194
  108. package/dist/scripts/conformance.mjs +14 -2
  109. package/dist/skills/actors/SKILL.md +46 -2
  110. package/dist/skills/actors/references/diagnostics.md +40 -0
  111. package/dist/skills/actors/references/runs.md +55 -1
  112. package/docs/README.md +9 -1
  113. package/docs/actor-inspector.md +84 -15
  114. package/docs/agent-experience.md +216 -0
  115. package/docs/async-runs.md +18 -2
  116. package/docs/composition-transport.md +103 -0
  117. package/docs/coordinator-delivery.md +204 -4
  118. package/docs/inspection.md +38 -2
  119. package/docs/output-projection-map.md +130 -0
  120. package/docs/releasing.md +19 -2
  121. package/docs/run-supervision.md +1622 -0
  122. package/docs/template-recipes.md +4 -0
  123. package/docs/windows-process-observation.md +248 -0
  124. package/index.ts +1 -1
  125. package/lib/async-runs.ts +164 -73
  126. package/lib/automatic-review-runtime.ts +4 -2
  127. package/lib/command-templates.ts +287 -138
  128. package/lib/execution-files.ts +118 -0
  129. package/lib/execution-output.ts +4 -4
  130. package/lib/execution-sessions.ts +57 -27
  131. package/lib/execution.ts +8 -0
  132. package/lib/extension-runtime.ts +102 -12
  133. package/lib/file-state.ts +16 -2
  134. package/lib/inspector-command.ts +2 -2
  135. package/lib/inspector-overlay.ts +524 -261
  136. package/lib/inspector.ts +101 -130
  137. package/lib/limits.ts +1 -0
  138. package/lib/observability.ts +235 -116
  139. package/lib/pi.ts +59 -11
  140. package/lib/recipe-projection.ts +232 -0
  141. package/lib/recipes-context.ts +27 -6
  142. package/lib/registry.ts +275 -36
  143. package/lib/run-delivery.ts +1737 -49
  144. package/lib/run-supervision.ts +411 -0
  145. package/lib/run-ui-runtime.ts +237 -73
  146. package/lib/runs-artifacts.ts +73 -1
  147. package/lib/runs-context-exports.ts +1871 -0
  148. package/lib/runs-context-index.ts +372 -0
  149. package/lib/runs-control-delivery.ts +36 -18
  150. package/lib/runs-control.ts +297 -1
  151. package/lib/runs-controls.ts +3 -1
  152. package/lib/runs-descendant-launches.ts +338 -0
  153. package/lib/runs-execution-evidence.ts +302 -0
  154. package/lib/runs-index.ts +345 -17
  155. package/lib/runs-native-launches.ts +270 -0
  156. package/lib/runs-outcomes.ts +377 -0
  157. package/lib/runs-ownership.ts +37 -4
  158. package/lib/runs-process-windows-mi.ts +113 -0
  159. package/lib/runs-process.ts +210 -34
  160. package/lib/runs-results.ts +601 -0
  161. package/lib/runs-retention.ts +134 -10
  162. package/lib/runs-session-exports.ts +406 -0
  163. package/lib/runs-snapshot.ts +143 -0
  164. package/lib/runs-start.ts +44 -2
  165. package/lib/runs-status.ts +252 -10
  166. package/lib/runs-supervision.ts +394 -0
  167. package/lib/runs-trace.ts +14 -3
  168. package/lib/runtime.ts +22 -3
  169. package/lib/session-evidence.ts +76 -4
  170. package/lib/state-readers.ts +16 -7
  171. package/lib/temp.ts +3 -56
  172. package/lib/tools-access.ts +13 -0
  173. package/lib/tools-inspect.ts +110 -51
  174. package/lib/tools-local.ts +37 -24
  175. package/lib/tools-message.ts +120 -37
  176. package/lib/tools-response.ts +273 -50
  177. package/lib/tools-spawn.ts +31 -10
  178. package/lib/trace-projection.ts +165 -42
  179. package/package.json +10 -6
  180. package/scripts/async-runner.mjs +272 -194
  181. package/scripts/conformance.mjs +14 -2
  182. package/skills/actors/SKILL.md +46 -2
  183. package/skills/actors/references/diagnostics.md +40 -0
  184. package/skills/actors/references/runs.md +55 -1
  185. package/banner.jpg +0 -0
  186. package/dist/lib/inspector-actions.d.ts +0 -16
  187. package/dist/lib/inspector-actions.js +0 -57
  188. package/lib/inspector-actions.ts +0 -79
package/lib/registry.ts CHANGED
@@ -16,11 +16,13 @@ import {
16
16
  writeTextAtomic,
17
17
  } from "./file-state.ts";
18
18
  import * as Identity from "./identity.ts";
19
+ import * as Limits from "./limits.ts";
19
20
  import * as Paths from "./paths.ts";
20
21
  import * as RecipesContext from "./recipes-context.ts";
21
22
  import * as RecipesDiscovery from "./recipes-discovery.ts";
22
23
  import * as RecipesReferences from "./recipes-references.ts";
23
24
  import * as Schema from "./schema.ts";
25
+ import * as ToolsResponse from "./tools-response.ts";
24
26
 
25
27
  export interface RegisterToolInput {
26
28
  name?: string;
@@ -89,17 +91,32 @@ function textContent(text: string) {
89
91
  return { type: "text" as const, text };
90
92
  }
91
93
 
94
+ function registryListText(rows: readonly string[]): string {
95
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
96
+ const coverage = (shown: number, reason?: string) => ({ ...(reason ? { reason } : {}), shown, omitted: rows.length - shown, total: rows.length });
97
+ const malformed = rows.some(row => !row.isWellFormed());
98
+ if (!malformed) {
99
+ // Remove only the compiler prefix after the original key coercion and join.
100
+ const complete = ToolsResponse.renderYamlMetadata({ registered_tools: rows.map(row => row.slice(2)), registered_tools_coverage: coverage(rows.length) }, budget);
101
+ if (complete.state === "available") return complete.text;
102
+ }
103
+ const fallback = ToolsResponse.renderYamlMetadata({ registered_tools: malformed ? "[unavailable]" : "[omitted]", registered_tools_coverage: coverage(0, malformed ? "malformed_selected_text" : "response_too_large") }, budget);
104
+ // Fixed keys/reasons and finite array-length counts are bounded far below budget.
105
+ return (fallback as Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
106
+ }
107
+
92
108
  function listTools<TContext>(
93
109
  deps: RegisterToolRuntimeDeps<TContext>,
94
110
  ): RegisterToolResult {
95
111
  const names = [...deps.getTools().keys()].sort();
112
+ const rows = names.length > 0 ? names.map((name) => `- ${name}`) : [];
113
+ // Retain the original conditional map/join observations before read-time projection.
114
+ void (names.length > 0 ? `Registered tools:\n${rows.join("\n")}` : "No registered tools.");
96
115
  return {
97
116
  content: [
98
117
  textContent(
99
118
  ExecutionOutput.formatToolText(
100
- names.length > 0
101
- ? `Registered tools:\n${names.map((name) => `- ${name}`).join("\n")}`
102
- : "No registered tools.",
119
+ registryListText(rows),
103
120
  ),
104
121
  ),
105
122
  ],
@@ -155,6 +172,139 @@ function registrationNextActions(
155
172
  ];
156
173
  }
157
174
 
175
+ /** Read-time text only: callers supply the original post-activation selections, not live ports. */
176
+ function registrationReceiptText(data: {
177
+ tool: string; result: "registered" | "updated" | "persisted"; source: string; callable: boolean;
178
+ boundary?: string; required: readonly unknown[]; optional: readonly unknown[];
179
+ warnings: readonly unknown[]; next: string;
180
+ }): string {
181
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
182
+ const renderPart = (value: Record<string, unknown>) => ToolsResponse.renderYamlMetadata(value, budget);
183
+ const omission = (key: string, count: number, malformed: boolean): Record<string, unknown> => ({
184
+ [key]: malformed ? "[unavailable]" : "[omitted]",
185
+ [key + "_omission"]: { reason: malformed ? "malformed_selected_text" : "response_too_large", shown: 0, omitted: count, total: count },
186
+ });
187
+ const part = (full: Record<string, unknown>, omitted: Record<string, unknown>) => {
188
+ const complete = renderPart(full), fallback = renderPart(omitted);
189
+ return { full, selected: complete.state === "available" && fallback.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted };
190
+ };
191
+ const scalar = (key: string, value: string) => {
192
+ const wellFormed = value.isWellFormed();
193
+ const omitted = omission(key, 1, !wellFormed);
194
+ return part(wellFormed ? { [key]: value } : omitted, omitted);
195
+ };
196
+ const sequence = (key: string, values: readonly unknown[]) => {
197
+ const rows: string[] = [];
198
+ let malformed = false;
199
+ for (let index = 0; index < values.length; index++) {
200
+ const value = values[index];
201
+ if (typeof value !== "string" || !value.isWellFormed()) { malformed = true; break; }
202
+ rows.push(value);
203
+ }
204
+ const omitted = omission(key, values.length, malformed);
205
+ return part(malformed ? omitted : { [key]: rows }, omitted);
206
+ };
207
+ const tool = scalar("tool", data.tool);
208
+ const source = scalar("source", data.source);
209
+ const required = sequence("required_args", data.required), optional = sequence("optional_args", data.optional);
210
+ const warnings = data.warnings.length ? sequence("warnings", data.warnings) : undefined;
211
+ const nextWellFormed = data.next.isWellFormed();
212
+ const omittedNext = omission("next_action", 1, !nextWellFormed);
213
+ // This producer knows only its existing status hint; invocation advice stays opaque.
214
+ const next = part(nextWellFormed
215
+ ? data.callable ? { instruction: data.next } : { next: { tool: "inspect", target: `tool:${data.tool}`, view: "status" } }
216
+ : omittedNext, omittedNext);
217
+ const render = () => renderPart({ ...tool.selected, result: data.result, ...source.selected, callable_now: data.callable,
218
+ ...(data.boundary === undefined ? {} : { activation_boundary: data.boundary }), ...required.selected, ...optional.selected,
219
+ ...(warnings ? warnings.selected : {}), ...next.selected });
220
+ // Preserve complete safety notes before large argument lists; all seeds are monotone.
221
+ for (const current of [...(warnings ? [warnings] : []), tool, source, next, required, optional]) {
222
+ if (current.selected === current.full) continue;
223
+ const previous = current.selected;
224
+ current.selected = current.full;
225
+ if (render().state !== "available") current.selected = previous;
226
+ }
227
+ return (render() as Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
228
+ }
229
+
230
+ function registryNoticeText(kind: "activated" | "promoted" | "deleted", name: string): string {
231
+ const report = { activated: "Tool activated", promoted: "Promoted draft recipe", deleted: "Deleted tool" }[kind];
232
+ const selected = name.isWellFormed() ? ToolsResponse.formatMarkdownField("tool", name)
233
+ : "- tool: [unavailable]\n- tool_omission:\n - reason: malformed_selected_text\n - shown: 0\n - omitted: 1\n - total: 1";
234
+ return ToolsResponse.formatMarkdownField("report", report) + "\n" + selected;
235
+ }
236
+
237
+ function registrationTransactionFailureText(stage: string, generation: string, summary: string, error: string): string {
238
+ const field = ToolsResponse.formatMarkdownField;
239
+ const part = (key: string, value: string) => {
240
+ const wellFormed = value.isWellFormed();
241
+ const omitted = `- ${key}: [${wellFormed ? "omitted" : "unavailable"}]\n- ${key}_omission:\n - reason: ${wellFormed ? "response_too_large" : "malformed_selected_text"}\n - shown: 0\n - omitted: 1\n - total: 1`;
242
+ const full = wellFormed ? field(key, value) : omitted;
243
+ return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
244
+ };
245
+ const selectedGeneration = part("resolution_generation", generation);
246
+ const selectedSummary = part("active_skills_summary", summary);
247
+ const selectedError = part("error", error);
248
+ const render = () => [field("result", "Tool registration transaction failed."), field("stage", stage),
249
+ selectedGeneration.selected, selectedSummary.selected, selectedError.selected].join("\n");
250
+ // Keep a fitting surfaced error before large optional resolution metadata.
251
+ for (const current of [selectedError, selectedGeneration, selectedSummary]) {
252
+ if (current.selected === current.full) continue;
253
+ const previous = current.selected;
254
+ current.selected = current.full;
255
+ if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
256
+ }
257
+ return render();
258
+ }
259
+
260
+ function candidateAdmissionFailureText(data: { kind: "source"; source: string; diagnostics: string; summary: string; instruction: string } | { kind: "recipe"; diagnostics: string }): string {
261
+ const field = ToolsResponse.formatMarkdownField;
262
+ const part = (key: string, value: string, literal = false) => {
263
+ const wellFormed = value.isWellFormed();
264
+ const omitted = `- ${key}: [${wellFormed ? "omitted" : "unavailable"}]\n- ${key}_omission:\n - reason: ${wellFormed ? "response_too_large" : "malformed_selected_text"}\n - shown: 0\n - omitted: 1\n - total: 1`;
265
+ const full = wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted;
266
+ return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
267
+ };
268
+ const diagnostics = part("diagnostics_summary", data.diagnostics);
269
+ const source = data.kind === "source" ? part("source", data.source, true) : undefined;
270
+ const summary = data.kind === "source" ? part("active_skills_summary", data.summary) : undefined;
271
+ const instruction = data.kind === "source" ? part("instruction", data.instruction) : undefined;
272
+ const render = () => [field("field", "recipe"), ...(source ? [source.selected] : []),
273
+ field("result", data.kind === "source" ? "Recipe source validation failed." : "Tool recipe validation failed."),
274
+ diagnostics.selected, ...(summary ? [summary.selected] : []), ...(instruction ? [instruction.selected] : [])].join("\n");
275
+ // Preserve fitting diagnostics before large optional source/context/advice fields.
276
+ for (const current of [diagnostics, source, summary, instruction]) {
277
+ if (!current || current.selected === current.full) continue;
278
+ const previous = current.selected;
279
+ current.selected = current.full;
280
+ if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
281
+ }
282
+ return render();
283
+ }
284
+
285
+ function sourceNormalizationFailureText(source: string, reason: string, summary: string, instruction: string): string {
286
+ const field = ToolsResponse.formatMarkdownField;
287
+ const part = (key: string, value: string, literal = false) => {
288
+ const wellFormed = value.isWellFormed();
289
+ const omitted = `- ${key}: [${wellFormed ? "omitted" : "unavailable"}]\n- ${key}_omission:\n - reason: ${wellFormed ? "response_too_large" : "malformed_selected_text"}\n - shown: 0\n - omitted: 1\n - total: 1`;
290
+ const full = wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted;
291
+ return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
292
+ };
293
+ const selectedSource = part("source", source, true);
294
+ const selectedReason = part("reason", reason);
295
+ const selectedSummary = part("active_skills_summary", summary);
296
+ const selectedInstruction = part("instruction", instruction);
297
+ const render = () => [field("field", "from"), selectedSource.selected, field("result", "Recipe source is invalid."),
298
+ selectedReason.selected, selectedSummary.selected, selectedInstruction.selected].join("\n");
299
+ for (const current of [selectedSource, selectedReason, selectedSummary, selectedInstruction]) {
300
+ if (current.selected === current.full) continue;
301
+ const previous = current.selected;
302
+ current.selected = current.full;
303
+ if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
304
+ }
305
+ return render();
306
+ }
307
+
158
308
  function activeSkillSummary(
159
309
  resolutionContext: RecipesContext.RecipeResolutionContext,
160
310
  ): string {
@@ -168,6 +318,19 @@ function activeSkillSummary(
168
318
  return names.length > 0 ? names.join(", ") : "<none>";
169
319
  }
170
320
 
321
+ function draftPathFailureText(kind: "root" | "missing", reference: string): string {
322
+ const key = kind === "root" ? "allowed_root" : "path";
323
+ const render = (selected: string) => "- field: draft\n" + selected + (kind === "root"
324
+ ? "\n- result: Draft must be under the allowed root.\n- instruction: Use inspect target=recipes view=summary to list drafts."
325
+ : "\n- result: Draft not found.");
326
+ const wellFormed = reference.isWellFormed();
327
+ if (wellFormed) {
328
+ const complete = render(ToolsResponse.formatMarkdownField(key, ToolsResponse.formatLiteralBlock(reference)));
329
+ if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
330
+ }
331
+ return render(`- ${key}: [${wellFormed ? "omitted" : "unavailable"}]\n- ${key}_omission:\n - reason: ${wellFormed ? "response_too_large" : "malformed_selected_text"}\n - shown: 0\n - omitted: 1\n - total: 1`);
332
+ }
333
+
171
334
  function assertDraftPath<TContext>(
172
335
  deps: RegisterToolRuntimeDeps<TContext>,
173
336
  draft: string,
@@ -182,16 +345,63 @@ function assertDraftPath<TContext>(
182
345
  ) {
183
346
  throw new Error(
184
347
  ExecutionOutput.formatToolText(
185
- `Draft must be under ${draftRoot}. Use inspect target=recipes view=summary to list drafts.`,
348
+ draftPathFailureText("root", `${draftRoot}`),
186
349
  ),
187
350
  );
188
351
  }
189
352
  if (!existsSync(path)) {
190
- throw new Error(ExecutionOutput.formatToolText(`Draft not found: ${path}`));
353
+ throw new Error(ExecutionOutput.formatToolText(draftPathFailureText("missing", `${path}`)));
191
354
  }
192
355
  return path;
193
356
  }
194
357
 
358
+ function draftReceiptText(result: "updated" | "registered", tool: string, callable: string, next: string): string {
359
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
360
+ const renderPart = (value: Record<string, unknown>) => ToolsResponse.renderYamlMetadata(value, budget);
361
+ const part = (key: string, value: string, rendered: Record<string, unknown>) => {
362
+ const wellFormed = value.isWellFormed();
363
+ const omitted: Record<string, unknown> = { [key]: wellFormed ? "[omitted]" : "[unavailable]",
364
+ [key + "_omission"]: { reason: wellFormed ? "response_too_large" : "malformed_selected_text", shown: 0, omitted: 1, total: 1 } };
365
+ const full = wellFormed ? rendered : omitted;
366
+ const complete = renderPart(full), fallback = renderPart(omitted);
367
+ return { full, selected: complete.state === "available" && fallback.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted };
368
+ };
369
+ const identity = part("tool", tool, { tool });
370
+ const flag = part("callable_now", callable, { callable_now: callable });
371
+ // Only this producer's exact canonical read-only status hint is typed, never body advice.
372
+ const status = /^inspect target=(tool:[a-z][a-z0-9_]{0,63}) view=status$/u.exec(next);
373
+ const hint = part("next_action", next, status ? { next: { tool: "inspect", target: status[1], view: "status" } } : { instruction: next });
374
+ const render = () => renderPart({ ...identity.selected, result, source: "draft", ...flag.selected, ...hint.selected });
375
+ for (const current of [identity, flag, hint]) {
376
+ if (current.selected === current.full) continue;
377
+ const previous = current.selected;
378
+ current.selected = current.full;
379
+ if (render().state !== "available") current.selected = previous;
380
+ }
381
+ return (render() as Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
382
+ }
383
+
384
+ function duplicateRegistrationText(value: string): string {
385
+ const render = (selected: string) => "- field: name\n" + selected + "\n- result: Tool already registered.\n- instruction: Use update=true to overwrite.";
386
+ const wellFormed = value.isWellFormed();
387
+ if (wellFormed) {
388
+ const complete = render(ToolsResponse.formatMarkdownField("value", value));
389
+ if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
390
+ }
391
+ return render(`- value: [${wellFormed ? "omitted" : "unavailable"}]\n- value_omission:\n - reason: ${wellFormed ? "response_too_large" : "malformed_selected_text"}\n - shown: 0\n - omitted: 1\n - total: 1`);
392
+ }
393
+
394
+ function draftRecipeFailureText(key: "reason" | "diagnostics_summary", value?: string): string {
395
+ const header = "- field: draft\n- result: Draft recipe is invalid.";
396
+ if (value === undefined) return header;
397
+ const wellFormed = value.isWellFormed();
398
+ if (wellFormed) {
399
+ const complete = header + "\n" + ToolsResponse.formatMarkdownField(key, value);
400
+ if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
401
+ }
402
+ return header + `\n- ${key}: [${wellFormed ? "omitted" : "unavailable"}]\n- ${key}_omission:\n - reason: ${wellFormed ? "response_too_large" : "malformed_selected_text"}\n - shown: 0\n - omitted: 1\n - total: 1`;
403
+ }
404
+
195
405
  function promoteDraftRecipe<TContext>(
196
406
  name: string,
197
407
  input: RegisterToolInput,
@@ -207,7 +417,7 @@ function promoteDraftRecipe<TContext>(
207
417
  if ((existing || existsSync(targetPath)) && !input.update) {
208
418
  throw new Error(
209
419
  ExecutionOutput.formatToolText(
210
- `Tool "${name}" already registered. Use update=true to overwrite.`,
420
+ duplicateRegistrationText(`${name}`),
211
421
  ),
212
422
  );
213
423
  }
@@ -216,7 +426,7 @@ function promoteDraftRecipe<TContext>(
216
426
  const reason = RecipesReferences.diagnoseRawRecipeConfigFailure(draftPath);
217
427
  throw new Error(
218
428
  ExecutionOutput.formatToolText(
219
- `Draft recipe is invalid${reason ? `: ${reason}` : "."}`,
429
+ draftRecipeFailureText("reason", reason ? `${reason}` : undefined),
220
430
  ),
221
431
  );
222
432
  }
@@ -239,7 +449,7 @@ function promoteDraftRecipe<TContext>(
239
449
  if (!candidate.validated || !candidate.tool) {
240
450
  throw new Error(
241
451
  ExecutionOutput.formatToolText(
242
- `Draft recipe is invalid: ${candidate.diagnostics.join("; ")}`,
452
+ draftRecipeFailureText("diagnostics_summary", `${candidate.diagnostics.join("; ")}`),
243
453
  ),
244
454
  );
245
455
  }
@@ -254,12 +464,13 @@ function promoteDraftRecipe<TContext>(
254
464
  const argSummary = RecipesDiscovery.summarizeRegisteredToolArgs(promoted);
255
465
  const callableNow = activation?.callable_now ?? false;
256
466
  const nextActions = registrationNextActions(name, "draft", callableNow);
257
- deps.notify(ctx, `Promoted draft recipe: ${name}`, "info");
467
+ deps.notify(ctx, registryNoticeText("promoted", `${name}`), "info");
258
468
  return {
259
469
  content: [
260
470
  textContent(
261
471
  ExecutionOutput.formatToolText(
262
- `${existing ? "Updated" : "Registered"} tool "${name}" from draft; callable_now=${callableNow}; next=${nextActions[0]}.`,
472
+ // Preserve the old name→flag→hint template conversions here, after notify.
473
+ draftReceiptText(existing ? "updated" : "registered", `${name}`, `${callableNow}`, `${nextActions[0]}`),
263
474
  ),
264
475
  ),
265
476
  ],
@@ -296,6 +507,19 @@ function persistToolRecipe<TContext>(
296
507
  return path;
297
508
  }
298
509
 
510
+ function registryDeletionText(name: string, result: "deleted" | "not found"): string {
511
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
512
+ const facts = { result, ...(result === "deleted" ? { instruction: "Reload to remove it from the complete registry." } : {}) };
513
+ const wellFormed = name.isWellFormed();
514
+ if (wellFormed) {
515
+ const complete = ToolsResponse.renderYamlMetadata({ tool: name, ...facts }, budget);
516
+ if (complete.state === "available") return complete.text;
517
+ }
518
+ const fallback = ToolsResponse.renderYamlMetadata({ tool: wellFormed ? "[omitted]" : "[unavailable]", tool_omission: { reason: wellFormed ? "response_too_large" : "malformed_selected_text", shown: 0, omitted: 1, total: 1 }, ...facts }, budget);
519
+ // Fixed metadata is well formed and bounded far below budget.
520
+ return (fallback as Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
521
+ }
522
+
299
523
  function deleteTool<TContext>(
300
524
  name: string,
301
525
  ctx: TContext,
@@ -306,7 +530,7 @@ function deleteTool<TContext>(
306
530
  return {
307
531
  content: [
308
532
  textContent(
309
- ExecutionOutput.formatToolText(`Tool "${name}" not found.`),
533
+ ExecutionOutput.formatToolText(registryDeletionText(`${name}`, "not found")),
310
534
  ),
311
535
  ],
312
536
  details: { tool: name },
@@ -318,12 +542,12 @@ function deleteTool<TContext>(
318
542
  deps.setActiveTools(
319
543
  deps.getActiveTools().filter((toolName) => toolName !== name),
320
544
  );
321
- deps.notify(ctx, `Deleted tool: ${name}`, "info");
545
+ deps.notify(ctx, registryNoticeText("deleted", `${name}`), "info");
322
546
  return {
323
547
  content: [
324
548
  textContent(
325
549
  ExecutionOutput.formatToolText(
326
- `Deleted tool "${name}". Reload to remove it from the complete registry.`,
550
+ registryDeletionText(`${name}`, "deleted"),
327
551
  ),
328
552
  ),
329
553
  ],
@@ -384,7 +608,7 @@ function assertTemplateIsNotNestedRecipe(value: unknown): void {
384
608
  ) {
385
609
  throw new Error(
386
610
  ExecutionOutput.formatToolText(
387
- "Nested Recipe-shaped templates are not a register_tool source mode. Next: retry with from=<skill>/<recipe> and defaults={...}.",
611
+ "- field: template\n- result: Nested Recipe-shaped templates are not a register_tool source mode.\n- instruction: retry with from=<skill>/<recipe> and defaults={...}.",
388
612
  ),
389
613
  );
390
614
  }
@@ -402,7 +626,7 @@ function getInputTemplate(
402
626
  });
403
627
  if (steps.length === 0)
404
628
  throw new Error(
405
- ExecutionOutput.formatToolText("Tool template sequence is empty."),
629
+ ExecutionOutput.formatToolText("- field: template\n- result: Tool template sequence is empty."),
406
630
  );
407
631
  return value;
408
632
  }
@@ -414,7 +638,7 @@ function getInputTemplate(
414
638
  }
415
639
  throw new Error(
416
640
  ExecutionOutput.formatToolText(
417
- "Tool template must be a string, object, or sequence.",
641
+ "- field: template\n- result: unsupported template type\n- supported_types:\n - string\n - object\n - sequence",
418
642
  ),
419
643
  );
420
644
  }
@@ -441,7 +665,7 @@ function normalizeDefaults(value: unknown): Record<string, unknown> | undefined
441
665
  if (value === undefined) return undefined;
442
666
  if (!value || typeof value !== "object" || Array.isArray(value)) {
443
667
  throw new Error(
444
- ExecutionOutput.formatToolText("Tool defaults must be an object."),
668
+ ExecutionOutput.formatToolText("- field: defaults\n- result: Tool defaults must be an object."),
445
669
  );
446
670
  }
447
671
  return { ...(value as Record<string, unknown>) };
@@ -502,7 +726,7 @@ function buildAuthoredRecipe(
502
726
  if (!description && !from) {
503
727
  throw new Error(
504
728
  ExecutionOutput.formatToolText(
505
- "Tool description is required for command-template registration.",
729
+ "- field: description\n- result: Tool description is required for command-template registration.",
506
730
  ),
507
731
  );
508
732
  }
@@ -514,7 +738,7 @@ function buildAuthoredRecipe(
514
738
  if (runtimeOwnedDefault) {
515
739
  throw new Error(
516
740
  ExecutionOutput.formatToolText(
517
- `Tool defaults cannot set runtime-owned input: ${runtimeOwnedDefault}. Next: remove that default and retry register_tool; the runtime supplies it.`,
741
+ `- field: defaults\n- key: ${runtimeOwnedDefault}\n- result: Tool defaults cannot set runtime-owned input.\n- instruction: remove that default and retry register_tool; the runtime supplies it.`,
518
742
  ),
519
743
  );
520
744
  }
@@ -534,7 +758,7 @@ function buildAuthoredRecipe(
534
758
  const template = getInputTemplate(input.template);
535
759
  if (template === null) {
536
760
  throw new Error(
537
- ExecutionOutput.formatToolText("Tool template cannot be null here."),
761
+ ExecutionOutput.formatToolText("- field: template\n- result: Tool template cannot be null here."),
538
762
  );
539
763
  }
540
764
  const finalTemplate =
@@ -543,7 +767,7 @@ function buildAuthoredRecipe(
543
767
  : template;
544
768
  if (!finalTemplate) {
545
769
  throw new Error(
546
- ExecutionOutput.formatToolText("Tool template is required."),
770
+ ExecutionOutput.formatToolText("- field: template\n- result: Tool template is required."),
547
771
  );
548
772
  }
549
773
  const authored: Record<string, unknown> = {
@@ -569,7 +793,7 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
569
793
  if (Object.hasOwn(record, "values")) {
570
794
  throw new Error(
571
795
  ExecutionOutput.formatToolText(
572
- "register_tool values was removed. Next: retry register_tool with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition.",
796
+ "- field: values\n- result: register_tool values was removed.\n- replacement_fields:\n - defaults\n - from\n- instruction: retry register_tool with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition.",
573
797
  ),
574
798
  );
575
799
  }
@@ -580,7 +804,7 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
580
804
  ) {
581
805
  throw new Error(
582
806
  ExecutionOutput.formatToolText(
583
- "Tool from must be a non-empty Recipe reference. Next: retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path.",
807
+ "- field: from\n- result: Tool from must be a non-empty Recipe reference.\n- reference_forms:\n - <skill>/<recipe>\n - explicit .json path\n - explicit .md path\n- instruction: retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path.",
584
808
  ),
585
809
  );
586
810
  }
@@ -592,7 +816,7 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
592
816
  if (modeCount > 1) {
593
817
  throw new Error(
594
818
  ExecutionOutput.formatToolText(
595
- "Use exactly one register_tool source mode: from, template, or draft. Next: remove the extra source fields and retry register_tool.",
819
+ "- result: Use exactly one register_tool source mode.\n- source_modes:\n - from\n - template\n - draft\n- instruction: remove the extra source fields and retry register_tool.",
596
820
  ),
597
821
  );
598
822
  }
@@ -602,12 +826,22 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
602
826
  ) {
603
827
  throw new Error(
604
828
  ExecutionOutput.formatToolText(
605
- "register_tool from inherits args and async behavior. Next: retry register_tool with from and optional defaults only; omit args and async.",
829
+ "- field: from\n- result: register_tool from inherits args and async behavior.\n- inherited_fields:\n - args\n - async\n- instruction: retry register_tool with from and optional defaults only; omit args and async.",
606
830
  ),
607
831
  );
608
832
  }
609
833
  }
610
834
 
835
+ function reservedRegistrationNameText(value: string): string {
836
+ const render = (selected: string) => "- field: name\n" + selected + "\n- result: Reserved tool name.";
837
+ const wellFormed = value.isWellFormed();
838
+ if (wellFormed) {
839
+ const complete = render(ToolsResponse.formatMarkdownField("value", value));
840
+ if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
841
+ }
842
+ return render(`- value: [${wellFormed ? "omitted" : "unavailable"}]\n- value_omission:\n - reason: ${wellFormed ? "response_too_large" : "malformed_selected_text"}\n - shown: 0\n - omitted: 1\n - total: 1`);
843
+ }
844
+
611
845
  function executeRegisterToolUnlocked<TContext>(
612
846
  params: unknown,
613
847
  ctx: TContext,
@@ -622,10 +856,10 @@ function executeRegisterToolUnlocked<TContext>(
622
856
  if (!input.name) return listTools(deps);
623
857
  const name = Identity.normalizeToolName(input.name);
624
858
  if (!name)
625
- throw new Error(ExecutionOutput.formatToolText("Invalid tool name."));
859
+ throw new Error(ExecutionOutput.formatToolText("- field: name\n- result: Invalid tool name."));
626
860
  if (deps.reservedToolNames.has(name)) {
627
861
  throw new Error(
628
- ExecutionOutput.formatToolText(`Reserved tool name: ${name}`),
862
+ ExecutionOutput.formatToolText(reservedRegistrationNameText(`${name}`)),
629
863
  );
630
864
  }
631
865
  if (typeof input.draft === "string" && input.draft.trim()) {
@@ -647,7 +881,7 @@ function executeRegisterToolUnlocked<TContext>(
647
881
  : requestedSource ?? "";
648
882
  throw new Error(
649
883
  ExecutionOutput.formatToolText(
650
- `Recipe source "${requestedSource}" is invalid: ${reason}. Active Skills: ${activeSkillSummary(resolutionContext)}. Next: ${recipeDoctorAction(diagnosticSource)}`,
884
+ sourceNormalizationFailureText(`${requestedSource}`, `${reason}`, `${activeSkillSummary(resolutionContext)}`, `${recipeDoctorAction(diagnosticSource)}`),
651
885
  ),
652
886
  );
653
887
  }
@@ -664,14 +898,14 @@ function executeRegisterToolUnlocked<TContext>(
664
898
  if ((authoritative.exists || existing) && !input.update) {
665
899
  throw new Error(
666
900
  ExecutionOutput.formatToolText(
667
- `Tool "${name}" already registered. Use update=true to overwrite.`,
901
+ duplicateRegistrationText(`${name}`),
668
902
  ),
669
903
  );
670
904
  }
671
905
  if (template === undefined && !input.from && !existing) {
672
906
  throw new Error(
673
907
  ExecutionOutput.formatToolText(
674
- "New registration requires exactly one source mode: from, template, or draft. Next: retry register_tool with one source field.",
908
+ "- result: New registration requires exactly one source mode.\n- source_modes:\n - from\n - template\n - draft\n- instruction: retry register_tool with one source field.",
675
909
  ),
676
910
  );
677
911
  }
@@ -688,8 +922,8 @@ function executeRegisterToolUnlocked<TContext>(
688
922
  );
689
923
  if (!candidate.validated || !candidate.tool) {
690
924
  const sourceDiagnostic = requestedSource
691
- ? `Recipe source "${requestedSource}" validation failed: ${candidate.diagnostics.join("; ")}. Active Skills: ${activeSkillSummary(resolutionContext)}. Next: ${recipeDoctorAction(requestedSource)}`
692
- : `Tool recipe validation failed: ${candidate.diagnostics.join("; ")}`;
925
+ ? candidateAdmissionFailureText({ kind: "source", source: `${requestedSource}`, diagnostics: `${candidate.diagnostics.join("; ")}`, summary: `${activeSkillSummary(resolutionContext)}`, instruction: `${recipeDoctorAction(requestedSource)}` })
926
+ : candidateAdmissionFailureText({ kind: "recipe", diagnostics: `${candidate.diagnostics.join("; ")}` });
693
927
  throw new Error(ExecutionOutput.formatToolText(sourceDiagnostic));
694
928
  }
695
929
  const activeBefore = deps.getActiveTools();
@@ -739,11 +973,11 @@ function executeRegisterToolUnlocked<TContext>(
739
973
  deps.setActiveTools(activeBefore);
740
974
  throw new Error(
741
975
  ExecutionOutput.formatToolText(
742
- `Tool registration transaction failed at ${transactionStage} (${resolutionContext.generation}; active Skills: ${activeSkillSummary(resolutionContext)}): ${error instanceof Error ? error.message : String(error)}`,
976
+ registrationTransactionFailureText(`${transactionStage}`, `${resolutionContext.generation}`, `${activeSkillSummary(resolutionContext)}`, `${error instanceof Error ? error.message : String(error)}`),
743
977
  ),
744
978
  );
745
979
  }
746
- deps.notify(ctx, `Tool activated: ${name}`, "info");
980
+ deps.notify(ctx, registryNoticeText("activated", `${name}`), "info");
747
981
  const templateWarnings = CommandTemplates.getCommandTemplateWarnings(
748
982
  typeof cfg.recipe?.template === "object" && !Array.isArray(cfg.recipe.template)
749
983
  ? cfg.recipe.template
@@ -757,14 +991,19 @@ function executeRegisterToolUnlocked<TContext>(
757
991
  const source = requestedSource ?? "template";
758
992
  const argSummary = RecipesDiscovery.summarizeRegisteredToolArgs(cfg);
759
993
  const nextActions = registrationNextActions(name, source, callableNow);
994
+ let displayBoundary: string | undefined;
760
995
  const outcomeText = callableNow
761
996
  ? `${existing ? "Updated" : "Registered"} tool "${name}" from ${source}; callable_now=true`
762
- : `Persisted tool "${name}" from ${source}; callable_now=false; activation_boundary=${activationBoundary(activation)}`;
997
+ : `Persisted tool "${name}" from ${source}; callable_now=false; activation_boundary=${displayBoundary = activationBoundary(activation)}`;
998
+ // Keep original fallible advisory evaluation/order after effects, before the new text.
999
+ void `${outcomeText}; required=${Schema.formatToolArgs(argSummary.required)}; optional=${Schema.formatToolArgs(argSummary.optional)}; next=${nextActions[0]}.${warningText}`;
763
1000
  return {
764
1001
  content: [
765
1002
  textContent(
766
1003
  ExecutionOutput.formatToolText(
767
- `${outcomeText}; required=${Schema.formatToolArgs(argSummary.required)}; optional=${Schema.formatToolArgs(argSummary.optional)}; next=${nextActions[0]}.${warningText}`,
1004
+ registrationReceiptText({ tool: name, result: callableNow ? existing ? "updated" : "registered" : "persisted",
1005
+ source, callable: Boolean(callableNow), boundary: displayBoundary, required: argSummary.required,
1006
+ optional: argSummary.optional, warnings: templateWarnings, next: nextActions[0] }),
768
1007
  ),
769
1008
  ),
770
1009
  ],