@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
|
@@ -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
|
+
}
|
package/lib/tools-inspect.ts
CHANGED
|
@@ -30,7 +30,6 @@ import * as ToolsResponse from "./tools-response.ts";
|
|
|
30
30
|
import * as TraceProjection from "./trace-projection.ts";
|
|
31
31
|
|
|
32
32
|
const asRecord = ToolsResponse.asRecord;
|
|
33
|
-
const maybeJsonText = ToolsResponse.maybeJsonText;
|
|
34
33
|
|
|
35
34
|
export interface InspectToolDeps<TContext = unknown> {
|
|
36
35
|
getRunStatus?: (runOrDir: string) => Record<string, any>;
|
|
@@ -185,16 +184,44 @@ function runtimeTriage(
|
|
|
185
184
|
};
|
|
186
185
|
}
|
|
187
186
|
|
|
187
|
+
/** Compact metadata-only documents share the same emitter/omission policy as other receipts. */
|
|
188
|
+
function compactDocument(value: Record<string, unknown>): string {
|
|
189
|
+
return ToolsResponse.compactYamlDocument(value);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Generated definition metadata is YAML like other owned output; the machine JSON stays in details. */
|
|
193
|
+
function schemaDocument(details: Record<string, unknown>): string {
|
|
194
|
+
// Absent definition fields stay absent exactly as JSON.stringify omitted them; values are native nodes.
|
|
195
|
+
const selected: Record<string, unknown> = {};
|
|
196
|
+
for (const key of ["name", "description", "parameters", "promptSnippet"])
|
|
197
|
+
if (details[key] !== undefined) selected[key] = details[key];
|
|
198
|
+
// The shared emitter's own execution ceiling is the only wider allowance; a pathological schema
|
|
199
|
+
// keeps the exact legacy JSON text rather than being clipped or partly reformatted.
|
|
200
|
+
const rendered = ToolsResponse.renderYamlMetadata(selected, 49152);
|
|
201
|
+
return rendered.state === "available"
|
|
202
|
+
? `\n${rendered.text}` : `\n${JSON.stringify(details, null, 2)}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function compactSelected(key: string, value: string): ToolsResponse.YamlPart {
|
|
206
|
+
return ToolsResponse.selectYamlField(key, value, Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function compactArguments(key: string, value: unknown): ToolsResponse.YamlPart {
|
|
210
|
+
const selected = Array.isArray(value) && value.length
|
|
211
|
+
? value.map(item => String(item)) : Array.isArray(value) ? "none" : "unknown";
|
|
212
|
+
return ToolsResponse.selectYamlField(key, selected, Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
213
|
+
}
|
|
214
|
+
|
|
188
215
|
function compactRuntime(view: string, details: Record<string, unknown>): string {
|
|
189
|
-
const
|
|
216
|
+
const count = (value: unknown) => (Array.isArray(value) ? value.length : 0);
|
|
217
|
+
return compactDocument(view === "runs" ? { runtime: view, runs: count(details.runs) }
|
|
190
218
|
: view === "triage" ? {
|
|
191
|
-
failed: (details.failed_runs
|
|
219
|
+
runtime: view, failed: count(details.failed_runs),
|
|
192
220
|
pending_controls: Number(details.pending_control_count ?? 0), stale_controls: Number(details.stale_control_count ?? 0),
|
|
193
221
|
backpressured: Number(details.backpressured_run_count ?? 0), incomplete_trace: Number(details.trace_diagnostic_count ?? 0),
|
|
194
|
-
attention: (details.attention_events
|
|
195
|
-
} : { version: String(details.version), state_schema: String(details.state_schema),
|
|
196
|
-
|
|
197
|
-
...Object.entries(fields).map(([key, value]) => ToolsResponse.formatMarkdownField(key, String(value)))].join("\n");
|
|
222
|
+
attention: count(details.attention_events),
|
|
223
|
+
} : { runtime: view, version: String(details.version), state_schema: String(details.state_schema),
|
|
224
|
+
automatic_review: String(details.automatic_review) });
|
|
198
225
|
}
|
|
199
226
|
|
|
200
227
|
// Projects local refusals only; does not intercept other throws or change existing catches.
|
|
@@ -202,28 +229,39 @@ function formatInspectFailure(
|
|
|
202
229
|
fields: readonly (readonly [key: string, value: string | readonly string[], omittable?: true])[],
|
|
203
230
|
next?: readonly [target: string, view: string],
|
|
204
231
|
): string {
|
|
205
|
-
// The unchanged public adapter
|
|
206
|
-
const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
const full =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!item[2]) return;
|
|
222
|
-
const fallback = projected[index]!;
|
|
223
|
-
projected[index] = formatted[index]!;
|
|
224
|
-
if (Buffer.byteLength(text(), "utf8") > budget) projected[index] = fallback;
|
|
232
|
+
// The unchanged public adapter replaces this document's trailing LF with its one leading LF.
|
|
233
|
+
const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES;
|
|
234
|
+
// Field-local coverage record: an unfit selected scalar/sequence is omitted whole, never clipped.
|
|
235
|
+
const omitted = { state: "omitted", reason: "response_too_large", shown: 0, omitted: 1, total: 1 };
|
|
236
|
+
const render = (value: Record<string, unknown>): string | undefined => {
|
|
237
|
+
const rendered = ToolsResponse.renderYamlMetadata(value, budget);
|
|
238
|
+
return rendered.state === "available" ? rendered.text : undefined;
|
|
239
|
+
};
|
|
240
|
+
const parts = fields.map(([key, value, omittable]) => {
|
|
241
|
+
const full: Record<string, unknown> = typeof value === "string" ? { [key]: value } : { [key]: [...value] };
|
|
242
|
+
const fallback: Record<string, unknown> = { [key]: omitted };
|
|
243
|
+
// Unmarked fields are bounded compiler literals/lists. Short values seed full so admission only grows.
|
|
244
|
+
const complete = render(full);
|
|
245
|
+
const selected = omittable && (complete === undefined || Buffer.byteLength(complete) > Buffer.byteLength(render(fallback) ?? ""))
|
|
246
|
+
? fallback : full;
|
|
247
|
+
return { omittable, full, selected };
|
|
225
248
|
});
|
|
226
|
-
|
|
249
|
+
// Only producer-known read-only inspect literals become a typed next, never a parsed action.
|
|
250
|
+
const typedNext = next ? { next: { tool: "inspect", target: next[0], view: next[1] } } : undefined;
|
|
251
|
+
const nextFallback = { next_action: omitted };
|
|
252
|
+
const document = () => ({ ...Object.assign({}, ...parts.map((part) => part.selected)),
|
|
253
|
+
...(typedNext ?? nextFallback) });
|
|
254
|
+
let selectedNext = typedNext;
|
|
255
|
+
if (next && render(document()) === undefined) selectedNext = undefined;
|
|
256
|
+
const nextValue = () => (next ? (selectedNext ?? nextFallback) : {});
|
|
257
|
+
for (const part of parts) {
|
|
258
|
+
if (!part.omittable) continue;
|
|
259
|
+
const previous = part.selected;
|
|
260
|
+
part.selected = part.full;
|
|
261
|
+
if (render({ ...Object.assign({}, ...parts.map((item) => item.selected)), ...nextValue() }) === undefined) part.selected = previous;
|
|
262
|
+
}
|
|
263
|
+
const text = render({ ...Object.assign({}, ...parts.map((item) => item.selected)), ...nextValue() });
|
|
264
|
+
return (text ?? "").replace(/\n$/u, "");
|
|
227
265
|
}
|
|
228
266
|
|
|
229
267
|
function inspectRuntime(
|
|
@@ -387,11 +425,13 @@ function inspectRecipeIdentity(
|
|
|
387
425
|
}
|
|
388
426
|
|
|
389
427
|
function inspectRecipes(
|
|
390
|
-
|
|
428
|
+
requestedView: string,
|
|
391
429
|
input: Record<string, unknown>,
|
|
392
430
|
deps: InspectToolDeps,
|
|
393
431
|
context: unknown,
|
|
394
432
|
): Record<string, unknown> {
|
|
433
|
+
// `registry` and `summary` name one inspection path; only `verbose` selects compact versus full output.
|
|
434
|
+
const view = requestedView === "registry" ? "summary" : requestedView;
|
|
395
435
|
if (
|
|
396
436
|
view !== "status" &&
|
|
397
437
|
view !== "summary" &&
|
|
@@ -401,7 +441,7 @@ function inspectRecipes(
|
|
|
401
441
|
) {
|
|
402
442
|
throw new Error(formatInspectFailure([
|
|
403
443
|
["target_kind", "recipes"], ["result", "unsupported view"],
|
|
404
|
-
["supported_views", ["status", "summary", "doctor", "imports", "reviews"]],
|
|
444
|
+
["supported_views", ["status", "summary", "registry", "doctor", "imports", "reviews"]],
|
|
405
445
|
]));
|
|
406
446
|
}
|
|
407
447
|
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
@@ -637,37 +677,47 @@ function inspectTool(
|
|
|
637
677
|
function compactResult(target: string, view: string, details: Record<string, unknown>): string {
|
|
638
678
|
if (target === "runtime") return compactRuntime(view, details);
|
|
639
679
|
if (target === "recipes" && typeof details.identity === "string") {
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
680
|
+
const identity = compactSelected("identity", String(details.identity));
|
|
681
|
+
const location = typeof details.source_location === "string"
|
|
682
|
+
? compactSelected("source_location", details.source_location) : undefined;
|
|
683
|
+
const generation = compactSelected("resolution_generation", String(details.resolution_generation));
|
|
684
|
+
const rejected = typeof details.rejected_reason === "string"
|
|
685
|
+
? compactSelected("rejected_reason", details.rejected_reason) : undefined;
|
|
646
686
|
const actions = details.next_actions as unknown[] | undefined;
|
|
647
|
-
|
|
648
|
-
|
|
687
|
+
const hint = actions?.length
|
|
688
|
+
? ToolsResponse.nextActionMetadata(String(actions[0]), Limits.RUN_DELIVERY_MODEL_MAX_BYTES) : undefined;
|
|
689
|
+
const parts = [identity, ...(location ? [location] : []), generation, ...(rejected ? [rejected] : []), ...(hint ? [hint] : [])];
|
|
690
|
+
const render = (): Record<string, unknown> => ({ recipes: "doctor", ...identity.selected,
|
|
691
|
+
skill_active: String(details.skill_active), resolvable: String(details.resolvable),
|
|
692
|
+
catalog_partial: String(details.catalog_partial), component_status: String(details.component_status),
|
|
693
|
+
...(location ? location.selected : {}), ...generation.selected, ...(rejected ? rejected.selected : {}),
|
|
694
|
+
...(hint ? hint.selected : {}) });
|
|
695
|
+
return `\n${ToolsResponse.growYamlDocument(render, parts, Limits.RUN_DELIVERY_MODEL_MAX_BYTES).replace(/\n$/u, "")}`;
|
|
649
696
|
}
|
|
650
697
|
if (target === "recipes") return ToolsResponse.compactRecipeRegistry(details);
|
|
651
698
|
if (target.startsWith("tool:") && view === "status") {
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
699
|
+
const name = target.slice(5);
|
|
700
|
+
const tool = compactSelected("tool", name);
|
|
701
|
+
const source = compactSelected("source", String(details.source ?? "unknown"));
|
|
702
|
+
const boundary = compactSelected("activation_boundary", String(details.activation_boundary ?? "unknown"));
|
|
655
703
|
const next = Array.isArray(details.next_actions) && details.next_actions.length > 0
|
|
656
704
|
? String(details.next_actions[0])
|
|
657
705
|
: details.callable_now === true
|
|
658
|
-
? `call tool ${
|
|
659
|
-
: `register_tool name=${
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
706
|
+
? `call tool ${name}`
|
|
707
|
+
: `register_tool name=${name} update=true`;
|
|
708
|
+
const hint = ToolsResponse.nextActionMetadata(next, Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
709
|
+
const required = compactArguments("required_args", details.required_args);
|
|
710
|
+
const optional = compactArguments("optional_args", details.optional_args);
|
|
711
|
+
const render = (): Record<string, unknown> => ({ ...tool.selected, ...source.selected,
|
|
712
|
+
callable_now: details.callable_now === true, ...boundary.selected,
|
|
713
|
+
...required.selected, ...optional.selected,
|
|
714
|
+
launch_kind: String(details.launch_kind ?? "none"),
|
|
715
|
+
spawn_calls: Number(details.spawn_calls ?? 0), tool_calls: Number(details.tool_calls ?? 0),
|
|
716
|
+
...hint.selected });
|
|
717
|
+
return `\n${ToolsResponse.growYamlDocument(render, [tool, source, boundary, required, optional, hint], Limits.RUN_DELIVERY_MODEL_MAX_BYTES).replace(/\n$/u, "")}`;
|
|
668
718
|
}
|
|
669
|
-
if (target.startsWith("tool:")) return
|
|
670
|
-
return
|
|
719
|
+
if (target.startsWith("tool:")) return compactDocument({ tool: target.slice(5), view });
|
|
720
|
+
return compactDocument({ run: target.slice(4), view });
|
|
671
721
|
}
|
|
672
722
|
|
|
673
723
|
export function createInspectToolDefinition<TContext = unknown>(
|
|
@@ -691,7 +741,7 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
691
741
|
target: Schema.stringSchema("Target: run:<id>, runtime, recipes, or tool:<name>."),
|
|
692
742
|
verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
|
|
693
743
|
view: Schema.stringSchema(
|
|
694
|
-
"Run view recipe|trace|control, or target-specific management view.",
|
|
744
|
+
"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.",
|
|
695
745
|
),
|
|
696
746
|
},
|
|
697
747
|
["target", "view"],
|
|
@@ -726,11 +776,11 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
726
776
|
return {
|
|
727
777
|
content: [{
|
|
728
778
|
type: "text" as const,
|
|
729
|
-
text:
|
|
730
|
-
details
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
779
|
+
text: input.verbose === true
|
|
780
|
+
? ToolsResponse.jsonText(details)
|
|
781
|
+
: target.startsWith("tool:") && view === "schema"
|
|
782
|
+
? schemaDocument(details)
|
|
783
|
+
: compactResult(target, view, details),
|
|
734
784
|
}],
|
|
735
785
|
details,
|
|
736
786
|
};
|
package/lib/tools-local.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import * as AsyncRuns from "./async-runs.ts";
|
|
8
8
|
import * as CommandTemplates from "./command-templates.ts";
|
|
9
|
+
import * as Limits from "./limits.ts";
|
|
9
10
|
import * as ModelContext from "./model-context.ts";
|
|
10
11
|
import type { RegisteredTool } from "./config.ts";
|
|
11
12
|
import * as Execution from "./execution.ts";
|
|
@@ -66,6 +67,25 @@ function shouldAddRuntimeToolUsageHint(error: unknown): boolean {
|
|
|
66
67
|
);
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
/** Owned usage facts only; the JSON invocation example stays unparsed instructional body text. */
|
|
71
|
+
function runtimeToolUsageMetadata(
|
|
72
|
+
cfg: RegisteredTool,
|
|
73
|
+
required: string[],
|
|
74
|
+
includeRunId: boolean,
|
|
75
|
+
): string {
|
|
76
|
+
const optional = cfg.args.filter(
|
|
77
|
+
(arg) =>
|
|
78
|
+
!RecipesReferences.isRuntimeOwnedRecipeInput(arg) &&
|
|
79
|
+
!required.includes(arg),
|
|
80
|
+
);
|
|
81
|
+
const budget = Limits.RUN_DELIVERY_MODEL_MAX_BYTES;
|
|
82
|
+
const tool = ToolsResponse.selectYamlField("tool", cfg.name, budget);
|
|
83
|
+
const requiredPart = ToolsResponse.selectYamlField("required", required, budget);
|
|
84
|
+
const optionalPart = ToolsResponse.selectYamlField("optional", [...optional, ...(includeRunId ? ["run_id"] : [])], budget);
|
|
85
|
+
return ToolsResponse.growYamlDocument(() => ({ ...tool.selected, ...requiredPart.selected, ...optionalPart.selected }),
|
|
86
|
+
[tool, requiredPart, optionalPart], budget);
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
function formatRuntimeToolUsageHint(
|
|
70
90
|
cfg: RegisteredTool,
|
|
71
91
|
required: string[],
|
|
@@ -87,16 +107,10 @@ function formatRuntimeToolUsageHint(
|
|
|
87
107
|
for (const arg of optional)
|
|
88
108
|
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], exampleDefaults);
|
|
89
109
|
if (includeRunId) example.run_id = `${cfg.name}-1`;
|
|
90
|
-
|
|
110
|
+
return [
|
|
91
111
|
`Expected call shape for ${cfg.name}:`,
|
|
92
112
|
`${cfg.name}(${JSON.stringify(example, null, 2)})`,
|
|
93
|
-
];
|
|
94
|
-
if (required.length) lines.push(`Required: ${required.join(", ")}`);
|
|
95
|
-
if (optional.length || includeRunId)
|
|
96
|
-
lines.push(
|
|
97
|
-
`Optional: ${[...optional, ...(includeRunId ? ["run_id"] : [])].join(", ")}`,
|
|
98
|
-
);
|
|
99
|
-
return lines.join("\n");
|
|
113
|
+
].join("\n");
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
function resolveRecipeToolValues(
|
|
@@ -134,13 +148,10 @@ function formatRuntimeToolArgumentError(
|
|
|
134
148
|
const message = error instanceof Error ? error.message : String(error);
|
|
135
149
|
if (!shouldAddRuntimeToolUsageHint(error))
|
|
136
150
|
return error instanceof Error ? error : new Error(message);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
includeRunId,
|
|
142
|
-
)}`,
|
|
143
|
-
);
|
|
151
|
+
// Whole usage groups fit the header budget or carry explicit omission; the JSON example stays opaque.
|
|
152
|
+
const header = runtimeToolUsageMetadata(cfg, required, includeRunId);
|
|
153
|
+
return new Error(ToolsResponse.withLeadingLineBreak(ToolsResponse.frameMetadataDocument(header,
|
|
154
|
+
`Invalid arguments for tool "${cfg.name}": ${message}\n\n${formatRuntimeToolUsageHint(cfg, required, includeRunId)}`)));
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
export function createRuntimeToolDefinition(
|