@llblab/pi-actors 0.56.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/AGENTS.md +9 -9
  2. package/BACKLOG.md +4 -23
  3. package/CHANGELOG.md +13 -0
  4. package/README.md +5 -2
  5. package/dist/lib/execution-output.js +13 -2
  6. package/dist/lib/observability.js +9 -14
  7. package/dist/lib/registry.js +132 -157
  8. package/dist/lib/run-ui-runtime.js +18 -27
  9. package/dist/lib/runs-process-windows-mi.d.ts +71 -0
  10. package/dist/lib/runs-process-windows-mi.js +146 -3
  11. package/dist/lib/tools-inspect.js +109 -59
  12. package/dist/lib/tools-local.js +16 -8
  13. package/dist/lib/tools-message.js +76 -65
  14. package/dist/lib/tools-response.d.ts +18 -0
  15. package/dist/lib/tools-response.js +88 -21
  16. package/dist/lib/tools-spawn.js +19 -12
  17. package/dist/skills/actors/SKILL.md +3 -3
  18. package/dist/skills/actors/references/diagnostics.md +3 -0
  19. package/docs/README.md +1 -0
  20. package/docs/actor-inspector.md +2 -2
  21. package/docs/agent-experience.md +9 -1
  22. package/docs/inactive-foundations-inventory.md +73 -0
  23. package/docs/inspection.md +9 -8
  24. package/docs/output-projection-map.md +16 -14
  25. package/docs/windows-process-observation.md +2 -2
  26. package/lib/execution-output.ts +13 -2
  27. package/lib/observability.ts +9 -11
  28. package/lib/registry.ts +138 -164
  29. package/lib/run-ui-runtime.ts +18 -34
  30. package/lib/runs-process-windows-mi.ts +167 -3
  31. package/lib/tools-inspect.ts +109 -59
  32. package/lib/tools-local.ts +26 -15
  33. package/lib/tools-message.ts +85 -58
  34. package/lib/tools-response.ts +97 -20
  35. package/lib/tools-spawn.ts +19 -12
  36. package/package.json +1 -1
  37. package/skills/actors/SKILL.md +3 -3
  38. package/skills/actors/references/diagnostics.md +3 -0
@@ -21,18 +21,18 @@ function textContent(text) {
21
21
  return { type: "text", text };
22
22
  }
23
23
  function registryListText(rows) {
24
- const coverage = (shown, reason) => "\n- registered_tools_coverage:" +
25
- (reason ? `\n - reason: ${reason}` : "") + `\n - shown: ${shown}\n - omitted: ${rows.length - shown}\n - total: ${rows.length}`;
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
26
  const malformed = rows.some(row => !row.isWellFormed());
27
27
  if (!malformed) {
28
- // Rows already contain the old compiler-owned bullet and selected key coercion.
29
- const body = rows.length ? "- registered_tools:\n" + rows.map(row => " " + row.replaceAll(/\r\n|\r|\n/gu, newline => newline + " ")).join("\n")
30
- : "- registered_tools: none";
31
- const complete = body + coverage(rows.length);
32
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
33
- return complete;
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;
34
32
  }
35
- return `- registered_tools: [${malformed ? "unavailable" : "omitted"}]` + coverage(0, malformed ? "malformed_selected_text" : "response_too_large");
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
36
  }
37
37
  function listTools(deps) {
38
38
  const names = [...deps.getTools().keys()].sort();
@@ -82,15 +82,35 @@ function registrationNextActions(tool, source, callableNow) {
82
82
  : [recipeDoctorAction(source)]),
83
83
  ];
84
84
  }
85
+ const REGISTRY_METADATA_BUDGET = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
86
+ /** Bare block-YAML registry document; builders own whole-value/omission policy and the byte budget. */
87
+ function registryMetadataText(value) {
88
+ return ToolsResponse.renderYamlMetadata(value, REGISTRY_METADATA_BUDGET).text;
89
+ }
90
+ /** One opaque selected scalar as a whole value or an explicit whole-field omission, never a clip. */
91
+ function registrySelected(key, value) {
92
+ return ToolsResponse.selectYamlField(key, value, REGISTRY_METADATA_BUDGET);
93
+ }
94
+ /** Grow whole selected fields in priority order while the complete document still fits. */
95
+ function growRegistryDocument(render, order) {
96
+ return ToolsResponse.growYamlDocument(render, order, REGISTRY_METADATA_BUDGET);
97
+ }
85
98
  /** Read-time text only: callers supply the original post-activation selections, not live ports. */
86
99
  function registrationReceiptText(data) {
87
- const field = ToolsResponse.formatMarkdownField;
88
- const omission = (key, count, malformed) => `- ${key}: [${malformed ? "unavailable" : "omitted"}]\n- ${key}_omission:\n - reason: ${malformed ? "malformed_selected_text" : "response_too_large"}\n - shown: 0\n - omitted: ${count}\n - total: ${count}`;
89
- const part = (full, omitted) => ({ full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted });
90
- const scalar = (key, value, literal = false) => {
100
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
101
+ const renderPart = (value) => ToolsResponse.renderYamlMetadata(value, budget);
102
+ const omission = (key, count, malformed) => ({
103
+ [key]: malformed ? "[unavailable]" : "[omitted]",
104
+ [key + "_omission"]: { reason: malformed ? "malformed_selected_text" : "response_too_large", shown: 0, omitted: count, total: count },
105
+ });
106
+ const part = (full, omitted) => {
107
+ const complete = renderPart(full), fallback = renderPart(omitted);
108
+ return { full, selected: complete.state === "available" && fallback.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted };
109
+ };
110
+ const scalar = (key, value) => {
91
111
  const wellFormed = value.isWellFormed();
92
112
  const omitted = omission(key, 1, !wellFormed);
93
- return part(wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted, omitted);
113
+ return part(wellFormed ? { [key]: value } : omitted, omitted);
94
114
  };
95
115
  const sequence = (key, values) => {
96
116
  const rows = [];
@@ -101,34 +121,34 @@ function registrationReceiptText(data) {
101
121
  malformed = true;
102
122
  break;
103
123
  }
104
- rows.push(" - " + value.replaceAll(/\r\n|\r|\n/gu, newline => newline + " "));
124
+ rows.push(value);
105
125
  }
106
126
  const omitted = omission(key, values.length, malformed);
107
- return part(malformed ? omitted : rows.length ? `- ${key}:\n${rows.join("\n")}` : field(key, "none"), omitted);
127
+ return part(malformed ? omitted : { [key]: rows }, omitted);
108
128
  };
109
129
  const tool = scalar("tool", data.tool);
110
- const source = scalar("source", data.source, data.source !== "template");
130
+ const source = scalar("source", data.source);
111
131
  const required = sequence("required_args", data.required), optional = sequence("optional_args", data.optional);
112
132
  const warnings = data.warnings.length ? sequence("warnings", data.warnings) : undefined;
113
133
  const nextWellFormed = data.next.isWellFormed();
114
134
  const omittedNext = omission("next_action", 1, !nextWellFormed);
115
135
  // This producer knows only its existing status hint; invocation advice stays opaque.
116
136
  const next = part(nextWellFormed
117
- ? data.callable ? field("instruction", data.next) : ToolsResponse.formatInspectNextAction(`tool:${data.tool}`, "status")
137
+ ? data.callable ? { instruction: data.next } : { next: { tool: "inspect", target: `tool:${data.tool}`, view: "status" } }
118
138
  : omittedNext, omittedNext);
119
- const render = () => [tool.selected, field("result", data.result), source.selected, field("callable_now", data.callable ? "true" : "false"),
120
- ...(data.boundary === undefined ? [] : [field("activation_boundary", data.boundary)]), required.selected, optional.selected,
121
- ...(warnings ? [warnings.selected] : []), next.selected].join("\n");
139
+ const render = () => renderPart({ ...tool.selected, result: data.result, ...source.selected, callable_now: data.callable,
140
+ ...(data.boundary === undefined ? {} : { activation_boundary: data.boundary }), ...required.selected, ...optional.selected,
141
+ ...(warnings ? warnings.selected : {}), ...next.selected });
122
142
  // Preserve complete safety notes before large argument lists; all seeds are monotone.
123
143
  for (const current of [...(warnings ? [warnings] : []), tool, source, next, required, optional]) {
124
144
  if (current.selected === current.full)
125
145
  continue;
126
146
  const previous = current.selected;
127
147
  current.selected = current.full;
128
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
148
+ if (render().state !== "available")
129
149
  current.selected = previous;
130
150
  }
131
- return render();
151
+ return render().text;
132
152
  }
133
153
  function registryNoticeText(kind, name) {
134
154
  const report = { activated: "Tool activated", promoted: "Promoted draft recipe", deleted: "Deleted tool" }[kind];
@@ -137,78 +157,33 @@ function registryNoticeText(kind, name) {
137
157
  return ToolsResponse.formatMarkdownField("report", report) + "\n" + selected;
138
158
  }
139
159
  function registrationTransactionFailureText(stage, generation, summary, error) {
140
- const field = ToolsResponse.formatMarkdownField;
141
- const part = (key, value) => {
142
- const wellFormed = value.isWellFormed();
143
- 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`;
144
- const full = wellFormed ? field(key, value) : omitted;
145
- return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
146
- };
147
- const selectedGeneration = part("resolution_generation", generation);
148
- const selectedSummary = part("active_skills_summary", summary);
149
- const selectedError = part("error", error);
150
- const render = () => [field("result", "Tool registration transaction failed."), field("stage", stage),
151
- selectedGeneration.selected, selectedSummary.selected, selectedError.selected].join("\n");
160
+ const selectedGeneration = registrySelected("resolution_generation", generation);
161
+ const selectedSummary = registrySelected("active_skills_summary", summary);
162
+ const selectedError = registrySelected("error", error);
163
+ const render = () => ({ result: "Tool registration transaction failed.", stage,
164
+ ...selectedGeneration.selected, ...selectedSummary.selected, ...selectedError.selected });
152
165
  // Keep a fitting surfaced error before large optional resolution metadata.
153
- for (const current of [selectedError, selectedGeneration, selectedSummary]) {
154
- if (current.selected === current.full)
155
- continue;
156
- const previous = current.selected;
157
- current.selected = current.full;
158
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
159
- current.selected = previous;
160
- }
161
- return render();
166
+ return growRegistryDocument(render, [selectedError, selectedGeneration, selectedSummary]);
162
167
  }
163
168
  function candidateAdmissionFailureText(data) {
164
- const field = ToolsResponse.formatMarkdownField;
165
- const part = (key, value, literal = false) => {
166
- const wellFormed = value.isWellFormed();
167
- 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`;
168
- const full = wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted;
169
- return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
170
- };
171
- const diagnostics = part("diagnostics_summary", data.diagnostics);
172
- const source = data.kind === "source" ? part("source", data.source, true) : undefined;
173
- const summary = data.kind === "source" ? part("active_skills_summary", data.summary) : undefined;
174
- const instruction = data.kind === "source" ? part("instruction", data.instruction) : undefined;
175
- const render = () => [field("field", "recipe"), ...(source ? [source.selected] : []),
176
- field("result", data.kind === "source" ? "Recipe source validation failed." : "Tool recipe validation failed."),
177
- diagnostics.selected, ...(summary ? [summary.selected] : []), ...(instruction ? [instruction.selected] : [])].join("\n");
169
+ const diagnostics = registrySelected("diagnostics_summary", data.diagnostics);
170
+ const source = data.kind === "source" ? registrySelected("source", data.source) : undefined;
171
+ const summary = data.kind === "source" ? registrySelected("active_skills_summary", data.summary) : undefined;
172
+ const instruction = data.kind === "source" ? registrySelected("instruction", data.instruction) : undefined;
173
+ const render = () => ({ field: "recipe", ...(source ? source.selected : {}),
174
+ result: data.kind === "source" ? "Recipe source validation failed." : "Tool recipe validation failed.",
175
+ ...diagnostics.selected, ...(summary ? summary.selected : {}), ...(instruction ? instruction.selected : {}) });
178
176
  // Preserve fitting diagnostics before large optional source/context/advice fields.
179
- for (const current of [diagnostics, source, summary, instruction]) {
180
- if (!current || current.selected === current.full)
181
- continue;
182
- const previous = current.selected;
183
- current.selected = current.full;
184
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
185
- current.selected = previous;
186
- }
187
- return render();
177
+ return growRegistryDocument(render, [diagnostics, ...(source ? [source] : []), ...(summary ? [summary] : []), ...(instruction ? [instruction] : [])]);
188
178
  }
189
179
  function sourceNormalizationFailureText(source, reason, summary, instruction) {
190
- const field = ToolsResponse.formatMarkdownField;
191
- const part = (key, value, literal = false) => {
192
- const wellFormed = value.isWellFormed();
193
- 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`;
194
- const full = wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted;
195
- return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
196
- };
197
- const selectedSource = part("source", source, true);
198
- const selectedReason = part("reason", reason);
199
- const selectedSummary = part("active_skills_summary", summary);
200
- const selectedInstruction = part("instruction", instruction);
201
- const render = () => [field("field", "from"), selectedSource.selected, field("result", "Recipe source is invalid."),
202
- selectedReason.selected, selectedSummary.selected, selectedInstruction.selected].join("\n");
203
- for (const current of [selectedSource, selectedReason, selectedSummary, selectedInstruction]) {
204
- if (current.selected === current.full)
205
- continue;
206
- const previous = current.selected;
207
- current.selected = current.full;
208
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
209
- current.selected = previous;
210
- }
211
- return render();
180
+ const selectedSource = registrySelected("source", source);
181
+ const selectedReason = registrySelected("reason", reason);
182
+ const selectedSummary = registrySelected("active_skills_summary", summary);
183
+ const selectedInstruction = registrySelected("instruction", instruction);
184
+ const render = () => ({ field: "from", ...selectedSource.selected, result: "Recipe source is invalid.",
185
+ ...selectedReason.selected, ...selectedSummary.selected, ...selectedInstruction.selected });
186
+ return growRegistryDocument(render, [selectedSource, selectedReason, selectedSummary, selectedInstruction]);
212
187
  }
213
188
  function activeSkillSummary(resolutionContext) {
214
189
  const names = Object.keys(RecipesReferences.getActiveSkillRecipeNamespaces(resolutionContext.activeSkills))
@@ -217,17 +192,11 @@ function activeSkillSummary(resolutionContext) {
217
192
  return names.length > 0 ? names.join(", ") : "<none>";
218
193
  }
219
194
  function draftPathFailureText(kind, reference) {
220
- const key = kind === "root" ? "allowed_root" : "path";
221
- const render = (selected) => "- field: draft\n" + selected + (kind === "root"
222
- ? "\n- result: Draft must be under the allowed root.\n- instruction: Use inspect target=recipes view=summary to list drafts."
223
- : "\n- result: Draft not found.");
224
- const wellFormed = reference.isWellFormed();
225
- if (wellFormed) {
226
- const complete = render(ToolsResponse.formatMarkdownField(key, ToolsResponse.formatLiteralBlock(reference)));
227
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
228
- return complete;
229
- }
230
- 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`);
195
+ const selected = registrySelected(kind === "root" ? "allowed_root" : "path", reference);
196
+ const render = () => ({ field: "draft", ...selected.selected,
197
+ result: kind === "root" ? "Draft must be under the allowed root." : "Draft not found.",
198
+ ...(kind === "root" ? { instruction: "Use inspect target=recipes view=summary to list drafts." } : {}) });
199
+ return growRegistryDocument(render, [selected]);
231
200
  }
232
201
  function assertDraftPath(deps, draft) {
233
202
  const draftRoot = resolve(getRecipeRoot(deps), "drafts");
@@ -244,50 +213,42 @@ function assertDraftPath(deps, draft) {
244
213
  return path;
245
214
  }
246
215
  function draftReceiptText(result, tool, callable, next) {
247
- const field = ToolsResponse.formatMarkdownField;
216
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
217
+ const renderPart = (value) => ToolsResponse.renderYamlMetadata(value, budget);
248
218
  const part = (key, value, rendered) => {
249
219
  const wellFormed = value.isWellFormed();
250
- 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`;
220
+ const omitted = { [key]: wellFormed ? "[omitted]" : "[unavailable]",
221
+ [key + "_omission"]: { reason: wellFormed ? "response_too_large" : "malformed_selected_text", shown: 0, omitted: 1, total: 1 } };
251
222
  const full = wellFormed ? rendered : omitted;
252
- return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
223
+ const complete = renderPart(full), fallback = renderPart(omitted);
224
+ return { full, selected: complete.state === "available" && fallback.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted };
253
225
  };
254
- const identity = part("tool", tool, field("tool", tool));
255
- const flag = part("callable_now", callable, field("callable_now", callable));
226
+ const identity = part("tool", tool, { tool });
227
+ const flag = part("callable_now", callable, { callable_now: callable });
256
228
  // Only this producer's exact canonical read-only status hint is typed, never body advice.
257
229
  const status = /^inspect target=(tool:[a-z][a-z0-9_]{0,63}) view=status$/u.exec(next);
258
- const hint = part("next_action", next, status ? ToolsResponse.formatInspectNextAction(status[1], "status") : field("instruction", next));
259
- const render = () => [identity.selected, field("result", result), field("source", "draft"), flag.selected, hint.selected].join("\n");
230
+ const hint = part("next_action", next, status ? { next: { tool: "inspect", target: status[1], view: "status" } } : { instruction: next });
231
+ const render = () => renderPart({ ...identity.selected, result, source: "draft", ...flag.selected, ...hint.selected });
260
232
  for (const current of [identity, flag, hint]) {
261
233
  if (current.selected === current.full)
262
234
  continue;
263
235
  const previous = current.selected;
264
236
  current.selected = current.full;
265
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
237
+ if (render().state !== "available")
266
238
  current.selected = previous;
267
239
  }
268
- return render();
240
+ return render().text;
269
241
  }
270
242
  function duplicateRegistrationText(value) {
271
- const render = (selected) => "- field: name\n" + selected + "\n- result: Tool already registered.\n- instruction: Use update=true to overwrite.";
272
- const wellFormed = value.isWellFormed();
273
- if (wellFormed) {
274
- const complete = render(ToolsResponse.formatMarkdownField("value", value));
275
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
276
- return complete;
277
- }
278
- 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`);
243
+ const selected = registrySelected("value", value);
244
+ const render = () => ({ field: "name", ...selected.selected,
245
+ result: "Tool already registered.", instruction: "Use update=true to overwrite." });
246
+ return growRegistryDocument(render, [selected]);
279
247
  }
280
248
  function draftRecipeFailureText(key, value) {
281
- const header = "- field: draft\n- result: Draft recipe is invalid.";
282
- if (value === undefined)
283
- return header;
284
- const wellFormed = value.isWellFormed();
285
- if (wellFormed) {
286
- const complete = header + "\n" + ToolsResponse.formatMarkdownField(key, value);
287
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
288
- return complete;
289
- }
290
- 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`;
249
+ const selected = value === undefined ? undefined : registrySelected(key, value);
250
+ const render = () => ({ field: "draft", result: "Draft recipe is invalid.", ...(selected ? selected.selected : {}) });
251
+ return growRegistryDocument(render, selected ? [selected] : []);
291
252
  }
292
253
  function promoteDraftRecipe(name, input, ctx, deps) {
293
254
  const draftPath = assertDraftPath(deps, String(input.draft ?? ""));
@@ -358,17 +319,17 @@ function persistToolRecipe(deps, name, recipe) {
358
319
  return path;
359
320
  }
360
321
  function registryDeletionText(name, result) {
361
- const field = ToolsResponse.formatMarkdownField;
362
- const facts = [field("result", result), ...(result === "deleted"
363
- ? [field("instruction", "Reload to remove it from the complete registry.")] : [])];
364
- const render = (identity) => [identity, ...facts].join("\n");
322
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
323
+ const facts = { result, ...(result === "deleted" ? { instruction: "Reload to remove it from the complete registry." } : {}) };
365
324
  const wellFormed = name.isWellFormed();
366
325
  if (wellFormed) {
367
- const complete = render(field("tool", name));
368
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
369
- return complete;
326
+ const complete = ToolsResponse.renderYamlMetadata({ tool: name, ...facts }, budget);
327
+ if (complete.state === "available")
328
+ return complete.text;
370
329
  }
371
- return render(`- tool: [${wellFormed ? "omitted" : "unavailable"}]\n- tool_omission:\n - reason: ${wellFormed ? "response_too_large" : "malformed_selected_text"}\n - shown: 0\n - omitted: 1\n - total: 1`);
330
+ 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);
331
+ // Fixed metadata is well formed and bounded far below budget.
332
+ return fallback.text;
372
333
  }
373
334
  function deleteTool(name, ctx, deps) {
374
335
  const tools = deps.getTools();
@@ -428,7 +389,9 @@ function assertTemplateIsNotNestedRecipe(value) {
428
389
  looksLikeRecipeReference(record.template) &&
429
390
  (Object.keys(record).length === 1 ||
430
391
  ["artifacts", "async", "control", "description", "imports", "values"].some((key) => Object.hasOwn(record, key)))) {
431
- 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={...}."));
392
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "template",
393
+ result: "Nested Recipe-shaped templates are not a register_tool source mode.",
394
+ instruction: "retry with from=<skill>/<recipe> and defaults={...}." })));
432
395
  }
433
396
  }
434
397
  function getInputTemplate(value) {
@@ -442,14 +405,15 @@ function getInputTemplate(value) {
442
405
  template: value,
443
406
  });
444
407
  if (steps.length === 0)
445
- throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: Tool template sequence is empty."));
408
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template sequence is empty." })));
446
409
  return value;
447
410
  }
448
411
  if (value && typeof value === "object") {
449
412
  CommandTemplates.expandCommandTemplateConfigs(value);
450
413
  return value;
451
414
  }
452
- throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: unsupported template type\n- supported_types:\n - string\n - object\n - sequence"));
415
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "unsupported template type",
416
+ supported_types: ["string", "object", "sequence"] })));
453
417
  }
454
418
  function getRegistrationResolutionContext(ctx, deps) {
455
419
  const runtimeContext = deps.getRecipeResolutionContext?.();
@@ -466,7 +430,7 @@ function normalizeDefaults(value) {
466
430
  if (value === undefined)
467
431
  return undefined;
468
432
  if (!value || typeof value !== "object" || Array.isArray(value)) {
469
- throw new Error(ExecutionOutput.formatToolText("- field: defaults\n- result: Tool defaults must be an object."));
433
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "defaults", result: "Tool defaults must be an object." })));
470
434
  }
471
435
  return { ...value };
472
436
  }
@@ -498,13 +462,17 @@ function buildAuthoredRecipe(input, existing, existingRecipe) {
498
462
  const from = typeof input.from === "string" ? input.from.trim() : "";
499
463
  const description = (input.description ?? existing?.description ?? "").trim();
500
464
  if (!description && !from) {
501
- throw new Error(ExecutionOutput.formatToolText("- field: description\n- result: Tool description is required for command-template registration."));
465
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "description",
466
+ result: "Tool description is required for command-template registration." })));
502
467
  }
503
468
  const inputDefaults = normalizeDefaults(input.defaults);
504
469
  if (from) {
505
470
  const runtimeOwnedDefault = Object.keys(inputDefaults ?? {}).find((key) => RecipesReferences.isRuntimeOwnedRecipeInput(key));
506
471
  if (runtimeOwnedDefault) {
507
- 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.`));
472
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "defaults",
473
+ ...registrySelected("key", runtimeOwnedDefault).selected,
474
+ result: "Tool defaults cannot set runtime-owned input.",
475
+ instruction: "remove that default and retry register_tool; the runtime supplies it." })));
508
476
  }
509
477
  const authored = {
510
478
  ...(description ? { description } : {}),
@@ -520,13 +488,13 @@ function buildAuthoredRecipe(input, existing, existingRecipe) {
520
488
  }
521
489
  const template = getInputTemplate(input.template);
522
490
  if (template === null) {
523
- throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: Tool template cannot be null here."));
491
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template cannot be null here." })));
524
492
  }
525
493
  const finalTemplate = template === undefined || template === ""
526
494
  ? existingRecipe?.template ?? existing?.template
527
495
  : template;
528
496
  if (!finalTemplate) {
529
- throw new Error(ExecutionOutput.formatToolText("- field: template\n- result: Tool template is required."));
497
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template is required." })));
530
498
  }
531
499
  const authored = {
532
500
  ...(existingRecipe ?? {}),
@@ -554,33 +522,38 @@ function buildAuthoredRecipe(input, existing, existingRecipe) {
554
522
  function assertRegisterToolInputModes(input) {
555
523
  const record = input;
556
524
  if (Object.hasOwn(record, "values")) {
557
- 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."));
525
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "values", result: "register_tool values was removed.",
526
+ replacement_fields: ["defaults", "from"],
527
+ instruction: "retry register_tool with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition." })));
558
528
  }
559
529
  const fromProvided = Object.hasOwn(record, "from");
560
530
  if (fromProvided &&
561
531
  (typeof input.from !== "string" || input.from.trim().length === 0)) {
562
- 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."));
532
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "from",
533
+ result: "Tool from must be a non-empty Recipe reference.",
534
+ reference_forms: ["<skill>/<recipe>", "explicit .json path", "explicit .md path"],
535
+ instruction: "retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path." })));
563
536
  }
564
537
  const draftProvided = typeof input.draft === "string" && input.draft.trim().length > 0;
565
538
  const templateProvided = Object.hasOwn(record, "template");
566
539
  const modeCount = Number(fromProvided) + Number(draftProvided) + Number(templateProvided);
567
540
  if (modeCount > 1) {
568
- 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."));
541
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ result: "Use exactly one register_tool source mode.",
542
+ source_modes: ["from", "template", "draft"],
543
+ instruction: "remove the extra source fields and retry register_tool." })));
569
544
  }
570
545
  if (fromProvided &&
571
546
  (Object.hasOwn(record, "args") || Object.hasOwn(record, "async"))) {
572
- 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."));
547
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "from",
548
+ result: "register_tool from inherits args and async behavior.",
549
+ inherited_fields: ["args", "async"],
550
+ instruction: "retry register_tool with from and optional defaults only; omit args and async." })));
573
551
  }
574
552
  }
575
553
  function reservedRegistrationNameText(value) {
576
- const render = (selected) => "- field: name\n" + selected + "\n- result: Reserved tool name.";
577
- const wellFormed = value.isWellFormed();
578
- if (wellFormed) {
579
- const complete = render(ToolsResponse.formatMarkdownField("value", value));
580
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1)
581
- return complete;
582
- }
583
- 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`);
554
+ const selected = registrySelected("value", value);
555
+ const render = () => ({ field: "name", ...selected.selected, result: "Reserved tool name." });
556
+ return growRegistryDocument(render, [selected]);
584
557
  }
585
558
  function executeRegisterToolUnlocked(params, ctx, deps) {
586
559
  const rawInput = params && typeof params === "object"
@@ -592,7 +565,7 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
592
565
  return listTools(deps);
593
566
  const name = Identity.normalizeToolName(input.name);
594
567
  if (!name)
595
- throw new Error(ExecutionOutput.formatToolText("- field: name\n- result: Invalid tool name."));
568
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "name", result: "Invalid tool name." })));
596
569
  if (deps.reservedToolNames.has(name)) {
597
570
  throw new Error(ExecutionOutput.formatToolText(reservedRegistrationNameText(`${name}`)));
598
571
  }
@@ -630,7 +603,9 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
630
603
  throw new Error(ExecutionOutput.formatToolText(duplicateRegistrationText(`${name}`)));
631
604
  }
632
605
  if (template === undefined && !input.from && !existing) {
633
- 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."));
606
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ result: "New registration requires exactly one source mode.",
607
+ source_modes: ["from", "template", "draft"],
608
+ instruction: "retry register_tool with one source field." })));
634
609
  }
635
610
  const recipePath = getToolRecipePath(deps, name);
636
611
  const authoredRecipe = buildAuthoredRecipe(input, existing, authoritative.recipe);
@@ -12,26 +12,21 @@ import * as Pi from "./pi.js";
12
12
  import * as RunDelivery from "./run-delivery.js";
13
13
  import * as RunDeliveryLineage from "./run-delivery-lineage.js";
14
14
  import * as RunIndex from "./runs-index.js";
15
- import { formatLiteralBlock, formatMarkdownField } from "./tools-response.js";
15
+ import { formatYamlNotice } from "./tools-response.js";
16
16
  function formatSteerDiagnosticMessage(eventId, stage, message) {
17
- const fields = [formatMarkdownField("event", eventId), formatMarkdownField("attention", "steer"),
18
- formatMarkdownField("stage", stage), formatMarkdownField("result", stage === "admission" ? "remains retryable" : "request failed")];
19
- if (stage === "delivery")
20
- fields.push(formatMarkdownField("delivery", "unconfirmed"), formatMarkdownField("presentation", "unconfirmed"));
21
17
  let end = Math.min(message.length, 240);
22
18
  const last = message.charCodeAt(end - 1), next = message.charCodeAt(end);
23
19
  if (last >= 0xd800 && last <= 0xdbff && next >= 0xdc00 && next <= 0xdfff)
24
20
  end -= 1;
25
21
  const preview = message.slice(0, end), clipped = end < message.length;
26
- if (Buffer.from(preview).toString("utf8") !== preview) {
27
- fields.push(formatMarkdownField("diagnostic", "unavailable"), formatMarkdownField("diagnostic_reason", "malformed legacy preview"), formatMarkdownField("diagnostic_omission", "[omitted: unknown]"));
28
- }
29
- else {
30
- fields.push(formatMarkdownField("diagnostic", preview + (clipped ? "…" : "")));
31
- if (clipped)
32
- fields.push(formatMarkdownField("diagnostic_omission", "[omitted: unknown]"));
33
- }
34
- return fields.join("\n");
22
+ // The 240-pair-aligned preview and its explicit unknown-loss marker are unchanged; only metadata is YAML.
23
+ const diagnostic = Buffer.from(preview).toString("utf8") !== preview
24
+ ? { diagnostic: "unavailable", diagnostic_reason: "malformed legacy preview", diagnostic_omission: "[omitted: unknown]" }
25
+ : { diagnostic: preview + (clipped ? "…" : ""), ...(clipped ? { diagnostic_omission: "[omitted: unknown]" } : {}) };
26
+ return formatYamlNotice({ event: eventId, attention: "steer", stage,
27
+ result: stage === "admission" ? "remains retryable" : "request failed",
28
+ ...(stage === "delivery" ? { delivery: "unconfirmed", presentation: "unconfirmed" } : {}),
29
+ ...diagnostic });
35
30
  }
36
31
  /** Transcript-only view of the exact delivered content; never re-read a Run or infer body labels. */
37
32
  export function registerRunCompletionRenderer(pi) {
@@ -162,8 +157,7 @@ export function createRunUiRuntime(deps) {
162
157
  }
163
158
  const message = error instanceof Error ? error.message : String(error);
164
159
  try {
165
- expectedContext.ui.notify([formatMarkdownField("callback", label), formatMarkdownField("result", "failed"),
166
- formatMarkdownField("diagnostic", message)].join("\n"), "error");
160
+ expectedContext.ui.notify(formatYamlNotice({ callback: label, result: "failed", diagnostic: message }), "error");
167
161
  }
168
162
  catch {
169
163
  /* stale context or unavailable UI */
@@ -327,9 +321,8 @@ export function createRunUiRuntime(deps) {
327
321
  const key = `${batch.batch_id}:${evidence.status}:${evidence.reason ?? ""}`;
328
322
  if (!completionDiagnostics.has(key)) {
329
323
  completionDiagnostics.add(key);
330
- ctx.ui.notify("\n" + [formatMarkdownField("batch", batch.batch_id), formatMarkdownField("stage", "completion recovery"),
331
- formatMarkdownField("evidence", evidence.status), formatMarkdownField("phase", "pending"),
332
- formatMarkdownField("reason", evidence.reason ?? "conflicting session evidence")].join("\n"), "warning");
324
+ ctx.ui.notify("\n" + formatYamlNotice({ batch: batch.batch_id, stage: "completion recovery",
325
+ evidence: evidence.status, phase: "pending", reason: evidence.reason ?? "conflicting session evidence" }), "warning");
333
326
  }
334
327
  return true;
335
328
  }
@@ -413,9 +406,8 @@ export function createRunUiRuntime(deps) {
413
406
  const key = `${steer.steer_id}:${evidence.status}:${evidence.reason ?? ""}`;
414
407
  if (!steerDiagnostics.has(key)) {
415
408
  steerDiagnostics.add(key);
416
- ctx.ui.notify([formatMarkdownField("event", steer.event_id), formatMarkdownField("attention", "steer"),
417
- formatMarkdownField("stage", "recovery"), formatMarkdownField("evidence", evidence.status),
418
- formatMarkdownField("phase", "queued"), formatMarkdownField("reason", evidence.reason ?? "conflicting session evidence")].join("\n"), "warning");
409
+ ctx.ui.notify(formatYamlNotice({ event: steer.event_id, attention: "steer", stage: "recovery",
410
+ evidence: evidence.status, phase: "queued", reason: evidence.reason ?? "conflicting session evidence" }), "warning");
419
411
  }
420
412
  continue;
421
413
  }
@@ -681,11 +673,10 @@ export function createRunUiRuntime(deps) {
681
673
  if (teardown.failed === 0 || !ctx)
682
674
  return;
683
675
  try {
684
- ctx.ui.notify([formatMarkdownField("stage", "shutdown teardown"), formatMarkdownField("killed", String(teardown.killed)),
685
- formatMarkdownField("failed", String(teardown.failed)), formatMarkdownField("skipped", String(teardown.skipped)),
686
- formatMarkdownField("discovery_failed", String(teardown.discoveryFailed)),
687
- formatMarkdownField("summary", teardown.summaryPath == null ? "unavailable" : formatLiteralBlock(teardown.summaryPath)),
688
- formatMarkdownField("basis", "teardown report; not scope closure")].join("\n"), "warning");
676
+ ctx.ui.notify(formatYamlNotice({ stage: "shutdown teardown", killed: teardown.killed,
677
+ failed: teardown.failed, skipped: teardown.skipped, discovery_failed: teardown.discoveryFailed,
678
+ summary: teardown.summaryPath ?? "unavailable",
679
+ basis: "teardown report; not scope closure" }), "warning");
689
680
  }
690
681
  catch {
691
682
  /* stale shutdown context */