@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/dist/lib/registry.js
CHANGED
|
@@ -21,18 +21,18 @@ function textContent(text) {
|
|
|
21
21
|
return { type: "text", text };
|
|
22
22
|
}
|
|
23
23
|
function registryListText(rows) {
|
|
24
|
-
const
|
|
25
|
-
|
|
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
|
-
//
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
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 ?
|
|
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(
|
|
124
|
+
rows.push(value);
|
|
105
125
|
}
|
|
106
126
|
const omitted = omission(key, values.length, malformed);
|
|
107
|
-
return part(malformed ? 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
|
|
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 ?
|
|
137
|
+
? data.callable ? { instruction: data.next } : { next: { tool: "inspect", target: `tool:${data.tool}`, view: "status" } }
|
|
118
138
|
: omittedNext, omittedNext);
|
|
119
|
-
const render = () =>
|
|
120
|
-
...(data.boundary === undefined ?
|
|
121
|
-
...(warnings ?
|
|
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 (
|
|
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
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
221
|
-
const render = (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
255
|
-
const flag = part("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 ?
|
|
259
|
-
const render = () =>
|
|
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 (
|
|
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
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
362
|
-
const facts =
|
|
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 =
|
|
368
|
-
if (
|
|
369
|
-
return complete;
|
|
326
|
+
const complete = ToolsResponse.renderYamlMetadata({ tool: name, ...facts }, budget);
|
|
327
|
+
if (complete.state === "available")
|
|
328
|
+
return complete.text;
|
|
370
329
|
}
|
|
371
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
577
|
-
const
|
|
578
|
-
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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(
|
|
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" +
|
|
331
|
-
|
|
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(
|
|
417
|
-
|
|
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(
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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 */
|