@llblab/pi-actors 0.43.0 → 0.44.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 +16 -10
- package/CHANGELOG.md +425 -536
- package/README.md +13 -11
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +2 -1
- package/dist/lib/async-runs.js +24 -34
- package/dist/lib/automatic-review-runtime.d.ts +1 -1
- package/dist/lib/automatic-review-runtime.js +5 -5
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/command-templates.js +38 -4
- package/dist/lib/control-projection.d.ts +20 -0
- package/dist/lib/control-projection.js +66 -0
- package/dist/lib/control.d.ts +3 -0
- package/dist/lib/control.js +27 -14
- package/dist/lib/draft-sleep.js +3 -3
- package/dist/lib/file-state.d.ts +4 -1
- package/dist/lib/file-state.js +118 -44
- package/dist/lib/inspector-overlay.d.ts +2 -0
- package/dist/lib/inspector-overlay.js +124 -69
- package/dist/lib/limits.d.ts +15 -3
- package/dist/lib/limits.js +15 -3
- package/dist/lib/observability.d.ts +4 -2
- package/dist/lib/observability.js +43 -36
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/recipe-control.js +6 -2
- package/dist/lib/review-control.d.ts +1 -1
- package/dist/lib/review-control.js +4 -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-control-delivery.d.ts +8 -1
- package/dist/lib/runs-control-delivery.js +38 -15
- package/dist/lib/runs-controls.d.ts +8 -4
- package/dist/lib/runs-controls.js +189 -50
- package/dist/lib/runs-retention.js +27 -14
- package/dist/lib/runs-trace.d.ts +26 -2
- package/dist/lib/runs-trace.js +411 -18
- package/dist/lib/runtime-identity.d.ts +7 -0
- package/dist/lib/runtime-identity.js +35 -0
- package/dist/lib/runtime-triage.d.ts +29 -0
- package/dist/lib/runtime-triage.js +60 -0
- package/dist/lib/tool-review-scheduler.js +7 -7
- package/dist/lib/tools-inspect.js +91 -18
- package/dist/lib/tools-message.d.ts +1 -2
- package/dist/lib/tools-message.js +6 -6
- package/dist/lib/tools-response.d.ts +0 -1
- package/dist/lib/tools-response.js +0 -9
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +1 -1
- package/dist/lib/trace-projection.js +107 -41
- package/dist/scripts/conformance.mjs +5 -0
- package/dist/scripts/locker.mjs +40 -90
- package/dist/scripts/music-player.mjs +48 -142
- package/dist/scripts/release-gates.mjs +56 -3
- package/dist/scripts/validate-recipe.mjs +5 -4
- package/dist/skills/actors/SKILL.md +17 -11
- package/dist/skills/swarm/SKILL.md +2 -4
- package/docs/README.md +1 -4
- package/docs/actor-inspector.md +6 -5
- package/docs/async-runs.md +11 -9
- package/docs/command-templates.md +6 -116
- package/docs/recipe-library.md +4 -6
- package/docs/releasing.md +28 -0
- package/docs/template-recipes.md +1 -1
- package/docs/tool-registry.md +2 -2
- package/index.ts +1 -1
- package/lib/async-runs.ts +26 -50
- package/lib/automatic-review-runtime.ts +7 -7
- package/lib/command-templates.ts +44 -4
- package/lib/control-projection.ts +105 -0
- package/lib/control.ts +33 -18
- package/lib/draft-sleep.ts +3 -3
- package/lib/file-state.ts +91 -63
- package/lib/inspector-overlay.ts +108 -61
- package/lib/limits.ts +15 -3
- package/lib/observability.ts +55 -57
- package/lib/prompts.ts +1 -1
- package/lib/recipe-control.ts +9 -2
- package/lib/review-control.ts +4 -5
- package/lib/run-evidence-policy.ts +242 -0
- package/lib/run-ui-runtime.ts +2 -0
- package/lib/runs-control-delivery.ts +45 -17
- package/lib/runs-controls.ts +180 -102
- package/lib/runs-retention.ts +28 -20
- package/lib/runs-trace.ts +499 -20
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-triage.ts +106 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +94 -20
- package/lib/tools-message.ts +7 -8
- package/lib/tools-response.ts +0 -12
- package/lib/tools.ts +4 -4
- package/lib/trace-projection.ts +156 -71
- package/package.json +1 -1
- package/scripts/conformance.mjs +5 -0
- package/scripts/locker.mjs +40 -90
- package/scripts/music-player.mjs +48 -142
- package/scripts/release-gates.mjs +56 -3
- package/scripts/validate-recipe.mjs +5 -4
- package/skills/actors/SKILL.md +17 -11
- package/skills/swarm/SKILL.md +2 -4
- package/dist/lib/runtime-notifier.d.ts +0 -48
- package/dist/lib/runtime-notifier.js +0 -138
- package/docs/0.43-baseline.md +0 -44
- package/docs/actors-deep-reference.md +0 -108
- package/docs/component-recipes.md +0 -45
- package/docs/task-first-recipes.md +0 -261
- package/lib/runtime-notifier.ts +0 -211
|
@@ -3,16 +3,21 @@
|
|
|
3
3
|
* Zones: Run Recipe/Trace/Control views and runtime/recipe/tool diagnostics
|
|
4
4
|
* Owns exact inspect target/view dispatch; source projection stays in domain modules.
|
|
5
5
|
*/
|
|
6
|
+
import { statSync } from "node:fs";
|
|
6
7
|
import { join } from "node:path";
|
|
7
8
|
import * as AsyncRuns from "./async-runs.js";
|
|
9
|
+
import * as ControlProjection from "./control-projection.js";
|
|
8
10
|
import * as Inspector from "./inspector.js";
|
|
9
11
|
import * as Limits from "./limits.js";
|
|
12
|
+
import { computeRunControlCapacity } from "./run-evidence-policy.js";
|
|
10
13
|
import * as Paths from "./paths.js";
|
|
11
14
|
import * as RecipesDiscovery from "./recipes-discovery.js";
|
|
12
15
|
import * as ReviewDiagnostics from "./review-diagnostics.js";
|
|
13
16
|
import * as RunsControls from "./runs-controls.js";
|
|
14
17
|
import * as RunControlDelivery from "./runs-control-delivery.js";
|
|
15
18
|
import * as RunsTrace from "./runs-trace.js";
|
|
19
|
+
import * as RuntimeIdentity from "./runtime-identity.js";
|
|
20
|
+
import * as RuntimeTriage from "./runtime-triage.js";
|
|
16
21
|
import * as Schema from "./schema.js";
|
|
17
22
|
import * as ToolsAccess from "./tools-access.js";
|
|
18
23
|
import * as ToolsResponse from "./tools-response.js";
|
|
@@ -23,7 +28,8 @@ function runtimeStatus() {
|
|
|
23
28
|
return {
|
|
24
29
|
automatic_review: process.env.PI_ACTORS_AUTOMATIC_REVIEW !== "off",
|
|
25
30
|
run_root: Paths.getRunStateRoot(),
|
|
26
|
-
|
|
31
|
+
state_schema: RuntimeIdentity.RUN_STATE_SCHEMA,
|
|
32
|
+
version: RuntimeIdentity.getPackageVersion(),
|
|
27
33
|
};
|
|
28
34
|
}
|
|
29
35
|
function runtimeRuns(ctx, deps, status) {
|
|
@@ -49,14 +55,47 @@ function runtimeTriage(ctx, deps) {
|
|
|
49
55
|
const inventory = runtimeRuns(ctx, deps, undefined);
|
|
50
56
|
const runs = inventory.runs;
|
|
51
57
|
const failed = runs.filter((run) => run.status === "failed");
|
|
52
|
-
const
|
|
58
|
+
const pendingControls = [];
|
|
59
|
+
const staleControls = [];
|
|
60
|
+
const backpressuredRuns = [];
|
|
61
|
+
const controlDiagnostics = [];
|
|
62
|
+
const traceDiagnostics = [];
|
|
63
|
+
const nowMs = Date.now();
|
|
64
|
+
for (const run of runs) {
|
|
53
65
|
const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
|
|
54
66
|
if (!stateDir)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
continue;
|
|
68
|
+
const runId = String(run.run ?? "unknown");
|
|
69
|
+
const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
70
|
+
const capacity = computeRunControlCapacity(journal.records);
|
|
71
|
+
controlDiagnostics.push(...journal.diagnostics.map((diagnostic) => ({
|
|
72
|
+
...(diagnostic.line === undefined ? {} : { line: diagnostic.line }),
|
|
73
|
+
reason: diagnostic.line === undefined ? "unreadable_control_journal" : "invalid_control_json", run: runId,
|
|
74
|
+
})));
|
|
75
|
+
if (capacity.backpressured)
|
|
76
|
+
backpressuredRuns.push({
|
|
77
|
+
run: runId, pending: capacity.pending, pending_limit: capacity.limit,
|
|
78
|
+
journal_bytes: statSync(RunsControls.runControlsFile(stateDir)).size,
|
|
79
|
+
});
|
|
80
|
+
const traceSummary = RunsTrace.summarizeRunTraceJournal(RunsTrace.readRunTraceJournal(stateDir));
|
|
81
|
+
if (!traceSummary.history_complete)
|
|
82
|
+
traceDiagnostics.push({ run: runId, ...traceSummary });
|
|
83
|
+
for (const control of journal.records) {
|
|
84
|
+
const classified = RuntimeTriage.classifyRuntimeControl({
|
|
85
|
+
run: runId,
|
|
86
|
+
...(typeof run.run_instance_id === "string"
|
|
87
|
+
? { runInstanceId: run.run_instance_id }
|
|
88
|
+
: {}),
|
|
89
|
+
status: String(run.status ?? "unknown"),
|
|
90
|
+
}, control, nowMs);
|
|
91
|
+
if (classified.pending)
|
|
92
|
+
pendingControls.push(classified.pending);
|
|
93
|
+
if (classified.stale)
|
|
94
|
+
staleControls.push(classified.stale);
|
|
95
|
+
if (classified.diagnostic)
|
|
96
|
+
controlDiagnostics.push({ ...classified.diagnostic });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
60
99
|
const attentionEvents = runs.flatMap((run) => {
|
|
61
100
|
const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
|
|
62
101
|
if (!stateDir)
|
|
@@ -72,16 +111,26 @@ function runtimeTriage(ctx, deps) {
|
|
|
72
111
|
ts: event.ts,
|
|
73
112
|
}));
|
|
74
113
|
});
|
|
114
|
+
const boundedStaleControls = staleControls.slice(0, 40);
|
|
115
|
+
const staleKeys = new Set(boundedStaleControls.map((control) => `${control.run}\0${control.run_instance_id}\0${control.id}`));
|
|
116
|
+
const boundedPendingControls = [
|
|
117
|
+
...boundedStaleControls,
|
|
118
|
+
...pendingControls.filter((control) => !staleKeys.has(`${control.run}\0${control.run_instance_id}\0${control.id}`)),
|
|
119
|
+
].slice(0, 40);
|
|
75
120
|
return {
|
|
76
121
|
...inventory,
|
|
77
122
|
failed_runs: failed,
|
|
78
|
-
|
|
123
|
+
pending_control_count: pendingControls.length,
|
|
124
|
+
pending_controls: boundedPendingControls,
|
|
125
|
+
stale_control_count: staleControls.length,
|
|
126
|
+
stale_controls: boundedStaleControls,
|
|
127
|
+
backpressured_run_count: backpressuredRuns.length, backpressured_runs: backpressuredRuns.slice(0, 40),
|
|
128
|
+
control_diagnostic_count: controlDiagnostics.length, control_diagnostics: controlDiagnostics.slice(0, 40),
|
|
129
|
+
trace_diagnostic_count: traceDiagnostics.length, trace_diagnostics: traceDiagnostics.slice(0, 40),
|
|
79
130
|
attention_events: attentionEvents.slice(-40).reverse(),
|
|
80
131
|
next_actions: [
|
|
81
132
|
...(failed.length ? ["inspect target=runtime view=runs status=failed"] : []),
|
|
82
|
-
...(staleControls.
|
|
83
|
-
? staleControls.slice(0, 3).map((entry) => `inspect target=run:${String(entry.run)} view=control`)
|
|
84
|
-
: []),
|
|
133
|
+
...new Set([...staleControls, ...backpressuredRuns].slice(0, 3).map((entry) => `inspect target=run:${entry.run} view=control`)),
|
|
85
134
|
...(attentionEvents.length
|
|
86
135
|
? attentionEvents.slice(-3).map((entry) => `inspect target=run:${String(entry.run)} view=trace source=runtime`)
|
|
87
136
|
: []),
|
|
@@ -93,9 +142,9 @@ function compactRuntime(view, details) {
|
|
|
93
142
|
return `\nruntime runs=${details.runs?.length ?? 0}`;
|
|
94
143
|
}
|
|
95
144
|
if (view === "triage") {
|
|
96
|
-
return `\nruntime failed=${details.failed_runs?.length ?? 0} stale_controls=${details.
|
|
145
|
+
return `\nruntime failed=${details.failed_runs?.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?.length ?? 0}`;
|
|
97
146
|
}
|
|
98
|
-
return `\nruntime version=${String(details.version
|
|
147
|
+
return `\nruntime version=${String(details.version)} state_schema=${String(details.state_schema)} automatic_review=${String(details.automatic_review)}`;
|
|
99
148
|
}
|
|
100
149
|
function inspectRuntime(view, input, ctx, deps) {
|
|
101
150
|
if (view === "status")
|
|
@@ -161,8 +210,8 @@ function inspectRun(run, view, input, ctx, deps) {
|
|
|
161
210
|
throw new Error(`unsupported Trace source: ${source}`);
|
|
162
211
|
}
|
|
163
212
|
return {
|
|
164
|
-
run,
|
|
165
|
-
|
|
213
|
+
run, source,
|
|
214
|
+
summary: RunsTrace.summarizeRunTraceJournal(RunsTrace.readRunTraceJournal(stateDir)),
|
|
166
215
|
items: TraceProjection.projectRunTrace(stateDir, {
|
|
167
216
|
artifacts: status.artifacts,
|
|
168
217
|
limit: Number(input.lines || Limits.DEFAULT_INSPECT_LINES),
|
|
@@ -171,18 +220,42 @@ function inspectRun(run, view, input, ctx, deps) {
|
|
|
171
220
|
};
|
|
172
221
|
}
|
|
173
222
|
const runInstanceId = String(status.run_instance_id ?? "");
|
|
223
|
+
const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
224
|
+
const capacity = computeRunControlCapacity(journal.records);
|
|
225
|
+
const structuralDiagnostics = journal.records.flatMap((value) => {
|
|
226
|
+
const classified = RuntimeTriage.classifyRuntimeControl({ run,
|
|
227
|
+
runInstanceId: runInstanceId || undefined, status: String(status.status ?? "unknown") }, value, Date.now());
|
|
228
|
+
return classified.diagnostic ? [classified.diagnostic.reason] : [];
|
|
229
|
+
});
|
|
230
|
+
let journalBytes = 0;
|
|
231
|
+
try {
|
|
232
|
+
journalBytes = statSync(RunsControls.runControlsFile(stateDir)).size;
|
|
233
|
+
}
|
|
234
|
+
catch { }
|
|
235
|
+
const diagnostics = [...journal.diagnostics.map((item) => ({
|
|
236
|
+
...(item.line === undefined ? {} : { line: item.line }),
|
|
237
|
+
reason: item.line === undefined ? "unreadable_control_journal" : "invalid_control_json",
|
|
238
|
+
})), ...structuralDiagnostics.map((reason) => ({ reason }))].slice(0, 40);
|
|
239
|
+
const nowMs = Date.now();
|
|
240
|
+
const stalePending = journal.records.reduce((count, control) => count + Number(Boolean(RuntimeTriage.classifyRuntimeControl({ run, runInstanceId: runInstanceId || undefined,
|
|
241
|
+
status: String(status.status ?? "unknown") }, control, nowMs).stale)), 0);
|
|
174
242
|
return {
|
|
175
243
|
run,
|
|
176
244
|
run_instance_id: runInstanceId,
|
|
177
245
|
status: status.status,
|
|
246
|
+
pending: capacity.pending, pending_limit: capacity.limit,
|
|
247
|
+
available: capacity.available, backpressured: capacity.backpressured,
|
|
248
|
+
journal_bytes: journalBytes, journal_limit: Limits.RUN_CONTROL_JOURNAL_MAX_BYTES,
|
|
249
|
+
...(capacity.oldest_pending_at ? { oldest_pending_at: capacity.oldest_pending_at } : {}),
|
|
250
|
+
stale_pending: stalePending,
|
|
251
|
+
diagnostics,
|
|
178
252
|
actor_actions: Array.isArray(status.control) ? status.control : [],
|
|
179
253
|
runtime_actions: status.status === "running" ? ["kill"] : ["archive", "prune"],
|
|
180
254
|
endpoint: runInstanceId
|
|
181
255
|
? RunControlDelivery.readRunControlEndpoint(stateDir, runInstanceId)
|
|
182
256
|
: undefined,
|
|
183
|
-
recent_controls:
|
|
184
|
-
.
|
|
185
|
-
.reverse(),
|
|
257
|
+
recent_controls: journal.records.slice(-Math.max(1, Number(input.lines || 20))).reverse()
|
|
258
|
+
.map((control) => ControlProjection.projectRunControl(control)),
|
|
186
259
|
};
|
|
187
260
|
}
|
|
188
261
|
function inspectTool(name, view, deps) {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Owns public Control execution; journaling, delivery, and lifecycle mutation stay in Run domains.
|
|
5
5
|
*/
|
|
6
6
|
export interface ControlToolDeps {
|
|
7
|
-
|
|
8
|
-
handleRuntimeMessage?: (action: string, input: unknown) => Record<string, unknown>;
|
|
7
|
+
handleRuntimeControl?: (action: string, input: unknown) => Record<string, unknown>;
|
|
9
8
|
}
|
|
10
9
|
export declare function createControlToolDefinition<TContext = unknown>(deps?: ControlToolDeps): any;
|
|
@@ -45,11 +45,11 @@ export function createControlToolDefinition(deps = {}) {
|
|
|
45
45
|
description: "Apply one Control action to an existing Run or the pi-actors runtime.",
|
|
46
46
|
parameters: Schema.objectSchema({
|
|
47
47
|
target: Schema.stringSchema("Run target run:<id>, or literal runtime."),
|
|
48
|
-
action: Schema.stringSchema("Lowercase
|
|
48
|
+
action: Schema.stringSchema("Lowercase ASCII Control action, at most 64 characters."),
|
|
49
49
|
input: Schema.unionSchema([
|
|
50
|
-
Schema.stringSchema("Optional Control input string."),
|
|
51
|
-
Schema.looseObjectSchema("Optional structured Control input."),
|
|
52
|
-
Schema.arraySchema("Optional structured Control input array."),
|
|
50
|
+
Schema.stringSchema("Optional Control input string; serialized JSON must fit 380 bytes."),
|
|
51
|
+
Schema.looseObjectSchema("Optional structured Control input; serialized JSON must fit 380 bytes."),
|
|
52
|
+
Schema.arraySchema("Optional structured Control input array; serialized JSON must fit 380 bytes."),
|
|
53
53
|
]),
|
|
54
54
|
verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
|
|
55
55
|
}, ["target", "action"]),
|
|
@@ -60,10 +60,10 @@ export function createControlToolDefinition(deps = {}) {
|
|
|
60
60
|
if (request.action !== "review.retry" && request.action !== "review.reset") {
|
|
61
61
|
throw new Error(`unsupported runtime Control action: ${request.action}; use review.retry or review.reset`);
|
|
62
62
|
}
|
|
63
|
-
if (!deps.
|
|
63
|
+
if (!deps.handleRuntimeControl) {
|
|
64
64
|
throw new Error("runtime Control is unavailable");
|
|
65
65
|
}
|
|
66
|
-
result = deps.
|
|
66
|
+
result = deps.handleRuntimeControl(request.action, request.input);
|
|
67
67
|
}
|
|
68
68
|
else {
|
|
69
69
|
const run = request.target.slice(4);
|
|
@@ -12,7 +12,6 @@ export declare function compactPreview(value: unknown, maxLength?: number): stri
|
|
|
12
12
|
export declare function compactNextActions(actions: string[]): string;
|
|
13
13
|
export declare function runNextActions(run: unknown): string[];
|
|
14
14
|
export declare function compactAsyncRunStatus(value: unknown): string;
|
|
15
|
-
export declare function artifactNextActions(run: unknown, artifacts: Record<string, unknown>): string[];
|
|
16
15
|
export declare function maybeJsonText(value: unknown, verbose: boolean | undefined, compact: string): string;
|
|
17
16
|
export declare function compactRecipeImports(summary: Record<string, unknown>): string;
|
|
18
17
|
export declare function compactRecipeDoctor(summary: Record<string, unknown>): string;
|
|
@@ -121,15 +121,6 @@ export function compactAsyncRunStatus(value) {
|
|
|
121
121
|
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
122
122
|
return `\n${tokens.join(" ")}`;
|
|
123
123
|
}
|
|
124
|
-
export function artifactNextActions(run, artifacts) {
|
|
125
|
-
const id = String(run ?? "").trim();
|
|
126
|
-
if (!id || Object.keys(artifacts).length === 0)
|
|
127
|
-
return [];
|
|
128
|
-
return [
|
|
129
|
-
`inspect target=run:${id} view=artifacts verbose=true`,
|
|
130
|
-
`inspect target=run:${id} view=messages`,
|
|
131
|
-
];
|
|
132
|
-
}
|
|
133
124
|
export function maybeJsonText(value, verbose, compact) {
|
|
134
125
|
return verbose ? jsonText(value) : compact;
|
|
135
126
|
}
|
package/dist/lib/tools.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface CoreActorToolDefinitionDeps<TContext extends RuntimeToolContext
|
|
|
15
15
|
configPath: string;
|
|
16
16
|
getActiveTools: () => string[];
|
|
17
17
|
getRuntimeTool: (name: string) => unknown;
|
|
18
|
-
|
|
18
|
+
handleRuntimeControl?: (action: string, input: unknown) => Record<string, unknown>;
|
|
19
19
|
registryRuntime: Pick<RegisterToolRuntimeDeps<TContext>, "getToolNameBlocker" | "getTools" | "notify" | "registerRuntimeTool">;
|
|
20
20
|
setActiveTools: (toolNames: string[]) => void;
|
|
21
21
|
}
|
package/dist/lib/tools.js
CHANGED
|
@@ -37,7 +37,7 @@ export function createCoreActorToolDefinitions(deps) {
|
|
|
37
37
|
}),
|
|
38
38
|
ToolsSpawn.createSpawnToolDefinition(),
|
|
39
39
|
ToolsMessage.createControlToolDefinition({
|
|
40
|
-
|
|
40
|
+
handleRuntimeControl: deps.handleRuntimeControl,
|
|
41
41
|
}),
|
|
42
42
|
ToolsInspect.createInspectToolDefinition({
|
|
43
43
|
getTool: (name) => deps.getRuntimeTool(name),
|
|
@@ -5,14 +5,25 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, statSync } from "node:fs";
|
|
7
7
|
import { join, relative } from "node:path";
|
|
8
|
+
import * as ControlProjection from "./control-projection.js";
|
|
8
9
|
import * as ExecutionSessions from "./execution-sessions.js";
|
|
9
10
|
import * as Limits from "./limits.js";
|
|
10
11
|
import { resolveArtifactManifest, } from "./runs-artifacts.js";
|
|
12
|
+
import * as RunsControls from "./runs-controls.js";
|
|
11
13
|
import { tailFile } from "./runs-status.js";
|
|
14
|
+
import * as RunsTrace from "./runs-trace.js";
|
|
12
15
|
import * as SessionEvidence from "./session-evidence.js";
|
|
13
|
-
import { readJsonFileResilient,
|
|
16
|
+
import { readJsonFileResilient, } from "./state-readers.js";
|
|
14
17
|
const LIFECYCLE_PREFIXES = ["run.", "command."];
|
|
15
18
|
const SOURCE_LIMIT = 100;
|
|
19
|
+
const SOURCE_RANK = {
|
|
20
|
+
lifecycle: 6,
|
|
21
|
+
control: 5,
|
|
22
|
+
process: 4,
|
|
23
|
+
agent: 3,
|
|
24
|
+
artifact: 2,
|
|
25
|
+
runtime: 1,
|
|
26
|
+
};
|
|
16
27
|
function redact(value) {
|
|
17
28
|
return SessionEvidence.redactSessionEvidenceValue(value);
|
|
18
29
|
}
|
|
@@ -21,6 +32,21 @@ function sourceForEvent(event) {
|
|
|
21
32
|
? "lifecycle"
|
|
22
33
|
: "runtime";
|
|
23
34
|
}
|
|
35
|
+
function ordered(item, sourceKey, ordinal) {
|
|
36
|
+
return { item, ordinal, sourceKey };
|
|
37
|
+
}
|
|
38
|
+
function compareTraceItems(left, right) {
|
|
39
|
+
const timestamp = right.item.ts.localeCompare(left.item.ts);
|
|
40
|
+
if (timestamp)
|
|
41
|
+
return timestamp;
|
|
42
|
+
if (left.sourceKey === right.sourceKey) {
|
|
43
|
+
const ordinal = right.ordinal - left.ordinal;
|
|
44
|
+
if (ordinal)
|
|
45
|
+
return ordinal;
|
|
46
|
+
}
|
|
47
|
+
return SOURCE_RANK[right.item.source] - SOURCE_RANK[left.item.source] ||
|
|
48
|
+
right.item.id.localeCompare(left.item.id);
|
|
49
|
+
}
|
|
24
50
|
function latestControlTimestamp(control) {
|
|
25
51
|
return (control.failed_at ??
|
|
26
52
|
control.handled_at ??
|
|
@@ -39,37 +65,74 @@ function diagnosticItems(diagnostics) {
|
|
|
39
65
|
detail: redact(diagnostic),
|
|
40
66
|
}));
|
|
41
67
|
}
|
|
68
|
+
function controlDiagnosticItems(diagnostics) {
|
|
69
|
+
return diagnostics.slice(-SOURCE_LIMIT).map((diagnostic, index) => ({
|
|
70
|
+
id: `control-diagnostic:${diagnostic.line ?? 0}:${index}`,
|
|
71
|
+
ts: new Date(0).toISOString(),
|
|
72
|
+
source: "runtime",
|
|
73
|
+
kind: "state.read_error",
|
|
74
|
+
summary: "Malformed or unreadable Control evidence",
|
|
75
|
+
level: "warning",
|
|
76
|
+
detail: {
|
|
77
|
+
...(diagnostic.line === undefined ? {} : { line: diagnostic.line }),
|
|
78
|
+
reason: diagnostic.line === undefined
|
|
79
|
+
? "unreadable_control_journal"
|
|
80
|
+
: "invalid_control_json",
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
42
84
|
function traceEventItems(stateDir) {
|
|
43
|
-
const read =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
85
|
+
const read = RunsTrace.readRunTraceJournal(stateDir);
|
|
86
|
+
const retained = read.events.slice(-SOURCE_LIMIT);
|
|
87
|
+
const projected = retained.map(({ event, ordinal }) => ordered({
|
|
88
|
+
id: event.id,
|
|
89
|
+
ts: event.ts,
|
|
90
|
+
source: sourceForEvent(event),
|
|
91
|
+
kind: event.kind,
|
|
92
|
+
summary: event.summary ?? event.kind,
|
|
93
|
+
...(event.level ? { level: event.level } : {}),
|
|
94
|
+
detail: redact({
|
|
95
|
+
...(event.attention ? { attention: event.attention } : {}),
|
|
96
|
+
...(event.data !== undefined ? { data: event.data } : {}),
|
|
97
|
+
}),
|
|
98
|
+
}, "trace", ordinal));
|
|
99
|
+
const diagnostics = diagnosticItems(read.diagnostics).map((item, index) => ordered(item, "trace", read.events.length + index + 1));
|
|
100
|
+
const hasMarker = read.events.some(({ event }) => event.kind === "runtime.trace_compacted");
|
|
101
|
+
if (read.omittedPrefixBytes > 0 && !hasMarker) {
|
|
102
|
+
diagnostics.push(ordered({
|
|
103
|
+
id: "runtime:trace-history-incomplete",
|
|
104
|
+
ts: new Date(0).toISOString(),
|
|
105
|
+
source: "runtime",
|
|
106
|
+
kind: "runtime.trace_history_incomplete",
|
|
107
|
+
summary: "Trace history is incomplete",
|
|
108
|
+
level: "warning",
|
|
109
|
+
detail: {
|
|
110
|
+
omitted_prefix_bytes: read.omittedPrefixBytes,
|
|
111
|
+
read_bytes: read.readBytes,
|
|
112
|
+
retained_events: read.events.length,
|
|
113
|
+
},
|
|
114
|
+
}, "trace", 0));
|
|
115
|
+
}
|
|
116
|
+
return [...projected, ...diagnostics];
|
|
59
117
|
}
|
|
60
118
|
function controlItems(stateDir) {
|
|
61
|
-
const read =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
119
|
+
const read = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
120
|
+
const start = Math.max(0, read.records.length - SOURCE_LIMIT);
|
|
121
|
+
const projected = read.records.slice(start).map((control, index) => {
|
|
122
|
+
const value = ControlProjection.projectRunControl(control);
|
|
123
|
+
return ordered({
|
|
124
|
+
id: `control:${value.id}`,
|
|
125
|
+
ts: latestControlTimestamp(value),
|
|
66
126
|
source: "control",
|
|
67
|
-
kind: `control.${
|
|
68
|
-
summary: `${
|
|
69
|
-
...(
|
|
70
|
-
detail:
|
|
71
|
-
})
|
|
72
|
-
|
|
127
|
+
kind: `control.${value.status}`,
|
|
128
|
+
summary: `${value.action} ${value.status}`,
|
|
129
|
+
...(value.status === "failed" ? { level: "error" } : {}),
|
|
130
|
+
detail: value,
|
|
131
|
+
}, "control", start + index + 1);
|
|
132
|
+
});
|
|
133
|
+
return [
|
|
134
|
+
...projected,
|
|
135
|
+
...controlDiagnosticItems(read.diagnostics).map((item, index) => ordered(item, "control", read.records.length + index + 1)),
|
|
73
136
|
];
|
|
74
137
|
}
|
|
75
138
|
function agentItems(stateDir) {
|
|
@@ -78,7 +141,7 @@ function agentItems(stateDir) {
|
|
|
78
141
|
.map((turn) => {
|
|
79
142
|
const text = turn.assistantText ?? turn.userText ?? "Agent turn";
|
|
80
143
|
const summary = text.replaceAll(/\s+/g, " ").trim().slice(0, 160);
|
|
81
|
-
return {
|
|
144
|
+
return ordered({
|
|
82
145
|
id: `agent:${turn.commandId}:${turn.sessionFile}:${turn.index}`,
|
|
83
146
|
ts: turn.timestamp ?? new Date(0).toISOString(),
|
|
84
147
|
source: "agent",
|
|
@@ -86,7 +149,7 @@ function agentItems(stateDir) {
|
|
|
86
149
|
summary: summary || "Agent turn",
|
|
87
150
|
...(turn.error ? { level: "error" } : {}),
|
|
88
151
|
detail: redact(turn),
|
|
89
|
-
};
|
|
152
|
+
}, "agent", turn.index);
|
|
90
153
|
});
|
|
91
154
|
}
|
|
92
155
|
function processItems(stateDir) {
|
|
@@ -98,7 +161,7 @@ function processItems(stateDir) {
|
|
|
98
161
|
const tail = tailFile(path, Limits.DEFAULT_INSPECT_LINES);
|
|
99
162
|
if (!tail && stat.size === 0)
|
|
100
163
|
return [];
|
|
101
|
-
return [{
|
|
164
|
+
return [ordered({
|
|
102
165
|
id: `process:${stream}`,
|
|
103
166
|
ts: stat.mtime.toISOString(),
|
|
104
167
|
source: "process",
|
|
@@ -106,17 +169,18 @@ function processItems(stateDir) {
|
|
|
106
169
|
summary: `${stream} (${stat.size} bytes)`,
|
|
107
170
|
...(stream === "stderr" ? { level: "warning" } : {}),
|
|
108
171
|
detail: redact({ bytes: stat.size, path, tail }),
|
|
109
|
-
}];
|
|
172
|
+
}, `process:${stream}`, 1)];
|
|
110
173
|
});
|
|
111
174
|
}
|
|
112
175
|
function resultItems(stateDir) {
|
|
113
176
|
const path = join(stateDir, "result.json");
|
|
114
177
|
const read = readJsonFileResilient(path, {});
|
|
115
|
-
if (Object.keys(read.value).length === 0)
|
|
116
|
-
return diagnosticItems(read.diagnostics);
|
|
178
|
+
if (Object.keys(read.value).length === 0) {
|
|
179
|
+
return diagnosticItems(read.diagnostics).map((item, index) => ordered(item, "process:result", index));
|
|
180
|
+
}
|
|
117
181
|
const stat = statSync(path);
|
|
118
182
|
const failed = Number(read.value.code ?? 0) !== 0;
|
|
119
|
-
return [{
|
|
183
|
+
return [ordered({
|
|
120
184
|
id: "process:result",
|
|
121
185
|
ts: typeof read.value.completedAt === "string"
|
|
122
186
|
? read.value.completedAt
|
|
@@ -126,13 +190,13 @@ function resultItems(stateDir) {
|
|
|
126
190
|
summary: failed ? "Run process failed" : "Run process completed",
|
|
127
191
|
...(failed ? { level: "error" } : {}),
|
|
128
192
|
detail: redact(read.value),
|
|
129
|
-
}, ...diagnosticItems(read.diagnostics)];
|
|
193
|
+
}, "process:result", 1), ...diagnosticItems(read.diagnostics).map((item, index) => ordered(item, "process:result", index + 2))];
|
|
130
194
|
}
|
|
131
195
|
function artifactItems(stateDir, declarations) {
|
|
132
196
|
const manifest = resolveArtifactManifest(declarations);
|
|
133
197
|
if (!manifest)
|
|
134
198
|
return [];
|
|
135
|
-
return Object.entries(manifest).slice(0, SOURCE_LIMIT).map(([name, artifact]) => ({
|
|
199
|
+
return Object.entries(manifest).slice(0, SOURCE_LIMIT).map(([name, artifact], index) => ordered({
|
|
136
200
|
id: `artifact:${name}`,
|
|
137
201
|
ts: artifact.exists && existsSync(artifact.path)
|
|
138
202
|
? statSync(artifact.path).mtime.toISOString()
|
|
@@ -147,7 +211,7 @@ function artifactItems(stateDir, declarations) {
|
|
|
147
211
|
required: artifact.required,
|
|
148
212
|
...(artifact.size !== undefined ? { size: artifact.size } : {}),
|
|
149
213
|
}),
|
|
150
|
-
}));
|
|
214
|
+
}, "artifact", index + 1));
|
|
151
215
|
}
|
|
152
216
|
export function projectRunTrace(stateDir, options = {}) {
|
|
153
217
|
const source = options.source ?? "all";
|
|
@@ -159,7 +223,9 @@ export function projectRunTrace(stateDir, options = {}) {
|
|
|
159
223
|
...resultItems(stateDir),
|
|
160
224
|
...artifactItems(stateDir, options.artifacts),
|
|
161
225
|
]
|
|
162
|
-
.filter((item) => source === "all" || item.source === source)
|
|
163
|
-
.sort(
|
|
164
|
-
return items
|
|
226
|
+
.filter(({ item }) => source === "all" || item.source === source)
|
|
227
|
+
.sort(compareTraceItems);
|
|
228
|
+
return items
|
|
229
|
+
.slice(0, Math.max(1, Math.min(options.limit ?? 100, 200)))
|
|
230
|
+
.map(({ item }) => item);
|
|
165
231
|
}
|
|
@@ -15,6 +15,11 @@ const conformanceSuites = [
|
|
|
15
15
|
"tests/control.test.ts",
|
|
16
16
|
"tests/runs-controls.test.ts",
|
|
17
17
|
"tests/runs-trace.test.ts",
|
|
18
|
+
"tests/observability.test.ts",
|
|
19
|
+
"tests/async-run-control.test.ts",
|
|
20
|
+
"tests/run-kernel-dogfood.test.ts",
|
|
21
|
+
"tests/installed.scripts.test.ts",
|
|
22
|
+
"tests/inspector-overlay.test.ts",
|
|
18
23
|
"tests/tools-inspect-kernel.test.ts",
|
|
19
24
|
"tests/recipes-discovery.test.ts",
|
|
20
25
|
"tests/review-swarm-dogfood.test.ts",
|