@llblab/pi-actors 0.56.1 → 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 +6 -6
- package/BACKLOG.md +4 -14
- package/CHANGELOG.md +9 -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 +82 -117
- 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/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 +15 -13
- 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 +89 -124
- 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/references/diagnostics.md +3 -0
package/lib/registry.ts
CHANGED
|
@@ -172,6 +172,26 @@ 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;
|
|
@@ -235,74 +255,35 @@ function registryNoticeText(kind: "activated" | "promoted" | "deleted", name: st
|
|
|
235
255
|
}
|
|
236
256
|
|
|
237
257
|
function registrationTransactionFailureText(stage: string, generation: string, summary: string, error: string): string {
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return { full, selected: Buffer.byteLength(full) <= Buffer.byteLength(omitted) ? full : omitted };
|
|
244
|
-
};
|
|
245
|
-
const selectedGeneration = part("resolution_generation", generation);
|
|
246
|
-
const selectedSummary = part("active_skills_summary", summary);
|
|
247
|
-
const selectedError = part("error", error);
|
|
248
|
-
const render = () => [field("result", "Tool registration transaction failed."), field("stage", stage),
|
|
249
|
-
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 });
|
|
250
263
|
// Keep a fitting surfaced error before large optional resolution metadata.
|
|
251
|
-
|
|
252
|
-
if (current.selected === current.full) continue;
|
|
253
|
-
const previous = current.selected;
|
|
254
|
-
current.selected = current.full;
|
|
255
|
-
if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
|
|
256
|
-
}
|
|
257
|
-
return render();
|
|
264
|
+
return growRegistryDocument(render, [selectedError, selectedGeneration, selectedSummary]);
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
function candidateAdmissionFailureText(data: { kind: "source"; source: string; diagnostics: string; summary: string; instruction: string } | { kind: "recipe"; diagnostics: string }): string {
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const diagnostics = part("diagnostics_summary", data.diagnostics);
|
|
269
|
-
const source = data.kind === "source" ? part("source", data.source, true) : undefined;
|
|
270
|
-
const summary = data.kind === "source" ? part("active_skills_summary", data.summary) : undefined;
|
|
271
|
-
const instruction = data.kind === "source" ? part("instruction", data.instruction) : undefined;
|
|
272
|
-
const render = () => [field("field", "recipe"), ...(source ? [source.selected] : []),
|
|
273
|
-
field("result", data.kind === "source" ? "Recipe source validation failed." : "Tool recipe validation failed."),
|
|
274
|
-
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 : {}) });
|
|
275
275
|
// Preserve fitting diagnostics before large optional source/context/advice fields.
|
|
276
|
-
|
|
277
|
-
if (!current || current.selected === current.full) continue;
|
|
278
|
-
const previous = current.selected;
|
|
279
|
-
current.selected = current.full;
|
|
280
|
-
if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
|
|
281
|
-
}
|
|
282
|
-
return render();
|
|
276
|
+
return growRegistryDocument(render, [diagnostics, ...(source ? [source] : []), ...(summary ? [summary] : []), ...(instruction ? [instruction] : [])]);
|
|
283
277
|
}
|
|
284
278
|
|
|
285
279
|
function sourceNormalizationFailureText(source: string, reason: string, summary: string, instruction: string): string {
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const selectedSource = part("source", source, true);
|
|
294
|
-
const selectedReason = part("reason", reason);
|
|
295
|
-
const selectedSummary = part("active_skills_summary", summary);
|
|
296
|
-
const selectedInstruction = part("instruction", instruction);
|
|
297
|
-
const render = () => [field("field", "from"), selectedSource.selected, field("result", "Recipe source is invalid."),
|
|
298
|
-
selectedReason.selected, selectedSummary.selected, selectedInstruction.selected].join("\n");
|
|
299
|
-
for (const current of [selectedSource, selectedReason, selectedSummary, selectedInstruction]) {
|
|
300
|
-
if (current.selected === current.full) continue;
|
|
301
|
-
const previous = current.selected;
|
|
302
|
-
current.selected = current.full;
|
|
303
|
-
if (Buffer.byteLength(render()) > Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) current.selected = previous;
|
|
304
|
-
}
|
|
305
|
-
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]);
|
|
306
287
|
}
|
|
307
288
|
|
|
308
289
|
function activeSkillSummary(
|
|
@@ -319,16 +300,11 @@ function activeSkillSummary(
|
|
|
319
300
|
}
|
|
320
301
|
|
|
321
302
|
function draftPathFailureText(kind: "root" | "missing", reference: string): string {
|
|
322
|
-
const
|
|
323
|
-
const render = (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (wellFormed) {
|
|
328
|
-
const complete = render(ToolsResponse.formatMarkdownField(key, ToolsResponse.formatLiteralBlock(reference)));
|
|
329
|
-
if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
|
|
330
|
-
}
|
|
331
|
-
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]);
|
|
332
308
|
}
|
|
333
309
|
|
|
334
310
|
function assertDraftPath<TContext>(
|
|
@@ -382,24 +358,16 @@ function draftReceiptText(result: "updated" | "registered", tool: string, callab
|
|
|
382
358
|
}
|
|
383
359
|
|
|
384
360
|
function duplicateRegistrationText(value: string): string {
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
|
|
390
|
-
}
|
|
391
|
-
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]);
|
|
392
365
|
}
|
|
393
366
|
|
|
394
367
|
function draftRecipeFailureText(key: "reason" | "diagnostics_summary", value?: string): string {
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (wellFormed) {
|
|
399
|
-
const complete = header + "\n" + ToolsResponse.formatMarkdownField(key, value);
|
|
400
|
-
if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
|
|
401
|
-
}
|
|
402
|
-
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] : []);
|
|
403
371
|
}
|
|
404
372
|
|
|
405
373
|
function promoteDraftRecipe<TContext>(
|
|
@@ -607,9 +575,9 @@ function assertTemplateIsNotNestedRecipe(value: unknown): void {
|
|
|
607
575
|
))
|
|
608
576
|
) {
|
|
609
577
|
throw new Error(
|
|
610
|
-
ExecutionOutput.formatToolText(
|
|
611
|
-
|
|
612
|
-
|
|
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={...}." })),
|
|
613
581
|
);
|
|
614
582
|
}
|
|
615
583
|
}
|
|
@@ -626,7 +594,7 @@ function getInputTemplate(
|
|
|
626
594
|
});
|
|
627
595
|
if (steps.length === 0)
|
|
628
596
|
throw new Error(
|
|
629
|
-
ExecutionOutput.formatToolText(
|
|
597
|
+
ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template sequence is empty." })),
|
|
630
598
|
);
|
|
631
599
|
return value;
|
|
632
600
|
}
|
|
@@ -637,9 +605,8 @@ function getInputTemplate(
|
|
|
637
605
|
return value as CommandTemplates.CommandTemplateConfig;
|
|
638
606
|
}
|
|
639
607
|
throw new Error(
|
|
640
|
-
ExecutionOutput.formatToolText(
|
|
641
|
-
|
|
642
|
-
),
|
|
608
|
+
ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "unsupported template type",
|
|
609
|
+
supported_types: ["string", "object", "sequence"] })),
|
|
643
610
|
);
|
|
644
611
|
}
|
|
645
612
|
|
|
@@ -665,7 +632,7 @@ function normalizeDefaults(value: unknown): Record<string, unknown> | undefined
|
|
|
665
632
|
if (value === undefined) return undefined;
|
|
666
633
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
667
634
|
throw new Error(
|
|
668
|
-
ExecutionOutput.formatToolText(
|
|
635
|
+
ExecutionOutput.formatToolText(registryMetadataText({ field: "defaults", result: "Tool defaults must be an object." })),
|
|
669
636
|
);
|
|
670
637
|
}
|
|
671
638
|
return { ...(value as Record<string, unknown>) };
|
|
@@ -725,9 +692,8 @@ function buildAuthoredRecipe(
|
|
|
725
692
|
const description = (input.description ?? existing?.description ?? "").trim();
|
|
726
693
|
if (!description && !from) {
|
|
727
694
|
throw new Error(
|
|
728
|
-
ExecutionOutput.formatToolText(
|
|
729
|
-
|
|
730
|
-
),
|
|
695
|
+
ExecutionOutput.formatToolText(registryMetadataText({ field: "description",
|
|
696
|
+
result: "Tool description is required for command-template registration." })),
|
|
731
697
|
);
|
|
732
698
|
}
|
|
733
699
|
const inputDefaults = normalizeDefaults(input.defaults);
|
|
@@ -737,9 +703,10 @@ function buildAuthoredRecipe(
|
|
|
737
703
|
);
|
|
738
704
|
if (runtimeOwnedDefault) {
|
|
739
705
|
throw new Error(
|
|
740
|
-
ExecutionOutput.formatToolText(
|
|
741
|
-
|
|
742
|
-
|
|
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." })),
|
|
743
710
|
);
|
|
744
711
|
}
|
|
745
712
|
const authored: Record<string, unknown> = {
|
|
@@ -758,7 +725,7 @@ function buildAuthoredRecipe(
|
|
|
758
725
|
const template = getInputTemplate(input.template);
|
|
759
726
|
if (template === null) {
|
|
760
727
|
throw new Error(
|
|
761
|
-
ExecutionOutput.formatToolText(
|
|
728
|
+
ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template cannot be null here." })),
|
|
762
729
|
);
|
|
763
730
|
}
|
|
764
731
|
const finalTemplate =
|
|
@@ -767,7 +734,7 @@ function buildAuthoredRecipe(
|
|
|
767
734
|
: template;
|
|
768
735
|
if (!finalTemplate) {
|
|
769
736
|
throw new Error(
|
|
770
|
-
ExecutionOutput.formatToolText(
|
|
737
|
+
ExecutionOutput.formatToolText(registryMetadataText({ field: "template", result: "Tool template is required." })),
|
|
771
738
|
);
|
|
772
739
|
}
|
|
773
740
|
const authored: Record<string, unknown> = {
|
|
@@ -792,9 +759,9 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
|
|
|
792
759
|
const record = input as RegisterToolInput & Record<string, unknown>;
|
|
793
760
|
if (Object.hasOwn(record, "values")) {
|
|
794
761
|
throw new Error(
|
|
795
|
-
ExecutionOutput.formatToolText(
|
|
796
|
-
|
|
797
|
-
|
|
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." })),
|
|
798
765
|
);
|
|
799
766
|
}
|
|
800
767
|
const fromProvided = Object.hasOwn(record, "from");
|
|
@@ -803,9 +770,10 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
|
|
|
803
770
|
(typeof input.from !== "string" || input.from.trim().length === 0)
|
|
804
771
|
) {
|
|
805
772
|
throw new Error(
|
|
806
|
-
ExecutionOutput.formatToolText(
|
|
807
|
-
|
|
808
|
-
|
|
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." })),
|
|
809
777
|
);
|
|
810
778
|
}
|
|
811
779
|
const draftProvided =
|
|
@@ -815,9 +783,9 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
|
|
|
815
783
|
Number(fromProvided) + Number(draftProvided) + Number(templateProvided);
|
|
816
784
|
if (modeCount > 1) {
|
|
817
785
|
throw new Error(
|
|
818
|
-
ExecutionOutput.formatToolText(
|
|
819
|
-
|
|
820
|
-
|
|
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." })),
|
|
821
789
|
);
|
|
822
790
|
}
|
|
823
791
|
if (
|
|
@@ -825,21 +793,18 @@ function assertRegisterToolInputModes(input: RegisterToolInput): void {
|
|
|
825
793
|
(Object.hasOwn(record, "args") || Object.hasOwn(record, "async"))
|
|
826
794
|
) {
|
|
827
795
|
throw new Error(
|
|
828
|
-
ExecutionOutput.formatToolText(
|
|
829
|
-
|
|
830
|
-
|
|
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." })),
|
|
831
800
|
);
|
|
832
801
|
}
|
|
833
802
|
}
|
|
834
803
|
|
|
835
804
|
function reservedRegistrationNameText(value: string): string {
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
const complete = render(ToolsResponse.formatMarkdownField("value", value));
|
|
840
|
-
if (Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1) return complete;
|
|
841
|
-
}
|
|
842
|
-
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]);
|
|
843
808
|
}
|
|
844
809
|
|
|
845
810
|
function executeRegisterToolUnlocked<TContext>(
|
|
@@ -856,7 +821,7 @@ function executeRegisterToolUnlocked<TContext>(
|
|
|
856
821
|
if (!input.name) return listTools(deps);
|
|
857
822
|
const name = Identity.normalizeToolName(input.name);
|
|
858
823
|
if (!name)
|
|
859
|
-
throw new Error(ExecutionOutput.formatToolText(
|
|
824
|
+
throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "name", result: "Invalid tool name." })));
|
|
860
825
|
if (deps.reservedToolNames.has(name)) {
|
|
861
826
|
throw new Error(
|
|
862
827
|
ExecutionOutput.formatToolText(reservedRegistrationNameText(`${name}`)),
|
|
@@ -904,9 +869,9 @@ function executeRegisterToolUnlocked<TContext>(
|
|
|
904
869
|
}
|
|
905
870
|
if (template === undefined && !input.from && !existing) {
|
|
906
871
|
throw new Error(
|
|
907
|
-
ExecutionOutput.formatToolText(
|
|
908
|
-
|
|
909
|
-
|
|
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." })),
|
|
910
875
|
);
|
|
911
876
|
}
|
|
912
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
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
// Owns: Bounded raw MI wire decoding, query inputs
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Owns: Bounded raw MI wire decoding, query inputs, unadmitted provenance fragments, and unrouted
|
|
2
|
+
// helper manifest/asset verification plus a shell-free invocation spec, subordinate to process acquisition.
|
|
3
|
+
// Excludes: Identity construction/digest/comparison, status/liveness/lifecycle/publication/admission, fallback,
|
|
4
|
+
// process spawning and package inventory. No production reader imports this owner; decoded data, verified
|
|
5
|
+
// asset bytes and the invocation spec never admit a representation profile, architecture or activation.
|
|
6
|
+
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
|
|
9
|
+
const MANIFEST_MAX_BYTES = 32 * 1024;
|
|
10
|
+
const EXECUTABLE_MAX_BYTES = 8 * 1024 * 1024;
|
|
11
|
+
const HELPER_VERSION = /^(?:0|[1-9][0-9]{0,5})\.(?:0|[1-9][0-9]{0,5})\.(?:0|[1-9][0-9]{0,5})$/u;
|
|
12
|
+
const MODULE_FILE = /\/lib\/runs-process-windows-mi\.(?:ts|js)$/u;
|
|
4
13
|
|
|
5
14
|
type NativeStage = 'application' | 'session' | 'input' | 'retrieval' | 'operation' | 'representation' | 'cleanup';
|
|
6
15
|
type RawTimestamp = {
|
|
@@ -111,3 +120,158 @@ export function decodeResponse(bytes: Uint8Array, profile: unknown, pids: unknow
|
|
|
111
120
|
if (optionalNull) return { kind: 'unusable', reason: 'unsupported_optional_fields' };
|
|
112
121
|
return { kind: 'decoded_unadmitted', rows };
|
|
113
122
|
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Complete-helper manifest claim. Bounded reading, exact-key schema and closed lexemes only; a valid
|
|
126
|
+
* manifest is integrity/provenance input, never executable authenticity, an approved asset or activation.
|
|
127
|
+
*/
|
|
128
|
+
type HelperManifest = {
|
|
129
|
+
readonly helper_version: string;
|
|
130
|
+
readonly architecture: 'windows-x64';
|
|
131
|
+
readonly protocol: 'native-mi-v1';
|
|
132
|
+
readonly profiles: readonly ['scalar', 'shared'];
|
|
133
|
+
readonly native_source_sha256: string;
|
|
134
|
+
readonly executable_bytes: number;
|
|
135
|
+
readonly executable_sha256: string;
|
|
136
|
+
readonly toolchain: { readonly compiler: string; readonly sdk: string; readonly build_arguments: readonly string[] };
|
|
137
|
+
readonly dependencies: readonly string[];
|
|
138
|
+
readonly originating_source_commit: string;
|
|
139
|
+
readonly originating_build_job: string;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
type ManifestUnusable =
|
|
143
|
+
| { readonly kind: 'unusable'; readonly reason: 'oversize_manifest' | 'invalid_manifest' | 'unsupported_manifest' };
|
|
144
|
+
|
|
145
|
+
function boundedText(bytes: Uint8Array, maximumBytes: number): string | undefined {
|
|
146
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength > maximumBytes) return undefined;
|
|
147
|
+
if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) return undefined;
|
|
148
|
+
try { return new TextDecoder('utf-8', { fatal: true }).decode(bytes); }
|
|
149
|
+
catch { return undefined; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function boundedLexemes(value: unknown, maximum: number): string[] | undefined {
|
|
153
|
+
if (!Array.isArray(value) || value.length > maximum) return undefined;
|
|
154
|
+
const selected: string[] = [];
|
|
155
|
+
for (const item of value) {
|
|
156
|
+
if (typeof item !== 'string' || item.length === 0 || item.length > 128 || !/^[\x20-\x7e]+$/.test(item) || selected.includes(item)) return undefined;
|
|
157
|
+
selected.push(item);
|
|
158
|
+
}
|
|
159
|
+
return selected;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Bounded complete-manifest reader: one strict UTF-8 JSON document with an exact reviewed shape. */
|
|
163
|
+
export function readHelperManifest(bytes: Uint8Array):
|
|
164
|
+
| { readonly kind: 'manifest_unadmitted'; readonly manifest: HelperManifest }
|
|
165
|
+
| ManifestUnusable {
|
|
166
|
+
const invalid: ManifestUnusable = { kind: 'unusable', reason: 'invalid_manifest' };
|
|
167
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength > MANIFEST_MAX_BYTES) return { kind: 'unusable', reason: 'oversize_manifest' };
|
|
168
|
+
const text = boundedText(bytes, MANIFEST_MAX_BYTES);
|
|
169
|
+
if (text === undefined) return invalid;
|
|
170
|
+
let value: unknown;
|
|
171
|
+
try { value = JSON.parse(text); }
|
|
172
|
+
catch { return invalid; }
|
|
173
|
+
if (!exactKeys(value, ['helper_version', 'architecture', 'protocol', 'profiles', 'native_source_sha256',
|
|
174
|
+
'executable_bytes', 'executable_sha256', 'toolchain', 'dependencies', 'originating_source_commit', 'originating_build_job']))
|
|
175
|
+
return invalid;
|
|
176
|
+
const { helper_version, architecture, protocol, profiles, native_source_sha256, executable_bytes,
|
|
177
|
+
executable_sha256, toolchain, dependencies, originating_source_commit, originating_build_job } = value;
|
|
178
|
+
if (typeof helper_version !== 'string' || !HELPER_VERSION.test(helper_version) ||
|
|
179
|
+
architecture !== 'windows-x64' || protocol !== 'native-mi-v1' ||
|
|
180
|
+
!Array.isArray(profiles) || profiles.length !== 2 || profiles[0] !== 'scalar' || profiles[1] !== 'shared')
|
|
181
|
+
return { kind: 'unusable', reason: 'unsupported_manifest' };
|
|
182
|
+
if (typeof native_source_sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(native_source_sha256) ||
|
|
183
|
+
typeof executable_sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(executable_sha256) ||
|
|
184
|
+
typeof originating_source_commit !== 'string' || !/^[0-9a-f]{40}$/.test(originating_source_commit) ||
|
|
185
|
+
typeof originating_build_job !== 'string' || !/^[1-9][0-9]*$/.test(originating_build_job) ||
|
|
186
|
+
typeof executable_bytes !== 'number' || !Number.isSafeInteger(executable_bytes) || executable_bytes <= 0)
|
|
187
|
+
return invalid;
|
|
188
|
+
if (executable_bytes > EXECUTABLE_MAX_BYTES) return { kind: 'unusable', reason: 'unsupported_manifest' };
|
|
189
|
+
if (!exactKeys(toolchain, ['compiler', 'sdk', 'build_arguments'])) return invalid;
|
|
190
|
+
const compiler = boundedLexemes([toolchain.compiler], 1)?.[0];
|
|
191
|
+
const sdk = boundedLexemes([toolchain.sdk], 1)?.[0];
|
|
192
|
+
const buildArguments = boundedLexemes(toolchain.build_arguments, 32);
|
|
193
|
+
const dependencyNames = boundedLexemes(dependencies, 32);
|
|
194
|
+
if (compiler === undefined || sdk === undefined || buildArguments === undefined || dependencyNames === undefined) return invalid;
|
|
195
|
+
// Fresh primitive claims only; the caller still owns target/version/source-digest policy.
|
|
196
|
+
return {
|
|
197
|
+
kind: 'manifest_unadmitted',
|
|
198
|
+
manifest: {
|
|
199
|
+
helper_version, architecture: 'windows-x64', protocol: 'native-mi-v1', profiles: ['scalar', 'shared'],
|
|
200
|
+
native_source_sha256, executable_bytes, executable_sha256,
|
|
201
|
+
toolchain: { compiler, sdk, build_arguments: buildArguments }, dependencies: dependencyNames,
|
|
202
|
+
originating_source_commit, originating_build_job,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
type HelperAssetLocation = {
|
|
208
|
+
readonly kind: 'located';
|
|
209
|
+
readonly helper_version: string;
|
|
210
|
+
readonly manifest_url: URL;
|
|
211
|
+
readonly executable_url: URL;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Fixed versioned package-relative lookup. Never searches cwd, PATH or caller locations: the only base is
|
|
216
|
+
* the sibling `native/` directory of this module's own `lib/` (source) or `dist/lib/` (compiled) home.
|
|
217
|
+
*/
|
|
218
|
+
export function resolveHelperAsset(moduleUrl: unknown, runtime: unknown, helperVersion: unknown):
|
|
219
|
+
| HelperAssetLocation
|
|
220
|
+
| { readonly kind: 'unusable'; readonly reason: 'unsupported_location' | 'unsupported_target' | 'unsupported_version' } {
|
|
221
|
+
let url: URL;
|
|
222
|
+
try { url = new URL(String(moduleUrl)); }
|
|
223
|
+
catch { return { kind: 'unusable', reason: 'unsupported_location' }; }
|
|
224
|
+
if (url.protocol !== 'file:' || !MODULE_FILE.test(url.pathname)) return { kind: 'unusable', reason: 'unsupported_location' };
|
|
225
|
+
const target = runtime as { platform?: unknown; arch?: unknown } | null | undefined;
|
|
226
|
+
if (target?.platform !== 'win32' || target?.arch !== 'x64') return { kind: 'unusable', reason: 'unsupported_target' };
|
|
227
|
+
if (typeof helperVersion !== 'string' || !HELPER_VERSION.test(helperVersion)) return { kind: 'unusable', reason: 'unsupported_version' };
|
|
228
|
+
const directory = `../native/windows-mi/win32-x64/${helperVersion}/`;
|
|
229
|
+
return {
|
|
230
|
+
kind: 'located', helper_version: helperVersion,
|
|
231
|
+
manifest_url: new URL(`${directory}manifest.json`, url),
|
|
232
|
+
executable_url: new URL(`${directory}helper.exe`, url),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Injected bounded reader: exact bytes for one fixed URL or nothing. Never a path, cwd or PATH lookup. */
|
|
237
|
+
export type HelperAssetReader = (url: URL, maximumBytes: number) => Uint8Array | undefined;
|
|
238
|
+
|
|
239
|
+
type HelperAssetOutcome =
|
|
240
|
+
| { readonly kind: 'asset_verified_unadmitted'; readonly manifest: HelperManifest; readonly executable_sha256: string; readonly executable_bytes: number }
|
|
241
|
+
| { readonly kind: 'unusable'; readonly reason:
|
|
242
|
+
'missing_manifest' | 'oversize_manifest' | 'invalid_manifest' | 'unsupported_manifest' | 'version_mismatch' |
|
|
243
|
+
'missing_asset' | 'oversize_asset' | 'size_mismatch' | 'digest_mismatch' };
|
|
244
|
+
|
|
245
|
+
/** Trusted exact-byte selection: bounded manifest read, exact declared size, then exact SHA-256 match. */
|
|
246
|
+
export function loadHelperAsset(location: HelperAssetLocation, read: HelperAssetReader): HelperAssetOutcome {
|
|
247
|
+
const manifestBytes = read(location.manifest_url, MANIFEST_MAX_BYTES + 1);
|
|
248
|
+
if (manifestBytes === undefined) return { kind: 'unusable', reason: 'missing_manifest' };
|
|
249
|
+
if (manifestBytes.byteLength > MANIFEST_MAX_BYTES) return { kind: 'unusable', reason: 'oversize_manifest' };
|
|
250
|
+
const reading = readHelperManifest(manifestBytes);
|
|
251
|
+
if (reading.kind === 'unusable') return { kind: 'unusable', reason: reading.reason };
|
|
252
|
+
const { manifest } = reading;
|
|
253
|
+
if (manifest.helper_version !== location.helper_version) return { kind: 'unusable', reason: 'version_mismatch' };
|
|
254
|
+
// One extra byte detects growth; a shorter read can never satisfy the declared size.
|
|
255
|
+
const executableBytes = read(location.executable_url, manifest.executable_bytes + 1);
|
|
256
|
+
if (executableBytes === undefined) return { kind: 'unusable', reason: 'missing_asset' };
|
|
257
|
+
if (executableBytes.byteLength > manifest.executable_bytes) return { kind: 'unusable', reason: 'oversize_asset' };
|
|
258
|
+
if (executableBytes.byteLength !== manifest.executable_bytes) return { kind: 'unusable', reason: 'size_mismatch' };
|
|
259
|
+
const digest = createHash('sha256').update(executableBytes).digest('hex');
|
|
260
|
+
if (digest !== manifest.executable_sha256) return { kind: 'unusable', reason: 'digest_mismatch' };
|
|
261
|
+
return { kind: 'asset_verified_unadmitted', manifest, executable_sha256: digest, executable_bytes: executableBytes.byteLength };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
type HelperInvocation =
|
|
265
|
+
| { readonly kind: 'invocation_unadmitted'; readonly command: string; readonly args: readonly string[];
|
|
266
|
+
readonly options: { readonly shell: false; readonly windowsHide: true } }
|
|
267
|
+
| { readonly kind: 'unusable'; readonly reason: 'unsupported_executable' | 'invalid_query' };
|
|
268
|
+
|
|
269
|
+
/** Shell-free invocation spec only: no spawn, retry, fallback or execution-policy bypass. */
|
|
270
|
+
export function helperInvocation(executablePath: unknown, profile: unknown, pids: unknown): HelperInvocation {
|
|
271
|
+
if (typeof executablePath !== 'string' || executablePath.length === 0 || executablePath.length > 4096 ||
|
|
272
|
+
!/^[A-Za-z]:[\\/][^\x00\r\n"]*\.exe$/.test(executablePath)) return { kind: 'unusable', reason: 'unsupported_executable' };
|
|
273
|
+
const query = queryArguments(profile, pids);
|
|
274
|
+
if (!query) return { kind: 'unusable', reason: 'invalid_query' };
|
|
275
|
+
return { kind: 'invocation_unadmitted', command: executablePath, args: query,
|
|
276
|
+
options: { shell: false, windowsHide: true } };
|
|
277
|
+
}
|