@llblab/pi-actors 0.43.1 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -6
- package/BACKLOG.md +1 -189
- package/CHANGELOG.md +419 -402
- package/README.md +8 -6
- package/dist/index.js +7 -3
- package/dist/lib/async-runs.d.ts +2 -0
- package/dist/lib/async-runs.js +41 -34
- package/dist/lib/config.js +1 -0
- package/dist/lib/file-state.d.ts +3 -1
- package/dist/lib/file-state.js +25 -7
- package/dist/lib/inspector-overlay.js +46 -30
- package/dist/lib/inspector.js +8 -2
- package/dist/lib/limits.d.ts +10 -0
- package/dist/lib/limits.js +10 -0
- package/dist/lib/observability.d.ts +4 -2
- package/dist/lib/observability.js +43 -36
- package/dist/lib/recipes-discovery.js +20 -3
- package/dist/lib/recipes-references.d.ts +10 -0
- package/dist/lib/recipes-references.js +168 -5
- package/dist/lib/run-evidence-policy.d.ts +95 -0
- package/dist/lib/run-evidence-policy.js +177 -0
- package/dist/lib/run-ui-runtime.js +2 -0
- package/dist/lib/runs-controls.d.ts +7 -5
- package/dist/lib/runs-controls.js +186 -49
- package/dist/lib/runs-retention.js +27 -14
- package/dist/lib/runs-trace.d.ts +25 -1
- package/dist/lib/runs-trace.js +410 -21
- package/dist/lib/runtime-triage.js +6 -22
- package/dist/lib/schema.d.ts +3 -0
- package/dist/lib/schema.js +34 -1
- package/dist/lib/tools-inspect.js +58 -14
- package/dist/lib/tools-local.js +27 -10
- package/dist/lib/tools-spawn.js +2 -2
- package/dist/lib/trace-projection.js +90 -44
- package/dist/scripts/conformance.mjs +5 -0
- package/dist/scripts/locker.mjs +31 -74
- package/dist/scripts/music-player.mjs +41 -129
- package/dist/scripts/release-gates.mjs +26 -4
- package/dist/skills/actors/SKILL.md +9 -8
- package/dist/skills/swarm/SKILL.md +2 -2
- package/docs/README.md +0 -1
- package/docs/actor-inspector.md +3 -2
- package/docs/async-runs.md +10 -8
- package/docs/command-templates.md +2 -2
- package/docs/recipe-library.md +4 -2
- package/docs/template-recipes.md +10 -4
- package/docs/tool-registry.md +1 -1
- package/index.ts +9 -3
- package/lib/async-runs.ts +51 -54
- package/lib/config.ts +4 -0
- package/lib/file-state.ts +28 -7
- package/lib/inspector-overlay.ts +29 -17
- package/lib/inspector.ts +16 -2
- package/lib/limits.ts +10 -0
- package/lib/observability.ts +55 -57
- package/lib/recipes-discovery.ts +23 -3
- package/lib/recipes-references.ts +227 -7
- package/lib/run-evidence-policy.ts +242 -0
- package/lib/run-ui-runtime.ts +2 -0
- package/lib/runs-controls.ts +177 -108
- package/lib/runs-retention.ts +28 -20
- package/lib/runs-trace.ts +496 -21
- package/lib/runtime-triage.ts +11 -25
- package/lib/schema.ts +53 -1
- package/lib/tools-inspect.ts +56 -15
- package/lib/tools-local.ts +49 -19
- package/lib/tools-spawn.ts +2 -2
- package/lib/trace-projection.ts +137 -75
- package/package.json +1 -1
- package/scripts/conformance.mjs +5 -0
- package/scripts/locker.mjs +31 -74
- package/scripts/music-player.mjs +41 -129
- package/scripts/release-gates.mjs +26 -4
- package/skills/actors/SKILL.md +9 -8
- package/skills/swarm/SKILL.md +2 -2
- package/dist/lib/runtime-notifier.d.ts +0 -48
- package/dist/lib/runtime-notifier.js +0 -138
- package/docs/0.43-baseline.md +0 -39
- package/lib/runtime-notifier.ts +0 -211
package/lib/schema.ts
CHANGED
|
@@ -185,7 +185,11 @@ function isValidDefault(type: ToolArgType, value: string): boolean {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
export function parseToolArgDeclarations(value: string): ToolArgSpec {
|
|
188
|
-
|
|
188
|
+
return parseToolArgDeclarationList(splitArgDeclarations(value));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function parseToolArgDeclarationList(value: unknown[]): ToolArgSpec {
|
|
192
|
+
const source = value.map(String);
|
|
189
193
|
const seen = new Set<string>();
|
|
190
194
|
const duplicates = new Set<string>();
|
|
191
195
|
const args: string[] = [];
|
|
@@ -324,6 +328,7 @@ function collectCommandTemplateConfigDeclarations(
|
|
|
324
328
|
collectTemplatePlaceholderDeclarations(config, declarations);
|
|
325
329
|
return;
|
|
326
330
|
}
|
|
331
|
+
if (!config || typeof config !== "object") return;
|
|
327
332
|
if (Array.isArray(config)) {
|
|
328
333
|
for (const step of config)
|
|
329
334
|
collectCommandTemplateConfigDeclarations(step, declarations);
|
|
@@ -356,11 +361,42 @@ export function getTemplatePlaceholderNames(
|
|
|
356
361
|
);
|
|
357
362
|
}
|
|
358
363
|
|
|
364
|
+
function toolArgTypesEqual(left: ToolArgType, right: ToolArgType): boolean {
|
|
365
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function assertCompatibleToolArgTypes(
|
|
369
|
+
...sources: Array<Record<string, ToolArgType> | undefined>
|
|
370
|
+
): void {
|
|
371
|
+
const seen: Record<string, ToolArgType> = {};
|
|
372
|
+
for (const source of sources) {
|
|
373
|
+
for (const [name, type] of Object.entries(source ?? {})) {
|
|
374
|
+
const previous = seen[name];
|
|
375
|
+
if (previous && !toolArgTypesEqual(previous, type)) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Conflicting argument type for ${name}: ${canonicalArgDeclaration(name, previous)} vs ${canonicalArgDeclaration(name, type)}`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
seen[name] = type;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
359
385
|
export function getTemplateArgTypes(
|
|
360
386
|
config: CommandTemplates.CommandTemplateConfig,
|
|
361
387
|
): Record<string, ToolArgType> {
|
|
362
388
|
const argTypes: Record<string, ToolArgType> = {};
|
|
363
389
|
for (const declaration of getTemplatePlaceholderDeclarations(config)) {
|
|
390
|
+
const previous = argTypes[declaration.arg];
|
|
391
|
+
if (
|
|
392
|
+
previous &&
|
|
393
|
+
declaration.type.kind !== "string" &&
|
|
394
|
+
!toolArgTypesEqual(previous, declaration.type)
|
|
395
|
+
) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`Conflicting argument type for ${declaration.arg}: ${canonicalArgDeclaration(declaration.arg, previous)} vs ${declaration.declaration}`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
364
400
|
if (declaration.type.kind !== "string")
|
|
365
401
|
argTypes[declaration.arg] = declaration.type;
|
|
366
402
|
}
|
|
@@ -373,6 +409,22 @@ export function getExplicitToolArgNames(args: string[] | undefined): string[] {
|
|
|
373
409
|
);
|
|
374
410
|
}
|
|
375
411
|
|
|
412
|
+
export function getExplicitToolArgDefaults(
|
|
413
|
+
args: string[] | string | undefined,
|
|
414
|
+
): Record<string, string> {
|
|
415
|
+
const declarations =
|
|
416
|
+
typeof args === "string" ? splitArgDeclarations(args) : (args ?? []);
|
|
417
|
+
return Object.fromEntries(
|
|
418
|
+
declarations
|
|
419
|
+
.map((item) => parseToolArgToken(String(item)))
|
|
420
|
+
.filter(
|
|
421
|
+
(item): item is ParsedToolArgToken & { defaultValue: string } =>
|
|
422
|
+
item.defaultValue !== undefined,
|
|
423
|
+
)
|
|
424
|
+
.map((item) => [item.arg, item.defaultValue]),
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
376
428
|
export function getToolArgNames(
|
|
377
429
|
config: CommandTemplates.CommandTemplateConfig,
|
|
378
430
|
): string[] {
|
package/lib/tools-inspect.ts
CHANGED
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
* Owns exact inspect target/view dispatch; source projection stays in domain modules.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { statSync } from "node:fs";
|
|
7
8
|
import { join } from "node:path";
|
|
8
9
|
|
|
9
10
|
import * as AsyncRuns from "./async-runs.ts";
|
|
10
11
|
import * as ControlProjection from "./control-projection.ts";
|
|
11
12
|
import * as Inspector from "./inspector.ts";
|
|
12
13
|
import * as Limits from "./limits.ts";
|
|
14
|
+
import { computeRunControlCapacity } from "./run-evidence-policy.ts";
|
|
13
15
|
import * as Paths from "./paths.ts";
|
|
14
16
|
import * as RecipesDiscovery from "./recipes-discovery.ts";
|
|
17
|
+
import * as RecipesReferences from "./recipes-references.ts";
|
|
15
18
|
import * as ReviewDiagnostics from "./review-diagnostics.ts";
|
|
16
19
|
import * as RunsControls from "./runs-controls.ts";
|
|
17
20
|
import * as RunControlDelivery from "./runs-control-delivery.ts";
|
|
@@ -75,20 +78,26 @@ function runtimeTriage(
|
|
|
75
78
|
const failed = runs.filter((run) => run.status === "failed");
|
|
76
79
|
const pendingControls: RuntimeTriage.RuntimePendingControl[] = [];
|
|
77
80
|
const staleControls: RuntimeTriage.RuntimePendingControl[] = [];
|
|
81
|
+
const backpressuredRuns: Array<Record<string, unknown>> = [];
|
|
78
82
|
const controlDiagnostics: Array<Record<string, unknown>> = [];
|
|
83
|
+
const traceDiagnostics: Array<Record<string, unknown>> = [];
|
|
79
84
|
const nowMs = Date.now();
|
|
80
85
|
for (const run of runs) {
|
|
81
86
|
const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
|
|
82
87
|
if (!stateDir) continue;
|
|
83
88
|
const runId = String(run.run ?? "unknown");
|
|
84
89
|
const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
90
|
+
const capacity = computeRunControlCapacity(journal.records);
|
|
85
91
|
controlDiagnostics.push(...journal.diagnostics.map((diagnostic) => ({
|
|
86
92
|
...(diagnostic.line === undefined ? {} : { line: diagnostic.line }),
|
|
87
|
-
reason: diagnostic.line === undefined
|
|
88
|
-
? "unreadable_control_journal"
|
|
89
|
-
: "invalid_control_json",
|
|
90
|
-
run: runId,
|
|
93
|
+
reason: diagnostic.line === undefined ? "unreadable_control_journal" : "invalid_control_json", run: runId,
|
|
91
94
|
})));
|
|
95
|
+
if (capacity.backpressured) backpressuredRuns.push({
|
|
96
|
+
run: runId, pending: capacity.pending, pending_limit: capacity.limit,
|
|
97
|
+
journal_bytes: statSync(RunsControls.runControlsFile(stateDir)).size,
|
|
98
|
+
});
|
|
99
|
+
const traceSummary = RunsTrace.summarizeRunTraceJournal(RunsTrace.readRunTraceJournal(stateDir));
|
|
100
|
+
if (!traceSummary.history_complete) traceDiagnostics.push({ run: runId, ...traceSummary });
|
|
92
101
|
for (const control of journal.records) {
|
|
93
102
|
const classified = RuntimeTriage.classifyRuntimeControl(
|
|
94
103
|
{
|
|
@@ -137,14 +146,15 @@ function runtimeTriage(
|
|
|
137
146
|
pending_controls: boundedPendingControls,
|
|
138
147
|
stale_control_count: staleControls.length,
|
|
139
148
|
stale_controls: boundedStaleControls,
|
|
140
|
-
|
|
141
|
-
control_diagnostics: controlDiagnostics.slice(0, 40),
|
|
149
|
+
backpressured_run_count: backpressuredRuns.length, backpressured_runs: backpressuredRuns.slice(0, 40),
|
|
150
|
+
control_diagnostic_count: controlDiagnostics.length, control_diagnostics: controlDiagnostics.slice(0, 40),
|
|
151
|
+
trace_diagnostic_count: traceDiagnostics.length, trace_diagnostics: traceDiagnostics.slice(0, 40),
|
|
142
152
|
attention_events: attentionEvents.slice(-40).reverse(),
|
|
143
153
|
next_actions: [
|
|
144
154
|
...(failed.length ? ["inspect target=runtime view=runs status=failed"] : []),
|
|
145
|
-
...staleControls.slice(0, 3).map((entry) =>
|
|
155
|
+
...new Set([...staleControls, ...backpressuredRuns].slice(0, 3).map((entry) =>
|
|
146
156
|
`inspect target=run:${entry.run} view=control`,
|
|
147
|
-
),
|
|
157
|
+
)),
|
|
148
158
|
...(attentionEvents.length
|
|
149
159
|
? attentionEvents.slice(-3).map((entry) =>
|
|
150
160
|
`inspect target=run:${String(entry.run)} view=trace source=runtime`,
|
|
@@ -159,7 +169,7 @@ function compactRuntime(view: string, details: Record<string, unknown>): string
|
|
|
159
169
|
return `\nruntime runs=${(details.runs as unknown[] | undefined)?.length ?? 0}`;
|
|
160
170
|
}
|
|
161
171
|
if (view === "triage") {
|
|
162
|
-
return `\nruntime failed=${(details.failed_runs as unknown[] | undefined)?.length ?? 0} pending_controls=${Number(details.pending_control_count ?? 0)} stale_controls=${Number(details.stale_control_count ?? 0)} attention=${(details.attention_events as unknown[] | undefined)?.length ?? 0}`;
|
|
172
|
+
return `\nruntime failed=${(details.failed_runs as unknown[] | undefined)?.length ?? 0} pending_controls=${Number(details.pending_control_count ?? 0)} stale_controls=${Number(details.stale_control_count ?? 0)} backpressured=${Number(details.backpressured_run_count ?? 0)} incomplete_trace=${Number(details.trace_diagnostic_count ?? 0)} attention=${(details.attention_events as unknown[] | undefined)?.length ?? 0}`;
|
|
163
173
|
}
|
|
164
174
|
return `\nruntime version=${String(details.version)} state_schema=${String(details.state_schema)} automatic_review=${String(details.automatic_review)}`;
|
|
165
175
|
}
|
|
@@ -205,8 +215,18 @@ function inspectRecipes(
|
|
|
205
215
|
{ root: recipeRoot, defaultTool: true, mutableUsage: true },
|
|
206
216
|
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
207
217
|
]);
|
|
218
|
+
const skillRecipeNamespaces =
|
|
219
|
+
RecipesReferences.getActiveSkillRecipeNamespaces();
|
|
208
220
|
const summary = {
|
|
209
221
|
...RecipesDiscovery.summarizeDiscovery(discovered),
|
|
222
|
+
skill_recipe_namespaces: skillRecipeNamespaces,
|
|
223
|
+
skill_recipe_namespace_diagnostics: Object.entries(skillRecipeNamespaces)
|
|
224
|
+
.filter(([, roots]) => roots.length > 1)
|
|
225
|
+
.map(([name, roots]) => ({
|
|
226
|
+
name,
|
|
227
|
+
roots,
|
|
228
|
+
error: `Ambiguous active Skill Recipe namespace ${name}`,
|
|
229
|
+
})),
|
|
210
230
|
drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
|
|
211
231
|
};
|
|
212
232
|
return {
|
|
@@ -252,8 +272,8 @@ function inspectRun(
|
|
|
252
272
|
throw new Error(`unsupported Trace source: ${source}`);
|
|
253
273
|
}
|
|
254
274
|
return {
|
|
255
|
-
run,
|
|
256
|
-
|
|
275
|
+
run, source,
|
|
276
|
+
summary: RunsTrace.summarizeRunTraceJournal(RunsTrace.readRunTraceJournal(stateDir)),
|
|
257
277
|
items: TraceProjection.projectRunTrace(stateDir, {
|
|
258
278
|
artifacts: status.artifacts as
|
|
259
279
|
| Record<string, AsyncRuns.RunArtifactDeclaration>
|
|
@@ -264,19 +284,40 @@ function inspectRun(
|
|
|
264
284
|
};
|
|
265
285
|
}
|
|
266
286
|
const runInstanceId = String(status.run_instance_id ?? "");
|
|
287
|
+
const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
288
|
+
const capacity = computeRunControlCapacity(journal.records);
|
|
289
|
+
const structuralDiagnostics = journal.records.flatMap((value) => {
|
|
290
|
+
const classified = RuntimeTriage.classifyRuntimeControl({ run,
|
|
291
|
+
runInstanceId: runInstanceId || undefined, status: String(status.status ?? "unknown") }, value, Date.now());
|
|
292
|
+
return classified.diagnostic ? [classified.diagnostic.reason] : [];
|
|
293
|
+
});
|
|
294
|
+
let journalBytes = 0;
|
|
295
|
+
try { journalBytes = statSync(RunsControls.runControlsFile(stateDir)).size; } catch {}
|
|
296
|
+
const diagnostics = [...journal.diagnostics.map((item) => ({
|
|
297
|
+
...(item.line === undefined ? {} : { line: item.line }),
|
|
298
|
+
reason: item.line === undefined ? "unreadable_control_journal" : "invalid_control_json",
|
|
299
|
+
})), ...structuralDiagnostics.map((reason) => ({ reason }))].slice(0, 40);
|
|
300
|
+
const nowMs = Date.now();
|
|
301
|
+
const stalePending = journal.records.reduce<number>((count, control) => count + Number(Boolean(
|
|
302
|
+
RuntimeTriage.classifyRuntimeControl({ run, runInstanceId: runInstanceId || undefined,
|
|
303
|
+
status: String(status.status ?? "unknown") }, control, nowMs).stale)), 0);
|
|
267
304
|
return {
|
|
268
305
|
run,
|
|
269
306
|
run_instance_id: runInstanceId,
|
|
270
307
|
status: status.status,
|
|
308
|
+
pending: capacity.pending, pending_limit: capacity.limit,
|
|
309
|
+
available: capacity.available, backpressured: capacity.backpressured,
|
|
310
|
+
journal_bytes: journalBytes, journal_limit: Limits.RUN_CONTROL_JOURNAL_MAX_BYTES,
|
|
311
|
+
...(capacity.oldest_pending_at ? { oldest_pending_at: capacity.oldest_pending_at } : {}),
|
|
312
|
+
stale_pending: stalePending,
|
|
313
|
+
diagnostics,
|
|
271
314
|
actor_actions: Array.isArray(status.control) ? status.control : [],
|
|
272
315
|
runtime_actions: status.status === "running" ? ["kill"] : ["archive", "prune"],
|
|
273
316
|
endpoint: runInstanceId
|
|
274
317
|
? RunControlDelivery.readRunControlEndpoint(stateDir, runInstanceId)
|
|
275
318
|
: undefined,
|
|
276
|
-
recent_controls:
|
|
277
|
-
.
|
|
278
|
-
.reverse()
|
|
279
|
-
.map(ControlProjection.projectRunControl),
|
|
319
|
+
recent_controls: journal.records.slice(-Math.max(1, Number(input.lines || 20))).reverse()
|
|
320
|
+
.map((control) => ControlProjection.projectRunControl(control as RunsControls.RunControlRecord)),
|
|
280
321
|
};
|
|
281
322
|
}
|
|
282
323
|
|
package/lib/tools-local.ts
CHANGED
|
@@ -67,11 +67,16 @@ function formatRuntimeToolUsageHint(
|
|
|
67
67
|
includeRunId: boolean,
|
|
68
68
|
): string {
|
|
69
69
|
const optional = cfg.args.filter((arg) => !required.includes(arg));
|
|
70
|
+
const exampleDefaults = {
|
|
71
|
+
...Schema.getExplicitToolArgDefaults(cfg.recipe?.args),
|
|
72
|
+
...cfg.defaults,
|
|
73
|
+
...(cfg.recipe?.values ?? {}),
|
|
74
|
+
};
|
|
70
75
|
const example: Record<string, unknown> = {};
|
|
71
76
|
for (const arg of required)
|
|
72
|
-
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg],
|
|
77
|
+
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], exampleDefaults);
|
|
73
78
|
for (const arg of optional)
|
|
74
|
-
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg],
|
|
79
|
+
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], exampleDefaults);
|
|
75
80
|
if (includeRunId) example.run_id = `${cfg.name}-1`;
|
|
76
81
|
const lines = [
|
|
77
82
|
`Expected call shape for ${cfg.name}:`,
|
|
@@ -85,6 +90,32 @@ function formatRuntimeToolUsageHint(
|
|
|
85
90
|
return lines.join("\n");
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
function resolveRecipeToolValues(
|
|
94
|
+
cfg: RegisteredTool,
|
|
95
|
+
callerValues: Record<string, unknown>,
|
|
96
|
+
ctx: RuntimeToolContext,
|
|
97
|
+
): Record<string, unknown> {
|
|
98
|
+
const recipeRuntimeValues = {
|
|
99
|
+
...(cfg.recipe?.recipe_dir
|
|
100
|
+
? { recipe_dir: cfg.recipe.recipe_dir }
|
|
101
|
+
: {}),
|
|
102
|
+
...(cfg.recipe?.skill_dir ? { skill_dir: cfg.recipe.skill_dir } : {}),
|
|
103
|
+
};
|
|
104
|
+
return Schema.normalizeRuntimeValues(
|
|
105
|
+
ModelContext.withCurrentModelValues(
|
|
106
|
+
{
|
|
107
|
+
...Schema.getExplicitToolArgDefaults(cfg.recipe?.args),
|
|
108
|
+
...cfg.defaults,
|
|
109
|
+
...(cfg.recipe?.values ?? {}),
|
|
110
|
+
...callerValues,
|
|
111
|
+
...recipeRuntimeValues,
|
|
112
|
+
},
|
|
113
|
+
ctx,
|
|
114
|
+
),
|
|
115
|
+
cfg.argTypes,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
88
119
|
function formatRuntimeToolArgumentError(
|
|
89
120
|
cfg: RegisteredTool,
|
|
90
121
|
error: unknown,
|
|
@@ -128,9 +159,18 @@ export function createRuntimeToolDefinition(
|
|
|
128
159
|
defaults: cfg.defaults,
|
|
129
160
|
template: requiredTemplate,
|
|
130
161
|
};
|
|
162
|
+
const recipeInlineDefaults = Schema.getExplicitToolArgDefaults(
|
|
163
|
+
cfg.recipe?.args,
|
|
164
|
+
);
|
|
131
165
|
const requiredArgs =
|
|
132
166
|
isRecipe && cfg.storedArgs !== undefined
|
|
133
|
-
? new Set(
|
|
167
|
+
? new Set(
|
|
168
|
+
cfg.args.filter(
|
|
169
|
+
(arg) =>
|
|
170
|
+
!Object.hasOwn(cfg.defaults, arg) &&
|
|
171
|
+
!Object.hasOwn(recipeInlineDefaults, arg),
|
|
172
|
+
),
|
|
173
|
+
)
|
|
134
174
|
: RecipesReferences.isRecipeReference(cfg.template) && !recipeTemplate
|
|
135
175
|
? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
|
|
136
176
|
: Schema.getRequiredToolArgNames(requiredTemplateConfig);
|
|
@@ -200,13 +240,7 @@ export function createRuntimeToolDefinition(
|
|
|
200
240
|
{ ...(cfg.recipe?.values ?? {}), ...values },
|
|
201
241
|
ctx,
|
|
202
242
|
),
|
|
203
|
-
values:
|
|
204
|
-
ModelContext.withCurrentModelValues(
|
|
205
|
-
{ ...(cfg.recipe?.values ?? {}), ...cfg.defaults, ...values },
|
|
206
|
-
ctx,
|
|
207
|
-
),
|
|
208
|
-
cfg.argTypes,
|
|
209
|
-
),
|
|
243
|
+
values: resolveRecipeToolValues(cfg, values, ctx),
|
|
210
244
|
},
|
|
211
245
|
ctx.cwd,
|
|
212
246
|
);
|
|
@@ -221,17 +255,13 @@ export function createRuntimeToolDefinition(
|
|
|
221
255
|
};
|
|
222
256
|
}
|
|
223
257
|
if (isRecipe && recipeTemplate) {
|
|
224
|
-
const paramsWithDefaults = ModelContext.withCurrentModelValues(
|
|
225
|
-
{
|
|
226
|
-
...(cfg.recipe?.values ?? {}),
|
|
227
|
-
...cfg.defaults,
|
|
228
|
-
...(params as Record<string, unknown>),
|
|
229
|
-
},
|
|
230
|
-
ctx,
|
|
231
|
-
);
|
|
232
258
|
return await Execution.executeRegisteredTool(
|
|
233
259
|
{ ...cfg, template: recipeTemplate },
|
|
234
|
-
|
|
260
|
+
resolveRecipeToolValues(
|
|
261
|
+
cfg,
|
|
262
|
+
params as Record<string, unknown>,
|
|
263
|
+
ctx,
|
|
264
|
+
),
|
|
235
265
|
exec,
|
|
236
266
|
ctx.cwd,
|
|
237
267
|
signal,
|
package/lib/tools-spawn.ts
CHANGED
|
@@ -138,10 +138,10 @@ export function createSpawnToolDefinition<
|
|
|
138
138
|
"Optional Run identity in exact run:<id> form.",
|
|
139
139
|
),
|
|
140
140
|
file: Schema.stringSchema(
|
|
141
|
-
"Optional template
|
|
141
|
+
"Optional template Recipe reference. Supports paths, bare user/adjacent/std names, std:<name>, and skill:<skill>/<recipe>.",
|
|
142
142
|
),
|
|
143
143
|
recipe: Schema.stringSchema(
|
|
144
|
-
"Alias for file;
|
|
144
|
+
"Alias for file; qualified Skill/std or ordinary Recipe reference to spawn.",
|
|
145
145
|
),
|
|
146
146
|
template: Schema.unionSchema([
|
|
147
147
|
Schema.stringSchema("Inline command template string"),
|
package/lib/trace-projection.ts
CHANGED
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
resolveArtifactManifest,
|
|
15
15
|
type RunArtifactDeclaration,
|
|
16
16
|
} from "./runs-artifacts.ts";
|
|
17
|
+
import * as RunsControls from "./runs-controls.ts";
|
|
17
18
|
import type { RunControlRecord } from "./runs-controls.ts";
|
|
18
19
|
import { tailFile } from "./runs-status.ts";
|
|
19
|
-
import
|
|
20
|
+
import * as RunsTrace from "./runs-trace.ts";
|
|
20
21
|
import * as SessionEvidence from "./session-evidence.ts";
|
|
21
22
|
import {
|
|
22
23
|
readJsonFileResilient,
|
|
23
|
-
readJsonlFileResilient,
|
|
24
24
|
type StateReadDiagnostic,
|
|
25
25
|
} from "./state-readers.ts";
|
|
26
26
|
|
|
@@ -44,19 +44,52 @@ export interface TraceItem {
|
|
|
44
44
|
detail: unknown;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
interface OrderedTraceItem {
|
|
48
|
+
item: TraceItem;
|
|
49
|
+
ordinal: number;
|
|
50
|
+
sourceKey: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
const LIFECYCLE_PREFIXES = ["run.", "command."];
|
|
48
54
|
const SOURCE_LIMIT = 100;
|
|
55
|
+
const SOURCE_RANK: Record<TraceSource, number> = {
|
|
56
|
+
lifecycle: 6,
|
|
57
|
+
control: 5,
|
|
58
|
+
process: 4,
|
|
59
|
+
agent: 3,
|
|
60
|
+
artifact: 2,
|
|
61
|
+
runtime: 1,
|
|
62
|
+
};
|
|
49
63
|
|
|
50
64
|
function redact(value: unknown): unknown {
|
|
51
65
|
return SessionEvidence.redactSessionEvidenceValue(value);
|
|
52
66
|
}
|
|
53
67
|
|
|
54
|
-
function sourceForEvent(event: TraceEvent): TraceSource {
|
|
68
|
+
function sourceForEvent(event: RunsTrace.TraceEvent): TraceSource {
|
|
55
69
|
return LIFECYCLE_PREFIXES.some((prefix) => event.kind.startsWith(prefix))
|
|
56
70
|
? "lifecycle"
|
|
57
71
|
: "runtime";
|
|
58
72
|
}
|
|
59
73
|
|
|
74
|
+
function ordered(
|
|
75
|
+
item: TraceItem,
|
|
76
|
+
sourceKey: string,
|
|
77
|
+
ordinal: number,
|
|
78
|
+
): OrderedTraceItem {
|
|
79
|
+
return { item, ordinal, sourceKey };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function compareTraceItems(left: OrderedTraceItem, right: OrderedTraceItem): number {
|
|
83
|
+
const timestamp = right.item.ts.localeCompare(left.item.ts);
|
|
84
|
+
if (timestamp) return timestamp;
|
|
85
|
+
if (left.sourceKey === right.sourceKey) {
|
|
86
|
+
const ordinal = right.ordinal - left.ordinal;
|
|
87
|
+
if (ordinal) return ordinal;
|
|
88
|
+
}
|
|
89
|
+
return SOURCE_RANK[right.item.source] - SOURCE_RANK[left.item.source] ||
|
|
90
|
+
right.item.id.localeCompare(left.item.id);
|
|
91
|
+
}
|
|
92
|
+
|
|
60
93
|
function latestControlTimestamp(control: RunControlRecord): string {
|
|
61
94
|
return (
|
|
62
95
|
control.failed_at ??
|
|
@@ -98,125 +131,154 @@ function controlDiagnosticItems(
|
|
|
98
131
|
}));
|
|
99
132
|
}
|
|
100
133
|
|
|
101
|
-
function traceEventItems(stateDir: string):
|
|
102
|
-
const read =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
function traceEventItems(stateDir: string): OrderedTraceItem[] {
|
|
135
|
+
const read = RunsTrace.readRunTraceJournal(stateDir);
|
|
136
|
+
const retained = read.events.slice(-SOURCE_LIMIT);
|
|
137
|
+
const projected = retained.map(({ event, ordinal }) => ordered({
|
|
138
|
+
id: event.id,
|
|
139
|
+
ts: event.ts,
|
|
140
|
+
source: sourceForEvent(event),
|
|
141
|
+
kind: event.kind,
|
|
142
|
+
summary: event.summary ?? event.kind,
|
|
143
|
+
...(event.level ? { level: event.level } : {}),
|
|
144
|
+
detail: redact({
|
|
145
|
+
...(event.attention ? { attention: event.attention } : {}),
|
|
146
|
+
...(event.data !== undefined ? { data: event.data } : {}),
|
|
147
|
+
}),
|
|
148
|
+
}, "trace", ordinal));
|
|
149
|
+
const diagnostics = diagnosticItems(read.diagnostics).map((item, index) =>
|
|
150
|
+
ordered(item, "trace", read.events.length + index + 1)
|
|
151
|
+
);
|
|
152
|
+
const hasMarker = read.events.some(
|
|
153
|
+
({ event }) => event.kind === "runtime.trace_compacted",
|
|
154
|
+
);
|
|
155
|
+
if (read.omittedPrefixBytes > 0 && !hasMarker) {
|
|
156
|
+
diagnostics.push(ordered({
|
|
157
|
+
id: "runtime:trace-history-incomplete",
|
|
158
|
+
ts: new Date(0).toISOString(),
|
|
159
|
+
source: "runtime",
|
|
160
|
+
kind: "runtime.trace_history_incomplete",
|
|
161
|
+
summary: "Trace history is incomplete",
|
|
162
|
+
level: "warning",
|
|
163
|
+
detail: {
|
|
164
|
+
omitted_prefix_bytes: read.omittedPrefixBytes,
|
|
165
|
+
read_bytes: read.readBytes,
|
|
166
|
+
retained_events: read.events.length,
|
|
167
|
+
},
|
|
168
|
+
}, "trace", 0));
|
|
169
|
+
}
|
|
170
|
+
return [...projected, ...diagnostics];
|
|
118
171
|
}
|
|
119
172
|
|
|
120
|
-
function controlItems(stateDir: string):
|
|
121
|
-
const read =
|
|
122
|
-
|
|
123
|
-
)
|
|
173
|
+
function controlItems(stateDir: string): OrderedTraceItem[] {
|
|
174
|
+
const read = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
175
|
+
const start = Math.max(0, read.records.length - SOURCE_LIMIT);
|
|
176
|
+
const projected = read.records.slice(start).map((control, index) => {
|
|
177
|
+
const value = ControlProjection.projectRunControl(control as RunControlRecord);
|
|
178
|
+
return ordered({
|
|
179
|
+
id: `control:${value.id}`,
|
|
180
|
+
ts: latestControlTimestamp(value),
|
|
181
|
+
source: "control",
|
|
182
|
+
kind: `control.${value.status}`,
|
|
183
|
+
summary: `${value.action} ${value.status}`,
|
|
184
|
+
...(value.status === "failed" ? { level: "error" as const } : {}),
|
|
185
|
+
detail: value,
|
|
186
|
+
}, "control", start + index + 1);
|
|
187
|
+
});
|
|
124
188
|
return [
|
|
125
|
-
...
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
ts: latestControlTimestamp(projected),
|
|
130
|
-
source: "control" as const,
|
|
131
|
-
kind: `control.${projected.status}`,
|
|
132
|
-
summary: `${projected.action} ${projected.status}`,
|
|
133
|
-
...(projected.status === "failed" ? { level: "error" as const } : {}),
|
|
134
|
-
detail: projected,
|
|
135
|
-
};
|
|
136
|
-
}),
|
|
137
|
-
...controlDiagnosticItems(read.diagnostics),
|
|
189
|
+
...projected,
|
|
190
|
+
...controlDiagnosticItems(read.diagnostics).map((item, index) =>
|
|
191
|
+
ordered(item, "control", read.records.length + index + 1)
|
|
192
|
+
),
|
|
138
193
|
];
|
|
139
194
|
}
|
|
140
195
|
|
|
141
|
-
function agentItems(stateDir: string):
|
|
196
|
+
function agentItems(stateDir: string): OrderedTraceItem[] {
|
|
142
197
|
return ExecutionSessions.readExecutionTurns(stateDir)
|
|
143
198
|
.slice(-SOURCE_LIMIT)
|
|
144
199
|
.map((turn) => {
|
|
145
200
|
const text = turn.assistantText ?? turn.userText ?? "Agent turn";
|
|
146
201
|
const summary = text.replaceAll(/\s+/g, " ").trim().slice(0, 160);
|
|
147
|
-
return {
|
|
202
|
+
return ordered({
|
|
148
203
|
id: `agent:${turn.commandId}:${turn.sessionFile}:${turn.index}`,
|
|
149
204
|
ts: turn.timestamp ?? new Date(0).toISOString(),
|
|
150
|
-
source: "agent"
|
|
205
|
+
source: "agent",
|
|
151
206
|
kind: "agent.turn",
|
|
152
207
|
summary: summary || "Agent turn",
|
|
153
208
|
...(turn.error ? { level: "error" as const } : {}),
|
|
154
209
|
detail: redact(turn),
|
|
155
|
-
};
|
|
210
|
+
}, "agent", turn.index);
|
|
156
211
|
});
|
|
157
212
|
}
|
|
158
213
|
|
|
159
|
-
function processItems(stateDir: string):
|
|
214
|
+
function processItems(stateDir: string): OrderedTraceItem[] {
|
|
160
215
|
return ["stdout", "stderr"].flatMap((stream) => {
|
|
161
216
|
const path = join(stateDir, `${stream}.log`);
|
|
162
217
|
if (!existsSync(path)) return [];
|
|
163
218
|
const stat = statSync(path);
|
|
164
219
|
const tail = tailFile(path, Limits.DEFAULT_INSPECT_LINES);
|
|
165
220
|
if (!tail && stat.size === 0) return [];
|
|
166
|
-
return [{
|
|
221
|
+
return [ordered({
|
|
167
222
|
id: `process:${stream}`,
|
|
168
223
|
ts: stat.mtime.toISOString(),
|
|
169
|
-
source: "process"
|
|
224
|
+
source: "process",
|
|
170
225
|
kind: `process.${stream}`,
|
|
171
226
|
summary: `${stream} (${stat.size} bytes)`,
|
|
172
227
|
...(stream === "stderr" ? { level: "warning" as const } : {}),
|
|
173
228
|
detail: redact({ bytes: stat.size, path, tail }),
|
|
174
|
-
}];
|
|
229
|
+
}, `process:${stream}`, 1)];
|
|
175
230
|
});
|
|
176
231
|
}
|
|
177
232
|
|
|
178
|
-
function resultItems(stateDir: string):
|
|
233
|
+
function resultItems(stateDir: string): OrderedTraceItem[] {
|
|
179
234
|
const path = join(stateDir, "result.json");
|
|
180
235
|
const read = readJsonFileResilient<Record<string, unknown>>(path, {});
|
|
181
|
-
if (Object.keys(read.value).length === 0)
|
|
236
|
+
if (Object.keys(read.value).length === 0) {
|
|
237
|
+
return diagnosticItems(read.diagnostics).map((item, index) =>
|
|
238
|
+
ordered(item, "process:result", index)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
182
241
|
const stat = statSync(path);
|
|
183
242
|
const failed = Number(read.value.code ?? 0) !== 0;
|
|
184
|
-
return [{
|
|
243
|
+
return [ordered({
|
|
185
244
|
id: "process:result",
|
|
186
|
-
ts:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
: stat.mtime.toISOString(),
|
|
245
|
+
ts: typeof read.value.completedAt === "string"
|
|
246
|
+
? read.value.completedAt
|
|
247
|
+
: stat.mtime.toISOString(),
|
|
190
248
|
source: "process",
|
|
191
249
|
kind: failed ? "process.failed" : "process.result",
|
|
192
250
|
summary: failed ? "Run process failed" : "Run process completed",
|
|
193
251
|
...(failed ? { level: "error" as const } : {}),
|
|
194
252
|
detail: redact(read.value),
|
|
195
|
-
}, ...diagnosticItems(read.diagnostics)
|
|
253
|
+
}, "process:result", 1), ...diagnosticItems(read.diagnostics).map(
|
|
254
|
+
(item, index) => ordered(item, "process:result", index + 2),
|
|
255
|
+
)];
|
|
196
256
|
}
|
|
197
257
|
|
|
198
258
|
function artifactItems(
|
|
199
259
|
stateDir: string,
|
|
200
260
|
declarations: Record<string, RunArtifactDeclaration> | undefined,
|
|
201
|
-
):
|
|
261
|
+
): OrderedTraceItem[] {
|
|
202
262
|
const manifest = resolveArtifactManifest(declarations);
|
|
203
263
|
if (!manifest) return [];
|
|
204
|
-
return Object.entries(manifest).slice(0, SOURCE_LIMIT).map(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
264
|
+
return Object.entries(manifest).slice(0, SOURCE_LIMIT).map(
|
|
265
|
+
([name, artifact], index) => ordered({
|
|
266
|
+
id: `artifact:${name}`,
|
|
267
|
+
ts: artifact.exists && existsSync(artifact.path)
|
|
268
|
+
? statSync(artifact.path).mtime.toISOString()
|
|
269
|
+
: new Date(0).toISOString(),
|
|
270
|
+
source: "artifact",
|
|
271
|
+
kind: artifact.exists ? "artifact.ready" : "artifact.missing",
|
|
272
|
+
summary: `${name}: ${artifact.exists ? "ready" : "missing"}`,
|
|
273
|
+
...(artifact.required && !artifact.exists ? { level: "warning" as const } : {}),
|
|
274
|
+
detail: redact({
|
|
275
|
+
exists: artifact.exists,
|
|
276
|
+
path: relative(stateDir, artifact.path).replaceAll("\\", "/"),
|
|
277
|
+
required: artifact.required,
|
|
278
|
+
...(artifact.size !== undefined ? { size: artifact.size } : {}),
|
|
279
|
+
}),
|
|
280
|
+
}, "artifact", index + 1),
|
|
281
|
+
);
|
|
220
282
|
}
|
|
221
283
|
|
|
222
284
|
export function projectRunTrace(
|
|
@@ -236,9 +298,9 @@ export function projectRunTrace(
|
|
|
236
298
|
...resultItems(stateDir),
|
|
237
299
|
...artifactItems(stateDir, options.artifacts),
|
|
238
300
|
]
|
|
239
|
-
.filter((item) => source === "all" || item.source === source)
|
|
240
|
-
.sort(
|
|
241
|
-
|
|
242
|
-
)
|
|
243
|
-
|
|
301
|
+
.filter(({ item }) => source === "all" || item.source === source)
|
|
302
|
+
.sort(compareTraceItems);
|
|
303
|
+
return items
|
|
304
|
+
.slice(0, Math.max(1, Math.min(options.limit ?? 100, 200)))
|
|
305
|
+
.map(({ item }) => item);
|
|
244
306
|
}
|