@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/tools-message.ts
CHANGED
|
@@ -17,7 +17,54 @@ function asRecord(value: unknown): Record<string, unknown> {
|
|
|
17
17
|
: {};
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const CONTROL_BUDGET = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
|
|
21
|
+
|
|
22
|
+
type ControlDocument = Record<string, unknown>;
|
|
23
|
+
type ControlPart = { full: ControlDocument; selected: ControlDocument };
|
|
24
|
+
|
|
25
|
+
function controlMetadata(value: ControlDocument): string {
|
|
26
|
+
return (ToolsResponse.renderYamlMetadata(value, CONTROL_BUDGET) as
|
|
27
|
+
Extract<ToolsResponse.YamlMetadataRendering, { state: "available" }>).text;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One opaque selected scalar as a whole value or an explicit whole-field omission, never a clip. */
|
|
31
|
+
function controlSelected(key: string, value: string): ControlPart {
|
|
32
|
+
return ToolsResponse.selectYamlField(key, value, CONTROL_BUDGET);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Compiler-selected option strings stay a native sequence; malformed selections stay unavailable. */
|
|
36
|
+
function controlChoices(options: { key: "supported_actions" | "available_actions"; values: readonly string[] }): ControlPart {
|
|
37
|
+
const key = options.key, values = options.values;
|
|
38
|
+
// Values were selected by the original string filter, not a new action grammar.
|
|
39
|
+
const wellFormed = values.every((value) => typeof value === "string" && value.isWellFormed());
|
|
40
|
+
const omitted: ControlDocument = { [key]: wellFormed ? "[omitted]" : "[unavailable]",
|
|
41
|
+
[key + "_omission"]: { reason: wellFormed ? "response_too_large" : "malformed_selected_text",
|
|
42
|
+
shown: 0, omitted: values.length, total: values.length } };
|
|
43
|
+
const full: ControlDocument = !wellFormed ? omitted : values.length ? { [key]: [...values] } : { [key]: "none" };
|
|
44
|
+
const complete = ToolsResponse.renderYamlMetadata(full, CONTROL_BUDGET);
|
|
45
|
+
return { full, selected: complete.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(controlMetadata(omitted)) ? full : omitted };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Grow whole selected fields in priority order while the complete document still fits. */
|
|
49
|
+
function controlDocument(render: () => ControlDocument, order: readonly ControlPart[]): string {
|
|
50
|
+
return ToolsResponse.growYamlDocument(render, order, CONTROL_BUDGET);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Only producer-known read-only inspect literals become typed next; other advice stays opaque content. */
|
|
54
|
+
function controlNextPart(action: string): ControlPart {
|
|
55
|
+
const inspect = /^inspect target=([^\s]+) view=([^\s]+)$/u.exec(action);
|
|
56
|
+
if (inspect) {
|
|
57
|
+
return ToolsResponse.selectYamlNode("next", { tool: "inspect", target: inspect[1], view: inspect[2] }, CONTROL_BUDGET);
|
|
58
|
+
}
|
|
59
|
+
const wellFormed = action.isWellFormed();
|
|
60
|
+
const omitted: ControlDocument = { next: { instruction: wellFormed ? "[omitted]" : "[unavailable]",
|
|
61
|
+
instruction_omission: { reason: wellFormed ? "response_too_large" : "malformed_selected_text", shown: 0, omitted: 1, total: 1 } } };
|
|
62
|
+
const full: ControlDocument = wellFormed ? { next: { instruction: action } } : omitted;
|
|
63
|
+
const complete = wellFormed ? ToolsResponse.renderYamlMetadata(full, CONTROL_BUDGET) : undefined;
|
|
64
|
+
const selected = complete?.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(controlMetadata(omitted))
|
|
65
|
+
? full : omitted;
|
|
66
|
+
return { full, selected };
|
|
67
|
+
}
|
|
21
68
|
|
|
22
69
|
/** Only the four compiler-owned guard sites below; never wraps dispatch/dependency errors. */
|
|
23
70
|
function controlGuardText(
|
|
@@ -26,37 +73,12 @@ function controlGuardText(
|
|
|
26
73
|
options?: { key: "supported_actions" | "available_actions"; values: readonly string[] },
|
|
27
74
|
): string {
|
|
28
75
|
const key = request.target === "runtime" ? "target" : "run";
|
|
29
|
-
const identity =
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let malformed = false;
|
|
36
|
-
// Values were selected by the original string filter, not by a new action grammar.
|
|
37
|
-
for (let index = 0; index < options.values.length; index++) {
|
|
38
|
-
const value = options.values[index];
|
|
39
|
-
if (!value.isWellFormed()) { malformed = true; break; }
|
|
40
|
-
rows.push(" - " + value.replaceAll(/\r\n|\r|\n/gu, newline => newline + " "));
|
|
41
|
-
}
|
|
42
|
-
omittedOptions = `- ${options.key}: [${malformed ? "unavailable" : "omitted"}]\n- ${options.key}_omission:\n - reason: ${malformed ? "malformed_selected_text" : "response_too_large"}\n - shown: 0\n - omitted: ${options.values.length}\n - total: ${options.values.length}`;
|
|
43
|
-
fullOptions = malformed ? omittedOptions : rows.length ? `- ${options.key}:\n${rows.join("\n")}` : controlField(options.key, "none");
|
|
44
|
-
}
|
|
45
|
-
// Start with the shorter complete-or-omitted rendering: adding an omission scaffold must
|
|
46
|
-
// never manufacture overflow when the complete small field would fit. Then grow only.
|
|
47
|
-
let selectedIdentity = Buffer.byteLength(identity) <= Buffer.byteLength(omittedIdentity) ? identity : omittedIdentity;
|
|
48
|
-
let selectedOptions = Buffer.byteLength(fullOptions) <= Buffer.byteLength(omittedOptions) ? fullOptions : omittedOptions;
|
|
49
|
-
const render = (target: string, choices: string) => ToolsResponse.withLeadingLineBreak(
|
|
50
|
-
[target, ...fixed, ...(options ? [choices] : [])].join("\n"));
|
|
51
|
-
if (Buffer.byteLength(render(identity, selectedOptions)) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES) selectedIdentity = identity;
|
|
52
|
-
if (options && Buffer.byteLength(render(selectedIdentity, fullOptions)) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES) selectedOptions = fullOptions;
|
|
53
|
-
return render(selectedIdentity, selectedOptions);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function controlNext(action: string): string {
|
|
57
|
-
const inspect = /^inspect target=([^\s]+) view=([^\s]+)$/u.exec(action);
|
|
58
|
-
return inspect ? ToolsResponse.formatInspectNextAction(inspect[1], inspect[2])
|
|
59
|
-
: "- next:\n" + controlField("instruction", action, 1);
|
|
76
|
+
const identity = controlSelected(key, request.target === "runtime" ? request.target : request.target.slice(4));
|
|
77
|
+
const choices = options ? controlChoices(options) : undefined;
|
|
78
|
+
const render = (): ControlDocument => ({ ...identity.selected, action: request.action, result,
|
|
79
|
+
...(choices ? choices.selected : {}) });
|
|
80
|
+
// Whole identity first, then the complete option sequence: the shorter seed must never manufacture overflow.
|
|
81
|
+
return ToolsResponse.withLeadingLineBreak(controlDocument(render, choices ? [identity, choices] : [identity]));
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
/** Pure first-layer copy; legacy stopped/killed flags are not scope-closure proof. */
|
|
@@ -64,32 +86,40 @@ export function compactControlResult(
|
|
|
64
86
|
request: Control.ControlRequest,
|
|
65
87
|
result: Record<string, unknown>,
|
|
66
88
|
): string {
|
|
67
|
-
const
|
|
68
|
-
|
|
89
|
+
const key = request.target.startsWith("run:") ? "run" : "target";
|
|
90
|
+
const identity = controlSelected(key, key === "run" ? request.target.slice(4) : request.target);
|
|
91
|
+
const facts: ControlDocument = {};
|
|
69
92
|
if (result.stopped === false || result.archived === false || result.pruned === false) {
|
|
70
|
-
|
|
71
|
-
if (typeof result.reason === "string") fields.push(controlField("reason", result.reason));
|
|
72
|
-
const observed = asRecord(result.status);
|
|
73
|
-
if (result.reason === "not running" && typeof observed.status === "string") fields.push(controlField("state", observed.status));
|
|
93
|
+
facts.result = "not applied";
|
|
74
94
|
} else if (request.action === "kill" && result.stopped === true) {
|
|
75
|
-
|
|
95
|
+
facts.result = "kill attempted";
|
|
96
|
+
facts.termination = "unconfirmed";
|
|
76
97
|
} else if (result.archived === true) {
|
|
77
|
-
|
|
98
|
+
facts.result = "archived";
|
|
78
99
|
} else if (result.pruned === true) {
|
|
79
|
-
|
|
100
|
+
facts.result = "pruned";
|
|
80
101
|
} else if (result.delivery === "queued" || result.delivery === "delivered") {
|
|
81
|
-
|
|
102
|
+
facts.delivery = result.delivery;
|
|
103
|
+
facts.handling = "unconfirmed";
|
|
82
104
|
} else {
|
|
83
|
-
|
|
84
|
-
typeof result.delivery === "string" ? result.delivery : typeof result.status === "string" ? result.status : "accepted"
|
|
85
|
-
if (typeof result.reason === "string") fields.push(controlField("reason", result.reason));
|
|
105
|
+
facts[typeof result.delivery === "string" ? "delivery" : "result"] =
|
|
106
|
+
typeof result.delivery === "string" ? result.delivery : typeof result.status === "string" ? result.status : "accepted";
|
|
86
107
|
}
|
|
87
|
-
|
|
108
|
+
const notApplied = result.stopped === false || result.archived === false || result.pruned === false;
|
|
109
|
+
const reason = typeof result.reason === "string" ? controlSelected("reason", result.reason) : undefined;
|
|
110
|
+
const observed = asRecord(result.status);
|
|
111
|
+
const state = notApplied && result.reason === "not running" && typeof observed.status === "string"
|
|
112
|
+
? controlSelected("state", observed.status) : undefined;
|
|
113
|
+
const controlId = typeof result.control_id === "string" ? controlSelected("control_id", result.control_id) : undefined;
|
|
88
114
|
const actions = Array.isArray(result.next_actions) ? result.next_actions
|
|
89
115
|
: request.target.startsWith("run:") ? runNextActions(request.target.slice(4), result) : [];
|
|
90
116
|
const next = actions.find((action): action is string => typeof action === "string" && action.length > 0);
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
const nextPart = next === undefined ? undefined : controlNextPart(next);
|
|
118
|
+
const parts = [identity, ...(reason ? [reason] : []), ...(state ? [state] : []), ...(controlId ? [controlId] : []), ...(nextPart ? [nextPart] : [])];
|
|
119
|
+
const render = (): ControlDocument => ({ ...identity.selected, action: request.action, ...facts,
|
|
120
|
+
...(reason ? reason.selected : {}), ...(state ? state.selected : {}), ...(controlId ? controlId.selected : {}),
|
|
121
|
+
...(nextPart ? nextPart.selected : {}) });
|
|
122
|
+
return ToolsResponse.withLeadingLineBreak(controlDocument(render, parts));
|
|
93
123
|
}
|
|
94
124
|
|
|
95
125
|
function runNextActions(run: string, result: Record<string, unknown>): string[] {
|
|
@@ -228,16 +258,13 @@ export function createControlToolDefinition<TContext = unknown>(
|
|
|
228
258
|
);
|
|
229
259
|
} catch (cause) {
|
|
230
260
|
// Admission-era correlation is not current reply-scope proof after an awaited dispatch.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
? identity
|
|
239
|
-
: "- run: [omitted]\n- run_omission:\n - reason: response_too_large\n - shown: 0\n - omitted: 1\n - total: 1";
|
|
240
|
-
throw new Error(ToolsResponse.withLeadingLineBreak(target + "\n" + summary), { cause });
|
|
261
|
+
// Display one whole caller-owned id or an explicit whole-id omission; the warning stays literal.
|
|
262
|
+
const identity = controlSelected("run", run);
|
|
263
|
+
const next = controlNextPart("inspect target=runtime view=runs");
|
|
264
|
+
const render = (): ControlDocument => ({ ...identity.selected, action: request.action,
|
|
265
|
+
result: "request failed", delivery: "unconfirmed", handling: "unconfirmed",
|
|
266
|
+
warning: "Effects may already exist.\nDo not repeat automatically.", ...next.selected });
|
|
267
|
+
throw new Error(ToolsResponse.withLeadingLineBreak(controlDocument(render, [identity])), { cause });
|
|
241
268
|
}
|
|
242
269
|
}
|
|
243
270
|
// A refused identity change must not publish the replacement's status, even in verbose/details.
|
package/lib/tools-response.ts
CHANGED
|
@@ -84,6 +84,69 @@ export function renderYamlMetadata(value: unknown, byteBudget: number): YamlMeta
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
export type YamlPart = { full: Record<string, unknown>; selected: Record<string, unknown> };
|
|
88
|
+
|
|
89
|
+
/** One selected metadata node, kept whole or covered by a field/group omission. */
|
|
90
|
+
export function selectYamlNode(key: string, value: unknown, byteBudget: number, total = 1): YamlPart {
|
|
91
|
+
const full = { [key]: value };
|
|
92
|
+
const complete = renderYamlMetadata(full, byteBudget);
|
|
93
|
+
const invalid = complete.state === "unavailable" && complete.reason === "metadata_invalid";
|
|
94
|
+
const omitted = { [key]: invalid ? "[unavailable]" : "[omitted]",
|
|
95
|
+
[key + "_omission"]: { reason: invalid ? "invalid_selected_metadata" : "response_too_large", shown: 0, omitted: total, total } };
|
|
96
|
+
const fallback = renderYamlMetadata(omitted, byteBudget);
|
|
97
|
+
// Seed the shorter complete-or-omitted form: omission scaffolding must never manufacture overflow.
|
|
98
|
+
const selected = complete.state === "available" && fallback.state === "available" &&
|
|
99
|
+
Buffer.byteLength(complete.text) <= Buffer.byteLength(fallback.text) ? full : omitted;
|
|
100
|
+
return { full: invalid ? omitted : full, selected };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Opaque selected text or string sequence; malformed selections stay unavailable without repair. */
|
|
104
|
+
export function selectYamlField(key: string, value: string | readonly string[], byteBudget: number): YamlPart {
|
|
105
|
+
const values = typeof value === "string" ? [value] : value;
|
|
106
|
+
if (values.every(item => item.isWellFormed()))
|
|
107
|
+
return selectYamlNode(key, typeof value === "string" ? value : [...value], byteBudget, typeof value === "string" ? 1 : value.length);
|
|
108
|
+
const total = typeof value === "string" ? 1 : value.length;
|
|
109
|
+
const omitted = { [key]: "[unavailable]",
|
|
110
|
+
[key + "_omission"]: { reason: "malformed_selected_text", shown: 0, omitted: total, total } };
|
|
111
|
+
return { full: omitted, selected: omitted };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Grow whole selected fields in priority order while the complete document still fits. */
|
|
115
|
+
export function growYamlDocument(
|
|
116
|
+
render: () => Record<string, unknown>,
|
|
117
|
+
order: readonly YamlPart[],
|
|
118
|
+
byteBudget: number,
|
|
119
|
+
): string {
|
|
120
|
+
const fits = () => renderYamlMetadata(render(), byteBudget).state === "available";
|
|
121
|
+
for (const part of order) {
|
|
122
|
+
if (part.selected === part.full) continue;
|
|
123
|
+
const previous = part.selected;
|
|
124
|
+
part.selected = part.full;
|
|
125
|
+
if (!fits()) part.selected = previous;
|
|
126
|
+
}
|
|
127
|
+
const rendered = renderYamlMetadata(render(), byteBudget);
|
|
128
|
+
if (rendered.state !== "available")
|
|
129
|
+
throw new Error("Selected YAML metadata document is unavailable: " + rendered.reason);
|
|
130
|
+
return rendered.text;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Uncapped UI-only flat metadata; never borrow a model-output allowance for notices. */
|
|
134
|
+
export function formatYamlNotice(value: Record<string, string | number | boolean>): string {
|
|
135
|
+
const selected: Record<string, unknown> = {};
|
|
136
|
+
for (const [key, field] of Object.entries(value)) {
|
|
137
|
+
if (typeof field === "string" && !field.isWellFormed()) {
|
|
138
|
+
selected[key] = "[unavailable]";
|
|
139
|
+
selected[key + "_omission"] = { reason: "malformed_selected_text", shown: 0, omitted: 1, total: 1 };
|
|
140
|
+
} else selected[key] = field;
|
|
141
|
+
}
|
|
142
|
+
return yamlMetadataText(selected).replace(/\n$/u, "");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Close one rendered YAML header; a separate opaque body gets exactly one delimiter pair. */
|
|
146
|
+
export function frameMetadataDocument(header: string, body?: string): string {
|
|
147
|
+
return body === undefined ? header : `---\n${header}---\n${body}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
87
150
|
export function formatMarkdownField(key: string, value: string, depth = 0): string {
|
|
88
151
|
const indent = " ".repeat(depth);
|
|
89
152
|
return /[\r\n]/u.test(value)
|
|
@@ -463,6 +526,28 @@ export function recipeRegistryNextActions(
|
|
|
463
526
|
return [...new Set(actions)].slice(0, 4);
|
|
464
527
|
}
|
|
465
528
|
|
|
529
|
+
/** Compact metadata-only document text: one leading LF, no trailing LF, shared emitter budget. */
|
|
530
|
+
export function compactYamlDocument(value: Record<string, unknown>): string {
|
|
531
|
+
const text = growYamlDocument(() => value, [], Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
532
|
+
return `\n${text.replace(/\n$/u, "")}`;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** Only the closed read-only Recipe inspect literals become typed next; other advice stays opaque content. */
|
|
536
|
+
export function nextActionMetadata(action: string, byteBudget: number): YamlPart {
|
|
537
|
+
const recognized = [
|
|
538
|
+
["inspect target=recipes view=doctor", "doctor", false],
|
|
539
|
+
["inspect target=recipes view=imports verbose=true", "imports", true],
|
|
540
|
+
["inspect target=recipes view=summary verbose=true", "summary", true],
|
|
541
|
+
] as const;
|
|
542
|
+
const match = recognized.find(([literal]) => literal === action);
|
|
543
|
+
if (match) {
|
|
544
|
+
const typed = { next: { tool: "inspect", target: "recipes", view: match[1], ...(match[2] ? { verbose: true } : {}) } };
|
|
545
|
+
return { full: typed, selected: typed };
|
|
546
|
+
}
|
|
547
|
+
const instruction = selectYamlField("instruction", action, byteBudget);
|
|
548
|
+
return { full: { next: instruction.full }, selected: { next: instruction.selected } };
|
|
549
|
+
}
|
|
550
|
+
|
|
466
551
|
/** Only these existing read-only inspection hints become action nodes; other advice stays content. */
|
|
467
552
|
export function formatInspectionHint(action: string): string {
|
|
468
553
|
if (action === "inspect target=recipes view=doctor") return formatInspectNextAction("recipes", "doctor");
|
|
@@ -474,21 +559,11 @@ export function formatInspectionHint(action: string): string {
|
|
|
474
559
|
export function compactRecipeRegistry(
|
|
475
560
|
summary: Record<string, unknown>,
|
|
476
561
|
): string {
|
|
477
|
-
const
|
|
478
|
-
const shadowed =
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
const disabled = Array.isArray(summary.disabled)
|
|
483
|
-
? summary.disabled.length
|
|
484
|
-
: 0;
|
|
485
|
-
const diagnostics = Array.isArray(summary.diagnostics)
|
|
486
|
-
? summary.diagnostics.length
|
|
487
|
-
: 0;
|
|
488
|
-
const drafts = Array.isArray(summary.drafts) ? summary.drafts.length : 0;
|
|
489
|
-
const recommendations = Array.isArray(summary.recommendations)
|
|
490
|
-
? summary.recommendations.length
|
|
491
|
-
: 0;
|
|
562
|
+
const count = (key: string, source: unknown) => Array.isArray(source) ? source.length : 0;
|
|
563
|
+
const active = count("active", summary.active), shadowed = count("shadowed", summary.shadowed);
|
|
564
|
+
const invalid = count("invalid", summary.invalid), disabled = count("disabled", summary.disabled);
|
|
565
|
+
const diagnostics = count("diagnostics", summary.diagnostics), drafts = count("drafts", summary.drafts);
|
|
566
|
+
const recommendations = count("recommendations", summary.recommendations);
|
|
492
567
|
const skillCatalogPartial = summary.skill_recipe_catalog_partial === true;
|
|
493
568
|
const currentPolicy = Array.isArray(summary.active)
|
|
494
569
|
? (summary.active as Array<Record<string, unknown>>).filter(
|
|
@@ -498,11 +573,13 @@ export function compactRecipeRegistry(
|
|
|
498
573
|
const nextActions = Array.isArray(summary.next_actions)
|
|
499
574
|
? (summary.next_actions as string[])
|
|
500
575
|
: [];
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return
|
|
576
|
+
const hint = nextActions.length ? nextActionMetadata(nextActions[0]!, Limits.RUN_DELIVERY_MODEL_MAX_BYTES) : undefined;
|
|
577
|
+
const render = (): Record<string, unknown> => ({ recipes: "registry", active, drafts, shadowed, invalid, disabled,
|
|
578
|
+
current_policy: currentPolicy, skill_catalog_partial: skillCatalogPartial, recommendations, diagnostics,
|
|
579
|
+
...(hint ? hint.selected : {}) });
|
|
580
|
+
return hint
|
|
581
|
+
? `\n${growYamlDocument(render, [hint], Limits.RUN_DELIVERY_MODEL_MAX_BYTES).replace(/\n$/u, "")}`
|
|
582
|
+
: compactYamlDocument(render());
|
|
506
583
|
}
|
|
507
584
|
|
|
508
585
|
export const DEFAULT_INSPECT_LINES = Limits.DEFAULT_INSPECT_LINES;
|
package/lib/tools-spawn.ts
CHANGED
|
@@ -84,21 +84,28 @@ function writeSpawnDraftRecipe(
|
|
|
84
84
|
|
|
85
85
|
/** Compiler-owned pre-launch diagnostics only; selected caller values are not admitted Run ids. */
|
|
86
86
|
function spawnGuardText(kind: "state_dir" | "namespace" | "id", value?: string): string {
|
|
87
|
-
const field = ToolsResponse.formatMarkdownField;
|
|
88
87
|
const facts = kind === "state_dir"
|
|
89
|
-
?
|
|
90
|
-
:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
? { result: "unsupported field", reason: "Run state is runtime-owned; run:<id> remains addressable and retention-safe." }
|
|
89
|
+
: { result: kind === "namespace" ? "unsupported Run target" : "invalid Run target", accepted_format: "run:<id>",
|
|
90
|
+
...(kind === "id" ? { id_rule: "nonempty ASCII letters, digits, underscore, dot or hyphen" } : {}) };
|
|
91
|
+
// Measure the shared emitter's own projection: an unfit selected value must become an omission, not a clip.
|
|
92
|
+
const render = (selected?: Record<string, unknown>): string | undefined => {
|
|
93
|
+
const rendered = ToolsResponse.renderYamlMetadata({ field: kind === "state_dir" ? "state_dir" : "as", ...(selected ?? {}), ...facts },
|
|
94
|
+
Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
95
|
+
return rendered.state === "available"
|
|
96
|
+
? ToolsResponse.withLeadingLineBreak(ToolsResponse.frameMetadataDocument(rendered.text))
|
|
97
|
+
: undefined;
|
|
98
|
+
};
|
|
99
|
+
const required = (selected?: Record<string, unknown>) => render(selected) ?? render()!;
|
|
100
|
+
const wellFormed = value !== undefined && value.isWellFormed();
|
|
101
|
+
if (value !== undefined && wellFormed) {
|
|
102
|
+
const complete = render({ value });
|
|
103
|
+
if (complete !== undefined && Buffer.byteLength(complete) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES) return complete;
|
|
99
104
|
}
|
|
105
|
+
if (value === undefined) return required();
|
|
100
106
|
const reason = wellFormed ? "response_too_large" : "malformed_selected_text";
|
|
101
|
-
return
|
|
107
|
+
return required({ value: reason === "response_too_large" ? "[omitted]" : "[unavailable]",
|
|
108
|
+
value_omission: { reason, shown: 0, omitted: 1, total: 1 } });
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
function runIdFromTarget(target: string | undefined): string | undefined {
|
package/package.json
CHANGED
package/skills/actors/SKILL.md
CHANGED
|
@@ -78,11 +78,11 @@ Four locally authored Control guard errors use caller target/action/result recor
|
|
|
78
78
|
|
|
79
79
|
Local spawn guards identify unsupported state_dir or invalid as with field/result and the selected caller value, not an admitted Run. Whole values fit16KiB or show field omission; malformed text is unavailable, not repaired. The host may reject unsupported state_dir by its own schema before the facade, so not every SDK validation error has AX text. This adds no launch, no-effects receipt, retry permission, generated identity or change to Recipe/session/post-launch failures.
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Fresh source normal register_tool receipts use bare block-YAML tool/result/source/callable_now fields and native argument/warning sequences, including empty argument lists. Normal callability remains Boolean and a non-template source stays one exact safely quoted scalar; complete warnings take priority under16KiB, and whole-field/list omissions count only selected metadata, not hidden chars. Call advice remains opaque instruction, not automatic invocation; an unverified registration keeps its existing read-only tool-status next. Persistence is not callability: rely on the existing activation evidence, never substitute spawn or infer fresh host readiness from this layout. Transaction/errors/list/delete/draft and saved messages are not restyled by this slice.
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
Fresh source registry listings and deletion receipts use bare block YAML. Listings preserve the same selected sorted names as a native sequence with coverage; empty selection is `[]`, an overlarge whole group is explicitly omitted rather than mislabeled empty, and malformed text is unavailable without repair. Counts describe selected names, not active/callable or the complete loaded-host registry. Deletion returns tool/result and preserves the manual reload note as opaque instruction: neither a current-map miss nor returned deletion proves full host removal or authorizes automatic reload/retry. Existing partial-effect exceptions and machine details remain unchanged. Combined compiled/package acceptance and loaded-host observation remain separate; an older runtime can still return the prior flat-list text.
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
Fresh source draft-promotion success uses bare block YAML only for its original selected name/new-update/source=draft/callable display and one prior hint. The selected callable display remains a string, including quoted "false", "true" or "0", not reconstructed Boolean readiness. It adds no default/body/argument/path or activation facts and does not become an ordinary registration transaction. A zero, opaque legacy display or unavailable field is not permission to infer readiness or repair callability. Whole scalar/action omissions stay explicit; invocation advice is content and only the existing read-only status hint may be typed. Promotion callbacks/display can still fail after writes with original errors; no automatic rollback/retry/reload follows.
|
|
86
86
|
|
|
87
87
|
Local register_tool source-mode refusals now identify field/result and actual replacement/reference/source-mode/inherited-field choices. Existing correction advice is instruction content, not automatic retry/update or a no-effects receipt. Supplied values are not echoed. The host owns clone/coercion/schema behavior before the facade, so use the actual outcome rather than assuming every wrong-typed request reaches a local AX guard. Other registry errors and all existing activation/transaction contracts remain unchanged.
|
|
88
88
|
|
|
@@ -6,9 +6,12 @@ Preserve the intended logical identity and diagnose through public pi-actors sur
|
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
8
|
inspect target=recipes view=status
|
|
9
|
+
inspect target=recipes view=summary
|
|
9
10
|
inspect target=recipes view=doctor identity=<skill>/<recipe>
|
|
10
11
|
```
|
|
11
12
|
|
|
13
|
+
`summary` and `registry` name the same inspection path: identical facts, details and rendering for identical `verbose` input, so only `verbose` selects compact text versus structured output. The compact label still reports `recipes: registry`, and that label is not a separate view to request. Unsupported-view refusals list the actual accepted view names.
|
|
14
|
+
|
|
12
15
|
Compact Recipe/Run/tool replies use flat AX fields without changing their existing evidence or authorization. Required/optional tool arguments are sequences, with none distinct from unknown. Focused source_location remains one exact literal value. Run compact replies still identify only run/view: request verbose for structured evidence, and tool schema remains structured even without verbose. Compact text selects at most one existing hint; only exact read-only recipes doctor/imports/summary inspection literals become typed next nodes. Other advice stays instruction content with its original Markdown, never executable metadata or authority to rerun work/register a tool. The full original next_actions array remains in details; do not infer callability from an advisory string.
|
|
13
16
|
|
|
14
17
|
Use the focused doctor form for one intended identity. It reports active-Skill ownership, exact resolvability, partial-catalog state, component status, portable source location, resolution generation, any rejection, and bounded next actions. Use the unfiltered doctor only for catalog-wide diagnosis. A partial catalog does not imply every exact component is unavailable. If the owning Skill is inactive, report that blocker rather than locating and running its helper manually.
|