@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
package/lib/registry.ts CHANGED
@@ -92,17 +92,17 @@ function textContent(text: string) {
92
92
  }
93
93
 
94
94
  function registryListText(rows: readonly string[]): string {
95
- const coverage = (shown: number, reason?: string) => "\n- registered_tools_coverage:" +
96
- (reason ? `\n - reason: ${reason}` : "") + `\n - shown: ${shown}\n - omitted: ${rows.length - shown}\n - total: ${rows.length}`;
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
97
  const malformed = rows.some(row => !row.isWellFormed());
98
98
  if (!malformed) {
99
- // Rows already contain the old compiler-owned bullet and selected key coercion.
100
- const body = rows.length ? "- registered_tools:\n" + rows.map(row => " " + row.replaceAll(/\r\n|\r|\n/gu, newline => newline + " ")).join("\n")
101
- : "- registered_tools: none";
102
- const complete = body + coverage(rows.length);
103
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
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;
104
102
  }
105
- return `- registered_tools: [${malformed ? "unavailable" : "omitted"}]` + coverage(0, malformed ? "malformed_selected_text" : "response_too_large");
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
106
  }
107
107
 
108
108
  function listTools<TContext>(
@@ -172,20 +172,46 @@ function registrationNextActions(
172
172
  ];
173
173
  }
174
174
 
175
+ const REGISTRY_METADATA_BUDGET = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
176
+
177
+ type RegistryMetadata = Record<string, unknown>;
178
+
179
+ /** Bare block-YAML registry document; builders own whole-value/omission policy and the byte budget. */
180
+ function registryMetadataText(value: RegistryMetadata): string {
181
+ return (ToolsResponse.renderYamlMetadata(value, REGISTRY_METADATA_BUDGET) as
182
+ Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
183
+ }
184
+
185
+ /** One opaque selected scalar as a whole value or an explicit whole-field omission, never a clip. */
186
+ function registrySelected(key: string, value: string): ToolsResponse.YamlPart {
187
+ return ToolsResponse.selectYamlField(key, value, REGISTRY_METADATA_BUDGET);
188
+ }
189
+
190
+ /** Grow whole selected fields in priority order while the complete document still fits. */
191
+ function growRegistryDocument(render: () => RegistryMetadata, order: readonly ToolsResponse.YamlPart[]): string {
192
+ return ToolsResponse.growYamlDocument(render, order, REGISTRY_METADATA_BUDGET);
193
+ }
194
+
175
195
  /** Read-time text only: callers supply the original post-activation selections, not live ports. */
176
196
  function registrationReceiptText(data: {
177
197
  tool: string; result: "registered" | "updated" | "persisted"; source: string; callable: boolean;
178
198
  boundary?: string; required: readonly unknown[]; optional: readonly unknown[];
179
199
  warnings: readonly unknown[]; next: string;
180
200
  }): string {
181
- const field = ToolsResponse.formatMarkdownField;
182
- const omission = (key: string, count: number, malformed: boolean) =>
183
- `- ${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}`;
184
- const part = (full: string, omitted: string) => ({ full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted });
185
- const scalar = (key: string, value: string, literal = false) => {
201
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
202
+ const renderPart = (value: Record<string, unknown>) => ToolsResponse.renderYamlMetadata(value, budget);
203
+ const omission = (key: string, count: number, malformed: boolean): Record<string, unknown> => ({
204
+ [key]: malformed ? "[unavailable]" : "[omitted]",
205
+ [key + "_omission"]: { reason: malformed ? "malformed_selected_text" : "response_too_large", shown: 0, omitted: count, total: count },
206
+ });
207
+ const part = (full: Record<string, unknown>, omitted: Record<string, unknown>) => {
208
+ const complete = renderPart(full), fallback = renderPart(omitted);
209
+ return { full, selected: complete.state === "available" && fallback.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted };
210
+ };
211
+ const scalar = (key: string, value: string) => {
186
212
  const wellFormed = value.isWellFormed();
187
213
  const omitted = omission(key, 1, !wellFormed);
188
- return part(wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted, omitted);
214
+ return part(wellFormed ? { [key]: value } : omitted, omitted);
189
215
  };
190
216
  const sequence = (key: string, values: readonly unknown[]) => {
191
217
  const rows: string[] = [];
@@ -193,32 +219,32 @@ function registrationReceiptText(data: {
193
219
  for (let index = 0; index < values.length; index++) {
194
220
  const value = values[index];
195
221
  if (typeof value !== "string" || !value.isWellFormed()) { malformed = true; break; }
196
- rows.push(" - " + value.replaceAll(/\r\n|\r|\n/gu, newline => newline + " "));
222
+ rows.push(value);
197
223
  }
198
224
  const omitted = omission(key, values.length, malformed);
199
- return part(malformed ? omitted : rows.length ? `- ${key}:\n${rows.join("\n")}` : field(key, "none"), omitted);
225
+ return part(malformed ? omitted : { [key]: rows }, omitted);
200
226
  };
201
227
  const tool = scalar("tool", data.tool);
202
- const source = scalar("source", data.source, data.source !== "template");
228
+ const source = scalar("source", data.source);
203
229
  const required = sequence("required_args", data.required), optional = sequence("optional_args", data.optional);
204
230
  const warnings = data.warnings.length ? sequence("warnings", data.warnings) : undefined;
205
231
  const nextWellFormed = data.next.isWellFormed();
206
232
  const omittedNext = omission("next_action", 1, !nextWellFormed);
207
233
  // This producer knows only its existing status hint; invocation advice stays opaque.
208
234
  const next = part(nextWellFormed
209
- ? data.callable ? field("instruction", data.next) : ToolsResponse.formatInspectNextAction(`tool:${data.tool}`, "status")
235
+ ? data.callable ? { instruction: data.next } : { next: { tool: "inspect", target: `tool:${data.tool}`, view: "status" } }
210
236
  : omittedNext, omittedNext);
211
- const render = () => [tool.selected, field("result", data.result), source.selected, field("callable_now", data.callable ? "true" : "false"),
212
- ...(data.boundary === undefined ? [] : [field("activation_boundary", data.boundary)]), required.selected, optional.selected,
213
- ...(warnings ? [warnings.selected] : []), next.selected].join("\n");
237
+ const render = () => renderPart({ ...tool.selected, result: data.result, ...source.selected, callable_now: data.callable,
238
+ ...(data.boundary === undefined ? {} : { activation_boundary: data.boundary }), ...required.selected, ...optional.selected,
239
+ ...(warnings ? warnings.selected : {}), ...next.selected });
214
240
  // Preserve complete safety notes before large argument lists; all seeds are monotone.
215
241
  for (const current of [...(warnings ? [warnings] : []), tool, source, next, required, optional]) {
216
242
  if (current.selected === current.full) continue;
217
243
  const previous = current.selected;
218
244
  current.selected = current.full;
219
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
245
+ if (render().state !== "available") current.selected = previous;
220
246
  }
221
- return render();
247
+ return (render() as Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
222
248
  }
223
249
 
224
250
  function registryNoticeText(kind: "activated" | "promoted" | "deleted", name: string): string {
@@ -229,74 +255,35 @@ function registryNoticeText(kind: "activated" | "promoted" | "deleted", name: st
229
255
  }
230
256
 
231
257
  function registrationTransactionFailureText(stage: string, generation: string, summary: string, error: string): string {
232
- const field = ToolsResponse.formatMarkdownField;
233
- const part = (key: string, value: string) => {
234
- const wellFormed = value.isWellFormed();
235
- 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`;
236
- const full = wellFormed ? field(key, value) : omitted;
237
- return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
238
- };
239
- const selectedGeneration = part("resolution_generation", generation);
240
- const selectedSummary = part("active_skills_summary", summary);
241
- const selectedError = part("error", error);
242
- const render = () => [field("result", "Tool registration transaction failed."), field("stage", stage),
243
- selectedGeneration.selected, selectedSummary.selected, selectedError.selected].join("\n");
258
+ const selectedGeneration = registrySelected("resolution_generation", generation);
259
+ const selectedSummary = registrySelected("active_skills_summary", summary);
260
+ const selectedError = registrySelected("error", error);
261
+ const render = (): RegistryMetadata => ({ result: "Tool registration transaction failed.", stage,
262
+ ...selectedGeneration.selected, ...selectedSummary.selected, ...selectedError.selected });
244
263
  // Keep a fitting surfaced error before large optional resolution metadata.
245
- for (const current of [selectedError, selectedGeneration, selectedSummary]) {
246
- if (current.selected === current.full) continue;
247
- const previous = current.selected;
248
- current.selected = current.full;
249
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
250
- }
251
- return render();
264
+ return growRegistryDocument(render, [selectedError, selectedGeneration, selectedSummary]);
252
265
  }
253
266
 
254
267
  function candidateAdmissionFailureText(data: { kind: "source"; source: string; diagnostics: string; summary: string; instruction: string } | { kind: "recipe"; diagnostics: string }): string {
255
- const field = ToolsResponse.formatMarkdownField;
256
- const part = (key: string, value: string, literal = false) => {
257
- const wellFormed = value.isWellFormed();
258
- 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`;
259
- const full = wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted;
260
- return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
261
- };
262
- const diagnostics = part("diagnostics_summary", data.diagnostics);
263
- const source = data.kind === "source" ? part("source", data.source, true) : undefined;
264
- const summary = data.kind === "source" ? part("active_skills_summary", data.summary) : undefined;
265
- const instruction = data.kind === "source" ? part("instruction", data.instruction) : undefined;
266
- const render = () => [field("field", "recipe"), ...(source ? [source.selected] : []),
267
- field("result", data.kind === "source" ? "Recipe source validation failed." : "Tool recipe validation failed."),
268
- diagnostics.selected, ...(summary ? [summary.selected] : []), ...(instruction ? [instruction.selected] : [])].join("\n");
268
+ const diagnostics = registrySelected("diagnostics_summary", data.diagnostics);
269
+ const source = data.kind === "source" ? registrySelected("source", data.source) : undefined;
270
+ const summary = data.kind === "source" ? registrySelected("active_skills_summary", data.summary) : undefined;
271
+ const instruction = data.kind === "source" ? registrySelected("instruction", data.instruction) : undefined;
272
+ const render = (): RegistryMetadata => ({ field: "recipe", ...(source ? source.selected : {}),
273
+ result: data.kind === "source" ? "Recipe source validation failed." : "Tool recipe validation failed.",
274
+ ...diagnostics.selected, ...(summary ? summary.selected : {}), ...(instruction ? instruction.selected : {}) });
269
275
  // Preserve fitting diagnostics before large optional source/context/advice fields.
270
- for (const current of [diagnostics, source, summary, instruction]) {
271
- if (!current || current.selected === current.full) continue;
272
- const previous = current.selected;
273
- current.selected = current.full;
274
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
275
- }
276
- return render();
276
+ return growRegistryDocument(render, [diagnostics, ...(source ? [source] : []), ...(summary ? [summary] : []), ...(instruction ? [instruction] : [])]);
277
277
  }
278
278
 
279
279
  function sourceNormalizationFailureText(source: string, reason: string, summary: string, instruction: string): string {
280
- const field = ToolsResponse.formatMarkdownField;
281
- const part = (key: string, value: string, literal = false) => {
282
- const wellFormed = value.isWellFormed();
283
- 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`;
284
- const full = wellFormed ? field(key, literal ? ToolsResponse.formatLiteralBlock(value) : value) : omitted;
285
- return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
286
- };
287
- const selectedSource = part("source", source, true);
288
- const selectedReason = part("reason", reason);
289
- const selectedSummary = part("active_skills_summary", summary);
290
- const selectedInstruction = part("instruction", instruction);
291
- const render = () => [field("field", "from"), selectedSource.selected, field("result", "Recipe source is invalid."),
292
- selectedReason.selected, selectedSummary.selected, selectedInstruction.selected].join("\n");
293
- for (const current of [selectedSource, selectedReason, selectedSummary, selectedInstruction]) {
294
- if (current.selected === current.full) continue;
295
- const previous = current.selected;
296
- current.selected = current.full;
297
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
298
- }
299
- return render();
280
+ const selectedSource = registrySelected("source", source);
281
+ const selectedReason = registrySelected("reason", reason);
282
+ const selectedSummary = registrySelected("active_skills_summary", summary);
283
+ const selectedInstruction = registrySelected("instruction", instruction);
284
+ const render = (): RegistryMetadata => ({ field: "from", ...selectedSource.selected, result: "Recipe source is invalid.",
285
+ ...selectedReason.selected, ...selectedSummary.selected, ...selectedInstruction.selected });
286
+ return growRegistryDocument(render, [selectedSource, selectedReason, selectedSummary, selectedInstruction]);
300
287
  }
301
288
 
302
289
  function activeSkillSummary(
@@ -313,16 +300,11 @@ function activeSkillSummary(
313
300
  }
314
301
 
315
302
  function draftPathFailureText(kind: "root" | "missing", reference: string): string {
316
- const key = kind === "root" ? "allowed_root" : "path";
317
- const render = (selected: string) => "- field: draft\n" + selected + (kind === "root"
318
- ? "\n- result: Draft must be under the allowed root.\n- instruction: Use inspect target=recipes view=summary to list drafts."
319
- : "\n- result: Draft not found.");
320
- const wellFormed = reference.isWellFormed();
321
- if (wellFormed) {
322
- const complete = render(ToolsResponse.formatMarkdownField(key, ToolsResponse.formatLiteralBlock(reference)));
323
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
324
- }
325
- 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`);
303
+ const selected = registrySelected(kind === "root" ? "allowed_root" : "path", reference);
304
+ const render = (): RegistryMetadata => ({ field: "draft", ...selected.selected,
305
+ result: kind === "root" ? "Draft must be under the allowed root." : "Draft not found.",
306
+ ...(kind === "root" ? { instruction: "Use inspect target=recipes view=summary to list drafts." } : {}) });
307
+ return growRegistryDocument(render, [selected]);
326
308
  }
327
309
 
328
310
  function assertDraftPath<TContext>(
@@ -350,47 +332,42 @@ function assertDraftPath<TContext>(
350
332
  }
351
333
 
352
334
  function draftReceiptText(result: "updated" | "registered", tool: string, callable: string, next: string): string {
353
- const field = ToolsResponse.formatMarkdownField;
354
- const part = (key: string, value: string, rendered: string) => {
335
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
336
+ const renderPart = (value: Record<string, unknown>) => ToolsResponse.renderYamlMetadata(value, budget);
337
+ const part = (key: string, value: string, rendered: Record<string, unknown>) => {
355
338
  const wellFormed = value.isWellFormed();
356
- 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`;
339
+ const omitted: Record<string, unknown> = { [key]: wellFormed ? "[omitted]" : "[unavailable]",
340
+ [key + "_omission"]: { reason: wellFormed ? "response_too_large" : "malformed_selected_text", shown: 0, omitted: 1, total: 1 } };
357
341
  const full = wellFormed ? rendered : omitted;
358
- return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
342
+ const complete = renderPart(full), fallback = renderPart(omitted);
343
+ return { full, selected: complete.state === "available" && fallback.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted };
359
344
  };
360
- const identity = part("tool", tool, field("tool", tool));
361
- const flag = part("callable_now", callable, field("callable_now", callable));
345
+ const identity = part("tool", tool, { tool });
346
+ const flag = part("callable_now", callable, { callable_now: callable });
362
347
  // Only this producer's exact canonical read-only status hint is typed, never body advice.
363
348
  const status = /^inspect target=(tool:[a-z][a-z0-9_]{0,63}) view=status$/u.exec(next);
364
- const hint = part("next_action", next, status ? ToolsResponse.formatInspectNextAction(status[1], "status") : field("instruction", next));
365
- const render = () => [identity.selected, field("result", result), field("source", "draft"), flag.selected, hint.selected].join("\n");
349
+ const hint = part("next_action", next, status ? { next: { tool: "inspect", target: status[1], view: "status" } } : { instruction: next });
350
+ const render = () => renderPart({ ...identity.selected, result, source: "draft", ...flag.selected, ...hint.selected });
366
351
  for (const current of [identity, flag, hint]) {
367
352
  if (current.selected === current.full) continue;
368
353
  const previous = current.selected;
369
354
  current.selected = current.full;
370
- if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
355
+ if (render().state !== "available") current.selected = previous;
371
356
  }
372
- return render();
357
+ return (render() as Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
373
358
  }
374
359
 
375
360
  function duplicateRegistrationText(value: string): string {
376
- const render = (selected: string) => "- field: name\n" + selected + "\n- result: Tool already registered.\n- instruction: Use update=true to overwrite.";
377
- const wellFormed = value.isWellFormed();
378
- if (wellFormed) {
379
- const complete = render(ToolsResponse.formatMarkdownField("value", value));
380
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
381
- }
382
- 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`);
361
+ const selected = registrySelected("value", value);
362
+ const render = (): RegistryMetadata => ({ field: "name", ...selected.selected,
363
+ result: "Tool already registered.", instruction: "Use update=true to overwrite." });
364
+ return growRegistryDocument(render, [selected]);
383
365
  }
384
366
 
385
367
  function draftRecipeFailureText(key: "reason" | "diagnostics_summary", value?: string): string {
386
- const header = "- field: draft\n- result: Draft recipe is invalid.";
387
- if (value === undefined) return header;
388
- const wellFormed = value.isWellFormed();
389
- if (wellFormed) {
390
- const complete = header + "\n" + ToolsResponse.formatMarkdownField(key, value);
391
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
392
- }
393
- 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`;
368
+ const selected = value === undefined ? undefined : registrySelected(key, value);
369
+ const render = (): RegistryMetadata => ({ field: "draft", result: "Draft recipe is invalid.", ...(selected ? selected.selected : {}) });
370
+ return growRegistryDocument(render, selected ? [selected] : []);
394
371
  }
395
372
 
396
373
  function promoteDraftRecipe<TContext>(
@@ -499,16 +476,16 @@ function persistToolRecipe<TContext>(
499
476
  }
500
477
 
501
478
  function registryDeletionText(name: string, result: "deleted" | "not found"): string {
502
- const field = ToolsResponse.formatMarkdownField;
503
- const facts = [field("result", result), ...(result === "deleted"
504
- ? [field("instruction", "Reload to remove it from the complete registry.")] : [])];
505
- const render = (identity: string) => [identity, ...facts].join("\n");
479
+ const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
480
+ const facts = { result, ...(result === "deleted" ? { instruction: "Reload to remove it from the complete registry." } : {}) };
506
481
  const wellFormed = name.isWellFormed();
507
482
  if (wellFormed) {
508
- const complete = render(field("tool", name));
509
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
483
+ const complete = ToolsResponse.renderYamlMetadata({ tool: name, ...facts }, budget);
484
+ if (complete.state === "available") return complete.text;
510
485
  }
511
- 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`);
486
+ 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);
487
+ // Fixed metadata is well formed and bounded far below budget.
488
+ return (fallback as Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
512
489
  }
513
490
 
514
491
  function deleteTool<TContext>(
@@ -598,9 +575,9 @@ function assertTemplateIsNotNestedRecipe(value: unknown): void {
598
575
  ))
599
576
  ) {
600
577
  throw new Error(
601
- ExecutionOutput.formatToolText(
602
- "- field: template\n- result: Nested Recipe-shaped templates are not a register_tool source mode.\n- instruction: retry with from=<skill>/<recipe> and defaults={...}.",
603
- ),
578
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "template",
579
+ result: "Nested Recipe-shaped templates are not a register_tool source mode.",
580
+ instruction: "retry with from=<skill>/<recipe> and defaults={...}." })),
604
581
  );
605
582
  }
606
583
  }
@@ -617,7 +594,7 @@ function getInputTemplate(
617
594
  });
618
595
  if (steps.length === 0)
619
596
  throw new Error(
620
- ExecutionOutput.formatToolText("- field: template\n- result: Tool template sequence is empty."),
597
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template sequence is empty." })),
621
598
  );
622
599
  return value;
623
600
  }
@@ -628,9 +605,8 @@ function getInputTemplate(
628
605
  return value as CommandTemplates.CommandTemplateConfig;
629
606
  }
630
607
  throw new Error(
631
- ExecutionOutput.formatToolText(
632
- "- field: template\n- result: unsupported template type\n- supported_types:\n - string\n - object\n - sequence",
633
- ),
608
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "unsupported template type",
609
+ supported_types: ["string", "object", "sequence"] })),
634
610
  );
635
611
  }
636
612
 
@@ -656,7 +632,7 @@ function normalizeDefaults(value: unknown): Record<string, unknown> | undefined
656
632
  if (value === undefined) return undefined;
657
633
  if (!value || typeof value !== "object" || Array.isArray(value)) {
658
634
  throw new Error(
659
- ExecutionOutput.formatToolText("- field: defaults\n- result: Tool defaults must be an object."),
635
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "defaults", result: "Tool defaults must be an object." })),
660
636
  );
661
637
  }
662
638
  return { ...(value as Record<string, unknown>) };
@@ -716,9 +692,8 @@ function buildAuthoredRecipe(
716
692
  const description = (input.description ?? existing?.description ?? "").trim();
717
693
  if (!description && !from) {
718
694
  throw new Error(
719
- ExecutionOutput.formatToolText(
720
- "- field: description\n- result: Tool description is required for command-template registration.",
721
- ),
695
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "description",
696
+ result: "Tool description is required for command-template registration." })),
722
697
  );
723
698
  }
724
699
  const inputDefaults = normalizeDefaults(input.defaults);
@@ -728,9 +703,10 @@ function buildAuthoredRecipe(
728
703
  );
729
704
  if (runtimeOwnedDefault) {
730
705
  throw new Error(
731
- ExecutionOutput.formatToolText(
732
- `- 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.`,
733
- ),
706
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "defaults",
707
+ ...registrySelected("key", runtimeOwnedDefault).selected,
708
+ result: "Tool defaults cannot set runtime-owned input.",
709
+ instruction: "remove that default and retry register_tool; the runtime supplies it." })),
734
710
  );
735
711
  }
736
712
  const authored: Record<string, unknown> = {
@@ -749,7 +725,7 @@ function buildAuthoredRecipe(
749
725
  const template = getInputTemplate(input.template);
750
726
  if (template === null) {
751
727
  throw new Error(
752
- ExecutionOutput.formatToolText("- field: template\n- result: Tool template cannot be null here."),
728
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template cannot be null here." })),
753
729
  );
754
730
  }
755
731
  const finalTemplate =
@@ -758,7 +734,7 @@ function buildAuthoredRecipe(
758
734
  : template;
759
735
  if (!finalTemplate) {
760
736
  throw new Error(
761
- ExecutionOutput.formatToolText("- field: template\n- result: Tool template is required."),
737
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template is required." })),
762
738
  );
763
739
  }
764
740
  const authored: Record<string, unknown> = {
@@ -783,9 +759,9 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
783
759
  const record = input as RegisterToolInput & Record<string, unknown>;
784
760
  if (Object.hasOwn(record, "values")) {
785
761
  throw new Error(
786
- ExecutionOutput.formatToolText(
787
- "- 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.",
788
- ),
762
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "values", result: "register_tool values was removed.",
763
+ replacement_fields: ["defaults", "from"],
764
+ instruction: "retry register_tool with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition." })),
789
765
  );
790
766
  }
791
767
  const fromProvided = Object.hasOwn(record, "from");
@@ -794,9 +770,10 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
794
770
  (typeof input.from !== "string" || input.from.trim().length === 0)
795
771
  ) {
796
772
  throw new Error(
797
- ExecutionOutput.formatToolText(
798
- "- 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.",
799
- ),
773
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "from",
774
+ result: "Tool from must be a non-empty Recipe reference.",
775
+ reference_forms: ["<skill>/<recipe>", "explicit .json path", "explicit .md path"],
776
+ instruction: "retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path." })),
800
777
  );
801
778
  }
802
779
  const draftProvided =
@@ -806,9 +783,9 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
806
783
  Number(fromProvided) + Number(draftProvided) + Number(templateProvided);
807
784
  if (modeCount > 1) {
808
785
  throw new Error(
809
- ExecutionOutput.formatToolText(
810
- "- 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.",
811
- ),
786
+ ExecutionOutput.formatToolText(registryMetadataText({ result: "Use exactly one register_tool source mode.",
787
+ source_modes: ["from", "template", "draft"],
788
+ instruction: "remove the extra source fields and retry register_tool." })),
812
789
  );
813
790
  }
814
791
  if (
@@ -816,21 +793,18 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
816
793
  (Object.hasOwn(record, "args") || Object.hasOwn(record, "async"))
817
794
  ) {
818
795
  throw new Error(
819
- ExecutionOutput.formatToolText(
820
- "- 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.",
821
- ),
796
+ ExecutionOutput.formatToolText(registryMetadataText({ field: "from",
797
+ result: "register_tool from inherits args and async behavior.",
798
+ inherited_fields: ["args", "async"],
799
+ instruction: "retry register_tool with from and optional defaults only; omit args and async." })),
822
800
  );
823
801
  }
824
802
  }
825
803
 
826
804
  function reservedRegistrationNameText(value: string): string {
827
- const render = (selected: string) => "- field: name\n" + selected + "\n- result: Reserved tool name.";
828
- const wellFormed = value.isWellFormed();
829
- if (wellFormed) {
830
- const complete = render(ToolsResponse.formatMarkdownField("value", value));
831
- if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
832
- }
833
- 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`);
805
+ const selected = registrySelected("value", value);
806
+ const render = (): RegistryMetadata => ({ field: "name", ...selected.selected, result: "Reserved tool name." });
807
+ return growRegistryDocument(render, [selected]);
834
808
  }
835
809
 
836
810
  function executeRegisterToolUnlocked<TContext>(
@@ -847,7 +821,7 @@ function executeRegisterToolUnlocked<TContext>(
847
821
  if (!input.name) return listTools(deps);
848
822
  const name = Identity.normalizeToolName(input.name);
849
823
  if (!name)
850
- throw new Error(ExecutionOutput.formatToolText("- field: name\n- result: Invalid tool name."));
824
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "name", result: "Invalid tool name." })));
851
825
  if (deps.reservedToolNames.has(name)) {
852
826
  throw new Error(
853
827
  ExecutionOutput.formatToolText(reservedRegistrationNameText(`${name}`)),
@@ -895,9 +869,9 @@ function executeRegisterToolUnlocked<TContext>(
895
869
  }
896
870
  if (template === undefined && !input.from && !existing) {
897
871
  throw new Error(
898
- ExecutionOutput.formatToolText(
899
- "- 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.",
900
- ),
872
+ ExecutionOutput.formatToolText(registryMetadataText({ result: "New registration requires exactly one source mode.",
873
+ source_modes: ["from", "template", "draft"],
874
+ instruction: "retry register_tool with one source field." })),
901
875
  );
902
876
  }
903
877
  const recipePath = getToolRecipePath(deps, name);
@@ -14,7 +14,7 @@ import * as Pi from "./pi.ts";
14
14
  import * as RunDelivery from "./run-delivery.ts";
15
15
  import * as RunDeliveryLineage from "./run-delivery-lineage.ts";
16
16
  import * as RunIndex from "./runs-index.ts";
17
- import { formatLiteralBlock, formatMarkdownField } from "./tools-response.ts";
17
+ import { formatLiteralBlock, formatYamlNotice } from "./tools-response.ts";
18
18
 
19
19
  export interface RunUiRuntime {
20
20
  close(): void;
@@ -48,21 +48,18 @@ export interface RunUiRuntimeDeps {
48
48
  }
49
49
 
50
50
  function formatSteerDiagnosticMessage(eventId: string, stage: "admission" | "delivery", message: string): string {
51
- const fields = [formatMarkdownField("event", eventId), formatMarkdownField("attention", "steer"),
52
- formatMarkdownField("stage", stage), formatMarkdownField("result", stage === "admission" ? "remains retryable" : "request failed")];
53
- if (stage === "delivery") fields.push(formatMarkdownField("delivery", "unconfirmed"), formatMarkdownField("presentation", "unconfirmed"));
54
51
  let end = Math.min(message.length, 240);
55
52
  const last = message.charCodeAt(end - 1), next = message.charCodeAt(end);
56
53
  if (last >= 0xd800 && last <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) end -= 1;
57
54
  const preview = message.slice(0, end), clipped = end < message.length;
58
- if (Buffer.from(preview).toString("utf8") !== preview) {
59
- fields.push(formatMarkdownField("diagnostic", "unavailable"), formatMarkdownField("diagnostic_reason", "malformed legacy preview"),
60
- formatMarkdownField("diagnostic_omission", "[omitted: unknown]"));
61
- } else {
62
- fields.push(formatMarkdownField("diagnostic", preview + (clipped ? "…" : "")));
63
- if (clipped) fields.push(formatMarkdownField("diagnostic_omission", "[omitted: unknown]"));
64
- }
65
- return fields.join("\n");
55
+ // The 240-pair-aligned preview and its explicit unknown-loss marker are unchanged; only metadata is YAML.
56
+ const diagnostic: Record<string, string> = Buffer.from(preview).toString("utf8") !== preview
57
+ ? { diagnostic: "unavailable", diagnostic_reason: "malformed legacy preview", diagnostic_omission: "[omitted: unknown]" }
58
+ : { diagnostic: preview + (clipped ? "…" : ""), ...(clipped ? { diagnostic_omission: "[omitted: unknown]" } : {}) };
59
+ return formatYamlNotice({ event: eventId, attention: "steer", stage,
60
+ result: stage === "admission" ? "remains retryable" : "request failed",
61
+ ...(stage === "delivery" ? { delivery: "unconfirmed", presentation: "unconfirmed" } : {}),
62
+ ...diagnostic });
66
63
  }
67
64
 
68
65
  /** Transcript-only view of the exact delivered content; never re-read a Run or infer body labels. */
@@ -184,8 +181,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
184
181
  }
185
182
  const message = error instanceof Error ? error.message : String(error);
186
183
  try {
187
- expectedContext.ui.notify([formatMarkdownField("callback", label), formatMarkdownField("result", "failed"),
188
- formatMarkdownField("diagnostic", message)].join("\n"), "error");
184
+ expectedContext.ui.notify(formatYamlNotice({ callback: label, result: "failed", diagnostic: message }), "error");
189
185
  } catch {
190
186
  /* stale context or unavailable UI */
191
187
  }
@@ -369,12 +365,8 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
369
365
  const key = `${batch.batch_id}:${evidence.status}:${evidence.reason ?? ""}`;
370
366
  if (!completionDiagnostics.has(key)) {
371
367
  completionDiagnostics.add(key);
372
- ctx.ui.notify(
373
- "\n" + [formatMarkdownField("batch", batch.batch_id), formatMarkdownField("stage", "completion recovery"),
374
- formatMarkdownField("evidence", evidence.status), formatMarkdownField("phase", "pending"),
375
- formatMarkdownField("reason", evidence.reason ?? "conflicting session evidence")].join("\n"),
376
- "warning",
377
- );
368
+ ctx.ui.notify("\n" + formatYamlNotice({ batch: batch.batch_id, stage: "completion recovery",
369
+ evidence: evidence.status, phase: "pending", reason: evidence.reason ?? "conflicting session evidence" }), "warning");
378
370
  }
379
371
  return true;
380
372
  }
@@ -440,12 +432,8 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
440
432
  const key = `${steer.steer_id}:${evidence.status}:${evidence.reason ?? ""}`;
441
433
  if (!steerDiagnostics.has(key)) {
442
434
  steerDiagnostics.add(key);
443
- ctx.ui.notify(
444
- [formatMarkdownField("event", steer.event_id), formatMarkdownField("attention", "steer"),
445
- formatMarkdownField("stage", "recovery"), formatMarkdownField("evidence", evidence.status),
446
- formatMarkdownField("phase", "queued"), formatMarkdownField("reason", evidence.reason ?? "conflicting session evidence")].join("\n"),
447
- "warning",
448
- );
435
+ ctx.ui.notify(formatYamlNotice({ event: steer.event_id, attention: "steer", stage: "recovery",
436
+ evidence: evidence.status, phase: "queued", reason: evidence.reason ?? "conflicting session evidence" }), "warning");
449
437
  }
450
438
  continue;
451
439
  }
@@ -714,14 +702,10 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
714
702
  );
715
703
  if (teardown.failed === 0 || !ctx) return;
716
704
  try {
717
- ctx.ui.notify(
718
- [formatMarkdownField("stage", "shutdown teardown"), formatMarkdownField("killed", String(teardown.killed)),
719
- formatMarkdownField("failed", String(teardown.failed)), formatMarkdownField("skipped", String(teardown.skipped)),
720
- formatMarkdownField("discovery_failed", String(teardown.discoveryFailed)),
721
- formatMarkdownField("summary", teardown.summaryPath == null ? "unavailable" : formatLiteralBlock(teardown.summaryPath)),
722
- formatMarkdownField("basis", "teardown report; not scope closure")].join("\n"),
723
- "warning",
724
- );
705
+ ctx.ui.notify(formatYamlNotice({ stage: "shutdown teardown", killed: teardown.killed,
706
+ failed: teardown.failed, skipped: teardown.skipped, discovery_failed: teardown.discoveryFailed,
707
+ summary: teardown.summaryPath ?? "unavailable",
708
+ basis: "teardown report; not scope closure" }), "warning");
725
709
  } catch {
726
710
  /* stale shutdown context */
727
711
  }