agent-inspect 4.2.0 → 4.4.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 (97) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/docs/API.md +3 -0
  3. package/docs/CLI.md +36 -0
  4. package/docs/SCHEMA.md +27 -1
  5. package/package.json +1 -1
  6. package/packages/cli/dist/{chunk-5VSPJEZ7.mjs → chunk-ORHDQUT6.mjs} +385 -10
  7. package/packages/cli/dist/chunk-ORHDQUT6.mjs.map +1 -0
  8. package/packages/cli/dist/index.cjs +2548 -1567
  9. package/packages/cli/dist/index.cjs.map +1 -1
  10. package/packages/cli/dist/index.mjs +2028 -1495
  11. package/packages/cli/dist/index.mjs.map +1 -1
  12. package/packages/cli/dist/{src-VQT7QLGV.mjs → src-FJOHPC4X.mjs} +3 -3
  13. package/packages/cli/dist/{src-VQT7QLGV.mjs.map → src-FJOHPC4X.mjs.map} +1 -1
  14. package/packages/core/dist/advanced.cjs +397 -12
  15. package/packages/core/dist/advanced.cjs.map +1 -1
  16. package/packages/core/dist/advanced.d.cts +105 -9
  17. package/packages/core/dist/advanced.d.ts +105 -9
  18. package/packages/core/dist/advanced.mjs +219 -17
  19. package/packages/core/dist/advanced.mjs.map +1 -1
  20. package/packages/core/dist/checks.cjs +117 -18
  21. package/packages/core/dist/checks.cjs.map +1 -1
  22. package/packages/core/dist/checks.d.cts +11 -3
  23. package/packages/core/dist/checks.d.ts +11 -3
  24. package/packages/core/dist/checks.mjs +42 -1
  25. package/packages/core/dist/checks.mjs.map +1 -1
  26. package/packages/core/dist/{chunk-2ZXEMWGK.mjs → chunk-3USJVBLA.mjs} +3 -3
  27. package/packages/core/dist/{chunk-2ZXEMWGK.mjs.map → chunk-3USJVBLA.mjs.map} +1 -1
  28. package/packages/core/dist/{chunk-HY7H3CQM.mjs → chunk-CAWPF22J.mjs} +4 -3
  29. package/packages/core/dist/chunk-CAWPF22J.mjs.map +1 -0
  30. package/packages/core/dist/{chunk-XUNJMAPD.mjs → chunk-KNYL56KZ.mjs} +38 -3
  31. package/packages/core/dist/chunk-KNYL56KZ.mjs.map +1 -0
  32. package/packages/core/dist/{chunk-IZBJAZGF.mjs → chunk-P366CCL2.mjs} +34 -3
  33. package/packages/core/dist/chunk-P366CCL2.mjs.map +1 -0
  34. package/packages/core/dist/{chunk-4JVGFNQH.mjs → chunk-ULCGIRTC.mjs} +95 -8
  35. package/packages/core/dist/chunk-ULCGIRTC.mjs.map +1 -0
  36. package/packages/core/dist/chunk-VFO76UH3.mjs +228 -0
  37. package/packages/core/dist/chunk-VFO76UH3.mjs.map +1 -0
  38. package/packages/core/dist/{chunk-NQZPZ2FN.mjs → chunk-WRE4CNPF.mjs} +6 -3
  39. package/packages/core/dist/chunk-WRE4CNPF.mjs.map +1 -0
  40. package/packages/core/dist/{chunk-A4V2H3YU.mjs → chunk-ZWA5RWMV.mjs} +40 -4
  41. package/packages/core/dist/chunk-ZWA5RWMV.mjs.map +1 -0
  42. package/packages/core/dist/{context-DjY-jvQk.d.cts → context-BbfkMYjz.d.cts} +5 -3
  43. package/packages/core/dist/{context-BDZmi53V.d.ts → context-Cp8NRbBX.d.ts} +5 -3
  44. package/packages/core/dist/diff.cjs.map +1 -1
  45. package/packages/core/dist/diff.d.cts +3 -3
  46. package/packages/core/dist/diff.d.ts +3 -3
  47. package/packages/core/dist/diff.mjs +1 -1
  48. package/packages/core/dist/exporters.cjs +137 -1
  49. package/packages/core/dist/exporters.cjs.map +1 -1
  50. package/packages/core/dist/exporters.d.cts +6 -3
  51. package/packages/core/dist/exporters.d.ts +6 -3
  52. package/packages/core/dist/exporters.mjs +71 -6
  53. package/packages/core/dist/exporters.mjs.map +1 -1
  54. package/packages/core/dist/index.cjs +181 -6
  55. package/packages/core/dist/index.cjs.map +1 -1
  56. package/packages/core/dist/index.d.cts +14 -7
  57. package/packages/core/dist/index.d.ts +14 -7
  58. package/packages/core/dist/index.mjs +51 -7
  59. package/packages/core/dist/index.mjs.map +1 -1
  60. package/packages/core/dist/{inspect-event-CYAV7Hxm.d.cts → inspect-event-IqwOHbGo.d.cts} +1 -1
  61. package/packages/core/dist/{inspect-event-CYAV7Hxm.d.ts → inspect-event-IqwOHbGo.d.ts} +1 -1
  62. package/packages/core/dist/{log-config-BNQ9UTmP.d.cts → log-config-CW8w5YQ9.d.cts} +1 -1
  63. package/packages/core/dist/{log-config-D3Yp1SZN.d.ts → log-config-SRA4xTp8.d.ts} +1 -1
  64. package/packages/core/dist/logs.d.cts +3 -3
  65. package/packages/core/dist/logs.d.ts +3 -3
  66. package/packages/core/dist/{persisted-inspect-event-BLvb0jSX.d.ts → persisted-inspect-event-DruwqARZ.d.ts} +1 -1
  67. package/packages/core/dist/{persisted-inspect-event-D5SjBz9b.d.cts → persisted-inspect-event-Gd-W8vB7.d.cts} +1 -1
  68. package/packages/core/dist/persisted.cjs +67 -1
  69. package/packages/core/dist/persisted.cjs.map +1 -1
  70. package/packages/core/dist/persisted.d.cts +5 -5
  71. package/packages/core/dist/persisted.d.ts +5 -5
  72. package/packages/core/dist/persisted.mjs +3 -3
  73. package/packages/core/dist/readers.cjs +72 -3
  74. package/packages/core/dist/readers.cjs.map +1 -1
  75. package/packages/core/dist/readers.d.cts +2 -2
  76. package/packages/core/dist/readers.d.ts +2 -2
  77. package/packages/core/dist/readers.mjs +5 -5
  78. package/packages/core/dist/readers.mjs.map +1 -1
  79. package/packages/core/dist/{types-CQun9LW6.d.ts → types-B1X87dUW.d.cts} +16 -2
  80. package/packages/core/dist/{types-C-llnPH0.d.cts → types-BVkjSd0q.d.ts} +16 -2
  81. package/packages/core/dist/types-CvsKGweN.d.cts +12 -0
  82. package/packages/core/dist/types-CvsKGweN.d.ts +12 -0
  83. package/packages/core/dist/writers.cjs +2 -1
  84. package/packages/core/dist/writers.cjs.map +1 -1
  85. package/packages/core/dist/writers.d.cts +2 -2
  86. package/packages/core/dist/writers.d.ts +2 -2
  87. package/packages/core/dist/writers.mjs +3 -4
  88. package/packages/core/dist/writers.mjs.map +1 -1
  89. package/packages/cli/dist/chunk-5VSPJEZ7.mjs.map +0 -1
  90. package/packages/core/dist/chunk-4JVGFNQH.mjs.map +0 -1
  91. package/packages/core/dist/chunk-A4V2H3YU.mjs.map +0 -1
  92. package/packages/core/dist/chunk-EDTQHZPM.mjs +0 -88
  93. package/packages/core/dist/chunk-EDTQHZPM.mjs.map +0 -1
  94. package/packages/core/dist/chunk-HY7H3CQM.mjs.map +0 -1
  95. package/packages/core/dist/chunk-IZBJAZGF.mjs.map +0 -1
  96. package/packages/core/dist/chunk-NQZPZ2FN.mjs.map +0 -1
  97. package/packages/core/dist/chunk-XUNJMAPD.mjs.map +0 -1
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { resolveTraceDir, parseDuration, buildSessionIndex, enrichSessionRunRecord, TraceDirectory, loadTraceMetadataList, loadSessionRunRecords, extractMetadata, filterTraces, truncateName, getTraceFilePath, buildRunSummary, formatDuration, formatTimestamp, isAgentInspectTrace, filterMetasBySessionScope, aggregateSessionCheckResults, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, buildActivitySummary, renderActivitySummaryHuman, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, persistedInspectEventsToTraceEvents, renderStepLine, renderErrorLine, getIndent, Redactor, validateEvent, isPersistedInspectEvent, nanoid, resolveRedactionProfile, applyProfileMetadataCaps, extractCorrelationMetadata, truncateStringForProfile, parseTraceJsonl } from './chunk-5VSPJEZ7.mjs';
2
+ import { resolveTraceDir, parseDuration, buildSessionIndex, enrichSessionRunRecord, TraceDirectory, loadTraceMetadataList, loadSessionRunRecords, extractMetadata, filterTraces, truncateName, getTraceFilePath, buildRunSummary, formatDuration, formatTimestamp, isAgentInspectTrace, filterMetasBySessionScope, aggregateSessionCheckResults, resolveBundleRunIds, aggregateBundleSafeStatus, bundleFailsOnSafety, buildPlaceholderArtifact, buildBundleMetadata, buildBundleSummaryMarkdown, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, buildActivitySummary, renderActivitySummaryHuman, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, persistedInspectEventsToTraceEvents, renderStepLine, renderErrorLine, getIndent, Redactor, validateEvent, isPersistedInspectEvent, normalizeBundleOutputPath, defaultBundleOutputPath, summarizeObservedOutcomes, extractOutcomesFromTraceEvents, renderObservedOutcomesMarkdown, renderObservedOutcomesHtml, nanoid, resolveRedactionProfile, applyProfileMetadataCaps, extractCorrelationMetadata, extractOutcomesFromPersistedEvents, outcomesMatchingStatus, truncateStringForProfile, parseTraceJsonl } from './chunk-ORHDQUT6.mjs';
3
3
  import { realpathSync, constants as constants$1 } from 'fs';
4
- import path11 from 'path';
4
+ import path13 from 'path';
5
5
  import { fileURLToPath, pathToFileURL } from 'url';
6
6
  import { Command, Option } from 'commander';
7
- import { unlink, stat, mkdir, writeFile, appendFile, rm, access, readFile, open, readdir, constants } from 'fs/promises';
7
+ import { unlink, stat, mkdir, writeFile, appendFile, readFile, rm, access, open, readdir, constants } from 'fs/promises';
8
8
  import process2, { stdin, stdout } from 'process';
9
9
  import crypto, { createHash } from 'crypto';
10
10
  import { createServer } from 'http';
11
11
  import { createRequire } from 'module';
12
12
 
13
13
  // package.json
14
- var version = "4.2.0";
14
+ var version = "4.4.0";
15
15
 
16
16
  // packages/cli/src/trace-dir-scale.ts
17
17
  var TRACE_COUNT_WARN = 1e3;
@@ -353,6 +353,8 @@ function nodeIdForEvent(event) {
353
353
  case "step_started":
354
354
  case "step_completed":
355
355
  return event.stepId;
356
+ case "outcome_observed":
357
+ return event.outcomeId;
356
358
  default:
357
359
  return "unknown";
358
360
  }
@@ -536,6 +538,39 @@ function traceEventToPersistedInspectEvent(event, options) {
536
538
  error
537
539
  };
538
540
  }
541
+ case "outcome_observed": {
542
+ const tsObserved = toIsoTimestamp(event.observedAt);
543
+ const attributes = compactAttributes({
544
+ legacyEvent: "outcome_observed",
545
+ outcomeId: event.outcomeId,
546
+ outcomeStatus: event.status,
547
+ expectation: event.expectation,
548
+ method: event.method,
549
+ actual: event.actual,
550
+ evidence: event.evidence,
551
+ observedAt: tsObserved.iso,
552
+ invalidTimestamp: tsMain.invalidTimestamp || tsObserved.invalidTimestamp ? true : void 0
553
+ });
554
+ const out = {
555
+ schemaVersion: "0.2",
556
+ eventId,
557
+ runId: event.runId,
558
+ kind: "OUTCOME",
559
+ name: event.name,
560
+ status: event.status === "failed" ? "error" : "ok",
561
+ timestamp: tsMain.iso,
562
+ confidence: "explicit",
563
+ source,
564
+ attributes
565
+ };
566
+ if (event.parentId !== void 0) {
567
+ out.parentId = event.parentId;
568
+ }
569
+ if (event.actual !== void 0) {
570
+ out.outputSummary = event.actual;
571
+ }
572
+ return out;
573
+ }
539
574
  default: {
540
575
  const _exhaustive = event;
541
576
  throw new Error(`Unsupported trace event: ${_exhaustive.event}`);
@@ -895,7 +930,7 @@ function findReaderByFormat(format, readers) {
895
930
  }
896
931
  async function jsonlFilesInDirectory(dirPath) {
897
932
  const entries = await readdir(dirPath, { withFileTypes: true });
898
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path11.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
933
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path13.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
899
934
  }
900
935
  async function resolveInput(input3) {
901
936
  const cached = resolvedInputCache.get(input3);
@@ -1265,7 +1300,7 @@ function sanitizeOpenInferenceAttributes(attributes, pathPrefix) {
1265
1300
  function mapOpenInferenceKind(span, attributes, pathPrefix) {
1266
1301
  const warnings = [];
1267
1302
  const agentInspectKind = attributes["agent_inspect.kind"];
1268
- if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG") {
1303
+ if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG" || agentInspectKind === "OUTCOME") {
1269
1304
  return { kind: agentInspectKind, warnings };
1270
1305
  }
1271
1306
  const rawKind = readStringField(span, ["kind", "span_kind", "spanKind"]) ?? (typeof attributes["openinference.span.kind"] === "string" ? attributes["openinference.span.kind"] : void 0);
@@ -1786,7 +1821,7 @@ function mapOtlpStatus(status) {
1786
1821
  function readOtlpKind(attributes, pathPrefix) {
1787
1822
  const warnings = [];
1788
1823
  const agentInspectKind = attributes["agent_inspect.kind"];
1789
- if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG") {
1824
+ if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG" || agentInspectKind === "OUTCOME") {
1790
1825
  return { kind: agentInspectKind, warnings };
1791
1826
  }
1792
1827
  const operation = attributes["gen_ai.operation.name"];
@@ -3914,6 +3949,40 @@ function redactTraceEventsForReport(events, options) {
3914
3949
  )
3915
3950
  } : {}
3916
3951
  };
3952
+ case "outcome_observed": {
3953
+ const actualAttrs = event.actual !== void 0 ? redactEventAttributes(
3954
+ { value: event.actual },
3955
+ redactor,
3956
+ maxMetadataValueLength,
3957
+ maxPreviewLength
3958
+ ) : void 0;
3959
+ const redactedActual = actualAttrs !== void 0 && "value" in actualAttrs ? actualAttrs.value : void 0;
3960
+ const redactedEvidence = event.evidence !== void 0 ? redactEventAttributes(
3961
+ isRecord6(event.evidence) ? event.evidence : { value: event.evidence },
3962
+ redactor,
3963
+ maxMetadataValueLength,
3964
+ maxPreviewLength
3965
+ ) : void 0;
3966
+ return {
3967
+ ...event,
3968
+ name: truncateStringForProfile(
3969
+ event.name,
3970
+ "name",
3971
+ maxMetadataValueLength,
3972
+ maxPreviewLength
3973
+ ),
3974
+ expectation: truncateStringForProfile(
3975
+ event.expectation,
3976
+ "expectation",
3977
+ maxMetadataValueLength,
3978
+ maxPreviewLength
3979
+ ),
3980
+ ...redactedActual !== void 0 ? { actual: redactedActual } : {},
3981
+ ...redactedEvidence !== void 0 ? { evidence: redactedEvidence } : {}
3982
+ };
3983
+ }
3984
+ default:
3985
+ return event;
3917
3986
  }
3918
3987
  });
3919
3988
  }
@@ -4052,7 +4121,8 @@ function zeroKinds() {
4052
4121
  RESULT: 0,
4053
4122
  ERROR: 0,
4054
4123
  LOGIC: 0,
4055
- LOG: 0
4124
+ LOG: 0,
4125
+ OUTCOME: 0
4056
4126
  };
4057
4127
  }
4058
4128
 
@@ -4361,7 +4431,8 @@ function exportOpenInference(tree, options) {
4361
4431
  ];
4362
4432
  const traceId = hexFrom(`trace:${tree.runId}`, 16);
4363
4433
  const includeAttributes = options?.includeAttributes ?? false;
4364
- const maxLen = options?.maxAttributeLength;
4434
+ const maxLen = options?.maxAttributeLength ?? 500;
4435
+ const pretty = options?.pretty ?? true;
4365
4436
  const spans = [];
4366
4437
  for (const n of flattenTree(tree)) {
4367
4438
  const ev = n.event;
@@ -4435,7 +4506,7 @@ function exportOpenInference(tree, options) {
4435
4506
  };
4436
4507
  return {
4437
4508
  format: "openinference",
4438
- content: JSON.stringify(payload, null, 2 ),
4509
+ content: JSON.stringify(payload, null, pretty ? 2 : void 0),
4439
4510
  contentType: "application/json",
4440
4511
  fileExtension: ".openinference.json",
4441
4512
  warnings
@@ -4469,7 +4540,8 @@ function exportOtlpJson(tree, options) {
4469
4540
  ];
4470
4541
  const traceId = hexFrom2(`trace:${tree.runId}`, 16);
4471
4542
  const includeAttributes = options?.includeAttributes ?? false;
4472
- const maxLen = options?.maxAttributeLength;
4543
+ const maxLen = options?.maxAttributeLength ?? 500;
4544
+ const pretty = options?.pretty ?? true;
4473
4545
  const flat = flattenTree(tree);
4474
4546
  const spans = [];
4475
4547
  for (const n of flat) {
@@ -4565,7 +4637,7 @@ function exportOtlpJson(tree, options) {
4565
4637
  };
4566
4638
  return {
4567
4639
  format: "otlp-json",
4568
- content: JSON.stringify(payload, null, 2 ),
4640
+ content: JSON.stringify(payload, null, pretty ? 2 : void 0),
4569
4641
  contentType: "application/json",
4570
4642
  fileExtension: ".otlp.json",
4571
4643
  warnings
@@ -4788,9 +4860,9 @@ function mergeExportDefaults(options) {
4788
4860
  includeMetadata: options.includeMetadata ?? true,
4789
4861
  includeAttributes: options.includeAttributes ?? false,
4790
4862
  includeErrors: options.includeErrors ?? true,
4791
- pretty: options.pretty,
4792
- redacted: options.redacted,
4793
- maxAttributeLength: options.maxAttributeLength,
4863
+ pretty: options.pretty ?? true,
4864
+ redacted: options.redacted ?? true,
4865
+ maxAttributeLength: options.maxAttributeLength ?? 500,
4794
4866
  redactionProfile: options.redactionProfile ?? "local"
4795
4867
  };
4796
4868
  }
@@ -4853,9 +4925,34 @@ footer{margin-top:2rem;font-size:0.85rem;color:#555}
4853
4925
  `.trim();
4854
4926
  function buildRunReport(events, options) {
4855
4927
  const profile = options.redactionProfile ?? "local";
4928
+ const section = options.section ?? "all";
4856
4929
  const safeEvents = redactTraceEventsForReport(events, {
4857
4930
  redactionProfile: profile
4858
4931
  });
4932
+ const outcomes = summarizeObservedOutcomes(
4933
+ extractOutcomesFromTraceEvents(safeEvents)
4934
+ );
4935
+ const observationsMarkdown = renderObservedOutcomesMarkdown(outcomes);
4936
+ const observationsHtml = renderObservedOutcomesHtml(outcomes);
4937
+ if (section === "observations") {
4938
+ if (options.format === "markdown") {
4939
+ return {
4940
+ format: "markdown",
4941
+ content: `## Observed outcomes
4942
+
4943
+ ${observationsMarkdown}
4944
+ `,
4945
+ contentType: "text/markdown",
4946
+ fileExtension: ".md"
4947
+ };
4948
+ }
4949
+ return {
4950
+ format: "html",
4951
+ content: `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>Observed outcomes</title><style>${REPORT_HTML_CSS}</style></head><body><h1>Observed outcomes</h1>${observationsHtml}</body></html>`,
4952
+ contentType: "text/html",
4953
+ fileExtension: ".html"
4954
+ };
4955
+ }
4859
4956
  const whatSummary = buildRunWhatSummary(safeEvents);
4860
4957
  const whatText = renderRunWhat(whatSummary, {
4861
4958
  correlation: options.correlation !== false
@@ -4889,6 +4986,10 @@ function buildRunReport(events, options) {
4889
4986
  "```text",
4890
4987
  timelineText,
4891
4988
  "```",
4989
+ "",
4990
+ "## Observed outcomes",
4991
+ "",
4992
+ observationsMarkdown,
4892
4993
  ""
4893
4994
  ];
4894
4995
  if (tail2) {
@@ -4932,6 +5033,7 @@ function buildRunReport(events, options) {
4932
5033
  <p class="note">Generated locally by AgentInspect. Review for sensitive data before sharing.</p>
4933
5034
  <section class="what"><h2>What happened</h2><pre>${escapeHtml(whatText)}</pre></section>
4934
5035
  <section class="timeline"><h2>Timeline</h2><pre>${escapeHtml(timelineText)}</pre></section>
5036
+ <section class="observations"><h2>Observed outcomes</h2>${observationsHtml}</section>
4935
5037
  ${treeSection || '<section class="tree"><h2>Execution tree</h2><p>No steps recorded.</p></section>'}
4936
5038
  ${errorsSection}
4937
5039
  ${attrsSection}
@@ -5024,9 +5126,9 @@ Trace directory: ${traceDir}`);
5024
5126
  if (validation !== void 0 && !validation.ok) {
5025
5127
  process.exitCode = 1;
5026
5128
  }
5027
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path11.resolve(options.output.trim()) : void 0;
5129
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
5028
5130
  if (outPath !== void 0) {
5029
- await mkdir(path11.dirname(outPath), { recursive: true });
5131
+ await mkdir(path13.dirname(outPath), { recursive: true });
5030
5132
  await writeFile(outPath, result.content, "utf-8");
5031
5133
  const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
5032
5134
  console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
@@ -5203,13 +5305,13 @@ function pairSteps(left, right) {
5203
5305
  return pairs;
5204
5306
  }
5205
5307
  function compareLeafSteps(L, R, segments, opts, out) {
5206
- const path18 = buildPath(segments);
5308
+ const path19 = buildPath(segments);
5207
5309
  if (L.name !== R.name) {
5208
5310
  out.push({
5209
5311
  kind: "structure",
5210
5312
  severity: "warning",
5211
5313
  message: "Step name differs",
5212
- path: path18,
5314
+ path: path19,
5213
5315
  left: L.name,
5214
5316
  right: R.name
5215
5317
  });
@@ -5219,7 +5321,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5219
5321
  kind: "step-type",
5220
5322
  severity: "warning",
5221
5323
  message: "Step type differs",
5222
- path: path18,
5324
+ path: path19,
5223
5325
  left: L.type,
5224
5326
  right: R.type
5225
5327
  });
@@ -5229,7 +5331,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5229
5331
  kind: "step-status",
5230
5332
  severity: "warning",
5231
5333
  message: "Step status differs",
5232
- path: path18,
5334
+ path: path19,
5233
5335
  left: L.status,
5234
5336
  right: R.status
5235
5337
  });
@@ -5241,7 +5343,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5241
5343
  kind: "error",
5242
5344
  severity: "error",
5243
5345
  message: "Step error message differs",
5244
- path: path18,
5346
+ path: path19,
5245
5347
  left: le || void 0,
5246
5348
  right: re || void 0
5247
5349
  });
@@ -5259,7 +5361,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5259
5361
  kind: "duration",
5260
5362
  severity: "info",
5261
5363
  message: "Step duration differs",
5262
- path: path18,
5364
+ path: path19,
5263
5365
  left: ld,
5264
5366
  right: rd
5265
5367
  });
@@ -5272,7 +5374,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5272
5374
  kind: "metadata",
5273
5375
  severity: "info",
5274
5376
  message: "Step metadata differs",
5275
- path: path18,
5377
+ path: path19,
5276
5378
  left: L.metadata,
5277
5379
  right: R.metadata
5278
5380
  });
@@ -5284,7 +5386,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5284
5386
  kind: "output",
5285
5387
  severity: "info",
5286
5388
  message: "Output preview differs",
5287
- path: path18,
5389
+ path: path19,
5288
5390
  left: L.outputPreview,
5289
5391
  right: R.outputPreview
5290
5392
  });
@@ -5444,11 +5546,11 @@ function diffRuns(left, right, options) {
5444
5546
  }
5445
5547
 
5446
5548
  // packages/core/src/diff/renderer.ts
5447
- function formatPath(path18) {
5448
- if (path18 === void 0 || path18.path.length === 0) {
5549
+ function formatPath(path19) {
5550
+ if (path19 === void 0 || path19.path.length === 0) {
5449
5551
  return "(run)";
5450
5552
  }
5451
- return path18.path.map((s) => s.name).join(" > ");
5553
+ return path19.path.map((s) => s.name).join(" > ");
5452
5554
  }
5453
5555
  function formatValue(v, verbose) {
5454
5556
  if (v === void 0) return "(undefined)";
@@ -5783,7 +5885,8 @@ async function searchCommand(options = {}) {
5783
5885
  duration: options.duration,
5784
5886
  limit: parseLimit2(options.limit),
5785
5887
  ...sessionId ? { session: sessionId } : {},
5786
- ...options.correlateGroup ? { correlateGroup: true } : {}
5888
+ ...options.correlateGroup ? { correlateGroup: true } : {},
5889
+ ...options.observation ? { observation: options.observation } : {}
5787
5890
  });
5788
5891
  if (options.json) {
5789
5892
  console.log(JSON.stringify(results, null, 2));
@@ -5826,7 +5929,7 @@ function indexedToMetadata(row, traceDir) {
5826
5929
  endedAt: row.endedAt ?? void 0,
5827
5930
  durationMs: row.durationMs ?? void 0,
5828
5931
  eventCount: 0,
5829
- filePath: path11.join(traceDir, row.file),
5932
+ filePath: path13.join(traceDir, row.file),
5830
5933
  fileSize: 0,
5831
5934
  createdAt: new Date(row.mtimeMs)
5832
5935
  };
@@ -5855,7 +5958,7 @@ async function newestTraceMtimeMs(traceDir) {
5855
5958
  }
5856
5959
  async function loadSessionRuns(traceDir) {
5857
5960
  try {
5858
- const mod = await import('./src-VQT7QLGV.mjs');
5961
+ const mod = await import('./src-FJOHPC4X.mjs');
5859
5962
  const dbPath = mod.resolveIndexDbPath(traceDir);
5860
5963
  const status = mod.indexStatus(dbPath);
5861
5964
  if (!status.healthy) {
@@ -6300,11 +6403,12 @@ async function reportCommand(runId, options = {}) {
6300
6403
  includeAttributes: options.includeAttributes === true,
6301
6404
  includeErrors: options.noErrors !== true,
6302
6405
  redactionProfile,
6303
- correlation: !options.noCorrelation
6406
+ correlation: !options.noCorrelation,
6407
+ ...options.section ? { section: options.section } : {}
6304
6408
  });
6305
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path11.resolve(options.output.trim()) : void 0;
6409
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
6306
6410
  if (outPath !== void 0) {
6307
- await mkdir(path11.dirname(outPath), { recursive: true });
6411
+ await mkdir(path13.dirname(outPath), { recursive: true });
6308
6412
  await writeFile(outPath, result.content, "utf-8");
6309
6413
  console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
6310
6414
  }
@@ -6551,17 +6655,17 @@ function applyRule(rule, value, replacement) {
6551
6655
  }
6552
6656
  return value;
6553
6657
  }
6554
- function childPath(path18, key) {
6658
+ function childPath(path19, key) {
6555
6659
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
6556
- return path18 ? `${path18}.${key}` : key;
6660
+ return path19 ? `${path19}.${key}` : key;
6557
6661
  }
6558
- return `${path18 || "$"}[${JSON.stringify(key)}]`;
6662
+ return `${path19 || "$"}[${JSON.stringify(key)}]`;
6559
6663
  }
6560
- function indexPath(path18, index) {
6561
- return `${path18 || "$"}[${index}]`;
6664
+ function indexPath(path19, index) {
6665
+ return `${path19 || "$"}[${index}]`;
6562
6666
  }
6563
- function makeFinding(path18, detector, action, matchKind, severity = "warning", preview) {
6564
- return preview === void 0 ? { path: path18, detector, action, severity, matchKind } : { path: path18, detector, action, severity, matchKind, preview };
6667
+ function makeFinding(path19, detector, action, matchKind, severity = "warning", preview) {
6668
+ return preview === void 0 ? { path: path19, detector, action, severity, matchKind } : { path: path19, detector, action, severity, matchKind, preview };
6565
6669
  }
6566
6670
  function createRedactionProfile(profile = "local") {
6567
6671
  switch (profile) {
@@ -6630,11 +6734,11 @@ var Redactor2 = class {
6630
6734
  #recordFinding(state, finding) {
6631
6735
  if (this.#collectFindings) state.findings.push(finding);
6632
6736
  }
6633
- #redactValue(value, key, path18, depth, state) {
6737
+ #redactValue(value, key, path19, depth, state) {
6634
6738
  if (depth > this.#maxDepth) {
6635
6739
  this.#recordFinding(
6636
6740
  state,
6637
- makeFinding(path18, "structure.maxDepth", "truncate", "value", "warning")
6741
+ makeFinding(path19, "structure.maxDepth", "truncate", "value", "warning")
6638
6742
  );
6639
6743
  return "[Truncated]";
6640
6744
  }
@@ -6643,19 +6747,19 @@ var Redactor2 = class {
6643
6747
  if (rule) {
6644
6748
  this.#recordFinding(
6645
6749
  state,
6646
- makeFinding(path18, `key.${rule.key}`, actionForRule(rule), "key", "warning")
6750
+ makeFinding(path19, `key.${rule.key}`, actionForRule(rule), "key", "warning")
6647
6751
  );
6648
6752
  return applyRule(rule, value, this.#replacement);
6649
6753
  }
6650
6754
  }
6651
6755
  for (const detector of this.#detectors) {
6652
- const detections = detector.detect({ path: path18, key, value });
6756
+ const detections = detector.detect({ path: path19, key, value });
6653
6757
  for (const detection of detections) {
6654
6758
  const action = detection.action ?? "replace";
6655
6759
  this.#recordFinding(
6656
6760
  state,
6657
6761
  makeFinding(
6658
- path18,
6762
+ path19,
6659
6763
  detector.id,
6660
6764
  action,
6661
6765
  detection.matchKind ?? detector.matchKind ?? "custom",
@@ -6673,7 +6777,7 @@ var Redactor2 = class {
6673
6777
  const out = [];
6674
6778
  state.seen.set(value, out);
6675
6779
  value.forEach((item, index) => {
6676
- out[index] = this.#redactValue(item, void 0, indexPath(path18, index), depth + 1, state);
6780
+ out[index] = this.#redactValue(item, void 0, indexPath(path19, index), depth + 1, state);
6677
6781
  });
6678
6782
  return out;
6679
6783
  }
@@ -6685,7 +6789,7 @@ var Redactor2 = class {
6685
6789
  out[entryKey] = this.#redactValue(
6686
6790
  entryValue,
6687
6791
  entryKey,
6688
- childPath(path18 === "$" ? "" : path18, entryKey),
6792
+ childPath(path19 === "$" ? "" : path19, entryKey),
6689
6793
  depth + 1,
6690
6794
  state
6691
6795
  );
@@ -6811,6 +6915,9 @@ function redactDocument(content, profile) {
6811
6915
  return redactJsonlText(content, profile);
6812
6916
  }
6813
6917
  }
6918
+ function redactTraceContent(content, profile) {
6919
+ return redactDocument(content, profile);
6920
+ }
6814
6921
  async function redactCommand(target, options = {}, stdin = process.stdin) {
6815
6922
  const profile = parseRedactionProfile3(options.profile);
6816
6923
  const source = await contentFromTarget(target, options, stdin);
@@ -7132,14 +7239,14 @@ function uniqueSorted(values) {
7132
7239
  return [...new Set(values)].sort();
7133
7240
  }
7134
7241
  function isWithinDirectory(child, parent) {
7135
- const relative = path11.relative(parent, child);
7136
- return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
7242
+ const relative = path13.relative(parent, child);
7243
+ return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
7137
7244
  }
7138
7245
  async function resolveOutputPath(inputPath, output2, force) {
7139
7246
  if (output2 === void 0 || output2.trim() === "") return void 0;
7140
- const inputAbs = path11.resolve(inputPath);
7141
- const outputAbs = path11.resolve(output2.trim());
7142
- const inputDir = path11.dirname(inputAbs);
7247
+ const inputAbs = path13.resolve(inputPath);
7248
+ const outputAbs = path13.resolve(output2.trim());
7249
+ const inputDir = path13.dirname(inputAbs);
7143
7250
  if (!isWithinDirectory(outputAbs, inputDir)) {
7144
7251
  throw new Error("Refusing to write migrated output outside the input directory.");
7145
7252
  }
@@ -7260,7 +7367,7 @@ async function migrateCommand(input3, options = {}) {
7260
7367
  process.exitCode = 1;
7261
7368
  return;
7262
7369
  }
7263
- const inputPath = path11.resolve(input3.trim());
7370
+ const inputPath = path13.resolve(input3.trim());
7264
7371
  const dryRun = options.dryRun === true;
7265
7372
  if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
7266
7373
  console.error("migrate requires --dry-run or --output <path>.");
@@ -7279,7 +7386,7 @@ async function migrateCommand(input3, options = {}) {
7279
7386
  );
7280
7387
  const result = await buildMigration(inputPath, outputPath);
7281
7388
  if (!dryRun && outputPath !== void 0) {
7282
- await mkdir(path11.dirname(outputPath), { recursive: true });
7389
+ await mkdir(path13.dirname(outputPath), { recursive: true });
7283
7390
  await writeFile(outputPath, result.content, "utf-8");
7284
7391
  }
7285
7392
  printSummary2(result, dryRun);
@@ -7557,7 +7664,7 @@ function stripPrefix(name, prefixes) {
7557
7664
  }
7558
7665
  return name;
7559
7666
  }
7560
- function eventEvidence(event, path18) {
7667
+ function eventEvidence(event, path19) {
7561
7668
  return {
7562
7669
  runId: event.runId,
7563
7670
  eventId: event.eventId,
@@ -7567,7 +7674,7 @@ function eventEvidence(event, path18) {
7567
7674
  kind: event.kind,
7568
7675
  name: event.name,
7569
7676
  status: event.status,
7570
- ...path18 ? { path: path18 } : {}
7677
+ ...path19 ? { path: path19 } : {}
7571
7678
  };
7572
7679
  }
7573
7680
  function runEvidence(run) {
@@ -7630,9 +7737,9 @@ function eventEndMs(event) {
7630
7737
  function normalizedKey(value) {
7631
7738
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
7632
7739
  }
7633
- function lastPathSegment(path18) {
7634
- const parts = path18.split(".");
7635
- return parts[parts.length - 1] ?? path18;
7740
+ function lastPathSegment(path19) {
7741
+ const parts = path19.split(".");
7742
+ return parts[parts.length - 1] ?? path19;
7636
7743
  }
7637
7744
  function valueType(value) {
7638
7745
  if (Array.isArray(value)) return "array";
@@ -7646,12 +7753,12 @@ function serializedByteLength(value) {
7646
7753
  return void 0;
7647
7754
  }
7648
7755
  }
7649
- function pushValueEntries(entries, event, value, path18, key, depth = 0) {
7650
- entries.push({ event, path: path18, key, value });
7756
+ function pushValueEntries(entries, event, value, path19, key, depth = 0) {
7757
+ entries.push({ event, path: path19, key, value });
7651
7758
  if (depth >= 8) return;
7652
7759
  if (Array.isArray(value)) {
7653
7760
  for (const [index, item] of value.entries()) {
7654
- pushValueEntries(entries, event, item, `${path18}.${index}`, String(index), depth + 1);
7761
+ pushValueEntries(entries, event, item, `${path19}.${index}`, String(index), depth + 1);
7655
7762
  }
7656
7763
  return;
7657
7764
  }
@@ -7661,7 +7768,7 @@ function pushValueEntries(entries, event, value, path18, key, depth = 0) {
7661
7768
  entries,
7662
7769
  event,
7663
7770
  value[nestedKey],
7664
- `${path18}.${nestedKey}`,
7771
+ `${path19}.${nestedKey}`,
7665
7772
  nestedKey,
7666
7773
  depth + 1
7667
7774
  );
@@ -7742,9 +7849,9 @@ function eventDurationMs(event) {
7742
7849
  }
7743
7850
  function treeShape(nodes) {
7744
7851
  const lines = [];
7745
- const visit = (node, path18) => {
7746
- lines.push(`${path18}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
7747
- node.children.forEach((child, index) => visit(child, `${path18}.${index}`));
7852
+ const visit = (node, path19) => {
7853
+ lines.push(`${path19}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
7854
+ node.children.forEach((child, index) => visit(child, `${path19}.${index}`));
7748
7855
  };
7749
7856
  nodes.forEach((node, index) => visit(node, String(index)));
7750
7857
  return lines;
@@ -7793,9 +7900,9 @@ function retrievalShape(context) {
7793
7900
  function guardrailShape(context) {
7794
7901
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
7795
7902
  }
7796
- function firstEvidenceForKind(context, kind, path18) {
7903
+ function firstEvidenceForKind(context, kind, path19) {
7797
7904
  const event = context.events.find((candidate) => candidate.kind === kind);
7798
- return event ? [eventEvidence(event, path18)] : runEvidence(context.selectedRun);
7905
+ return event ? [eventEvidence(event, path19)] : runEvidence(context.selectedRun);
7799
7906
  }
7800
7907
  function baselineDiffFinding(message, evidence, expected, actual) {
7801
7908
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -8145,13 +8252,13 @@ function createStructureCycleRule() {
8145
8252
  const seenCycles = /* @__PURE__ */ new Set();
8146
8253
  const findings = [];
8147
8254
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
8148
- const path18 = [];
8255
+ const path19 = [];
8149
8256
  const seenAt = /* @__PURE__ */ new Map();
8150
8257
  let current = event;
8151
8258
  while (current) {
8152
8259
  const existing = seenAt.get(current.eventId);
8153
8260
  if (existing !== void 0) {
8154
- const cycle = path18.slice(existing);
8261
+ const cycle = path19.slice(existing);
8155
8262
  const key = cycle.map((item) => item.eventId).sort().join("\0");
8156
8263
  if (!seenCycles.has(key)) {
8157
8264
  seenCycles.add(key);
@@ -8167,8 +8274,8 @@ function createStructureCycleRule() {
8167
8274
  }
8168
8275
  break;
8169
8276
  }
8170
- seenAt.set(current.eventId, path18.length);
8171
- path18.push(current);
8277
+ seenAt.set(current.eventId, path19.length);
8278
+ path19.push(current);
8172
8279
  current = current.parentId ? byId.get(current.parentId) : void 0;
8173
8280
  }
8174
8281
  }
@@ -8601,6 +8708,40 @@ function createBaselineRegressionRule(options) {
8601
8708
  }
8602
8709
  };
8603
8710
  }
8711
+ function createObservedOutcomeRule(options = {}) {
8712
+ const failOn = options.failOn ?? ["failed"];
8713
+ return {
8714
+ id: "outcome.status",
8715
+ category: "run",
8716
+ defaultSeverity: "error",
8717
+ evaluate(context) {
8718
+ const outcomes = extractOutcomesFromPersistedEvents(context.events);
8719
+ const matching = outcomesMatchingStatus(outcomes, failOn);
8720
+ if (matching.length === 0) return [];
8721
+ return [
8722
+ failFinding(
8723
+ "outcome.status",
8724
+ `Observed outcome count ${matching.length} matched [${failOn.join(", ")}].`,
8725
+ matching.map((outcome) => ({
8726
+ runId: outcome.runId,
8727
+ eventId: outcome.outcomeId,
8728
+ ...outcome.parentId !== void 0 ? { parentId: outcome.parentId } : {},
8729
+ kind: "OUTCOME",
8730
+ name: outcome.name,
8731
+ status: outcome.status,
8732
+ path: `outcome.${outcome.name}`
8733
+ })),
8734
+ { failOn },
8735
+ matching.map((outcome) => ({
8736
+ name: outcome.name,
8737
+ status: outcome.status,
8738
+ expectation: outcome.expectation
8739
+ }))
8740
+ )
8741
+ ];
8742
+ }
8743
+ };
8744
+ }
8604
8745
  function runTraceChecks(input3, options = {}) {
8605
8746
  const selected = resolveSelectedRun(input3, options.runId);
8606
8747
  if (selected.diagnostics.length > 0) {
@@ -8965,23 +9106,23 @@ function evaluatePromptInjection(text, options = {}) {
8965
9106
  }
8966
9107
  return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
8967
9108
  }
8968
- function validateSchemaField(value, field, path18, evidence) {
9109
+ function validateSchemaField(value, field, path19, evidence) {
8969
9110
  const ruleId = "guardrail.structured-output";
8970
9111
  if (field.type) {
8971
9112
  const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
8972
9113
  if (actual !== field.type) {
8973
- evidence.push({ ruleId, path: path18, preview: `expected ${field.type}, got ${actual}` });
9114
+ evidence.push({ ruleId, path: path19, preview: `expected ${field.type}, got ${actual}` });
8974
9115
  return;
8975
9116
  }
8976
9117
  }
8977
9118
  if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
8978
- evidence.push({ ruleId, path: path18, preview: "value not in enum" });
9119
+ evidence.push({ ruleId, path: path19, preview: "value not in enum" });
8979
9120
  }
8980
9121
  if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
8981
9122
  const record = value;
8982
9123
  for (const key of field.required) {
8983
9124
  if (!(key in record)) {
8984
- evidence.push({ ruleId, path: `${path18}.${key}`, preview: "missing required key" });
9125
+ evidence.push({ ruleId, path: `${path19}.${key}`, preview: "missing required key" });
8985
9126
  }
8986
9127
  }
8987
9128
  }
@@ -9308,7 +9449,7 @@ function asConfig(value) {
9308
9449
  }
9309
9450
  async function loadConfig(configPath) {
9310
9451
  if (configPath === void 0) return {};
9311
- const extension = path11.extname(configPath);
9452
+ const extension = path13.extname(configPath);
9312
9453
  if (TS_CONFIG_EXTENSIONS.has(extension)) {
9313
9454
  throw new Error(
9314
9455
  "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -9317,7 +9458,7 @@ async function loadConfig(configPath) {
9317
9458
  if (!CONFIG_EXTENSIONS.has(extension)) {
9318
9459
  throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
9319
9460
  }
9320
- const absolute = path11.resolve(configPath);
9461
+ const absolute = path13.resolve(configPath);
9321
9462
  if (extension === ".json") {
9322
9463
  const raw = await readFile(absolute, "utf-8");
9323
9464
  return asConfig(JSON.parse(raw));
@@ -9372,6 +9513,22 @@ function buildRules(config, options) {
9372
9513
  if (options.detectStalls) {
9373
9514
  rules.push(createStallDetectionRule({ requireEndedAt: true }));
9374
9515
  }
9516
+ if (options.failOnObservation !== void 0 && options.failOnObservation.trim() !== "") {
9517
+ try {
9518
+ const statuses = options.failOnObservation.split(",").map((value) => value.trim()).filter((value) => value !== "").map((value) => {
9519
+ if (value === "passed" || value === "failed" || value === "unknown" || value === "skipped") {
9520
+ return value;
9521
+ }
9522
+ throw new Error(`unsupported status "${value}"`);
9523
+ });
9524
+ rules.push(createObservedOutcomeRule({ failOn: statuses }));
9525
+ } catch (error) {
9526
+ const message = error instanceof Error ? error.message : String(error);
9527
+ diagnostics.push(
9528
+ diagnostic2("AI_CHECK_INVALID_ARGUMENTS", `--fail-on-observation: ${message}`)
9529
+ );
9530
+ }
9531
+ }
9375
9532
  if (run.maxDepth !== void 0) {
9376
9533
  rules.push(createRunDepthRule({ maxDepth: run.maxDepth }));
9377
9534
  }
@@ -9466,10 +9623,10 @@ function printHuman(result) {
9466
9623
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
9467
9624
  }
9468
9625
  for (const finding of result.findings) {
9469
- const path18 = finding.evidence[0]?.path;
9626
+ const path19 = finding.evidence[0]?.path;
9470
9627
  const run = finding.evidence[0]?.runId;
9471
9628
  const runPrefix = run ? `[${run}] ` : "";
9472
- console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
9629
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
9473
9630
  }
9474
9631
  }
9475
9632
  function readErrorResult(error) {
@@ -9669,7 +9826,7 @@ function createViewerServer(options = {}) {
9669
9826
  return sendJson(res, 200, {
9670
9827
  ok: true,
9671
9828
  readOnly: true,
9672
- traceDir: path11.resolve(traceDir)
9829
+ traceDir: path13.resolve(traceDir)
9673
9830
  });
9674
9831
  }
9675
9832
  const td = new TraceDirectory({ dir: traceDir });
@@ -9687,7 +9844,7 @@ function createViewerServer(options = {}) {
9687
9844
  runId: meta.runId,
9688
9845
  name: meta.name,
9689
9846
  status: meta.status,
9690
- file: path11.basename(meta.filePath),
9847
+ file: path13.basename(meta.filePath),
9691
9848
  startedAt: meta.startedAt,
9692
9849
  durationMs: meta.durationMs
9693
9850
  }))
@@ -9802,7 +9959,7 @@ function startViewerServer(options = {}) {
9802
9959
  resolve({
9803
9960
  host,
9804
9961
  port: resolvedPort,
9805
- traceDir: path11.resolve(traceDir),
9962
+ traceDir: path13.resolve(traceDir),
9806
9963
  url: `http://${host}:${resolvedPort}`
9807
9964
  });
9808
9965
  });
@@ -10007,10 +10164,10 @@ async function evalRun(input3, options = {}) {
10007
10164
  diagnostics: []
10008
10165
  };
10009
10166
  }
10010
- function evidenceForRun(run, path18) {
10011
- return [{ runId: run.runId, ...path18 !== void 0 ? { path: path18 } : {} }];
10167
+ function evidenceForRun(run, path19) {
10168
+ return [{ runId: run.runId, ...path19 !== void 0 ? { path: path19 } : {} }];
10012
10169
  }
10013
- function evidenceForEvent(event, path18) {
10170
+ function evidenceForEvent(event, path19) {
10014
10171
  return [
10015
10172
  {
10016
10173
  runId: event.runId,
@@ -10018,7 +10175,7 @@ function evidenceForEvent(event, path18) {
10018
10175
  ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
10019
10176
  kind: event.kind,
10020
10177
  name: event.name,
10021
- ...path18 !== void 0 ? { path: path18 } : {}
10178
+ ...path19 !== void 0 ? { path: path19 } : {}
10022
10179
  }
10023
10180
  ];
10024
10181
  }
@@ -10176,9 +10333,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
10176
10333
  function tokenize(text) {
10177
10334
  return [...text.toLowerCase().matchAll(/[a-z0-9][a-z0-9'-]{2,}/g)].map((match) => match[0].replace(/^['-]+|['-]+$/g, "")).filter((token) => token.length > 2 && !STOP_WORDS.has(token));
10178
10335
  }
10179
- function firstEvidence(fields, run, path18) {
10336
+ function firstEvidence(fields, run, path19) {
10180
10337
  const first = fields[0];
10181
- return first === void 0 ? evidenceForRun(run, path18) : evidenceForEvent(first.node.event, first.path);
10338
+ return first === void 0 ? evidenceForRun(run, path19) : evidenceForEvent(first.node.event, first.path);
10182
10339
  }
10183
10340
  function collectSourceIds(nodes, keys) {
10184
10341
  const wanted = keySet(keys);
@@ -10555,8 +10712,8 @@ function renderEvalMarkdown(result) {
10555
10712
  if (result.findings.length > 0) {
10556
10713
  lines.push("", "## Findings");
10557
10714
  for (const finding of result.findings) {
10558
- const path18 = finding.evidence[0]?.path;
10559
- lines.push(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
10715
+ const path19 = finding.evidence[0]?.path;
10716
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10560
10717
  }
10561
10718
  }
10562
10719
  return `${lines.join("\n")}
@@ -10603,7 +10760,7 @@ function asConfig2(value) {
10603
10760
  }
10604
10761
  async function loadConfig2(configPath) {
10605
10762
  if (configPath === void 0) return {};
10606
- const extension = path11.extname(configPath);
10763
+ const extension = path13.extname(configPath);
10607
10764
  if (TS_CONFIG_EXTENSIONS2.has(extension)) {
10608
10765
  throw new Error(
10609
10766
  "TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -10612,7 +10769,7 @@ async function loadConfig2(configPath) {
10612
10769
  if (!CONFIG_EXTENSIONS2.has(extension)) {
10613
10770
  throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
10614
10771
  }
10615
- const absolute = path11.resolve(configPath);
10772
+ const absolute = path13.resolve(configPath);
10616
10773
  if (extension === ".json") {
10617
10774
  const raw = await readFile(absolute, "utf-8");
10618
10775
  return asConfig2(JSON.parse(raw));
@@ -10749,8 +10906,8 @@ function printHuman2(result) {
10749
10906
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10750
10907
  }
10751
10908
  for (const finding of result.findings) {
10752
- const path18 = finding.evidence[0]?.path;
10753
- console.log(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
10909
+ const path19 = finding.evidence[0]?.path;
10910
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10754
10911
  }
10755
10912
  }
10756
10913
  function readErrorResult2(error) {
@@ -10988,8 +11145,8 @@ function printHuman3(result) {
10988
11145
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10989
11146
  }
10990
11147
  for (const finding of result.findings) {
10991
- const path18 = finding.evidence[0]?.path;
10992
- console.log(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
11148
+ const path19 = finding.evidence[0]?.path;
11149
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10993
11150
  }
10994
11151
  console.log(`Note: ${result.note}`);
10995
11152
  }
@@ -11035,6 +11192,38 @@ function scanCommand(target, options = {}, stdin = process.stdin) {
11035
11192
  function verifySafeCommand(target, options = {}, stdin = process.stdin) {
11036
11193
  return safetyCommand("verify-safe", target, options, stdin);
11037
11194
  }
11195
+ function assessOpenedTrace(read, options = {}) {
11196
+ try {
11197
+ const rules = buildSafetyRules(options);
11198
+ const checkResult = runTraceChecks(
11199
+ { read },
11200
+ {
11201
+ rules,
11202
+ ...options.runId !== void 0 ? { runId: options.runId } : {},
11203
+ ...options.run !== void 0 ? { runId: options.run } : {}
11204
+ }
11205
+ );
11206
+ const detectorFindings = checkResult.diagnostics.length === 0 ? redactionDetectorFindings(read, checkResult.runId) : [];
11207
+ return resultFromParts({
11208
+ command: "verify-safe",
11209
+ format: checkResult.format,
11210
+ runId: checkResult.runId,
11211
+ findings: [...checkResult.findings, ...detectorFindings],
11212
+ diagnostics: [
11213
+ ...checkResult.diagnostics.map(diagnosticFromCheck),
11214
+ ...warningDiagnostics(read.warnings, read.unsupportedFields)
11215
+ ],
11216
+ warnings: read.warnings,
11217
+ unsupportedFields: read.unsupportedFields
11218
+ });
11219
+ } catch (error) {
11220
+ return messageStartsWithDash(error) ? invalidArgumentResult("verify-safe", error) : readErrorResult3("verify-safe", error);
11221
+ }
11222
+ }
11223
+ function messageStartsWithDash(error) {
11224
+ const message = error instanceof Error ? error.message : String(error);
11225
+ return message.startsWith("--");
11226
+ }
11038
11227
  var NOTE = "Generated locally by AgentInspect. Artifacts are best-effort summaries, not compliance or security certification.";
11039
11228
  var SAFETY_RULES = [
11040
11229
  createSafetyRawContentRule(),
@@ -11108,8 +11297,8 @@ function renderCheckSection(result) {
11108
11297
  `Diagnostics: ${result.diagnostics.length}`
11109
11298
  ];
11110
11299
  for (const finding of result.findings.slice(0, 10)) {
11111
- const path18 = finding.evidence[0]?.path ?? "(run)";
11112
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path18})`);
11300
+ const path19 = finding.evidence[0]?.path ?? "(run)";
11301
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path19})`);
11113
11302
  }
11114
11303
  for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
11115
11304
  lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
@@ -11187,8 +11376,8 @@ function renderHtml(trace, check, diff) {
11187
11376
  `;
11188
11377
  }
11189
11378
  async function writeArtifact(outputDir, relativePath, content, files) {
11190
- const outPath = path11.join(outputDir, relativePath);
11191
- await mkdir(path11.dirname(outPath), { recursive: true });
11379
+ const outPath = path13.join(outputDir, relativePath);
11380
+ await mkdir(path13.dirname(outPath), { recursive: true });
11192
11381
  await writeFile(outPath, content, "utf-8");
11193
11382
  files.push(relativePath);
11194
11383
  }
@@ -11204,7 +11393,7 @@ function manifestStatus(check, diff) {
11204
11393
  return "ok";
11205
11394
  }
11206
11395
  async function artifactsCommand(target, options = {}, stdin = process.stdin) {
11207
- const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path11.resolve(options.outputDir.trim()) : "";
11396
+ const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path13.resolve(options.outputDir.trim()) : "";
11208
11397
  if (outputDir === "") {
11209
11398
  console.error("--output-dir is required.");
11210
11399
  process.exitCode = 1;
@@ -11270,8 +11459,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
11270
11459
  await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
11271
11460
  const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
11272
11461
  if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
11273
- await mkdir(path11.dirname(path11.resolve(summaryTarget)), { recursive: true });
11274
- await appendFile(path11.resolve(summaryTarget), `
11462
+ await mkdir(path13.dirname(path13.resolve(summaryTarget)), { recursive: true });
11463
+ await appendFile(path13.resolve(summaryTarget), `
11275
11464
  ${renderMarkdown(trace, check, diff)}`, "utf-8");
11276
11465
  }
11277
11466
  const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
@@ -11290,10 +11479,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
11290
11479
  findings: diff?.findings.length ?? 0,
11291
11480
  diagnostics: diff?.diagnostics.length ?? 0
11292
11481
  },
11293
- ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path11.resolve(summaryTarget) } : {},
11482
+ ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path13.resolve(summaryTarget) } : {},
11294
11483
  note: NOTE
11295
11484
  };
11296
- await writeFile(path11.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
11485
+ await writeFile(path13.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
11297
11486
  if (options.json === true) {
11298
11487
  console.log(writeJson3(manifest).trimEnd());
11299
11488
  } else {
@@ -11304,1568 +11493,1893 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
11304
11493
  }
11305
11494
  }
11306
11495
  }
11307
- function validateReporterArtifactPath(options) {
11308
- const outputDir = path11.resolve(options.outputDir);
11309
- const diagnostics = [];
11310
- const rawPath = options.relativePath;
11311
- if (rawPath.length === 0) {
11312
- diagnostics.push({
11313
- code: "artifact_path_empty",
11314
- severity: "error",
11315
- message: "Reporter artifact path must not be empty."
11316
- });
11317
- return { ok: false, outputDir, diagnostics };
11318
- }
11319
- if (rawPath.includes("\0")) {
11320
- diagnostics.push({
11321
- code: "invalid_artifact_path",
11322
- severity: "error",
11323
- message: "Reporter artifact path must not contain null bytes.",
11324
- target: rawPath
11325
- });
11326
- return { ok: false, outputDir, diagnostics };
11327
- }
11328
- if (path11.isAbsolute(rawPath) || path11.win32.isAbsolute(rawPath)) {
11329
- diagnostics.push({
11330
- code: "artifact_path_absolute",
11331
- severity: "error",
11332
- message: "Reporter artifact path must be relative.",
11333
- target: rawPath
11334
- });
11335
- return { ok: false, outputDir, diagnostics };
11336
- }
11337
- const normalized = path11.posix.normalize(rawPath.replace(/\\/g, "/"));
11338
- const segments = normalized.split("/");
11339
- if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
11340
- diagnostics.push({
11341
- code: "artifact_path_escape",
11342
- severity: "error",
11343
- message: "Reporter artifact path must stay under the output directory.",
11344
- target: rawPath
11345
- });
11346
- return { ok: false, outputDir, diagnostics };
11347
- }
11348
- const absolutePath = path11.resolve(outputDir, normalized);
11349
- const relFromOutput = path11.relative(outputDir, absolutePath);
11350
- if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path11.isAbsolute(relFromOutput)) {
11351
- diagnostics.push({
11352
- code: "artifact_path_escape",
11353
- severity: "error",
11354
- message: "Reporter artifact path resolved outside the output directory.",
11355
- target: rawPath
11356
- });
11357
- return { ok: false, outputDir, diagnostics };
11358
- }
11496
+
11497
+ // packages/core/src/workspace/types.ts
11498
+ var WORKSPACE_SCHEMA_VERSION = "1.0";
11499
+ var WORKSPACE_DIR_NAME = ".agent-inspect";
11500
+ var WORKSPACE_MANIFEST_FILENAME = "workspace.json";
11501
+
11502
+ // packages/core/src/workspace/manifest.ts
11503
+ var DEFAULT_WORKSPACE_LAYOUT = {
11504
+ traceDirs: ["runs"],
11505
+ reportsDir: "reports",
11506
+ artifactsDir: "artifacts",
11507
+ bundlesDir: "bundles",
11508
+ notesDir: "notes"
11509
+ };
11510
+ var DEFAULT_REDACTION_PROFILE = "share";
11511
+ var REDACTION_PROFILES = [
11512
+ "local",
11513
+ "share",
11514
+ "strict"
11515
+ ];
11516
+ var INDEX_TYPES = ["none", "sqlite", "custom"];
11517
+ var MAX_WORKSPACE_MANIFEST_BYTES = 64 * 1024;
11518
+ function createDefaultWorkspaceManifest(options) {
11519
+ const project = typeof options.project === "string" ? options.project.trim() : "";
11520
+ const index = {
11521
+ enabled: options.index?.enabled ?? false,
11522
+ type: options.index?.type ?? "none",
11523
+ ...options.index?.path !== void 0 ? { path: options.index.path } : {}
11524
+ };
11359
11525
  return {
11360
- ok: true,
11361
- outputDir,
11362
- relativePath: normalized,
11363
- absolutePath,
11364
- diagnostics
11526
+ schemaVersion: WORKSPACE_SCHEMA_VERSION,
11527
+ project,
11528
+ createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
11529
+ traceDirs: options.traceDirs ? [...options.traceDirs] : [...DEFAULT_WORKSPACE_LAYOUT.traceDirs],
11530
+ reportsDir: options.reportsDir ?? DEFAULT_WORKSPACE_LAYOUT.reportsDir,
11531
+ artifactsDir: options.artifactsDir ?? DEFAULT_WORKSPACE_LAYOUT.artifactsDir,
11532
+ bundlesDir: options.bundlesDir ?? DEFAULT_WORKSPACE_LAYOUT.bundlesDir,
11533
+ notesDir: options.notesDir ?? DEFAULT_WORKSPACE_LAYOUT.notesDir,
11534
+ redactionProfile: options.redactionProfile ?? DEFAULT_REDACTION_PROFILE,
11535
+ index
11365
11536
  };
11366
11537
  }
11367
-
11368
- // packages/cli/src/ci-summary.ts
11369
- var NOTE2 = "Generated locally by AgentInspect from reporter artifact manifests. Trace contents are not embedded.";
11370
- var MAX_TEXT = 180;
11371
- var FRAMEWORKS = /* @__PURE__ */ new Set(["vitest", "jest", "manual"]);
11372
- var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
11373
- var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
11374
- var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
11375
- var REDACTION_PROFILES = /* @__PURE__ */ new Set(["local", "share", "strict"]);
11376
- function stable6(value) {
11377
- if (Array.isArray(value)) return value.map(stable6);
11378
- if (value === null || typeof value !== "object") return value;
11379
- const record = value;
11380
- return Object.fromEntries(
11381
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
11382
- );
11383
- }
11384
- function writeJson4(value) {
11385
- return `${JSON.stringify(stable6(value), null, 2)}
11386
- `;
11387
- }
11388
- function isObject(value) {
11538
+ function isPlainObject(value) {
11389
11539
  return typeof value === "object" && value !== null && !Array.isArray(value);
11390
11540
  }
11391
- function readString(value, label) {
11541
+ function isSafeRelativeWorkspacePath(p) {
11542
+ if (typeof p !== "string") return false;
11543
+ const trimmed = p.trim();
11544
+ if (trimmed === "") return false;
11545
+ if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return false;
11546
+ if (/^[a-zA-Z]:/.test(trimmed)) return false;
11547
+ const segments = trimmed.split(/[/\\]+/);
11548
+ return !segments.some((seg) => seg === "..");
11549
+ }
11550
+ function validateDirField(value, field, errors) {
11392
11551
  if (typeof value !== "string" || value.trim() === "") {
11393
- throw new Error(`${label} must be a non-empty string.`);
11552
+ errors.push(`${field} must be a non-empty string`);
11553
+ return;
11394
11554
  }
11395
- return safeText(value);
11396
- }
11397
- function readOptionalString(value) {
11398
- return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
11399
- }
11400
- function readFramework(value) {
11401
- const framework = readString(value, "manifest.framework");
11402
- if (!FRAMEWORKS.has(framework)) {
11403
- throw new Error(`Unsupported reporter framework: ${framework}.`);
11555
+ if (!isSafeRelativeWorkspacePath(value)) {
11556
+ errors.push(
11557
+ `${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
11558
+ );
11404
11559
  }
11405
- return framework;
11406
11560
  }
11407
- function readStatus(value) {
11408
- const status = readString(value, "result.status");
11409
- if (!STATUSES.has(status)) {
11410
- throw new Error(`Unsupported reporter test status: ${status}.`);
11561
+ function validateIndex(value, errors) {
11562
+ if (!isPlainObject(value)) {
11563
+ errors.push("index must be an object");
11564
+ return void 0;
11411
11565
  }
11412
- return status;
11413
- }
11414
- function readArtifact(value, index) {
11415
- if (!isObject(value)) throw new Error(`manifest.artifacts[${index}] must be an object.`);
11416
- const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
11417
- const format = readString(value.format, `manifest.artifacts[${index}].format`);
11418
- const redactionProfile = readString(
11419
- value.redactionProfile,
11420
- `manifest.artifacts[${index}].redactionProfile`
11421
- );
11422
- if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
11423
- if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
11424
- if (!REDACTION_PROFILES.has(redactionProfile)) {
11425
- throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
11566
+ if (typeof value.enabled !== "boolean") {
11567
+ errors.push("index.enabled must be a boolean");
11426
11568
  }
11427
- const artifactPath = readString(value.path, `manifest.artifacts[${index}].path`);
11428
- const pathCheck = validateReporterArtifactPath({
11429
- outputDir: process.cwd(),
11430
- relativePath: artifactPath
11431
- });
11432
- if (!pathCheck.ok || pathCheck.relativePath === void 0) {
11433
- throw new Error(`Unsafe reporter artifact path: ${artifactPath}.`);
11569
+ if (!INDEX_TYPES.includes(value.type)) {
11570
+ errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
11571
+ }
11572
+ if (value.path !== void 0 && !isSafeRelativeWorkspacePath(value.path)) {
11573
+ errors.push(
11574
+ 'index.path must be a relative path inside the workspace (no absolute paths or ".." traversal)'
11575
+ );
11434
11576
  }
11577
+ if (errors.length > 0) return void 0;
11435
11578
  return {
11436
- kind,
11437
- path: pathCheck.relativePath,
11438
- format,
11439
- redactionProfile
11579
+ enabled: value.enabled,
11580
+ type: value.type,
11581
+ ...value.path !== void 0 ? { path: value.path } : {}
11440
11582
  };
11441
11583
  }
11442
- function readArtifacts(value, label) {
11443
- if (value === void 0) return [];
11444
- if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
11445
- return value.map((item, index) => readArtifact(item, index));
11584
+ function validateWorkspaceManifest(input3) {
11585
+ const errors = [];
11586
+ const warnings = [];
11587
+ if (!isPlainObject(input3)) {
11588
+ return { ok: false, errors: ["manifest must be an object"], warnings };
11589
+ }
11590
+ if (input3.schemaVersion !== WORKSPACE_SCHEMA_VERSION) {
11591
+ errors.push(
11592
+ `schemaVersion must be "${WORKSPACE_SCHEMA_VERSION}" (received ${JSON.stringify(
11593
+ input3.schemaVersion
11594
+ )})`
11595
+ );
11596
+ }
11597
+ if (typeof input3.project !== "string" || input3.project.trim() === "") {
11598
+ errors.push("project must be a non-empty string");
11599
+ }
11600
+ if (typeof input3.createdAt !== "string" || input3.createdAt.trim() === "") {
11601
+ errors.push("createdAt must be a non-empty ISO-8601 string");
11602
+ } else if (Number.isNaN(Date.parse(input3.createdAt))) {
11603
+ errors.push("createdAt must be a valid ISO-8601 date string");
11604
+ }
11605
+ if (!Array.isArray(input3.traceDirs) || input3.traceDirs.length === 0) {
11606
+ errors.push("traceDirs must be a non-empty array");
11607
+ } else {
11608
+ input3.traceDirs.forEach((dir, i) => {
11609
+ if (typeof dir !== "string" || dir.trim() === "") {
11610
+ errors.push(`traceDirs[${i}] must be a non-empty string`);
11611
+ } else if (!isSafeRelativeWorkspacePath(dir)) {
11612
+ errors.push(
11613
+ `traceDirs[${i}] must be a relative path inside the workspace (no absolute paths or ".." traversal)`
11614
+ );
11615
+ }
11616
+ });
11617
+ }
11618
+ validateDirField(input3.reportsDir, "reportsDir", errors);
11619
+ validateDirField(input3.artifactsDir, "artifactsDir", errors);
11620
+ validateDirField(input3.bundlesDir, "bundlesDir", errors);
11621
+ validateDirField(input3.notesDir, "notesDir", errors);
11622
+ if (!REDACTION_PROFILES.includes(input3.redactionProfile)) {
11623
+ errors.push(`redactionProfile must be one of: ${REDACTION_PROFILES.join(", ")}`);
11624
+ }
11625
+ const indexErrors = [];
11626
+ const index = validateIndex(input3.index, indexErrors);
11627
+ errors.push(...indexErrors);
11628
+ if (index && index.type !== "none" && !index.enabled) {
11629
+ warnings.push(`index.type is "${index.type}" but index.enabled is false`);
11630
+ }
11631
+ if (errors.length > 0 || index === void 0) {
11632
+ return { ok: false, errors, warnings };
11633
+ }
11634
+ const manifest = {
11635
+ schemaVersion: WORKSPACE_SCHEMA_VERSION,
11636
+ project: input3.project.trim(),
11637
+ createdAt: input3.createdAt,
11638
+ traceDirs: input3.traceDirs.map((d) => d.trim()),
11639
+ reportsDir: input3.reportsDir.trim(),
11640
+ artifactsDir: input3.artifactsDir.trim(),
11641
+ bundlesDir: input3.bundlesDir.trim(),
11642
+ notesDir: input3.notesDir.trim(),
11643
+ redactionProfile: input3.redactionProfile,
11644
+ index
11645
+ };
11646
+ return { ok: true, manifest, errors, warnings };
11446
11647
  }
11447
- function readDiagnosticsCount(value, label) {
11448
- if (value === void 0) return 0;
11449
- if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
11450
- return value.length;
11648
+ function parseWorkspaceManifest(json) {
11649
+ const warnings = [];
11650
+ if (typeof json !== "string") {
11651
+ return { ok: false, errors: ["manifest input must be a string"], warnings };
11652
+ }
11653
+ if (json.length > MAX_WORKSPACE_MANIFEST_BYTES) {
11654
+ return {
11655
+ ok: false,
11656
+ errors: [
11657
+ `manifest exceeds maximum size of ${MAX_WORKSPACE_MANIFEST_BYTES} bytes`
11658
+ ],
11659
+ warnings
11660
+ };
11661
+ }
11662
+ let parsed;
11663
+ try {
11664
+ parsed = JSON.parse(json);
11665
+ } catch {
11666
+ return { ok: false, errors: ["manifest is not valid JSON"], warnings };
11667
+ }
11668
+ return validateWorkspaceManifest(parsed);
11451
11669
  }
11452
- function readResult(value, index) {
11453
- if (!isObject(value)) throw new Error(`manifest.results[${index}] must be an object.`);
11454
- const file = readOptionalString(value.file);
11455
- const tracePath = readOptionalString(value.tracePath);
11670
+ function serializeWorkspaceManifest(manifest) {
11671
+ return `${JSON.stringify(manifest, null, 2)}
11672
+ `;
11673
+ }
11674
+ var INDEX_DIR_NAME = "index";
11675
+ function resolveWorkspaceLocation(cwd = process.cwd()) {
11676
+ const projectRoot = path13.resolve(cwd);
11677
+ const workspaceDir = path13.join(projectRoot, WORKSPACE_DIR_NAME);
11456
11678
  return {
11457
- testId: readString(value.testId, `manifest.results[${index}].testId`),
11458
- name: readString(value.name, `manifest.results[${index}].name`),
11459
- ...file === void 0 ? {} : { file },
11460
- status: readStatus(value.status),
11461
- ...tracePath === void 0 ? {} : { tracePath },
11462
- artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
11463
- diagnostics: readDiagnosticsCount(
11464
- value.diagnostics,
11465
- `manifest.results[${index}].diagnostics`
11466
- )
11679
+ projectRoot,
11680
+ workspaceDir,
11681
+ manifestPath: path13.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
11467
11682
  };
11468
11683
  }
11469
- function readManifestDocument(value) {
11470
- if (!isObject(value)) throw new Error("Reporter manifest file must contain a JSON object.");
11471
- const candidate = isObject(value.manifest) ? value.manifest : value;
11472
- if (!isObject(candidate)) throw new Error("Reporter manifest must be a JSON object.");
11473
- const schemaVersion = readString(candidate.schemaVersion, "manifest.schemaVersion");
11474
- if (schemaVersion !== "0.1") {
11475
- throw new Error(`Unsupported reporter manifest schemaVersion: ${schemaVersion}.`);
11684
+ function resolveInsideWorkspace(workspaceDir, relative) {
11685
+ const base = path13.resolve(workspaceDir);
11686
+ const resolved = path13.resolve(base, relative);
11687
+ const rel = path13.relative(base, resolved);
11688
+ if (rel === "" || rel === "." || !rel.startsWith("..") && !path13.isAbsolute(rel)) {
11689
+ return resolved;
11476
11690
  }
11477
- if (!Array.isArray(candidate.results)) {
11478
- throw new Error("manifest.results must be an array.");
11691
+ throw new Error(
11692
+ `Workspace path "${relative}" resolves outside the workspace directory`
11693
+ );
11694
+ }
11695
+ async function pathExists(p) {
11696
+ try {
11697
+ await access(p);
11698
+ return true;
11699
+ } catch {
11700
+ return false;
11479
11701
  }
11480
- const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
11481
- const results = candidate.results.map((item, index) => readResult(item, index));
11482
- const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
11483
- const manifest = {
11484
- framework: readFramework(candidate.framework),
11485
- generatedAt: readString(candidate.generatedAt, "manifest.generatedAt"),
11486
- results,
11487
- artifacts
11488
- };
11489
- return {
11490
- packageName: readOptionalString(value.package),
11491
- manifest,
11492
- diagnostics
11493
- };
11494
11702
  }
11495
- function cwdRelative(filePath) {
11496
- const relative = path11.relative(process.cwd(), path11.resolve(filePath)).replace(/\\/g, "/");
11497
- if (relative === "" || relative.startsWith("../") || path11.isAbsolute(relative)) {
11498
- return path11.basename(filePath);
11703
+ async function isWritable(p) {
11704
+ try {
11705
+ await access(p, constants$1.W_OK);
11706
+ return true;
11707
+ } catch {
11708
+ return false;
11499
11709
  }
11500
- return relative;
11501
11710
  }
11502
- async function readReporterManifest(filePath) {
11503
- const absolute = path11.resolve(filePath);
11504
- const raw = await readFile(absolute, "utf-8");
11505
- const document = readManifestDocument(JSON.parse(raw));
11506
- const manifest = document.manifest;
11507
- const results = manifest.results.map((result) => ({
11508
- testId: safeText(result.testId),
11509
- name: safeText(result.name),
11510
- ...result.file === void 0 ? {} : { file: safeText(path11.basename(result.file)) },
11511
- status: result.status,
11512
- ...result.tracePath === void 0 ? {} : { tracePath: safeText(path11.basename(result.tracePath)) },
11513
- artifacts: result.artifacts,
11514
- diagnostics: result.diagnostics
11515
- }));
11516
- return {
11517
- ...document.packageName === void 0 ? {} : { packageName: document.packageName },
11518
- manifestFile: cwdRelative(absolute),
11519
- framework: manifest.framework,
11520
- generatedAt: manifest.generatedAt,
11521
- results,
11522
- artifacts: manifest.artifacts,
11523
- diagnostics: document.diagnostics
11524
- };
11711
+ async function listJsonl(dir) {
11712
+ try {
11713
+ const entries = await readdir(dir);
11714
+ return entries.filter((f) => f.endsWith(".jsonl"));
11715
+ } catch {
11716
+ return [];
11717
+ }
11525
11718
  }
11526
- function summarize3(manifests) {
11527
- const summary = {
11528
- manifests: manifests.length,
11529
- tests: 0,
11530
- failed: 0,
11531
- passed: 0,
11532
- skipped: 0,
11533
- todo: 0,
11534
- artifacts: 0,
11535
- diagnostics: 0
11536
- };
11537
- for (const manifest of manifests) {
11538
- summary.artifacts += manifest.artifacts.length;
11539
- summary.diagnostics += manifest.diagnostics;
11540
- for (const result of manifest.results) {
11541
- summary.tests += 1;
11542
- if (result.status === "failed") summary.failed += 1;
11543
- else if (result.status === "passed") summary.passed += 1;
11544
- else if (result.status === "skipped") summary.skipped += 1;
11545
- else summary.todo += 1;
11546
- summary.artifacts += result.artifacts.length;
11547
- summary.diagnostics += result.diagnostics;
11548
- }
11719
+ async function countFiles(dir) {
11720
+ try {
11721
+ const entries = await readdir(dir, { withFileTypes: true });
11722
+ return entries.filter((e) => e.isFile()).length;
11723
+ } catch {
11724
+ return 0;
11725
+ }
11726
+ }
11727
+ async function readWorkspaceManifestFile(location) {
11728
+ let raw;
11729
+ try {
11730
+ raw = await readFile(location.manifestPath, "utf-8");
11731
+ } catch {
11732
+ return { exists: false, ok: false, errors: ["workspace.json not found"], warnings: [] };
11549
11733
  }
11734
+ const parsed = parseWorkspaceManifest(raw);
11550
11735
  return {
11551
- status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
11552
- manifests,
11553
- summary,
11554
- note: NOTE2
11736
+ exists: true,
11737
+ ok: parsed.ok,
11738
+ ...parsed.manifest ? { manifest: parsed.manifest } : {},
11739
+ errors: parsed.errors,
11740
+ warnings: parsed.warnings
11555
11741
  };
11556
11742
  }
11557
- function markdownCell(value) {
11558
- return safeText(String(value ?? "unknown")).replaceAll("|", "\\|").replace(/\r?\n/g, " ");
11559
- }
11560
- function renderMarkdown2(result) {
11561
- const lines = [
11562
- "# AgentInspect CI Summary",
11563
- "",
11564
- NOTE2,
11565
- "",
11566
- "| Field | Value |",
11567
- "| --- | --- |",
11568
- `| Status | ${result.status} |`,
11569
- `| Manifests | ${result.summary.manifests} |`,
11570
- `| Tests | ${result.summary.tests} |`,
11571
- `| Failed | ${result.summary.failed} |`,
11572
- `| Passed | ${result.summary.passed} |`,
11573
- `| Skipped | ${result.summary.skipped} |`,
11574
- `| Todo | ${result.summary.todo} |`,
11575
- `| Artifacts | ${result.summary.artifacts} |`,
11576
- `| Diagnostics | ${result.summary.diagnostics} |`,
11577
- "",
11578
- "## Tests",
11579
- "",
11580
- "| Framework | Status | Test | File | Trace | Artifacts |",
11581
- "| --- | --- | --- | --- | --- | --- |"
11582
- ];
11583
- const rows = result.manifests.flatMap(
11584
- (manifest) => manifest.results.map((test) => ({
11585
- framework: manifest.framework,
11586
- test
11587
- }))
11588
- );
11589
- if (rows.length === 0) {
11590
- lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
11743
+ async function createWorkspace(options = {}) {
11744
+ const location = resolveWorkspaceLocation(options.cwd);
11745
+ const dryRun = options.dryRun === true;
11746
+ const existing = await readWorkspaceManifestFile(location);
11747
+ const topLevelTraces = await listJsonl(location.workspaceDir);
11748
+ const detectedExistingTraces = topLevelTraces.length > 0;
11749
+ let manifest;
11750
+ let created;
11751
+ let adopted;
11752
+ if (existing.exists && existing.ok && existing.manifest) {
11753
+ manifest = existing.manifest;
11754
+ created = false;
11755
+ adopted = true;
11591
11756
  } else {
11592
- for (const row of rows) {
11593
- lines.push(
11594
- `| ${markdownCell(row.framework)} | ${markdownCell(row.test.status)} | ${markdownCell(row.test.name)} | ${markdownCell(row.test.file)} | ${markdownCell(row.test.tracePath)} | ${row.test.artifacts.length} |`
11595
- );
11596
- }
11757
+ const project = options.project?.trim() || path13.basename(location.projectRoot) || "workspace";
11758
+ const traceDirs = detectedExistingTraces ? ["runs", "."] : ["runs"];
11759
+ manifest = createDefaultWorkspaceManifest({
11760
+ project,
11761
+ traceDirs,
11762
+ ...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {}
11763
+ });
11764
+ created = true;
11765
+ adopted = detectedExistingTraces || existing.exists && !existing.ok;
11597
11766
  }
11598
- lines.push("", "## Manifests", "", "| Framework | File | Generated | Artifacts |", "| --- | --- | --- | --- |");
11599
- for (const manifest of result.manifests) {
11600
- lines.push(
11601
- `| ${markdownCell(manifest.framework)} | ${markdownCell(manifest.manifestFile)} | ${markdownCell(manifest.generatedAt)} | ${manifest.artifacts.length} |`
11602
- );
11767
+ const relDirs = uniqueDirs([
11768
+ ...manifest.traceDirs.filter((d) => d !== "."),
11769
+ manifest.reportsDir,
11770
+ manifest.artifactsDir,
11771
+ manifest.bundlesDir,
11772
+ manifest.notesDir,
11773
+ INDEX_DIR_NAME
11774
+ ]);
11775
+ const createdDirs = [];
11776
+ for (const rel of relDirs) {
11777
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
11778
+ if (await pathExists(abs)) continue;
11779
+ createdDirs.push(rel);
11780
+ if (!dryRun) await mkdir(abs, { recursive: true });
11603
11781
  }
11604
- lines.push("", "## Artifacts", "", "| Framework | Kind | Path | Format | Profile |", "| --- | --- | --- | --- | --- |");
11605
- const artifacts = result.manifests.flatMap(
11606
- (manifest) => manifest.artifacts.map((artifact) => ({ framework: manifest.framework, artifact }))
11607
- );
11608
- if (artifacts.length === 0) {
11609
- lines.push("| unknown | unknown | No artifacts found | unknown | unknown |");
11610
- } else {
11611
- for (const row of artifacts) {
11612
- lines.push(
11613
- `| ${markdownCell(row.framework)} | ${markdownCell(row.artifact.kind)} | ${markdownCell(row.artifact.path)} | ${markdownCell(row.artifact.format)} | ${markdownCell(row.artifact.redactionProfile)} |`
11614
- );
11615
- }
11782
+ if (!dryRun && created) {
11783
+ await mkdir(location.workspaceDir, { recursive: true });
11784
+ await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
11616
11785
  }
11617
- lines.push("");
11618
- return `${lines.join("\n")}
11619
- `;
11786
+ return {
11787
+ location,
11788
+ manifest,
11789
+ created,
11790
+ adopted,
11791
+ createdDirs,
11792
+ detectedExistingTraces,
11793
+ dryRun
11794
+ };
11620
11795
  }
11621
- function safeText(value) {
11622
- const compact = value.replace(/\s+/g, " ").trim();
11623
- const redacted = compact.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(
11624
- /\b(?:api[_-]?key|authorization|token|secret|password)\s*[:=]\s*[^,\s]+/gi,
11625
- "$1=[REDACTED]"
11626
- );
11627
- if (redacted.length <= MAX_TEXT) return redacted;
11628
- return `${redacted.slice(0, MAX_TEXT - 12)}...[truncated]`;
11796
+ function uniqueDirs(dirs) {
11797
+ const seen = /* @__PURE__ */ new Set();
11798
+ const out = [];
11799
+ for (const d of dirs) {
11800
+ const t = d.trim();
11801
+ if (t === "" || t === "." || seen.has(t)) continue;
11802
+ seen.add(t);
11803
+ out.push(t);
11804
+ }
11805
+ return out;
11629
11806
  }
11630
- async function ciSummaryCommand(manifestPaths, options = {}) {
11631
- if (manifestPaths.length === 0) {
11632
- console.error("At least one reporter manifest path is required.");
11633
- process.exitCode = 1;
11634
- return;
11807
+ async function getWorkspaceStatus(location, manifest) {
11808
+ let traceFiles = 0;
11809
+ for (const rel of manifest.traceDirs) {
11810
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
11811
+ traceFiles += (await listJsonl(abs)).length;
11635
11812
  }
11636
- let result;
11637
- try {
11638
- const manifests = [];
11639
- for (const manifestPath of manifestPaths) {
11640
- manifests.push(await readReporterManifest(manifestPath));
11813
+ const reports = await countFiles(
11814
+ resolveInsideWorkspace(location.workspaceDir, manifest.reportsDir)
11815
+ );
11816
+ const artifacts = await countFiles(
11817
+ resolveInsideWorkspace(location.workspaceDir, manifest.artifactsDir)
11818
+ );
11819
+ const bundles = await countFiles(
11820
+ resolveInsideWorkspace(location.workspaceDir, manifest.bundlesDir)
11821
+ );
11822
+ const notes = await countFiles(
11823
+ resolveInsideWorkspace(location.workspaceDir, manifest.notesDir)
11824
+ );
11825
+ const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
11826
+ return {
11827
+ project: manifest.project,
11828
+ traceFiles,
11829
+ reports,
11830
+ artifacts,
11831
+ bundles,
11832
+ notes,
11833
+ index: {
11834
+ enabled: manifest.index.enabled,
11835
+ type: manifest.index.type,
11836
+ exists: await pathExists(indexPath2)
11641
11837
  }
11642
- manifests.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
11643
- result = summarize3(manifests);
11644
- } catch (error) {
11645
- const message = error instanceof Error ? error.message : String(error);
11646
- console.error(`[AgentInspect] ci-summary failed: ${message}`);
11647
- process.exitCode = 1;
11648
- return;
11838
+ };
11839
+ }
11840
+ async function doctorWorkspace(location) {
11841
+ const checks2 = [];
11842
+ const manifestResult = await readWorkspaceManifestFile(location);
11843
+ if (!manifestResult.exists) {
11844
+ checks2.push({
11845
+ id: "manifest",
11846
+ status: "fail",
11847
+ message: "workspace.json not found (run `agent-inspect workspace init`)"
11848
+ });
11849
+ return { ok: false, checks: checks2 };
11649
11850
  }
11650
- const markdown = renderMarkdown2(result);
11651
- const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path11.resolve(options.output.trim()) : void 0;
11652
- if (outputPath !== void 0) {
11653
- await mkdir(path11.dirname(outputPath), { recursive: true });
11654
- await writeFile(outputPath, markdown, "utf-8");
11851
+ if (!manifestResult.ok || !manifestResult.manifest) {
11852
+ checks2.push({
11853
+ id: "manifest",
11854
+ status: "fail",
11855
+ message: `workspace.json is invalid: ${manifestResult.errors.join("; ")}`
11856
+ });
11857
+ return { ok: false, checks: checks2 };
11655
11858
  }
11656
- const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
11657
- if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
11658
- const summaryPath = path11.resolve(summaryTarget);
11659
- await mkdir(path11.dirname(summaryPath), { recursive: true });
11660
- await appendFile(summaryPath, `
11661
- ${markdown}`, "utf-8");
11859
+ const manifest = manifestResult.manifest;
11860
+ checks2.push({ id: "manifest", status: "pass", message: "workspace.json is valid" });
11861
+ for (const warning of manifestResult.warnings) {
11862
+ checks2.push({ id: "manifest-warning", status: "warn", message: warning });
11662
11863
  }
11663
- if (options.json === true) {
11664
- console.log(writeJson4(result).trimEnd());
11665
- } else if (outputPath !== void 0) {
11666
- console.log(`Wrote AgentInspect CI summary to ${outputPath}`);
11667
- console.log(`Status: ${result.status}`);
11668
- } else {
11669
- console.log(markdown.trimEnd());
11864
+ const dirFields = [
11865
+ ...manifest.traceDirs.filter((d) => d !== ".").map((d, i) => [`traceDir[${i}]`, d]),
11866
+ ["reportsDir", manifest.reportsDir],
11867
+ ["artifactsDir", manifest.artifactsDir],
11868
+ ["bundlesDir", manifest.bundlesDir],
11869
+ ["notesDir", manifest.notesDir]
11870
+ ];
11871
+ for (const [id, rel] of dirFields) {
11872
+ let abs;
11873
+ try {
11874
+ abs = resolveInsideWorkspace(location.workspaceDir, rel);
11875
+ } catch (error) {
11876
+ checks2.push({
11877
+ id,
11878
+ status: "fail",
11879
+ message: error instanceof Error ? error.message : String(error)
11880
+ });
11881
+ continue;
11882
+ }
11883
+ if (!await pathExists(abs)) {
11884
+ checks2.push({ id, status: "warn", message: `${rel}/ does not exist yet` });
11885
+ } else if (!await isWritable(abs)) {
11886
+ checks2.push({ id, status: "fail", message: `${rel}/ is not writable` });
11887
+ } else {
11888
+ checks2.push({ id, status: "pass", message: `${rel}/ is present and writable` });
11889
+ }
11670
11890
  }
11671
- }
11672
- var CONFIG_FILE = "agent-inspect.config.ts";
11673
- var TRACE_DIR = ".agent-inspect";
11674
- var GITKEEP = ".agent-inspect/.gitkeep";
11675
- function normalizeFramework(value) {
11676
- const raw = (value ?? "custom").trim();
11677
- if (raw === "ai-sdk" || raw === "openai-agents" || raw === "langchain" || raw === "custom") {
11678
- return raw;
11891
+ let newestTraceMtime = 0;
11892
+ for (const rel of manifest.traceDirs) {
11893
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
11894
+ for (const file of await listJsonl(abs)) {
11895
+ try {
11896
+ const s = await stat(path13.join(abs, file));
11897
+ newestTraceMtime = Math.max(newestTraceMtime, s.mtimeMs);
11898
+ } catch {
11899
+ checks2.push({ id: "trace-readability", status: "warn", message: `cannot stat ${rel}/${file}` });
11900
+ }
11901
+ }
11679
11902
  }
11680
- throw new Error(
11681
- "Unsupported --framework value. Use ai-sdk, openai-agents, langchain, or custom."
11682
- );
11683
- }
11684
- function configTemplate(framework) {
11685
- const base = `/**
11686
- * AgentInspect local config (metadata-only capture by default).
11687
- * See https://github.com/rajudandigam/agent-inspect/blob/main/docs/SAFE-TRACE-SHARING.md
11688
- */
11689
- export const agentInspectConfig = {
11690
- traceDir: ".agent-inspect",
11691
- enabled: process.env.AGENT_INSPECT !== "0",
11692
- redactionProfile: "local" as const,
11693
- };
11694
- `;
11695
- if (framework === "custom") return base;
11696
- return `${base}
11697
- export const framework = "${framework}" as const;
11698
- `;
11903
+ if (manifest.index.enabled) {
11904
+ const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
11905
+ if (!await pathExists(indexPath2)) {
11906
+ checks2.push({ id: "index", status: "warn", message: "index enabled but not built" });
11907
+ } else {
11908
+ try {
11909
+ const s = await stat(indexPath2);
11910
+ if (newestTraceMtime > s.mtimeMs) {
11911
+ checks2.push({ id: "index", status: "warn", message: "index is stale (traces are newer)" });
11912
+ } else {
11913
+ checks2.push({ id: "index", status: "pass", message: "index is present" });
11914
+ }
11915
+ } catch {
11916
+ checks2.push({ id: "index", status: "warn", message: "cannot stat index" });
11917
+ }
11918
+ }
11919
+ }
11920
+ const ok = !checks2.some((c) => c.status === "fail");
11921
+ return { ok, checks: checks2 };
11699
11922
  }
11700
- function demoTemplate(framework) {
11701
- switch (framework) {
11702
- case "ai-sdk":
11703
- return `/**
11704
- * AI SDK starter \u2014 metadata-only telemetry (no real model calls in this demo).
11705
- * Install: npm install agent-inspect @agent-inspect/ai-sdk ai
11706
- */
11707
- import { inspectRun, step } from "agent-inspect";
11708
-
11709
- async function main() {
11710
- await inspectRun("ai-sdk-demo", async () => {
11711
- await step.tool("mock-generate", async () => ({ text: "ok" }));
11712
- }, { traceDir: ".agent-inspect", silent: true });
11713
- console.log("Trace written to .agent-inspect/");
11923
+ async function cleanWorkspace(location, manifest, options = {}) {
11924
+ const dryRun = options.confirm !== true;
11925
+ const targets = uniqueDirs([
11926
+ manifest.reportsDir,
11927
+ manifest.artifactsDir,
11928
+ manifest.bundlesDir,
11929
+ manifest.index.path ?? INDEX_DIR_NAME
11930
+ ]);
11931
+ const removed = [];
11932
+ for (const rel of targets) {
11933
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
11934
+ let entries;
11935
+ try {
11936
+ entries = await readdir(abs);
11937
+ } catch {
11938
+ continue;
11939
+ }
11940
+ for (const entry of entries) {
11941
+ const relPath = `${rel}/${entry}`;
11942
+ removed.push(relPath);
11943
+ if (!dryRun) {
11944
+ await rm(path13.join(abs, entry), { recursive: true, force: true });
11945
+ }
11946
+ }
11947
+ }
11948
+ return { dryRun, removed };
11714
11949
  }
11715
11950
 
11716
- main().catch((error) => {
11717
- console.error(error);
11718
- process.exitCode = 1;
11719
- });
11720
- `;
11721
- case "openai-agents":
11722
- return `/**
11723
- * OpenAI Agents starter \u2014 use @agent-inspect/openai-agents for local-only processors.
11724
- * This demo uses manual steps (no API keys).
11725
- */
11726
- import { inspectRun, step } from "agent-inspect";
11727
-
11728
- async function main() {
11729
- await inspectRun("openai-agents-demo", async () => {
11730
- await step.tool("mock-agent-run", async () => "ok");
11731
- }, { traceDir: ".agent-inspect", silent: true });
11732
- console.log("Trace written to .agent-inspect/");
11951
+ // packages/cli/src/bundle.ts
11952
+ var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification.";
11953
+ function stable6(value) {
11954
+ if (Array.isArray(value)) return value.map(stable6);
11955
+ if (value === null || typeof value !== "object") return value;
11956
+ const record = value;
11957
+ return Object.fromEntries(
11958
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
11959
+ );
11733
11960
  }
11734
-
11735
- main().catch((error) => {
11736
- console.error(error);
11737
- process.exitCode = 1;
11738
- });
11961
+ function writeJson4(value) {
11962
+ return `${JSON.stringify(stable6(value), null, 2)}
11739
11963
  `;
11740
- case "langchain":
11741
- return `/**
11742
- * LangChain starter \u2014 wire @agent-inspect/langchain callbacks in your app.
11743
- * This demo uses manual steps (no API keys).
11744
- */
11745
- import { inspectRun, step } from "agent-inspect";
11746
-
11747
- async function main() {
11748
- await inspectRun("langchain-demo", async () => {
11749
- await step.tool("mock-chain", async () => "ok");
11750
- }, { traceDir: ".agent-inspect", silent: true });
11751
- console.log("Trace written to .agent-inspect/");
11752
11964
  }
11753
-
11754
- main().catch((error) => {
11755
- console.error(error);
11756
- process.exitCode = 1;
11757
- });
11758
- `;
11759
- default:
11760
- return `import { observe } from "agent-inspect";
11761
-
11762
- class DemoAgent {
11763
- async run(input: { question: string }) {
11764
- return { answer: \`Echo: \${input.question}\` };
11965
+ function parseBundleProfile(value) {
11966
+ if (value === void 0 || value === "local" || value === "share" || value === "strict") {
11967
+ return value ?? "share";
11765
11968
  }
11969
+ throw new Error(`Unsupported --profile "${value}". Use local, share, or strict.`);
11766
11970
  }
11767
-
11768
- const agent = observe(new DemoAgent(), {
11769
- traceDir: ".agent-inspect",
11770
- silent: true,
11771
- });
11772
-
11773
- await agent.run({ question: "hello" });
11774
- console.log("Trace written to .agent-inspect/");
11775
- `;
11776
- }
11971
+ function toReportProfile(profile) {
11972
+ return profile;
11777
11973
  }
11778
- function githubWorkflowTemplate() {
11779
- return `name: AgentInspect artifacts
11780
-
11781
- on:
11782
- workflow_dispatch:
11783
- push:
11784
- branches: [main]
11785
-
11786
- jobs:
11787
- trace-artifacts:
11788
- runs-on: ubuntu-latest
11789
- steps:
11790
- - uses: actions/checkout@v4
11791
- - uses: actions/setup-node@v4
11792
- with:
11793
- node-version: "22"
11794
- - run: npm ci
11795
- - run: npm test
11796
- - name: Upload AgentInspect traces
11797
- if: always()
11798
- uses: actions/upload-artifact@v4
11799
- with:
11800
- name: agent-inspect-traces
11801
- path: .agent-inspect/**/*.jsonl
11802
- if-no-files-found: ignore
11803
- `;
11974
+ function escapeHtml3(value) {
11975
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
11804
11976
  }
11805
- async function planInit(options = {}) {
11806
- const framework = normalizeFramework(options.framework);
11807
- const cwd = path11.resolve(options.cwd ?? process.cwd());
11808
- const demoPath = framework === "custom" ? path11.join("examples", "agent-inspect-demo.mjs") : path11.join("examples", `agent-inspect-${framework}-demo.mjs`);
11809
- const candidates = [
11810
- { rel: CONFIG_FILE, content: configTemplate(framework) },
11811
- { rel: GITKEEP, content: "" },
11812
- { rel: demoPath, content: demoTemplate(framework) }
11813
- ];
11814
- if (options.ci === "github") {
11815
- candidates.push({
11816
- rel: ".github/workflows/agent-inspect-artifacts.yml",
11817
- content: githubWorkflowTemplate()
11818
- });
11977
+ function safetyStatusFromAssess(status) {
11978
+ if (status === "SAFE" || status === "SAFE WITH WARNINGS" || status === "UNSAFE" || status === "UNKNOWN") {
11979
+ return status;
11819
11980
  }
11820
- const files = [];
11821
- for (const candidate of candidates) {
11822
- const abs = path11.join(cwd, candidate.rel);
11981
+ return "UNKNOWN";
11982
+ }
11983
+ async function resolveOutputDir(options, runIds, cwd) {
11984
+ if (options.out !== void 0 && options.out.trim() !== "") {
11985
+ const normalized = normalizeBundleOutputPath(options.out);
11823
11986
  try {
11824
- await access(abs);
11825
- files.push({
11826
- path: candidate.rel,
11827
- action: "skip",
11828
- reason: "file already exists"
11829
- });
11830
- } catch (error) {
11831
- if (error.code !== "ENOENT") throw error;
11832
- files.push({ path: candidate.rel, action: "create" });
11987
+ const location = resolveWorkspaceLocation(cwd);
11988
+ const manifest = await readWorkspaceManifestFile(location);
11989
+ if (manifest.ok && manifest.manifest) {
11990
+ const rel = path13.relative(location.workspaceDir, normalized);
11991
+ if (!rel.startsWith("..") && !path13.isAbsolute(rel)) {
11992
+ return resolveInsideWorkspace(location.workspaceDir, rel);
11993
+ }
11994
+ }
11995
+ } catch {
11833
11996
  }
11997
+ return normalized;
11834
11998
  }
11835
- return { framework, ...options.ci ? { ci: options.ci } : {}, files };
11836
- }
11837
- async function writePlannedFiles(plan, cwd, options) {
11838
- const written = [];
11839
- for (const entry of plan.files) {
11840
- if (entry.action === "skip") {
11841
- continue;
11842
- }
11843
- const abs = path11.join(cwd, entry.path);
11844
- if (options.dryRun) {
11845
- written.push(entry.path);
11846
- continue;
11999
+ try {
12000
+ const location = resolveWorkspaceLocation(cwd);
12001
+ const manifest = await readWorkspaceManifestFile(location);
12002
+ if (manifest.ok && manifest.manifest) {
12003
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
12004
+ const label = runIds.length === 1 ? runIds[0] : `multi-${runIds.length}`;
12005
+ return resolveInsideWorkspace(
12006
+ location.workspaceDir,
12007
+ path13.join(manifest.manifest.bundlesDir, `bundle-${label}-${stamp}`)
12008
+ );
11847
12009
  }
11848
- await mkdir(path11.dirname(abs), { recursive: true });
11849
- const content = entry.path === CONFIG_FILE ? configTemplate(plan.framework) : entry.path === GITKEEP ? "" : entry.path.endsWith(".yml") ? githubWorkflowTemplate() : demoTemplate(plan.framework);
11850
- await writeFile(abs, content, "utf-8");
11851
- written.push(entry.path);
12010
+ } catch {
11852
12011
  }
11853
- return written;
12012
+ return defaultBundleOutputPath(runIds);
11854
12013
  }
11855
- async function initCommand(options = {}) {
11856
- const cwd = path11.resolve(options.cwd ?? process.cwd());
12014
+ async function writeBundleFile(outputDir, relativePath, content, files) {
12015
+ const outPath = path13.join(outputDir, relativePath);
12016
+ await mkdir(path13.dirname(outPath), { recursive: true });
12017
+ await writeFile(outPath, content, "utf-8");
12018
+ files.push(relativePath);
12019
+ }
12020
+ function renderBundleIndexHtml(parts) {
12021
+ const links = parts.runIds.map(
12022
+ (runId) => `<li><a href="assets/runs/${escapeHtml3(runId)}.html">${escapeHtml3(runId)}</a></li>`
12023
+ ).join("");
12024
+ const sections = parts.runIds.map((runId) => {
12025
+ const html = parts.reports.get(runId) ?? "";
12026
+ return `<section id="run-${escapeHtml3(runId)}"><h2>${escapeHtml3(runId)}</h2>${html}</section>`;
12027
+ }).join("\n");
12028
+ return `<!doctype html>
12029
+ <html lang="en">
12030
+ <head>
12031
+ <meta charset="utf-8"/>
12032
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
12033
+ <title>AgentInspect Bundle</title>
12034
+ <style>
12035
+ body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}
12036
+ a{color:#0366d6}
12037
+ section{border-top:1px solid #ddd;margin-top:1.5rem;padding-top:1rem}
12038
+ </style>
12039
+ </head>
12040
+ <body>
12041
+ <h1>AgentInspect trace bundle</h1>
12042
+ <p>${escapeHtml3(BUNDLE_NOTE)}</p>
12043
+ <h2>Runs</h2>
12044
+ <ul>${links}</ul>
12045
+ ${sections}
12046
+ </body>
12047
+ </html>
12048
+ `;
12049
+ }
12050
+ function extractHtmlBody(content) {
12051
+ const match = content.match(/<body[^>]*>([\s\S]*)<\/body>/i);
12052
+ return match?.[1]?.trim() ?? content;
12053
+ }
12054
+ async function bundleCommand(runIdArg, options = {}) {
12055
+ const profile = parseBundleProfile(options.profile);
12056
+ const traceDir = resolveTraceDir({ dir: options.dir });
12057
+ const cwd = process.cwd();
12058
+ let resolveResult;
11857
12059
  try {
11858
- const plan = await planInit({ ...options, cwd });
11859
- const toWrite = plan.files.filter((file) => file.action === "create").map((f) => f.path);
11860
- const skipped = plan.files.filter((file) => file.action === "skip");
12060
+ const { runs } = await loadSessionRuns(traceDir);
12061
+ const staleThresholdMs = options.staleAfter && options.staleAfter.trim() !== "" ? parseDuration(options.staleAfter.trim()) : void 0;
12062
+ const index = buildSessionIndex(runs, {
12063
+ correlateByGroupId: options.correlateGroup === true,
12064
+ staleThresholdMs
12065
+ });
12066
+ resolveResult = resolveBundleRunIds(index, runs, {
12067
+ ...runIdArg !== void 0 && runIdArg.trim() !== "" ? { runId: runIdArg.trim() } : {},
12068
+ ...options.session ? { sessionId: options.session } : {},
12069
+ ...options.since ? { since: options.since } : {}
12070
+ });
12071
+ } catch (error) {
12072
+ const message = error instanceof Error ? error.message : String(error);
11861
12073
  if (options.json) {
11862
- const payload = {
11863
- ok: true,
11864
- version,
11865
- framework: plan.framework,
11866
- ci: plan.ci ?? null,
11867
- dryRun: options.dryRun === true,
11868
- planned: plan.files,
11869
- wouldWrite: options.dryRun ? toWrite : void 0
11870
- };
11871
- console.log(JSON.stringify(payload, null, 2));
11872
- if (options.dryRun) return;
12074
+ console.log(writeJson4({ ok: false, error: message }).trimEnd());
12075
+ } else {
12076
+ console.error(`[AgentInspect] bundle failed: ${message}`);
11873
12077
  }
11874
- const written = await writePlannedFiles(plan, cwd, options);
11875
- if (!options.json) {
11876
- console.log("AgentInspect init");
11877
- console.log(`Framework: ${plan.framework}`);
11878
- console.log(`Trace directory: ${TRACE_DIR}/`);
11879
- if (options.dryRun) {
11880
- console.log("Dry run \u2014 would create:");
11881
- for (const file of toWrite) console.log(`- ${file}`);
11882
- for (const file of skipped) {
11883
- console.log(`- ${file.path} (skip: ${file.reason ?? "exists"})`);
11884
- }
11885
- return;
12078
+ process.exitCode = 1;
12079
+ return;
12080
+ }
12081
+ const outputDir = await resolveOutputDir(options, resolveResult.runIds, cwd);
12082
+ const files = [];
12083
+ const checkRuns = [];
12084
+ const redactionRuns = [];
12085
+ const htmlByRun = /* @__PURE__ */ new Map();
12086
+ const redactedJsonlByRun = /* @__PURE__ */ new Map();
12087
+ let combinedJsonl = "";
12088
+ for (const runId of resolveResult.runIds) {
12089
+ const tracePath = getTraceFilePath(runId, traceDir);
12090
+ let rawContent;
12091
+ let sourceMtimeMs;
12092
+ try {
12093
+ rawContent = await readFile(tracePath, "utf-8");
12094
+ sourceMtimeMs = (await stat(tracePath)).mtimeMs;
12095
+ } catch (error) {
12096
+ const message = error instanceof Error ? error.message : String(error);
12097
+ if (options.json) {
12098
+ console.log(writeJson4({ ok: false, error: message, runId }).trimEnd());
12099
+ } else {
12100
+ console.error(`[AgentInspect] bundle failed: ${message}`);
11886
12101
  }
11887
- console.log("Created:");
11888
- for (const file of written) console.log(`- ${file}`);
11889
- for (const file of skipped) {
11890
- console.log(`- ${file.path} (skipped: ${file.reason ?? "exists"})`);
12102
+ process.exitCode = 1;
12103
+ return;
12104
+ }
12105
+ let read;
12106
+ try {
12107
+ read = await openTrace(
12108
+ { type: "file", path: tracePath },
12109
+ { format: "agent-inspect-jsonl" }
12110
+ );
12111
+ } catch (error) {
12112
+ const message = error instanceof Error ? error.message : String(error);
12113
+ if (options.json) {
12114
+ console.log(writeJson4({ ok: false, error: message, runId }).trimEnd());
12115
+ } else {
12116
+ console.error(`[AgentInspect] bundle failed: ${message}`);
11891
12117
  }
11892
- console.log("\nNext: run your demo, then `npx agent-inspect list --dir .agent-inspect`");
11893
- console.log("No dependencies were installed. Add packages manually when ready.");
12118
+ process.exitCode = 1;
12119
+ return;
11894
12120
  }
11895
- } catch (error) {
11896
- const msg = error instanceof Error ? error.message : String(error);
12121
+ const safety = assessOpenedTrace(read, { run: runId });
12122
+ const status = safetyStatusFromAssess(safety.status);
12123
+ checkRuns.push({
12124
+ runId,
12125
+ status,
12126
+ errors: safety.summary.errors,
12127
+ warnings: safety.summary.warnings,
12128
+ findings: safety.summary.findings
12129
+ });
12130
+ const redacted = redactTraceContent(rawContent, toReportProfile(profile));
12131
+ redactedJsonlByRun.set(runId, redacted.content);
12132
+ combinedJsonl += redacted.content.endsWith("\n") ? redacted.content : `${redacted.content}
12133
+ `;
12134
+ const detectors = [
12135
+ ...new Set(redacted.findings.map((finding) => finding.detector))
12136
+ ].sort((a, b) => a.localeCompare(b));
12137
+ redactionRuns.push({
12138
+ runId,
12139
+ findings: redacted.findings.length,
12140
+ detectors
12141
+ });
12142
+ const selected = read.runs.find((run) => run.runId === runId);
12143
+ const runReport = exportRunTree(selected ?? read.runs[0], {
12144
+ format: "html",
12145
+ redacted: true,
12146
+ redactionProfile: toReportProfile(profile)
12147
+ });
12148
+ htmlByRun.set(runId, extractHtmlBody(runReport.content));
12149
+ const afterMtime = (await stat(tracePath)).mtimeMs;
12150
+ if (afterMtime !== sourceMtimeMs) {
12151
+ const message = `Source trace "${runId}" was modified during bundle creation.`;
12152
+ if (options.json) {
12153
+ console.log(writeJson4({ ok: false, error: message }).trimEnd());
12154
+ } else {
12155
+ console.error(`[AgentInspect] bundle failed: ${message}`);
12156
+ }
12157
+ process.exitCode = 1;
12158
+ return;
12159
+ }
12160
+ }
12161
+ const checks2 = {
12162
+ aggregateStatus: aggregateBundleSafeStatus(checkRuns.map((run) => run.status)),
12163
+ runs: checkRuns
12164
+ };
12165
+ if (bundleFailsOnSafety(checks2.aggregateStatus, options.allowUnsafe === true)) {
11897
12166
  if (options.json) {
11898
- console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
12167
+ console.log(
12168
+ writeJson4({
12169
+ ok: false,
12170
+ error: `Bundle safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`,
12171
+ checks: checks2
12172
+ }).trimEnd()
12173
+ );
11899
12174
  } else {
11900
- console.error(`[AgentInspect] init failed: ${msg}`);
12175
+ console.error(
12176
+ `[AgentInspect] bundle refused: safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`
12177
+ );
11901
12178
  }
11902
12179
  process.exitCode = 1;
12180
+ return;
11903
12181
  }
11904
- }
11905
- var OPTIONAL_PACKAGES = {
11906
- custom: [],
11907
- "ai-sdk": ["@agent-inspect/ai-sdk"],
11908
- "openai-agents": ["@agent-inspect/openai-agents"],
11909
- langchain: ["@agent-inspect/langchain"]
11910
- };
11911
- function nodeVersionCheck() {
11912
- const major = Number(process2.versions.node.split(".")[0]);
11913
- if (Number.isNaN(major) || major < 20) {
11914
- return {
11915
- id: "node-version",
11916
- status: "fail",
11917
- message: `Node ${process2.versions.node} is below the supported minimum (20).`,
11918
- remediation: "Upgrade to Node 20 LTS or newer.",
11919
- evidence: process2.versions.node
11920
- };
11921
- }
11922
- return {
11923
- id: "node-version",
11924
- status: "pass",
11925
- message: `Node ${process2.versions.node} meets the minimum (>=20).`,
11926
- evidence: process2.versions.node
12182
+ const redactionReport = {
12183
+ profile,
12184
+ totalFindings: redactionRuns.reduce((sum, run) => sum + run.findings, 0),
12185
+ runs: redactionRuns
11927
12186
  };
11928
- }
11929
- function envCheck(name, optional = true) {
11930
- const value = process2.env[name];
11931
- if (value === void 0 || value.trim() === "") {
11932
- return {
11933
- id: `env-${name.toLowerCase()}`,
11934
- status: optional ? "skipped" : "warn",
11935
- message: `${name} is not set.`,
11936
- remediation: optional ? void 0 : `Set ${name}=1 to enable tracing.`
11937
- };
12187
+ await mkdir(outputDir, { recursive: true });
12188
+ for (const runId of resolveResult.runIds) {
12189
+ const jsonl = redactedJsonlByRun.get(runId) ?? "";
12190
+ await writeBundleFile(outputDir, `assets/runs/${runId}.jsonl`, jsonl, files);
12191
+ const html = htmlByRun.get(runId) ?? "";
12192
+ await writeBundleFile(
12193
+ outputDir,
12194
+ `assets/runs/${runId}.html`,
12195
+ runReportWrap(html, runId),
12196
+ files
12197
+ );
11938
12198
  }
11939
- return {
11940
- id: `env-${name.toLowerCase()}`,
11941
- status: "pass",
11942
- message: `${name} is set.`,
11943
- evidence: value
11944
- };
11945
- }
11946
- async function traceDirWritable(traceDir) {
11947
- const resolved = path11.resolve(traceDir);
11948
- try {
11949
- await mkdir(resolved, { recursive: true });
11950
- await access(resolved, constants.W_OK);
11951
- return {
11952
- id: "trace-dir-writable",
11953
- status: "pass",
11954
- message: `Trace directory is writable: ${resolved}`,
11955
- evidence: resolved
11956
- };
11957
- } catch (error) {
11958
- return {
11959
- id: "trace-dir-writable",
11960
- status: "fail",
11961
- message: `Trace directory is not writable: ${resolved}`,
11962
- remediation: "Create the directory or set AGENT_INSPECT_TRACE_DIR to a writable path.",
11963
- evidence: error instanceof Error ? error.message : String(error)
11964
- };
12199
+ const primaryRunId = resolveResult.runIds[0];
12200
+ const traceHtml = resolveResult.runIds.length === 1 ? runReportWrap(htmlByRun.get(primaryRunId) ?? "", primaryRunId) : renderBundleIndexHtml({ runIds: resolveResult.runIds, reports: htmlByRun });
12201
+ await writeBundleFile(outputDir, "trace.jsonl", combinedJsonl, files);
12202
+ await writeBundleFile(outputDir, "trace.html", traceHtml, files);
12203
+ await writeBundleFile(
12204
+ outputDir,
12205
+ "check-results.json",
12206
+ writeJson4(checks2),
12207
+ files
12208
+ );
12209
+ await writeBundleFile(
12210
+ outputDir,
12211
+ "eval-results.json",
12212
+ writeJson4(buildPlaceholderArtifact()),
12213
+ files
12214
+ );
12215
+ await writeBundleFile(
12216
+ outputDir,
12217
+ "redaction-report.json",
12218
+ writeJson4(redactionReport),
12219
+ files
12220
+ );
12221
+ await writeBundleFile(
12222
+ outputDir,
12223
+ "performance-summary.json",
12224
+ writeJson4(buildPlaceholderArtifact()),
12225
+ files
12226
+ );
12227
+ const metadata = buildBundleMetadata({
12228
+ agentInspectVersion: version,
12229
+ profile,
12230
+ resolve: resolveResult,
12231
+ checks: checks2,
12232
+ files: [...files]
12233
+ });
12234
+ await writeBundleFile(outputDir, "metadata.json", writeJson4(metadata), files);
12235
+ await writeBundleFile(
12236
+ outputDir,
12237
+ "summary.md",
12238
+ buildBundleSummaryMarkdown({ metadata, checks: checks2, redaction: redactionReport }),
12239
+ files
12240
+ );
12241
+ metadata.files = [...files].sort((a, b) => a.localeCompare(b));
12242
+ await writeFile(path13.join(outputDir, "metadata.json"), writeJson4(metadata), "utf-8");
12243
+ if (options.json) {
12244
+ console.log(
12245
+ writeJson4({
12246
+ ok: true,
12247
+ outputDir,
12248
+ metadata,
12249
+ checks: checks2,
12250
+ redaction: redactionReport
12251
+ }).trimEnd()
12252
+ );
12253
+ return;
11965
12254
  }
12255
+ console.log(`Bundle written to ${outputDir}`);
12256
+ console.log(`Safe status: ${metadata.safeStatus}`);
12257
+ console.log(`Runs: ${resolveResult.runIds.join(", ")}`);
12258
+ console.log(`Files: ${metadata.files.length}`);
11966
12259
  }
11967
- function resolvePackage(cwd, name) {
11968
- const require2 = createRequire(path11.join(cwd, "package.json"));
11969
- try {
11970
- const pkgPath = require2.resolve(`${name}/package.json`);
11971
- const pkg = require2(pkgPath);
11972
- return { ok: true, version: pkg.version };
11973
- } catch {
11974
- return { ok: false };
11975
- }
12260
+ function runReportWrap(body, runId) {
12261
+ return `<!doctype html>
12262
+ <html lang="en">
12263
+ <head>
12264
+ <meta charset="utf-8"/>
12265
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
12266
+ <title>AgentInspect \u2014 ${escapeHtml3(runId)}</title>
12267
+ <style>body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}</style>
12268
+ </head>
12269
+ <body>
12270
+ ${body}
12271
+ </body>
12272
+ </html>
12273
+ `;
11976
12274
  }
11977
- function importSmoke(cwd) {
11978
- const results = [];
11979
- const require2 = createRequire(path11.join(cwd, "package.json"));
11980
- try {
11981
- require2.resolve("agent-inspect");
11982
- results.push({
11983
- id: "import-agent-inspect",
11984
- status: "pass",
11985
- message: "agent-inspect resolves from the current project."
12275
+ function validateReporterArtifactPath(options) {
12276
+ const outputDir = path13.resolve(options.outputDir);
12277
+ const diagnostics = [];
12278
+ const rawPath = options.relativePath;
12279
+ if (rawPath.length === 0) {
12280
+ diagnostics.push({
12281
+ code: "artifact_path_empty",
12282
+ severity: "error",
12283
+ message: "Reporter artifact path must not be empty."
11986
12284
  });
11987
- } catch {
11988
- results.push({
11989
- id: "import-agent-inspect",
11990
- status: "warn",
11991
- message: "agent-inspect is not installed in the current project.",
11992
- remediation: "Run npm install agent-inspect (or pnpm add agent-inspect)."
12285
+ return { ok: false, outputDir, diagnostics };
12286
+ }
12287
+ if (rawPath.includes("\0")) {
12288
+ diagnostics.push({
12289
+ code: "invalid_artifact_path",
12290
+ severity: "error",
12291
+ message: "Reporter artifact path must not contain null bytes.",
12292
+ target: rawPath
11993
12293
  });
12294
+ return { ok: false, outputDir, diagnostics };
11994
12295
  }
11995
- try {
11996
- require2.resolve("agent-inspect/package.json");
11997
- results.push({
11998
- id: "import-agent-inspect-cjs",
11999
- status: "pass",
12000
- message: "CJS resolution for agent-inspect succeeded."
12296
+ if (path13.isAbsolute(rawPath) || path13.win32.isAbsolute(rawPath)) {
12297
+ diagnostics.push({
12298
+ code: "artifact_path_absolute",
12299
+ severity: "error",
12300
+ message: "Reporter artifact path must be relative.",
12301
+ target: rawPath
12001
12302
  });
12002
- } catch {
12003
- results.push({
12004
- id: "import-agent-inspect-cjs",
12005
- status: "skipped",
12006
- message: "CJS resolution check skipped (package not installed locally)."
12303
+ return { ok: false, outputDir, diagnostics };
12304
+ }
12305
+ const normalized = path13.posix.normalize(rawPath.replace(/\\/g, "/"));
12306
+ const segments = normalized.split("/");
12307
+ if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
12308
+ diagnostics.push({
12309
+ code: "artifact_path_escape",
12310
+ severity: "error",
12311
+ message: "Reporter artifact path must stay under the output directory.",
12312
+ target: rawPath
12007
12313
  });
12314
+ return { ok: false, outputDir, diagnostics };
12008
12315
  }
12009
- return results;
12010
- }
12011
- function optionalPackageChecks(cwd, framework) {
12012
- const packages = framework !== void 0 ? OPTIONAL_PACKAGES[framework] : [
12013
- "@agent-inspect/ai-sdk",
12014
- "@agent-inspect/openai-agents",
12015
- "@agent-inspect/langchain",
12016
- "@agent-inspect/redact",
12017
- "@agent-inspect/eval"
12018
- ];
12019
- return packages.map((name) => {
12020
- const resolved = resolvePackage(cwd, name);
12021
- if (!resolved.ok) {
12022
- return {
12023
- id: `optional-package-${name}`,
12024
- status: framework !== void 0 ? "warn" : "skipped",
12025
- message: `${name} is not installed.`,
12026
- remediation: framework !== void 0 ? `npm install ${name}` : void 0
12027
- };
12028
- }
12029
- return {
12030
- id: `optional-package-${name}`,
12031
- status: "pass",
12032
- message: `${name} is available.`,
12033
- evidence: resolved.version
12034
- };
12035
- });
12036
- }
12037
- function versionMismatchCheck(cwd) {
12038
- const root = resolvePackage(cwd, "agent-inspect");
12039
- if (!root.ok || root.version === void 0) {
12040
- return {
12041
- id: "version-alignment",
12042
- status: "skipped",
12043
- message: "Skipped version alignment \u2014 agent-inspect not installed locally."
12044
- };
12045
- }
12046
- if (root.version !== version) {
12047
- return {
12048
- id: "version-alignment",
12049
- status: "warn",
12050
- message: `CLI version ${version} differs from local agent-inspect@${root.version}.`,
12051
- remediation: "Align versions with npm install agent-inspect@latest",
12052
- evidence: `cli=${version};local=${root.version}`
12053
- };
12316
+ const absolutePath = path13.resolve(outputDir, normalized);
12317
+ const relFromOutput = path13.relative(outputDir, absolutePath);
12318
+ if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path13.isAbsolute(relFromOutput)) {
12319
+ diagnostics.push({
12320
+ code: "artifact_path_escape",
12321
+ severity: "error",
12322
+ message: "Reporter artifact path resolved outside the output directory.",
12323
+ target: rawPath
12324
+ });
12325
+ return { ok: false, outputDir, diagnostics };
12054
12326
  }
12055
12327
  return {
12056
- id: "version-alignment",
12057
- status: "pass",
12058
- message: `CLI and local agent-inspect are aligned at ${version}.`
12328
+ ok: true,
12329
+ outputDir,
12330
+ relativePath: normalized,
12331
+ absolutePath,
12332
+ diagnostics
12059
12333
  };
12060
12334
  }
12061
- async function runDoctorChecks(options = {}) {
12062
- const cwd = path11.resolve(options.cwd ?? process2.cwd());
12063
- const traceDir = options.traceDir?.trim() || process2.env.AGENT_INSPECT_TRACE_DIR?.trim() || ".agent-inspect";
12064
- const checks2 = [
12065
- nodeVersionCheck(),
12066
- {
12067
- id: "cli-version",
12068
- status: "pass",
12069
- message: `agent-inspect CLI ${version}`,
12070
- evidence: version
12071
- },
12072
- await traceDirWritable(traceDir),
12073
- envCheck("AGENT_INSPECT"),
12074
- envCheck("AGENT_INSPECT_TRACE_DIR"),
12075
- versionMismatchCheck(cwd)
12076
- ];
12077
- if (options.checkImports !== false) {
12078
- checks2.push(...importSmoke(cwd));
12079
- }
12080
- checks2.push(...optionalPackageChecks(cwd, options.framework));
12081
- return checks2.sort((a, b) => a.id.localeCompare(b.id));
12335
+
12336
+ // packages/cli/src/ci-summary.ts
12337
+ var NOTE2 = "Generated locally by AgentInspect from reporter artifact manifests. Trace contents are not embedded.";
12338
+ var MAX_TEXT = 180;
12339
+ var FRAMEWORKS = /* @__PURE__ */ new Set(["vitest", "jest", "manual"]);
12340
+ var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
12341
+ var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
12342
+ var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
12343
+ var REDACTION_PROFILES2 = /* @__PURE__ */ new Set(["local", "share", "strict"]);
12344
+ function stable7(value) {
12345
+ if (Array.isArray(value)) return value.map(stable7);
12346
+ if (value === null || typeof value !== "object") return value;
12347
+ const record = value;
12348
+ return Object.fromEntries(
12349
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable7(record[key])])
12350
+ );
12082
12351
  }
12083
- async function doctorCommand(options = {}) {
12084
- const checks2 = await runDoctorChecks(options);
12085
- const failed = checks2.filter((check) => check.status === "fail").length;
12086
- const warned = checks2.filter((check) => check.status === "warn").length;
12087
- if (options.json) {
12088
- console.log(
12089
- JSON.stringify(
12090
- {
12091
- ok: failed === 0,
12092
- version,
12093
- summary: { pass: checks2.filter((c) => c.status === "pass").length, warn: warned, fail: failed },
12094
- checks: checks2
12095
- },
12096
- null,
12097
- 2
12098
- )
12099
- );
12100
- if (failed > 0) process2.exitCode = 1;
12101
- return;
12102
- }
12103
- console.log("AgentInspect doctor");
12104
- for (const check of checks2) {
12105
- const tag = check.status.toUpperCase();
12106
- console.log(`[${tag}] ${check.id}: ${check.message}`);
12107
- if (check.remediation) console.log(` \u2192 ${check.remediation}`);
12352
+ function writeJson5(value) {
12353
+ return `${JSON.stringify(stable7(value), null, 2)}
12354
+ `;
12355
+ }
12356
+ function isObject(value) {
12357
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12358
+ }
12359
+ function readString(value, label) {
12360
+ if (typeof value !== "string" || value.trim() === "") {
12361
+ throw new Error(`${label} must be a non-empty string.`);
12108
12362
  }
12109
- console.log(`
12110
- Summary: ${failed} failed, ${warned} warnings`);
12111
- if (failed > 0) process2.exitCode = 1;
12363
+ return safeText(value);
12112
12364
  }
12113
-
12114
- // packages/adapter-sdk/src/indexer.ts
12115
- function defineIndexer(indexer) {
12116
- if (!indexer.id.trim()) throw new Error("indexer id is required");
12117
- return indexer;
12365
+ function readOptionalString(value) {
12366
+ return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
12118
12367
  }
12119
- async function indexIsStale(snapshot, traceDir) {
12120
- const builtMs = Date.parse(snapshot.builtAt);
12121
- if (Number.isNaN(builtMs)) return true;
12122
- const td = new TraceDirectory({ dir: traceDir });
12123
- const files = await td.list();
12124
- for (const file of files) {
12125
- const stats = await td.getFileStats(file);
12126
- if (stats.mtimeMs > builtMs) return true;
12368
+ function readFramework(value) {
12369
+ const framework = readString(value, "manifest.framework");
12370
+ if (!FRAMEWORKS.has(framework)) {
12371
+ throw new Error(`Unsupported reporter framework: ${framework}.`);
12127
12372
  }
12128
- return false;
12373
+ return framework;
12129
12374
  }
12130
- function createTraceDirectoryIndexer() {
12131
- return defineIndexer({
12132
- id: "trace-directory-metadata",
12133
- async rebuild(traceDir, options = {}) {
12134
- const warnings = [];
12135
- const td = new TraceDirectory({ dir: traceDir });
12136
- const files = await td.list();
12137
- const maxEntries = options.maxEntries ?? 1e4;
12138
- if (files.length > maxEntries) {
12139
- warnings.push(
12140
- `indexer.truncated: trace directory has ${files.length} files; indexing first ${maxEntries}`
12141
- );
12142
- }
12143
- const slice = files.slice(0, maxEntries);
12144
- const metas = await loadTraceMetadataList(
12145
- traceDir,
12146
- slice,
12147
- (fileName) => td.getPath(fileName)
12148
- );
12149
- const entries = metas.map((meta) => ({
12150
- runId: meta.runId,
12151
- path: meta.filePath,
12152
- name: meta.name,
12153
- startedAt: meta.startedAt,
12154
- status: meta.status
12155
- })).sort((a, b) => a.runId.localeCompare(b.runId));
12156
- if (entries.length < slice.length) {
12157
- warnings.push(
12158
- `indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
12159
- );
12160
- }
12161
- return {
12162
- traceDir,
12163
- builtAt: (/* @__PURE__ */ new Date()).toISOString(),
12164
- entries,
12165
- warnings
12166
- };
12167
- }
12375
+ function readStatus(value) {
12376
+ const status = readString(value, "result.status");
12377
+ if (!STATUSES.has(status)) {
12378
+ throw new Error(`Unsupported reporter test status: ${status}.`);
12379
+ }
12380
+ return status;
12381
+ }
12382
+ function readArtifact(value, index) {
12383
+ if (!isObject(value)) throw new Error(`manifest.artifacts[${index}] must be an object.`);
12384
+ const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
12385
+ const format = readString(value.format, `manifest.artifacts[${index}].format`);
12386
+ const redactionProfile = readString(
12387
+ value.redactionProfile,
12388
+ `manifest.artifacts[${index}].redactionProfile`
12389
+ );
12390
+ if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
12391
+ if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
12392
+ if (!REDACTION_PROFILES2.has(redactionProfile)) {
12393
+ throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
12394
+ }
12395
+ const artifactPath = readString(value.path, `manifest.artifacts[${index}].path`);
12396
+ const pathCheck = validateReporterArtifactPath({
12397
+ outputDir: process.cwd(),
12398
+ relativePath: artifactPath
12168
12399
  });
12400
+ if (!pathCheck.ok || pathCheck.relativePath === void 0) {
12401
+ throw new Error(`Unsafe reporter artifact path: ${artifactPath}.`);
12402
+ }
12403
+ return {
12404
+ kind,
12405
+ path: pathCheck.relativePath,
12406
+ format,
12407
+ redactionProfile
12408
+ };
12169
12409
  }
12170
-
12171
- // packages/cli/src/index-cmd.ts
12172
- var INDEX_FILENAME = ".agent-inspect-index.json";
12173
- function traceIndexPath(traceDir) {
12174
- return path11.join(traceDir, INDEX_FILENAME);
12410
+ function readArtifacts(value, label) {
12411
+ if (value === void 0) return [];
12412
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
12413
+ return value.map((item, index) => readArtifact(item, index));
12175
12414
  }
12176
- function parseMaxEntries(raw) {
12177
- if (raw === void 0 || raw.trim() === "") return void 0;
12178
- const parsed = Number.parseInt(raw, 10);
12179
- if (!Number.isFinite(parsed) || parsed <= 0) {
12180
- throw new Error("--max-entries must be a positive integer.");
12415
+ function readDiagnosticsCount(value, label) {
12416
+ if (value === void 0) return 0;
12417
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
12418
+ return value.length;
12419
+ }
12420
+ function readResult(value, index) {
12421
+ if (!isObject(value)) throw new Error(`manifest.results[${index}] must be an object.`);
12422
+ const file = readOptionalString(value.file);
12423
+ const tracePath = readOptionalString(value.tracePath);
12424
+ return {
12425
+ testId: readString(value.testId, `manifest.results[${index}].testId`),
12426
+ name: readString(value.name, `manifest.results[${index}].name`),
12427
+ ...file === void 0 ? {} : { file },
12428
+ status: readStatus(value.status),
12429
+ ...tracePath === void 0 ? {} : { tracePath },
12430
+ artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
12431
+ diagnostics: readDiagnosticsCount(
12432
+ value.diagnostics,
12433
+ `manifest.results[${index}].diagnostics`
12434
+ )
12435
+ };
12436
+ }
12437
+ function readManifestDocument(value) {
12438
+ if (!isObject(value)) throw new Error("Reporter manifest file must contain a JSON object.");
12439
+ const candidate = isObject(value.manifest) ? value.manifest : value;
12440
+ if (!isObject(candidate)) throw new Error("Reporter manifest must be a JSON object.");
12441
+ const schemaVersion = readString(candidate.schemaVersion, "manifest.schemaVersion");
12442
+ if (schemaVersion !== "0.1") {
12443
+ throw new Error(`Unsupported reporter manifest schemaVersion: ${schemaVersion}.`);
12181
12444
  }
12182
- return parsed;
12445
+ if (!Array.isArray(candidate.results)) {
12446
+ throw new Error("manifest.results must be an array.");
12447
+ }
12448
+ const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
12449
+ const results = candidate.results.map((item, index) => readResult(item, index));
12450
+ const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
12451
+ const manifest = {
12452
+ framework: readFramework(candidate.framework),
12453
+ generatedAt: readString(candidate.generatedAt, "manifest.generatedAt"),
12454
+ results,
12455
+ artifacts
12456
+ };
12457
+ return {
12458
+ packageName: readOptionalString(value.package),
12459
+ manifest,
12460
+ diagnostics
12461
+ };
12183
12462
  }
12184
- async function readSnapshot(indexPath2) {
12185
- try {
12186
- const raw = await readFile(indexPath2, "utf8");
12187
- return JSON.parse(raw);
12188
- } catch {
12189
- return void 0;
12463
+ function cwdRelative(filePath) {
12464
+ const relative = path13.relative(process.cwd(), path13.resolve(filePath)).replace(/\\/g, "/");
12465
+ if (relative === "" || relative.startsWith("../") || path13.isAbsolute(relative)) {
12466
+ return path13.basename(filePath);
12190
12467
  }
12468
+ return relative;
12191
12469
  }
12192
- async function indexBuildCommand(options = {}) {
12193
- try {
12194
- const traceDir = resolveTraceDir({ dir: options.dir });
12195
- await mkdir(traceDir, { recursive: true });
12196
- const indexer = createTraceDirectoryIndexer();
12197
- const snapshot = await indexer.rebuild(traceDir, {
12198
- maxEntries: parseMaxEntries(options.maxEntries)
12199
- });
12200
- const indexPath2 = traceIndexPath(traceDir);
12201
- await writeFile(indexPath2, `${JSON.stringify(snapshot, null, 2)}
12202
- `, "utf8");
12203
- if (options.json) {
12204
- console.log(JSON.stringify({ ok: true, indexPath: indexPath2, ...snapshot }, null, 2));
12205
- return;
12206
- }
12207
- console.log(`Built trace index: ${indexPath2}`);
12208
- console.log(`Entries: ${snapshot.entries.length}`);
12209
- if (snapshot.warnings.length > 0) {
12210
- for (const warning of snapshot.warnings) {
12211
- console.log(`warning: ${warning}`);
12212
- }
12470
+ async function readReporterManifest(filePath) {
12471
+ const absolute = path13.resolve(filePath);
12472
+ const raw = await readFile(absolute, "utf-8");
12473
+ const document = readManifestDocument(JSON.parse(raw));
12474
+ const manifest = document.manifest;
12475
+ const results = manifest.results.map((result) => ({
12476
+ testId: safeText(result.testId),
12477
+ name: safeText(result.name),
12478
+ ...result.file === void 0 ? {} : { file: safeText(path13.basename(result.file)) },
12479
+ status: result.status,
12480
+ ...result.tracePath === void 0 ? {} : { tracePath: safeText(path13.basename(result.tracePath)) },
12481
+ artifacts: result.artifacts,
12482
+ diagnostics: result.diagnostics
12483
+ }));
12484
+ return {
12485
+ ...document.packageName === void 0 ? {} : { packageName: document.packageName },
12486
+ manifestFile: cwdRelative(absolute),
12487
+ framework: manifest.framework,
12488
+ generatedAt: manifest.generatedAt,
12489
+ results,
12490
+ artifacts: manifest.artifacts,
12491
+ diagnostics: document.diagnostics
12492
+ };
12493
+ }
12494
+ function summarize3(manifests) {
12495
+ const summary = {
12496
+ manifests: manifests.length,
12497
+ tests: 0,
12498
+ failed: 0,
12499
+ passed: 0,
12500
+ skipped: 0,
12501
+ todo: 0,
12502
+ artifacts: 0,
12503
+ diagnostics: 0
12504
+ };
12505
+ for (const manifest of manifests) {
12506
+ summary.artifacts += manifest.artifacts.length;
12507
+ summary.diagnostics += manifest.diagnostics;
12508
+ for (const result of manifest.results) {
12509
+ summary.tests += 1;
12510
+ if (result.status === "failed") summary.failed += 1;
12511
+ else if (result.status === "passed") summary.passed += 1;
12512
+ else if (result.status === "skipped") summary.skipped += 1;
12513
+ else summary.todo += 1;
12514
+ summary.artifacts += result.artifacts.length;
12515
+ summary.diagnostics += result.diagnostics;
12213
12516
  }
12214
- } catch (e) {
12215
- const msg = e instanceof Error ? e.message : String(e);
12216
- console.error(`[AgentInspect] index build failed: ${msg}`);
12217
- process.exitCode = 1;
12218
12517
  }
12518
+ return {
12519
+ status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
12520
+ manifests,
12521
+ summary,
12522
+ note: NOTE2
12523
+ };
12219
12524
  }
12220
- async function indexStatusCommand(options = {}) {
12221
- try {
12222
- const traceDir = resolveTraceDir({ dir: options.dir });
12223
- const indexPath2 = traceIndexPath(traceDir);
12224
- const snapshot = await readSnapshot(indexPath2);
12225
- if (!snapshot) {
12226
- const payload2 = { ok: true, exists: false, indexPath: indexPath2, traceDir, stale: true };
12227
- if (options.json) {
12228
- console.log(JSON.stringify(payload2, null, 2));
12229
- } else {
12230
- console.log(`No index at ${indexPath2}`);
12231
- console.log("Run: agent-inspect index build");
12232
- }
12233
- return;
12525
+ function markdownCell(value) {
12526
+ return safeText(String(value ?? "unknown")).replaceAll("|", "\\|").replace(/\r?\n/g, " ");
12527
+ }
12528
+ function renderMarkdown2(result) {
12529
+ const lines = [
12530
+ "# AgentInspect CI Summary",
12531
+ "",
12532
+ NOTE2,
12533
+ "",
12534
+ "| Field | Value |",
12535
+ "| --- | --- |",
12536
+ `| Status | ${result.status} |`,
12537
+ `| Manifests | ${result.summary.manifests} |`,
12538
+ `| Tests | ${result.summary.tests} |`,
12539
+ `| Failed | ${result.summary.failed} |`,
12540
+ `| Passed | ${result.summary.passed} |`,
12541
+ `| Skipped | ${result.summary.skipped} |`,
12542
+ `| Todo | ${result.summary.todo} |`,
12543
+ `| Artifacts | ${result.summary.artifacts} |`,
12544
+ `| Diagnostics | ${result.summary.diagnostics} |`,
12545
+ "",
12546
+ "## Tests",
12547
+ "",
12548
+ "| Framework | Status | Test | File | Trace | Artifacts |",
12549
+ "| --- | --- | --- | --- | --- | --- |"
12550
+ ];
12551
+ const rows = result.manifests.flatMap(
12552
+ (manifest) => manifest.results.map((test) => ({
12553
+ framework: manifest.framework,
12554
+ test
12555
+ }))
12556
+ );
12557
+ if (rows.length === 0) {
12558
+ lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
12559
+ } else {
12560
+ for (const row of rows) {
12561
+ lines.push(
12562
+ `| ${markdownCell(row.framework)} | ${markdownCell(row.test.status)} | ${markdownCell(row.test.name)} | ${markdownCell(row.test.file)} | ${markdownCell(row.test.tracePath)} | ${row.test.artifacts.length} |`
12563
+ );
12234
12564
  }
12235
- const stale = await indexIsStale(snapshot, traceDir);
12236
- const payload = {
12237
- ok: true,
12238
- exists: true,
12239
- indexPath: indexPath2,
12240
- traceDir,
12241
- stale,
12242
- builtAt: snapshot.builtAt,
12243
- entries: snapshot.entries.length,
12244
- warnings: snapshot.warnings
12245
- };
12246
- if (options.json) {
12247
- console.log(JSON.stringify(payload, null, 2));
12248
- return;
12565
+ }
12566
+ lines.push("", "## Manifests", "", "| Framework | File | Generated | Artifacts |", "| --- | --- | --- | --- |");
12567
+ for (const manifest of result.manifests) {
12568
+ lines.push(
12569
+ `| ${markdownCell(manifest.framework)} | ${markdownCell(manifest.manifestFile)} | ${markdownCell(manifest.generatedAt)} | ${manifest.artifacts.length} |`
12570
+ );
12571
+ }
12572
+ lines.push("", "## Artifacts", "", "| Framework | Kind | Path | Format | Profile |", "| --- | --- | --- | --- | --- |");
12573
+ const artifacts = result.manifests.flatMap(
12574
+ (manifest) => manifest.artifacts.map((artifact) => ({ framework: manifest.framework, artifact }))
12575
+ );
12576
+ if (artifacts.length === 0) {
12577
+ lines.push("| unknown | unknown | No artifacts found | unknown | unknown |");
12578
+ } else {
12579
+ for (const row of artifacts) {
12580
+ lines.push(
12581
+ `| ${markdownCell(row.framework)} | ${markdownCell(row.artifact.kind)} | ${markdownCell(row.artifact.path)} | ${markdownCell(row.artifact.format)} | ${markdownCell(row.artifact.redactionProfile)} |`
12582
+ );
12249
12583
  }
12250
- console.log(`Index: ${indexPath2}`);
12251
- console.log(`Built: ${snapshot.builtAt}`);
12252
- console.log(`Entries: ${snapshot.entries.length}`);
12253
- console.log(`Stale: ${stale ? "yes" : "no"}`);
12254
- if (snapshot.warnings.length > 0) {
12255
- for (const warning of snapshot.warnings) {
12256
- console.log(`warning: ${warning}`);
12257
- }
12584
+ }
12585
+ lines.push("");
12586
+ return `${lines.join("\n")}
12587
+ `;
12588
+ }
12589
+ function safeText(value) {
12590
+ const compact = value.replace(/\s+/g, " ").trim();
12591
+ const redacted = compact.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(
12592
+ /\b(?:api[_-]?key|authorization|token|secret|password)\s*[:=]\s*[^,\s]+/gi,
12593
+ "$1=[REDACTED]"
12594
+ );
12595
+ if (redacted.length <= MAX_TEXT) return redacted;
12596
+ return `${redacted.slice(0, MAX_TEXT - 12)}...[truncated]`;
12597
+ }
12598
+ async function ciSummaryCommand(manifestPaths, options = {}) {
12599
+ if (manifestPaths.length === 0) {
12600
+ console.error("At least one reporter manifest path is required.");
12601
+ process.exitCode = 1;
12602
+ return;
12603
+ }
12604
+ let result;
12605
+ try {
12606
+ const manifests = [];
12607
+ for (const manifestPath of manifestPaths) {
12608
+ manifests.push(await readReporterManifest(manifestPath));
12258
12609
  }
12259
- } catch (e) {
12260
- const msg = e instanceof Error ? e.message : String(e);
12261
- console.error(`[AgentInspect] index status failed: ${msg}`);
12610
+ manifests.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
12611
+ result = summarize3(manifests);
12612
+ } catch (error) {
12613
+ const message = error instanceof Error ? error.message : String(error);
12614
+ console.error(`[AgentInspect] ci-summary failed: ${message}`);
12262
12615
  process.exitCode = 1;
12616
+ return;
12617
+ }
12618
+ const markdown = renderMarkdown2(result);
12619
+ const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
12620
+ if (outputPath !== void 0) {
12621
+ await mkdir(path13.dirname(outputPath), { recursive: true });
12622
+ await writeFile(outputPath, markdown, "utf-8");
12623
+ }
12624
+ const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
12625
+ if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
12626
+ const summaryPath = path13.resolve(summaryTarget);
12627
+ await mkdir(path13.dirname(summaryPath), { recursive: true });
12628
+ await appendFile(summaryPath, `
12629
+ ${markdown}`, "utf-8");
12630
+ }
12631
+ if (options.json === true) {
12632
+ console.log(writeJson5(result).trimEnd());
12633
+ } else if (outputPath !== void 0) {
12634
+ console.log(`Wrote AgentInspect CI summary to ${outputPath}`);
12635
+ console.log(`Status: ${result.status}`);
12636
+ } else {
12637
+ console.log(markdown.trimEnd());
12263
12638
  }
12264
12639
  }
12265
- async function indexCleanCommand(options = {}) {
12266
- try {
12267
- const traceDir = resolveTraceDir({ dir: options.dir });
12268
- const indexPath2 = traceIndexPath(traceDir);
12269
- await rm(indexPath2, { force: true });
12270
- if (options.json) {
12271
- console.log(JSON.stringify({ ok: true, removed: indexPath2 }, null, 2));
12272
- return;
12640
+ var CONFIG_FILE = "agent-inspect.config.ts";
12641
+ var TRACE_DIR = ".agent-inspect";
12642
+ var GITKEEP = ".agent-inspect/.gitkeep";
12643
+ function normalizeFramework(value) {
12644
+ const raw = (value ?? "custom").trim();
12645
+ if (raw === "ai-sdk" || raw === "openai-agents" || raw === "langchain" || raw === "custom") {
12646
+ return raw;
12647
+ }
12648
+ throw new Error(
12649
+ "Unsupported --framework value. Use ai-sdk, openai-agents, langchain, or custom."
12650
+ );
12651
+ }
12652
+ function configTemplate(framework) {
12653
+ const base = `/**
12654
+ * AgentInspect local config (metadata-only capture by default).
12655
+ * See https://github.com/rajudandigam/agent-inspect/blob/main/docs/SAFE-TRACE-SHARING.md
12656
+ */
12657
+ export const agentInspectConfig = {
12658
+ traceDir: ".agent-inspect",
12659
+ enabled: process.env.AGENT_INSPECT !== "0",
12660
+ redactionProfile: "local" as const,
12661
+ };
12662
+ `;
12663
+ if (framework === "custom") return base;
12664
+ return `${base}
12665
+ export const framework = "${framework}" as const;
12666
+ `;
12667
+ }
12668
+ function demoTemplate(framework) {
12669
+ switch (framework) {
12670
+ case "ai-sdk":
12671
+ return `/**
12672
+ * AI SDK starter \u2014 metadata-only telemetry (no real model calls in this demo).
12673
+ * Install: npm install agent-inspect @agent-inspect/ai-sdk ai
12674
+ */
12675
+ import { inspectRun, step } from "agent-inspect";
12676
+
12677
+ async function main() {
12678
+ await inspectRun("ai-sdk-demo", async () => {
12679
+ await step.tool("mock-generate", async () => ({ text: "ok" }));
12680
+ }, { traceDir: ".agent-inspect", silent: true });
12681
+ console.log("Trace written to .agent-inspect/");
12682
+ }
12683
+
12684
+ main().catch((error) => {
12685
+ console.error(error);
12686
+ process.exitCode = 1;
12687
+ });
12688
+ `;
12689
+ case "openai-agents":
12690
+ return `/**
12691
+ * OpenAI Agents starter \u2014 use @agent-inspect/openai-agents for local-only processors.
12692
+ * This demo uses manual steps (no API keys).
12693
+ */
12694
+ import { inspectRun, step } from "agent-inspect";
12695
+
12696
+ async function main() {
12697
+ await inspectRun("openai-agents-demo", async () => {
12698
+ await step.tool("mock-agent-run", async () => "ok");
12699
+ }, { traceDir: ".agent-inspect", silent: true });
12700
+ console.log("Trace written to .agent-inspect/");
12701
+ }
12702
+
12703
+ main().catch((error) => {
12704
+ console.error(error);
12705
+ process.exitCode = 1;
12706
+ });
12707
+ `;
12708
+ case "langchain":
12709
+ return `/**
12710
+ * LangChain starter \u2014 wire @agent-inspect/langchain callbacks in your app.
12711
+ * This demo uses manual steps (no API keys).
12712
+ */
12713
+ import { inspectRun, step } from "agent-inspect";
12714
+
12715
+ async function main() {
12716
+ await inspectRun("langchain-demo", async () => {
12717
+ await step.tool("mock-chain", async () => "ok");
12718
+ }, { traceDir: ".agent-inspect", silent: true });
12719
+ console.log("Trace written to .agent-inspect/");
12720
+ }
12721
+
12722
+ main().catch((error) => {
12723
+ console.error(error);
12724
+ process.exitCode = 1;
12725
+ });
12726
+ `;
12727
+ default:
12728
+ return `import { observe } from "agent-inspect";
12729
+
12730
+ class DemoAgent {
12731
+ async run(input: { question: string }) {
12732
+ return { answer: \`Echo: \${input.question}\` };
12733
+ }
12734
+ }
12735
+
12736
+ const agent = observe(new DemoAgent(), {
12737
+ traceDir: ".agent-inspect",
12738
+ silent: true,
12739
+ });
12740
+
12741
+ await agent.run({ question: "hello" });
12742
+ console.log("Trace written to .agent-inspect/");
12743
+ `;
12744
+ }
12745
+ }
12746
+ function githubWorkflowTemplate() {
12747
+ return `name: AgentInspect artifacts
12748
+
12749
+ on:
12750
+ workflow_dispatch:
12751
+ push:
12752
+ branches: [main]
12753
+
12754
+ jobs:
12755
+ trace-artifacts:
12756
+ runs-on: ubuntu-latest
12757
+ steps:
12758
+ - uses: actions/checkout@v4
12759
+ - uses: actions/setup-node@v4
12760
+ with:
12761
+ node-version: "22"
12762
+ - run: npm ci
12763
+ - run: npm test
12764
+ - name: Upload AgentInspect traces
12765
+ if: always()
12766
+ uses: actions/upload-artifact@v4
12767
+ with:
12768
+ name: agent-inspect-traces
12769
+ path: .agent-inspect/**/*.jsonl
12770
+ if-no-files-found: ignore
12771
+ `;
12772
+ }
12773
+ async function planInit(options = {}) {
12774
+ const framework = normalizeFramework(options.framework);
12775
+ const cwd = path13.resolve(options.cwd ?? process.cwd());
12776
+ const demoPath = framework === "custom" ? path13.join("examples", "agent-inspect-demo.mjs") : path13.join("examples", `agent-inspect-${framework}-demo.mjs`);
12777
+ const candidates = [
12778
+ { rel: CONFIG_FILE, content: configTemplate(framework) },
12779
+ { rel: GITKEEP, content: "" },
12780
+ { rel: demoPath, content: demoTemplate(framework) }
12781
+ ];
12782
+ if (options.ci === "github") {
12783
+ candidates.push({
12784
+ rel: ".github/workflows/agent-inspect-artifacts.yml",
12785
+ content: githubWorkflowTemplate()
12786
+ });
12787
+ }
12788
+ const files = [];
12789
+ for (const candidate of candidates) {
12790
+ const abs = path13.join(cwd, candidate.rel);
12791
+ try {
12792
+ await access(abs);
12793
+ files.push({
12794
+ path: candidate.rel,
12795
+ action: "skip",
12796
+ reason: "file already exists"
12797
+ });
12798
+ } catch (error) {
12799
+ if (error.code !== "ENOENT") throw error;
12800
+ files.push({ path: candidate.rel, action: "create" });
12273
12801
  }
12274
- console.log(`Removed index: ${indexPath2}`);
12275
- } catch (e) {
12276
- const msg = e instanceof Error ? e.message : String(e);
12277
- console.error(`[AgentInspect] index clean failed: ${msg}`);
12278
- process.exitCode = 1;
12279
12802
  }
12803
+ return { framework, ...options.ci ? { ci: options.ci } : {}, files };
12280
12804
  }
12281
- var PACKAGE = "@agent-inspect/index-sqlite";
12282
- function isModuleNotFound3(e) {
12283
- return e !== null && typeof e === "object" && "code" in e && (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND");
12284
- }
12285
- async function loadIndexSqlite() {
12286
- try {
12287
- return await import('./src-VQT7QLGV.mjs');
12288
- } catch (e) {
12289
- if (isModuleNotFound3(e)) {
12290
- console.error(
12291
- `The optional SQLite index is not installed. Run: npm install ${PACKAGE}`
12292
- );
12293
- process.exitCode = 1;
12294
- return null;
12805
+ async function writePlannedFiles(plan, cwd, options) {
12806
+ const written = [];
12807
+ for (const entry of plan.files) {
12808
+ if (entry.action === "skip") {
12809
+ continue;
12295
12810
  }
12296
- const msg = e instanceof Error ? e.message : String(e);
12297
- console.error(`[AgentInspect] failed to load ${PACKAGE}: ${msg}`);
12298
- process.exitCode = 1;
12299
- return null;
12300
- }
12301
- }
12302
- function parsePositiveInt(raw, flag) {
12303
- if (raw === void 0 || raw.trim() === "") return void 0;
12304
- const parsed = Number.parseInt(raw, 10);
12305
- if (!Number.isFinite(parsed) || parsed <= 0) {
12306
- throw new Error(`${flag} must be a positive integer.`);
12811
+ const abs = path13.join(cwd, entry.path);
12812
+ if (options.dryRun) {
12813
+ written.push(entry.path);
12814
+ continue;
12815
+ }
12816
+ await mkdir(path13.dirname(abs), { recursive: true });
12817
+ const content = entry.path === CONFIG_FILE ? configTemplate(plan.framework) : entry.path === GITKEEP ? "" : entry.path.endsWith(".yml") ? githubWorkflowTemplate() : demoTemplate(plan.framework);
12818
+ await writeFile(abs, content, "utf-8");
12819
+ written.push(entry.path);
12307
12820
  }
12308
- return parsed;
12821
+ return written;
12309
12822
  }
12310
- async function newestTraceMtimeMs2(traceDir) {
12311
- let newest = 0;
12823
+ async function initCommand(options = {}) {
12824
+ const cwd = path13.resolve(options.cwd ?? process.cwd());
12312
12825
  try {
12313
- const files = await readdir(traceDir);
12314
- for (const file of files) {
12315
- if (!file.endsWith(".jsonl")) continue;
12316
- try {
12317
- const s = await stat(path11.join(traceDir, file));
12318
- if (s.mtimeMs > newest) newest = s.mtimeMs;
12319
- } catch {
12826
+ const plan = await planInit({ ...options, cwd });
12827
+ const toWrite = plan.files.filter((file) => file.action === "create").map((f) => f.path);
12828
+ const skipped = plan.files.filter((file) => file.action === "skip");
12829
+ if (options.json) {
12830
+ const payload = {
12831
+ ok: true,
12832
+ version,
12833
+ framework: plan.framework,
12834
+ ci: plan.ci ?? null,
12835
+ dryRun: options.dryRun === true,
12836
+ planned: plan.files,
12837
+ wouldWrite: options.dryRun ? toWrite : void 0
12838
+ };
12839
+ console.log(JSON.stringify(payload, null, 2));
12840
+ if (options.dryRun) return;
12841
+ }
12842
+ const written = await writePlannedFiles(plan, cwd, options);
12843
+ if (!options.json) {
12844
+ console.log("AgentInspect init");
12845
+ console.log(`Framework: ${plan.framework}`);
12846
+ console.log(`Trace directory: ${TRACE_DIR}/`);
12847
+ if (options.dryRun) {
12848
+ console.log("Dry run \u2014 would create:");
12849
+ for (const file of toWrite) console.log(`- ${file}`);
12850
+ for (const file of skipped) {
12851
+ console.log(`- ${file.path} (skip: ${file.reason ?? "exists"})`);
12852
+ }
12853
+ return;
12854
+ }
12855
+ console.log("Created:");
12856
+ for (const file of written) console.log(`- ${file}`);
12857
+ for (const file of skipped) {
12858
+ console.log(`- ${file.path} (skipped: ${file.reason ?? "exists"})`);
12320
12859
  }
12860
+ console.log("\nNext: run your demo, then `npx agent-inspect list --dir .agent-inspect`");
12861
+ console.log("No dependencies were installed. Add packages manually when ready.");
12321
12862
  }
12322
- } catch {
12323
- }
12324
- return newest;
12325
- }
12326
- async function indexSqliteBuildCommand(options = {}) {
12327
- const mod = await loadIndexSqlite();
12328
- if (!mod) return;
12329
- const result = await mod.buildIndex({
12330
- traceDir: options.dir,
12331
- maxRuns: parsePositiveInt(options.maxRuns, "--max-runs")
12332
- });
12333
- if (options.json) {
12334
- console.log(JSON.stringify({ ok: true, ...result }, null, 2));
12335
- return;
12336
- }
12337
- console.log(`Built SQLite index: ${result.dbPath}`);
12338
- console.log(`Runs: ${result.runs} Steps: ${result.steps} Errors: ${result.errors}`);
12339
- for (const warning of result.warnings) console.log(`warning: ${warning}`);
12340
- }
12341
- async function indexSqliteStatusCommand(options = {}) {
12342
- const mod = await loadIndexSqlite();
12343
- if (!mod) return;
12344
- const traceDir = resolveTraceDir({ dir: options.dir });
12345
- const dbPath = mod.resolveIndexDbPath(traceDir);
12346
- const status = mod.indexStatus(dbPath);
12347
- const stale = mod.isIndexStale(dbPath, await newestTraceMtimeMs2(traceDir));
12348
- if (options.json) {
12349
- console.log(JSON.stringify({ ok: true, traceDir, stale, ...status }, null, 2));
12350
- return;
12351
- }
12352
- if (!status.exists) {
12353
- console.log(`No SQLite index at ${dbPath}`);
12354
- console.log("Run: agent-inspect index sqlite build");
12355
- return;
12356
- }
12357
- console.log(`Index: ${status.dbPath}`);
12358
- console.log(`Healthy: ${status.healthy ? "yes" : "no"}`);
12359
- console.log(`Built: ${status.builtAt ?? "unknown"}`);
12360
- console.log(`Runs: ${status.runs} Steps: ${status.steps}`);
12361
- console.log(`Stale: ${stale ? "yes" : "no"}`);
12362
- }
12363
- async function indexSqliteQueryCommand(options = {}) {
12364
- const mod = await loadIndexSqlite();
12365
- if (!mod) return;
12366
- const traceDir = resolveTraceDir({ dir: options.dir });
12367
- const dbPath = mod.resolveIndexDbPath(traceDir);
12368
- const status = mod.indexStatus(dbPath);
12369
- if (!status.exists || !status.healthy) {
12863
+ } catch (error) {
12864
+ const msg = error instanceof Error ? error.message : String(error);
12370
12865
  if (options.json) {
12371
- console.log(JSON.stringify({ ok: false, reason: "index-missing", dbPath }, null, 2));
12866
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
12372
12867
  } else {
12373
- console.log("No usable SQLite index. Run: agent-inspect index sqlite build");
12868
+ console.error(`[AgentInspect] init failed: ${msg}`);
12374
12869
  }
12375
12870
  process.exitCode = 1;
12376
- return;
12377
- }
12378
- const rows = mod.queryRuns(dbPath, {
12379
- status: options.status,
12380
- sessionId: options.session,
12381
- name: options.name,
12382
- kind: options.kind,
12383
- tool: options.tool,
12384
- limit: parsePositiveInt(options.limit, "--limit")
12385
- });
12386
- if (options.json) {
12387
- console.log(JSON.stringify({ ok: true, count: rows.length, runs: rows }, null, 2));
12388
- return;
12389
- }
12390
- if (rows.length === 0) {
12391
- console.log("No matching runs.");
12392
- return;
12393
- }
12394
- for (const run of rows) {
12395
- const parts = [
12396
- run.runId,
12397
- run.status ?? "unknown",
12398
- run.name ?? "",
12399
- run.durationMs != null ? `${run.durationMs}ms` : ""
12400
- ].filter((p) => p !== "");
12401
- console.log(parts.join(" "));
12402
- }
12403
- }
12404
- async function indexSqliteCleanCommand(options = {}) {
12405
- const mod = await loadIndexSqlite();
12406
- if (!mod) return;
12407
- const traceDir = resolveTraceDir({ dir: options.dir });
12408
- const dbPath = mod.resolveIndexDbPath(traceDir);
12409
- await mod.cleanIndex(dbPath);
12410
- if (options.json) {
12411
- console.log(JSON.stringify({ ok: true, removed: dbPath }, null, 2));
12412
- return;
12413
12871
  }
12414
- console.log(`Removed SQLite index: ${dbPath}`);
12415
12872
  }
12416
-
12417
- // packages/core/src/workspace/types.ts
12418
- var WORKSPACE_SCHEMA_VERSION = "1.0";
12419
- var WORKSPACE_DIR_NAME = ".agent-inspect";
12420
- var WORKSPACE_MANIFEST_FILENAME = "workspace.json";
12421
-
12422
- // packages/core/src/workspace/manifest.ts
12423
- var DEFAULT_WORKSPACE_LAYOUT = {
12424
- traceDirs: ["runs"],
12425
- reportsDir: "reports",
12426
- artifactsDir: "artifacts",
12427
- bundlesDir: "bundles",
12428
- notesDir: "notes"
12873
+ var OPTIONAL_PACKAGES = {
12874
+ custom: [],
12875
+ "ai-sdk": ["@agent-inspect/ai-sdk"],
12876
+ "openai-agents": ["@agent-inspect/openai-agents"],
12877
+ langchain: ["@agent-inspect/langchain"]
12429
12878
  };
12430
- var DEFAULT_REDACTION_PROFILE = "share";
12431
- var REDACTION_PROFILES2 = [
12432
- "local",
12433
- "share",
12434
- "strict"
12435
- ];
12436
- var INDEX_TYPES = ["none", "sqlite", "custom"];
12437
- var MAX_WORKSPACE_MANIFEST_BYTES = 64 * 1024;
12438
- function createDefaultWorkspaceManifest(options) {
12439
- const project = typeof options.project === "string" ? options.project.trim() : "";
12440
- const index = {
12441
- enabled: options.index?.enabled ?? false,
12442
- type: options.index?.type ?? "none",
12443
- ...options.index?.path !== void 0 ? { path: options.index.path } : {}
12444
- };
12445
- return {
12446
- schemaVersion: WORKSPACE_SCHEMA_VERSION,
12447
- project,
12448
- createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
12449
- traceDirs: options.traceDirs ? [...options.traceDirs] : [...DEFAULT_WORKSPACE_LAYOUT.traceDirs],
12450
- reportsDir: options.reportsDir ?? DEFAULT_WORKSPACE_LAYOUT.reportsDir,
12451
- artifactsDir: options.artifactsDir ?? DEFAULT_WORKSPACE_LAYOUT.artifactsDir,
12452
- bundlesDir: options.bundlesDir ?? DEFAULT_WORKSPACE_LAYOUT.bundlesDir,
12453
- notesDir: options.notesDir ?? DEFAULT_WORKSPACE_LAYOUT.notesDir,
12454
- redactionProfile: options.redactionProfile ?? DEFAULT_REDACTION_PROFILE,
12455
- index
12456
- };
12457
- }
12458
- function isPlainObject(value) {
12459
- return typeof value === "object" && value !== null && !Array.isArray(value);
12460
- }
12461
- function isSafeRelativeWorkspacePath(p) {
12462
- if (typeof p !== "string") return false;
12463
- const trimmed = p.trim();
12464
- if (trimmed === "") return false;
12465
- if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return false;
12466
- if (/^[a-zA-Z]:/.test(trimmed)) return false;
12467
- const segments = trimmed.split(/[/\\]+/);
12468
- return !segments.some((seg) => seg === "..");
12469
- }
12470
- function validateDirField(value, field, errors) {
12471
- if (typeof value !== "string" || value.trim() === "") {
12472
- errors.push(`${field} must be a non-empty string`);
12473
- return;
12474
- }
12475
- if (!isSafeRelativeWorkspacePath(value)) {
12476
- errors.push(
12477
- `${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
12478
- );
12879
+ function nodeVersionCheck() {
12880
+ const major = Number(process2.versions.node.split(".")[0]);
12881
+ if (Number.isNaN(major) || major < 20) {
12882
+ return {
12883
+ id: "node-version",
12884
+ status: "fail",
12885
+ message: `Node ${process2.versions.node} is below the supported minimum (20).`,
12886
+ remediation: "Upgrade to Node 20 LTS or newer.",
12887
+ evidence: process2.versions.node
12888
+ };
12479
12889
  }
12890
+ return {
12891
+ id: "node-version",
12892
+ status: "pass",
12893
+ message: `Node ${process2.versions.node} meets the minimum (>=20).`,
12894
+ evidence: process2.versions.node
12895
+ };
12480
12896
  }
12481
- function validateIndex(value, errors) {
12482
- if (!isPlainObject(value)) {
12483
- errors.push("index must be an object");
12484
- return void 0;
12485
- }
12486
- if (typeof value.enabled !== "boolean") {
12487
- errors.push("index.enabled must be a boolean");
12488
- }
12489
- if (!INDEX_TYPES.includes(value.type)) {
12490
- errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
12491
- }
12492
- if (value.path !== void 0 && !isSafeRelativeWorkspacePath(value.path)) {
12493
- errors.push(
12494
- 'index.path must be a relative path inside the workspace (no absolute paths or ".." traversal)'
12495
- );
12897
+ function envCheck(name, optional = true) {
12898
+ const value = process2.env[name];
12899
+ if (value === void 0 || value.trim() === "") {
12900
+ return {
12901
+ id: `env-${name.toLowerCase()}`,
12902
+ status: optional ? "skipped" : "warn",
12903
+ message: `${name} is not set.`,
12904
+ remediation: optional ? void 0 : `Set ${name}=1 to enable tracing.`
12905
+ };
12496
12906
  }
12497
- if (errors.length > 0) return void 0;
12498
12907
  return {
12499
- enabled: value.enabled,
12500
- type: value.type,
12501
- ...value.path !== void 0 ? { path: value.path } : {}
12908
+ id: `env-${name.toLowerCase()}`,
12909
+ status: "pass",
12910
+ message: `${name} is set.`,
12911
+ evidence: value
12502
12912
  };
12503
12913
  }
12504
- function validateWorkspaceManifest(input3) {
12505
- const errors = [];
12506
- const warnings = [];
12507
- if (!isPlainObject(input3)) {
12508
- return { ok: false, errors: ["manifest must be an object"], warnings };
12509
- }
12510
- if (input3.schemaVersion !== WORKSPACE_SCHEMA_VERSION) {
12511
- errors.push(
12512
- `schemaVersion must be "${WORKSPACE_SCHEMA_VERSION}" (received ${JSON.stringify(
12513
- input3.schemaVersion
12514
- )})`
12515
- );
12516
- }
12517
- if (typeof input3.project !== "string" || input3.project.trim() === "") {
12518
- errors.push("project must be a non-empty string");
12914
+ async function traceDirWritable(traceDir) {
12915
+ const resolved = path13.resolve(traceDir);
12916
+ try {
12917
+ await mkdir(resolved, { recursive: true });
12918
+ await access(resolved, constants.W_OK);
12919
+ return {
12920
+ id: "trace-dir-writable",
12921
+ status: "pass",
12922
+ message: `Trace directory is writable: ${resolved}`,
12923
+ evidence: resolved
12924
+ };
12925
+ } catch (error) {
12926
+ return {
12927
+ id: "trace-dir-writable",
12928
+ status: "fail",
12929
+ message: `Trace directory is not writable: ${resolved}`,
12930
+ remediation: "Create the directory or set AGENT_INSPECT_TRACE_DIR to a writable path.",
12931
+ evidence: error instanceof Error ? error.message : String(error)
12932
+ };
12519
12933
  }
12520
- if (typeof input3.createdAt !== "string" || input3.createdAt.trim() === "") {
12521
- errors.push("createdAt must be a non-empty ISO-8601 string");
12522
- } else if (Number.isNaN(Date.parse(input3.createdAt))) {
12523
- errors.push("createdAt must be a valid ISO-8601 date string");
12934
+ }
12935
+ function resolvePackage(cwd, name) {
12936
+ const require2 = createRequire(path13.join(cwd, "package.json"));
12937
+ try {
12938
+ const pkgPath = require2.resolve(`${name}/package.json`);
12939
+ const pkg = require2(pkgPath);
12940
+ return { ok: true, version: pkg.version };
12941
+ } catch {
12942
+ return { ok: false };
12524
12943
  }
12525
- if (!Array.isArray(input3.traceDirs) || input3.traceDirs.length === 0) {
12526
- errors.push("traceDirs must be a non-empty array");
12527
- } else {
12528
- input3.traceDirs.forEach((dir, i) => {
12529
- if (typeof dir !== "string" || dir.trim() === "") {
12530
- errors.push(`traceDirs[${i}] must be a non-empty string`);
12531
- } else if (!isSafeRelativeWorkspacePath(dir)) {
12532
- errors.push(
12533
- `traceDirs[${i}] must be a relative path inside the workspace (no absolute paths or ".." traversal)`
12534
- );
12535
- }
12944
+ }
12945
+ function importSmoke(cwd) {
12946
+ const results = [];
12947
+ const require2 = createRequire(path13.join(cwd, "package.json"));
12948
+ try {
12949
+ require2.resolve("agent-inspect");
12950
+ results.push({
12951
+ id: "import-agent-inspect",
12952
+ status: "pass",
12953
+ message: "agent-inspect resolves from the current project."
12954
+ });
12955
+ } catch {
12956
+ results.push({
12957
+ id: "import-agent-inspect",
12958
+ status: "warn",
12959
+ message: "agent-inspect is not installed in the current project.",
12960
+ remediation: "Run npm install agent-inspect (or pnpm add agent-inspect)."
12536
12961
  });
12537
12962
  }
12538
- validateDirField(input3.reportsDir, "reportsDir", errors);
12539
- validateDirField(input3.artifactsDir, "artifactsDir", errors);
12540
- validateDirField(input3.bundlesDir, "bundlesDir", errors);
12541
- validateDirField(input3.notesDir, "notesDir", errors);
12542
- if (!REDACTION_PROFILES2.includes(input3.redactionProfile)) {
12543
- errors.push(`redactionProfile must be one of: ${REDACTION_PROFILES2.join(", ")}`);
12963
+ try {
12964
+ require2.resolve("agent-inspect/package.json");
12965
+ results.push({
12966
+ id: "import-agent-inspect-cjs",
12967
+ status: "pass",
12968
+ message: "CJS resolution for agent-inspect succeeded."
12969
+ });
12970
+ } catch {
12971
+ results.push({
12972
+ id: "import-agent-inspect-cjs",
12973
+ status: "skipped",
12974
+ message: "CJS resolution check skipped (package not installed locally)."
12975
+ });
12544
12976
  }
12545
- const indexErrors = [];
12546
- const index = validateIndex(input3.index, indexErrors);
12547
- errors.push(...indexErrors);
12548
- if (index && index.type !== "none" && !index.enabled) {
12549
- warnings.push(`index.type is "${index.type}" but index.enabled is false`);
12977
+ return results;
12978
+ }
12979
+ function optionalPackageChecks(cwd, framework) {
12980
+ const packages = framework !== void 0 ? OPTIONAL_PACKAGES[framework] : [
12981
+ "@agent-inspect/ai-sdk",
12982
+ "@agent-inspect/openai-agents",
12983
+ "@agent-inspect/langchain",
12984
+ "@agent-inspect/redact",
12985
+ "@agent-inspect/eval"
12986
+ ];
12987
+ return packages.map((name) => {
12988
+ const resolved = resolvePackage(cwd, name);
12989
+ if (!resolved.ok) {
12990
+ return {
12991
+ id: `optional-package-${name}`,
12992
+ status: framework !== void 0 ? "warn" : "skipped",
12993
+ message: `${name} is not installed.`,
12994
+ remediation: framework !== void 0 ? `npm install ${name}` : void 0
12995
+ };
12996
+ }
12997
+ return {
12998
+ id: `optional-package-${name}`,
12999
+ status: "pass",
13000
+ message: `${name} is available.`,
13001
+ evidence: resolved.version
13002
+ };
13003
+ });
13004
+ }
13005
+ function versionMismatchCheck(cwd) {
13006
+ const root = resolvePackage(cwd, "agent-inspect");
13007
+ if (!root.ok || root.version === void 0) {
13008
+ return {
13009
+ id: "version-alignment",
13010
+ status: "skipped",
13011
+ message: "Skipped version alignment \u2014 agent-inspect not installed locally."
13012
+ };
12550
13013
  }
12551
- if (errors.length > 0 || index === void 0) {
12552
- return { ok: false, errors, warnings };
13014
+ if (root.version !== version) {
13015
+ return {
13016
+ id: "version-alignment",
13017
+ status: "warn",
13018
+ message: `CLI version ${version} differs from local agent-inspect@${root.version}.`,
13019
+ remediation: "Align versions with npm install agent-inspect@latest",
13020
+ evidence: `cli=${version};local=${root.version}`
13021
+ };
12553
13022
  }
12554
- const manifest = {
12555
- schemaVersion: WORKSPACE_SCHEMA_VERSION,
12556
- project: input3.project.trim(),
12557
- createdAt: input3.createdAt,
12558
- traceDirs: input3.traceDirs.map((d) => d.trim()),
12559
- reportsDir: input3.reportsDir.trim(),
12560
- artifactsDir: input3.artifactsDir.trim(),
12561
- bundlesDir: input3.bundlesDir.trim(),
12562
- notesDir: input3.notesDir.trim(),
12563
- redactionProfile: input3.redactionProfile,
12564
- index
13023
+ return {
13024
+ id: "version-alignment",
13025
+ status: "pass",
13026
+ message: `CLI and local agent-inspect are aligned at ${version}.`
12565
13027
  };
12566
- return { ok: true, manifest, errors, warnings };
12567
13028
  }
12568
- function parseWorkspaceManifest(json) {
12569
- const warnings = [];
12570
- if (typeof json !== "string") {
12571
- return { ok: false, errors: ["manifest input must be a string"], warnings };
13029
+ async function runDoctorChecks(options = {}) {
13030
+ const cwd = path13.resolve(options.cwd ?? process2.cwd());
13031
+ const traceDir = options.traceDir?.trim() || process2.env.AGENT_INSPECT_TRACE_DIR?.trim() || ".agent-inspect";
13032
+ const checks2 = [
13033
+ nodeVersionCheck(),
13034
+ {
13035
+ id: "cli-version",
13036
+ status: "pass",
13037
+ message: `agent-inspect CLI ${version}`,
13038
+ evidence: version
13039
+ },
13040
+ await traceDirWritable(traceDir),
13041
+ envCheck("AGENT_INSPECT"),
13042
+ envCheck("AGENT_INSPECT_TRACE_DIR"),
13043
+ versionMismatchCheck(cwd)
13044
+ ];
13045
+ if (options.checkImports !== false) {
13046
+ checks2.push(...importSmoke(cwd));
12572
13047
  }
12573
- if (json.length > MAX_WORKSPACE_MANIFEST_BYTES) {
12574
- return {
12575
- ok: false,
12576
- errors: [
12577
- `manifest exceeds maximum size of ${MAX_WORKSPACE_MANIFEST_BYTES} bytes`
12578
- ],
12579
- warnings
12580
- };
13048
+ checks2.push(...optionalPackageChecks(cwd, options.framework));
13049
+ return checks2.sort((a, b) => a.id.localeCompare(b.id));
13050
+ }
13051
+ async function doctorCommand(options = {}) {
13052
+ const checks2 = await runDoctorChecks(options);
13053
+ const failed = checks2.filter((check) => check.status === "fail").length;
13054
+ const warned = checks2.filter((check) => check.status === "warn").length;
13055
+ if (options.json) {
13056
+ console.log(
13057
+ JSON.stringify(
13058
+ {
13059
+ ok: failed === 0,
13060
+ version,
13061
+ summary: { pass: checks2.filter((c) => c.status === "pass").length, warn: warned, fail: failed },
13062
+ checks: checks2
13063
+ },
13064
+ null,
13065
+ 2
13066
+ )
13067
+ );
13068
+ if (failed > 0) process2.exitCode = 1;
13069
+ return;
12581
13070
  }
12582
- let parsed;
12583
- try {
12584
- parsed = JSON.parse(json);
12585
- } catch {
12586
- return { ok: false, errors: ["manifest is not valid JSON"], warnings };
13071
+ console.log("AgentInspect doctor");
13072
+ for (const check of checks2) {
13073
+ const tag = check.status.toUpperCase();
13074
+ console.log(`[${tag}] ${check.id}: ${check.message}`);
13075
+ if (check.remediation) console.log(` \u2192 ${check.remediation}`);
12587
13076
  }
12588
- return validateWorkspaceManifest(parsed);
12589
- }
12590
- function serializeWorkspaceManifest(manifest) {
12591
- return `${JSON.stringify(manifest, null, 2)}
12592
- `;
13077
+ console.log(`
13078
+ Summary: ${failed} failed, ${warned} warnings`);
13079
+ if (failed > 0) process2.exitCode = 1;
12593
13080
  }
12594
- var INDEX_DIR_NAME = "index";
12595
- function resolveWorkspaceLocation(cwd = process.cwd()) {
12596
- const projectRoot = path11.resolve(cwd);
12597
- const workspaceDir = path11.join(projectRoot, WORKSPACE_DIR_NAME);
12598
- return {
12599
- projectRoot,
12600
- workspaceDir,
12601
- manifestPath: path11.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
12602
- };
13081
+
13082
+ // packages/adapter-sdk/src/indexer.ts
13083
+ function defineIndexer(indexer) {
13084
+ if (!indexer.id.trim()) throw new Error("indexer id is required");
13085
+ return indexer;
12603
13086
  }
12604
- function resolveInsideWorkspace(workspaceDir, relative) {
12605
- const base = path11.resolve(workspaceDir);
12606
- const resolved = path11.resolve(base, relative);
12607
- const rel = path11.relative(base, resolved);
12608
- if (rel === "" || rel === "." || !rel.startsWith("..") && !path11.isAbsolute(rel)) {
12609
- return resolved;
13087
+ async function indexIsStale(snapshot, traceDir) {
13088
+ const builtMs = Date.parse(snapshot.builtAt);
13089
+ if (Number.isNaN(builtMs)) return true;
13090
+ const td = new TraceDirectory({ dir: traceDir });
13091
+ const files = await td.list();
13092
+ for (const file of files) {
13093
+ const stats = await td.getFileStats(file);
13094
+ if (stats.mtimeMs > builtMs) return true;
12610
13095
  }
12611
- throw new Error(
12612
- `Workspace path "${relative}" resolves outside the workspace directory`
12613
- );
13096
+ return false;
12614
13097
  }
12615
- async function pathExists(p) {
12616
- try {
12617
- await access(p);
12618
- return true;
12619
- } catch {
12620
- return false;
13098
+ function createTraceDirectoryIndexer() {
13099
+ return defineIndexer({
13100
+ id: "trace-directory-metadata",
13101
+ async rebuild(traceDir, options = {}) {
13102
+ const warnings = [];
13103
+ const td = new TraceDirectory({ dir: traceDir });
13104
+ const files = await td.list();
13105
+ const maxEntries = options.maxEntries ?? 1e4;
13106
+ if (files.length > maxEntries) {
13107
+ warnings.push(
13108
+ `indexer.truncated: trace directory has ${files.length} files; indexing first ${maxEntries}`
13109
+ );
13110
+ }
13111
+ const slice = files.slice(0, maxEntries);
13112
+ const metas = await loadTraceMetadataList(
13113
+ traceDir,
13114
+ slice,
13115
+ (fileName) => td.getPath(fileName)
13116
+ );
13117
+ const entries = metas.map((meta) => ({
13118
+ runId: meta.runId,
13119
+ path: meta.filePath,
13120
+ name: meta.name,
13121
+ startedAt: meta.startedAt,
13122
+ status: meta.status
13123
+ })).sort((a, b) => a.runId.localeCompare(b.runId));
13124
+ if (entries.length < slice.length) {
13125
+ warnings.push(
13126
+ `indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
13127
+ );
13128
+ }
13129
+ return {
13130
+ traceDir,
13131
+ builtAt: (/* @__PURE__ */ new Date()).toISOString(),
13132
+ entries,
13133
+ warnings
13134
+ };
13135
+ }
13136
+ });
13137
+ }
13138
+
13139
+ // packages/cli/src/index-cmd.ts
13140
+ var INDEX_FILENAME = ".agent-inspect-index.json";
13141
+ function traceIndexPath(traceDir) {
13142
+ return path13.join(traceDir, INDEX_FILENAME);
13143
+ }
13144
+ function parseMaxEntries(raw) {
13145
+ if (raw === void 0 || raw.trim() === "") return void 0;
13146
+ const parsed = Number.parseInt(raw, 10);
13147
+ if (!Number.isFinite(parsed) || parsed <= 0) {
13148
+ throw new Error("--max-entries must be a positive integer.");
12621
13149
  }
13150
+ return parsed;
12622
13151
  }
12623
- async function isWritable(p) {
13152
+ async function readSnapshot(indexPath2) {
12624
13153
  try {
12625
- await access(p, constants$1.W_OK);
12626
- return true;
13154
+ const raw = await readFile(indexPath2, "utf8");
13155
+ return JSON.parse(raw);
12627
13156
  } catch {
12628
- return false;
13157
+ return void 0;
12629
13158
  }
12630
13159
  }
12631
- async function listJsonl(dir) {
13160
+ async function indexBuildCommand(options = {}) {
12632
13161
  try {
12633
- const entries = await readdir(dir);
12634
- return entries.filter((f) => f.endsWith(".jsonl"));
12635
- } catch {
12636
- return [];
13162
+ const traceDir = resolveTraceDir({ dir: options.dir });
13163
+ await mkdir(traceDir, { recursive: true });
13164
+ const indexer = createTraceDirectoryIndexer();
13165
+ const snapshot = await indexer.rebuild(traceDir, {
13166
+ maxEntries: parseMaxEntries(options.maxEntries)
13167
+ });
13168
+ const indexPath2 = traceIndexPath(traceDir);
13169
+ await writeFile(indexPath2, `${JSON.stringify(snapshot, null, 2)}
13170
+ `, "utf8");
13171
+ if (options.json) {
13172
+ console.log(JSON.stringify({ ok: true, indexPath: indexPath2, ...snapshot }, null, 2));
13173
+ return;
13174
+ }
13175
+ console.log(`Built trace index: ${indexPath2}`);
13176
+ console.log(`Entries: ${snapshot.entries.length}`);
13177
+ if (snapshot.warnings.length > 0) {
13178
+ for (const warning of snapshot.warnings) {
13179
+ console.log(`warning: ${warning}`);
13180
+ }
13181
+ }
13182
+ } catch (e) {
13183
+ const msg = e instanceof Error ? e.message : String(e);
13184
+ console.error(`[AgentInspect] index build failed: ${msg}`);
13185
+ process.exitCode = 1;
12637
13186
  }
12638
13187
  }
12639
- async function countFiles(dir) {
13188
+ async function indexStatusCommand(options = {}) {
12640
13189
  try {
12641
- const entries = await readdir(dir, { withFileTypes: true });
12642
- return entries.filter((e) => e.isFile()).length;
12643
- } catch {
12644
- return 0;
13190
+ const traceDir = resolveTraceDir({ dir: options.dir });
13191
+ const indexPath2 = traceIndexPath(traceDir);
13192
+ const snapshot = await readSnapshot(indexPath2);
13193
+ if (!snapshot) {
13194
+ const payload2 = { ok: true, exists: false, indexPath: indexPath2, traceDir, stale: true };
13195
+ if (options.json) {
13196
+ console.log(JSON.stringify(payload2, null, 2));
13197
+ } else {
13198
+ console.log(`No index at ${indexPath2}`);
13199
+ console.log("Run: agent-inspect index build");
13200
+ }
13201
+ return;
13202
+ }
13203
+ const stale = await indexIsStale(snapshot, traceDir);
13204
+ const payload = {
13205
+ ok: true,
13206
+ exists: true,
13207
+ indexPath: indexPath2,
13208
+ traceDir,
13209
+ stale,
13210
+ builtAt: snapshot.builtAt,
13211
+ entries: snapshot.entries.length,
13212
+ warnings: snapshot.warnings
13213
+ };
13214
+ if (options.json) {
13215
+ console.log(JSON.stringify(payload, null, 2));
13216
+ return;
13217
+ }
13218
+ console.log(`Index: ${indexPath2}`);
13219
+ console.log(`Built: ${snapshot.builtAt}`);
13220
+ console.log(`Entries: ${snapshot.entries.length}`);
13221
+ console.log(`Stale: ${stale ? "yes" : "no"}`);
13222
+ if (snapshot.warnings.length > 0) {
13223
+ for (const warning of snapshot.warnings) {
13224
+ console.log(`warning: ${warning}`);
13225
+ }
13226
+ }
13227
+ } catch (e) {
13228
+ const msg = e instanceof Error ? e.message : String(e);
13229
+ console.error(`[AgentInspect] index status failed: ${msg}`);
13230
+ process.exitCode = 1;
12645
13231
  }
12646
13232
  }
12647
- async function readWorkspaceManifestFile(location) {
12648
- let raw;
13233
+ async function indexCleanCommand(options = {}) {
12649
13234
  try {
12650
- raw = await readFile(location.manifestPath, "utf-8");
12651
- } catch {
12652
- return { exists: false, ok: false, errors: ["workspace.json not found"], warnings: [] };
13235
+ const traceDir = resolveTraceDir({ dir: options.dir });
13236
+ const indexPath2 = traceIndexPath(traceDir);
13237
+ await rm(indexPath2, { force: true });
13238
+ if (options.json) {
13239
+ console.log(JSON.stringify({ ok: true, removed: indexPath2 }, null, 2));
13240
+ return;
13241
+ }
13242
+ console.log(`Removed index: ${indexPath2}`);
13243
+ } catch (e) {
13244
+ const msg = e instanceof Error ? e.message : String(e);
13245
+ console.error(`[AgentInspect] index clean failed: ${msg}`);
13246
+ process.exitCode = 1;
12653
13247
  }
12654
- const parsed = parseWorkspaceManifest(raw);
12655
- return {
12656
- exists: true,
12657
- ok: parsed.ok,
12658
- ...parsed.manifest ? { manifest: parsed.manifest } : {},
12659
- errors: parsed.errors,
12660
- warnings: parsed.warnings
12661
- };
12662
13248
  }
12663
- async function createWorkspace(options = {}) {
12664
- const location = resolveWorkspaceLocation(options.cwd);
12665
- const dryRun = options.dryRun === true;
12666
- const existing = await readWorkspaceManifestFile(location);
12667
- const topLevelTraces = await listJsonl(location.workspaceDir);
12668
- const detectedExistingTraces = topLevelTraces.length > 0;
12669
- let manifest;
12670
- let created;
12671
- let adopted;
12672
- if (existing.exists && existing.ok && existing.manifest) {
12673
- manifest = existing.manifest;
12674
- created = false;
12675
- adopted = true;
12676
- } else {
12677
- const project = options.project?.trim() || path11.basename(location.projectRoot) || "workspace";
12678
- const traceDirs = detectedExistingTraces ? ["runs", "."] : ["runs"];
12679
- manifest = createDefaultWorkspaceManifest({
12680
- project,
12681
- traceDirs,
12682
- ...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {}
12683
- });
12684
- created = true;
12685
- adopted = detectedExistingTraces || existing.exists && !existing.ok;
12686
- }
12687
- const relDirs = uniqueDirs([
12688
- ...manifest.traceDirs.filter((d) => d !== "."),
12689
- manifest.reportsDir,
12690
- manifest.artifactsDir,
12691
- manifest.bundlesDir,
12692
- manifest.notesDir,
12693
- INDEX_DIR_NAME
12694
- ]);
12695
- const createdDirs = [];
12696
- for (const rel of relDirs) {
12697
- const abs = resolveInsideWorkspace(location.workspaceDir, rel);
12698
- if (await pathExists(abs)) continue;
12699
- createdDirs.push(rel);
12700
- if (!dryRun) await mkdir(abs, { recursive: true });
12701
- }
12702
- if (!dryRun && created) {
12703
- await mkdir(location.workspaceDir, { recursive: true });
12704
- await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
12705
- }
12706
- return {
12707
- location,
12708
- manifest,
12709
- created,
12710
- adopted,
12711
- createdDirs,
12712
- detectedExistingTraces,
12713
- dryRun
12714
- };
13249
+ var PACKAGE = "@agent-inspect/index-sqlite";
13250
+ function isModuleNotFound3(e) {
13251
+ return e !== null && typeof e === "object" && "code" in e && (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND");
12715
13252
  }
12716
- function uniqueDirs(dirs) {
12717
- const seen = /* @__PURE__ */ new Set();
12718
- const out = [];
12719
- for (const d of dirs) {
12720
- const t = d.trim();
12721
- if (t === "" || t === "." || seen.has(t)) continue;
12722
- seen.add(t);
12723
- out.push(t);
13253
+ async function loadIndexSqlite() {
13254
+ try {
13255
+ return await import('./src-FJOHPC4X.mjs');
13256
+ } catch (e) {
13257
+ if (isModuleNotFound3(e)) {
13258
+ console.error(
13259
+ `The optional SQLite index is not installed. Run: npm install ${PACKAGE}`
13260
+ );
13261
+ process.exitCode = 1;
13262
+ return null;
13263
+ }
13264
+ const msg = e instanceof Error ? e.message : String(e);
13265
+ console.error(`[AgentInspect] failed to load ${PACKAGE}: ${msg}`);
13266
+ process.exitCode = 1;
13267
+ return null;
12724
13268
  }
12725
- return out;
12726
13269
  }
12727
- async function getWorkspaceStatus(location, manifest) {
12728
- let traceFiles = 0;
12729
- for (const rel of manifest.traceDirs) {
12730
- const abs = resolveInsideWorkspace(location.workspaceDir, rel);
12731
- traceFiles += (await listJsonl(abs)).length;
13270
+ function parsePositiveInt(raw, flag) {
13271
+ if (raw === void 0 || raw.trim() === "") return void 0;
13272
+ const parsed = Number.parseInt(raw, 10);
13273
+ if (!Number.isFinite(parsed) || parsed <= 0) {
13274
+ throw new Error(`${flag} must be a positive integer.`);
12732
13275
  }
12733
- const reports = await countFiles(
12734
- resolveInsideWorkspace(location.workspaceDir, manifest.reportsDir)
12735
- );
12736
- const artifacts = await countFiles(
12737
- resolveInsideWorkspace(location.workspaceDir, manifest.artifactsDir)
12738
- );
12739
- const bundles = await countFiles(
12740
- resolveInsideWorkspace(location.workspaceDir, manifest.bundlesDir)
12741
- );
12742
- const notes = await countFiles(
12743
- resolveInsideWorkspace(location.workspaceDir, manifest.notesDir)
12744
- );
12745
- const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
12746
- return {
12747
- project: manifest.project,
12748
- traceFiles,
12749
- reports,
12750
- artifacts,
12751
- bundles,
12752
- notes,
12753
- index: {
12754
- enabled: manifest.index.enabled,
12755
- type: manifest.index.type,
12756
- exists: await pathExists(indexPath2)
13276
+ return parsed;
13277
+ }
13278
+ async function newestTraceMtimeMs2(traceDir) {
13279
+ let newest = 0;
13280
+ try {
13281
+ const files = await readdir(traceDir);
13282
+ for (const file of files) {
13283
+ if (!file.endsWith(".jsonl")) continue;
13284
+ try {
13285
+ const s = await stat(path13.join(traceDir, file));
13286
+ if (s.mtimeMs > newest) newest = s.mtimeMs;
13287
+ } catch {
13288
+ }
12757
13289
  }
12758
- };
13290
+ } catch {
13291
+ }
13292
+ return newest;
12759
13293
  }
12760
- async function doctorWorkspace(location) {
12761
- const checks2 = [];
12762
- const manifestResult = await readWorkspaceManifestFile(location);
12763
- if (!manifestResult.exists) {
12764
- checks2.push({
12765
- id: "manifest",
12766
- status: "fail",
12767
- message: "workspace.json not found (run `agent-inspect workspace init`)"
12768
- });
12769
- return { ok: false, checks: checks2 };
13294
+ async function indexSqliteBuildCommand(options = {}) {
13295
+ const mod = await loadIndexSqlite();
13296
+ if (!mod) return;
13297
+ const result = await mod.buildIndex({
13298
+ traceDir: options.dir,
13299
+ maxRuns: parsePositiveInt(options.maxRuns, "--max-runs")
13300
+ });
13301
+ if (options.json) {
13302
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
13303
+ return;
12770
13304
  }
12771
- if (!manifestResult.ok || !manifestResult.manifest) {
12772
- checks2.push({
12773
- id: "manifest",
12774
- status: "fail",
12775
- message: `workspace.json is invalid: ${manifestResult.errors.join("; ")}`
12776
- });
12777
- return { ok: false, checks: checks2 };
13305
+ console.log(`Built SQLite index: ${result.dbPath}`);
13306
+ console.log(`Runs: ${result.runs} Steps: ${result.steps} Errors: ${result.errors}`);
13307
+ for (const warning of result.warnings) console.log(`warning: ${warning}`);
13308
+ }
13309
+ async function indexSqliteStatusCommand(options = {}) {
13310
+ const mod = await loadIndexSqlite();
13311
+ if (!mod) return;
13312
+ const traceDir = resolveTraceDir({ dir: options.dir });
13313
+ const dbPath = mod.resolveIndexDbPath(traceDir);
13314
+ const status = mod.indexStatus(dbPath);
13315
+ const stale = mod.isIndexStale(dbPath, await newestTraceMtimeMs2(traceDir));
13316
+ if (options.json) {
13317
+ console.log(JSON.stringify({ ok: true, traceDir, stale, ...status }, null, 2));
13318
+ return;
12778
13319
  }
12779
- const manifest = manifestResult.manifest;
12780
- checks2.push({ id: "manifest", status: "pass", message: "workspace.json is valid" });
12781
- for (const warning of manifestResult.warnings) {
12782
- checks2.push({ id: "manifest-warning", status: "warn", message: warning });
13320
+ if (!status.exists) {
13321
+ console.log(`No SQLite index at ${dbPath}`);
13322
+ console.log("Run: agent-inspect index sqlite build");
13323
+ return;
12783
13324
  }
12784
- const dirFields = [
12785
- ...manifest.traceDirs.filter((d) => d !== ".").map((d, i) => [`traceDir[${i}]`, d]),
12786
- ["reportsDir", manifest.reportsDir],
12787
- ["artifactsDir", manifest.artifactsDir],
12788
- ["bundlesDir", manifest.bundlesDir],
12789
- ["notesDir", manifest.notesDir]
12790
- ];
12791
- for (const [id, rel] of dirFields) {
12792
- let abs;
12793
- try {
12794
- abs = resolveInsideWorkspace(location.workspaceDir, rel);
12795
- } catch (error) {
12796
- checks2.push({
12797
- id,
12798
- status: "fail",
12799
- message: error instanceof Error ? error.message : String(error)
12800
- });
12801
- continue;
12802
- }
12803
- if (!await pathExists(abs)) {
12804
- checks2.push({ id, status: "warn", message: `${rel}/ does not exist yet` });
12805
- } else if (!await isWritable(abs)) {
12806
- checks2.push({ id, status: "fail", message: `${rel}/ is not writable` });
13325
+ console.log(`Index: ${status.dbPath}`);
13326
+ console.log(`Healthy: ${status.healthy ? "yes" : "no"}`);
13327
+ console.log(`Built: ${status.builtAt ?? "unknown"}`);
13328
+ console.log(`Runs: ${status.runs} Steps: ${status.steps}`);
13329
+ console.log(`Stale: ${stale ? "yes" : "no"}`);
13330
+ }
13331
+ async function indexSqliteQueryCommand(options = {}) {
13332
+ const mod = await loadIndexSqlite();
13333
+ if (!mod) return;
13334
+ const traceDir = resolveTraceDir({ dir: options.dir });
13335
+ const dbPath = mod.resolveIndexDbPath(traceDir);
13336
+ const status = mod.indexStatus(dbPath);
13337
+ if (!status.exists || !status.healthy) {
13338
+ if (options.json) {
13339
+ console.log(JSON.stringify({ ok: false, reason: "index-missing", dbPath }, null, 2));
12807
13340
  } else {
12808
- checks2.push({ id, status: "pass", message: `${rel}/ is present and writable` });
13341
+ console.log("No usable SQLite index. Run: agent-inspect index sqlite build");
12809
13342
  }
13343
+ process.exitCode = 1;
13344
+ return;
12810
13345
  }
12811
- let newestTraceMtime = 0;
12812
- for (const rel of manifest.traceDirs) {
12813
- const abs = resolveInsideWorkspace(location.workspaceDir, rel);
12814
- for (const file of await listJsonl(abs)) {
12815
- try {
12816
- const s = await stat(path11.join(abs, file));
12817
- newestTraceMtime = Math.max(newestTraceMtime, s.mtimeMs);
12818
- } catch {
12819
- checks2.push({ id: "trace-readability", status: "warn", message: `cannot stat ${rel}/${file}` });
12820
- }
12821
- }
13346
+ const rows = mod.queryRuns(dbPath, {
13347
+ status: options.status,
13348
+ sessionId: options.session,
13349
+ name: options.name,
13350
+ kind: options.kind,
13351
+ tool: options.tool,
13352
+ limit: parsePositiveInt(options.limit, "--limit")
13353
+ });
13354
+ if (options.json) {
13355
+ console.log(JSON.stringify({ ok: true, count: rows.length, runs: rows }, null, 2));
13356
+ return;
12822
13357
  }
12823
- if (manifest.index.enabled) {
12824
- const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
12825
- if (!await pathExists(indexPath2)) {
12826
- checks2.push({ id: "index", status: "warn", message: "index enabled but not built" });
12827
- } else {
12828
- try {
12829
- const s = await stat(indexPath2);
12830
- if (newestTraceMtime > s.mtimeMs) {
12831
- checks2.push({ id: "index", status: "warn", message: "index is stale (traces are newer)" });
12832
- } else {
12833
- checks2.push({ id: "index", status: "pass", message: "index is present" });
12834
- }
12835
- } catch {
12836
- checks2.push({ id: "index", status: "warn", message: "cannot stat index" });
12837
- }
12838
- }
13358
+ if (rows.length === 0) {
13359
+ console.log("No matching runs.");
13360
+ return;
13361
+ }
13362
+ for (const run of rows) {
13363
+ const parts = [
13364
+ run.runId,
13365
+ run.status ?? "unknown",
13366
+ run.name ?? "",
13367
+ run.durationMs != null ? `${run.durationMs}ms` : ""
13368
+ ].filter((p) => p !== "");
13369
+ console.log(parts.join(" "));
12839
13370
  }
12840
- const ok = !checks2.some((c) => c.status === "fail");
12841
- return { ok, checks: checks2 };
12842
13371
  }
12843
- async function cleanWorkspace(location, manifest, options = {}) {
12844
- const dryRun = options.confirm !== true;
12845
- const targets = uniqueDirs([
12846
- manifest.reportsDir,
12847
- manifest.artifactsDir,
12848
- manifest.bundlesDir,
12849
- manifest.index.path ?? INDEX_DIR_NAME
12850
- ]);
12851
- const removed = [];
12852
- for (const rel of targets) {
12853
- const abs = resolveInsideWorkspace(location.workspaceDir, rel);
12854
- let entries;
12855
- try {
12856
- entries = await readdir(abs);
12857
- } catch {
12858
- continue;
12859
- }
12860
- for (const entry of entries) {
12861
- const relPath = `${rel}/${entry}`;
12862
- removed.push(relPath);
12863
- if (!dryRun) {
12864
- await rm(path11.join(abs, entry), { recursive: true, force: true });
12865
- }
12866
- }
13372
+ async function indexSqliteCleanCommand(options = {}) {
13373
+ const mod = await loadIndexSqlite();
13374
+ if (!mod) return;
13375
+ const traceDir = resolveTraceDir({ dir: options.dir });
13376
+ const dbPath = mod.resolveIndexDbPath(traceDir);
13377
+ await mod.cleanIndex(dbPath);
13378
+ if (options.json) {
13379
+ console.log(JSON.stringify({ ok: true, removed: dbPath }, null, 2));
13380
+ return;
12867
13381
  }
12868
- return { dryRun, removed };
13382
+ console.log(`Removed SQLite index: ${dbPath}`);
12869
13383
  }
12870
13384
 
12871
13385
  // packages/cli/src/workspace.ts
@@ -13143,7 +13657,10 @@ function createCliProgram() {
13143
13657
  ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").option(
13144
13658
  "--max-step-duration <duration>",
13145
13659
  "add run.maxStepDuration (e.g. 30s, 5m)"
13146
- ).option("--require-completed", "add run.requireCompleted").option("--detect-stalls", "add run.stall for running or incomplete events").option("--session <id>", "check all runs in a workflow session (requires --dir)").option("--group <id>", "check all runs sharing a groupId (requires --dir)").option(
13660
+ ).option("--require-completed", "add run.requireCompleted").option("--detect-stalls", "add run.stall for running or incomplete events").option(
13661
+ "--fail-on-observation <status>",
13662
+ "fail when observed outcomes match status (passed, failed, unknown, skipped; comma-separated)"
13663
+ ).option("--session <id>", "check all runs in a workflow session (requires --dir)").option("--group <id>", "check all runs sharing a groupId (requires --dir)").option(
13147
13664
  "--correlate-group",
13148
13665
  "when using --session, also match synthetic group: session keys"
13149
13666
  ).option(
@@ -13217,6 +13734,11 @@ function createCliProgram() {
13217
13734
  ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--baseline <trace-path-or-run-id>", "optional baseline trace for diff artifacts").option("--baseline-run <run-id>", "select a run from the baseline trace").option("--github-summary <path>", "append a safe summary to this file, e.g. GITHUB_STEP_SUMMARY").option("--json", "print deterministic JSON manifest").action((target, opts) => {
13218
13735
  runCommand(() => artifactsCommand(target, opts));
13219
13736
  });
13737
+ program.command("bundle").description("Create a share-safe offline trace bundle (local folder)").argument("[run-id]", "run id to bundle (optional with --session or --since)").option("--dir <path>", "trace directory for run/session lookup").option("--session <session-id>", "bundle all runs in a session").option("--since <duration>", "bundle runs with activity since a duration (e.g. 24h)").addOption(
13738
+ new Option("--profile <profile>", "redaction profile for exported copies").choices(["local", "share", "strict"]).default("share")
13739
+ ).option("--out <path>", "output directory (folder; .zip suffix is stripped)").option("--allow-unsafe", "write bundle even when verify-safe reports UNSAFE").option("--json", "print deterministic JSON manifest").action((runId, opts) => {
13740
+ runCommand(() => bundleCommand(runId, opts));
13741
+ });
13220
13742
  program.command("ci-summary").description("Summarize local reporter artifact manifests for CI").argument("<manifest...>", "reporter artifact manifest JSON files").option("-o, --output <path>", "write Markdown summary to a local file").option("--github-summary <path>", "append Markdown summary to this local file, e.g. GITHUB_STEP_SUMMARY").option("--json", "print deterministic JSON summary").action((manifest, opts) => {
13221
13743
  runCommand(() => ciSummaryCommand(manifest, opts));
13222
13744
  });
@@ -13259,6 +13781,9 @@ function createCliProgram() {
13259
13781
  "unknown"
13260
13782
  ])
13261
13783
  ).option("--kind <kind>", "filter by step kind/type (llm, tool, logic, \u2026)").option("--type <type>", "alias for --kind on manual trace step type").option("--name <query>", "substring match on run or step name").option("--tool <query>", "substring match on tool step name or metadata.toolName").option(
13784
+ "--observation <status>",
13785
+ "filter by observed outcome status (passed, failed, unknown, skipped)"
13786
+ ).option(
13262
13787
  "--duration <expr>",
13263
13788
  "duration filter on run or step (e.g. >5s, >=500ms)"
13264
13789
  ).option("--limit <number>", "max results (default 50)").option("--session <id>", "limit search to one workflow session").option(
@@ -13305,6 +13830,14 @@ function createCliProgram() {
13305
13830
  "html"
13306
13831
  ])
13307
13832
  ).option("-o, --output <path>", "write report to file (creates parent dirs)").option("--json", "emit JSON wrapper (includes content when writing to stdout)").option("--include-attributes", "include bounded step attributes in tree section").option("--no-errors", "omit error details from tree section").option("--no-correlation", "omit correlation metadata from what section").addOption(
13833
+ new Option("--section <section>", "report section (default: all)").choices([
13834
+ "all",
13835
+ "observations",
13836
+ "what",
13837
+ "timeline",
13838
+ "tree"
13839
+ ])
13840
+ ).addOption(
13308
13841
  new Option(
13309
13842
  "--redaction-profile <profile>",
13310
13843
  "redaction profile for entire report: local, share, strict (default: local)"
@@ -13421,9 +13954,9 @@ function isPrimaryModule() {
13421
13954
  if (!entry) return false;
13422
13955
  const selfPath = fileURLToPath(import.meta.url);
13423
13956
  try {
13424
- return realpathSync(path11.resolve(entry)) === realpathSync(path11.resolve(selfPath));
13957
+ return realpathSync(path13.resolve(entry)) === realpathSync(path13.resolve(selfPath));
13425
13958
  } catch {
13426
- return path11.resolve(entry) === path11.resolve(selfPath);
13959
+ return path13.resolve(entry) === path13.resolve(selfPath);
13427
13960
  }
13428
13961
  }
13429
13962
  if (isPrimaryModule()) {