@llblab/pi-actors 0.54.0 → 0.56.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 (188) hide show
  1. package/AGENTS.md +110 -20
  2. package/BACKLOG.md +207 -4
  3. package/CHANGELOG.md +17 -0
  4. package/README.md +33 -3
  5. package/dist/index.js +2 -1
  6. package/dist/lib/async-runs.d.ts +37 -0
  7. package/dist/lib/async-runs.js +131 -62
  8. package/dist/lib/automatic-review-runtime.d.ts +2 -0
  9. package/dist/lib/automatic-review-runtime.js +3 -2
  10. package/dist/lib/command-templates.d.ts +37 -0
  11. package/dist/lib/command-templates.js +263 -129
  12. package/dist/lib/execution-files.d.ts +32 -0
  13. package/dist/lib/execution-files.js +128 -0
  14. package/dist/lib/execution-output.js +5 -4
  15. package/dist/lib/execution-sessions.d.ts +19 -1
  16. package/dist/lib/execution-sessions.js +62 -26
  17. package/dist/lib/execution.d.ts +4 -1
  18. package/dist/lib/execution.js +6 -1
  19. package/dist/lib/extension-runtime.d.ts +10 -2
  20. package/dist/lib/extension-runtime.js +95 -14
  21. package/dist/lib/file-state.js +21 -2
  22. package/dist/lib/inspector-command.js +2 -2
  23. package/dist/lib/inspector-overlay.d.ts +25 -7
  24. package/dist/lib/inspector-overlay.js +562 -262
  25. package/dist/lib/inspector.d.ts +20 -10
  26. package/dist/lib/inspector.js +80 -91
  27. package/dist/lib/limits.d.ts +1 -0
  28. package/dist/lib/limits.js +1 -0
  29. package/dist/lib/observability.d.ts +15 -1
  30. package/dist/lib/observability.js +216 -99
  31. package/dist/lib/pi.d.ts +9 -3
  32. package/dist/lib/pi.js +80 -12
  33. package/dist/lib/recipe-projection.d.ts +18 -0
  34. package/dist/lib/recipe-projection.js +220 -0
  35. package/dist/lib/recipes-context.d.ts +2 -0
  36. package/dist/lib/recipes-context.js +30 -6
  37. package/dist/lib/registry.js +270 -36
  38. package/dist/lib/run-delivery.d.ts +213 -5
  39. package/dist/lib/run-delivery.js +1909 -40
  40. package/dist/lib/run-supervision.d.ts +107 -0
  41. package/dist/lib/run-supervision.js +412 -0
  42. package/dist/lib/run-ui-runtime.d.ts +10 -2
  43. package/dist/lib/run-ui-runtime.js +347 -124
  44. package/dist/lib/runs-artifacts.d.ts +26 -1
  45. package/dist/lib/runs-artifacts.js +68 -2
  46. package/dist/lib/runs-context-exports.d.ts +451 -0
  47. package/dist/lib/runs-context-exports.js +2315 -0
  48. package/dist/lib/runs-context-index.d.ts +97 -0
  49. package/dist/lib/runs-context-index.js +368 -0
  50. package/dist/lib/runs-control-delivery.js +46 -7
  51. package/dist/lib/runs-control.d.ts +78 -2
  52. package/dist/lib/runs-control.js +334 -2
  53. package/dist/lib/runs-controls.js +4 -1
  54. package/dist/lib/runs-descendant-launches.d.ts +80 -0
  55. package/dist/lib/runs-descendant-launches.js +287 -0
  56. package/dist/lib/runs-execution-evidence.d.ts +110 -0
  57. package/dist/lib/runs-execution-evidence.js +282 -0
  58. package/dist/lib/runs-index.d.ts +66 -1
  59. package/dist/lib/runs-index.js +349 -13
  60. package/dist/lib/runs-native-launches.d.ts +73 -0
  61. package/dist/lib/runs-native-launches.js +227 -0
  62. package/dist/lib/runs-outcomes.d.ts +141 -0
  63. package/dist/lib/runs-outcomes.js +292 -0
  64. package/dist/lib/runs-ownership.d.ts +10 -1
  65. package/dist/lib/runs-ownership.js +36 -4
  66. package/dist/lib/runs-process-windows-mi.d.ts +46 -0
  67. package/dist/lib/runs-process-windows-mi.js +114 -0
  68. package/dist/lib/runs-process.d.ts +34 -1
  69. package/dist/lib/runs-process.js +158 -26
  70. package/dist/lib/runs-results.d.ts +116 -0
  71. package/dist/lib/runs-results.js +518 -0
  72. package/dist/lib/runs-retention.d.ts +15 -2
  73. package/dist/lib/runs-retention.js +148 -9
  74. package/dist/lib/runs-session-exports.d.ts +95 -0
  75. package/dist/lib/runs-session-exports.js +470 -0
  76. package/dist/lib/runs-snapshot.d.ts +58 -0
  77. package/dist/lib/runs-snapshot.js +92 -0
  78. package/dist/lib/runs-start.d.ts +2 -0
  79. package/dist/lib/runs-start.js +44 -3
  80. package/dist/lib/runs-status.d.ts +15 -1
  81. package/dist/lib/runs-status.js +337 -13
  82. package/dist/lib/runs-supervision.d.ts +105 -0
  83. package/dist/lib/runs-supervision.js +501 -0
  84. package/dist/lib/runs-trace.d.ts +1 -0
  85. package/dist/lib/runs-trace.js +14 -3
  86. package/dist/lib/runtime.js +22 -3
  87. package/dist/lib/session-evidence.d.ts +37 -0
  88. package/dist/lib/session-evidence.js +59 -3
  89. package/dist/lib/state-readers.d.ts +4 -1
  90. package/dist/lib/state-readers.js +14 -8
  91. package/dist/lib/temp.d.ts +2 -4
  92. package/dist/lib/temp.js +4 -56
  93. package/dist/lib/tools-access.d.ts +3 -0
  94. package/dist/lib/tools-access.js +13 -0
  95. package/dist/lib/tools-inspect.d.ts +1 -0
  96. package/dist/lib/tools-inspect.js +110 -43
  97. package/dist/lib/tools-local.d.ts +3 -0
  98. package/dist/lib/tools-local.js +18 -6
  99. package/dist/lib/tools-message.d.ts +4 -1
  100. package/dist/lib/tools-message.js +126 -31
  101. package/dist/lib/tools-response.d.ts +31 -3
  102. package/dist/lib/tools-response.js +311 -47
  103. package/dist/lib/tools-spawn.d.ts +3 -0
  104. package/dist/lib/tools-spawn.js +29 -7
  105. package/dist/lib/trace-projection.d.ts +20 -0
  106. package/dist/lib/trace-projection.js +192 -43
  107. package/dist/scripts/async-runner.mjs +272 -194
  108. package/dist/scripts/conformance.mjs +14 -2
  109. package/dist/skills/actors/SKILL.md +47 -3
  110. package/dist/skills/actors/references/diagnostics.md +40 -0
  111. package/dist/skills/actors/references/runs.md +56 -2
  112. package/docs/README.md +9 -1
  113. package/docs/actor-inspector.md +84 -15
  114. package/docs/agent-experience.md +216 -0
  115. package/docs/async-runs.md +21 -3
  116. package/docs/composition-transport.md +103 -0
  117. package/docs/coordinator-delivery.md +226 -24
  118. package/docs/inspection.md +38 -2
  119. package/docs/output-projection-map.md +130 -0
  120. package/docs/releasing.md +19 -2
  121. package/docs/run-supervision.md +1622 -0
  122. package/docs/template-recipes.md +4 -0
  123. package/docs/windows-process-observation.md +248 -0
  124. package/index.ts +2 -1
  125. package/lib/async-runs.ts +164 -73
  126. package/lib/automatic-review-runtime.ts +4 -2
  127. package/lib/command-templates.ts +287 -138
  128. package/lib/execution-files.ts +118 -0
  129. package/lib/execution-output.ts +4 -4
  130. package/lib/execution-sessions.ts +57 -27
  131. package/lib/execution.ts +8 -0
  132. package/lib/extension-runtime.ts +106 -12
  133. package/lib/file-state.ts +16 -2
  134. package/lib/inspector-command.ts +2 -2
  135. package/lib/inspector-overlay.ts +524 -261
  136. package/lib/inspector.ts +101 -130
  137. package/lib/limits.ts +1 -0
  138. package/lib/observability.ts +235 -116
  139. package/lib/pi.ts +89 -17
  140. package/lib/recipe-projection.ts +232 -0
  141. package/lib/recipes-context.ts +27 -6
  142. package/lib/registry.ts +266 -36
  143. package/lib/run-delivery.ts +1737 -49
  144. package/lib/run-supervision.ts +411 -0
  145. package/lib/run-ui-runtime.ts +293 -126
  146. package/lib/runs-artifacts.ts +73 -1
  147. package/lib/runs-context-exports.ts +1871 -0
  148. package/lib/runs-context-index.ts +372 -0
  149. package/lib/runs-control-delivery.ts +36 -18
  150. package/lib/runs-control.ts +297 -1
  151. package/lib/runs-controls.ts +3 -1
  152. package/lib/runs-descendant-launches.ts +338 -0
  153. package/lib/runs-execution-evidence.ts +302 -0
  154. package/lib/runs-index.ts +345 -17
  155. package/lib/runs-native-launches.ts +270 -0
  156. package/lib/runs-outcomes.ts +377 -0
  157. package/lib/runs-ownership.ts +37 -4
  158. package/lib/runs-process-windows-mi.ts +113 -0
  159. package/lib/runs-process.ts +210 -34
  160. package/lib/runs-results.ts +601 -0
  161. package/lib/runs-retention.ts +134 -10
  162. package/lib/runs-session-exports.ts +406 -0
  163. package/lib/runs-snapshot.ts +143 -0
  164. package/lib/runs-start.ts +44 -2
  165. package/lib/runs-status.ts +252 -10
  166. package/lib/runs-supervision.ts +394 -0
  167. package/lib/runs-trace.ts +14 -3
  168. package/lib/runtime.ts +22 -3
  169. package/lib/session-evidence.ts +76 -4
  170. package/lib/state-readers.ts +16 -7
  171. package/lib/temp.ts +3 -56
  172. package/lib/tools-access.ts +13 -0
  173. package/lib/tools-inspect.ts +110 -51
  174. package/lib/tools-local.ts +37 -24
  175. package/lib/tools-message.ts +120 -37
  176. package/lib/tools-response.ts +273 -50
  177. package/lib/tools-spawn.ts +31 -10
  178. package/lib/trace-projection.ts +165 -42
  179. package/package.json +10 -6
  180. package/scripts/async-runner.mjs +272 -194
  181. package/scripts/conformance.mjs +14 -2
  182. package/skills/actors/SKILL.md +47 -3
  183. package/skills/actors/references/diagnostics.md +40 -0
  184. package/skills/actors/references/runs.md +56 -2
  185. package/banner.jpg +0 -0
  186. package/dist/lib/inspector-actions.d.ts +0 -16
  187. package/dist/lib/inspector-actions.js +0 -57
  188. package/lib/inspector-actions.ts +0 -79
@@ -3,18 +3,22 @@
3
3
  * Zones: causal evidence merge, source filtering, redaction, deterministic ordering
4
4
  * Owns public Trace items; source journals and session parsing remain in their domains.
5
5
  */
6
+ import { createHash } from "node:crypto";
6
7
  import { existsSync, statSync } from "node:fs";
7
8
  import { join, relative } from "node:path";
8
9
  import * as ControlProjection from "./control-projection.js";
9
10
  import * as ExecutionSessions from "./execution-sessions.js";
10
11
  import * as Limits from "./limits.js";
11
- import { resolveArtifactManifest, } from "./runs-artifacts.js";
12
+ import { createRunRecipeEvidenceRedactor } from "./recipe-projection.js";
13
+ import { describeRunArtifactReferences, observeRunArtifactReference, } from "./runs-artifacts.js";
12
14
  import * as RunsControls from "./runs-controls.js";
15
+ import { isRunStopResultCapture } from "./runs-outcomes.js";
13
16
  import { tailFile } from "./runs-status.js";
14
17
  import * as RunsTrace from "./runs-trace.js";
15
18
  import * as SessionEvidence from "./session-evidence.js";
16
19
  import { readJsonFileResilient, } from "./state-readers.js";
17
20
  export const TRACE_ITEM_SEQUENCE = Symbol("trace-item-sequence");
21
+ export const TRACE_ITEM_CHRONOLOGY = Symbol("trace-item-chronology");
18
22
  const LIFECYCLE_PREFIXES = ["run.", "command."];
19
23
  const SOURCE_LIMIT = 100;
20
24
  const SOURCE_RANK = {
@@ -49,6 +53,69 @@ function compareTraceItems(left, right) {
49
53
  return SOURCE_RANK[right.item.source] - SOURCE_RANK[left.item.source] ||
50
54
  right.item.id.localeCompare(left.item.id);
51
55
  }
56
+ // Read-local metadata only; availability is not an original-history, issuer or work capability.
57
+ class TraceChronologyBuilder {
58
+ entries = [];
59
+ bytes = 2;
60
+ reason;
61
+ unavailable(reason) { this.reason ??= reason; this.entries.length = 0; }
62
+ add(item, sourceKey, ordinal) {
63
+ if (this.reason)
64
+ return;
65
+ if (typeof item.id !== "string" || !item.id) {
66
+ this.unavailable("metadata_unavailable");
67
+ return;
68
+ }
69
+ const projected = redact([item.id, item.ts]);
70
+ if (projected[0] !== item.id || projected[1] !== item.ts) {
71
+ this.unavailable("metadata_private");
72
+ return;
73
+ }
74
+ if (typeof item.ts !== "string" || item.ts.length !== 24 || !Number.isFinite(Date.parse(item.ts)) || new Date(item.ts).toISOString() !== item.ts) {
75
+ this.unavailable("timestamp_unavailable");
76
+ return;
77
+ }
78
+ const entry = { item: { id: item.id, ts: item.ts, source: item.source }, sourceKey, ordinal };
79
+ this.bytes += Buffer.byteLength(JSON.stringify(entry)) + Number(this.entries.length > 0);
80
+ if (this.bytes > Limits.TRACE_JOURNAL_MAX_BYTES) {
81
+ this.unavailable("metadata_too_large");
82
+ return;
83
+ }
84
+ this.entries.push(entry);
85
+ }
86
+ finish(items) {
87
+ const ranks = new Map();
88
+ if (!this.reason) {
89
+ this.entries.sort(compareTraceItems);
90
+ for (const [index, key] of this.entries.entries()) {
91
+ if (ranks.has(key.item.id)) {
92
+ this.unavailable("metadata_ambiguous");
93
+ break;
94
+ }
95
+ ranks.set(key.item.id, { key, ordinal: this.entries.length - index });
96
+ }
97
+ }
98
+ if (!this.reason && items.some(({ item, sourceKey }) => {
99
+ const entry = ranks.get(item.id);
100
+ return !entry || entry.key.sourceKey !== sourceKey || entry.key.item.ts !== item.ts || entry.key.item.source !== item.source;
101
+ }))
102
+ this.unavailable("metadata_unavailable");
103
+ // The legacy same-source ordinal/cross-source rank tie rule is not always a total order.
104
+ if (!this.reason)
105
+ for (const source of ["all", ...Object.keys(SOURCE_RANK)]) {
106
+ let previous = Infinity;
107
+ const view = items.filter(({ item }) => source === "all" || item.source === source).sort(compareTraceItems);
108
+ if (view.some(({ item }) => { const rank = ranks.get(item.id).ordinal; const mismatch = rank >= previous; previous = rank; return mismatch; })) {
109
+ this.unavailable("metadata_ambiguous");
110
+ break;
111
+ }
112
+ }
113
+ for (const { item } of items)
114
+ Object.defineProperty(item, TRACE_ITEM_CHRONOLOGY, { value: Object.freeze(this.reason
115
+ ? { state: "unavailable", basis: "recorded_observations", reason: this.reason }
116
+ : { state: "available", basis: "recorded_observations", ordinal: ranks.get(item.id).ordinal, total: ranks.size }) });
117
+ }
118
+ }
52
119
  function latestControlTimestamp(control) {
53
120
  return (control.failed_at ??
54
121
  control.handled_at ??
@@ -83,9 +150,16 @@ function controlDiagnosticItems(diagnostics) {
83
150
  },
84
151
  }));
85
152
  }
86
- function traceEventItems(stateDir) {
87
- const read = RunsTrace.readRunTraceJournal(stateDir);
153
+ function traceEventItems(read, recipe, projectEvidence = recipe ? createRunRecipeEvidenceRedactor(recipe) : redact, chronology) {
88
154
  const summary = RunsTrace.summarizeRunTraceJournal(read);
155
+ if (!summary.history_complete)
156
+ chronology?.unavailable("history_incomplete");
157
+ if (chronology)
158
+ for (const { event, ordinal } of read.events) {
159
+ if (redact(event.kind) !== event.kind)
160
+ chronology.unavailable("metadata_private");
161
+ chronology.add({ id: event.id, ts: event.ts, source: sourceForEvent(event) }, "trace", ordinal);
162
+ }
89
163
  const sequenceOffset = summary.dropped_events + Math.max(0, summary.compactions_total - 1);
90
164
  const retained = read.events.slice(-SOURCE_LIMIT);
91
165
  const projected = retained.map(({ event, ordinal }) => ordered({
@@ -93,9 +167,9 @@ function traceEventItems(stateDir) {
93
167
  ts: event.ts,
94
168
  source: sourceForEvent(event),
95
169
  kind: event.kind,
96
- summary: event.summary ?? event.kind,
170
+ summary: projectEvidence(event.summary ?? event.kind),
97
171
  ...(event.level ? { level: event.level } : {}),
98
- detail: redact({
172
+ detail: projectEvidence({
99
173
  ...(event.attention ? { attention: event.attention } : {}),
100
174
  ...(event.data !== undefined ? { data: event.data } : {}),
101
175
  }),
@@ -119,8 +193,34 @@ function traceEventItems(stateDir) {
119
193
  }
120
194
  return [...projected, ...diagnostics];
121
195
  }
122
- function controlItems(stateDir) {
196
+ function controlItems(stateDir, chronology) {
123
197
  const read = RunsControls.readRunControlJournalFromStateDir(stateDir);
198
+ if (read.diagnostics.length)
199
+ chronology?.unavailable("metadata_unavailable");
200
+ let terminal = 0;
201
+ if (chronology)
202
+ for (const [index, raw] of read.records.entries()) {
203
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
204
+ chronology.unavailable("metadata_unavailable");
205
+ continue;
206
+ }
207
+ const record = raw;
208
+ if (![record.id, record.action, record.run_instance_id, record.queued_at].every(value => typeof value === "string") || !record.id || !record.run_instance_id ||
209
+ !["queued", "delivered", "claimed", "handled", "failed"].includes(record.status)) {
210
+ chronology.unavailable("metadata_unavailable");
211
+ continue;
212
+ }
213
+ if (record.status === "handled" || record.status === "failed")
214
+ terminal++;
215
+ const metadata = { id: record.id, action: record.action, run_instance_id: record.run_instance_id, queued_at: record.queued_at, status: record.status,
216
+ ...Object.fromEntries(["failed_at", "handled_at", "claimed_at", "delivered_at"].flatMap(field => typeof raw[field] === "string" ? [[field, raw[field]]] : [])) };
217
+ const value = ControlProjection.projectRunControl(metadata);
218
+ if (value.id !== record.id || latestControlTimestamp(value) !== latestControlTimestamp(record))
219
+ chronology.unavailable("metadata_unavailable");
220
+ chronology.add({ id: `control:${value.id}`, ts: latestControlTimestamp(value), source: "control" }, "control", index + 1);
221
+ }
222
+ if (terminal >= Limits.RUN_CONTROL_TERMINAL_LIMIT)
223
+ chronology?.unavailable("control_history_unproven");
124
224
  const start = Math.max(0, read.records.length - SOURCE_LIMIT);
125
225
  const projected = read.records.slice(start).map((control, index) => {
126
226
  const value = ControlProjection.projectRunControl(control);
@@ -139,14 +239,41 @@ function controlItems(stateDir) {
139
239
  ...controlDiagnosticItems(read.diagnostics).map((item, index) => ordered(item, "control", read.records.length + index + 1)),
140
240
  ];
141
241
  }
142
- function agentItems(stateDir) {
143
- return ExecutionSessions.readExecutionTurns(stateDir)
144
- .slice(-SOURCE_LIMIT)
145
- .map((turn) => {
242
+ function agentIdentity(commandId, sessionFile, index, admitted) {
243
+ const namespace = redact([commandId, sessionFile]);
244
+ const identity = namespace[0] === commandId && namespace[1] === sessionFile ? admitted : undefined;
245
+ const basis = identity?.basis ?? "legacy";
246
+ const key = createHash("sha256").update(JSON.stringify([
247
+ "agent-turn-v1", ...namespace, identity ?? { basis, ordinal: index },
248
+ ])).digest("hex");
249
+ return `agent:${basis}:${key}`;
250
+ }
251
+ function agentItems(stateDir, chronology) {
252
+ const turns = ExecutionSessions.readExecutionTurns(stateDir);
253
+ const metadata = turns[ExecutionSessions.EXECUTION_CHRONOLOGY];
254
+ if (!metadata || metadata.state !== "available")
255
+ chronology?.unavailable("metadata_unavailable");
256
+ else
257
+ for (const entry of metadata.entries)
258
+ chronology?.add({
259
+ id: agentIdentity(entry.commandId, entry.sessionFile, entry.ordinal, entry.identity), ts: entry.timestamp, source: "agent",
260
+ }, "agent", entry.ordinal);
261
+ return turns.slice(-SOURCE_LIMIT).map((turn) => {
146
262
  const text = turn.assistantText ?? turn.userText ?? "Agent turn";
147
- const summary = text.replaceAll(/\s+/g, " ").trim().slice(0, 160);
263
+ const compact = text.replaceAll(/\s+/g, " ").trim();
264
+ let summary = compact;
265
+ if (Buffer.from(compact).toString("utf8") !== compact) {
266
+ summary = "Agent turn [omitted: unknown] (malformed retained text)";
267
+ }
268
+ else if (compact.length > 160) {
269
+ const last = compact.charCodeAt(159), next = compact.charCodeAt(160);
270
+ const end = last >= 0xd800 && last <= 0xdbff && next >= 0xdc00 && next <= 0xdfff ? 159 : 160;
271
+ // Only this normalized retained suffix supplies a count; upstream source loss stays unknown.
272
+ const omitted = Array.from(compact.slice(end)).length;
273
+ summary = `${compact.slice(0, end)}… [omitted: ${omitted} chars] from normalized retained turn; upstream [omitted: unknown]`;
274
+ }
148
275
  return ordered({
149
- id: `agent:${turn.commandId}:${turn.sessionFile}:${turn.index}`,
276
+ id: agentIdentity(turn.commandId, turn.sessionFile, turn.index, turn[SessionEvidence.SESSION_TURN_IDENTITY]),
150
277
  ts: turn.timestamp ?? new Date(0).toISOString(),
151
278
  source: "agent",
152
279
  kind: "agent.turn",
@@ -184,51 +311,73 @@ function resultItems(stateDir) {
184
311
  }
185
312
  const stat = statSync(path);
186
313
  const failed = Number(read.value.code ?? 0) !== 0;
314
+ const captured = isRunStopResultCapture(read.value);
187
315
  return [ordered({
188
316
  id: "process:result",
189
317
  ts: typeof read.value.completedAt === "string"
190
318
  ? read.value.completedAt
191
319
  : stat.mtime.toISOString(),
192
320
  source: "process",
193
- kind: failed ? "process.failed" : "process.result",
194
- summary: failed ? "Run process failed" : "Run process completed",
321
+ kind: captured ? "process.result_captured" : failed ? "process.failed" : "process.result",
322
+ summary: captured ? "Workload result captured while stopping" : failed ? "Run process failed" : "Run process completed",
195
323
  ...(failed ? { level: "error" } : {}),
196
324
  detail: redact(read.value),
197
325
  }, "process:result", 1, 2), ...diagnosticItems(read.diagnostics).map((item, index) => ordered(item, "process:result", index + 2))];
198
326
  }
199
- function artifactItems(stateDir, declarations) {
200
- const manifest = resolveArtifactManifest(declarations);
201
- if (!manifest)
327
+ function artifactItems(stateDir, declarations, chronology) {
328
+ const described = describeRunArtifactReferences(declarations, process.cwd());
329
+ if (described.state !== "available") {
330
+ if (declarations)
331
+ chronology?.unavailable("metadata_unavailable");
202
332
  return [];
203
- return Object.entries(manifest).slice(0, SOURCE_LIMIT).map(([name, artifact], index) => ordered({
204
- id: `artifact:${name}`,
205
- ts: artifact.exists && existsSync(artifact.path)
206
- ? statSync(artifact.path).mtime.toISOString()
207
- : new Date(0).toISOString(),
208
- source: "artifact",
209
- kind: artifact.exists ? "artifact.ready" : "artifact.missing",
210
- summary: `${name}: ${artifact.exists ? "ready" : "missing"}`,
211
- ...(artifact.required && !artifact.exists ? { level: "warning" } : {}),
212
- detail: redact({
213
- exists: artifact.exists,
214
- path: relative(stateDir, artifact.path).replaceAll("\\", "/"),
215
- required: artifact.required,
216
- ...(artifact.size !== undefined ? { size: artifact.size } : {}),
217
- }),
218
- }, "artifact", index + 1, index));
333
+ }
334
+ if (described.references.length > SOURCE_LIMIT)
335
+ chronology?.unavailable("metadata_unavailable");
336
+ return described.references.slice(0, SOURCE_LIMIT).map((reference, index) => {
337
+ const artifact = observeRunArtifactReference(reference);
338
+ const exists = artifact.availability === "available";
339
+ if (!artifact.modified_at)
340
+ chronology?.unavailable("timestamp_unavailable");
341
+ return ordered({
342
+ id: `artifact:${artifact.name}`,
343
+ ts: artifact.modified_at ?? new Date(0).toISOString(),
344
+ source: "artifact",
345
+ kind: exists ? "artifact.ready" : artifact.availability === "missing" ? "artifact.missing" : "artifact.unavailable",
346
+ summary: redact(`${artifact.name}: ${exists ? "ready" : artifact.availability}`),
347
+ ...(artifact.required && !exists ? { level: "warning" } : {}),
348
+ detail: redact({
349
+ ...(artifact.availability !== "unavailable" ? { exists } : {}),
350
+ availability: artifact.availability,
351
+ path: relative(stateDir, artifact.path).replaceAll("\\", "/"),
352
+ required: artifact.required,
353
+ ...(artifact.bytes_total !== undefined ? { size: artifact.bytes_total } : {}),
354
+ ...(artifact.reason ? { reason: artifact.reason } : {}),
355
+ }),
356
+ }, "artifact", index + 1, index);
357
+ });
358
+ }
359
+ /** Data-only canonical-journal projection; captured Recipe context supplies privacy, never lifecycle/work authority. */
360
+ export function projectRunTraceJournal(read, options = {}) {
361
+ return traceEventItems(read, options.recipe, options.redactEvidence).sort(compareTraceItems)
362
+ .slice(0, Math.max(1, Math.min(options.limit ?? 100, 200))).map(({ item }) => item);
219
363
  }
220
364
  export function projectRunTrace(stateDir, options = {}) {
221
365
  const source = options.source ?? "all";
222
- const items = [
223
- ...traceEventItems(stateDir),
224
- ...controlItems(stateDir),
225
- ...agentItems(stateDir),
226
- ...processItems(stateDir),
227
- ...resultItems(stateDir),
228
- ...artifactItems(stateDir, options.artifacts),
229
- ]
230
- .filter(({ item }) => source === "all" || item.source === source)
231
- .sort(compareTraceItems);
366
+ const chronology = new TraceChronologyBuilder();
367
+ const entries = [
368
+ ...traceEventItems(RunsTrace.readRunTraceJournal(stateDir), undefined, undefined, chronology),
369
+ ...controlItems(stateDir, chronology),
370
+ ...agentItems(stateDir, chronology),
371
+ ];
372
+ const snapshots = [...processItems(stateDir), ...resultItems(stateDir), ...artifactItems(stateDir, options.artifacts, chronology)];
373
+ for (const entry of snapshots) {
374
+ if (entry.item.kind === "state.read_error")
375
+ chronology.unavailable("metadata_unavailable");
376
+ chronology.add(entry.item, entry.sourceKey, entry.ordinal);
377
+ }
378
+ const all = [...entries, ...snapshots];
379
+ chronology.finish(all);
380
+ const items = all.filter(({ item }) => source === "all" || item.source === source).sort(compareTraceItems);
232
381
  return items
233
382
  .slice(0, Math.max(1, Math.min(options.limit ?? 100, 200)))
234
383
  .map(({ item }) => item);