agent-inspect 4.2.0 → 4.3.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.
@@ -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, nanoid, resolveRedactionProfile, applyProfileMetadataCaps, extractCorrelationMetadata, truncateStringForProfile, parseTraceJsonl } from './chunk-BS5LSKZ3.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.3.0";
15
15
 
16
16
  // packages/cli/src/trace-dir-scale.ts
17
17
  var TRACE_COUNT_WARN = 1e3;
@@ -895,7 +895,7 @@ function findReaderByFormat(format, readers) {
895
895
  }
896
896
  async function jsonlFilesInDirectory(dirPath) {
897
897
  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));
898
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path13.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
899
899
  }
900
900
  async function resolveInput(input3) {
901
901
  const cached = resolvedInputCache.get(input3);
@@ -4361,7 +4361,8 @@ function exportOpenInference(tree, options) {
4361
4361
  ];
4362
4362
  const traceId = hexFrom(`trace:${tree.runId}`, 16);
4363
4363
  const includeAttributes = options?.includeAttributes ?? false;
4364
- const maxLen = options?.maxAttributeLength;
4364
+ const maxLen = options?.maxAttributeLength ?? 500;
4365
+ const pretty = options?.pretty ?? true;
4365
4366
  const spans = [];
4366
4367
  for (const n of flattenTree(tree)) {
4367
4368
  const ev = n.event;
@@ -4435,7 +4436,7 @@ function exportOpenInference(tree, options) {
4435
4436
  };
4436
4437
  return {
4437
4438
  format: "openinference",
4438
- content: JSON.stringify(payload, null, 2 ),
4439
+ content: JSON.stringify(payload, null, pretty ? 2 : void 0),
4439
4440
  contentType: "application/json",
4440
4441
  fileExtension: ".openinference.json",
4441
4442
  warnings
@@ -4469,7 +4470,8 @@ function exportOtlpJson(tree, options) {
4469
4470
  ];
4470
4471
  const traceId = hexFrom2(`trace:${tree.runId}`, 16);
4471
4472
  const includeAttributes = options?.includeAttributes ?? false;
4472
- const maxLen = options?.maxAttributeLength;
4473
+ const maxLen = options?.maxAttributeLength ?? 500;
4474
+ const pretty = options?.pretty ?? true;
4473
4475
  const flat = flattenTree(tree);
4474
4476
  const spans = [];
4475
4477
  for (const n of flat) {
@@ -4565,7 +4567,7 @@ function exportOtlpJson(tree, options) {
4565
4567
  };
4566
4568
  return {
4567
4569
  format: "otlp-json",
4568
- content: JSON.stringify(payload, null, 2 ),
4570
+ content: JSON.stringify(payload, null, pretty ? 2 : void 0),
4569
4571
  contentType: "application/json",
4570
4572
  fileExtension: ".otlp.json",
4571
4573
  warnings
@@ -4788,9 +4790,9 @@ function mergeExportDefaults(options) {
4788
4790
  includeMetadata: options.includeMetadata ?? true,
4789
4791
  includeAttributes: options.includeAttributes ?? false,
4790
4792
  includeErrors: options.includeErrors ?? true,
4791
- pretty: options.pretty,
4792
- redacted: options.redacted,
4793
- maxAttributeLength: options.maxAttributeLength,
4793
+ pretty: options.pretty ?? true,
4794
+ redacted: options.redacted ?? true,
4795
+ maxAttributeLength: options.maxAttributeLength ?? 500,
4794
4796
  redactionProfile: options.redactionProfile ?? "local"
4795
4797
  };
4796
4798
  }
@@ -5024,9 +5026,9 @@ Trace directory: ${traceDir}`);
5024
5026
  if (validation !== void 0 && !validation.ok) {
5025
5027
  process.exitCode = 1;
5026
5028
  }
5027
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path11.resolve(options.output.trim()) : void 0;
5029
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
5028
5030
  if (outPath !== void 0) {
5029
- await mkdir(path11.dirname(outPath), { recursive: true });
5031
+ await mkdir(path13.dirname(outPath), { recursive: true });
5030
5032
  await writeFile(outPath, result.content, "utf-8");
5031
5033
  const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
5032
5034
  console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
@@ -5203,13 +5205,13 @@ function pairSteps(left, right) {
5203
5205
  return pairs;
5204
5206
  }
5205
5207
  function compareLeafSteps(L, R, segments, opts, out) {
5206
- const path18 = buildPath(segments);
5208
+ const path19 = buildPath(segments);
5207
5209
  if (L.name !== R.name) {
5208
5210
  out.push({
5209
5211
  kind: "structure",
5210
5212
  severity: "warning",
5211
5213
  message: "Step name differs",
5212
- path: path18,
5214
+ path: path19,
5213
5215
  left: L.name,
5214
5216
  right: R.name
5215
5217
  });
@@ -5219,7 +5221,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5219
5221
  kind: "step-type",
5220
5222
  severity: "warning",
5221
5223
  message: "Step type differs",
5222
- path: path18,
5224
+ path: path19,
5223
5225
  left: L.type,
5224
5226
  right: R.type
5225
5227
  });
@@ -5229,7 +5231,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5229
5231
  kind: "step-status",
5230
5232
  severity: "warning",
5231
5233
  message: "Step status differs",
5232
- path: path18,
5234
+ path: path19,
5233
5235
  left: L.status,
5234
5236
  right: R.status
5235
5237
  });
@@ -5241,7 +5243,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5241
5243
  kind: "error",
5242
5244
  severity: "error",
5243
5245
  message: "Step error message differs",
5244
- path: path18,
5246
+ path: path19,
5245
5247
  left: le || void 0,
5246
5248
  right: re || void 0
5247
5249
  });
@@ -5259,7 +5261,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5259
5261
  kind: "duration",
5260
5262
  severity: "info",
5261
5263
  message: "Step duration differs",
5262
- path: path18,
5264
+ path: path19,
5263
5265
  left: ld,
5264
5266
  right: rd
5265
5267
  });
@@ -5272,7 +5274,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5272
5274
  kind: "metadata",
5273
5275
  severity: "info",
5274
5276
  message: "Step metadata differs",
5275
- path: path18,
5277
+ path: path19,
5276
5278
  left: L.metadata,
5277
5279
  right: R.metadata
5278
5280
  });
@@ -5284,7 +5286,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
5284
5286
  kind: "output",
5285
5287
  severity: "info",
5286
5288
  message: "Output preview differs",
5287
- path: path18,
5289
+ path: path19,
5288
5290
  left: L.outputPreview,
5289
5291
  right: R.outputPreview
5290
5292
  });
@@ -5444,11 +5446,11 @@ function diffRuns(left, right, options) {
5444
5446
  }
5445
5447
 
5446
5448
  // packages/core/src/diff/renderer.ts
5447
- function formatPath(path18) {
5448
- if (path18 === void 0 || path18.path.length === 0) {
5449
+ function formatPath(path19) {
5450
+ if (path19 === void 0 || path19.path.length === 0) {
5449
5451
  return "(run)";
5450
5452
  }
5451
- return path18.path.map((s) => s.name).join(" > ");
5453
+ return path19.path.map((s) => s.name).join(" > ");
5452
5454
  }
5453
5455
  function formatValue(v, verbose) {
5454
5456
  if (v === void 0) return "(undefined)";
@@ -5826,7 +5828,7 @@ function indexedToMetadata(row, traceDir) {
5826
5828
  endedAt: row.endedAt ?? void 0,
5827
5829
  durationMs: row.durationMs ?? void 0,
5828
5830
  eventCount: 0,
5829
- filePath: path11.join(traceDir, row.file),
5831
+ filePath: path13.join(traceDir, row.file),
5830
5832
  fileSize: 0,
5831
5833
  createdAt: new Date(row.mtimeMs)
5832
5834
  };
@@ -5855,7 +5857,7 @@ async function newestTraceMtimeMs(traceDir) {
5855
5857
  }
5856
5858
  async function loadSessionRuns(traceDir) {
5857
5859
  try {
5858
- const mod = await import('./src-VQT7QLGV.mjs');
5860
+ const mod = await import('./src-YFMPWEIS.mjs');
5859
5861
  const dbPath = mod.resolveIndexDbPath(traceDir);
5860
5862
  const status = mod.indexStatus(dbPath);
5861
5863
  if (!status.healthy) {
@@ -6302,9 +6304,9 @@ async function reportCommand(runId, options = {}) {
6302
6304
  redactionProfile,
6303
6305
  correlation: !options.noCorrelation
6304
6306
  });
6305
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path11.resolve(options.output.trim()) : void 0;
6307
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
6306
6308
  if (outPath !== void 0) {
6307
- await mkdir(path11.dirname(outPath), { recursive: true });
6309
+ await mkdir(path13.dirname(outPath), { recursive: true });
6308
6310
  await writeFile(outPath, result.content, "utf-8");
6309
6311
  console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
6310
6312
  }
@@ -6551,17 +6553,17 @@ function applyRule(rule, value, replacement) {
6551
6553
  }
6552
6554
  return value;
6553
6555
  }
6554
- function childPath(path18, key) {
6556
+ function childPath(path19, key) {
6555
6557
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
6556
- return path18 ? `${path18}.${key}` : key;
6558
+ return path19 ? `${path19}.${key}` : key;
6557
6559
  }
6558
- return `${path18 || "$"}[${JSON.stringify(key)}]`;
6560
+ return `${path19 || "$"}[${JSON.stringify(key)}]`;
6559
6561
  }
6560
- function indexPath(path18, index) {
6561
- return `${path18 || "$"}[${index}]`;
6562
+ function indexPath(path19, index) {
6563
+ return `${path19 || "$"}[${index}]`;
6562
6564
  }
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 };
6565
+ function makeFinding(path19, detector, action, matchKind, severity = "warning", preview) {
6566
+ return preview === void 0 ? { path: path19, detector, action, severity, matchKind } : { path: path19, detector, action, severity, matchKind, preview };
6565
6567
  }
6566
6568
  function createRedactionProfile(profile = "local") {
6567
6569
  switch (profile) {
@@ -6630,11 +6632,11 @@ var Redactor2 = class {
6630
6632
  #recordFinding(state, finding) {
6631
6633
  if (this.#collectFindings) state.findings.push(finding);
6632
6634
  }
6633
- #redactValue(value, key, path18, depth, state) {
6635
+ #redactValue(value, key, path19, depth, state) {
6634
6636
  if (depth > this.#maxDepth) {
6635
6637
  this.#recordFinding(
6636
6638
  state,
6637
- makeFinding(path18, "structure.maxDepth", "truncate", "value", "warning")
6639
+ makeFinding(path19, "structure.maxDepth", "truncate", "value", "warning")
6638
6640
  );
6639
6641
  return "[Truncated]";
6640
6642
  }
@@ -6643,19 +6645,19 @@ var Redactor2 = class {
6643
6645
  if (rule) {
6644
6646
  this.#recordFinding(
6645
6647
  state,
6646
- makeFinding(path18, `key.${rule.key}`, actionForRule(rule), "key", "warning")
6648
+ makeFinding(path19, `key.${rule.key}`, actionForRule(rule), "key", "warning")
6647
6649
  );
6648
6650
  return applyRule(rule, value, this.#replacement);
6649
6651
  }
6650
6652
  }
6651
6653
  for (const detector of this.#detectors) {
6652
- const detections = detector.detect({ path: path18, key, value });
6654
+ const detections = detector.detect({ path: path19, key, value });
6653
6655
  for (const detection of detections) {
6654
6656
  const action = detection.action ?? "replace";
6655
6657
  this.#recordFinding(
6656
6658
  state,
6657
6659
  makeFinding(
6658
- path18,
6660
+ path19,
6659
6661
  detector.id,
6660
6662
  action,
6661
6663
  detection.matchKind ?? detector.matchKind ?? "custom",
@@ -6673,7 +6675,7 @@ var Redactor2 = class {
6673
6675
  const out = [];
6674
6676
  state.seen.set(value, out);
6675
6677
  value.forEach((item, index) => {
6676
- out[index] = this.#redactValue(item, void 0, indexPath(path18, index), depth + 1, state);
6678
+ out[index] = this.#redactValue(item, void 0, indexPath(path19, index), depth + 1, state);
6677
6679
  });
6678
6680
  return out;
6679
6681
  }
@@ -6685,7 +6687,7 @@ var Redactor2 = class {
6685
6687
  out[entryKey] = this.#redactValue(
6686
6688
  entryValue,
6687
6689
  entryKey,
6688
- childPath(path18 === "$" ? "" : path18, entryKey),
6690
+ childPath(path19 === "$" ? "" : path19, entryKey),
6689
6691
  depth + 1,
6690
6692
  state
6691
6693
  );
@@ -6811,6 +6813,9 @@ function redactDocument(content, profile) {
6811
6813
  return redactJsonlText(content, profile);
6812
6814
  }
6813
6815
  }
6816
+ function redactTraceContent(content, profile) {
6817
+ return redactDocument(content, profile);
6818
+ }
6814
6819
  async function redactCommand(target, options = {}, stdin = process.stdin) {
6815
6820
  const profile = parseRedactionProfile3(options.profile);
6816
6821
  const source = await contentFromTarget(target, options, stdin);
@@ -7132,14 +7137,14 @@ function uniqueSorted(values) {
7132
7137
  return [...new Set(values)].sort();
7133
7138
  }
7134
7139
  function isWithinDirectory(child, parent) {
7135
- const relative = path11.relative(parent, child);
7136
- return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
7140
+ const relative = path13.relative(parent, child);
7141
+ return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
7137
7142
  }
7138
7143
  async function resolveOutputPath(inputPath, output2, force) {
7139
7144
  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);
7145
+ const inputAbs = path13.resolve(inputPath);
7146
+ const outputAbs = path13.resolve(output2.trim());
7147
+ const inputDir = path13.dirname(inputAbs);
7143
7148
  if (!isWithinDirectory(outputAbs, inputDir)) {
7144
7149
  throw new Error("Refusing to write migrated output outside the input directory.");
7145
7150
  }
@@ -7260,7 +7265,7 @@ async function migrateCommand(input3, options = {}) {
7260
7265
  process.exitCode = 1;
7261
7266
  return;
7262
7267
  }
7263
- const inputPath = path11.resolve(input3.trim());
7268
+ const inputPath = path13.resolve(input3.trim());
7264
7269
  const dryRun = options.dryRun === true;
7265
7270
  if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
7266
7271
  console.error("migrate requires --dry-run or --output <path>.");
@@ -7279,7 +7284,7 @@ async function migrateCommand(input3, options = {}) {
7279
7284
  );
7280
7285
  const result = await buildMigration(inputPath, outputPath);
7281
7286
  if (!dryRun && outputPath !== void 0) {
7282
- await mkdir(path11.dirname(outputPath), { recursive: true });
7287
+ await mkdir(path13.dirname(outputPath), { recursive: true });
7283
7288
  await writeFile(outputPath, result.content, "utf-8");
7284
7289
  }
7285
7290
  printSummary2(result, dryRun);
@@ -7557,7 +7562,7 @@ function stripPrefix(name, prefixes) {
7557
7562
  }
7558
7563
  return name;
7559
7564
  }
7560
- function eventEvidence(event, path18) {
7565
+ function eventEvidence(event, path19) {
7561
7566
  return {
7562
7567
  runId: event.runId,
7563
7568
  eventId: event.eventId,
@@ -7567,7 +7572,7 @@ function eventEvidence(event, path18) {
7567
7572
  kind: event.kind,
7568
7573
  name: event.name,
7569
7574
  status: event.status,
7570
- ...path18 ? { path: path18 } : {}
7575
+ ...path19 ? { path: path19 } : {}
7571
7576
  };
7572
7577
  }
7573
7578
  function runEvidence(run) {
@@ -7630,9 +7635,9 @@ function eventEndMs(event) {
7630
7635
  function normalizedKey(value) {
7631
7636
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
7632
7637
  }
7633
- function lastPathSegment(path18) {
7634
- const parts = path18.split(".");
7635
- return parts[parts.length - 1] ?? path18;
7638
+ function lastPathSegment(path19) {
7639
+ const parts = path19.split(".");
7640
+ return parts[parts.length - 1] ?? path19;
7636
7641
  }
7637
7642
  function valueType(value) {
7638
7643
  if (Array.isArray(value)) return "array";
@@ -7646,12 +7651,12 @@ function serializedByteLength(value) {
7646
7651
  return void 0;
7647
7652
  }
7648
7653
  }
7649
- function pushValueEntries(entries, event, value, path18, key, depth = 0) {
7650
- entries.push({ event, path: path18, key, value });
7654
+ function pushValueEntries(entries, event, value, path19, key, depth = 0) {
7655
+ entries.push({ event, path: path19, key, value });
7651
7656
  if (depth >= 8) return;
7652
7657
  if (Array.isArray(value)) {
7653
7658
  for (const [index, item] of value.entries()) {
7654
- pushValueEntries(entries, event, item, `${path18}.${index}`, String(index), depth + 1);
7659
+ pushValueEntries(entries, event, item, `${path19}.${index}`, String(index), depth + 1);
7655
7660
  }
7656
7661
  return;
7657
7662
  }
@@ -7661,7 +7666,7 @@ function pushValueEntries(entries, event, value, path18, key, depth = 0) {
7661
7666
  entries,
7662
7667
  event,
7663
7668
  value[nestedKey],
7664
- `${path18}.${nestedKey}`,
7669
+ `${path19}.${nestedKey}`,
7665
7670
  nestedKey,
7666
7671
  depth + 1
7667
7672
  );
@@ -7742,9 +7747,9 @@ function eventDurationMs(event) {
7742
7747
  }
7743
7748
  function treeShape(nodes) {
7744
7749
  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}`));
7750
+ const visit = (node, path19) => {
7751
+ lines.push(`${path19}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
7752
+ node.children.forEach((child, index) => visit(child, `${path19}.${index}`));
7748
7753
  };
7749
7754
  nodes.forEach((node, index) => visit(node, String(index)));
7750
7755
  return lines;
@@ -7793,9 +7798,9 @@ function retrievalShape(context) {
7793
7798
  function guardrailShape(context) {
7794
7799
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
7795
7800
  }
7796
- function firstEvidenceForKind(context, kind, path18) {
7801
+ function firstEvidenceForKind(context, kind, path19) {
7797
7802
  const event = context.events.find((candidate) => candidate.kind === kind);
7798
- return event ? [eventEvidence(event, path18)] : runEvidence(context.selectedRun);
7803
+ return event ? [eventEvidence(event, path19)] : runEvidence(context.selectedRun);
7799
7804
  }
7800
7805
  function baselineDiffFinding(message, evidence, expected, actual) {
7801
7806
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -8145,13 +8150,13 @@ function createStructureCycleRule() {
8145
8150
  const seenCycles = /* @__PURE__ */ new Set();
8146
8151
  const findings = [];
8147
8152
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
8148
- const path18 = [];
8153
+ const path19 = [];
8149
8154
  const seenAt = /* @__PURE__ */ new Map();
8150
8155
  let current = event;
8151
8156
  while (current) {
8152
8157
  const existing = seenAt.get(current.eventId);
8153
8158
  if (existing !== void 0) {
8154
- const cycle = path18.slice(existing);
8159
+ const cycle = path19.slice(existing);
8155
8160
  const key = cycle.map((item) => item.eventId).sort().join("\0");
8156
8161
  if (!seenCycles.has(key)) {
8157
8162
  seenCycles.add(key);
@@ -8167,8 +8172,8 @@ function createStructureCycleRule() {
8167
8172
  }
8168
8173
  break;
8169
8174
  }
8170
- seenAt.set(current.eventId, path18.length);
8171
- path18.push(current);
8175
+ seenAt.set(current.eventId, path19.length);
8176
+ path19.push(current);
8172
8177
  current = current.parentId ? byId.get(current.parentId) : void 0;
8173
8178
  }
8174
8179
  }
@@ -8965,23 +8970,23 @@ function evaluatePromptInjection(text, options = {}) {
8965
8970
  }
8966
8971
  return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
8967
8972
  }
8968
- function validateSchemaField(value, field, path18, evidence) {
8973
+ function validateSchemaField(value, field, path19, evidence) {
8969
8974
  const ruleId = "guardrail.structured-output";
8970
8975
  if (field.type) {
8971
8976
  const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
8972
8977
  if (actual !== field.type) {
8973
- evidence.push({ ruleId, path: path18, preview: `expected ${field.type}, got ${actual}` });
8978
+ evidence.push({ ruleId, path: path19, preview: `expected ${field.type}, got ${actual}` });
8974
8979
  return;
8975
8980
  }
8976
8981
  }
8977
8982
  if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
8978
- evidence.push({ ruleId, path: path18, preview: "value not in enum" });
8983
+ evidence.push({ ruleId, path: path19, preview: "value not in enum" });
8979
8984
  }
8980
8985
  if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
8981
8986
  const record = value;
8982
8987
  for (const key of field.required) {
8983
8988
  if (!(key in record)) {
8984
- evidence.push({ ruleId, path: `${path18}.${key}`, preview: "missing required key" });
8989
+ evidence.push({ ruleId, path: `${path19}.${key}`, preview: "missing required key" });
8985
8990
  }
8986
8991
  }
8987
8992
  }
@@ -9308,7 +9313,7 @@ function asConfig(value) {
9308
9313
  }
9309
9314
  async function loadConfig(configPath) {
9310
9315
  if (configPath === void 0) return {};
9311
- const extension = path11.extname(configPath);
9316
+ const extension = path13.extname(configPath);
9312
9317
  if (TS_CONFIG_EXTENSIONS.has(extension)) {
9313
9318
  throw new Error(
9314
9319
  "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -9317,7 +9322,7 @@ async function loadConfig(configPath) {
9317
9322
  if (!CONFIG_EXTENSIONS.has(extension)) {
9318
9323
  throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
9319
9324
  }
9320
- const absolute = path11.resolve(configPath);
9325
+ const absolute = path13.resolve(configPath);
9321
9326
  if (extension === ".json") {
9322
9327
  const raw = await readFile(absolute, "utf-8");
9323
9328
  return asConfig(JSON.parse(raw));
@@ -9466,10 +9471,10 @@ function printHuman(result) {
9466
9471
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
9467
9472
  }
9468
9473
  for (const finding of result.findings) {
9469
- const path18 = finding.evidence[0]?.path;
9474
+ const path19 = finding.evidence[0]?.path;
9470
9475
  const run = finding.evidence[0]?.runId;
9471
9476
  const runPrefix = run ? `[${run}] ` : "";
9472
- console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
9477
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
9473
9478
  }
9474
9479
  }
9475
9480
  function readErrorResult(error) {
@@ -9669,7 +9674,7 @@ function createViewerServer(options = {}) {
9669
9674
  return sendJson(res, 200, {
9670
9675
  ok: true,
9671
9676
  readOnly: true,
9672
- traceDir: path11.resolve(traceDir)
9677
+ traceDir: path13.resolve(traceDir)
9673
9678
  });
9674
9679
  }
9675
9680
  const td = new TraceDirectory({ dir: traceDir });
@@ -9687,7 +9692,7 @@ function createViewerServer(options = {}) {
9687
9692
  runId: meta.runId,
9688
9693
  name: meta.name,
9689
9694
  status: meta.status,
9690
- file: path11.basename(meta.filePath),
9695
+ file: path13.basename(meta.filePath),
9691
9696
  startedAt: meta.startedAt,
9692
9697
  durationMs: meta.durationMs
9693
9698
  }))
@@ -9802,7 +9807,7 @@ function startViewerServer(options = {}) {
9802
9807
  resolve({
9803
9808
  host,
9804
9809
  port: resolvedPort,
9805
- traceDir: path11.resolve(traceDir),
9810
+ traceDir: path13.resolve(traceDir),
9806
9811
  url: `http://${host}:${resolvedPort}`
9807
9812
  });
9808
9813
  });
@@ -10007,10 +10012,10 @@ async function evalRun(input3, options = {}) {
10007
10012
  diagnostics: []
10008
10013
  };
10009
10014
  }
10010
- function evidenceForRun(run, path18) {
10011
- return [{ runId: run.runId, ...path18 !== void 0 ? { path: path18 } : {} }];
10015
+ function evidenceForRun(run, path19) {
10016
+ return [{ runId: run.runId, ...path19 !== void 0 ? { path: path19 } : {} }];
10012
10017
  }
10013
- function evidenceForEvent(event, path18) {
10018
+ function evidenceForEvent(event, path19) {
10014
10019
  return [
10015
10020
  {
10016
10021
  runId: event.runId,
@@ -10018,7 +10023,7 @@ function evidenceForEvent(event, path18) {
10018
10023
  ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
10019
10024
  kind: event.kind,
10020
10025
  name: event.name,
10021
- ...path18 !== void 0 ? { path: path18 } : {}
10026
+ ...path19 !== void 0 ? { path: path19 } : {}
10022
10027
  }
10023
10028
  ];
10024
10029
  }
@@ -10176,9 +10181,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
10176
10181
  function tokenize(text) {
10177
10182
  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
10183
  }
10179
- function firstEvidence(fields, run, path18) {
10184
+ function firstEvidence(fields, run, path19) {
10180
10185
  const first = fields[0];
10181
- return first === void 0 ? evidenceForRun(run, path18) : evidenceForEvent(first.node.event, first.path);
10186
+ return first === void 0 ? evidenceForRun(run, path19) : evidenceForEvent(first.node.event, first.path);
10182
10187
  }
10183
10188
  function collectSourceIds(nodes, keys) {
10184
10189
  const wanted = keySet(keys);
@@ -10555,8 +10560,8 @@ function renderEvalMarkdown(result) {
10555
10560
  if (result.findings.length > 0) {
10556
10561
  lines.push("", "## Findings");
10557
10562
  for (const finding of result.findings) {
10558
- const path18 = finding.evidence[0]?.path;
10559
- lines.push(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
10563
+ const path19 = finding.evidence[0]?.path;
10564
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10560
10565
  }
10561
10566
  }
10562
10567
  return `${lines.join("\n")}
@@ -10603,7 +10608,7 @@ function asConfig2(value) {
10603
10608
  }
10604
10609
  async function loadConfig2(configPath) {
10605
10610
  if (configPath === void 0) return {};
10606
- const extension = path11.extname(configPath);
10611
+ const extension = path13.extname(configPath);
10607
10612
  if (TS_CONFIG_EXTENSIONS2.has(extension)) {
10608
10613
  throw new Error(
10609
10614
  "TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -10612,7 +10617,7 @@ async function loadConfig2(configPath) {
10612
10617
  if (!CONFIG_EXTENSIONS2.has(extension)) {
10613
10618
  throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
10614
10619
  }
10615
- const absolute = path11.resolve(configPath);
10620
+ const absolute = path13.resolve(configPath);
10616
10621
  if (extension === ".json") {
10617
10622
  const raw = await readFile(absolute, "utf-8");
10618
10623
  return asConfig2(JSON.parse(raw));
@@ -10749,8 +10754,8 @@ function printHuman2(result) {
10749
10754
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10750
10755
  }
10751
10756
  for (const finding of result.findings) {
10752
- const path18 = finding.evidence[0]?.path;
10753
- console.log(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
10757
+ const path19 = finding.evidence[0]?.path;
10758
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10754
10759
  }
10755
10760
  }
10756
10761
  function readErrorResult2(error) {
@@ -10988,8 +10993,8 @@ function printHuman3(result) {
10988
10993
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10989
10994
  }
10990
10995
  for (const finding of result.findings) {
10991
- const path18 = finding.evidence[0]?.path;
10992
- console.log(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
10996
+ const path19 = finding.evidence[0]?.path;
10997
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10993
10998
  }
10994
10999
  console.log(`Note: ${result.note}`);
10995
11000
  }
@@ -11035,6 +11040,38 @@ function scanCommand(target, options = {}, stdin = process.stdin) {
11035
11040
  function verifySafeCommand(target, options = {}, stdin = process.stdin) {
11036
11041
  return safetyCommand("verify-safe", target, options, stdin);
11037
11042
  }
11043
+ function assessOpenedTrace(read, options = {}) {
11044
+ try {
11045
+ const rules = buildSafetyRules(options);
11046
+ const checkResult = runTraceChecks(
11047
+ { read },
11048
+ {
11049
+ rules,
11050
+ ...options.runId !== void 0 ? { runId: options.runId } : {},
11051
+ ...options.run !== void 0 ? { runId: options.run } : {}
11052
+ }
11053
+ );
11054
+ const detectorFindings = checkResult.diagnostics.length === 0 ? redactionDetectorFindings(read, checkResult.runId) : [];
11055
+ return resultFromParts({
11056
+ command: "verify-safe",
11057
+ format: checkResult.format,
11058
+ runId: checkResult.runId,
11059
+ findings: [...checkResult.findings, ...detectorFindings],
11060
+ diagnostics: [
11061
+ ...checkResult.diagnostics.map(diagnosticFromCheck),
11062
+ ...warningDiagnostics(read.warnings, read.unsupportedFields)
11063
+ ],
11064
+ warnings: read.warnings,
11065
+ unsupportedFields: read.unsupportedFields
11066
+ });
11067
+ } catch (error) {
11068
+ return messageStartsWithDash(error) ? invalidArgumentResult("verify-safe", error) : readErrorResult3("verify-safe", error);
11069
+ }
11070
+ }
11071
+ function messageStartsWithDash(error) {
11072
+ const message = error instanceof Error ? error.message : String(error);
11073
+ return message.startsWith("--");
11074
+ }
11038
11075
  var NOTE = "Generated locally by AgentInspect. Artifacts are best-effort summaries, not compliance or security certification.";
11039
11076
  var SAFETY_RULES = [
11040
11077
  createSafetyRawContentRule(),
@@ -11108,8 +11145,8 @@ function renderCheckSection(result) {
11108
11145
  `Diagnostics: ${result.diagnostics.length}`
11109
11146
  ];
11110
11147
  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})`);
11148
+ const path19 = finding.evidence[0]?.path ?? "(run)";
11149
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path19})`);
11113
11150
  }
11114
11151
  for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
11115
11152
  lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
@@ -11187,8 +11224,8 @@ function renderHtml(trace, check, diff) {
11187
11224
  `;
11188
11225
  }
11189
11226
  async function writeArtifact(outputDir, relativePath, content, files) {
11190
- const outPath = path11.join(outputDir, relativePath);
11191
- await mkdir(path11.dirname(outPath), { recursive: true });
11227
+ const outPath = path13.join(outputDir, relativePath);
11228
+ await mkdir(path13.dirname(outPath), { recursive: true });
11192
11229
  await writeFile(outPath, content, "utf-8");
11193
11230
  files.push(relativePath);
11194
11231
  }
@@ -11204,7 +11241,7 @@ function manifestStatus(check, diff) {
11204
11241
  return "ok";
11205
11242
  }
11206
11243
  async function artifactsCommand(target, options = {}, stdin = process.stdin) {
11207
- const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path11.resolve(options.outputDir.trim()) : "";
11244
+ const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path13.resolve(options.outputDir.trim()) : "";
11208
11245
  if (outputDir === "") {
11209
11246
  console.error("--output-dir is required.");
11210
11247
  process.exitCode = 1;
@@ -11270,8 +11307,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
11270
11307
  await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
11271
11308
  const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
11272
11309
  if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
11273
- await mkdir(path11.dirname(path11.resolve(summaryTarget)), { recursive: true });
11274
- await appendFile(path11.resolve(summaryTarget), `
11310
+ await mkdir(path13.dirname(path13.resolve(summaryTarget)), { recursive: true });
11311
+ await appendFile(path13.resolve(summaryTarget), `
11275
11312
  ${renderMarkdown(trace, check, diff)}`, "utf-8");
11276
11313
  }
11277
11314
  const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
@@ -11290,10 +11327,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
11290
11327
  findings: diff?.findings.length ?? 0,
11291
11328
  diagnostics: diff?.diagnostics.length ?? 0
11292
11329
  },
11293
- ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path11.resolve(summaryTarget) } : {},
11330
+ ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path13.resolve(summaryTarget) } : {},
11294
11331
  note: NOTE
11295
11332
  };
11296
- await writeFile(path11.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
11333
+ await writeFile(path13.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
11297
11334
  if (options.json === true) {
11298
11335
  console.log(writeJson3(manifest).trimEnd());
11299
11336
  } else {
@@ -11304,1568 +11341,1893 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
11304
11341
  }
11305
11342
  }
11306
11343
  }
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
- }
11344
+
11345
+ // packages/core/src/workspace/types.ts
11346
+ var WORKSPACE_SCHEMA_VERSION = "1.0";
11347
+ var WORKSPACE_DIR_NAME = ".agent-inspect";
11348
+ var WORKSPACE_MANIFEST_FILENAME = "workspace.json";
11349
+
11350
+ // packages/core/src/workspace/manifest.ts
11351
+ var DEFAULT_WORKSPACE_LAYOUT = {
11352
+ traceDirs: ["runs"],
11353
+ reportsDir: "reports",
11354
+ artifactsDir: "artifacts",
11355
+ bundlesDir: "bundles",
11356
+ notesDir: "notes"
11357
+ };
11358
+ var DEFAULT_REDACTION_PROFILE = "share";
11359
+ var REDACTION_PROFILES = [
11360
+ "local",
11361
+ "share",
11362
+ "strict"
11363
+ ];
11364
+ var INDEX_TYPES = ["none", "sqlite", "custom"];
11365
+ var MAX_WORKSPACE_MANIFEST_BYTES = 64 * 1024;
11366
+ function createDefaultWorkspaceManifest(options) {
11367
+ const project = typeof options.project === "string" ? options.project.trim() : "";
11368
+ const index = {
11369
+ enabled: options.index?.enabled ?? false,
11370
+ type: options.index?.type ?? "none",
11371
+ ...options.index?.path !== void 0 ? { path: options.index.path } : {}
11372
+ };
11359
11373
  return {
11360
- ok: true,
11361
- outputDir,
11362
- relativePath: normalized,
11363
- absolutePath,
11364
- diagnostics
11374
+ schemaVersion: WORKSPACE_SCHEMA_VERSION,
11375
+ project,
11376
+ createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
11377
+ traceDirs: options.traceDirs ? [...options.traceDirs] : [...DEFAULT_WORKSPACE_LAYOUT.traceDirs],
11378
+ reportsDir: options.reportsDir ?? DEFAULT_WORKSPACE_LAYOUT.reportsDir,
11379
+ artifactsDir: options.artifactsDir ?? DEFAULT_WORKSPACE_LAYOUT.artifactsDir,
11380
+ bundlesDir: options.bundlesDir ?? DEFAULT_WORKSPACE_LAYOUT.bundlesDir,
11381
+ notesDir: options.notesDir ?? DEFAULT_WORKSPACE_LAYOUT.notesDir,
11382
+ redactionProfile: options.redactionProfile ?? DEFAULT_REDACTION_PROFILE,
11383
+ index
11365
11384
  };
11366
11385
  }
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) {
11386
+ function isPlainObject(value) {
11389
11387
  return typeof value === "object" && value !== null && !Array.isArray(value);
11390
11388
  }
11391
- function readString(value, label) {
11389
+ function isSafeRelativeWorkspacePath(p) {
11390
+ if (typeof p !== "string") return false;
11391
+ const trimmed = p.trim();
11392
+ if (trimmed === "") return false;
11393
+ if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return false;
11394
+ if (/^[a-zA-Z]:/.test(trimmed)) return false;
11395
+ const segments = trimmed.split(/[/\\]+/);
11396
+ return !segments.some((seg) => seg === "..");
11397
+ }
11398
+ function validateDirField(value, field, errors) {
11392
11399
  if (typeof value !== "string" || value.trim() === "") {
11393
- throw new Error(`${label} must be a non-empty string.`);
11400
+ errors.push(`${field} must be a non-empty string`);
11401
+ return;
11394
11402
  }
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}.`);
11403
+ if (!isSafeRelativeWorkspacePath(value)) {
11404
+ errors.push(
11405
+ `${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
11406
+ );
11404
11407
  }
11405
- return framework;
11406
11408
  }
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}.`);
11409
+ function validateIndex(value, errors) {
11410
+ if (!isPlainObject(value)) {
11411
+ errors.push("index must be an object");
11412
+ return void 0;
11411
11413
  }
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}.`);
11414
+ if (typeof value.enabled !== "boolean") {
11415
+ errors.push("index.enabled must be a boolean");
11426
11416
  }
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}.`);
11417
+ if (!INDEX_TYPES.includes(value.type)) {
11418
+ errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
11434
11419
  }
11420
+ if (value.path !== void 0 && !isSafeRelativeWorkspacePath(value.path)) {
11421
+ errors.push(
11422
+ 'index.path must be a relative path inside the workspace (no absolute paths or ".." traversal)'
11423
+ );
11424
+ }
11425
+ if (errors.length > 0) return void 0;
11435
11426
  return {
11436
- kind,
11437
- path: pathCheck.relativePath,
11438
- format,
11439
- redactionProfile
11440
- };
11441
- }
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));
11446
- }
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;
11451
- }
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);
11456
- 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
- )
11427
+ enabled: value.enabled,
11428
+ type: value.type,
11429
+ ...value.path !== void 0 ? { path: value.path } : {}
11467
11430
  };
11468
11431
  }
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}.`);
11476
- }
11477
- if (!Array.isArray(candidate.results)) {
11478
- throw new Error("manifest.results must be an array.");
11432
+ function validateWorkspaceManifest(input3) {
11433
+ const errors = [];
11434
+ const warnings = [];
11435
+ if (!isPlainObject(input3)) {
11436
+ return { ok: false, errors: ["manifest must be an object"], warnings };
11437
+ }
11438
+ if (input3.schemaVersion !== WORKSPACE_SCHEMA_VERSION) {
11439
+ errors.push(
11440
+ `schemaVersion must be "${WORKSPACE_SCHEMA_VERSION}" (received ${JSON.stringify(
11441
+ input3.schemaVersion
11442
+ )})`
11443
+ );
11444
+ }
11445
+ if (typeof input3.project !== "string" || input3.project.trim() === "") {
11446
+ errors.push("project must be a non-empty string");
11447
+ }
11448
+ if (typeof input3.createdAt !== "string" || input3.createdAt.trim() === "") {
11449
+ errors.push("createdAt must be a non-empty ISO-8601 string");
11450
+ } else if (Number.isNaN(Date.parse(input3.createdAt))) {
11451
+ errors.push("createdAt must be a valid ISO-8601 date string");
11452
+ }
11453
+ if (!Array.isArray(input3.traceDirs) || input3.traceDirs.length === 0) {
11454
+ errors.push("traceDirs must be a non-empty array");
11455
+ } else {
11456
+ input3.traceDirs.forEach((dir, i) => {
11457
+ if (typeof dir !== "string" || dir.trim() === "") {
11458
+ errors.push(`traceDirs[${i}] must be a non-empty string`);
11459
+ } else if (!isSafeRelativeWorkspacePath(dir)) {
11460
+ errors.push(
11461
+ `traceDirs[${i}] must be a relative path inside the workspace (no absolute paths or ".." traversal)`
11462
+ );
11463
+ }
11464
+ });
11465
+ }
11466
+ validateDirField(input3.reportsDir, "reportsDir", errors);
11467
+ validateDirField(input3.artifactsDir, "artifactsDir", errors);
11468
+ validateDirField(input3.bundlesDir, "bundlesDir", errors);
11469
+ validateDirField(input3.notesDir, "notesDir", errors);
11470
+ if (!REDACTION_PROFILES.includes(input3.redactionProfile)) {
11471
+ errors.push(`redactionProfile must be one of: ${REDACTION_PROFILES.join(", ")}`);
11472
+ }
11473
+ const indexErrors = [];
11474
+ const index = validateIndex(input3.index, indexErrors);
11475
+ errors.push(...indexErrors);
11476
+ if (index && index.type !== "none" && !index.enabled) {
11477
+ warnings.push(`index.type is "${index.type}" but index.enabled is false`);
11478
+ }
11479
+ if (errors.length > 0 || index === void 0) {
11480
+ return { ok: false, errors, warnings };
11479
11481
  }
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
11482
  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
11483
+ schemaVersion: WORKSPACE_SCHEMA_VERSION,
11484
+ project: input3.project.trim(),
11485
+ createdAt: input3.createdAt,
11486
+ traceDirs: input3.traceDirs.map((d) => d.trim()),
11487
+ reportsDir: input3.reportsDir.trim(),
11488
+ artifactsDir: input3.artifactsDir.trim(),
11489
+ bundlesDir: input3.bundlesDir.trim(),
11490
+ notesDir: input3.notesDir.trim(),
11491
+ redactionProfile: input3.redactionProfile,
11492
+ index
11493
11493
  };
11494
+ return { ok: true, manifest, errors, warnings };
11494
11495
  }
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);
11496
+ function parseWorkspaceManifest(json) {
11497
+ const warnings = [];
11498
+ if (typeof json !== "string") {
11499
+ return { ok: false, errors: ["manifest input must be a string"], warnings };
11499
11500
  }
11500
- return relative;
11501
+ if (json.length > MAX_WORKSPACE_MANIFEST_BYTES) {
11502
+ return {
11503
+ ok: false,
11504
+ errors: [
11505
+ `manifest exceeds maximum size of ${MAX_WORKSPACE_MANIFEST_BYTES} bytes`
11506
+ ],
11507
+ warnings
11508
+ };
11509
+ }
11510
+ let parsed;
11511
+ try {
11512
+ parsed = JSON.parse(json);
11513
+ } catch {
11514
+ return { ok: false, errors: ["manifest is not valid JSON"], warnings };
11515
+ }
11516
+ return validateWorkspaceManifest(parsed);
11501
11517
  }
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
- };
11518
+ function serializeWorkspaceManifest(manifest) {
11519
+ return `${JSON.stringify(manifest, null, 2)}
11520
+ `;
11525
11521
  }
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
- }
11549
- }
11522
+ var INDEX_DIR_NAME = "index";
11523
+ function resolveWorkspaceLocation(cwd = process.cwd()) {
11524
+ const projectRoot = path13.resolve(cwd);
11525
+ const workspaceDir = path13.join(projectRoot, WORKSPACE_DIR_NAME);
11550
11526
  return {
11551
- status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
11552
- manifests,
11553
- summary,
11554
- note: NOTE2
11527
+ projectRoot,
11528
+ workspaceDir,
11529
+ manifestPath: path13.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
11555
11530
  };
11556
11531
  }
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 |");
11591
- } 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
- }
11597
- }
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
- );
11532
+ function resolveInsideWorkspace(workspaceDir, relative) {
11533
+ const base = path13.resolve(workspaceDir);
11534
+ const resolved = path13.resolve(base, relative);
11535
+ const rel = path13.relative(base, resolved);
11536
+ if (rel === "" || rel === "." || !rel.startsWith("..") && !path13.isAbsolute(rel)) {
11537
+ return resolved;
11603
11538
  }
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 }))
11539
+ throw new Error(
11540
+ `Workspace path "${relative}" resolves outside the workspace directory`
11607
11541
  );
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
- }
11542
+ }
11543
+ async function pathExists(p) {
11544
+ try {
11545
+ await access(p);
11546
+ return true;
11547
+ } catch {
11548
+ return false;
11616
11549
  }
11617
- lines.push("");
11618
- return `${lines.join("\n")}
11619
- `;
11620
11550
  }
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]`;
11551
+ async function isWritable(p) {
11552
+ try {
11553
+ await access(p, constants$1.W_OK);
11554
+ return true;
11555
+ } catch {
11556
+ return false;
11557
+ }
11629
11558
  }
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;
11559
+ async function listJsonl(dir) {
11560
+ try {
11561
+ const entries = await readdir(dir);
11562
+ return entries.filter((f) => f.endsWith(".jsonl"));
11563
+ } catch {
11564
+ return [];
11635
11565
  }
11636
- let result;
11566
+ }
11567
+ async function countFiles(dir) {
11637
11568
  try {
11638
- const manifests = [];
11639
- for (const manifestPath of manifestPaths) {
11640
- manifests.push(await readReporterManifest(manifestPath));
11641
- }
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;
11569
+ const entries = await readdir(dir, { withFileTypes: true });
11570
+ return entries.filter((e) => e.isFile()).length;
11571
+ } catch {
11572
+ return 0;
11649
11573
  }
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");
11655
- }
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");
11574
+ }
11575
+ async function readWorkspaceManifestFile(location) {
11576
+ let raw;
11577
+ try {
11578
+ raw = await readFile(location.manifestPath, "utf-8");
11579
+ } catch {
11580
+ return { exists: false, ok: false, errors: ["workspace.json not found"], warnings: [] };
11662
11581
  }
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}`);
11582
+ const parsed = parseWorkspaceManifest(raw);
11583
+ return {
11584
+ exists: true,
11585
+ ok: parsed.ok,
11586
+ ...parsed.manifest ? { manifest: parsed.manifest } : {},
11587
+ errors: parsed.errors,
11588
+ warnings: parsed.warnings
11589
+ };
11590
+ }
11591
+ async function createWorkspace(options = {}) {
11592
+ const location = resolveWorkspaceLocation(options.cwd);
11593
+ const dryRun = options.dryRun === true;
11594
+ const existing = await readWorkspaceManifestFile(location);
11595
+ const topLevelTraces = await listJsonl(location.workspaceDir);
11596
+ const detectedExistingTraces = topLevelTraces.length > 0;
11597
+ let manifest;
11598
+ let created;
11599
+ let adopted;
11600
+ if (existing.exists && existing.ok && existing.manifest) {
11601
+ manifest = existing.manifest;
11602
+ created = false;
11603
+ adopted = true;
11668
11604
  } else {
11669
- console.log(markdown.trimEnd());
11605
+ const project = options.project?.trim() || path13.basename(location.projectRoot) || "workspace";
11606
+ const traceDirs = detectedExistingTraces ? ["runs", "."] : ["runs"];
11607
+ manifest = createDefaultWorkspaceManifest({
11608
+ project,
11609
+ traceDirs,
11610
+ ...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {}
11611
+ });
11612
+ created = true;
11613
+ adopted = detectedExistingTraces || existing.exists && !existing.ok;
11670
11614
  }
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;
11615
+ const relDirs = uniqueDirs([
11616
+ ...manifest.traceDirs.filter((d) => d !== "."),
11617
+ manifest.reportsDir,
11618
+ manifest.artifactsDir,
11619
+ manifest.bundlesDir,
11620
+ manifest.notesDir,
11621
+ INDEX_DIR_NAME
11622
+ ]);
11623
+ const createdDirs = [];
11624
+ for (const rel of relDirs) {
11625
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
11626
+ if (await pathExists(abs)) continue;
11627
+ createdDirs.push(rel);
11628
+ if (!dryRun) await mkdir(abs, { recursive: true });
11679
11629
  }
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
- `;
11630
+ if (!dryRun && created) {
11631
+ await mkdir(location.workspaceDir, { recursive: true });
11632
+ await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
11633
+ }
11634
+ return {
11635
+ location,
11636
+ manifest,
11637
+ created,
11638
+ adopted,
11639
+ createdDirs,
11640
+ detectedExistingTraces,
11641
+ dryRun
11642
+ };
11699
11643
  }
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/");
11644
+ function uniqueDirs(dirs) {
11645
+ const seen = /* @__PURE__ */ new Set();
11646
+ const out = [];
11647
+ for (const d of dirs) {
11648
+ const t = d.trim();
11649
+ if (t === "" || t === "." || seen.has(t)) continue;
11650
+ seen.add(t);
11651
+ out.push(t);
11652
+ }
11653
+ return out;
11714
11654
  }
11715
-
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/");
11655
+ async function getWorkspaceStatus(location, manifest) {
11656
+ let traceFiles = 0;
11657
+ for (const rel of manifest.traceDirs) {
11658
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
11659
+ traceFiles += (await listJsonl(abs)).length;
11660
+ }
11661
+ const reports = await countFiles(
11662
+ resolveInsideWorkspace(location.workspaceDir, manifest.reportsDir)
11663
+ );
11664
+ const artifacts = await countFiles(
11665
+ resolveInsideWorkspace(location.workspaceDir, manifest.artifactsDir)
11666
+ );
11667
+ const bundles = await countFiles(
11668
+ resolveInsideWorkspace(location.workspaceDir, manifest.bundlesDir)
11669
+ );
11670
+ const notes = await countFiles(
11671
+ resolveInsideWorkspace(location.workspaceDir, manifest.notesDir)
11672
+ );
11673
+ const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
11674
+ return {
11675
+ project: manifest.project,
11676
+ traceFiles,
11677
+ reports,
11678
+ artifacts,
11679
+ bundles,
11680
+ notes,
11681
+ index: {
11682
+ enabled: manifest.index.enabled,
11683
+ type: manifest.index.type,
11684
+ exists: await pathExists(indexPath2)
11685
+ }
11686
+ };
11733
11687
  }
11734
-
11735
- main().catch((error) => {
11736
- console.error(error);
11737
- process.exitCode = 1;
11738
- });
11739
- `;
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/");
11688
+ async function doctorWorkspace(location) {
11689
+ const checks2 = [];
11690
+ const manifestResult = await readWorkspaceManifestFile(location);
11691
+ if (!manifestResult.exists) {
11692
+ checks2.push({
11693
+ id: "manifest",
11694
+ status: "fail",
11695
+ message: "workspace.json not found (run `agent-inspect workspace init`)"
11696
+ });
11697
+ return { ok: false, checks: checks2 };
11698
+ }
11699
+ if (!manifestResult.ok || !manifestResult.manifest) {
11700
+ checks2.push({
11701
+ id: "manifest",
11702
+ status: "fail",
11703
+ message: `workspace.json is invalid: ${manifestResult.errors.join("; ")}`
11704
+ });
11705
+ return { ok: false, checks: checks2 };
11706
+ }
11707
+ const manifest = manifestResult.manifest;
11708
+ checks2.push({ id: "manifest", status: "pass", message: "workspace.json is valid" });
11709
+ for (const warning of manifestResult.warnings) {
11710
+ checks2.push({ id: "manifest-warning", status: "warn", message: warning });
11711
+ }
11712
+ const dirFields = [
11713
+ ...manifest.traceDirs.filter((d) => d !== ".").map((d, i) => [`traceDir[${i}]`, d]),
11714
+ ["reportsDir", manifest.reportsDir],
11715
+ ["artifactsDir", manifest.artifactsDir],
11716
+ ["bundlesDir", manifest.bundlesDir],
11717
+ ["notesDir", manifest.notesDir]
11718
+ ];
11719
+ for (const [id, rel] of dirFields) {
11720
+ let abs;
11721
+ try {
11722
+ abs = resolveInsideWorkspace(location.workspaceDir, rel);
11723
+ } catch (error) {
11724
+ checks2.push({
11725
+ id,
11726
+ status: "fail",
11727
+ message: error instanceof Error ? error.message : String(error)
11728
+ });
11729
+ continue;
11730
+ }
11731
+ if (!await pathExists(abs)) {
11732
+ checks2.push({ id, status: "warn", message: `${rel}/ does not exist yet` });
11733
+ } else if (!await isWritable(abs)) {
11734
+ checks2.push({ id, status: "fail", message: `${rel}/ is not writable` });
11735
+ } else {
11736
+ checks2.push({ id, status: "pass", message: `${rel}/ is present and writable` });
11737
+ }
11738
+ }
11739
+ let newestTraceMtime = 0;
11740
+ for (const rel of manifest.traceDirs) {
11741
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
11742
+ for (const file of await listJsonl(abs)) {
11743
+ try {
11744
+ const s = await stat(path13.join(abs, file));
11745
+ newestTraceMtime = Math.max(newestTraceMtime, s.mtimeMs);
11746
+ } catch {
11747
+ checks2.push({ id: "trace-readability", status: "warn", message: `cannot stat ${rel}/${file}` });
11748
+ }
11749
+ }
11750
+ }
11751
+ if (manifest.index.enabled) {
11752
+ const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
11753
+ if (!await pathExists(indexPath2)) {
11754
+ checks2.push({ id: "index", status: "warn", message: "index enabled but not built" });
11755
+ } else {
11756
+ try {
11757
+ const s = await stat(indexPath2);
11758
+ if (newestTraceMtime > s.mtimeMs) {
11759
+ checks2.push({ id: "index", status: "warn", message: "index is stale (traces are newer)" });
11760
+ } else {
11761
+ checks2.push({ id: "index", status: "pass", message: "index is present" });
11762
+ }
11763
+ } catch {
11764
+ checks2.push({ id: "index", status: "warn", message: "cannot stat index" });
11765
+ }
11766
+ }
11767
+ }
11768
+ const ok = !checks2.some((c) => c.status === "fail");
11769
+ return { ok, checks: checks2 };
11752
11770
  }
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}\` };
11771
+ async function cleanWorkspace(location, manifest, options = {}) {
11772
+ const dryRun = options.confirm !== true;
11773
+ const targets = uniqueDirs([
11774
+ manifest.reportsDir,
11775
+ manifest.artifactsDir,
11776
+ manifest.bundlesDir,
11777
+ manifest.index.path ?? INDEX_DIR_NAME
11778
+ ]);
11779
+ const removed = [];
11780
+ for (const rel of targets) {
11781
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
11782
+ let entries;
11783
+ try {
11784
+ entries = await readdir(abs);
11785
+ } catch {
11786
+ continue;
11787
+ }
11788
+ for (const entry of entries) {
11789
+ const relPath = `${rel}/${entry}`;
11790
+ removed.push(relPath);
11791
+ if (!dryRun) {
11792
+ await rm(path13.join(abs, entry), { recursive: true, force: true });
11793
+ }
11794
+ }
11765
11795
  }
11796
+ return { dryRun, removed };
11766
11797
  }
11767
11798
 
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/");
11799
+ // packages/cli/src/bundle.ts
11800
+ var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification.";
11801
+ function stable6(value) {
11802
+ if (Array.isArray(value)) return value.map(stable6);
11803
+ if (value === null || typeof value !== "object") return value;
11804
+ const record = value;
11805
+ return Object.fromEntries(
11806
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
11807
+ );
11808
+ }
11809
+ function writeJson4(value) {
11810
+ return `${JSON.stringify(stable6(value), null, 2)}
11775
11811
  `;
11812
+ }
11813
+ function parseBundleProfile(value) {
11814
+ if (value === void 0 || value === "local" || value === "share" || value === "strict") {
11815
+ return value ?? "share";
11776
11816
  }
11817
+ throw new Error(`Unsupported --profile "${value}". Use local, share, or strict.`);
11777
11818
  }
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
- `;
11819
+ function toReportProfile(profile) {
11820
+ return profile;
11804
11821
  }
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
- });
11822
+ function escapeHtml3(value) {
11823
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
11824
+ }
11825
+ function safetyStatusFromAssess(status) {
11826
+ if (status === "SAFE" || status === "SAFE WITH WARNINGS" || status === "UNSAFE" || status === "UNKNOWN") {
11827
+ return status;
11819
11828
  }
11820
- const files = [];
11821
- for (const candidate of candidates) {
11822
- const abs = path11.join(cwd, candidate.rel);
11829
+ return "UNKNOWN";
11830
+ }
11831
+ async function resolveOutputDir(options, runIds, cwd) {
11832
+ if (options.out !== void 0 && options.out.trim() !== "") {
11833
+ const normalized = normalizeBundleOutputPath(options.out);
11823
11834
  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" });
11835
+ const location = resolveWorkspaceLocation(cwd);
11836
+ const manifest = await readWorkspaceManifestFile(location);
11837
+ if (manifest.ok && manifest.manifest) {
11838
+ const rel = path13.relative(location.workspaceDir, normalized);
11839
+ if (!rel.startsWith("..") && !path13.isAbsolute(rel)) {
11840
+ return resolveInsideWorkspace(location.workspaceDir, rel);
11841
+ }
11842
+ }
11843
+ } catch {
11833
11844
  }
11845
+ return normalized;
11834
11846
  }
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;
11847
+ try {
11848
+ const location = resolveWorkspaceLocation(cwd);
11849
+ const manifest = await readWorkspaceManifestFile(location);
11850
+ if (manifest.ok && manifest.manifest) {
11851
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11852
+ const label = runIds.length === 1 ? runIds[0] : `multi-${runIds.length}`;
11853
+ return resolveInsideWorkspace(
11854
+ location.workspaceDir,
11855
+ path13.join(manifest.manifest.bundlesDir, `bundle-${label}-${stamp}`)
11856
+ );
11847
11857
  }
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);
11858
+ } catch {
11852
11859
  }
11853
- return written;
11860
+ return defaultBundleOutputPath(runIds);
11854
11861
  }
11855
- async function initCommand(options = {}) {
11856
- const cwd = path11.resolve(options.cwd ?? process.cwd());
11862
+ async function writeBundleFile(outputDir, relativePath, content, files) {
11863
+ const outPath = path13.join(outputDir, relativePath);
11864
+ await mkdir(path13.dirname(outPath), { recursive: true });
11865
+ await writeFile(outPath, content, "utf-8");
11866
+ files.push(relativePath);
11867
+ }
11868
+ function renderBundleIndexHtml(parts) {
11869
+ const links = parts.runIds.map(
11870
+ (runId) => `<li><a href="assets/runs/${escapeHtml3(runId)}.html">${escapeHtml3(runId)}</a></li>`
11871
+ ).join("");
11872
+ const sections = parts.runIds.map((runId) => {
11873
+ const html = parts.reports.get(runId) ?? "";
11874
+ return `<section id="run-${escapeHtml3(runId)}"><h2>${escapeHtml3(runId)}</h2>${html}</section>`;
11875
+ }).join("\n");
11876
+ return `<!doctype html>
11877
+ <html lang="en">
11878
+ <head>
11879
+ <meta charset="utf-8"/>
11880
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
11881
+ <title>AgentInspect Bundle</title>
11882
+ <style>
11883
+ body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}
11884
+ a{color:#0366d6}
11885
+ section{border-top:1px solid #ddd;margin-top:1.5rem;padding-top:1rem}
11886
+ </style>
11887
+ </head>
11888
+ <body>
11889
+ <h1>AgentInspect trace bundle</h1>
11890
+ <p>${escapeHtml3(BUNDLE_NOTE)}</p>
11891
+ <h2>Runs</h2>
11892
+ <ul>${links}</ul>
11893
+ ${sections}
11894
+ </body>
11895
+ </html>
11896
+ `;
11897
+ }
11898
+ function extractHtmlBody(content) {
11899
+ const match = content.match(/<body[^>]*>([\s\S]*)<\/body>/i);
11900
+ return match?.[1]?.trim() ?? content;
11901
+ }
11902
+ async function bundleCommand(runIdArg, options = {}) {
11903
+ const profile = parseBundleProfile(options.profile);
11904
+ const traceDir = resolveTraceDir({ dir: options.dir });
11905
+ const cwd = process.cwd();
11906
+ let resolveResult;
11857
11907
  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");
11908
+ const { runs } = await loadSessionRuns(traceDir);
11909
+ const staleThresholdMs = options.staleAfter && options.staleAfter.trim() !== "" ? parseDuration(options.staleAfter.trim()) : void 0;
11910
+ const index = buildSessionIndex(runs, {
11911
+ correlateByGroupId: options.correlateGroup === true,
11912
+ staleThresholdMs
11913
+ });
11914
+ resolveResult = resolveBundleRunIds(index, runs, {
11915
+ ...runIdArg !== void 0 && runIdArg.trim() !== "" ? { runId: runIdArg.trim() } : {},
11916
+ ...options.session ? { sessionId: options.session } : {},
11917
+ ...options.since ? { since: options.since } : {}
11918
+ });
11919
+ } catch (error) {
11920
+ const message = error instanceof Error ? error.message : String(error);
11861
11921
  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;
11922
+ console.log(writeJson4({ ok: false, error: message }).trimEnd());
11923
+ } else {
11924
+ console.error(`[AgentInspect] bundle failed: ${message}`);
11873
11925
  }
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;
11926
+ process.exitCode = 1;
11927
+ return;
11928
+ }
11929
+ const outputDir = await resolveOutputDir(options, resolveResult.runIds, cwd);
11930
+ const files = [];
11931
+ const checkRuns = [];
11932
+ const redactionRuns = [];
11933
+ const htmlByRun = /* @__PURE__ */ new Map();
11934
+ const redactedJsonlByRun = /* @__PURE__ */ new Map();
11935
+ let combinedJsonl = "";
11936
+ for (const runId of resolveResult.runIds) {
11937
+ const tracePath = getTraceFilePath(runId, traceDir);
11938
+ let rawContent;
11939
+ let sourceMtimeMs;
11940
+ try {
11941
+ rawContent = await readFile(tracePath, "utf-8");
11942
+ sourceMtimeMs = (await stat(tracePath)).mtimeMs;
11943
+ } catch (error) {
11944
+ const message = error instanceof Error ? error.message : String(error);
11945
+ if (options.json) {
11946
+ console.log(writeJson4({ ok: false, error: message, runId }).trimEnd());
11947
+ } else {
11948
+ console.error(`[AgentInspect] bundle failed: ${message}`);
11886
11949
  }
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"})`);
11950
+ process.exitCode = 1;
11951
+ return;
11952
+ }
11953
+ let read;
11954
+ try {
11955
+ read = await openTrace(
11956
+ { type: "file", path: tracePath },
11957
+ { format: "agent-inspect-jsonl" }
11958
+ );
11959
+ } catch (error) {
11960
+ const message = error instanceof Error ? error.message : String(error);
11961
+ if (options.json) {
11962
+ console.log(writeJson4({ ok: false, error: message, runId }).trimEnd());
11963
+ } else {
11964
+ console.error(`[AgentInspect] bundle failed: ${message}`);
11891
11965
  }
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.");
11966
+ process.exitCode = 1;
11967
+ return;
11894
11968
  }
11895
- } catch (error) {
11896
- const msg = error instanceof Error ? error.message : String(error);
11969
+ const safety = assessOpenedTrace(read, { run: runId });
11970
+ const status = safetyStatusFromAssess(safety.status);
11971
+ checkRuns.push({
11972
+ runId,
11973
+ status,
11974
+ errors: safety.summary.errors,
11975
+ warnings: safety.summary.warnings,
11976
+ findings: safety.summary.findings
11977
+ });
11978
+ const redacted = redactTraceContent(rawContent, toReportProfile(profile));
11979
+ redactedJsonlByRun.set(runId, redacted.content);
11980
+ combinedJsonl += redacted.content.endsWith("\n") ? redacted.content : `${redacted.content}
11981
+ `;
11982
+ const detectors = [
11983
+ ...new Set(redacted.findings.map((finding) => finding.detector))
11984
+ ].sort((a, b) => a.localeCompare(b));
11985
+ redactionRuns.push({
11986
+ runId,
11987
+ findings: redacted.findings.length,
11988
+ detectors
11989
+ });
11990
+ const selected = read.runs.find((run) => run.runId === runId);
11991
+ const runReport = exportRunTree(selected ?? read.runs[0], {
11992
+ format: "html",
11993
+ redacted: true,
11994
+ redactionProfile: toReportProfile(profile)
11995
+ });
11996
+ htmlByRun.set(runId, extractHtmlBody(runReport.content));
11997
+ const afterMtime = (await stat(tracePath)).mtimeMs;
11998
+ if (afterMtime !== sourceMtimeMs) {
11999
+ const message = `Source trace "${runId}" was modified during bundle creation.`;
12000
+ if (options.json) {
12001
+ console.log(writeJson4({ ok: false, error: message }).trimEnd());
12002
+ } else {
12003
+ console.error(`[AgentInspect] bundle failed: ${message}`);
12004
+ }
12005
+ process.exitCode = 1;
12006
+ return;
12007
+ }
12008
+ }
12009
+ const checks2 = {
12010
+ aggregateStatus: aggregateBundleSafeStatus(checkRuns.map((run) => run.status)),
12011
+ runs: checkRuns
12012
+ };
12013
+ if (bundleFailsOnSafety(checks2.aggregateStatus, options.allowUnsafe === true)) {
11897
12014
  if (options.json) {
11898
- console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
12015
+ console.log(
12016
+ writeJson4({
12017
+ ok: false,
12018
+ error: `Bundle safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`,
12019
+ checks: checks2
12020
+ }).trimEnd()
12021
+ );
11899
12022
  } else {
11900
- console.error(`[AgentInspect] init failed: ${msg}`);
12023
+ console.error(
12024
+ `[AgentInspect] bundle refused: safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`
12025
+ );
11901
12026
  }
11902
12027
  process.exitCode = 1;
12028
+ return;
12029
+ }
12030
+ const redactionReport = {
12031
+ profile,
12032
+ totalFindings: redactionRuns.reduce((sum, run) => sum + run.findings, 0),
12033
+ runs: redactionRuns
12034
+ };
12035
+ await mkdir(outputDir, { recursive: true });
12036
+ for (const runId of resolveResult.runIds) {
12037
+ const jsonl = redactedJsonlByRun.get(runId) ?? "";
12038
+ await writeBundleFile(outputDir, `assets/runs/${runId}.jsonl`, jsonl, files);
12039
+ const html = htmlByRun.get(runId) ?? "";
12040
+ await writeBundleFile(
12041
+ outputDir,
12042
+ `assets/runs/${runId}.html`,
12043
+ runReportWrap(html, runId),
12044
+ files
12045
+ );
12046
+ }
12047
+ const primaryRunId = resolveResult.runIds[0];
12048
+ const traceHtml = resolveResult.runIds.length === 1 ? runReportWrap(htmlByRun.get(primaryRunId) ?? "", primaryRunId) : renderBundleIndexHtml({ runIds: resolveResult.runIds, reports: htmlByRun });
12049
+ await writeBundleFile(outputDir, "trace.jsonl", combinedJsonl, files);
12050
+ await writeBundleFile(outputDir, "trace.html", traceHtml, files);
12051
+ await writeBundleFile(
12052
+ outputDir,
12053
+ "check-results.json",
12054
+ writeJson4(checks2),
12055
+ files
12056
+ );
12057
+ await writeBundleFile(
12058
+ outputDir,
12059
+ "eval-results.json",
12060
+ writeJson4(buildPlaceholderArtifact()),
12061
+ files
12062
+ );
12063
+ await writeBundleFile(
12064
+ outputDir,
12065
+ "redaction-report.json",
12066
+ writeJson4(redactionReport),
12067
+ files
12068
+ );
12069
+ await writeBundleFile(
12070
+ outputDir,
12071
+ "performance-summary.json",
12072
+ writeJson4(buildPlaceholderArtifact()),
12073
+ files
12074
+ );
12075
+ const metadata = buildBundleMetadata({
12076
+ agentInspectVersion: version,
12077
+ profile,
12078
+ resolve: resolveResult,
12079
+ checks: checks2,
12080
+ files: [...files]
12081
+ });
12082
+ await writeBundleFile(outputDir, "metadata.json", writeJson4(metadata), files);
12083
+ await writeBundleFile(
12084
+ outputDir,
12085
+ "summary.md",
12086
+ buildBundleSummaryMarkdown({ metadata, checks: checks2, redaction: redactionReport }),
12087
+ files
12088
+ );
12089
+ metadata.files = [...files].sort((a, b) => a.localeCompare(b));
12090
+ await writeFile(path13.join(outputDir, "metadata.json"), writeJson4(metadata), "utf-8");
12091
+ if (options.json) {
12092
+ console.log(
12093
+ writeJson4({
12094
+ ok: true,
12095
+ outputDir,
12096
+ metadata,
12097
+ checks: checks2,
12098
+ redaction: redactionReport
12099
+ }).trimEnd()
12100
+ );
12101
+ return;
12102
+ }
12103
+ console.log(`Bundle written to ${outputDir}`);
12104
+ console.log(`Safe status: ${metadata.safeStatus}`);
12105
+ console.log(`Runs: ${resolveResult.runIds.join(", ")}`);
12106
+ console.log(`Files: ${metadata.files.length}`);
12107
+ }
12108
+ function runReportWrap(body, runId) {
12109
+ return `<!doctype html>
12110
+ <html lang="en">
12111
+ <head>
12112
+ <meta charset="utf-8"/>
12113
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
12114
+ <title>AgentInspect \u2014 ${escapeHtml3(runId)}</title>
12115
+ <style>body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}</style>
12116
+ </head>
12117
+ <body>
12118
+ ${body}
12119
+ </body>
12120
+ </html>
12121
+ `;
12122
+ }
12123
+ function validateReporterArtifactPath(options) {
12124
+ const outputDir = path13.resolve(options.outputDir);
12125
+ const diagnostics = [];
12126
+ const rawPath = options.relativePath;
12127
+ if (rawPath.length === 0) {
12128
+ diagnostics.push({
12129
+ code: "artifact_path_empty",
12130
+ severity: "error",
12131
+ message: "Reporter artifact path must not be empty."
12132
+ });
12133
+ return { ok: false, outputDir, diagnostics };
12134
+ }
12135
+ if (rawPath.includes("\0")) {
12136
+ diagnostics.push({
12137
+ code: "invalid_artifact_path",
12138
+ severity: "error",
12139
+ message: "Reporter artifact path must not contain null bytes.",
12140
+ target: rawPath
12141
+ });
12142
+ return { ok: false, outputDir, diagnostics };
11903
12143
  }
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
- };
12144
+ if (path13.isAbsolute(rawPath) || path13.win32.isAbsolute(rawPath)) {
12145
+ diagnostics.push({
12146
+ code: "artifact_path_absolute",
12147
+ severity: "error",
12148
+ message: "Reporter artifact path must be relative.",
12149
+ target: rawPath
12150
+ });
12151
+ return { ok: false, outputDir, diagnostics };
12152
+ }
12153
+ const normalized = path13.posix.normalize(rawPath.replace(/\\/g, "/"));
12154
+ const segments = normalized.split("/");
12155
+ if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
12156
+ diagnostics.push({
12157
+ code: "artifact_path_escape",
12158
+ severity: "error",
12159
+ message: "Reporter artifact path must stay under the output directory.",
12160
+ target: rawPath
12161
+ });
12162
+ return { ok: false, outputDir, diagnostics };
12163
+ }
12164
+ const absolutePath = path13.resolve(outputDir, normalized);
12165
+ const relFromOutput = path13.relative(outputDir, absolutePath);
12166
+ if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path13.isAbsolute(relFromOutput)) {
12167
+ diagnostics.push({
12168
+ code: "artifact_path_escape",
12169
+ severity: "error",
12170
+ message: "Reporter artifact path resolved outside the output directory.",
12171
+ target: rawPath
12172
+ });
12173
+ return { ok: false, outputDir, diagnostics };
11921
12174
  }
11922
12175
  return {
11923
- id: "node-version",
11924
- status: "pass",
11925
- message: `Node ${process2.versions.node} meets the minimum (>=20).`,
11926
- evidence: process2.versions.node
12176
+ ok: true,
12177
+ outputDir,
12178
+ relativePath: normalized,
12179
+ absolutePath,
12180
+ diagnostics
11927
12181
  };
11928
12182
  }
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
- };
12183
+
12184
+ // packages/cli/src/ci-summary.ts
12185
+ var NOTE2 = "Generated locally by AgentInspect from reporter artifact manifests. Trace contents are not embedded.";
12186
+ var MAX_TEXT = 180;
12187
+ var FRAMEWORKS = /* @__PURE__ */ new Set(["vitest", "jest", "manual"]);
12188
+ var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
12189
+ var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
12190
+ var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
12191
+ var REDACTION_PROFILES2 = /* @__PURE__ */ new Set(["local", "share", "strict"]);
12192
+ function stable7(value) {
12193
+ if (Array.isArray(value)) return value.map(stable7);
12194
+ if (value === null || typeof value !== "object") return value;
12195
+ const record = value;
12196
+ return Object.fromEntries(
12197
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable7(record[key])])
12198
+ );
12199
+ }
12200
+ function writeJson5(value) {
12201
+ return `${JSON.stringify(stable7(value), null, 2)}
12202
+ `;
12203
+ }
12204
+ function isObject(value) {
12205
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12206
+ }
12207
+ function readString(value, label) {
12208
+ if (typeof value !== "string" || value.trim() === "") {
12209
+ throw new Error(`${label} must be a non-empty string.`);
11938
12210
  }
11939
- return {
11940
- id: `env-${name.toLowerCase()}`,
11941
- status: "pass",
11942
- message: `${name} is set.`,
11943
- evidence: value
11944
- };
12211
+ return safeText(value);
11945
12212
  }
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
- };
12213
+ function readOptionalString(value) {
12214
+ return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
12215
+ }
12216
+ function readFramework(value) {
12217
+ const framework = readString(value, "manifest.framework");
12218
+ if (!FRAMEWORKS.has(framework)) {
12219
+ throw new Error(`Unsupported reporter framework: ${framework}.`);
11965
12220
  }
12221
+ return framework;
11966
12222
  }
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 };
12223
+ function readStatus(value) {
12224
+ const status = readString(value, "result.status");
12225
+ if (!STATUSES.has(status)) {
12226
+ throw new Error(`Unsupported reporter test status: ${status}.`);
11975
12227
  }
12228
+ return status;
11976
12229
  }
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."
11986
- });
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)."
11993
- });
12230
+ function readArtifact(value, index) {
12231
+ if (!isObject(value)) throw new Error(`manifest.artifacts[${index}] must be an object.`);
12232
+ const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
12233
+ const format = readString(value.format, `manifest.artifacts[${index}].format`);
12234
+ const redactionProfile = readString(
12235
+ value.redactionProfile,
12236
+ `manifest.artifacts[${index}].redactionProfile`
12237
+ );
12238
+ if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
12239
+ if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
12240
+ if (!REDACTION_PROFILES2.has(redactionProfile)) {
12241
+ throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
11994
12242
  }
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."
12001
- });
12002
- } catch {
12003
- results.push({
12004
- id: "import-agent-inspect-cjs",
12005
- status: "skipped",
12006
- message: "CJS resolution check skipped (package not installed locally)."
12007
- });
12243
+ const artifactPath = readString(value.path, `manifest.artifacts[${index}].path`);
12244
+ const pathCheck = validateReporterArtifactPath({
12245
+ outputDir: process.cwd(),
12246
+ relativePath: artifactPath
12247
+ });
12248
+ if (!pathCheck.ok || pathCheck.relativePath === void 0) {
12249
+ throw new Error(`Unsafe reporter artifact path: ${artifactPath}.`);
12008
12250
  }
12009
- return results;
12251
+ return {
12252
+ kind,
12253
+ path: pathCheck.relativePath,
12254
+ format,
12255
+ redactionProfile
12256
+ };
12010
12257
  }
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
- });
12258
+ function readArtifacts(value, label) {
12259
+ if (value === void 0) return [];
12260
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
12261
+ return value.map((item, index) => readArtifact(item, index));
12262
+ }
12263
+ function readDiagnosticsCount(value, label) {
12264
+ if (value === void 0) return 0;
12265
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
12266
+ return value.length;
12267
+ }
12268
+ function readResult(value, index) {
12269
+ if (!isObject(value)) throw new Error(`manifest.results[${index}] must be an object.`);
12270
+ const file = readOptionalString(value.file);
12271
+ const tracePath = readOptionalString(value.tracePath);
12272
+ return {
12273
+ testId: readString(value.testId, `manifest.results[${index}].testId`),
12274
+ name: readString(value.name, `manifest.results[${index}].name`),
12275
+ ...file === void 0 ? {} : { file },
12276
+ status: readStatus(value.status),
12277
+ ...tracePath === void 0 ? {} : { tracePath },
12278
+ artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
12279
+ diagnostics: readDiagnosticsCount(
12280
+ value.diagnostics,
12281
+ `manifest.results[${index}].diagnostics`
12282
+ )
12283
+ };
12284
+ }
12285
+ function readManifestDocument(value) {
12286
+ if (!isObject(value)) throw new Error("Reporter manifest file must contain a JSON object.");
12287
+ const candidate = isObject(value.manifest) ? value.manifest : value;
12288
+ if (!isObject(candidate)) throw new Error("Reporter manifest must be a JSON object.");
12289
+ const schemaVersion = readString(candidate.schemaVersion, "manifest.schemaVersion");
12290
+ if (schemaVersion !== "0.1") {
12291
+ throw new Error(`Unsupported reporter manifest schemaVersion: ${schemaVersion}.`);
12292
+ }
12293
+ if (!Array.isArray(candidate.results)) {
12294
+ throw new Error("manifest.results must be an array.");
12295
+ }
12296
+ const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
12297
+ const results = candidate.results.map((item, index) => readResult(item, index));
12298
+ const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
12299
+ const manifest = {
12300
+ framework: readFramework(candidate.framework),
12301
+ generatedAt: readString(candidate.generatedAt, "manifest.generatedAt"),
12302
+ results,
12303
+ artifacts
12304
+ };
12305
+ return {
12306
+ packageName: readOptionalString(value.package),
12307
+ manifest,
12308
+ diagnostics
12309
+ };
12036
12310
  }
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
- };
12311
+ function cwdRelative(filePath) {
12312
+ const relative = path13.relative(process.cwd(), path13.resolve(filePath)).replace(/\\/g, "/");
12313
+ if (relative === "" || relative.startsWith("../") || path13.isAbsolute(relative)) {
12314
+ return path13.basename(filePath);
12045
12315
  }
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
+ return relative;
12317
+ }
12318
+ async function readReporterManifest(filePath) {
12319
+ const absolute = path13.resolve(filePath);
12320
+ const raw = await readFile(absolute, "utf-8");
12321
+ const document = readManifestDocument(JSON.parse(raw));
12322
+ const manifest = document.manifest;
12323
+ const results = manifest.results.map((result) => ({
12324
+ testId: safeText(result.testId),
12325
+ name: safeText(result.name),
12326
+ ...result.file === void 0 ? {} : { file: safeText(path13.basename(result.file)) },
12327
+ status: result.status,
12328
+ ...result.tracePath === void 0 ? {} : { tracePath: safeText(path13.basename(result.tracePath)) },
12329
+ artifacts: result.artifacts,
12330
+ diagnostics: result.diagnostics
12331
+ }));
12332
+ return {
12333
+ ...document.packageName === void 0 ? {} : { packageName: document.packageName },
12334
+ manifestFile: cwdRelative(absolute),
12335
+ framework: manifest.framework,
12336
+ generatedAt: manifest.generatedAt,
12337
+ results,
12338
+ artifacts: manifest.artifacts,
12339
+ diagnostics: document.diagnostics
12340
+ };
12341
+ }
12342
+ function summarize3(manifests) {
12343
+ const summary = {
12344
+ manifests: manifests.length,
12345
+ tests: 0,
12346
+ failed: 0,
12347
+ passed: 0,
12348
+ skipped: 0,
12349
+ todo: 0,
12350
+ artifacts: 0,
12351
+ diagnostics: 0
12352
+ };
12353
+ for (const manifest of manifests) {
12354
+ summary.artifacts += manifest.artifacts.length;
12355
+ summary.diagnostics += manifest.diagnostics;
12356
+ for (const result of manifest.results) {
12357
+ summary.tests += 1;
12358
+ if (result.status === "failed") summary.failed += 1;
12359
+ else if (result.status === "passed") summary.passed += 1;
12360
+ else if (result.status === "skipped") summary.skipped += 1;
12361
+ else summary.todo += 1;
12362
+ summary.artifacts += result.artifacts.length;
12363
+ summary.diagnostics += result.diagnostics;
12364
+ }
12054
12365
  }
12055
12366
  return {
12056
- id: "version-alignment",
12057
- status: "pass",
12058
- message: `CLI and local agent-inspect are aligned at ${version}.`
12367
+ status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
12368
+ manifests,
12369
+ summary,
12370
+ note: NOTE2
12059
12371
  };
12060
12372
  }
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)
12373
+ function markdownCell(value) {
12374
+ return safeText(String(value ?? "unknown")).replaceAll("|", "\\|").replace(/\r?\n/g, " ");
12375
+ }
12376
+ function renderMarkdown2(result) {
12377
+ const lines = [
12378
+ "# AgentInspect CI Summary",
12379
+ "",
12380
+ NOTE2,
12381
+ "",
12382
+ "| Field | Value |",
12383
+ "| --- | --- |",
12384
+ `| Status | ${result.status} |`,
12385
+ `| Manifests | ${result.summary.manifests} |`,
12386
+ `| Tests | ${result.summary.tests} |`,
12387
+ `| Failed | ${result.summary.failed} |`,
12388
+ `| Passed | ${result.summary.passed} |`,
12389
+ `| Skipped | ${result.summary.skipped} |`,
12390
+ `| Todo | ${result.summary.todo} |`,
12391
+ `| Artifacts | ${result.summary.artifacts} |`,
12392
+ `| Diagnostics | ${result.summary.diagnostics} |`,
12393
+ "",
12394
+ "## Tests",
12395
+ "",
12396
+ "| Framework | Status | Test | File | Trace | Artifacts |",
12397
+ "| --- | --- | --- | --- | --- | --- |"
12076
12398
  ];
12077
- if (options.checkImports !== false) {
12078
- checks2.push(...importSmoke(cwd));
12399
+ const rows = result.manifests.flatMap(
12400
+ (manifest) => manifest.results.map((test) => ({
12401
+ framework: manifest.framework,
12402
+ test
12403
+ }))
12404
+ );
12405
+ if (rows.length === 0) {
12406
+ lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
12407
+ } else {
12408
+ for (const row of rows) {
12409
+ lines.push(
12410
+ `| ${markdownCell(row.framework)} | ${markdownCell(row.test.status)} | ${markdownCell(row.test.name)} | ${markdownCell(row.test.file)} | ${markdownCell(row.test.tracePath)} | ${row.test.artifacts.length} |`
12411
+ );
12412
+ }
12079
12413
  }
12080
- checks2.push(...optionalPackageChecks(cwd, options.framework));
12081
- return checks2.sort((a, b) => a.id.localeCompare(b.id));
12082
- }
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
- )
12414
+ lines.push("", "## Manifests", "", "| Framework | File | Generated | Artifacts |", "| --- | --- | --- | --- |");
12415
+ for (const manifest of result.manifests) {
12416
+ lines.push(
12417
+ `| ${markdownCell(manifest.framework)} | ${markdownCell(manifest.manifestFile)} | ${markdownCell(manifest.generatedAt)} | ${manifest.artifacts.length} |`
12099
12418
  );
12100
- if (failed > 0) process2.exitCode = 1;
12419
+ }
12420
+ lines.push("", "## Artifacts", "", "| Framework | Kind | Path | Format | Profile |", "| --- | --- | --- | --- | --- |");
12421
+ const artifacts = result.manifests.flatMap(
12422
+ (manifest) => manifest.artifacts.map((artifact) => ({ framework: manifest.framework, artifact }))
12423
+ );
12424
+ if (artifacts.length === 0) {
12425
+ lines.push("| unknown | unknown | No artifacts found | unknown | unknown |");
12426
+ } else {
12427
+ for (const row of artifacts) {
12428
+ lines.push(
12429
+ `| ${markdownCell(row.framework)} | ${markdownCell(row.artifact.kind)} | ${markdownCell(row.artifact.path)} | ${markdownCell(row.artifact.format)} | ${markdownCell(row.artifact.redactionProfile)} |`
12430
+ );
12431
+ }
12432
+ }
12433
+ lines.push("");
12434
+ return `${lines.join("\n")}
12435
+ `;
12436
+ }
12437
+ function safeText(value) {
12438
+ const compact = value.replace(/\s+/g, " ").trim();
12439
+ const redacted = compact.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(
12440
+ /\b(?:api[_-]?key|authorization|token|secret|password)\s*[:=]\s*[^,\s]+/gi,
12441
+ "$1=[REDACTED]"
12442
+ );
12443
+ if (redacted.length <= MAX_TEXT) return redacted;
12444
+ return `${redacted.slice(0, MAX_TEXT - 12)}...[truncated]`;
12445
+ }
12446
+ async function ciSummaryCommand(manifestPaths, options = {}) {
12447
+ if (manifestPaths.length === 0) {
12448
+ console.error("At least one reporter manifest path is required.");
12449
+ process.exitCode = 1;
12101
12450
  return;
12102
12451
  }
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}`);
12452
+ let result;
12453
+ try {
12454
+ const manifests = [];
12455
+ for (const manifestPath of manifestPaths) {
12456
+ manifests.push(await readReporterManifest(manifestPath));
12457
+ }
12458
+ manifests.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
12459
+ result = summarize3(manifests);
12460
+ } catch (error) {
12461
+ const message = error instanceof Error ? error.message : String(error);
12462
+ console.error(`[AgentInspect] ci-summary failed: ${message}`);
12463
+ process.exitCode = 1;
12464
+ return;
12108
12465
  }
12109
- console.log(`
12110
- Summary: ${failed} failed, ${warned} warnings`);
12111
- if (failed > 0) process2.exitCode = 1;
12466
+ const markdown = renderMarkdown2(result);
12467
+ const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
12468
+ if (outputPath !== void 0) {
12469
+ await mkdir(path13.dirname(outputPath), { recursive: true });
12470
+ await writeFile(outputPath, markdown, "utf-8");
12471
+ }
12472
+ const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
12473
+ if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
12474
+ const summaryPath = path13.resolve(summaryTarget);
12475
+ await mkdir(path13.dirname(summaryPath), { recursive: true });
12476
+ await appendFile(summaryPath, `
12477
+ ${markdown}`, "utf-8");
12478
+ }
12479
+ if (options.json === true) {
12480
+ console.log(writeJson5(result).trimEnd());
12481
+ } else if (outputPath !== void 0) {
12482
+ console.log(`Wrote AgentInspect CI summary to ${outputPath}`);
12483
+ console.log(`Status: ${result.status}`);
12484
+ } else {
12485
+ console.log(markdown.trimEnd());
12486
+ }
12487
+ }
12488
+ var CONFIG_FILE = "agent-inspect.config.ts";
12489
+ var TRACE_DIR = ".agent-inspect";
12490
+ var GITKEEP = ".agent-inspect/.gitkeep";
12491
+ function normalizeFramework(value) {
12492
+ const raw = (value ?? "custom").trim();
12493
+ if (raw === "ai-sdk" || raw === "openai-agents" || raw === "langchain" || raw === "custom") {
12494
+ return raw;
12495
+ }
12496
+ throw new Error(
12497
+ "Unsupported --framework value. Use ai-sdk, openai-agents, langchain, or custom."
12498
+ );
12499
+ }
12500
+ function configTemplate(framework) {
12501
+ const base = `/**
12502
+ * AgentInspect local config (metadata-only capture by default).
12503
+ * See https://github.com/rajudandigam/agent-inspect/blob/main/docs/SAFE-TRACE-SHARING.md
12504
+ */
12505
+ export const agentInspectConfig = {
12506
+ traceDir: ".agent-inspect",
12507
+ enabled: process.env.AGENT_INSPECT !== "0",
12508
+ redactionProfile: "local" as const,
12509
+ };
12510
+ `;
12511
+ if (framework === "custom") return base;
12512
+ return `${base}
12513
+ export const framework = "${framework}" as const;
12514
+ `;
12112
12515
  }
12516
+ function demoTemplate(framework) {
12517
+ switch (framework) {
12518
+ case "ai-sdk":
12519
+ return `/**
12520
+ * AI SDK starter \u2014 metadata-only telemetry (no real model calls in this demo).
12521
+ * Install: npm install agent-inspect @agent-inspect/ai-sdk ai
12522
+ */
12523
+ import { inspectRun, step } from "agent-inspect";
12113
12524
 
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;
12118
- }
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;
12127
- }
12128
- return false;
12525
+ async function main() {
12526
+ await inspectRun("ai-sdk-demo", async () => {
12527
+ await step.tool("mock-generate", async () => ({ text: "ok" }));
12528
+ }, { traceDir: ".agent-inspect", silent: true });
12529
+ console.log("Trace written to .agent-inspect/");
12129
12530
  }
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
- }
12168
- });
12531
+
12532
+ main().catch((error) => {
12533
+ console.error(error);
12534
+ process.exitCode = 1;
12535
+ });
12536
+ `;
12537
+ case "openai-agents":
12538
+ return `/**
12539
+ * OpenAI Agents starter \u2014 use @agent-inspect/openai-agents for local-only processors.
12540
+ * This demo uses manual steps (no API keys).
12541
+ */
12542
+ import { inspectRun, step } from "agent-inspect";
12543
+
12544
+ async function main() {
12545
+ await inspectRun("openai-agents-demo", async () => {
12546
+ await step.tool("mock-agent-run", async () => "ok");
12547
+ }, { traceDir: ".agent-inspect", silent: true });
12548
+ console.log("Trace written to .agent-inspect/");
12169
12549
  }
12170
12550
 
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);
12551
+ main().catch((error) => {
12552
+ console.error(error);
12553
+ process.exitCode = 1;
12554
+ });
12555
+ `;
12556
+ case "langchain":
12557
+ return `/**
12558
+ * LangChain starter \u2014 wire @agent-inspect/langchain callbacks in your app.
12559
+ * This demo uses manual steps (no API keys).
12560
+ */
12561
+ import { inspectRun, step } from "agent-inspect";
12562
+
12563
+ async function main() {
12564
+ await inspectRun("langchain-demo", async () => {
12565
+ await step.tool("mock-chain", async () => "ok");
12566
+ }, { traceDir: ".agent-inspect", silent: true });
12567
+ console.log("Trace written to .agent-inspect/");
12175
12568
  }
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.");
12569
+
12570
+ main().catch((error) => {
12571
+ console.error(error);
12572
+ process.exitCode = 1;
12573
+ });
12574
+ `;
12575
+ default:
12576
+ return `import { observe } from "agent-inspect";
12577
+
12578
+ class DemoAgent {
12579
+ async run(input: { question: string }) {
12580
+ return { answer: \`Echo: \${input.question}\` };
12181
12581
  }
12182
- return parsed;
12183
12582
  }
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;
12583
+
12584
+ const agent = observe(new DemoAgent(), {
12585
+ traceDir: ".agent-inspect",
12586
+ silent: true,
12587
+ });
12588
+
12589
+ await agent.run({ question: "hello" });
12590
+ console.log("Trace written to .agent-inspect/");
12591
+ `;
12190
12592
  }
12191
12593
  }
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
- }
12213
- }
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
- }
12594
+ function githubWorkflowTemplate() {
12595
+ return `name: AgentInspect artifacts
12596
+
12597
+ on:
12598
+ workflow_dispatch:
12599
+ push:
12600
+ branches: [main]
12601
+
12602
+ jobs:
12603
+ trace-artifacts:
12604
+ runs-on: ubuntu-latest
12605
+ steps:
12606
+ - uses: actions/checkout@v4
12607
+ - uses: actions/setup-node@v4
12608
+ with:
12609
+ node-version: "22"
12610
+ - run: npm ci
12611
+ - run: npm test
12612
+ - name: Upload AgentInspect traces
12613
+ if: always()
12614
+ uses: actions/upload-artifact@v4
12615
+ with:
12616
+ name: agent-inspect-traces
12617
+ path: .agent-inspect/**/*.jsonl
12618
+ if-no-files-found: ignore
12619
+ `;
12219
12620
  }
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;
12234
- }
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;
12249
- }
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
- }
12258
- }
12259
- } catch (e) {
12260
- const msg = e instanceof Error ? e.message : String(e);
12261
- console.error(`[AgentInspect] index status failed: ${msg}`);
12262
- process.exitCode = 1;
12621
+ async function planInit(options = {}) {
12622
+ const framework = normalizeFramework(options.framework);
12623
+ const cwd = path13.resolve(options.cwd ?? process.cwd());
12624
+ const demoPath = framework === "custom" ? path13.join("examples", "agent-inspect-demo.mjs") : path13.join("examples", `agent-inspect-${framework}-demo.mjs`);
12625
+ const candidates = [
12626
+ { rel: CONFIG_FILE, content: configTemplate(framework) },
12627
+ { rel: GITKEEP, content: "" },
12628
+ { rel: demoPath, content: demoTemplate(framework) }
12629
+ ];
12630
+ if (options.ci === "github") {
12631
+ candidates.push({
12632
+ rel: ".github/workflows/agent-inspect-artifacts.yml",
12633
+ content: githubWorkflowTemplate()
12634
+ });
12263
12635
  }
12264
- }
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;
12636
+ const files = [];
12637
+ for (const candidate of candidates) {
12638
+ const abs = path13.join(cwd, candidate.rel);
12639
+ try {
12640
+ await access(abs);
12641
+ files.push({
12642
+ path: candidate.rel,
12643
+ action: "skip",
12644
+ reason: "file already exists"
12645
+ });
12646
+ } catch (error) {
12647
+ if (error.code !== "ENOENT") throw error;
12648
+ files.push({ path: candidate.rel, action: "create" });
12273
12649
  }
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
12650
  }
12651
+ return { framework, ...options.ci ? { ci: options.ci } : {}, files };
12280
12652
  }
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;
12653
+ async function writePlannedFiles(plan, cwd, options) {
12654
+ const written = [];
12655
+ for (const entry of plan.files) {
12656
+ if (entry.action === "skip") {
12657
+ continue;
12295
12658
  }
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.`);
12659
+ const abs = path13.join(cwd, entry.path);
12660
+ if (options.dryRun) {
12661
+ written.push(entry.path);
12662
+ continue;
12663
+ }
12664
+ await mkdir(path13.dirname(abs), { recursive: true });
12665
+ const content = entry.path === CONFIG_FILE ? configTemplate(plan.framework) : entry.path === GITKEEP ? "" : entry.path.endsWith(".yml") ? githubWorkflowTemplate() : demoTemplate(plan.framework);
12666
+ await writeFile(abs, content, "utf-8");
12667
+ written.push(entry.path);
12307
12668
  }
12308
- return parsed;
12669
+ return written;
12309
12670
  }
12310
- async function newestTraceMtimeMs2(traceDir) {
12311
- let newest = 0;
12671
+ async function initCommand(options = {}) {
12672
+ const cwd = path13.resolve(options.cwd ?? process.cwd());
12312
12673
  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 {
12674
+ const plan = await planInit({ ...options, cwd });
12675
+ const toWrite = plan.files.filter((file) => file.action === "create").map((f) => f.path);
12676
+ const skipped = plan.files.filter((file) => file.action === "skip");
12677
+ if (options.json) {
12678
+ const payload = {
12679
+ ok: true,
12680
+ version,
12681
+ framework: plan.framework,
12682
+ ci: plan.ci ?? null,
12683
+ dryRun: options.dryRun === true,
12684
+ planned: plan.files,
12685
+ wouldWrite: options.dryRun ? toWrite : void 0
12686
+ };
12687
+ console.log(JSON.stringify(payload, null, 2));
12688
+ if (options.dryRun) return;
12689
+ }
12690
+ const written = await writePlannedFiles(plan, cwd, options);
12691
+ if (!options.json) {
12692
+ console.log("AgentInspect init");
12693
+ console.log(`Framework: ${plan.framework}`);
12694
+ console.log(`Trace directory: ${TRACE_DIR}/`);
12695
+ if (options.dryRun) {
12696
+ console.log("Dry run \u2014 would create:");
12697
+ for (const file of toWrite) console.log(`- ${file}`);
12698
+ for (const file of skipped) {
12699
+ console.log(`- ${file.path} (skip: ${file.reason ?? "exists"})`);
12700
+ }
12701
+ return;
12702
+ }
12703
+ console.log("Created:");
12704
+ for (const file of written) console.log(`- ${file}`);
12705
+ for (const file of skipped) {
12706
+ console.log(`- ${file.path} (skipped: ${file.reason ?? "exists"})`);
12320
12707
  }
12708
+ console.log("\nNext: run your demo, then `npx agent-inspect list --dir .agent-inspect`");
12709
+ console.log("No dependencies were installed. Add packages manually when ready.");
12321
12710
  }
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) {
12711
+ } catch (error) {
12712
+ const msg = error instanceof Error ? error.message : String(error);
12370
12713
  if (options.json) {
12371
- console.log(JSON.stringify({ ok: false, reason: "index-missing", dbPath }, null, 2));
12714
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
12372
12715
  } else {
12373
- console.log("No usable SQLite index. Run: agent-inspect index sqlite build");
12716
+ console.error(`[AgentInspect] init failed: ${msg}`);
12374
12717
  }
12375
12718
  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
12719
  }
12414
- console.log(`Removed SQLite index: ${dbPath}`);
12415
12720
  }
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"
12721
+ var OPTIONAL_PACKAGES = {
12722
+ custom: [],
12723
+ "ai-sdk": ["@agent-inspect/ai-sdk"],
12724
+ "openai-agents": ["@agent-inspect/openai-agents"],
12725
+ langchain: ["@agent-inspect/langchain"]
12429
12726
  };
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
- );
12727
+ function nodeVersionCheck() {
12728
+ const major = Number(process2.versions.node.split(".")[0]);
12729
+ if (Number.isNaN(major) || major < 20) {
12730
+ return {
12731
+ id: "node-version",
12732
+ status: "fail",
12733
+ message: `Node ${process2.versions.node} is below the supported minimum (20).`,
12734
+ remediation: "Upgrade to Node 20 LTS or newer.",
12735
+ evidence: process2.versions.node
12736
+ };
12479
12737
  }
12738
+ return {
12739
+ id: "node-version",
12740
+ status: "pass",
12741
+ message: `Node ${process2.versions.node} meets the minimum (>=20).`,
12742
+ evidence: process2.versions.node
12743
+ };
12480
12744
  }
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
- );
12745
+ function envCheck(name, optional = true) {
12746
+ const value = process2.env[name];
12747
+ if (value === void 0 || value.trim() === "") {
12748
+ return {
12749
+ id: `env-${name.toLowerCase()}`,
12750
+ status: optional ? "skipped" : "warn",
12751
+ message: `${name} is not set.`,
12752
+ remediation: optional ? void 0 : `Set ${name}=1 to enable tracing.`
12753
+ };
12496
12754
  }
12497
- if (errors.length > 0) return void 0;
12498
12755
  return {
12499
- enabled: value.enabled,
12500
- type: value.type,
12501
- ...value.path !== void 0 ? { path: value.path } : {}
12756
+ id: `env-${name.toLowerCase()}`,
12757
+ status: "pass",
12758
+ message: `${name} is set.`,
12759
+ evidence: value
12502
12760
  };
12503
12761
  }
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");
12762
+ async function traceDirWritable(traceDir) {
12763
+ const resolved = path13.resolve(traceDir);
12764
+ try {
12765
+ await mkdir(resolved, { recursive: true });
12766
+ await access(resolved, constants.W_OK);
12767
+ return {
12768
+ id: "trace-dir-writable",
12769
+ status: "pass",
12770
+ message: `Trace directory is writable: ${resolved}`,
12771
+ evidence: resolved
12772
+ };
12773
+ } catch (error) {
12774
+ return {
12775
+ id: "trace-dir-writable",
12776
+ status: "fail",
12777
+ message: `Trace directory is not writable: ${resolved}`,
12778
+ remediation: "Create the directory or set AGENT_INSPECT_TRACE_DIR to a writable path.",
12779
+ evidence: error instanceof Error ? error.message : String(error)
12780
+ };
12519
12781
  }
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");
12782
+ }
12783
+ function resolvePackage(cwd, name) {
12784
+ const require2 = createRequire(path13.join(cwd, "package.json"));
12785
+ try {
12786
+ const pkgPath = require2.resolve(`${name}/package.json`);
12787
+ const pkg = require2(pkgPath);
12788
+ return { ok: true, version: pkg.version };
12789
+ } catch {
12790
+ return { ok: false };
12524
12791
  }
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
- }
12792
+ }
12793
+ function importSmoke(cwd) {
12794
+ const results = [];
12795
+ const require2 = createRequire(path13.join(cwd, "package.json"));
12796
+ try {
12797
+ require2.resolve("agent-inspect");
12798
+ results.push({
12799
+ id: "import-agent-inspect",
12800
+ status: "pass",
12801
+ message: "agent-inspect resolves from the current project."
12802
+ });
12803
+ } catch {
12804
+ results.push({
12805
+ id: "import-agent-inspect",
12806
+ status: "warn",
12807
+ message: "agent-inspect is not installed in the current project.",
12808
+ remediation: "Run npm install agent-inspect (or pnpm add agent-inspect)."
12536
12809
  });
12537
12810
  }
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(", ")}`);
12811
+ try {
12812
+ require2.resolve("agent-inspect/package.json");
12813
+ results.push({
12814
+ id: "import-agent-inspect-cjs",
12815
+ status: "pass",
12816
+ message: "CJS resolution for agent-inspect succeeded."
12817
+ });
12818
+ } catch {
12819
+ results.push({
12820
+ id: "import-agent-inspect-cjs",
12821
+ status: "skipped",
12822
+ message: "CJS resolution check skipped (package not installed locally)."
12823
+ });
12544
12824
  }
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`);
12825
+ return results;
12826
+ }
12827
+ function optionalPackageChecks(cwd, framework) {
12828
+ const packages = framework !== void 0 ? OPTIONAL_PACKAGES[framework] : [
12829
+ "@agent-inspect/ai-sdk",
12830
+ "@agent-inspect/openai-agents",
12831
+ "@agent-inspect/langchain",
12832
+ "@agent-inspect/redact",
12833
+ "@agent-inspect/eval"
12834
+ ];
12835
+ return packages.map((name) => {
12836
+ const resolved = resolvePackage(cwd, name);
12837
+ if (!resolved.ok) {
12838
+ return {
12839
+ id: `optional-package-${name}`,
12840
+ status: framework !== void 0 ? "warn" : "skipped",
12841
+ message: `${name} is not installed.`,
12842
+ remediation: framework !== void 0 ? `npm install ${name}` : void 0
12843
+ };
12844
+ }
12845
+ return {
12846
+ id: `optional-package-${name}`,
12847
+ status: "pass",
12848
+ message: `${name} is available.`,
12849
+ evidence: resolved.version
12850
+ };
12851
+ });
12852
+ }
12853
+ function versionMismatchCheck(cwd) {
12854
+ const root = resolvePackage(cwd, "agent-inspect");
12855
+ if (!root.ok || root.version === void 0) {
12856
+ return {
12857
+ id: "version-alignment",
12858
+ status: "skipped",
12859
+ message: "Skipped version alignment \u2014 agent-inspect not installed locally."
12860
+ };
12550
12861
  }
12551
- if (errors.length > 0 || index === void 0) {
12552
- return { ok: false, errors, warnings };
12862
+ if (root.version !== version) {
12863
+ return {
12864
+ id: "version-alignment",
12865
+ status: "warn",
12866
+ message: `CLI version ${version} differs from local agent-inspect@${root.version}.`,
12867
+ remediation: "Align versions with npm install agent-inspect@latest",
12868
+ evidence: `cli=${version};local=${root.version}`
12869
+ };
12553
12870
  }
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
12871
+ return {
12872
+ id: "version-alignment",
12873
+ status: "pass",
12874
+ message: `CLI and local agent-inspect are aligned at ${version}.`
12565
12875
  };
12566
- return { ok: true, manifest, errors, warnings };
12567
12876
  }
12568
- function parseWorkspaceManifest(json) {
12569
- const warnings = [];
12570
- if (typeof json !== "string") {
12571
- return { ok: false, errors: ["manifest input must be a string"], warnings };
12877
+ async function runDoctorChecks(options = {}) {
12878
+ const cwd = path13.resolve(options.cwd ?? process2.cwd());
12879
+ const traceDir = options.traceDir?.trim() || process2.env.AGENT_INSPECT_TRACE_DIR?.trim() || ".agent-inspect";
12880
+ const checks2 = [
12881
+ nodeVersionCheck(),
12882
+ {
12883
+ id: "cli-version",
12884
+ status: "pass",
12885
+ message: `agent-inspect CLI ${version}`,
12886
+ evidence: version
12887
+ },
12888
+ await traceDirWritable(traceDir),
12889
+ envCheck("AGENT_INSPECT"),
12890
+ envCheck("AGENT_INSPECT_TRACE_DIR"),
12891
+ versionMismatchCheck(cwd)
12892
+ ];
12893
+ if (options.checkImports !== false) {
12894
+ checks2.push(...importSmoke(cwd));
12572
12895
  }
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
- };
12896
+ checks2.push(...optionalPackageChecks(cwd, options.framework));
12897
+ return checks2.sort((a, b) => a.id.localeCompare(b.id));
12898
+ }
12899
+ async function doctorCommand(options = {}) {
12900
+ const checks2 = await runDoctorChecks(options);
12901
+ const failed = checks2.filter((check) => check.status === "fail").length;
12902
+ const warned = checks2.filter((check) => check.status === "warn").length;
12903
+ if (options.json) {
12904
+ console.log(
12905
+ JSON.stringify(
12906
+ {
12907
+ ok: failed === 0,
12908
+ version,
12909
+ summary: { pass: checks2.filter((c) => c.status === "pass").length, warn: warned, fail: failed },
12910
+ checks: checks2
12911
+ },
12912
+ null,
12913
+ 2
12914
+ )
12915
+ );
12916
+ if (failed > 0) process2.exitCode = 1;
12917
+ return;
12581
12918
  }
12582
- let parsed;
12583
- try {
12584
- parsed = JSON.parse(json);
12585
- } catch {
12586
- return { ok: false, errors: ["manifest is not valid JSON"], warnings };
12919
+ console.log("AgentInspect doctor");
12920
+ for (const check of checks2) {
12921
+ const tag = check.status.toUpperCase();
12922
+ console.log(`[${tag}] ${check.id}: ${check.message}`);
12923
+ if (check.remediation) console.log(` \u2192 ${check.remediation}`);
12587
12924
  }
12588
- return validateWorkspaceManifest(parsed);
12589
- }
12590
- function serializeWorkspaceManifest(manifest) {
12591
- return `${JSON.stringify(manifest, null, 2)}
12592
- `;
12925
+ console.log(`
12926
+ Summary: ${failed} failed, ${warned} warnings`);
12927
+ if (failed > 0) process2.exitCode = 1;
12593
12928
  }
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
- };
12929
+
12930
+ // packages/adapter-sdk/src/indexer.ts
12931
+ function defineIndexer(indexer) {
12932
+ if (!indexer.id.trim()) throw new Error("indexer id is required");
12933
+ return indexer;
12603
12934
  }
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;
12935
+ async function indexIsStale(snapshot, traceDir) {
12936
+ const builtMs = Date.parse(snapshot.builtAt);
12937
+ if (Number.isNaN(builtMs)) return true;
12938
+ const td = new TraceDirectory({ dir: traceDir });
12939
+ const files = await td.list();
12940
+ for (const file of files) {
12941
+ const stats = await td.getFileStats(file);
12942
+ if (stats.mtimeMs > builtMs) return true;
12610
12943
  }
12611
- throw new Error(
12612
- `Workspace path "${relative}" resolves outside the workspace directory`
12613
- );
12944
+ return false;
12614
12945
  }
12615
- async function pathExists(p) {
12616
- try {
12617
- await access(p);
12618
- return true;
12619
- } catch {
12620
- return false;
12621
- }
12946
+ function createTraceDirectoryIndexer() {
12947
+ return defineIndexer({
12948
+ id: "trace-directory-metadata",
12949
+ async rebuild(traceDir, options = {}) {
12950
+ const warnings = [];
12951
+ const td = new TraceDirectory({ dir: traceDir });
12952
+ const files = await td.list();
12953
+ const maxEntries = options.maxEntries ?? 1e4;
12954
+ if (files.length > maxEntries) {
12955
+ warnings.push(
12956
+ `indexer.truncated: trace directory has ${files.length} files; indexing first ${maxEntries}`
12957
+ );
12958
+ }
12959
+ const slice = files.slice(0, maxEntries);
12960
+ const metas = await loadTraceMetadataList(
12961
+ traceDir,
12962
+ slice,
12963
+ (fileName) => td.getPath(fileName)
12964
+ );
12965
+ const entries = metas.map((meta) => ({
12966
+ runId: meta.runId,
12967
+ path: meta.filePath,
12968
+ name: meta.name,
12969
+ startedAt: meta.startedAt,
12970
+ status: meta.status
12971
+ })).sort((a, b) => a.runId.localeCompare(b.runId));
12972
+ if (entries.length < slice.length) {
12973
+ warnings.push(
12974
+ `indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
12975
+ );
12976
+ }
12977
+ return {
12978
+ traceDir,
12979
+ builtAt: (/* @__PURE__ */ new Date()).toISOString(),
12980
+ entries,
12981
+ warnings
12982
+ };
12983
+ }
12984
+ });
12622
12985
  }
12623
- async function isWritable(p) {
12624
- try {
12625
- await access(p, constants$1.W_OK);
12626
- return true;
12627
- } catch {
12628
- return false;
12986
+
12987
+ // packages/cli/src/index-cmd.ts
12988
+ var INDEX_FILENAME = ".agent-inspect-index.json";
12989
+ function traceIndexPath(traceDir) {
12990
+ return path13.join(traceDir, INDEX_FILENAME);
12991
+ }
12992
+ function parseMaxEntries(raw) {
12993
+ if (raw === void 0 || raw.trim() === "") return void 0;
12994
+ const parsed = Number.parseInt(raw, 10);
12995
+ if (!Number.isFinite(parsed) || parsed <= 0) {
12996
+ throw new Error("--max-entries must be a positive integer.");
12629
12997
  }
12998
+ return parsed;
12630
12999
  }
12631
- async function listJsonl(dir) {
13000
+ async function readSnapshot(indexPath2) {
12632
13001
  try {
12633
- const entries = await readdir(dir);
12634
- return entries.filter((f) => f.endsWith(".jsonl"));
13002
+ const raw = await readFile(indexPath2, "utf8");
13003
+ return JSON.parse(raw);
12635
13004
  } catch {
12636
- return [];
13005
+ return void 0;
12637
13006
  }
12638
13007
  }
12639
- async function countFiles(dir) {
13008
+ async function indexBuildCommand(options = {}) {
12640
13009
  try {
12641
- const entries = await readdir(dir, { withFileTypes: true });
12642
- return entries.filter((e) => e.isFile()).length;
12643
- } catch {
12644
- return 0;
13010
+ const traceDir = resolveTraceDir({ dir: options.dir });
13011
+ await mkdir(traceDir, { recursive: true });
13012
+ const indexer = createTraceDirectoryIndexer();
13013
+ const snapshot = await indexer.rebuild(traceDir, {
13014
+ maxEntries: parseMaxEntries(options.maxEntries)
13015
+ });
13016
+ const indexPath2 = traceIndexPath(traceDir);
13017
+ await writeFile(indexPath2, `${JSON.stringify(snapshot, null, 2)}
13018
+ `, "utf8");
13019
+ if (options.json) {
13020
+ console.log(JSON.stringify({ ok: true, indexPath: indexPath2, ...snapshot }, null, 2));
13021
+ return;
13022
+ }
13023
+ console.log(`Built trace index: ${indexPath2}`);
13024
+ console.log(`Entries: ${snapshot.entries.length}`);
13025
+ if (snapshot.warnings.length > 0) {
13026
+ for (const warning of snapshot.warnings) {
13027
+ console.log(`warning: ${warning}`);
13028
+ }
13029
+ }
13030
+ } catch (e) {
13031
+ const msg = e instanceof Error ? e.message : String(e);
13032
+ console.error(`[AgentInspect] index build failed: ${msg}`);
13033
+ process.exitCode = 1;
12645
13034
  }
12646
13035
  }
12647
- async function readWorkspaceManifestFile(location) {
12648
- let raw;
13036
+ async function indexStatusCommand(options = {}) {
12649
13037
  try {
12650
- raw = await readFile(location.manifestPath, "utf-8");
12651
- } catch {
12652
- return { exists: false, ok: false, errors: ["workspace.json not found"], warnings: [] };
13038
+ const traceDir = resolveTraceDir({ dir: options.dir });
13039
+ const indexPath2 = traceIndexPath(traceDir);
13040
+ const snapshot = await readSnapshot(indexPath2);
13041
+ if (!snapshot) {
13042
+ const payload2 = { ok: true, exists: false, indexPath: indexPath2, traceDir, stale: true };
13043
+ if (options.json) {
13044
+ console.log(JSON.stringify(payload2, null, 2));
13045
+ } else {
13046
+ console.log(`No index at ${indexPath2}`);
13047
+ console.log("Run: agent-inspect index build");
13048
+ }
13049
+ return;
13050
+ }
13051
+ const stale = await indexIsStale(snapshot, traceDir);
13052
+ const payload = {
13053
+ ok: true,
13054
+ exists: true,
13055
+ indexPath: indexPath2,
13056
+ traceDir,
13057
+ stale,
13058
+ builtAt: snapshot.builtAt,
13059
+ entries: snapshot.entries.length,
13060
+ warnings: snapshot.warnings
13061
+ };
13062
+ if (options.json) {
13063
+ console.log(JSON.stringify(payload, null, 2));
13064
+ return;
13065
+ }
13066
+ console.log(`Index: ${indexPath2}`);
13067
+ console.log(`Built: ${snapshot.builtAt}`);
13068
+ console.log(`Entries: ${snapshot.entries.length}`);
13069
+ console.log(`Stale: ${stale ? "yes" : "no"}`);
13070
+ if (snapshot.warnings.length > 0) {
13071
+ for (const warning of snapshot.warnings) {
13072
+ console.log(`warning: ${warning}`);
13073
+ }
13074
+ }
13075
+ } catch (e) {
13076
+ const msg = e instanceof Error ? e.message : String(e);
13077
+ console.error(`[AgentInspect] index status failed: ${msg}`);
13078
+ process.exitCode = 1;
12653
13079
  }
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
13080
  }
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
- };
13081
+ async function indexCleanCommand(options = {}) {
13082
+ try {
13083
+ const traceDir = resolveTraceDir({ dir: options.dir });
13084
+ const indexPath2 = traceIndexPath(traceDir);
13085
+ await rm(indexPath2, { force: true });
13086
+ if (options.json) {
13087
+ console.log(JSON.stringify({ ok: true, removed: indexPath2 }, null, 2));
13088
+ return;
13089
+ }
13090
+ console.log(`Removed index: ${indexPath2}`);
13091
+ } catch (e) {
13092
+ const msg = e instanceof Error ? e.message : String(e);
13093
+ console.error(`[AgentInspect] index clean failed: ${msg}`);
13094
+ process.exitCode = 1;
13095
+ }
12715
13096
  }
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);
13097
+ var PACKAGE = "@agent-inspect/index-sqlite";
13098
+ function isModuleNotFound3(e) {
13099
+ return e !== null && typeof e === "object" && "code" in e && (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND");
13100
+ }
13101
+ async function loadIndexSqlite() {
13102
+ try {
13103
+ return await import('./src-YFMPWEIS.mjs');
13104
+ } catch (e) {
13105
+ if (isModuleNotFound3(e)) {
13106
+ console.error(
13107
+ `The optional SQLite index is not installed. Run: npm install ${PACKAGE}`
13108
+ );
13109
+ process.exitCode = 1;
13110
+ return null;
13111
+ }
13112
+ const msg = e instanceof Error ? e.message : String(e);
13113
+ console.error(`[AgentInspect] failed to load ${PACKAGE}: ${msg}`);
13114
+ process.exitCode = 1;
13115
+ return null;
12724
13116
  }
12725
- return out;
12726
13117
  }
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;
13118
+ function parsePositiveInt(raw, flag) {
13119
+ if (raw === void 0 || raw.trim() === "") return void 0;
13120
+ const parsed = Number.parseInt(raw, 10);
13121
+ if (!Number.isFinite(parsed) || parsed <= 0) {
13122
+ throw new Error(`${flag} must be a positive integer.`);
12732
13123
  }
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)
13124
+ return parsed;
13125
+ }
13126
+ async function newestTraceMtimeMs2(traceDir) {
13127
+ let newest = 0;
13128
+ try {
13129
+ const files = await readdir(traceDir);
13130
+ for (const file of files) {
13131
+ if (!file.endsWith(".jsonl")) continue;
13132
+ try {
13133
+ const s = await stat(path13.join(traceDir, file));
13134
+ if (s.mtimeMs > newest) newest = s.mtimeMs;
13135
+ } catch {
13136
+ }
12757
13137
  }
12758
- };
13138
+ } catch {
13139
+ }
13140
+ return newest;
12759
13141
  }
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 };
13142
+ async function indexSqliteBuildCommand(options = {}) {
13143
+ const mod = await loadIndexSqlite();
13144
+ if (!mod) return;
13145
+ const result = await mod.buildIndex({
13146
+ traceDir: options.dir,
13147
+ maxRuns: parsePositiveInt(options.maxRuns, "--max-runs")
13148
+ });
13149
+ if (options.json) {
13150
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
13151
+ return;
12770
13152
  }
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 };
13153
+ console.log(`Built SQLite index: ${result.dbPath}`);
13154
+ console.log(`Runs: ${result.runs} Steps: ${result.steps} Errors: ${result.errors}`);
13155
+ for (const warning of result.warnings) console.log(`warning: ${warning}`);
13156
+ }
13157
+ async function indexSqliteStatusCommand(options = {}) {
13158
+ const mod = await loadIndexSqlite();
13159
+ if (!mod) return;
13160
+ const traceDir = resolveTraceDir({ dir: options.dir });
13161
+ const dbPath = mod.resolveIndexDbPath(traceDir);
13162
+ const status = mod.indexStatus(dbPath);
13163
+ const stale = mod.isIndexStale(dbPath, await newestTraceMtimeMs2(traceDir));
13164
+ if (options.json) {
13165
+ console.log(JSON.stringify({ ok: true, traceDir, stale, ...status }, null, 2));
13166
+ return;
12778
13167
  }
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 });
13168
+ if (!status.exists) {
13169
+ console.log(`No SQLite index at ${dbPath}`);
13170
+ console.log("Run: agent-inspect index sqlite build");
13171
+ return;
12783
13172
  }
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` });
13173
+ console.log(`Index: ${status.dbPath}`);
13174
+ console.log(`Healthy: ${status.healthy ? "yes" : "no"}`);
13175
+ console.log(`Built: ${status.builtAt ?? "unknown"}`);
13176
+ console.log(`Runs: ${status.runs} Steps: ${status.steps}`);
13177
+ console.log(`Stale: ${stale ? "yes" : "no"}`);
13178
+ }
13179
+ async function indexSqliteQueryCommand(options = {}) {
13180
+ const mod = await loadIndexSqlite();
13181
+ if (!mod) return;
13182
+ const traceDir = resolveTraceDir({ dir: options.dir });
13183
+ const dbPath = mod.resolveIndexDbPath(traceDir);
13184
+ const status = mod.indexStatus(dbPath);
13185
+ if (!status.exists || !status.healthy) {
13186
+ if (options.json) {
13187
+ console.log(JSON.stringify({ ok: false, reason: "index-missing", dbPath }, null, 2));
12807
13188
  } else {
12808
- checks2.push({ id, status: "pass", message: `${rel}/ is present and writable` });
13189
+ console.log("No usable SQLite index. Run: agent-inspect index sqlite build");
12809
13190
  }
13191
+ process.exitCode = 1;
13192
+ return;
12810
13193
  }
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
- }
13194
+ const rows = mod.queryRuns(dbPath, {
13195
+ status: options.status,
13196
+ sessionId: options.session,
13197
+ name: options.name,
13198
+ kind: options.kind,
13199
+ tool: options.tool,
13200
+ limit: parsePositiveInt(options.limit, "--limit")
13201
+ });
13202
+ if (options.json) {
13203
+ console.log(JSON.stringify({ ok: true, count: rows.length, runs: rows }, null, 2));
13204
+ return;
12822
13205
  }
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
- }
13206
+ if (rows.length === 0) {
13207
+ console.log("No matching runs.");
13208
+ return;
13209
+ }
13210
+ for (const run of rows) {
13211
+ const parts = [
13212
+ run.runId,
13213
+ run.status ?? "unknown",
13214
+ run.name ?? "",
13215
+ run.durationMs != null ? `${run.durationMs}ms` : ""
13216
+ ].filter((p) => p !== "");
13217
+ console.log(parts.join(" "));
12839
13218
  }
12840
- const ok = !checks2.some((c) => c.status === "fail");
12841
- return { ok, checks: checks2 };
12842
13219
  }
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
- }
13220
+ async function indexSqliteCleanCommand(options = {}) {
13221
+ const mod = await loadIndexSqlite();
13222
+ if (!mod) return;
13223
+ const traceDir = resolveTraceDir({ dir: options.dir });
13224
+ const dbPath = mod.resolveIndexDbPath(traceDir);
13225
+ await mod.cleanIndex(dbPath);
13226
+ if (options.json) {
13227
+ console.log(JSON.stringify({ ok: true, removed: dbPath }, null, 2));
13228
+ return;
12867
13229
  }
12868
- return { dryRun, removed };
13230
+ console.log(`Removed SQLite index: ${dbPath}`);
12869
13231
  }
12870
13232
 
12871
13233
  // packages/cli/src/workspace.ts
@@ -13217,6 +13579,11 @@ function createCliProgram() {
13217
13579
  ).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
13580
  runCommand(() => artifactsCommand(target, opts));
13219
13581
  });
13582
+ 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(
13583
+ new Option("--profile <profile>", "redaction profile for exported copies").choices(["local", "share", "strict"]).default("share")
13584
+ ).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) => {
13585
+ runCommand(() => bundleCommand(runId, opts));
13586
+ });
13220
13587
  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
13588
  runCommand(() => ciSummaryCommand(manifest, opts));
13222
13589
  });
@@ -13421,9 +13788,9 @@ function isPrimaryModule() {
13421
13788
  if (!entry) return false;
13422
13789
  const selfPath = fileURLToPath(import.meta.url);
13423
13790
  try {
13424
- return realpathSync(path11.resolve(entry)) === realpathSync(path11.resolve(selfPath));
13791
+ return realpathSync(path13.resolve(entry)) === realpathSync(path13.resolve(selfPath));
13425
13792
  } catch {
13426
- return path11.resolve(entry) === path11.resolve(selfPath);
13793
+ return path13.resolve(entry) === path13.resolve(selfPath);
13427
13794
  }
13428
13795
  }
13429
13796
  if (isPrimaryModule()) {