@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.
- package/AGENTS.md +9 -9
- package/BACKLOG.md +4 -23
- package/CHANGELOG.md +13 -0
- package/README.md +5 -2
- package/dist/lib/execution-output.js +13 -2
- package/dist/lib/observability.js +9 -14
- package/dist/lib/registry.js +132 -157
- package/dist/lib/run-ui-runtime.js +18 -27
- package/dist/lib/runs-process-windows-mi.d.ts +71 -0
- package/dist/lib/runs-process-windows-mi.js +146 -3
- package/dist/lib/tools-inspect.js +109 -59
- package/dist/lib/tools-local.js +16 -8
- package/dist/lib/tools-message.js +76 -65
- package/dist/lib/tools-response.d.ts +18 -0
- package/dist/lib/tools-response.js +88 -21
- package/dist/lib/tools-spawn.js +19 -12
- package/dist/skills/actors/SKILL.md +3 -3
- package/dist/skills/actors/references/diagnostics.md +3 -0
- package/docs/README.md +1 -0
- package/docs/actor-inspector.md +2 -2
- package/docs/agent-experience.md +9 -1
- package/docs/inactive-foundations-inventory.md +73 -0
- package/docs/inspection.md +9 -8
- package/docs/output-projection-map.md +16 -14
- package/docs/windows-process-observation.md +2 -2
- package/lib/execution-output.ts +13 -2
- package/lib/observability.ts +9 -11
- package/lib/registry.ts +138 -164
- package/lib/run-ui-runtime.ts +18 -34
- package/lib/runs-process-windows-mi.ts +167 -3
- package/lib/tools-inspect.ts +109 -59
- package/lib/tools-local.ts +26 -15
- package/lib/tools-message.ts +85 -58
- package/lib/tools-response.ts +97 -20
- package/lib/tools-spawn.ts +19 -12
- package/package.json +1 -1
- package/skills/actors/SKILL.md +3 -3
- 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
|
|
96
|
-
|
|
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
|
-
//
|
|
100
|
-
const
|
|
101
|
-
|
|
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
|
-
|
|
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
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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 ?
|
|
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(
|
|
222
|
+
rows.push(value);
|
|
197
223
|
}
|
|
198
224
|
const omitted = omission(key, values.length, malformed);
|
|
199
|
-
return part(malformed ? 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
|
|
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 ?
|
|
235
|
+
? data.callable ? { instruction: data.next } : { next: { tool: "inspect", target: `tool:${data.tool}`, view: "status" } }
|
|
210
236
|
: omittedNext, omittedNext);
|
|
211
|
-
const render = () =>
|
|
212
|
-
...(data.boundary === undefined ?
|
|
213
|
-
...(warnings ?
|
|
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 (
|
|
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
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
317
|
-
const render = (
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
354
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
361
|
-
const flag = part("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 ?
|
|
365
|
-
const render = () =>
|
|
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 (
|
|
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
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
503
|
-
const facts =
|
|
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 =
|
|
509
|
-
if (
|
|
483
|
+
const complete = ToolsResponse.renderYamlMetadata({ tool: name, ...facts }, budget);
|
|
484
|
+
if (complete.state === "available") return complete.text;
|
|
510
485
|
}
|
|
511
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
828
|
-
const
|
|
829
|
-
|
|
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(
|
|
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
|
-
|
|
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);
|
package/lib/run-ui-runtime.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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
|
}
|