@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
@@ -10,21 +10,38 @@ import * as Config from "./config.js";
10
10
  import * as ExecutionOutput from "./execution-output.js";
11
11
  import { withFileMutationLock, writeJsonAtomic, writeTextAtomic, } from "./file-state.js";
12
12
  import * as Identity from "./identity.js";
13
+ import * as Limits from "./limits.js";
13
14
  import * as Paths from "./paths.js";
14
15
  import * as RecipesContext from "./recipes-context.js";
15
16
  import * as RecipesDiscovery from "./recipes-discovery.js";
16
17
  import * as RecipesReferences from "./recipes-references.js";
17
18
  import * as Schema from "./schema.js";
19
+ import * as ToolsResponse from "./tools-response.js";
18
20
  function textContent(text) {
19
21
  return { type: "text", text };
20
22
  }
23
+ function registryListText(rows) {
24
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
25
+ const coverage = (shown, reason) => ({ ...(reason ? { reason } : {}), shown, omitted: rows.length - shown, total: rows.length });
26
+ const malformed = rows.some(row => !row.isWellFormed());
27
+ if (!malformed) {
28
+ // Remove only the compiler prefix after the original key coercion and join.
29
+ const complete = ToolsResponse.renderYamlMetadata({ registered_tools: rows.map(row => row.slice(2)), registered_tools_coverage: coverage(rows.length) }, budget);
30
+ if (complete.state === "available")
31
+ return complete.text;
32
+ }
33
+ const fallback = ToolsResponse.renderYamlMetadata({ registered_tools: malformed ? "[unavailable]" : "[omitted]", registered_tools_coverage: coverage(0, malformed ? "malformed_selected_text" : "response_too_large") }, budget);
34
+ // Fixed keys/reasons and finite array-length counts are bounded far below budget.
35
+ return fallback.text;
36
+ }
21
37
  function listTools(deps) {
22
38
  const names = [...deps.getTools().keys()].sort();
39
+ const rows = names.length > 0 ? names.map((name) => `- ${name}`) : [];
40
+ // Retain the original conditional map/join observations before read-time projection.
41
+ void (names.length > 0 ? `Registered tools:\n${rows.join("\n")}` : "No registered tools.");
23
42
  return {
24
43
  content: [
25
- textContent(ExecutionOutput.formatToolText(names.length > 0
26
- ? `Registered tools:\n${names.map((name) => `- ${name}`).join("\n")}`
27
- : "No registered tools.")),
44
+ textContent(ExecutionOutput.formatToolText(registryListText(rows))),
28
45
  ],
29
46
  details: { tool: "register_tool" },
30
47
  };
@@ -65,12 +82,160 @@ function registrationNextActions(tool, source, callableNow) {
65
82
  : [recipeDoctorAction(source)]),
66
83
  ];
67
84
  }
85
+ /** Read-time text only: callers supply the original post-activation selections, not live ports. */
86
+ function registrationReceiptText(data) {
87
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
88
+ const renderPart = (value) => ToolsResponse.renderYamlMetadata(value, budget);
89
+ const omission = (key, count, malformed) => ({
90
+ [key]: malformed ? "[unavailable]" : "[omitted]",
91
+ [key + "_omission"]: { reason: malformed ? "malformed_selected_text" : "response_too_large", shown: 0, omitted: count, total: count },
92
+ });
93
+ const part = (full, omitted) => {
94
+ const complete = renderPart(full), fallback = renderPart(omitted);
95
+ return { full, selected: complete.state === "available" && fallback.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted };
96
+ };
97
+ const scalar = (key, value) => {
98
+ const wellFormed = value.isWellFormed();
99
+ const omitted = omission(key, 1, !wellFormed);
100
+ return part(wellFormed ? { [key]: value } : omitted, omitted);
101
+ };
102
+ const sequence = (key, values) => {
103
+ const rows = [];
104
+ let malformed = false;
105
+ for (let index = 0; index < values.length; index++) {
106
+ const value = values[index];
107
+ if (typeof value !== "string" || !value.isWellFormed()) {
108
+ malformed = true;
109
+ break;
110
+ }
111
+ rows.push(value);
112
+ }
113
+ const omitted = omission(key, values.length, malformed);
114
+ return part(malformed ? omitted : { [key]: rows }, omitted);
115
+ };
116
+ const tool = scalar("tool", data.tool);
117
+ const source = scalar("source", data.source);
118
+ const required = sequence("required_args", data.required), optional = sequence("optional_args", data.optional);
119
+ const warnings = data.warnings.length ? sequence("warnings", data.warnings) : undefined;
120
+ const nextWellFormed = data.next.isWellFormed();
121
+ const omittedNext = omission("next_action", 1, !nextWellFormed);
122
+ // This producer knows only its existing status hint; invocation advice stays opaque.
123
+ const next = part(nextWellFormed
124
+ ? data.callable ? { instruction: data.next } : { next: { tool: "inspect", target: `tool:${data.tool}`, view: "status" } }
125
+ : omittedNext, omittedNext);
126
+ const render = () => renderPart({ ...tool.selected, result: data.result, ...source.selected, callable_now: data.callable,
127
+ ...(data.boundary === undefined ? {} : { activation_boundary: data.boundary }), ...required.selected, ...optional.selected,
128
+ ...(warnings ? warnings.selected : {}), ...next.selected });
129
+ // Preserve complete safety notes before large argument lists; all seeds are monotone.
130
+ for (const current of [...(warnings ? [warnings] : []), tool, source, next, required, optional]) {
131
+ if (current.selected === current.full)
132
+ continue;
133
+ const previous = current.selected;
134
+ current.selected = current.full;
135
+ if (render().state !== "available")
136
+ current.selected = previous;
137
+ }
138
+ return render().text;
139
+ }
140
+ function registryNoticeText(kind, name) {
141
+ const report = { activated: "Tool activated", promoted: "Promoted draft recipe", deleted: "Deleted tool" }[kind];
142
+ const selected = name.isWellFormed() ? ToolsResponse.formatMarkdownField("tool", name)
143
+ : "- tool: [unavailable]\n- tool_omission:\n - reason: malformed_selected_text\n - shown: 0\n - omitted: 1\n - total: 1";
144
+ return ToolsResponse.formatMarkdownField("report", report) + "\n" + selected;
145
+ }
146
+ function registrationTransactionFailureText(stage, generation, summary, error) {
147
+ const field = ToolsResponse.formatMarkdownField;
148
+ const part = (key, value) => {
149
+ const wellFormed = value.isWellFormed();
150
+ 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`;
151
+ const full = wellFormed ? field(key, value) : omitted;
152
+ return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
153
+ };
154
+ const selectedGeneration = part("resolution_generation", generation);
155
+ const selectedSummary = part("active_skills_summary", summary);
156
+ const selectedError = part("error", error);
157
+ const render = () => [field("result", "Tool registration transaction failed."), field("stage", stage),
158
+ selectedGeneration.selected, selectedSummary.selected, selectedError.selected].join("\n");
159
+ // Keep a fitting surfaced error before large optional resolution metadata.
160
+ for (const current of [selectedError, selectedGeneration, selectedSummary]) {
161
+ if (current.selected === current.full)
162
+ continue;
163
+ const previous = current.selected;
164
+ current.selected = current.full;
165
+ if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
166
+ current.selected = previous;
167
+ }
168
+ return render();
169
+ }
170
+ function candidateAdmissionFailureText(data) {
171
+ const field = ToolsResponse.formatMarkdownField;
172
+ const part = (key, value, literal = false) => {
173
+ const wellFormed = value.isWellFormed();
174
+ 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`;
175
+ const full = wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted;
176
+ return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
177
+ };
178
+ const diagnostics = part("diagnostics_summary", data.diagnostics);
179
+ const source = data.kind === "source" ? part("source", data.source, true) : undefined;
180
+ const summary = data.kind === "source" ? part("active_skills_summary", data.summary) : undefined;
181
+ const instruction = data.kind === "source" ? part("instruction", data.instruction) : undefined;
182
+ const render = () => [field("field", "recipe"), ...(source ? [source.selected] : []),
183
+ field("result", data.kind === "source" ? "Recipe source validation failed." : "Tool recipe validation failed."),
184
+ diagnostics.selected, ...(summary ? [summary.selected] : []), ...(instruction ? [instruction.selected] : [])].join("\n");
185
+ // Preserve fitting diagnostics before large optional source/context/advice fields.
186
+ for (const current of [diagnostics, source, summary, instruction]) {
187
+ if (!current || current.selected === current.full)
188
+ continue;
189
+ const previous = current.selected;
190
+ current.selected = current.full;
191
+ if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
192
+ current.selected = previous;
193
+ }
194
+ return render();
195
+ }
196
+ function sourceNormalizationFailureText(source, reason, summary, instruction) {
197
+ const field = ToolsResponse.formatMarkdownField;
198
+ const part = (key, value, literal = false) => {
199
+ const wellFormed = value.isWellFormed();
200
+ 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`;
201
+ const full = wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted;
202
+ return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
203
+ };
204
+ const selectedSource = part("source", source, true);
205
+ const selectedReason = part("reason", reason);
206
+ const selectedSummary = part("active_skills_summary", summary);
207
+ const selectedInstruction = part("instruction", instruction);
208
+ const render = () => [field("field", "from"), selectedSource.selected, field("result", "Recipe source is invalid."),
209
+ selectedReason.selected, selectedSummary.selected, selectedInstruction.selected].join("\n");
210
+ for (const current of [selectedSource, selectedReason, selectedSummary, selectedInstruction]) {
211
+ if (current.selected === current.full)
212
+ continue;
213
+ const previous = current.selected;
214
+ current.selected = current.full;
215
+ if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
216
+ current.selected = previous;
217
+ }
218
+ return render();
219
+ }
68
220
  function activeSkillSummary(resolutionContext) {
69
221
  const names = Object.keys(RecipesReferences.getActiveSkillRecipeNamespaces(resolutionContext.activeSkills))
70
222
  .sort()
71
223
  .slice(0, 20);
72
224
  return names.length > 0 ? names.join(", ") : "<none>";
73
225
  }
226
+ function draftPathFailureText(kind, reference) {
227
+ const key = kind === "root" ? "allowed_root" : "path";
228
+ const render = (selected) => "- field: draft\n" + selected + (kind === "root"
229
+ ? "\n- result: Draft must be under the allowed root.\n- instruction: Use inspect target=recipes view=summary to list drafts."
230
+ : "\n- result: Draft not found.");
231
+ const wellFormed = reference.isWellFormed();
232
+ if (wellFormed) {
233
+ const complete = render(ToolsResponse.formatMarkdownField(key, ToolsResponse.formatLiteralBlock(reference)));
234
+ if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
235
+ return complete;
236
+ }
237
+ 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`);
238
+ }
74
239
  function assertDraftPath(deps, draft) {
75
240
  const draftRoot = resolve(getRecipeRoot(deps), "drafts");
76
241
  const path = resolve(draft);
@@ -78,13 +243,62 @@ function assertDraftPath(deps, draft) {
78
243
  if (relation === "" ||
79
244
  relation.startsWith("..") ||
80
245
  resolve(relation) === relation) {
81
- throw new Error(ExecutionOutput.formatToolText(`Draft must be under ${draftRoot}. Use inspect target=recipes view=summary to list drafts.`));
246
+ throw new Error(ExecutionOutput.formatToolText(draftPathFailureText("root", `${draftRoot}`)));
82
247
  }
83
248
  if (!existsSync(path)) {
84
- throw new Error(ExecutionOutput.formatToolText(`Draft not found: ${path}`));
249
+ throw new Error(ExecutionOutput.formatToolText(draftPathFailureText("missing", `${path}`)));
85
250
  }
86
251
  return path;
87
252
  }
253
+ function draftReceiptText(result, tool, callable, next) {
254
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
255
+ const renderPart = (value) => ToolsResponse.renderYamlMetadata(value, budget);
256
+ const part = (key, value, rendered) => {
257
+ const wellFormed = value.isWellFormed();
258
+ const omitted = { [key]: wellFormed ? "[omitted]" : "[unavailable]",
259
+ [key + "_omission"]: { reason: wellFormed ? "response_too_large" : "malformed_selected_text", shown: 0, omitted: 1, total: 1 } };
260
+ const full = wellFormed ? rendered : omitted;
261
+ const complete = renderPart(full), fallback = renderPart(omitted);
262
+ return { full, selected: complete.state === "available" && fallback.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted };
263
+ };
264
+ const identity = part("tool", tool, { tool });
265
+ const flag = part("callable_now", callable, { callable_now: callable });
266
+ // Only this producer's exact canonical read-only status hint is typed, never body advice.
267
+ const status = /^inspect target=(tool:[a-z][a-z0-9_]{0,63}) view=status$/u.exec(next);
268
+ const hint = part("next_action", next, status ? { next: { tool: "inspect", target: status[1], view: "status" } } : { instruction: next });
269
+ const render = () => renderPart({ ...identity.selected, result, source: "draft", ...flag.selected, ...hint.selected });
270
+ for (const current of [identity, flag, hint]) {
271
+ if (current.selected === current.full)
272
+ continue;
273
+ const previous = current.selected;
274
+ current.selected = current.full;
275
+ if (render().state !== "available")
276
+ current.selected = previous;
277
+ }
278
+ return render().text;
279
+ }
280
+ function duplicateRegistrationText(value) {
281
+ const render = (selected) => "- field: name\n" + selected + "\n- result: Tool already registered.\n- instruction: Use update=true to overwrite.";
282
+ const wellFormed = value.isWellFormed();
283
+ if (wellFormed) {
284
+ const complete = render(ToolsResponse.formatMarkdownField("value", value));
285
+ if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
286
+ return complete;
287
+ }
288
+ 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`);
289
+ }
290
+ function draftRecipeFailureText(key, value) {
291
+ const header = "- field: draft\n- result: Draft recipe is invalid.";
292
+ if (value === undefined)
293
+ return header;
294
+ const wellFormed = value.isWellFormed();
295
+ if (wellFormed) {
296
+ const complete = header + "\n" + ToolsResponse.formatMarkdownField(key, value);
297
+ if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
298
+ return complete;
299
+ }
300
+ 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`;
301
+ }
88
302
  function promoteDraftRecipe(name, input, ctx, deps) {
89
303
  const draftPath = assertDraftPath(deps, String(input.draft ?? ""));
90
304
  const targetPath = getToolRecipePath(deps, name);
@@ -94,12 +308,12 @@ function promoteDraftRecipe(name, input, ctx, deps) {
94
308
  if (blocker)
95
309
  throw new Error(ExecutionOutput.formatToolText(blocker));
96
310
  if ((existing || existsSync(targetPath)) && !input.update) {
97
- throw new Error(ExecutionOutput.formatToolText(`Tool "${name}" already registered. Use update=true to overwrite.`));
311
+ throw new Error(ExecutionOutput.formatToolText(duplicateRegistrationText(`${name}`)));
98
312
  }
99
313
  const raw = RecipesReferences.readRawRecipeConfig(draftPath);
100
314
  if (!raw) {
101
315
  const reason = RecipesReferences.diagnoseRawRecipeConfigFailure(draftPath);
102
- throw new Error(ExecutionOutput.formatToolText(`Draft recipe is invalid${reason ? `: ${reason}` : "."}`));
316
+ throw new Error(ExecutionOutput.formatToolText(draftRecipeFailureText("reason", reason ? `${reason}` : undefined)));
103
317
  }
104
318
  const authoredRecipe = buildAuthoredRecipe({
105
319
  ...input,
@@ -109,7 +323,7 @@ function promoteDraftRecipe(name, input, ctx, deps) {
109
323
  const resolutionContext = getRegistrationResolutionContext(ctx, deps);
110
324
  const candidate = RecipesDiscovery.admitUserRecipe(targetPath, resolutionContext, authoredRecipe);
111
325
  if (!candidate.validated || !candidate.tool) {
112
- throw new Error(ExecutionOutput.formatToolText(`Draft recipe is invalid: ${candidate.diagnostics.join("; ")}`));
326
+ throw new Error(ExecutionOutput.formatToolText(draftRecipeFailureText("diagnostics_summary", `${candidate.diagnostics.join("; ")}`)));
113
327
  }
114
328
  mkdirSync(dirname(targetPath), { recursive: true });
115
329
  writeJsonAtomic(targetPath, authoredRecipe);
@@ -119,10 +333,12 @@ function promoteDraftRecipe(name, input, ctx, deps) {
119
333
  const argSummary = RecipesDiscovery.summarizeRegisteredToolArgs(promoted);
120
334
  const callableNow = activation?.callable_now ?? false;
121
335
  const nextActions = registrationNextActions(name, "draft", callableNow);
122
- deps.notify(ctx, `Promoted draft recipe: ${name}`, "info");
336
+ deps.notify(ctx, registryNoticeText("promoted", `${name}`), "info");
123
337
  return {
124
338
  content: [
125
- textContent(ExecutionOutput.formatToolText(`${existing ? "Updated" : "Registered"} tool "${name}" from draft; callable_now=${callableNow}; next=${nextActions[0]}.`)),
339
+ textContent(ExecutionOutput.formatToolText(
340
+ // Preserve the old name→flag→hint template conversions here, after notify.
341
+ draftReceiptText(existing ? "updated" : "registered", `${name}`, `${callableNow}`, `${nextActions[0]}`))),
126
342
  ],
127
343
  details: {
128
344
  active_tool: activation?.active_tool ?? false,
@@ -151,12 +367,25 @@ function persistToolRecipe(deps, name, recipe) {
151
367
  writeJsonAtomic(path, recipe);
152
368
  return path;
153
369
  }
370
+ function registryDeletionText(name, result) {
371
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
372
+ const facts = { result, ...(result === "deleted" ? { instruction: "Reload to remove it from the complete registry." } : {}) };
373
+ const wellFormed = name.isWellFormed();
374
+ if (wellFormed) {
375
+ const complete = ToolsResponse.renderYamlMetadata({ tool: name, ...facts }, budget);
376
+ if (complete.state === "available")
377
+ return complete.text;
378
+ }
379
+ 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);
380
+ // Fixed metadata is well formed and bounded far below budget.
381
+ return fallback.text;
382
+ }
154
383
  function deleteTool(name, ctx, deps) {
155
384
  const tools = deps.getTools();
156
385
  if (!tools.has(name)) {
157
386
  return {
158
387
  content: [
159
- textContent(ExecutionOutput.formatToolText(`Tool "${name}" not found.`)),
388
+ textContent(ExecutionOutput.formatToolText(registryDeletionText(`${name}`, "not found"))),
160
389
  ],
161
390
  details: { tool: name },
162
391
  };
@@ -166,10 +395,10 @@ function deleteTool(name, ctx, deps) {
166
395
  unlinkSync(recipePath);
167
396
  tools.delete(name);
168
397
  deps.setActiveTools(deps.getActiveTools().filter((toolName) => toolName !== name));
169
- deps.notify(ctx, `Deleted tool: ${name}`, "info");
398
+ deps.notify(ctx, registryNoticeText("deleted", `${name}`), "info");
170
399
  return {
171
400
  content: [
172
- textContent(ExecutionOutput.formatToolText(`Deleted tool "${name}". Reload to remove it from the complete registry.`)),
401
+ textContent(ExecutionOutput.formatToolText(registryDeletionText(`${name}`, "deleted"))),
173
402
  ],
174
403
  details: { tool: name },
175
404
  };
@@ -209,7 +438,7 @@ function assertTemplateIsNotNestedRecipe(value) {
209
438
  looksLikeRecipeReference(record.template) &&
210
439
  (Object.keys(record).length === 1 ||
211
440
  ["artifacts", "async", "control", "description", "imports", "values"].some((key) => Object.hasOwn(record, key)))) {
212
- throw new Error(ExecutionOutput.formatToolText("Nested Recipe-shaped templates are not a register_tool source mode. Next: retry with from=<skill>/<recipe> and defaults={...}."));
441
+ throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: Nested Recipe-shaped templates are not a register_tool source mode.\n- instruction: retry with from=<skill>/<recipe> and defaults={...}."));
213
442
  }
214
443
  }
215
444
  function getInputTemplate(value) {
@@ -223,14 +452,14 @@ function getInputTemplate(value) {
223
452
  template: value,
224
453
  });
225
454
  if (steps.length === 0)
226
- throw new Error(ExecutionOutput.formatToolText("Tool template sequence is empty."));
455
+ throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: Tool template sequence is empty."));
227
456
  return value;
228
457
  }
229
458
  if (value && typeof value === "object") {
230
459
  CommandTemplates.expandCommandTemplateConfigs(value);
231
460
  return value;
232
461
  }
233
- throw new Error(ExecutionOutput.formatToolText("Tool template must be a string, object, or sequence."));
462
+ throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: unsupported template type\n- supported_types:\n - string\n - object\n - sequence"));
234
463
  }
235
464
  function getRegistrationResolutionContext(ctx, deps) {
236
465
  const runtimeContext = deps.getRecipeResolutionContext?.();
@@ -247,7 +476,7 @@ function normalizeDefaults(value) {
247
476
  if (value === undefined)
248
477
  return undefined;
249
478
  if (!value || typeof value !== "object" || Array.isArray(value)) {
250
- throw new Error(ExecutionOutput.formatToolText("Tool defaults must be an object."));
479
+ throw new Error(ExecutionOutput.formatToolText("- field: defaults\n- result: Tool defaults must be an object."));
251
480
  }
252
481
  return { ...value };
253
482
  }
@@ -279,13 +508,13 @@ function buildAuthoredRecipe(input, existing, existingRecipe) {
279
508
  const from = typeof input.from === "string" ? input.from.trim() : "";
280
509
  const description = (input.description ?? existing?.description ?? "").trim();
281
510
  if (!description && !from) {
282
- throw new Error(ExecutionOutput.formatToolText("Tool description is required for command-template registration."));
511
+ throw new Error(ExecutionOutput.formatToolText("- field: description\n- result: Tool description is required for command-template registration."));
283
512
  }
284
513
  const inputDefaults = normalizeDefaults(input.defaults);
285
514
  if (from) {
286
515
  const runtimeOwnedDefault = Object.keys(inputDefaults ?? {}).find((key) => RecipesReferences.isRuntimeOwnedRecipeInput(key));
287
516
  if (runtimeOwnedDefault) {
288
- throw new Error(ExecutionOutput.formatToolText(`Tool defaults cannot set runtime-owned input: ${runtimeOwnedDefault}. Next: remove that default and retry register_tool; the runtime supplies it.`));
517
+ throw new Error(ExecutionOutput.formatToolText(`- 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.`));
289
518
  }
290
519
  const authored = {
291
520
  ...(description ? { description } : {}),
@@ -301,13 +530,13 @@ function buildAuthoredRecipe(input, existing, existingRecipe) {
301
530
  }
302
531
  const template = getInputTemplate(input.template);
303
532
  if (template === null) {
304
- throw new Error(ExecutionOutput.formatToolText("Tool template cannot be null here."));
533
+ throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: Tool template cannot be null here."));
305
534
  }
306
535
  const finalTemplate = template === undefined || template === ""
307
536
  ? existingRecipe?.template ?? existing?.template
308
537
  : template;
309
538
  if (!finalTemplate) {
310
- throw new Error(ExecutionOutput.formatToolText("Tool template is required."));
539
+ throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: Tool template is required."));
311
540
  }
312
541
  const authored = {
313
542
  ...(existingRecipe ?? {}),
@@ -335,24 +564,34 @@ function buildAuthoredRecipe(input, existing, existingRecipe) {
335
564
  function assertRegisterToolInputModes(input) {
336
565
  const record = input;
337
566
  if (Object.hasOwn(record, "values")) {
338
- throw new Error(ExecutionOutput.formatToolText("register_tool values was removed. Next: retry register_tool with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition."));
567
+ throw new Error(ExecutionOutput.formatToolText("- 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."));
339
568
  }
340
569
  const fromProvided = Object.hasOwn(record, "from");
341
570
  if (fromProvided &&
342
571
  (typeof input.from !== "string" || input.from.trim().length === 0)) {
343
- throw new Error(ExecutionOutput.formatToolText("Tool from must be a non-empty Recipe reference. Next: retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path."));
572
+ throw new Error(ExecutionOutput.formatToolText("- 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."));
344
573
  }
345
574
  const draftProvided = typeof input.draft === "string" && input.draft.trim().length > 0;
346
575
  const templateProvided = Object.hasOwn(record, "template");
347
576
  const modeCount = Number(fromProvided) + Number(draftProvided) + Number(templateProvided);
348
577
  if (modeCount > 1) {
349
- throw new Error(ExecutionOutput.formatToolText("Use exactly one register_tool source mode: from, template, or draft. Next: remove the extra source fields and retry register_tool."));
578
+ throw new Error(ExecutionOutput.formatToolText("- 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."));
350
579
  }
351
580
  if (fromProvided &&
352
581
  (Object.hasOwn(record, "args") || Object.hasOwn(record, "async"))) {
353
- throw new Error(ExecutionOutput.formatToolText("register_tool from inherits args and async behavior. Next: retry register_tool with from and optional defaults only; omit args and async."));
582
+ throw new Error(ExecutionOutput.formatToolText("- 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."));
354
583
  }
355
584
  }
585
+ function reservedRegistrationNameText(value) {
586
+ const render = (selected) => "- field: name\n" + selected + "\n- result: Reserved tool name.";
587
+ const wellFormed = value.isWellFormed();
588
+ if (wellFormed) {
589
+ const complete = render(ToolsResponse.formatMarkdownField("value", value));
590
+ if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
591
+ return complete;
592
+ }
593
+ 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`);
594
+ }
356
595
  function executeRegisterToolUnlocked(params, ctx, deps) {
357
596
  const rawInput = params && typeof params === "object"
358
597
  ? params
@@ -363,9 +602,9 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
363
602
  return listTools(deps);
364
603
  const name = Identity.normalizeToolName(input.name);
365
604
  if (!name)
366
- throw new Error(ExecutionOutput.formatToolText("Invalid tool name."));
605
+ throw new Error(ExecutionOutput.formatToolText("- field: name\n- result: Invalid tool name."));
367
606
  if (deps.reservedToolNames.has(name)) {
368
- throw new Error(ExecutionOutput.formatToolText(`Reserved tool name: ${name}`));
607
+ throw new Error(ExecutionOutput.formatToolText(reservedRegistrationNameText(`${name}`)));
369
608
  }
370
609
  if (typeof input.draft === "string" && input.draft.trim()) {
371
610
  return promoteDraftRecipe(name, input, ctx, deps);
@@ -384,7 +623,7 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
384
623
  const diagnosticSource = requestedSource?.startsWith("skill:")
385
624
  ? requestedSource.slice("skill:".length)
386
625
  : requestedSource ?? "";
387
- throw new Error(ExecutionOutput.formatToolText(`Recipe source "${requestedSource}" is invalid: ${reason}. Active Skills: ${activeSkillSummary(resolutionContext)}. Next: ${recipeDoctorAction(diagnosticSource)}`));
626
+ throw new Error(ExecutionOutput.formatToolText(sourceNormalizationFailureText(`${requestedSource}`, `${reason}`, `${activeSkillSummary(resolutionContext)}`, `${recipeDoctorAction(diagnosticSource)}`)));
388
627
  }
389
628
  }
390
629
  const templateProvided = Object.hasOwn(input, "template");
@@ -398,18 +637,18 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
398
637
  if (blocker)
399
638
  throw new Error(ExecutionOutput.formatToolText(blocker));
400
639
  if ((authoritative.exists || existing) && !input.update) {
401
- throw new Error(ExecutionOutput.formatToolText(`Tool "${name}" already registered. Use update=true to overwrite.`));
640
+ throw new Error(ExecutionOutput.formatToolText(duplicateRegistrationText(`${name}`)));
402
641
  }
403
642
  if (template === undefined && !input.from && !existing) {
404
- throw new Error(ExecutionOutput.formatToolText("New registration requires exactly one source mode: from, template, or draft. Next: retry register_tool with one source field."));
643
+ throw new Error(ExecutionOutput.formatToolText("- 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."));
405
644
  }
406
645
  const recipePath = getToolRecipePath(deps, name);
407
646
  const authoredRecipe = buildAuthoredRecipe(input, existing, authoritative.recipe);
408
647
  const candidate = RecipesDiscovery.admitUserRecipe(recipePath, resolutionContext, authoredRecipe);
409
648
  if (!candidate.validated || !candidate.tool) {
410
649
  const sourceDiagnostic = requestedSource
411
- ? `Recipe source "${requestedSource}" validation failed: ${candidate.diagnostics.join("; ")}. Active Skills: ${activeSkillSummary(resolutionContext)}. Next: ${recipeDoctorAction(requestedSource)}`
412
- : `Tool recipe validation failed: ${candidate.diagnostics.join("; ")}`;
650
+ ? candidateAdmissionFailureText({ kind: "source", source: `${requestedSource}`, diagnostics: `${candidate.diagnostics.join("; ")}`, summary: `${activeSkillSummary(resolutionContext)}`, instruction: `${recipeDoctorAction(requestedSource)}` })
651
+ : candidateAdmissionFailureText({ kind: "recipe", diagnostics: `${candidate.diagnostics.join("; ")}` });
413
652
  throw new Error(ExecutionOutput.formatToolText(sourceDiagnostic));
414
653
  }
415
654
  const activeBefore = deps.getActiveTools();
@@ -451,9 +690,9 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
451
690
  tools.delete(name);
452
691
  }
453
692
  deps.setActiveTools(activeBefore);
454
- throw new Error(ExecutionOutput.formatToolText(`Tool registration transaction failed at ${transactionStage} (${resolutionContext.generation}; active Skills: ${activeSkillSummary(resolutionContext)}): ${error instanceof Error ? error.message : String(error)}`));
693
+ throw new Error(ExecutionOutput.formatToolText(registrationTransactionFailureText(`${transactionStage}`, `${resolutionContext.generation}`, `${activeSkillSummary(resolutionContext)}`, `${error instanceof Error ? error.message : String(error)}`)));
455
694
  }
456
- deps.notify(ctx, `Tool activated: ${name}`, "info");
695
+ deps.notify(ctx, registryNoticeText("activated", `${name}`), "info");
457
696
  const templateWarnings = CommandTemplates.getCommandTemplateWarnings(typeof cfg.recipe?.template === "object" && !Array.isArray(cfg.recipe.template)
458
697
  ? cfg.recipe.template
459
698
  : { template: cfg.recipe?.template ?? cfg.template });
@@ -464,12 +703,17 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
464
703
  const source = requestedSource ?? "template";
465
704
  const argSummary = RecipesDiscovery.summarizeRegisteredToolArgs(cfg);
466
705
  const nextActions = registrationNextActions(name, source, callableNow);
706
+ let displayBoundary;
467
707
  const outcomeText = callableNow
468
708
  ? `${existing ? "Updated" : "Registered"} tool "${name}" from ${source}; callable_now=true`
469
- : `Persisted tool "${name}" from ${source}; callable_now=false; activation_boundary=${activationBoundary(activation)}`;
709
+ : `Persisted tool "${name}" from ${source}; callable_now=false; activation_boundary=${displayBoundary = activationBoundary(activation)}`;
710
+ // Keep original fallible advisory evaluation/order after effects, before the new text.
711
+ void `${outcomeText}; required=${Schema.formatToolArgs(argSummary.required)}; optional=${Schema.formatToolArgs(argSummary.optional)}; next=${nextActions[0]}.${warningText}`;
470
712
  return {
471
713
  content: [
472
- textContent(ExecutionOutput.formatToolText(`${outcomeText}; required=${Schema.formatToolArgs(argSummary.required)}; optional=${Schema.formatToolArgs(argSummary.optional)}; next=${nextActions[0]}.${warningText}`)),
714
+ textContent(ExecutionOutput.formatToolText(registrationReceiptText({ tool: name, result: callableNow ? existing ? "updated" : "registered" : "persisted",
715
+ source, callable: Boolean(callableNow), boundary: displayBoundary, required: argSummary.required,
716
+ optional: argSummary.optional, warnings: templateWarnings, next: nextActions[0] }))),
473
717
  ],
474
718
  details: {
475
719
  active_tool: activation?.active_tool ?? false,