@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.
@@ -1,6 +1,13 @@
1
- // Owns: Bounded raw MI wire decoding, query inputs and unadmitted provenance fragments, subordinate to process acquisition.
2
- // Excludes: Identity construction/digest/comparison, status/liveness/lifecycle/publication/admission,
3
- // fallback, native invocation and asset loading. Decoded data never admits a representation profile.
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
+ import { createHash } from 'node:crypto';
7
+ const MANIFEST_MAX_BYTES = 32 * 1024;
8
+ const EXECUTABLE_MAX_BYTES = 8 * 1024 * 1024;
9
+ 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;
10
+ const MODULE_FILE = /\/lib\/runs-process-windows-mi\.(?:ts|js)$/u;
4
11
  export function queryArguments(profile, pids) {
5
12
  if ((profile !== 'scalar' && profile !== 'shared') || !Array.isArray(pids) ||
6
13
  pids.length < 1 || pids.length > 64 || (profile === 'scalar' && pids.length !== 1))
@@ -112,3 +119,139 @@ export function decodeResponse(bytes, profile, pids) {
112
119
  return { kind: 'unusable', reason: 'unsupported_optional_fields' };
113
120
  return { kind: 'decoded_unadmitted', rows };
114
121
  }
122
+ function boundedText(bytes, maximumBytes) {
123
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength > maximumBytes)
124
+ return undefined;
125
+ if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf)
126
+ return undefined;
127
+ try {
128
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
129
+ }
130
+ catch {
131
+ return undefined;
132
+ }
133
+ }
134
+ function boundedLexemes(value, maximum) {
135
+ if (!Array.isArray(value) || value.length > maximum)
136
+ return undefined;
137
+ const selected = [];
138
+ for (const item of value) {
139
+ if (typeof item !== 'string' || item.length === 0 || item.length > 128 || !/^[\x20-\x7e]+$/.test(item) || selected.includes(item))
140
+ return undefined;
141
+ selected.push(item);
142
+ }
143
+ return selected;
144
+ }
145
+ /** Bounded complete-manifest reader: one strict UTF-8 JSON document with an exact reviewed shape. */
146
+ export function readHelperManifest(bytes) {
147
+ const invalid = { kind: 'unusable', reason: 'invalid_manifest' };
148
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength > MANIFEST_MAX_BYTES)
149
+ return { kind: 'unusable', reason: 'oversize_manifest' };
150
+ const text = boundedText(bytes, MANIFEST_MAX_BYTES);
151
+ if (text === undefined)
152
+ return invalid;
153
+ let value;
154
+ try {
155
+ value = JSON.parse(text);
156
+ }
157
+ catch {
158
+ return invalid;
159
+ }
160
+ if (!exactKeys(value, ['helper_version', 'architecture', 'protocol', 'profiles', 'native_source_sha256',
161
+ 'executable_bytes', 'executable_sha256', 'toolchain', 'dependencies', 'originating_source_commit', 'originating_build_job']))
162
+ return invalid;
163
+ const { helper_version, architecture, protocol, profiles, native_source_sha256, executable_bytes, executable_sha256, toolchain, dependencies, originating_source_commit, originating_build_job } = value;
164
+ if (typeof helper_version !== 'string' || !HELPER_VERSION.test(helper_version) ||
165
+ architecture !== 'windows-x64' || protocol !== 'native-mi-v1' ||
166
+ !Array.isArray(profiles) || profiles.length !== 2 || profiles[0] !== 'scalar' || profiles[1] !== 'shared')
167
+ return { kind: 'unusable', reason: 'unsupported_manifest' };
168
+ if (typeof native_source_sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(native_source_sha256) ||
169
+ typeof executable_sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(executable_sha256) ||
170
+ typeof originating_source_commit !== 'string' || !/^[0-9a-f]{40}$/.test(originating_source_commit) ||
171
+ typeof originating_build_job !== 'string' || !/^[1-9][0-9]*$/.test(originating_build_job) ||
172
+ typeof executable_bytes !== 'number' || !Number.isSafeInteger(executable_bytes) || executable_bytes <= 0)
173
+ return invalid;
174
+ if (executable_bytes > EXECUTABLE_MAX_BYTES)
175
+ return { kind: 'unusable', reason: 'unsupported_manifest' };
176
+ if (!exactKeys(toolchain, ['compiler', 'sdk', 'build_arguments']))
177
+ return invalid;
178
+ const compiler = boundedLexemes([toolchain.compiler], 1)?.[0];
179
+ const sdk = boundedLexemes([toolchain.sdk], 1)?.[0];
180
+ const buildArguments = boundedLexemes(toolchain.build_arguments, 32);
181
+ const dependencyNames = boundedLexemes(dependencies, 32);
182
+ if (compiler === undefined || sdk === undefined || buildArguments === undefined || dependencyNames === undefined)
183
+ return invalid;
184
+ // Fresh primitive claims only; the caller still owns target/version/source-digest policy.
185
+ return {
186
+ kind: 'manifest_unadmitted',
187
+ manifest: {
188
+ helper_version, architecture: 'windows-x64', protocol: 'native-mi-v1', profiles: ['scalar', 'shared'],
189
+ native_source_sha256, executable_bytes, executable_sha256,
190
+ toolchain: { compiler, sdk, build_arguments: buildArguments }, dependencies: dependencyNames,
191
+ originating_source_commit, originating_build_job,
192
+ },
193
+ };
194
+ }
195
+ /**
196
+ * Fixed versioned package-relative lookup. Never searches cwd, PATH or caller locations: the only base is
197
+ * the sibling `native/` directory of this module's own `lib/` (source) or `dist/lib/` (compiled) home.
198
+ */
199
+ export function resolveHelperAsset(moduleUrl, runtime, helperVersion) {
200
+ let url;
201
+ try {
202
+ url = new URL(String(moduleUrl));
203
+ }
204
+ catch {
205
+ return { kind: 'unusable', reason: 'unsupported_location' };
206
+ }
207
+ if (url.protocol !== 'file:' || !MODULE_FILE.test(url.pathname))
208
+ return { kind: 'unusable', reason: 'unsupported_location' };
209
+ const target = runtime;
210
+ if (target?.platform !== 'win32' || target?.arch !== 'x64')
211
+ return { kind: 'unusable', reason: 'unsupported_target' };
212
+ if (typeof helperVersion !== 'string' || !HELPER_VERSION.test(helperVersion))
213
+ return { kind: 'unusable', reason: 'unsupported_version' };
214
+ const directory = `../native/windows-mi/win32-x64/${helperVersion}/`;
215
+ return {
216
+ kind: 'located', helper_version: helperVersion,
217
+ manifest_url: new URL(`${directory}manifest.json`, url),
218
+ executable_url: new URL(`${directory}helper.exe`, url),
219
+ };
220
+ }
221
+ /** Trusted exact-byte selection: bounded manifest read, exact declared size, then exact SHA-256 match. */
222
+ export function loadHelperAsset(location, read) {
223
+ const manifestBytes = read(location.manifest_url, MANIFEST_MAX_BYTES + 1);
224
+ if (manifestBytes === undefined)
225
+ return { kind: 'unusable', reason: 'missing_manifest' };
226
+ if (manifestBytes.byteLength > MANIFEST_MAX_BYTES)
227
+ return { kind: 'unusable', reason: 'oversize_manifest' };
228
+ const reading = readHelperManifest(manifestBytes);
229
+ if (reading.kind === 'unusable')
230
+ return { kind: 'unusable', reason: reading.reason };
231
+ const { manifest } = reading;
232
+ if (manifest.helper_version !== location.helper_version)
233
+ return { kind: 'unusable', reason: 'version_mismatch' };
234
+ // One extra byte detects growth; a shorter read can never satisfy the declared size.
235
+ const executableBytes = read(location.executable_url, manifest.executable_bytes + 1);
236
+ if (executableBytes === undefined)
237
+ return { kind: 'unusable', reason: 'missing_asset' };
238
+ if (executableBytes.byteLength > manifest.executable_bytes)
239
+ return { kind: 'unusable', reason: 'oversize_asset' };
240
+ if (executableBytes.byteLength !== manifest.executable_bytes)
241
+ return { kind: 'unusable', reason: 'size_mismatch' };
242
+ const digest = createHash('sha256').update(executableBytes).digest('hex');
243
+ if (digest !== manifest.executable_sha256)
244
+ return { kind: 'unusable', reason: 'digest_mismatch' };
245
+ return { kind: 'asset_verified_unadmitted', manifest, executable_sha256: digest, executable_bytes: executableBytes.byteLength };
246
+ }
247
+ /** Shell-free invocation spec only: no spawn, retry, fallback or execution-policy bypass. */
248
+ export function helperInvocation(executablePath, profile, pids) {
249
+ if (typeof executablePath !== 'string' || executablePath.length === 0 || executablePath.length > 4096 ||
250
+ !/^[A-Za-z]:[\\/][^\x00\r\n"]*\.exe$/.test(executablePath))
251
+ return { kind: 'unusable', reason: 'unsupported_executable' };
252
+ const query = queryArguments(profile, pids);
253
+ if (!query)
254
+ return { kind: 'unusable', reason: 'invalid_query' };
255
+ return { kind: 'invocation_unadmitted', command: executablePath, args: query,
256
+ options: { shell: false, windowsHide: true } };
257
+ }
@@ -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 fields = view === "runs" ? { runs: details.runs?.length ?? 0 }
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?.length ?? 0,
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?.length ?? 0,
164
- } : { version: String(details.version), state_schema: String(details.state_schema), automatic_review: String(details.automatic_review) };
165
- return "\n" + [ToolsResponse.formatMarkdownField("runtime", view),
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 adds exactly one leading LF to these Error messages.
171
- const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES - 1;
172
- const omitted = (key) => `- ${key}:\n - state: omitted\n - reason: response_too_large\n - shown: 0\n - omitted: 1\n - total: 1`;
173
- const field = ([key, value]) => typeof value === "string"
174
- ? ToolsResponse.formatMarkdownField(key, value)
175
- : `- ${key}:\n${value.map(item => ` - ${item}`).join("\n")}`;
176
- // Unmarked fields are bounded compiler literals/lists. Short values seed full so admission only grows.
177
- const formatted = fields.map(field);
178
- const projected = fields.map((item, index) => {
179
- const full = formatted[index], fallback = omitted(item[0]);
180
- return item[2] && Buffer.byteLength(full, "utf8") > Buffer.byteLength(fallback, "utf8") ? fallback : full;
181
- });
182
- let action = next ? ToolsResponse.formatInspectNextAction(next[0], next[1]) : "";
183
- const text = () => [...projected, ...(action ? [action] : [])].join("\n");
184
- if (next && Buffer.byteLength(text(), "utf8") > budget)
185
- action = omitted("next_action");
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
- return text();
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(view, input, deps, context) {
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 fields = [ToolsResponse.formatMarkdownField("recipes", "doctor"),
519
- ...["identity", "skill_active", "resolvable", "catalog_partial", "component_status"]
520
- .map(key => ToolsResponse.formatMarkdownField(key, String(details[key]))),
521
- ...(details.source_location ? [ToolsResponse.formatMarkdownField("source_location", ToolsResponse.formatLiteralBlock(String(details.source_location)))] : []),
522
- ToolsResponse.formatMarkdownField("resolution_generation", String(details.resolution_generation)),
523
- ...(details.rejected_reason ? [ToolsResponse.formatMarkdownField("rejected_reason", String(details.rejected_reason))] : [])];
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
- if (actions?.length)
526
- fields.push(ToolsResponse.formatInspectionHint(String(actions[0])));
527
- return "\n" + fields.join("\n");
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 argumentsField = (key, value) => Array.isArray(value) && value.length
533
- ? "- " + key + ":\n" + value.map(item => " - " + String(item).replace(/\r\n|\r|\n/gu, match => match + " ")).join("\n")
534
- : ToolsResponse.formatMarkdownField(key, Array.isArray(value) ? "none" : "unknown");
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 ${target.slice(5)}`
539
- : `register_tool name=${target.slice(5)} update=true`;
540
- return "\n" + [ToolsResponse.formatMarkdownField("tool", target.slice(5)),
541
- ToolsResponse.formatMarkdownField("source", String(details.source ?? "unknown")),
542
- ToolsResponse.formatMarkdownField("callable_now", String(details.callable_now ?? false)),
543
- ToolsResponse.formatMarkdownField("activation_boundary", String(details.activation_boundary ?? "unknown")),
544
- argumentsField("required_args", details.required_args), argumentsField("optional_args", details.optional_args),
545
- ToolsResponse.formatMarkdownField("launch_kind", String(details.launch_kind ?? "none")),
546
- ToolsResponse.formatMarkdownField("spawn_calls", String(Number(details.spawn_calls ?? 0))),
547
- ToolsResponse.formatMarkdownField("tool_calls", String(Number(details.tool_calls ?? 0))), ToolsResponse.formatInspectionHint(next)].join("\n");
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 "\n" + ToolsResponse.formatMarkdownField("tool", target.slice(5)) + "\n" + ToolsResponse.formatMarkdownField("view", view);
551
- return "\n" + ToolsResponse.formatMarkdownField("run", target.slice(4)) + "\n" + ToolsResponse.formatMarkdownField("view", view);
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: maybeJsonText(details, input.verbose === true || (target.startsWith("tool:") && view === "schema"), compactResult(target, view, details)),
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
  };
@@ -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
- const lines = [
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
- return new Error(`Invalid arguments for tool "${cfg.name}": ${message}\n\n${formatRuntimeToolUsageHint(cfg, required, includeRunId)}`);
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 = {};