agent-inspect 6.31.29 → 6.31.31

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.31.31
4
+
5
+ ### Patch Changes
6
+
7
+ - 1ca1c11: Oct 30 residuals: stricter saved-evidence verifiers, HTML attempt-preserving lifecycle presentation, Om beyond-budget readiness guard, Jan packed MCP SDK assertions, Roy path-scrubbed -003 packet and UNPROVABLE receipt fixtures, and status/docs reconcile.
8
+
9
+ ## 6.31.30
10
+
11
+ ### Patch Changes
12
+
13
+ - 35923dd: Join sibling start/complete lifecycle rows in HTML/Markdown presentation and coalesce bundle HTML trees so completed steps are not shown as still running.
14
+ - f1c1752: Nest tenant recipe now executes real SupportService/PolicyService seams and scores consumption from reopened saved outcomes (N01–N08), not in-memory joinOk.
15
+ - f664b85: Om browser recipe records injected-clock wait/readiness diagnostics (document, in-flight, obstruction, elapsed/budget/remaining, stop reason) without inventing overlay from LocatorNotFound.
16
+
3
17
  ## 6.31.29
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -213,7 +213,7 @@ The root package is enough for custom capture, the CLI, checks, and Evidence wor
213
213
 
214
214
  ## Status and documentation
215
215
 
216
- **Current published baseline:** **6.31.29** · persisted schema `1.0` · Node.js `>=20` · MIT.
216
+ **Current published baseline:** **6.31.31** · persisted schema `1.0` · Node.js `>=20` · MIT.
217
217
 
218
218
  Legacy v0.1 and v0.2 traces remain readable. Check the npm badge and [changelog](CHANGELOG.md) for the current published version.
219
219
 
package/docs/CLI.md CHANGED
@@ -382,12 +382,37 @@ Rich TraceContract JSON (6.30+):
382
382
  }
383
383
  ```
384
384
 
385
- Supported `contract` fields: `run`, `tools` (including `arguments`, `orderRules`),
385
+ Supported `contract` fields: `run`, `tools` (including `arguments`, `orderRules`,
386
+ `requiredOrder`, `requiredOrderMode`), `steps` (including `orderRelations`),
386
387
  `llm`, `observations` (including `requireProvenance`), `scope`, `alternatives.anyOf`,
387
388
  `controls`, and `retry`. Unknown keys fail closed (exit 2). With `--evidence-on`,
388
389
  contract mode writes `contract.resolved.json` and binds digests into check-results.
389
390
  YAML is not supported. TypeScript config files (`.ts`, `.mts`, `.cts`) fail clearly unless a future explicit loader strategy is added; use precompiled JavaScript config instead.
390
391
 
392
+ `steps.orderRelations` (6.31+) declares typed cross-kind ordering (for example TOOL
393
+ before LLM). Each relation has `before` / `after` step refs (`kind` + `name`), optional
394
+ `mode` (`first-occurrence` | `happens-before` | `all-occurrences`; default
395
+ `first-occurrence`), and optional `requireEndpoints` (default `true`). This is separate
396
+ from TOOL-only `tools.requiredOrder` / `orderRules`. Invalid shapes and unknown keys
397
+ fail closed.
398
+
399
+ ```json
400
+ {
401
+ "contract": {
402
+ "tools": { "required": ["retrieve_policy"] },
403
+ "steps": {
404
+ "orderRelations": [
405
+ {
406
+ "before": { "kind": "TOOL", "name": "retrieve_policy" },
407
+ "after": { "kind": "LLM", "name": "answer" },
408
+ "mode": "first-occurrence"
409
+ }
410
+ ]
411
+ }
412
+ }
413
+ }
414
+ ```
415
+
391
416
  Examples:
392
417
 
393
418
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-inspect",
3
- "version": "6.31.29",
3
+ "version": "6.31.31",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Local evidence debugger and trajectory-test toolkit for TypeScript AI agents — execution trees, TraceContract checks, Evidence v2, and read-only MCP",
@@ -252,7 +252,7 @@
252
252
  "demo:verify": "node scripts/demo-verify.mjs",
253
253
  "compat:smoke": "node scripts/compat-smoke.mjs",
254
254
  "compat:ai-sdk-matrix": "node scripts/ai-sdk-peer-matrix.mjs",
255
- "fixtures:check": "node scripts/validate-fixtures.mjs",
255
+ "fixtures:check": "node scripts/validate-fixtures.mjs && node scripts/check-otlp-identity-003-shareable.mjs && node scripts/check-roy-437-unprovable.mjs",
256
256
  "recipes:check": "node scripts/validate-recipes.mjs",
257
257
  "test:integration-offline": "node --test examples/recipes/integration-fixtures/helpers.test.mjs examples/recipes/promptfoo-use-together/lib/matrix-interpret.test.mjs examples/reviewer-kits/promptfoo-trajectory/lib/matrix-interpret.test.mjs examples/recipes/elastic-otlp/live-controls.test.mjs examples/recipes/openai-node-chat-completions/openai-boundary.test.mjs && node examples/recipes/otel-collector-roundtrip/verify.mjs && node examples/recipes/elastic-otlp/verify.mjs",
258
258
  "perf:baseline": "node scripts/performance-baseline.mjs",
@@ -17304,7 +17304,7 @@ var init_src = __esm({
17304
17304
  });
17305
17305
 
17306
17306
  // package.json
17307
- var version = "6.31.29";
17307
+ var version = "6.31.31";
17308
17308
 
17309
17309
  // packages/cli/src/list.ts
17310
17310
  init_advanced();
@@ -19516,6 +19516,161 @@ function redactRunTreeForExport(tree, options) {
19516
19516
 
19517
19517
  // packages/core/src/exporters/html-exporter.ts
19518
19518
  init_helpers();
19519
+
19520
+ // packages/core/src/exporters/presentation-lifecycle.ts
19521
+ function stepIdOf2(event) {
19522
+ const value = event.attributes?.stepId;
19523
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
19524
+ }
19525
+ function attemptIdOf3(event) {
19526
+ const direct = event.attributes?.attemptId;
19527
+ if (typeof direct === "string" && direct.trim() !== "") return direct;
19528
+ const meta2 = event.attributes?.metadata;
19529
+ if (meta2 && typeof meta2 === "object" && !Array.isArray(meta2)) {
19530
+ const nested = meta2.attemptId;
19531
+ if (typeof nested === "string" && nested.trim() !== "") return nested;
19532
+ }
19533
+ return void 0;
19534
+ }
19535
+ function lifecycleKey(event) {
19536
+ if (event.kind === "RUN") return `run:${event.runId}`;
19537
+ const stepId = stepIdOf2(event);
19538
+ if (stepId !== void 0) return `step:${stepId}`;
19539
+ return `event:${event.eventId}`;
19540
+ }
19541
+ function isTerminal(status) {
19542
+ return status === "ok" || status === "error";
19543
+ }
19544
+ function eventTime3(event) {
19545
+ return typeof event.timestamp === "number" ? event.timestamp : 0;
19546
+ }
19547
+ function cloneNode(node) {
19548
+ return {
19549
+ event: {
19550
+ ...node.event,
19551
+ ...node.event.attributes !== void 0 ? { attributes: { ...node.event.attributes } } : {}
19552
+ },
19553
+ children: node.children.map(cloneNode),
19554
+ depth: node.depth
19555
+ };
19556
+ }
19557
+ function joinOnePair(start, terminal) {
19558
+ const childMap = /* @__PURE__ */ new Map();
19559
+ if (start) {
19560
+ for (const child of start.children) {
19561
+ childMap.set(child.event.eventId, child);
19562
+ }
19563
+ }
19564
+ for (const child of terminal.children) {
19565
+ childMap.set(child.event.eventId, child);
19566
+ }
19567
+ return {
19568
+ event: {
19569
+ ...terminal.event,
19570
+ name: start?.event.name ?? terminal.event.name,
19571
+ kind: start?.event.kind ?? terminal.event.kind,
19572
+ ...start && attemptIdOf3(terminal.event) === void 0 && attemptIdOf3(start.event) ? {
19573
+ attributes: {
19574
+ ...terminal.event.attributes,
19575
+ attemptId: attemptIdOf3(start.event)
19576
+ }
19577
+ } : {}
19578
+ },
19579
+ children: presentSiblingNodes([...childMap.values()]),
19580
+ depth: terminal.depth
19581
+ };
19582
+ }
19583
+ function joinSiblingGroup(group) {
19584
+ if (group.length <= 1) return group;
19585
+ const terminals = group.filter((n) => isTerminal(n.event.status)).sort((a, b) => eventTime3(a.event) - eventTime3(b.event));
19586
+ const runnings = group.filter((n) => n.event.status === "running").sort((a, b) => eventTime3(a.event) - eventTime3(b.event));
19587
+ const others = group.filter(
19588
+ (n) => !isTerminal(n.event.status) && n.event.status !== "running"
19589
+ );
19590
+ const terminalStatuses = new Set(terminals.map((n) => n.event.status));
19591
+ if (terminalStatuses.size > 1) {
19592
+ return group;
19593
+ }
19594
+ if (terminals.length > 1 && terminals.every((t) => attemptIdOf3(t.event) === void 0) && runnings.every((r) => attemptIdOf3(r.event) === void 0)) {
19595
+ return group;
19596
+ }
19597
+ const usedRunning = /* @__PURE__ */ new Set();
19598
+ const out = [];
19599
+ for (const terminal of terminals) {
19600
+ const terminalAttempt = attemptIdOf3(terminal.event);
19601
+ const terminalTs = eventTime3(terminal.event);
19602
+ const candidate = runnings.find((r) => {
19603
+ if (usedRunning.has(r.event.eventId)) return false;
19604
+ if (eventTime3(r.event) > terminalTs) return false;
19605
+ const runningAttempt = attemptIdOf3(r.event);
19606
+ if (terminalAttempt !== void 0) {
19607
+ return runningAttempt === terminalAttempt;
19608
+ }
19609
+ if (runningAttempt !== void 0) {
19610
+ return true;
19611
+ }
19612
+ return true;
19613
+ });
19614
+ if (candidate) {
19615
+ usedRunning.add(candidate.event.eventId);
19616
+ out.push(joinOnePair(candidate, terminal));
19617
+ } else {
19618
+ out.push({
19619
+ ...terminal,
19620
+ children: presentSiblingNodes(terminal.children)
19621
+ });
19622
+ }
19623
+ }
19624
+ for (const running of runnings) {
19625
+ if (usedRunning.has(running.event.eventId)) continue;
19626
+ out.push({
19627
+ ...running,
19628
+ children: presentSiblingNodes(running.children)
19629
+ });
19630
+ }
19631
+ for (const other of others) {
19632
+ out.push({
19633
+ ...other,
19634
+ children: presentSiblingNodes(other.children)
19635
+ });
19636
+ }
19637
+ return out;
19638
+ }
19639
+ function presentSiblingNodes(nodes) {
19640
+ const prepared = nodes.map((n) => ({
19641
+ ...cloneNode(n),
19642
+ children: presentSiblingNodes(n.children)
19643
+ }));
19644
+ const order = [];
19645
+ const groups = /* @__PURE__ */ new Map();
19646
+ for (const node of prepared) {
19647
+ const key = lifecycleKey(node.event);
19648
+ if (!groups.has(key)) {
19649
+ groups.set(key, []);
19650
+ order.push(key);
19651
+ }
19652
+ groups.get(key).push(node);
19653
+ }
19654
+ const out = [];
19655
+ for (const key of order) {
19656
+ out.push(...joinSiblingGroup(groups.get(key)));
19657
+ }
19658
+ return out;
19659
+ }
19660
+ function presentLifecycleJoinedTree(tree) {
19661
+ return {
19662
+ ...tree,
19663
+ children: presentSiblingNodes(tree.children),
19664
+ metadata: {
19665
+ ...tree.metadata,
19666
+ confidenceBreakdown: { ...tree.metadata.confidenceBreakdown },
19667
+ kinds: { ...tree.metadata.kinds },
19668
+ ...tree.metadata.relationshipSummary !== void 0 ? { relationshipSummary: { ...tree.metadata.relationshipSummary } } : {}
19669
+ }
19670
+ };
19671
+ }
19672
+
19673
+ // packages/core/src/exporters/html-exporter.ts
19519
19674
  function renderTreeHtml2(nodes, ulClass = "tree") {
19520
19675
  if (nodes.length === 0) return "";
19521
19676
  const parts = [`<ul class="${ulClass}">`];
@@ -19542,32 +19697,33 @@ function exportHtml(tree, options) {
19542
19697
  const includeErrors = options?.includeErrors ?? true;
19543
19698
  const maxLen = options?.maxAttributeLength ?? 500;
19544
19699
  const redacted = options?.redacted ?? true;
19545
- const titleName = escapeHtml(tree.name ?? tree.runId);
19700
+ const view2 = presentLifecycleJoinedTree(tree);
19701
+ const titleName = escapeHtml(view2.name ?? view2.runId);
19546
19702
  const summaryRows = [];
19547
19703
  summaryRows.push(
19548
- `<tr><th scope="row">runId</th><td><code>${escapeHtml(tree.runId)}</code></td></tr>`
19704
+ `<tr><th scope="row">runId</th><td><code>${escapeHtml(view2.runId)}</code></td></tr>`
19549
19705
  );
19550
- if (tree.name !== void 0) {
19551
- summaryRows.push(`<tr><th scope="row">name</th><td>${escapeHtml(tree.name)}</td></tr>`);
19706
+ if (view2.name !== void 0) {
19707
+ summaryRows.push(`<tr><th scope="row">name</th><td>${escapeHtml(view2.name)}</td></tr>`);
19552
19708
  }
19553
19709
  summaryRows.push(
19554
- `<tr><th scope="row">status</th><td>${escapeHtml(String(tree.status ?? "unknown"))}</td></tr>`
19710
+ `<tr><th scope="row">status</th><td>${escapeHtml(String(view2.status ?? "unknown"))}</td></tr>`
19555
19711
  );
19556
19712
  summaryRows.push(
19557
- `<tr><th scope="row">durationMs</th><td>${tree.durationMs !== void 0 ? escapeHtml(String(tree.durationMs)) : "\u2014"}</td></tr>`
19713
+ `<tr><th scope="row">durationMs</th><td>${view2.durationMs !== void 0 ? escapeHtml(String(view2.durationMs)) : "\u2014"}</td></tr>`
19558
19714
  );
19559
19715
  summaryRows.push(
19560
- `<tr><th scope="row">startedAt</th><td>${tree.startedAt !== void 0 ? escapeHtml(String(tree.startedAt)) : "\u2014"}</td></tr>`
19716
+ `<tr><th scope="row">startedAt</th><td>${view2.startedAt !== void 0 ? escapeHtml(String(view2.startedAt)) : "\u2014"}</td></tr>`
19561
19717
  );
19562
19718
  summaryRows.push(
19563
- `<tr><th scope="row">endedAt</th><td>${tree.endedAt !== void 0 ? escapeHtml(String(tree.endedAt)) : "\u2014"}</td></tr>`
19719
+ `<tr><th scope="row">endedAt</th><td>${view2.endedAt !== void 0 ? escapeHtml(String(view2.endedAt)) : "\u2014"}</td></tr>`
19564
19720
  );
19565
19721
  summaryRows.push(
19566
- `<tr><th scope="row">totalEvents</th><td>${escapeHtml(String(tree.metadata.totalEvents))}</td></tr>`
19722
+ `<tr><th scope="row">totalEvents</th><td>${escapeHtml(String(view2.metadata.totalEvents))}</td></tr>`
19567
19723
  );
19568
19724
  let confidenceHtml = "";
19569
19725
  if (includeMetadata) {
19570
- const cb = tree.metadata.confidenceBreakdown;
19726
+ const cb = view2.metadata.confidenceBreakdown;
19571
19727
  confidenceHtml += "<h3>Confidence breakdown</h3><table><thead><tr><th>bucket</th><th>count</th></tr></thead><tbody>";
19572
19728
  for (const k of Object.keys(cb).sort()) {
19573
19729
  const key = k;
@@ -19575,14 +19731,14 @@ function exportHtml(tree, options) {
19575
19731
  }
19576
19732
  confidenceHtml += "</tbody></table>";
19577
19733
  confidenceHtml += "<h3>Kind breakdown</h3><table><thead><tr><th>kind</th><th>count</th></tr></thead><tbody>";
19578
- for (const k of Object.keys(tree.metadata.kinds).sort()) {
19734
+ for (const k of Object.keys(view2.metadata.kinds).sort()) {
19579
19735
  const key = k;
19580
- const c = tree.metadata.kinds[key];
19736
+ const c = view2.metadata.kinds[key];
19581
19737
  if (c > 0) confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${c}</td></tr>`;
19582
19738
  }
19583
19739
  confidenceHtml += "</tbody></table>";
19584
19740
  }
19585
- const flat = flattenTree(tree);
19741
+ const flat = flattenTree(view2);
19586
19742
  const errors = flat.filter((n) => n.event.status === "error");
19587
19743
  let errorsHtml = "";
19588
19744
  if (includeErrors && errors.length > 0) {
@@ -19637,7 +19793,7 @@ footer{margin-top:2rem;font-size:0.85rem;color:#555}
19637
19793
  <header><h1>AgentInspect Run: ${titleName}</h1></header>
19638
19794
  <p class="note">Generated locally by AgentInspect.</p>
19639
19795
  ${includeMetadata ? `<section class="summary"><h2>Summary</h2><table>${summaryRows.join("")}</table>${confidenceHtml}</section>` : ""}
19640
- <section class="tree"><h2>Execution tree</h2>${tree.children.length > 0 ? renderTreeHtml2(tree.children) : "<p>No steps recorded.</p>"}</section>
19796
+ <section class="tree"><h2>Execution tree</h2>${view2.children.length > 0 ? renderTreeHtml2(view2.children) : "<p>No steps recorded.</p>"}</section>
19641
19797
  ${errorsHtml}
19642
19798
  ${attrsHtml}
19643
19799
  <footer>Generated locally by AgentInspect. Review for sensitive data before sharing.</footer>
@@ -19679,7 +19835,8 @@ function exportMarkdown(tree, options) {
19679
19835
  const includeErrors = options?.includeErrors ?? true;
19680
19836
  const maxLen = options?.maxAttributeLength ?? 500;
19681
19837
  const redacted = options?.redacted ?? true;
19682
- const titleName = tree.name ?? tree.runId;
19838
+ const view2 = presentLifecycleJoinedTree(tree);
19839
+ const titleName = view2.name ?? view2.runId;
19683
19840
  const lines = [];
19684
19841
  lines.push(`# AgentInspect Run: ${escapeMarkdown(titleName)}`);
19685
19842
  lines.push("");
@@ -19688,28 +19845,28 @@ function exportMarkdown(tree, options) {
19688
19845
  if (includeMetadata) {
19689
19846
  lines.push("## Summary");
19690
19847
  lines.push("");
19691
- lines.push(`- **runId**: ${escapeMarkdown(tree.runId)}`);
19692
- if (tree.name !== void 0) lines.push(`- **name**: ${escapeMarkdown(tree.name)}`);
19693
- lines.push(`- **status**: ${escapeMarkdown(String(tree.status ?? "unknown"))}`);
19848
+ lines.push(`- **runId**: ${escapeMarkdown(view2.runId)}`);
19849
+ if (view2.name !== void 0) lines.push(`- **name**: ${escapeMarkdown(view2.name)}`);
19850
+ lines.push(`- **status**: ${escapeMarkdown(String(view2.status ?? "unknown"))}`);
19694
19851
  lines.push(
19695
- `- **durationMs**: ${tree.durationMs !== void 0 ? escapeMarkdown(String(tree.durationMs)) : "-"}`
19852
+ `- **durationMs**: ${view2.durationMs !== void 0 ? escapeMarkdown(String(view2.durationMs)) : "-"}`
19696
19853
  );
19697
19854
  lines.push(
19698
- `- **startedAt**: ${tree.startedAt !== void 0 ? escapeMarkdown(String(tree.startedAt)) : "-"}`
19855
+ `- **startedAt**: ${view2.startedAt !== void 0 ? escapeMarkdown(String(view2.startedAt)) : "-"}`
19699
19856
  );
19700
19857
  lines.push(
19701
- `- **endedAt**: ${tree.endedAt !== void 0 ? escapeMarkdown(String(tree.endedAt)) : "-"}`
19858
+ `- **endedAt**: ${view2.endedAt !== void 0 ? escapeMarkdown(String(view2.endedAt)) : "-"}`
19702
19859
  );
19703
- lines.push(`- **totalEvents**: ${tree.metadata.totalEvents}`);
19860
+ lines.push(`- **totalEvents**: ${view2.metadata.totalEvents}`);
19704
19861
  lines.push("");
19705
19862
  lines.push("### Confidence breakdown");
19706
19863
  lines.push("");
19707
19864
  lines.push("| bucket | count |");
19708
19865
  lines.push("| --- | --- |");
19709
- for (const k of Object.keys(tree.metadata.confidenceBreakdown).sort()) {
19866
+ for (const k of Object.keys(view2.metadata.confidenceBreakdown).sort()) {
19710
19867
  const key = k;
19711
19868
  lines.push(
19712
- `| ${escapeMarkdown(key)} | ${tree.metadata.confidenceBreakdown[key]} |`
19869
+ `| ${escapeMarkdown(key)} | ${view2.metadata.confidenceBreakdown[key]} |`
19713
19870
  );
19714
19871
  }
19715
19872
  lines.push("");
@@ -19717,9 +19874,9 @@ function exportMarkdown(tree, options) {
19717
19874
  lines.push("");
19718
19875
  lines.push("| kind | count |");
19719
19876
  lines.push("| --- | --- |");
19720
- for (const k of Object.keys(tree.metadata.kinds).sort()) {
19877
+ for (const k of Object.keys(view2.metadata.kinds).sort()) {
19721
19878
  const key = k;
19722
- const c = tree.metadata.kinds[key];
19879
+ const c = view2.metadata.kinds[key];
19723
19880
  if (c > 0) lines.push(`| ${escapeMarkdown(key)} | ${c} |`);
19724
19881
  }
19725
19882
  lines.push("");
@@ -19728,11 +19885,11 @@ function exportMarkdown(tree, options) {
19728
19885
  lines.push("");
19729
19886
  lines.push("```text");
19730
19887
  lines.push(
19731
- tree.children.length > 0 ? renderTreeAscii(tree.children) : "(no steps)"
19888
+ view2.children.length > 0 ? renderTreeAscii(view2.children) : "(no steps)"
19732
19889
  );
19733
19890
  lines.push("```");
19734
19891
  lines.push("");
19735
- const flat = flattenTree(tree);
19892
+ const flat = flattenTree(view2);
19736
19893
  const errors = flat.filter((n) => n.event.status === "error");
19737
19894
  if (includeErrors && errors.length > 0) {
19738
19895
  lines.push("## Errors");
@@ -29434,11 +29591,14 @@ async function bundleCommand(runIdArg, options = {}) {
29434
29591
  ...finding.action !== void 0 ? { action: finding.action } : {}
29435
29592
  });
29436
29593
  }
29437
- if (artifactTree) {
29438
- artifactTrees.push(artifactTree);
29439
- } else {
29440
- const selected2 = read.runs.find((run) => run.runId === runId);
29441
- if (selected2) artifactTrees.push(selected2);
29594
+ const persistedForRun = artifactEventsByRun.get(runId) ?? read.events.filter((event) => event.runId === runId);
29595
+ const presentationTrees = persistedInspectEventsToRunTrees(persistedForRun, {
29596
+ coalesceLifecycle: true,
29597
+ skipInvalid: true
29598
+ });
29599
+ const presentationTree = presentationTrees.find((candidate) => candidate.runId === runId) ?? presentationTrees[0] ?? artifactTree ?? read.runs.find((run) => run.runId === runId);
29600
+ if (presentationTree) {
29601
+ artifactTrees.push(presentationTree);
29442
29602
  }
29443
29603
  const status = safetyStatusFromAssess(artifactSafety.status);
29444
29604
  checkRuns.push({
@@ -29449,8 +29609,17 @@ async function bundleCommand(runIdArg, options = {}) {
29449
29609
  warnings: artifactSafety.summary.warnings,
29450
29610
  findings: artifactSafety.summary.findings
29451
29611
  });
29452
- const selected = read.runs.find((run) => run.runId === runId);
29453
- const runReport = exportRunTree(selected ?? read.runs[0], {
29612
+ if (!presentationTree) {
29613
+ const message = `No run tree could be built for HTML presentation: ${runId}`;
29614
+ if (options.json) {
29615
+ console.log(writeJson4({ ok: false, error: message }).trimEnd());
29616
+ } else {
29617
+ console.error(`[AgentInspect] bundle failed: ${message}`);
29618
+ }
29619
+ process.exitCode = 1;
29620
+ return;
29621
+ }
29622
+ const runReport = exportRunTree(presentationTree, {
29454
29623
  format: "html",
29455
29624
  redacted: true,
29456
29625
  redactionProfile: toReportProfile(profile)