@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.
Files changed (109) hide show
  1. package/AGENTS.md +16 -10
  2. package/CHANGELOG.md +425 -536
  3. package/README.md +13 -11
  4. package/dist/index.js +1 -1
  5. package/dist/lib/async-runs.d.ts +2 -1
  6. package/dist/lib/async-runs.js +24 -34
  7. package/dist/lib/automatic-review-runtime.d.ts +1 -1
  8. package/dist/lib/automatic-review-runtime.js +5 -5
  9. package/dist/lib/command-templates.d.ts +2 -0
  10. package/dist/lib/command-templates.js +38 -4
  11. package/dist/lib/control-projection.d.ts +20 -0
  12. package/dist/lib/control-projection.js +66 -0
  13. package/dist/lib/control.d.ts +3 -0
  14. package/dist/lib/control.js +27 -14
  15. package/dist/lib/draft-sleep.js +3 -3
  16. package/dist/lib/file-state.d.ts +4 -1
  17. package/dist/lib/file-state.js +118 -44
  18. package/dist/lib/inspector-overlay.d.ts +2 -0
  19. package/dist/lib/inspector-overlay.js +124 -69
  20. package/dist/lib/limits.d.ts +15 -3
  21. package/dist/lib/limits.js +15 -3
  22. package/dist/lib/observability.d.ts +4 -2
  23. package/dist/lib/observability.js +43 -36
  24. package/dist/lib/prompts.d.ts +1 -1
  25. package/dist/lib/prompts.js +1 -1
  26. package/dist/lib/recipe-control.js +6 -2
  27. package/dist/lib/review-control.d.ts +1 -1
  28. package/dist/lib/review-control.js +4 -5
  29. package/dist/lib/run-evidence-policy.d.ts +95 -0
  30. package/dist/lib/run-evidence-policy.js +177 -0
  31. package/dist/lib/run-ui-runtime.js +2 -0
  32. package/dist/lib/runs-control-delivery.d.ts +8 -1
  33. package/dist/lib/runs-control-delivery.js +38 -15
  34. package/dist/lib/runs-controls.d.ts +8 -4
  35. package/dist/lib/runs-controls.js +189 -50
  36. package/dist/lib/runs-retention.js +27 -14
  37. package/dist/lib/runs-trace.d.ts +26 -2
  38. package/dist/lib/runs-trace.js +411 -18
  39. package/dist/lib/runtime-identity.d.ts +7 -0
  40. package/dist/lib/runtime-identity.js +35 -0
  41. package/dist/lib/runtime-triage.d.ts +29 -0
  42. package/dist/lib/runtime-triage.js +60 -0
  43. package/dist/lib/tool-review-scheduler.js +7 -7
  44. package/dist/lib/tools-inspect.js +91 -18
  45. package/dist/lib/tools-message.d.ts +1 -2
  46. package/dist/lib/tools-message.js +6 -6
  47. package/dist/lib/tools-response.d.ts +0 -1
  48. package/dist/lib/tools-response.js +0 -9
  49. package/dist/lib/tools.d.ts +1 -1
  50. package/dist/lib/tools.js +1 -1
  51. package/dist/lib/trace-projection.js +107 -41
  52. package/dist/scripts/conformance.mjs +5 -0
  53. package/dist/scripts/locker.mjs +40 -90
  54. package/dist/scripts/music-player.mjs +48 -142
  55. package/dist/scripts/release-gates.mjs +56 -3
  56. package/dist/scripts/validate-recipe.mjs +5 -4
  57. package/dist/skills/actors/SKILL.md +17 -11
  58. package/dist/skills/swarm/SKILL.md +2 -4
  59. package/docs/README.md +1 -4
  60. package/docs/actor-inspector.md +6 -5
  61. package/docs/async-runs.md +11 -9
  62. package/docs/command-templates.md +6 -116
  63. package/docs/recipe-library.md +4 -6
  64. package/docs/releasing.md +28 -0
  65. package/docs/template-recipes.md +1 -1
  66. package/docs/tool-registry.md +2 -2
  67. package/index.ts +1 -1
  68. package/lib/async-runs.ts +26 -50
  69. package/lib/automatic-review-runtime.ts +7 -7
  70. package/lib/command-templates.ts +44 -4
  71. package/lib/control-projection.ts +105 -0
  72. package/lib/control.ts +33 -18
  73. package/lib/draft-sleep.ts +3 -3
  74. package/lib/file-state.ts +91 -63
  75. package/lib/inspector-overlay.ts +108 -61
  76. package/lib/limits.ts +15 -3
  77. package/lib/observability.ts +55 -57
  78. package/lib/prompts.ts +1 -1
  79. package/lib/recipe-control.ts +9 -2
  80. package/lib/review-control.ts +4 -5
  81. package/lib/run-evidence-policy.ts +242 -0
  82. package/lib/run-ui-runtime.ts +2 -0
  83. package/lib/runs-control-delivery.ts +45 -17
  84. package/lib/runs-controls.ts +180 -102
  85. package/lib/runs-retention.ts +28 -20
  86. package/lib/runs-trace.ts +499 -20
  87. package/lib/runtime-identity.ts +39 -0
  88. package/lib/runtime-triage.ts +106 -0
  89. package/lib/tool-review-scheduler.ts +7 -7
  90. package/lib/tools-inspect.ts +94 -20
  91. package/lib/tools-message.ts +7 -8
  92. package/lib/tools-response.ts +0 -12
  93. package/lib/tools.ts +4 -4
  94. package/lib/trace-projection.ts +156 -71
  95. package/package.json +1 -1
  96. package/scripts/conformance.mjs +5 -0
  97. package/scripts/locker.mjs +40 -90
  98. package/scripts/music-player.mjs +48 -142
  99. package/scripts/release-gates.mjs +56 -3
  100. package/scripts/validate-recipe.mjs +5 -4
  101. package/skills/actors/SKILL.md +17 -11
  102. package/skills/swarm/SKILL.md +2 -4
  103. package/dist/lib/runtime-notifier.d.ts +0 -48
  104. package/dist/lib/runtime-notifier.js +0 -138
  105. package/docs/0.43-baseline.md +0 -44
  106. package/docs/actors-deep-reference.md +0 -108
  107. package/docs/component-recipes.md +0 -45
  108. package/docs/task-first-recipes.md +0 -261
  109. 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
- version: process.env.npm_package_version ?? "unknown",
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 staleControls = runs.flatMap((run) => {
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
- return [];
56
- return RunsControls.readRunControlsFromStateDir(stateDir)
57
- .filter((control) => control.status === "queued" || control.status === "claimed")
58
- .map((control) => ({ run: run.run, control }));
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
- stale_controls: staleControls.slice(0, 40),
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.length
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.stale_controls?.length ?? 0} attention=${details.attention_events?.length ?? 0}`;
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 ?? "unknown")} automatic_review=${String(details.automatic_review)}`;
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
- source,
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: RunsControls.readRunControlsFromStateDir(stateDir)
184
- .slice(-Math.max(1, Number(input.lines || 20)))
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
- getTool?: (name: string) => unknown;
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 semantic Control action."),
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.handleRuntimeMessage) {
63
+ if (!deps.handleRuntimeControl) {
64
64
  throw new Error("runtime Control is unavailable");
65
65
  }
66
- result = deps.handleRuntimeMessage(request.action, request.input);
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
  }
@@ -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
- handleRuntimeMessage?: (type: string, body: unknown) => Record<string, unknown>;
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
- handleRuntimeMessage: deps.handleRuntimeMessage,
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, readJsonlFileResilient, } from "./state-readers.js";
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 = readJsonlFileResilient(join(stateDir, "trace.jsonl"));
44
- return [
45
- ...read.records.slice(-SOURCE_LIMIT).map((event) => ({
46
- id: event.id,
47
- ts: event.ts,
48
- source: sourceForEvent(event),
49
- kind: event.kind,
50
- summary: event.summary ?? event.kind,
51
- ...(event.level ? { level: event.level } : {}),
52
- detail: redact({
53
- ...(event.attention ? { attention: event.attention } : {}),
54
- ...(event.data !== undefined ? { data: event.data } : {}),
55
- }),
56
- })),
57
- ...diagnosticItems(read.diagnostics),
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 = readJsonlFileResilient(join(stateDir, "controls.jsonl"));
62
- return [
63
- ...read.records.slice(-SOURCE_LIMIT).map((control) => ({
64
- id: `control:${control.id}`,
65
- ts: latestControlTimestamp(control),
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.${control.status}`,
68
- summary: `${control.action} ${control.status}`,
69
- ...(control.status === "failed" ? { level: "error" } : {}),
70
- detail: redact(control),
71
- })),
72
- ...diagnosticItems(read.diagnostics),
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((left, right) => right.ts.localeCompare(left.ts) || right.id.localeCompare(left.id));
164
- return items.slice(0, Math.max(1, Math.min(options.limit ?? 100, 200)));
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",