@llblab/pi-actors 0.56.1 → 0.57.1
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 +7 -7
- package/BACKLOG.md +4 -14
- package/CHANGELOG.md +13 -0
- package/README.md +7 -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-delivery.d.ts +1 -1
- package/dist/lib/run-delivery.js +146 -32
- 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 +1 -1
- package/dist/skills/actors/references/diagnostics.md +3 -0
- package/dist/skills/actors/references/runs.md +1 -1
- package/docs/README.md +1 -0
- package/docs/actor-inspector.md +2 -2
- package/docs/agent-experience.md +51 -4
- package/docs/coordinator-delivery.md +2 -2
- package/docs/inactive-foundations-inventory.md +73 -0
- package/docs/inspection.md +9 -8
- package/docs/output-projection-map.md +19 -17
- 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-delivery.ts +130 -31
- 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 +1 -1
- package/skills/actors/references/diagnostics.md +3 -0
- package/skills/actors/references/runs.md +1 -1
|
@@ -25,7 +25,6 @@ import * as ToolsAccess from "./tools-access.js";
|
|
|
25
25
|
import * as ToolsResponse from "./tools-response.js";
|
|
26
26
|
import * as TraceProjection from "./trace-projection.js";
|
|
27
27
|
const asRecord = ToolsResponse.asRecord;
|
|
28
|
-
const maybeJsonText = ToolsResponse.maybeJsonText;
|
|
29
28
|
function runtimeStatus() {
|
|
30
29
|
return {
|
|
31
30
|
automatic_review: Paths.isAutomaticRecipeReviewEnabled(),
|
|
@@ -154,44 +153,80 @@ function runtimeTriage(ctx, deps) {
|
|
|
154
153
|
],
|
|
155
154
|
};
|
|
156
155
|
}
|
|
156
|
+
/** Compact metadata-only documents share the same emitter/omission policy as other receipts. */
|
|
157
|
+
function compactDocument(value) {
|
|
158
|
+
return ToolsResponse.compactYamlDocument(value);
|
|
159
|
+
}
|
|
160
|
+
/** Generated definition metadata is YAML like other owned output; the machine JSON stays in details. */
|
|
161
|
+
function schemaDocument(details) {
|
|
162
|
+
// Absent definition fields stay absent exactly as JSON.stringify omitted them; values are native nodes.
|
|
163
|
+
const selected = {};
|
|
164
|
+
for (const key of ["name", "description", "parameters", "promptSnippet"])
|
|
165
|
+
if (details[key] !== undefined)
|
|
166
|
+
selected[key] = details[key];
|
|
167
|
+
// The shared emitter's own execution ceiling is the only wider allowance; a pathological schema
|
|
168
|
+
// keeps the exact legacy JSON text rather than being clipped or partly reformatted.
|
|
169
|
+
const rendered = ToolsResponse.renderYamlMetadata(selected, 49152);
|
|
170
|
+
return rendered.state === "available"
|
|
171
|
+
? `\n${rendered.text}` : `\n${JSON.stringify(details, null, 2)}`;
|
|
172
|
+
}
|
|
173
|
+
function compactSelected(key, value) {
|
|
174
|
+
return ToolsResponse.selectYamlField(key, value, Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
175
|
+
}
|
|
176
|
+
function compactArguments(key, value) {
|
|
177
|
+
const selected = Array.isArray(value) && value.length
|
|
178
|
+
? value.map(item => String(item)) : Array.isArray(value) ? "none" : "unknown";
|
|
179
|
+
return ToolsResponse.selectYamlField(key, selected, Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
180
|
+
}
|
|
157
181
|
function compactRuntime(view, details) {
|
|
158
|
-
const
|
|
182
|
+
const count = (value) => (Array.isArray(value) ? value.length : 0);
|
|
183
|
+
return compactDocument(view === "runs" ? { runtime: view, runs: count(details.runs) }
|
|
159
184
|
: view === "triage" ? {
|
|
160
|
-
failed: details.failed_runs
|
|
185
|
+
runtime: view, failed: count(details.failed_runs),
|
|
161
186
|
pending_controls: Number(details.pending_control_count ?? 0), stale_controls: Number(details.stale_control_count ?? 0),
|
|
162
187
|
backpressured: Number(details.backpressured_run_count ?? 0), incomplete_trace: Number(details.trace_diagnostic_count ?? 0),
|
|
163
|
-
attention: details.attention_events
|
|
164
|
-
} : { version: String(details.version), state_schema: String(details.state_schema),
|
|
165
|
-
|
|
166
|
-
...Object.entries(fields).map(([key, value]) => ToolsResponse.formatMarkdownField(key, String(value)))].join("\n");
|
|
188
|
+
attention: count(details.attention_events),
|
|
189
|
+
} : { runtime: view, version: String(details.version), state_schema: String(details.state_schema),
|
|
190
|
+
automatic_review: String(details.automatic_review) });
|
|
167
191
|
}
|
|
168
192
|
// Projects local refusals only; does not intercept other throws or change existing catches.
|
|
169
193
|
function formatInspectFailure(fields, next) {
|
|
170
|
-
// The unchanged public adapter
|
|
171
|
-
const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
const full =
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
fields.forEach((item, index) => {
|
|
187
|
-
if (!item[2])
|
|
188
|
-
return;
|
|
189
|
-
const fallback = projected[index];
|
|
190
|
-
projected[index] = formatted[index];
|
|
191
|
-
if (Buffer.byteLength(text(), "utf8") > budget)
|
|
192
|
-
projected[index] = fallback;
|
|
194
|
+
// The unchanged public adapter replaces this document's trailing LF with its one leading LF.
|
|
195
|
+
const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES;
|
|
196
|
+
// Field-local coverage record: an unfit selected scalar/sequence is omitted whole, never clipped.
|
|
197
|
+
const omitted = { state: "omitted", reason: "response_too_large", shown: 0, omitted: 1, total: 1 };
|
|
198
|
+
const render = (value) => {
|
|
199
|
+
const rendered = ToolsResponse.renderYamlMetadata(value, budget);
|
|
200
|
+
return rendered.state === "available" ? rendered.text : undefined;
|
|
201
|
+
};
|
|
202
|
+
const parts = fields.map(([key, value, omittable]) => {
|
|
203
|
+
const full = typeof value === "string" ? { [key]: value } : { [key]: [...value] };
|
|
204
|
+
const fallback = { [key]: omitted };
|
|
205
|
+
// Unmarked fields are bounded compiler literals/lists. Short values seed full so admission only grows.
|
|
206
|
+
const complete = render(full);
|
|
207
|
+
const selected = omittable && (complete === undefined || Buffer.byteLength(complete) > Buffer.byteLength(render(fallback) ?? ""))
|
|
208
|
+
? fallback : full;
|
|
209
|
+
return { omittable, full, selected };
|
|
193
210
|
});
|
|
194
|
-
|
|
211
|
+
// Only producer-known read-only inspect literals become a typed next, never a parsed action.
|
|
212
|
+
const typedNext = next ? { next: { tool: "inspect", target: next[0], view: next[1] } } : undefined;
|
|
213
|
+
const nextFallback = { next_action: omitted };
|
|
214
|
+
const document = () => ({ ...Object.assign({}, ...parts.map((part) => part.selected)),
|
|
215
|
+
...(typedNext ?? nextFallback) });
|
|
216
|
+
let selectedNext = typedNext;
|
|
217
|
+
if (next && render(document()) === undefined)
|
|
218
|
+
selectedNext = undefined;
|
|
219
|
+
const nextValue = () => (next ? (selectedNext ?? nextFallback) : {});
|
|
220
|
+
for (const part of parts) {
|
|
221
|
+
if (!part.omittable)
|
|
222
|
+
continue;
|
|
223
|
+
const previous = part.selected;
|
|
224
|
+
part.selected = part.full;
|
|
225
|
+
if (render({ ...Object.assign({}, ...parts.map((item) => item.selected)), ...nextValue() }) === undefined)
|
|
226
|
+
part.selected = previous;
|
|
227
|
+
}
|
|
228
|
+
const text = render({ ...Object.assign({}, ...parts.map((item) => item.selected)), ...nextValue() });
|
|
229
|
+
return (text ?? "").replace(/\n$/u, "");
|
|
195
230
|
}
|
|
196
231
|
function inspectRuntime(view, input, ctx, deps) {
|
|
197
232
|
if (view === "status")
|
|
@@ -312,7 +347,9 @@ function inspectRecipeIdentity(identity, resolutionContext, namespaces, inventor
|
|
|
312
347
|
: ["inspect target=recipes view=imports verbose=true"],
|
|
313
348
|
};
|
|
314
349
|
}
|
|
315
|
-
function inspectRecipes(
|
|
350
|
+
function inspectRecipes(requestedView, input, deps, context) {
|
|
351
|
+
// `registry` and `summary` name one inspection path; only `verbose` selects compact versus full output.
|
|
352
|
+
const view = requestedView === "registry" ? "summary" : requestedView;
|
|
316
353
|
if (view !== "status" &&
|
|
317
354
|
view !== "summary" &&
|
|
318
355
|
view !== "doctor" &&
|
|
@@ -320,7 +357,7 @@ function inspectRecipes(view, input, deps, context) {
|
|
|
320
357
|
view !== "reviews") {
|
|
321
358
|
throw new Error(formatInspectFailure([
|
|
322
359
|
["target_kind", "recipes"], ["result", "unsupported view"],
|
|
323
|
-
["supported_views", ["status", "summary", "doctor", "imports", "reviews"]],
|
|
360
|
+
["supported_views", ["status", "summary", "registry", "doctor", "imports", "reviews"]],
|
|
324
361
|
]));
|
|
325
362
|
}
|
|
326
363
|
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
@@ -515,40 +552,49 @@ function compactResult(target, view, details) {
|
|
|
515
552
|
if (target === "runtime")
|
|
516
553
|
return compactRuntime(view, details);
|
|
517
554
|
if (target === "recipes" && typeof details.identity === "string") {
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
555
|
+
const identity = compactSelected("identity", String(details.identity));
|
|
556
|
+
const location = typeof details.source_location === "string"
|
|
557
|
+
? compactSelected("source_location", details.source_location) : undefined;
|
|
558
|
+
const generation = compactSelected("resolution_generation", String(details.resolution_generation));
|
|
559
|
+
const rejected = typeof details.rejected_reason === "string"
|
|
560
|
+
? compactSelected("rejected_reason", details.rejected_reason) : undefined;
|
|
524
561
|
const actions = details.next_actions;
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
562
|
+
const hint = actions?.length
|
|
563
|
+
? ToolsResponse.nextActionMetadata(String(actions[0]), Limits.RUN_DELIVERY_MODEL_MAX_BYTES) : undefined;
|
|
564
|
+
const parts = [identity, ...(location ? [location] : []), generation, ...(rejected ? [rejected] : []), ...(hint ? [hint] : [])];
|
|
565
|
+
const render = () => ({ recipes: "doctor", ...identity.selected,
|
|
566
|
+
skill_active: String(details.skill_active), resolvable: String(details.resolvable),
|
|
567
|
+
catalog_partial: String(details.catalog_partial), component_status: String(details.component_status),
|
|
568
|
+
...(location ? location.selected : {}), ...generation.selected, ...(rejected ? rejected.selected : {}),
|
|
569
|
+
...(hint ? hint.selected : {}) });
|
|
570
|
+
return `\n${ToolsResponse.growYamlDocument(render, parts, Limits.RUN_DELIVERY_MODEL_MAX_BYTES).replace(/\n$/u, "")}`;
|
|
528
571
|
}
|
|
529
572
|
if (target === "recipes")
|
|
530
573
|
return ToolsResponse.compactRecipeRegistry(details);
|
|
531
574
|
if (target.startsWith("tool:") && view === "status") {
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
575
|
+
const name = target.slice(5);
|
|
576
|
+
const tool = compactSelected("tool", name);
|
|
577
|
+
const source = compactSelected("source", String(details.source ?? "unknown"));
|
|
578
|
+
const boundary = compactSelected("activation_boundary", String(details.activation_boundary ?? "unknown"));
|
|
535
579
|
const next = Array.isArray(details.next_actions) && details.next_actions.length > 0
|
|
536
580
|
? String(details.next_actions[0])
|
|
537
581
|
: details.callable_now === true
|
|
538
|
-
? `call tool ${
|
|
539
|
-
: `register_tool name=${
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
582
|
+
? `call tool ${name}`
|
|
583
|
+
: `register_tool name=${name} update=true`;
|
|
584
|
+
const hint = ToolsResponse.nextActionMetadata(next, Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
585
|
+
const required = compactArguments("required_args", details.required_args);
|
|
586
|
+
const optional = compactArguments("optional_args", details.optional_args);
|
|
587
|
+
const render = () => ({ ...tool.selected, ...source.selected,
|
|
588
|
+
callable_now: details.callable_now === true, ...boundary.selected,
|
|
589
|
+
...required.selected, ...optional.selected,
|
|
590
|
+
launch_kind: String(details.launch_kind ?? "none"),
|
|
591
|
+
spawn_calls: Number(details.spawn_calls ?? 0), tool_calls: Number(details.tool_calls ?? 0),
|
|
592
|
+
...hint.selected });
|
|
593
|
+
return `\n${ToolsResponse.growYamlDocument(render, [tool, source, boundary, required, optional, hint], Limits.RUN_DELIVERY_MODEL_MAX_BYTES).replace(/\n$/u, "")}`;
|
|
548
594
|
}
|
|
549
595
|
if (target.startsWith("tool:"))
|
|
550
|
-
return
|
|
551
|
-
return
|
|
596
|
+
return compactDocument({ tool: target.slice(5), view });
|
|
597
|
+
return compactDocument({ run: target.slice(4), view });
|
|
552
598
|
}
|
|
553
599
|
export function createInspectToolDefinition(deps = {}) {
|
|
554
600
|
return {
|
|
@@ -562,7 +608,7 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
562
608
|
status: Schema.stringSchema("Optional runtime Run status filter. With verbose runs, failed prioritizes observed failed subtrees without filtering the navigation map."),
|
|
563
609
|
target: Schema.stringSchema("Target: run:<id>, runtime, recipes, or tool:<name>."),
|
|
564
610
|
verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
|
|
565
|
-
view: Schema.stringSchema("Run view recipe|trace|control, or target-specific management view."),
|
|
611
|
+
view: Schema.stringSchema("Run view recipe|trace|control, or one target-specific management view. runtime status|runs|triage; recipes status|summary|doctor|imports|reviews, where registry aliases summary; tool status|schema."),
|
|
566
612
|
}, ["target", "view"]),
|
|
567
613
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
568
614
|
const input = asRecord(params);
|
|
@@ -592,7 +638,11 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
592
638
|
return {
|
|
593
639
|
content: [{
|
|
594
640
|
type: "text",
|
|
595
|
-
text:
|
|
641
|
+
text: input.verbose === true
|
|
642
|
+
? ToolsResponse.jsonText(details)
|
|
643
|
+
: target.startsWith("tool:") && view === "schema"
|
|
644
|
+
? schemaDocument(details)
|
|
645
|
+
: compactResult(target, view, details),
|
|
596
646
|
}],
|
|
597
647
|
details,
|
|
598
648
|
};
|
package/dist/lib/tools-local.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Owns wrapping saved local capabilities as executable pi tools
|
|
5
5
|
*/
|
|
6
6
|
import * as AsyncRuns from "./async-runs.js";
|
|
7
|
+
import * as Limits from "./limits.js";
|
|
7
8
|
import * as ModelContext from "./model-context.js";
|
|
8
9
|
import * as Execution from "./execution.js";
|
|
9
10
|
import * as Prompts from "./prompts.js";
|
|
@@ -44,6 +45,16 @@ function shouldAddRuntimeToolUsageHint(error) {
|
|
|
44
45
|
const message = error instanceof Error ? error.message : String(error);
|
|
45
46
|
return (/^Argument \S+ must /.test(message) || /^Missing .* value: /.test(message));
|
|
46
47
|
}
|
|
48
|
+
/** Owned usage facts only; the JSON invocation example stays unparsed instructional body text. */
|
|
49
|
+
function runtimeToolUsageMetadata(cfg, required, includeRunId) {
|
|
50
|
+
const optional = cfg.args.filter((arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg) &&
|
|
51
|
+
!required.includes(arg));
|
|
52
|
+
const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES;
|
|
53
|
+
const tool = ToolsResponse.selectYamlField("tool", cfg.name, budget);
|
|
54
|
+
const requiredPart = ToolsResponse.selectYamlField("required", required, budget);
|
|
55
|
+
const optionalPart = ToolsResponse.selectYamlField("optional", [...optional, ...(includeRunId ? ["run_id"] : [])], budget);
|
|
56
|
+
return ToolsResponse.growYamlDocument(() => ({ ...tool.selected, ...requiredPart.selected, ...optionalPart.selected }), [tool, requiredPart, optionalPart], budget);
|
|
57
|
+
}
|
|
47
58
|
function formatRuntimeToolUsageHint(cfg, required, includeRunId) {
|
|
48
59
|
const optional = cfg.args.filter((arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg) &&
|
|
49
60
|
!required.includes(arg));
|
|
@@ -59,15 +70,10 @@ function formatRuntimeToolUsageHint(cfg, required, includeRunId) {
|
|
|
59
70
|
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], exampleDefaults);
|
|
60
71
|
if (includeRunId)
|
|
61
72
|
example.run_id = `${cfg.name}-1`;
|
|
62
|
-
|
|
73
|
+
return [
|
|
63
74
|
`Expected call shape for ${cfg.name}:`,
|
|
64
75
|
`${cfg.name}(${JSON.stringify(example, null, 2)})`,
|
|
65
|
-
];
|
|
66
|
-
if (required.length)
|
|
67
|
-
lines.push(`Required: ${required.join(", ")}`);
|
|
68
|
-
if (optional.length || includeRunId)
|
|
69
|
-
lines.push(`Optional: ${[...optional, ...(includeRunId ? ["run_id"] : [])].join(", ")}`);
|
|
70
|
-
return lines.join("\n");
|
|
76
|
+
].join("\n");
|
|
71
77
|
}
|
|
72
78
|
function resolveRecipeToolValues(cfg, callerValues, ctx) {
|
|
73
79
|
const recipeRuntimeValues = {
|
|
@@ -88,7 +94,9 @@ function formatRuntimeToolArgumentError(cfg, error, required, includeRunId) {
|
|
|
88
94
|
const message = error instanceof Error ? error.message : String(error);
|
|
89
95
|
if (!shouldAddRuntimeToolUsageHint(error))
|
|
90
96
|
return error instanceof Error ? error : new Error(message);
|
|
91
|
-
|
|
97
|
+
// Whole usage groups fit the header budget or carry explicit omission; the JSON example stays opaque.
|
|
98
|
+
const header = runtimeToolUsageMetadata(cfg, required, includeRunId);
|
|
99
|
+
return new Error(ToolsResponse.withLeadingLineBreak(ToolsResponse.frameMetadataDocument(header, `Invalid arguments for tool "${cfg.name}": ${message}\n\n${formatRuntimeToolUsageHint(cfg, required, includeRunId)}`)));
|
|
92
100
|
}
|
|
93
101
|
export function createRuntimeToolDefinition(cfg, exec) {
|
|
94
102
|
const paramSchema = {};
|
|
@@ -14,82 +14,96 @@ function asRecord(value) {
|
|
|
14
14
|
? value
|
|
15
15
|
: {};
|
|
16
16
|
}
|
|
17
|
-
const
|
|
17
|
+
const CONTROL_BUDGET = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
|
|
18
|
+
function controlMetadata(value) {
|
|
19
|
+
return ToolsResponse.renderYamlMetadata(value, CONTROL_BUDGET).text;
|
|
20
|
+
}
|
|
21
|
+
/** One opaque selected scalar as a whole value or an explicit whole-field omission, never a clip. */
|
|
22
|
+
function controlSelected(key, value) {
|
|
23
|
+
return ToolsResponse.selectYamlField(key, value, CONTROL_BUDGET);
|
|
24
|
+
}
|
|
25
|
+
/** Compiler-selected option strings stay a native sequence; malformed selections stay unavailable. */
|
|
26
|
+
function controlChoices(options) {
|
|
27
|
+
const key = options.key, values = options.values;
|
|
28
|
+
// Values were selected by the original string filter, not a new action grammar.
|
|
29
|
+
const wellFormed = values.every((value) => typeof value === "string" && value.isWellFormed());
|
|
30
|
+
const omitted = { [key]: wellFormed ? "[omitted]" : "[unavailable]",
|
|
31
|
+
[key + "_omission"]: { reason: wellFormed ? "response_too_large" : "malformed_selected_text",
|
|
32
|
+
shown: 0, omitted: values.length, total: values.length } };
|
|
33
|
+
const full = !wellFormed ? omitted : values.length ? { [key]: [...values] } : { [key]: "none" };
|
|
34
|
+
const complete = ToolsResponse.renderYamlMetadata(full, CONTROL_BUDGET);
|
|
35
|
+
return { full, selected: complete.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(controlMetadata(omitted)) ? full : omitted };
|
|
36
|
+
}
|
|
37
|
+
/** Grow whole selected fields in priority order while the complete document still fits. */
|
|
38
|
+
function controlDocument(render, order) {
|
|
39
|
+
return ToolsResponse.growYamlDocument(render, order, CONTROL_BUDGET);
|
|
40
|
+
}
|
|
41
|
+
/** Only producer-known read-only inspect literals become typed next; other advice stays opaque content. */
|
|
42
|
+
function controlNextPart(action) {
|
|
43
|
+
const inspect = /^inspect target=([^\s]+) view=([^\s]+)$/u.exec(action);
|
|
44
|
+
if (inspect) {
|
|
45
|
+
return ToolsResponse.selectYamlNode("next", { tool: "inspect", target: inspect[1], view: inspect[2] }, CONTROL_BUDGET);
|
|
46
|
+
}
|
|
47
|
+
const wellFormed = action.isWellFormed();
|
|
48
|
+
const omitted = { next: { instruction: wellFormed ? "[omitted]" : "[unavailable]",
|
|
49
|
+
instruction_omission: { reason: wellFormed ? "response_too_large" : "malformed_selected_text", shown: 0, omitted: 1, total: 1 } } };
|
|
50
|
+
const full = wellFormed ? { next: { instruction: action } } : omitted;
|
|
51
|
+
const complete = wellFormed ? ToolsResponse.renderYamlMetadata(full, CONTROL_BUDGET) : undefined;
|
|
52
|
+
const selected = complete?.state === "available" && Buffer.byteLength(complete.text) <= Buffer.byteLength(controlMetadata(omitted))
|
|
53
|
+
? full : omitted;
|
|
54
|
+
return { full, selected };
|
|
55
|
+
}
|
|
18
56
|
/** Only the four compiler-owned guard sites below; never wraps dispatch/dependency errors. */
|
|
19
57
|
function controlGuardText(request, result, options) {
|
|
20
58
|
const key = request.target === "runtime" ? "target" : "run";
|
|
21
|
-
const identity =
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
let malformed = false;
|
|
28
|
-
// Values were selected by the original string filter, not by a new action grammar.
|
|
29
|
-
for (let index = 0; index < options.values.length; index++) {
|
|
30
|
-
const value = options.values[index];
|
|
31
|
-
if (!value.isWellFormed()) {
|
|
32
|
-
malformed = true;
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
rows.push(" - " + value.replaceAll(/\r\n|\r|\n/gu, newline => newline + " "));
|
|
36
|
-
}
|
|
37
|
-
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}`;
|
|
38
|
-
fullOptions = malformed ? omittedOptions : rows.length ? `- ${options.key}:\n${rows.join("\n")}` : controlField(options.key, "none");
|
|
39
|
-
}
|
|
40
|
-
// Start with the shorter complete-or-omitted rendering: adding an omission scaffold must
|
|
41
|
-
// never manufacture overflow when the complete small field would fit. Then grow only.
|
|
42
|
-
let selectedIdentity = Buffer.byteLength(identity) <= Buffer.byteLength(omittedIdentity) ? identity : omittedIdentity;
|
|
43
|
-
let selectedOptions = Buffer.byteLength(fullOptions) <= Buffer.byteLength(omittedOptions) ? fullOptions : omittedOptions;
|
|
44
|
-
const render = (target, choices) => ToolsResponse.withLeadingLineBreak([target, ...fixed, ...(options ? [choices] : [])].join("\n"));
|
|
45
|
-
if (Buffer.byteLength(render(identity, selectedOptions)) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES)
|
|
46
|
-
selectedIdentity = identity;
|
|
47
|
-
if (options && Buffer.byteLength(render(selectedIdentity, fullOptions)) <= Limits.RUN_DELIVERY_MODEL_MAX_BYTES)
|
|
48
|
-
selectedOptions = fullOptions;
|
|
49
|
-
return render(selectedIdentity, selectedOptions);
|
|
50
|
-
}
|
|
51
|
-
function controlNext(action) {
|
|
52
|
-
const inspect = /^inspect target=([^\s]+) view=([^\s]+)$/u.exec(action);
|
|
53
|
-
return inspect ? ToolsResponse.formatInspectNextAction(inspect[1], inspect[2])
|
|
54
|
-
: "- next:\n" + controlField("instruction", action, 1);
|
|
59
|
+
const identity = controlSelected(key, request.target === "runtime" ? request.target : request.target.slice(4));
|
|
60
|
+
const choices = options ? controlChoices(options) : undefined;
|
|
61
|
+
const render = () => ({ ...identity.selected, action: request.action, result,
|
|
62
|
+
...(choices ? choices.selected : {}) });
|
|
63
|
+
// Whole identity first, then the complete option sequence: the shorter seed must never manufacture overflow.
|
|
64
|
+
return ToolsResponse.withLeadingLineBreak(controlDocument(render, choices ? [identity, choices] : [identity]));
|
|
55
65
|
}
|
|
56
66
|
/** Pure first-layer copy; legacy stopped/killed flags are not scope-closure proof. */
|
|
57
67
|
export function compactControlResult(request, result) {
|
|
58
|
-
const
|
|
59
|
-
|
|
68
|
+
const key = request.target.startsWith("run:") ? "run" : "target";
|
|
69
|
+
const identity = controlSelected(key, key === "run" ? request.target.slice(4) : request.target);
|
|
70
|
+
const facts = {};
|
|
60
71
|
if (result.stopped === false || result.archived === false || result.pruned === false) {
|
|
61
|
-
|
|
62
|
-
if (typeof result.reason === "string")
|
|
63
|
-
fields.push(controlField("reason", result.reason));
|
|
64
|
-
const observed = asRecord(result.status);
|
|
65
|
-
if (result.reason === "not running" && typeof observed.status === "string")
|
|
66
|
-
fields.push(controlField("state", observed.status));
|
|
72
|
+
facts.result = "not applied";
|
|
67
73
|
}
|
|
68
74
|
else if (request.action === "kill" && result.stopped === true) {
|
|
69
|
-
|
|
75
|
+
facts.result = "kill attempted";
|
|
76
|
+
facts.termination = "unconfirmed";
|
|
70
77
|
}
|
|
71
78
|
else if (result.archived === true) {
|
|
72
|
-
|
|
79
|
+
facts.result = "archived";
|
|
73
80
|
}
|
|
74
81
|
else if (result.pruned === true) {
|
|
75
|
-
|
|
82
|
+
facts.result = "pruned";
|
|
76
83
|
}
|
|
77
84
|
else if (result.delivery === "queued" || result.delivery === "delivered") {
|
|
78
|
-
|
|
85
|
+
facts.delivery = result.delivery;
|
|
86
|
+
facts.handling = "unconfirmed";
|
|
79
87
|
}
|
|
80
88
|
else {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
fields.push(controlField("reason", result.reason));
|
|
89
|
+
facts[typeof result.delivery === "string" ? "delivery" : "result"] =
|
|
90
|
+
typeof result.delivery === "string" ? result.delivery : typeof result.status === "string" ? result.status : "accepted";
|
|
84
91
|
}
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
const notApplied = result.stopped === false || result.archived === false || result.pruned === false;
|
|
93
|
+
const reason = typeof result.reason === "string" ? controlSelected("reason", result.reason) : undefined;
|
|
94
|
+
const observed = asRecord(result.status);
|
|
95
|
+
const state = notApplied && result.reason === "not running" && typeof observed.status === "string"
|
|
96
|
+
? controlSelected("state", observed.status) : undefined;
|
|
97
|
+
const controlId = typeof result.control_id === "string" ? controlSelected("control_id", result.control_id) : undefined;
|
|
87
98
|
const actions = Array.isArray(result.next_actions) ? result.next_actions
|
|
88
99
|
: request.target.startsWith("run:") ? runNextActions(request.target.slice(4), result) : [];
|
|
89
100
|
const next = actions.find((action) => typeof action === "string" && action.length > 0);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
const nextPart = next === undefined ? undefined : controlNextPart(next);
|
|
102
|
+
const parts = [identity, ...(reason ? [reason] : []), ...(state ? [state] : []), ...(controlId ? [controlId] : []), ...(nextPart ? [nextPart] : [])];
|
|
103
|
+
const render = () => ({ ...identity.selected, action: request.action, ...facts,
|
|
104
|
+
...(reason ? reason.selected : {}), ...(state ? state.selected : {}), ...(controlId ? controlId.selected : {}),
|
|
105
|
+
...(nextPart ? nextPart.selected : {}) });
|
|
106
|
+
return ToolsResponse.withLeadingLineBreak(controlDocument(render, parts));
|
|
93
107
|
}
|
|
94
108
|
function runNextActions(run, result) {
|
|
95
109
|
if (result.stopped === true)
|
|
@@ -198,16 +212,13 @@ export function createControlToolDefinition(deps = {}) {
|
|
|
198
212
|
}
|
|
199
213
|
catch (cause) {
|
|
200
214
|
// Admission-era correlation is not current reply-scope proof after an awaited dispatch.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
? identity
|
|
209
|
-
: "- run: [omitted]\n- run_omission:\n - reason: response_too_large\n - shown: 0\n - omitted: 1\n - total: 1";
|
|
210
|
-
throw new Error(ToolsResponse.withLeadingLineBreak(target + "\n" + summary), { cause });
|
|
215
|
+
// Display one whole caller-owned id or an explicit whole-id omission; the warning stays literal.
|
|
216
|
+
const identity = controlSelected("run", run);
|
|
217
|
+
const next = controlNextPart("inspect target=runtime view=runs");
|
|
218
|
+
const render = () => ({ ...identity.selected, action: request.action,
|
|
219
|
+
result: "request failed", delivery: "unconfirmed", handling: "unconfirmed",
|
|
220
|
+
warning: "Effects may already exist.\nDo not repeat automatically.", ...next.selected });
|
|
221
|
+
throw new Error(ToolsResponse.withLeadingLineBreak(controlDocument(render, [identity])), { cause });
|
|
211
222
|
}
|
|
212
223
|
}
|
|
213
224
|
// A refused identity change must not publish the replacement's status, even in verbose/details.
|
|
@@ -13,6 +13,20 @@ export type YamlMetadataRendering = {
|
|
|
13
13
|
};
|
|
14
14
|
/** Pure selected-metadata formatting, not admission or authority. */
|
|
15
15
|
export declare function renderYamlMetadata(value: unknown, byteBudget: number): YamlMetadataRendering;
|
|
16
|
+
export type YamlPart = {
|
|
17
|
+
full: Record<string, unknown>;
|
|
18
|
+
selected: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
/** One selected metadata node, kept whole or covered by a field/group omission. */
|
|
21
|
+
export declare function selectYamlNode(key: string, value: unknown, byteBudget: number, total?: number): YamlPart;
|
|
22
|
+
/** Opaque selected text or string sequence; malformed selections stay unavailable without repair. */
|
|
23
|
+
export declare function selectYamlField(key: string, value: string | readonly string[], byteBudget: number): YamlPart;
|
|
24
|
+
/** Grow whole selected fields in priority order while the complete document still fits. */
|
|
25
|
+
export declare function growYamlDocument(render: () => Record<string, unknown>, order: readonly YamlPart[], byteBudget: number): string;
|
|
26
|
+
/** Uncapped UI-only flat metadata; never borrow a model-output allowance for notices. */
|
|
27
|
+
export declare function formatYamlNotice(value: Record<string, string | number | boolean>): string;
|
|
28
|
+
/** Close one rendered YAML header; a separate opaque body gets exactly one delimiter pair. */
|
|
29
|
+
export declare function frameMetadataDocument(header: string, body?: string): string;
|
|
16
30
|
export declare function formatMarkdownField(key: string, value: string, depth?: number): string;
|
|
17
31
|
export declare function formatInspectNextAction(target: string, view: string, verbose?: boolean): string;
|
|
18
32
|
export declare function formatLiteralBlock(value: string): string;
|
|
@@ -42,6 +56,10 @@ export declare function spawnMetadataText(value: unknown, verbose: boolean | und
|
|
|
42
56
|
export declare function compactRecipeImports(summary: Record<string, unknown>): string;
|
|
43
57
|
export declare function compactRecipeDoctor(summary: Record<string, unknown>): string;
|
|
44
58
|
export declare function recipeRegistryNextActions(summary: Record<string, unknown>, view: string): string[];
|
|
59
|
+
/** Compact metadata-only document text: one leading LF, no trailing LF, shared emitter budget. */
|
|
60
|
+
export declare function compactYamlDocument(value: Record<string, unknown>): string;
|
|
61
|
+
/** Only the closed read-only Recipe inspect literals become typed next; other advice stays opaque content. */
|
|
62
|
+
export declare function nextActionMetadata(action: string, byteBudget: number): YamlPart;
|
|
45
63
|
/** Only these existing read-only inspection hints become action nodes; other advice stays content. */
|
|
46
64
|
export declare function formatInspectionHint(action: string): string;
|
|
47
65
|
export declare function compactRecipeRegistry(summary: Record<string, unknown>): string;
|