agent-inspect 4.1.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, TraceDirectory, parseDuration, extractMetadata, filterTraces, truncateName, getTraceFilePath, buildRunSummary, formatDuration, formatTimestamp, isAgentInspectTrace, loadTraceMetadataList, loadSessionRunRecords, filterMetasBySessionScope, aggregateSessionCheckResults, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, persistedInspectEventsToTraceEvents, renderStepLine, renderErrorLine, getIndent, Redactor, validateEvent, isPersistedInspectEvent, buildSessionIndex, nanoid, resolveRedactionProfile, applyProfileMetadataCaps, extractCorrelationMetadata, truncateStringForProfile, parseTraceJsonl } from './chunk-MT5G7JFO.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 path10 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.1.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) => path10.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() !== "" ? path10.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(path10.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 path17 = 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: path17,
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: path17,
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: path17,
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: path17,
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: path17,
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: path17,
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: path17,
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(path17) {
5448
- if (path17 === void 0 || path17.path.length === 0) {
5449
+ function formatPath(path19) {
5450
+ if (path19 === void 0 || path19.path.length === 0) {
5449
5451
  return "(run)";
5450
5452
  }
5451
- return path17.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)";
@@ -5810,9 +5812,28 @@ async function searchCommand(options = {}) {
5810
5812
  process.exitCode = 1;
5811
5813
  }
5812
5814
  }
5813
-
5814
- // packages/cli/src/sessions.ts
5815
- async function loadSessionIndex(traceDir, correlateGroup) {
5815
+ function isModuleNotFound2(e) {
5816
+ return e !== null && typeof e === "object" && "code" in e && (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND");
5817
+ }
5818
+ function toStatus(raw) {
5819
+ if (raw === "success" || raw === "error" || raw === "running") return raw;
5820
+ return "unknown";
5821
+ }
5822
+ function indexedToMetadata(row, traceDir) {
5823
+ return {
5824
+ runId: row.runId,
5825
+ name: row.name ?? void 0,
5826
+ status: toStatus(row.status),
5827
+ startedAt: row.startedAt ?? void 0,
5828
+ endedAt: row.endedAt ?? void 0,
5829
+ durationMs: row.durationMs ?? void 0,
5830
+ eventCount: 0,
5831
+ filePath: path13.join(traceDir, row.file),
5832
+ fileSize: 0,
5833
+ createdAt: new Date(row.mtimeMs)
5834
+ };
5835
+ }
5836
+ async function loadFromScan(traceDir) {
5816
5837
  const td = new TraceDirectory({ dir: traceDir });
5817
5838
  const files = await td.list();
5818
5839
  const metas = await loadTraceMetadataList(
@@ -5820,12 +5841,77 @@ async function loadSessionIndex(traceDir, correlateGroup) {
5820
5841
  files,
5821
5842
  (fileName) => td.getPath(fileName)
5822
5843
  );
5823
- const runs = await loadSessionRunRecords(metas);
5824
- return buildSessionIndex(runs, { correlateByGroupId: correlateGroup === true });
5844
+ return loadSessionRunRecords(metas);
5845
+ }
5846
+ async function newestTraceMtimeMs(traceDir) {
5847
+ const td = new TraceDirectory({ dir: traceDir });
5848
+ let newest = 0;
5849
+ for (const file of await td.list()) {
5850
+ try {
5851
+ const stats = await td.getFileStats(file);
5852
+ if (stats.mtimeMs > newest) newest = stats.mtimeMs;
5853
+ } catch {
5854
+ }
5855
+ }
5856
+ return newest;
5857
+ }
5858
+ async function loadSessionRuns(traceDir) {
5859
+ try {
5860
+ const mod = await import('./src-YFMPWEIS.mjs');
5861
+ const dbPath = mod.resolveIndexDbPath(traceDir);
5862
+ const status = mod.indexStatus(dbPath);
5863
+ if (!status.healthy) {
5864
+ return { runs: await loadFromScan(traceDir), source: "scan" };
5865
+ }
5866
+ const newest = await newestTraceMtimeMs(traceDir);
5867
+ if (mod.isIndexStale(dbPath, newest)) {
5868
+ return { runs: await loadFromScan(traceDir), source: "scan" };
5869
+ }
5870
+ const indexed = mod.queryRuns(dbPath, { limit: 1e4 });
5871
+ if (indexed.length === 0) {
5872
+ return { runs: await loadFromScan(traceDir), source: "scan" };
5873
+ }
5874
+ const runs = [];
5875
+ for (const row of indexed) {
5876
+ runs.push(await enrichSessionRunRecord(indexedToMetadata(row, traceDir)));
5877
+ }
5878
+ runs.sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0));
5879
+ return { runs, source: "index" };
5880
+ } catch (e) {
5881
+ if (!isModuleNotFound2(e)) ;
5882
+ return { runs: await loadFromScan(traceDir), source: "scan" };
5883
+ }
5884
+ }
5885
+
5886
+ // packages/cli/src/sessions.ts
5887
+ async function loadSessionIndex(traceDir, options = {}) {
5888
+ const { runs } = await loadSessionRuns(traceDir);
5889
+ const staleThresholdMs = options.staleAfter && options.staleAfter.trim() !== "" ? parseDuration(options.staleAfter.trim()) : void 0;
5890
+ return buildSessionIndex(runs, {
5891
+ correlateByGroupId: options.correlateGroup === true,
5892
+ staleThresholdMs
5893
+ });
5825
5894
  }
5826
5895
  function findSession(index, sessionId) {
5827
5896
  return index.sessions.find((session) => session.sessionId === sessionId);
5828
5897
  }
5898
+ function latestSession(index) {
5899
+ if (index.sessions.length === 0) return void 0;
5900
+ return [...index.sessions].sort(
5901
+ (a, b) => Date.parse(b.lastActivity) - Date.parse(a.lastActivity)
5902
+ )[0];
5903
+ }
5904
+ function parseSinceCutoff(since) {
5905
+ if (!since || since.trim() === "") return void 0;
5906
+ return Date.now() - parseDuration(since.trim());
5907
+ }
5908
+ function sessionsInSinceWindow(index, since) {
5909
+ const cutoff = parseSinceCutoff(since);
5910
+ if (cutoff === void 0) return index.sessions;
5911
+ return index.sessions.filter(
5912
+ (session) => Date.parse(session.lastActivity) >= cutoff
5913
+ );
5914
+ }
5829
5915
  function renderSessionsHuman(index, traceDir) {
5830
5916
  if (index.sessions.length === 0) {
5831
5917
  console.log("No sessions found");
@@ -5839,13 +5925,9 @@ function renderSessionsHuman(index, traceDir) {
5839
5925
  }
5840
5926
  console.log("Sessions:");
5841
5927
  for (const session of index.sessions) {
5842
- const firstRun = index.runs.find(
5843
- (run) => session.runIds.includes(run.runId)
5844
- );
5845
- const workflowName = firstRun?.metadata && typeof firstRun.metadata.workflowName === "string" ? firstRun.metadata.workflowName : void 0;
5846
- const suffix = workflowName ? ` workflow=${workflowName}` : "";
5928
+ const suffix = session.workflowId ? ` workflow=${session.workflowId}` : "";
5847
5929
  console.log(
5848
- ` ${session.sessionId} (${session.runIds.length} run${session.runIds.length === 1 ? "" : "s"})${suffix}`
5930
+ ` ${session.sessionId} [${session.status}] (${session.runIds.length} run${session.runIds.length === 1 ? "" : "s"})${suffix}`
5849
5931
  );
5850
5932
  }
5851
5933
  if (index.unscopedRunIds.length > 0) {
@@ -5857,7 +5939,14 @@ function renderSessionsHuman(index, traceDir) {
5857
5939
  }
5858
5940
  function renderSessionHuman(session, index, options) {
5859
5941
  console.log(`Session: ${session.sessionId}`);
5942
+ console.log(`Status: ${session.status}`);
5860
5943
  console.log(`Runs: ${session.runIds.join(", ")}`);
5944
+ if (session.lastActivity) console.log(`Last activity: ${session.lastActivity}`);
5945
+ if (session.lastError) {
5946
+ console.log(
5947
+ `Last error: ${session.lastError.message} (run ${session.lastError.runId})`
5948
+ );
5949
+ }
5861
5950
  if (session.handoffs.length > 0) {
5862
5951
  console.log("");
5863
5952
  console.log("Handoffs:");
@@ -5902,10 +5991,29 @@ function renderSessionHuman(session, index, options) {
5902
5991
  }
5903
5992
  }
5904
5993
  }
5994
+ function collectHandoffs(index, sessionId) {
5995
+ const sessions = sessionId ? index.sessions.filter((s) => s.sessionId === sessionId) : index.sessions;
5996
+ const out = [];
5997
+ for (const session of sessions) {
5998
+ for (const edge of session.handoffs) {
5999
+ out.push({ ...edge, sessionId: session.sessionId });
6000
+ }
6001
+ }
6002
+ return out.sort((a, b) => {
6003
+ const session = a.sessionId.localeCompare(b.sessionId);
6004
+ if (session !== 0) return session;
6005
+ const from = a.from.localeCompare(b.from);
6006
+ if (from !== 0) return from;
6007
+ return a.to.localeCompare(b.to);
6008
+ });
6009
+ }
5905
6010
  async function sessionsCommand(options = {}) {
5906
6011
  try {
5907
6012
  const traceDir = resolveTraceDir({ dir: options.dir });
5908
- const index = await loadSessionIndex(traceDir, options.correlateGroup);
6013
+ const index = await loadSessionIndex(traceDir, {
6014
+ correlateGroup: options.correlateGroup,
6015
+ staleAfter: options.staleAfter
6016
+ });
5909
6017
  if (options.json) {
5910
6018
  console.log(
5911
6019
  JSON.stringify(
@@ -5928,6 +6036,121 @@ async function sessionsCommand(options = {}) {
5928
6036
  process.exitCode = 1;
5929
6037
  }
5930
6038
  }
6039
+ async function sessionsLatestCommand(options = {}) {
6040
+ try {
6041
+ const traceDir = resolveTraceDir({ dir: options.dir });
6042
+ const index = await loadSessionIndex(traceDir, {
6043
+ correlateGroup: options.correlateGroup,
6044
+ staleAfter: options.staleAfter
6045
+ });
6046
+ const latest = latestSession(index);
6047
+ if (!latest) {
6048
+ if (options.json) {
6049
+ console.log(JSON.stringify({ ok: false, traceDir, reason: "no-sessions" }, null, 2));
6050
+ } else {
6051
+ console.log("No sessions found");
6052
+ console.log(`Trace directory: ${traceDir}`);
6053
+ }
6054
+ process.exitCode = 1;
6055
+ return;
6056
+ }
6057
+ if (options.json) {
6058
+ console.log(JSON.stringify({ ok: true, traceDir, session: latest }, null, 2));
6059
+ return;
6060
+ }
6061
+ console.log(`Latest session: ${latest.sessionId}`);
6062
+ console.log(`Status: ${latest.status}`);
6063
+ console.log(`Last activity: ${latest.lastActivity}`);
6064
+ console.log(`Runs: ${latest.runIds.join(", ")}`);
6065
+ } catch (e) {
6066
+ const msg = e instanceof Error ? e.message : String(e);
6067
+ console.error(`[AgentInspect] sessions latest failed: ${msg}`);
6068
+ process.exitCode = 1;
6069
+ }
6070
+ }
6071
+ async function sessionsActivityCommand(options = {}) {
6072
+ try {
6073
+ const traceDir = resolveTraceDir({ dir: options.dir });
6074
+ const index = await loadSessionIndex(traceDir, {
6075
+ correlateGroup: options.correlateGroup,
6076
+ staleAfter: options.staleAfter
6077
+ });
6078
+ const summary = buildActivitySummary(index, { since: options.since });
6079
+ if (options.json) {
6080
+ console.log(JSON.stringify({ ok: true, traceDir, ...summary }, null, 2));
6081
+ return;
6082
+ }
6083
+ console.log(renderActivitySummaryHuman(summary));
6084
+ } catch (e) {
6085
+ const msg = e instanceof Error ? e.message : String(e);
6086
+ console.error(`[AgentInspect] sessions activity failed: ${msg}`);
6087
+ process.exitCode = 1;
6088
+ }
6089
+ }
6090
+ async function sessionsHandoffsCommand(options = {}) {
6091
+ try {
6092
+ const traceDir = resolveTraceDir({ dir: options.dir });
6093
+ const index = await loadSessionIndex(traceDir, {
6094
+ correlateGroup: options.correlateGroup
6095
+ });
6096
+ const handoffs = collectHandoffs(index, options.session);
6097
+ if (options.json) {
6098
+ console.log(
6099
+ JSON.stringify({ ok: true, traceDir, count: handoffs.length, handoffs }, null, 2)
6100
+ );
6101
+ return;
6102
+ }
6103
+ if (handoffs.length === 0) {
6104
+ console.log("No handoffs found");
6105
+ return;
6106
+ }
6107
+ for (const edge of handoffs) {
6108
+ console.log(
6109
+ `${edge.sessionId}: ${edge.from} -> ${edge.to} (${edge.confidence})`
6110
+ );
6111
+ }
6112
+ } catch (e) {
6113
+ const msg = e instanceof Error ? e.message : String(e);
6114
+ console.error(`[AgentInspect] sessions handoffs failed: ${msg}`);
6115
+ process.exitCode = 1;
6116
+ }
6117
+ }
6118
+ async function sessionsErrorsCommand(options = {}) {
6119
+ try {
6120
+ const traceDir = resolveTraceDir({ dir: options.dir });
6121
+ const index = await loadSessionIndex(traceDir, {
6122
+ correlateGroup: options.correlateGroup,
6123
+ staleAfter: options.staleAfter
6124
+ });
6125
+ const scoped = sessionsInSinceWindow(index, options.since);
6126
+ const errors = scoped.filter((session) => session.status === "error");
6127
+ if (options.json) {
6128
+ console.log(
6129
+ JSON.stringify(
6130
+ { ok: true, traceDir, count: errors.length, sessions: errors },
6131
+ null,
6132
+ 2
6133
+ )
6134
+ );
6135
+ return;
6136
+ }
6137
+ if (errors.length === 0) {
6138
+ console.log("No error sessions found");
6139
+ return;
6140
+ }
6141
+ for (const session of errors) {
6142
+ const detail = session.lastError?.message ?? "error";
6143
+ console.log(`${session.sessionId} ${detail} (${session.runIds.length} runs)`);
6144
+ }
6145
+ } catch (e) {
6146
+ const msg = e instanceof Error ? e.message : String(e);
6147
+ console.error(`[AgentInspect] sessions errors failed: ${msg}`);
6148
+ process.exitCode = 1;
6149
+ }
6150
+ }
6151
+ async function sessionsShowCommand(sessionId, options = {}) {
6152
+ await sessionCommand(sessionId, options);
6153
+ }
5931
6154
  async function sessionCommand(sessionId, options = {}) {
5932
6155
  const id = typeof sessionId === "string" && sessionId.trim() !== "" ? sessionId.trim() : "";
5933
6156
  if (id === "") {
@@ -6081,9 +6304,9 @@ async function reportCommand(runId, options = {}) {
6081
6304
  redactionProfile,
6082
6305
  correlation: !options.noCorrelation
6083
6306
  });
6084
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path10.resolve(options.output.trim()) : void 0;
6307
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
6085
6308
  if (outPath !== void 0) {
6086
- await mkdir(path10.dirname(outPath), { recursive: true });
6309
+ await mkdir(path13.dirname(outPath), { recursive: true });
6087
6310
  await writeFile(outPath, result.content, "utf-8");
6088
6311
  console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
6089
6312
  }
@@ -6330,17 +6553,17 @@ function applyRule(rule, value, replacement) {
6330
6553
  }
6331
6554
  return value;
6332
6555
  }
6333
- function childPath(path17, key) {
6556
+ function childPath(path19, key) {
6334
6557
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
6335
- return path17 ? `${path17}.${key}` : key;
6558
+ return path19 ? `${path19}.${key}` : key;
6336
6559
  }
6337
- return `${path17 || "$"}[${JSON.stringify(key)}]`;
6560
+ return `${path19 || "$"}[${JSON.stringify(key)}]`;
6338
6561
  }
6339
- function indexPath(path17, index) {
6340
- return `${path17 || "$"}[${index}]`;
6562
+ function indexPath(path19, index) {
6563
+ return `${path19 || "$"}[${index}]`;
6341
6564
  }
6342
- function makeFinding(path17, detector, action, matchKind, severity = "warning", preview) {
6343
- return preview === void 0 ? { path: path17, detector, action, severity, matchKind } : { path: path17, 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 };
6344
6567
  }
6345
6568
  function createRedactionProfile(profile = "local") {
6346
6569
  switch (profile) {
@@ -6409,11 +6632,11 @@ var Redactor2 = class {
6409
6632
  #recordFinding(state, finding) {
6410
6633
  if (this.#collectFindings) state.findings.push(finding);
6411
6634
  }
6412
- #redactValue(value, key, path17, depth, state) {
6635
+ #redactValue(value, key, path19, depth, state) {
6413
6636
  if (depth > this.#maxDepth) {
6414
6637
  this.#recordFinding(
6415
6638
  state,
6416
- makeFinding(path17, "structure.maxDepth", "truncate", "value", "warning")
6639
+ makeFinding(path19, "structure.maxDepth", "truncate", "value", "warning")
6417
6640
  );
6418
6641
  return "[Truncated]";
6419
6642
  }
@@ -6422,19 +6645,19 @@ var Redactor2 = class {
6422
6645
  if (rule) {
6423
6646
  this.#recordFinding(
6424
6647
  state,
6425
- makeFinding(path17, `key.${rule.key}`, actionForRule(rule), "key", "warning")
6648
+ makeFinding(path19, `key.${rule.key}`, actionForRule(rule), "key", "warning")
6426
6649
  );
6427
6650
  return applyRule(rule, value, this.#replacement);
6428
6651
  }
6429
6652
  }
6430
6653
  for (const detector of this.#detectors) {
6431
- const detections = detector.detect({ path: path17, key, value });
6654
+ const detections = detector.detect({ path: path19, key, value });
6432
6655
  for (const detection of detections) {
6433
6656
  const action = detection.action ?? "replace";
6434
6657
  this.#recordFinding(
6435
6658
  state,
6436
6659
  makeFinding(
6437
- path17,
6660
+ path19,
6438
6661
  detector.id,
6439
6662
  action,
6440
6663
  detection.matchKind ?? detector.matchKind ?? "custom",
@@ -6452,7 +6675,7 @@ var Redactor2 = class {
6452
6675
  const out = [];
6453
6676
  state.seen.set(value, out);
6454
6677
  value.forEach((item, index) => {
6455
- out[index] = this.#redactValue(item, void 0, indexPath(path17, index), depth + 1, state);
6678
+ out[index] = this.#redactValue(item, void 0, indexPath(path19, index), depth + 1, state);
6456
6679
  });
6457
6680
  return out;
6458
6681
  }
@@ -6464,7 +6687,7 @@ var Redactor2 = class {
6464
6687
  out[entryKey] = this.#redactValue(
6465
6688
  entryValue,
6466
6689
  entryKey,
6467
- childPath(path17 === "$" ? "" : path17, entryKey),
6690
+ childPath(path19 === "$" ? "" : path19, entryKey),
6468
6691
  depth + 1,
6469
6692
  state
6470
6693
  );
@@ -6590,6 +6813,9 @@ function redactDocument(content, profile) {
6590
6813
  return redactJsonlText(content, profile);
6591
6814
  }
6592
6815
  }
6816
+ function redactTraceContent(content, profile) {
6817
+ return redactDocument(content, profile);
6818
+ }
6593
6819
  async function redactCommand(target, options = {}, stdin = process.stdin) {
6594
6820
  const profile = parseRedactionProfile3(options.profile);
6595
6821
  const source = await contentFromTarget(target, options, stdin);
@@ -6911,14 +7137,14 @@ function uniqueSorted(values) {
6911
7137
  return [...new Set(values)].sort();
6912
7138
  }
6913
7139
  function isWithinDirectory(child, parent) {
6914
- const relative = path10.relative(parent, child);
6915
- return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
7140
+ const relative = path13.relative(parent, child);
7141
+ return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
6916
7142
  }
6917
7143
  async function resolveOutputPath(inputPath, output2, force) {
6918
7144
  if (output2 === void 0 || output2.trim() === "") return void 0;
6919
- const inputAbs = path10.resolve(inputPath);
6920
- const outputAbs = path10.resolve(output2.trim());
6921
- const inputDir = path10.dirname(inputAbs);
7145
+ const inputAbs = path13.resolve(inputPath);
7146
+ const outputAbs = path13.resolve(output2.trim());
7147
+ const inputDir = path13.dirname(inputAbs);
6922
7148
  if (!isWithinDirectory(outputAbs, inputDir)) {
6923
7149
  throw new Error("Refusing to write migrated output outside the input directory.");
6924
7150
  }
@@ -7039,7 +7265,7 @@ async function migrateCommand(input3, options = {}) {
7039
7265
  process.exitCode = 1;
7040
7266
  return;
7041
7267
  }
7042
- const inputPath = path10.resolve(input3.trim());
7268
+ const inputPath = path13.resolve(input3.trim());
7043
7269
  const dryRun = options.dryRun === true;
7044
7270
  if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
7045
7271
  console.error("migrate requires --dry-run or --output <path>.");
@@ -7058,7 +7284,7 @@ async function migrateCommand(input3, options = {}) {
7058
7284
  );
7059
7285
  const result = await buildMigration(inputPath, outputPath);
7060
7286
  if (!dryRun && outputPath !== void 0) {
7061
- await mkdir(path10.dirname(outputPath), { recursive: true });
7287
+ await mkdir(path13.dirname(outputPath), { recursive: true });
7062
7288
  await writeFile(outputPath, result.content, "utf-8");
7063
7289
  }
7064
7290
  printSummary2(result, dryRun);
@@ -7336,7 +7562,7 @@ function stripPrefix(name, prefixes) {
7336
7562
  }
7337
7563
  return name;
7338
7564
  }
7339
- function eventEvidence(event, path17) {
7565
+ function eventEvidence(event, path19) {
7340
7566
  return {
7341
7567
  runId: event.runId,
7342
7568
  eventId: event.eventId,
@@ -7346,7 +7572,7 @@ function eventEvidence(event, path17) {
7346
7572
  kind: event.kind,
7347
7573
  name: event.name,
7348
7574
  status: event.status,
7349
- ...path17 ? { path: path17 } : {}
7575
+ ...path19 ? { path: path19 } : {}
7350
7576
  };
7351
7577
  }
7352
7578
  function runEvidence(run) {
@@ -7409,9 +7635,9 @@ function eventEndMs(event) {
7409
7635
  function normalizedKey(value) {
7410
7636
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
7411
7637
  }
7412
- function lastPathSegment(path17) {
7413
- const parts = path17.split(".");
7414
- return parts[parts.length - 1] ?? path17;
7638
+ function lastPathSegment(path19) {
7639
+ const parts = path19.split(".");
7640
+ return parts[parts.length - 1] ?? path19;
7415
7641
  }
7416
7642
  function valueType(value) {
7417
7643
  if (Array.isArray(value)) return "array";
@@ -7425,12 +7651,12 @@ function serializedByteLength(value) {
7425
7651
  return void 0;
7426
7652
  }
7427
7653
  }
7428
- function pushValueEntries(entries, event, value, path17, key, depth = 0) {
7429
- entries.push({ event, path: path17, key, value });
7654
+ function pushValueEntries(entries, event, value, path19, key, depth = 0) {
7655
+ entries.push({ event, path: path19, key, value });
7430
7656
  if (depth >= 8) return;
7431
7657
  if (Array.isArray(value)) {
7432
7658
  for (const [index, item] of value.entries()) {
7433
- pushValueEntries(entries, event, item, `${path17}.${index}`, String(index), depth + 1);
7659
+ pushValueEntries(entries, event, item, `${path19}.${index}`, String(index), depth + 1);
7434
7660
  }
7435
7661
  return;
7436
7662
  }
@@ -7440,7 +7666,7 @@ function pushValueEntries(entries, event, value, path17, key, depth = 0) {
7440
7666
  entries,
7441
7667
  event,
7442
7668
  value[nestedKey],
7443
- `${path17}.${nestedKey}`,
7669
+ `${path19}.${nestedKey}`,
7444
7670
  nestedKey,
7445
7671
  depth + 1
7446
7672
  );
@@ -7521,9 +7747,9 @@ function eventDurationMs(event) {
7521
7747
  }
7522
7748
  function treeShape(nodes) {
7523
7749
  const lines = [];
7524
- const visit = (node, path17) => {
7525
- lines.push(`${path17}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
7526
- node.children.forEach((child, index) => visit(child, `${path17}.${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}`));
7527
7753
  };
7528
7754
  nodes.forEach((node, index) => visit(node, String(index)));
7529
7755
  return lines;
@@ -7572,9 +7798,9 @@ function retrievalShape(context) {
7572
7798
  function guardrailShape(context) {
7573
7799
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
7574
7800
  }
7575
- function firstEvidenceForKind(context, kind, path17) {
7801
+ function firstEvidenceForKind(context, kind, path19) {
7576
7802
  const event = context.events.find((candidate) => candidate.kind === kind);
7577
- return event ? [eventEvidence(event, path17)] : runEvidence(context.selectedRun);
7803
+ return event ? [eventEvidence(event, path19)] : runEvidence(context.selectedRun);
7578
7804
  }
7579
7805
  function baselineDiffFinding(message, evidence, expected, actual) {
7580
7806
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -7924,13 +8150,13 @@ function createStructureCycleRule() {
7924
8150
  const seenCycles = /* @__PURE__ */ new Set();
7925
8151
  const findings = [];
7926
8152
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
7927
- const path17 = [];
8153
+ const path19 = [];
7928
8154
  const seenAt = /* @__PURE__ */ new Map();
7929
8155
  let current = event;
7930
8156
  while (current) {
7931
8157
  const existing = seenAt.get(current.eventId);
7932
8158
  if (existing !== void 0) {
7933
- const cycle = path17.slice(existing);
8159
+ const cycle = path19.slice(existing);
7934
8160
  const key = cycle.map((item) => item.eventId).sort().join("\0");
7935
8161
  if (!seenCycles.has(key)) {
7936
8162
  seenCycles.add(key);
@@ -7946,8 +8172,8 @@ function createStructureCycleRule() {
7946
8172
  }
7947
8173
  break;
7948
8174
  }
7949
- seenAt.set(current.eventId, path17.length);
7950
- path17.push(current);
8175
+ seenAt.set(current.eventId, path19.length);
8176
+ path19.push(current);
7951
8177
  current = current.parentId ? byId.get(current.parentId) : void 0;
7952
8178
  }
7953
8179
  }
@@ -8744,23 +8970,23 @@ function evaluatePromptInjection(text, options = {}) {
8744
8970
  }
8745
8971
  return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
8746
8972
  }
8747
- function validateSchemaField(value, field, path17, evidence) {
8973
+ function validateSchemaField(value, field, path19, evidence) {
8748
8974
  const ruleId = "guardrail.structured-output";
8749
8975
  if (field.type) {
8750
8976
  const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
8751
8977
  if (actual !== field.type) {
8752
- evidence.push({ ruleId, path: path17, preview: `expected ${field.type}, got ${actual}` });
8978
+ evidence.push({ ruleId, path: path19, preview: `expected ${field.type}, got ${actual}` });
8753
8979
  return;
8754
8980
  }
8755
8981
  }
8756
8982
  if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
8757
- evidence.push({ ruleId, path: path17, preview: "value not in enum" });
8983
+ evidence.push({ ruleId, path: path19, preview: "value not in enum" });
8758
8984
  }
8759
8985
  if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
8760
8986
  const record = value;
8761
8987
  for (const key of field.required) {
8762
8988
  if (!(key in record)) {
8763
- evidence.push({ ruleId, path: `${path17}.${key}`, preview: "missing required key" });
8989
+ evidence.push({ ruleId, path: `${path19}.${key}`, preview: "missing required key" });
8764
8990
  }
8765
8991
  }
8766
8992
  }
@@ -9087,7 +9313,7 @@ function asConfig(value) {
9087
9313
  }
9088
9314
  async function loadConfig(configPath) {
9089
9315
  if (configPath === void 0) return {};
9090
- const extension = path10.extname(configPath);
9316
+ const extension = path13.extname(configPath);
9091
9317
  if (TS_CONFIG_EXTENSIONS.has(extension)) {
9092
9318
  throw new Error(
9093
9319
  "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -9096,7 +9322,7 @@ async function loadConfig(configPath) {
9096
9322
  if (!CONFIG_EXTENSIONS.has(extension)) {
9097
9323
  throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
9098
9324
  }
9099
- const absolute = path10.resolve(configPath);
9325
+ const absolute = path13.resolve(configPath);
9100
9326
  if (extension === ".json") {
9101
9327
  const raw = await readFile(absolute, "utf-8");
9102
9328
  return asConfig(JSON.parse(raw));
@@ -9245,10 +9471,10 @@ function printHuman(result) {
9245
9471
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
9246
9472
  }
9247
9473
  for (const finding of result.findings) {
9248
- const path17 = finding.evidence[0]?.path;
9474
+ const path19 = finding.evidence[0]?.path;
9249
9475
  const run = finding.evidence[0]?.runId;
9250
9476
  const runPrefix = run ? `[${run}] ` : "";
9251
- console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path17 ? ` (${path17})` : ""}`);
9477
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
9252
9478
  }
9253
9479
  }
9254
9480
  function readErrorResult(error) {
@@ -9448,7 +9674,7 @@ function createViewerServer(options = {}) {
9448
9674
  return sendJson(res, 200, {
9449
9675
  ok: true,
9450
9676
  readOnly: true,
9451
- traceDir: path10.resolve(traceDir)
9677
+ traceDir: path13.resolve(traceDir)
9452
9678
  });
9453
9679
  }
9454
9680
  const td = new TraceDirectory({ dir: traceDir });
@@ -9466,7 +9692,7 @@ function createViewerServer(options = {}) {
9466
9692
  runId: meta.runId,
9467
9693
  name: meta.name,
9468
9694
  status: meta.status,
9469
- file: path10.basename(meta.filePath),
9695
+ file: path13.basename(meta.filePath),
9470
9696
  startedAt: meta.startedAt,
9471
9697
  durationMs: meta.durationMs
9472
9698
  }))
@@ -9581,7 +9807,7 @@ function startViewerServer(options = {}) {
9581
9807
  resolve({
9582
9808
  host,
9583
9809
  port: resolvedPort,
9584
- traceDir: path10.resolve(traceDir),
9810
+ traceDir: path13.resolve(traceDir),
9585
9811
  url: `http://${host}:${resolvedPort}`
9586
9812
  });
9587
9813
  });
@@ -9786,10 +10012,10 @@ async function evalRun(input3, options = {}) {
9786
10012
  diagnostics: []
9787
10013
  };
9788
10014
  }
9789
- function evidenceForRun(run, path17) {
9790
- return [{ runId: run.runId, ...path17 !== void 0 ? { path: path17 } : {} }];
10015
+ function evidenceForRun(run, path19) {
10016
+ return [{ runId: run.runId, ...path19 !== void 0 ? { path: path19 } : {} }];
9791
10017
  }
9792
- function evidenceForEvent(event, path17) {
10018
+ function evidenceForEvent(event, path19) {
9793
10019
  return [
9794
10020
  {
9795
10021
  runId: event.runId,
@@ -9797,7 +10023,7 @@ function evidenceForEvent(event, path17) {
9797
10023
  ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
9798
10024
  kind: event.kind,
9799
10025
  name: event.name,
9800
- ...path17 !== void 0 ? { path: path17 } : {}
10026
+ ...path19 !== void 0 ? { path: path19 } : {}
9801
10027
  }
9802
10028
  ];
9803
10029
  }
@@ -9955,9 +10181,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
9955
10181
  function tokenize(text) {
9956
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));
9957
10183
  }
9958
- function firstEvidence(fields, run, path17) {
10184
+ function firstEvidence(fields, run, path19) {
9959
10185
  const first = fields[0];
9960
- return first === void 0 ? evidenceForRun(run, path17) : evidenceForEvent(first.node.event, first.path);
10186
+ return first === void 0 ? evidenceForRun(run, path19) : evidenceForEvent(first.node.event, first.path);
9961
10187
  }
9962
10188
  function collectSourceIds(nodes, keys) {
9963
10189
  const wanted = keySet(keys);
@@ -10334,8 +10560,8 @@ function renderEvalMarkdown(result) {
10334
10560
  if (result.findings.length > 0) {
10335
10561
  lines.push("", "## Findings");
10336
10562
  for (const finding of result.findings) {
10337
- const path17 = finding.evidence[0]?.path;
10338
- lines.push(`- ${finding.ruleId}: ${finding.message}${path17 ? ` (${path17})` : ""}`);
10563
+ const path19 = finding.evidence[0]?.path;
10564
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10339
10565
  }
10340
10566
  }
10341
10567
  return `${lines.join("\n")}
@@ -10382,7 +10608,7 @@ function asConfig2(value) {
10382
10608
  }
10383
10609
  async function loadConfig2(configPath) {
10384
10610
  if (configPath === void 0) return {};
10385
- const extension = path10.extname(configPath);
10611
+ const extension = path13.extname(configPath);
10386
10612
  if (TS_CONFIG_EXTENSIONS2.has(extension)) {
10387
10613
  throw new Error(
10388
10614
  "TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -10391,7 +10617,7 @@ async function loadConfig2(configPath) {
10391
10617
  if (!CONFIG_EXTENSIONS2.has(extension)) {
10392
10618
  throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
10393
10619
  }
10394
- const absolute = path10.resolve(configPath);
10620
+ const absolute = path13.resolve(configPath);
10395
10621
  if (extension === ".json") {
10396
10622
  const raw = await readFile(absolute, "utf-8");
10397
10623
  return asConfig2(JSON.parse(raw));
@@ -10528,8 +10754,8 @@ function printHuman2(result) {
10528
10754
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10529
10755
  }
10530
10756
  for (const finding of result.findings) {
10531
- const path17 = finding.evidence[0]?.path;
10532
- console.log(`- ${finding.ruleId}: ${finding.message}${path17 ? ` (${path17})` : ""}`);
10757
+ const path19 = finding.evidence[0]?.path;
10758
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10533
10759
  }
10534
10760
  }
10535
10761
  function readErrorResult2(error) {
@@ -10767,8 +10993,8 @@ function printHuman3(result) {
10767
10993
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10768
10994
  }
10769
10995
  for (const finding of result.findings) {
10770
- const path17 = finding.evidence[0]?.path;
10771
- console.log(`- ${finding.ruleId}: ${finding.message}${path17 ? ` (${path17})` : ""}`);
10996
+ const path19 = finding.evidence[0]?.path;
10997
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
10772
10998
  }
10773
10999
  console.log(`Note: ${result.note}`);
10774
11000
  }
@@ -10814,6 +11040,38 @@ function scanCommand(target, options = {}, stdin = process.stdin) {
10814
11040
  function verifySafeCommand(target, options = {}, stdin = process.stdin) {
10815
11041
  return safetyCommand("verify-safe", target, options, stdin);
10816
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
+ }
10817
11075
  var NOTE = "Generated locally by AgentInspect. Artifacts are best-effort summaries, not compliance or security certification.";
10818
11076
  var SAFETY_RULES = [
10819
11077
  createSafetyRawContentRule(),
@@ -10887,8 +11145,8 @@ function renderCheckSection(result) {
10887
11145
  `Diagnostics: ${result.diagnostics.length}`
10888
11146
  ];
10889
11147
  for (const finding of result.findings.slice(0, 10)) {
10890
- const path17 = finding.evidence[0]?.path ?? "(run)";
10891
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path17})`);
11148
+ const path19 = finding.evidence[0]?.path ?? "(run)";
11149
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path19})`);
10892
11150
  }
10893
11151
  for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
10894
11152
  lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
@@ -10966,8 +11224,8 @@ function renderHtml(trace, check, diff) {
10966
11224
  `;
10967
11225
  }
10968
11226
  async function writeArtifact(outputDir, relativePath, content, files) {
10969
- const outPath = path10.join(outputDir, relativePath);
10970
- await mkdir(path10.dirname(outPath), { recursive: true });
11227
+ const outPath = path13.join(outputDir, relativePath);
11228
+ await mkdir(path13.dirname(outPath), { recursive: true });
10971
11229
  await writeFile(outPath, content, "utf-8");
10972
11230
  files.push(relativePath);
10973
11231
  }
@@ -10983,7 +11241,7 @@ function manifestStatus(check, diff) {
10983
11241
  return "ok";
10984
11242
  }
10985
11243
  async function artifactsCommand(target, options = {}, stdin = process.stdin) {
10986
- const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path10.resolve(options.outputDir.trim()) : "";
11244
+ const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path13.resolve(options.outputDir.trim()) : "";
10987
11245
  if (outputDir === "") {
10988
11246
  console.error("--output-dir is required.");
10989
11247
  process.exitCode = 1;
@@ -11049,8 +11307,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
11049
11307
  await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
11050
11308
  const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
11051
11309
  if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
11052
- await mkdir(path10.dirname(path10.resolve(summaryTarget)), { recursive: true });
11053
- await appendFile(path10.resolve(summaryTarget), `
11310
+ await mkdir(path13.dirname(path13.resolve(summaryTarget)), { recursive: true });
11311
+ await appendFile(path13.resolve(summaryTarget), `
11054
11312
  ${renderMarkdown(trace, check, diff)}`, "utf-8");
11055
11313
  }
11056
11314
  const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
@@ -11069,10 +11327,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
11069
11327
  findings: diff?.findings.length ?? 0,
11070
11328
  diagnostics: diff?.diagnostics.length ?? 0
11071
11329
  },
11072
- ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path10.resolve(summaryTarget) } : {},
11330
+ ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path13.resolve(summaryTarget) } : {},
11073
11331
  note: NOTE
11074
11332
  };
11075
- await writeFile(path10.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
11333
+ await writeFile(path13.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
11076
11334
  if (options.json === true) {
11077
11335
  console.log(writeJson3(manifest).trimEnd());
11078
11336
  } else {
@@ -11083,1568 +11341,1893 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
11083
11341
  }
11084
11342
  }
11085
11343
  }
11086
- function validateReporterArtifactPath(options) {
11087
- const outputDir = path10.resolve(options.outputDir);
11088
- const diagnostics = [];
11089
- const rawPath = options.relativePath;
11090
- if (rawPath.length === 0) {
11091
- diagnostics.push({
11092
- code: "artifact_path_empty",
11093
- severity: "error",
11094
- message: "Reporter artifact path must not be empty."
11095
- });
11096
- return { ok: false, outputDir, diagnostics };
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
+ };
11373
+ return {
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
11384
+ };
11385
+ }
11386
+ function isPlainObject(value) {
11387
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11388
+ }
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) {
11399
+ if (typeof value !== "string" || value.trim() === "") {
11400
+ errors.push(`${field} must be a non-empty string`);
11401
+ return;
11097
11402
  }
11098
- if (rawPath.includes("\0")) {
11099
- diagnostics.push({
11100
- code: "invalid_artifact_path",
11101
- severity: "error",
11102
- message: "Reporter artifact path must not contain null bytes.",
11103
- target: rawPath
11104
- });
11105
- return { ok: false, outputDir, diagnostics };
11403
+ if (!isSafeRelativeWorkspacePath(value)) {
11404
+ errors.push(
11405
+ `${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
11406
+ );
11106
11407
  }
11107
- if (path10.isAbsolute(rawPath) || path10.win32.isAbsolute(rawPath)) {
11108
- diagnostics.push({
11109
- code: "artifact_path_absolute",
11110
- severity: "error",
11111
- message: "Reporter artifact path must be relative.",
11112
- target: rawPath
11113
- });
11114
- return { ok: false, outputDir, diagnostics };
11408
+ }
11409
+ function validateIndex(value, errors) {
11410
+ if (!isPlainObject(value)) {
11411
+ errors.push("index must be an object");
11412
+ return void 0;
11115
11413
  }
11116
- const normalized = path10.posix.normalize(rawPath.replace(/\\/g, "/"));
11117
- const segments = normalized.split("/");
11118
- if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
11119
- diagnostics.push({
11120
- code: "artifact_path_escape",
11121
- severity: "error",
11122
- message: "Reporter artifact path must stay under the output directory.",
11123
- target: rawPath
11124
- });
11125
- return { ok: false, outputDir, diagnostics };
11414
+ if (typeof value.enabled !== "boolean") {
11415
+ errors.push("index.enabled must be a boolean");
11126
11416
  }
11127
- const absolutePath = path10.resolve(outputDir, normalized);
11128
- const relFromOutput = path10.relative(outputDir, absolutePath);
11129
- if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path10.isAbsolute(relFromOutput)) {
11130
- diagnostics.push({
11131
- code: "artifact_path_escape",
11132
- severity: "error",
11133
- message: "Reporter artifact path resolved outside the output directory.",
11134
- target: rawPath
11135
- });
11136
- return { ok: false, outputDir, diagnostics };
11417
+ if (!INDEX_TYPES.includes(value.type)) {
11418
+ errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
11137
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;
11138
11426
  return {
11139
- ok: true,
11140
- outputDir,
11141
- relativePath: normalized,
11142
- absolutePath,
11143
- diagnostics
11427
+ enabled: value.enabled,
11428
+ type: value.type,
11429
+ ...value.path !== void 0 ? { path: value.path } : {}
11144
11430
  };
11145
11431
  }
11146
-
11147
- // packages/cli/src/ci-summary.ts
11148
- var NOTE2 = "Generated locally by AgentInspect from reporter artifact manifests. Trace contents are not embedded.";
11149
- var MAX_TEXT = 180;
11150
- var FRAMEWORKS = /* @__PURE__ */ new Set(["vitest", "jest", "manual"]);
11151
- var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
11152
- var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
11153
- var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
11154
- var REDACTION_PROFILES = /* @__PURE__ */ new Set(["local", "share", "strict"]);
11155
- function stable6(value) {
11156
- if (Array.isArray(value)) return value.map(stable6);
11157
- if (value === null || typeof value !== "object") return value;
11158
- const record = value;
11159
- return Object.fromEntries(
11160
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
11161
- );
11162
- }
11163
- function writeJson4(value) {
11164
- return `${JSON.stringify(stable6(value), null, 2)}
11165
- `;
11166
- }
11167
- function isObject(value) {
11168
- return typeof value === "object" && value !== null && !Array.isArray(value);
11169
- }
11170
- function readString(value, label) {
11171
- if (typeof value !== "string" || value.trim() === "") {
11172
- throw new Error(`${label} must be a non-empty string.`);
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 };
11173
11437
  }
11174
- return safeText(value);
11175
- }
11176
- function readOptionalString(value) {
11177
- return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
11178
- }
11179
- function readFramework(value) {
11180
- const framework = readString(value, "manifest.framework");
11181
- if (!FRAMEWORKS.has(framework)) {
11182
- throw new Error(`Unsupported reporter framework: ${framework}.`);
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
+ );
11183
11444
  }
11184
- return framework;
11185
- }
11186
- function readStatus(value) {
11187
- const status = readString(value, "result.status");
11188
- if (!STATUSES.has(status)) {
11189
- throw new Error(`Unsupported reporter test status: ${status}.`);
11445
+ if (typeof input3.project !== "string" || input3.project.trim() === "") {
11446
+ errors.push("project must be a non-empty string");
11190
11447
  }
11191
- return status;
11192
- }
11193
- function readArtifact(value, index) {
11194
- if (!isObject(value)) throw new Error(`manifest.artifacts[${index}] must be an object.`);
11195
- const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
11196
- const format = readString(value.format, `manifest.artifacts[${index}].format`);
11197
- const redactionProfile = readString(
11198
- value.redactionProfile,
11199
- `manifest.artifacts[${index}].redactionProfile`
11200
- );
11201
- if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
11202
- if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
11203
- if (!REDACTION_PROFILES.has(redactionProfile)) {
11204
- throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
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");
11205
11452
  }
11206
- const artifactPath = readString(value.path, `manifest.artifacts[${index}].path`);
11207
- const pathCheck = validateReporterArtifactPath({
11208
- outputDir: process.cwd(),
11209
- relativePath: artifactPath
11210
- });
11211
- if (!pathCheck.ok || pathCheck.relativePath === void 0) {
11212
- throw new Error(`Unsafe reporter artifact path: ${artifactPath}.`);
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
+ });
11213
11465
  }
11214
- return {
11215
- kind,
11216
- path: pathCheck.relativePath,
11217
- format,
11218
- redactionProfile
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 };
11481
+ }
11482
+ const manifest = {
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
11219
11493
  };
11494
+ return { ok: true, manifest, errors, warnings };
11220
11495
  }
11221
- function readArtifacts(value, label) {
11222
- if (value === void 0) return [];
11223
- if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
11224
- return value.map((item, index) => readArtifact(item, index));
11496
+ function parseWorkspaceManifest(json) {
11497
+ const warnings = [];
11498
+ if (typeof json !== "string") {
11499
+ return { ok: false, errors: ["manifest input must be a string"], warnings };
11500
+ }
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);
11225
11517
  }
11226
- function readDiagnosticsCount(value, label) {
11227
- if (value === void 0) return 0;
11228
- if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
11229
- return value.length;
11518
+ function serializeWorkspaceManifest(manifest) {
11519
+ return `${JSON.stringify(manifest, null, 2)}
11520
+ `;
11230
11521
  }
11231
- function readResult(value, index) {
11232
- if (!isObject(value)) throw new Error(`manifest.results[${index}] must be an object.`);
11233
- const file = readOptionalString(value.file);
11234
- const tracePath = readOptionalString(value.tracePath);
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);
11235
11526
  return {
11236
- testId: readString(value.testId, `manifest.results[${index}].testId`),
11237
- name: readString(value.name, `manifest.results[${index}].name`),
11238
- ...file === void 0 ? {} : { file },
11239
- status: readStatus(value.status),
11240
- ...tracePath === void 0 ? {} : { tracePath },
11241
- artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
11242
- diagnostics: readDiagnosticsCount(
11243
- value.diagnostics,
11244
- `manifest.results[${index}].diagnostics`
11245
- )
11527
+ projectRoot,
11528
+ workspaceDir,
11529
+ manifestPath: path13.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
11246
11530
  };
11247
11531
  }
11248
- function readManifestDocument(value) {
11249
- if (!isObject(value)) throw new Error("Reporter manifest file must contain a JSON object.");
11250
- const candidate = isObject(value.manifest) ? value.manifest : value;
11251
- if (!isObject(candidate)) throw new Error("Reporter manifest must be a JSON object.");
11252
- const schemaVersion = readString(candidate.schemaVersion, "manifest.schemaVersion");
11253
- if (schemaVersion !== "0.1") {
11254
- throw new Error(`Unsupported reporter manifest schemaVersion: ${schemaVersion}.`);
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;
11255
11538
  }
11256
- if (!Array.isArray(candidate.results)) {
11257
- throw new Error("manifest.results must be an array.");
11539
+ throw new Error(
11540
+ `Workspace path "${relative}" resolves outside the workspace directory`
11541
+ );
11542
+ }
11543
+ async function pathExists(p) {
11544
+ try {
11545
+ await access(p);
11546
+ return true;
11547
+ } catch {
11548
+ return false;
11258
11549
  }
11259
- const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
11260
- const results = candidate.results.map((item, index) => readResult(item, index));
11261
- const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
11262
- const manifest = {
11263
- framework: readFramework(candidate.framework),
11264
- generatedAt: readString(candidate.generatedAt, "manifest.generatedAt"),
11265
- results,
11266
- artifacts
11267
- };
11268
- return {
11269
- packageName: readOptionalString(value.package),
11270
- manifest,
11271
- diagnostics
11272
- };
11273
11550
  }
11274
- function cwdRelative(filePath) {
11275
- const relative = path10.relative(process.cwd(), path10.resolve(filePath)).replace(/\\/g, "/");
11276
- if (relative === "" || relative.startsWith("../") || path10.isAbsolute(relative)) {
11277
- return path10.basename(filePath);
11551
+ async function isWritable(p) {
11552
+ try {
11553
+ await access(p, constants$1.W_OK);
11554
+ return true;
11555
+ } catch {
11556
+ return false;
11278
11557
  }
11279
- return relative;
11280
11558
  }
11281
- async function readReporterManifest(filePath) {
11282
- const absolute = path10.resolve(filePath);
11283
- const raw = await readFile(absolute, "utf-8");
11284
- const document = readManifestDocument(JSON.parse(raw));
11285
- const manifest = document.manifest;
11286
- const results = manifest.results.map((result) => ({
11287
- testId: safeText(result.testId),
11288
- name: safeText(result.name),
11289
- ...result.file === void 0 ? {} : { file: safeText(path10.basename(result.file)) },
11290
- status: result.status,
11291
- ...result.tracePath === void 0 ? {} : { tracePath: safeText(path10.basename(result.tracePath)) },
11292
- artifacts: result.artifacts,
11293
- diagnostics: result.diagnostics
11294
- }));
11295
- return {
11296
- ...document.packageName === void 0 ? {} : { packageName: document.packageName },
11297
- manifestFile: cwdRelative(absolute),
11298
- framework: manifest.framework,
11299
- generatedAt: manifest.generatedAt,
11300
- results,
11301
- artifacts: manifest.artifacts,
11302
- diagnostics: document.diagnostics
11303
- };
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 [];
11565
+ }
11304
11566
  }
11305
- function summarize3(manifests) {
11306
- const summary = {
11307
- manifests: manifests.length,
11308
- tests: 0,
11309
- failed: 0,
11310
- passed: 0,
11311
- skipped: 0,
11312
- todo: 0,
11313
- artifacts: 0,
11314
- diagnostics: 0
11315
- };
11316
- for (const manifest of manifests) {
11317
- summary.artifacts += manifest.artifacts.length;
11318
- summary.diagnostics += manifest.diagnostics;
11319
- for (const result of manifest.results) {
11320
- summary.tests += 1;
11321
- if (result.status === "failed") summary.failed += 1;
11322
- else if (result.status === "passed") summary.passed += 1;
11323
- else if (result.status === "skipped") summary.skipped += 1;
11324
- else summary.todo += 1;
11325
- summary.artifacts += result.artifacts.length;
11326
- summary.diagnostics += result.diagnostics;
11327
- }
11567
+ async function countFiles(dir) {
11568
+ try {
11569
+ const entries = await readdir(dir, { withFileTypes: true });
11570
+ return entries.filter((e) => e.isFile()).length;
11571
+ } catch {
11572
+ return 0;
11573
+ }
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: [] };
11328
11581
  }
11582
+ const parsed = parseWorkspaceManifest(raw);
11329
11583
  return {
11330
- status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
11331
- manifests,
11332
- summary,
11333
- note: NOTE2
11584
+ exists: true,
11585
+ ok: parsed.ok,
11586
+ ...parsed.manifest ? { manifest: parsed.manifest } : {},
11587
+ errors: parsed.errors,
11588
+ warnings: parsed.warnings
11334
11589
  };
11335
11590
  }
11336
- function markdownCell(value) {
11337
- return safeText(String(value ?? "unknown")).replaceAll("|", "\\|").replace(/\r?\n/g, " ");
11338
- }
11339
- function renderMarkdown2(result) {
11340
- const lines = [
11341
- "# AgentInspect CI Summary",
11342
- "",
11343
- NOTE2,
11344
- "",
11345
- "| Field | Value |",
11346
- "| --- | --- |",
11347
- `| Status | ${result.status} |`,
11348
- `| Manifests | ${result.summary.manifests} |`,
11349
- `| Tests | ${result.summary.tests} |`,
11350
- `| Failed | ${result.summary.failed} |`,
11351
- `| Passed | ${result.summary.passed} |`,
11352
- `| Skipped | ${result.summary.skipped} |`,
11353
- `| Todo | ${result.summary.todo} |`,
11354
- `| Artifacts | ${result.summary.artifacts} |`,
11355
- `| Diagnostics | ${result.summary.diagnostics} |`,
11356
- "",
11357
- "## Tests",
11358
- "",
11359
- "| Framework | Status | Test | File | Trace | Artifacts |",
11360
- "| --- | --- | --- | --- | --- | --- |"
11361
- ];
11362
- const rows = result.manifests.flatMap(
11363
- (manifest) => manifest.results.map((test) => ({
11364
- framework: manifest.framework,
11365
- test
11366
- }))
11367
- );
11368
- if (rows.length === 0) {
11369
- lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
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;
11370
11604
  } else {
11371
- for (const row of rows) {
11372
- lines.push(
11373
- `| ${markdownCell(row.framework)} | ${markdownCell(row.test.status)} | ${markdownCell(row.test.name)} | ${markdownCell(row.test.file)} | ${markdownCell(row.test.tracePath)} | ${row.test.artifacts.length} |`
11374
- );
11375
- }
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;
11376
11614
  }
11377
- lines.push("", "## Manifests", "", "| Framework | File | Generated | Artifacts |", "| --- | --- | --- | --- |");
11378
- for (const manifest of result.manifests) {
11379
- lines.push(
11380
- `| ${markdownCell(manifest.framework)} | ${markdownCell(manifest.manifestFile)} | ${markdownCell(manifest.generatedAt)} | ${manifest.artifacts.length} |`
11381
- );
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 });
11382
11629
  }
11383
- lines.push("", "## Artifacts", "", "| Framework | Kind | Path | Format | Profile |", "| --- | --- | --- | --- | --- |");
11384
- const artifacts = result.manifests.flatMap(
11385
- (manifest) => manifest.artifacts.map((artifact) => ({ framework: manifest.framework, artifact }))
11386
- );
11387
- if (artifacts.length === 0) {
11388
- lines.push("| unknown | unknown | No artifacts found | unknown | unknown |");
11389
- } else {
11390
- for (const row of artifacts) {
11391
- lines.push(
11392
- `| ${markdownCell(row.framework)} | ${markdownCell(row.artifact.kind)} | ${markdownCell(row.artifact.path)} | ${markdownCell(row.artifact.format)} | ${markdownCell(row.artifact.redactionProfile)} |`
11393
- );
11394
- }
11630
+ if (!dryRun && created) {
11631
+ await mkdir(location.workspaceDir, { recursive: true });
11632
+ await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
11395
11633
  }
11396
- lines.push("");
11397
- return `${lines.join("\n")}
11398
- `;
11634
+ return {
11635
+ location,
11636
+ manifest,
11637
+ created,
11638
+ adopted,
11639
+ createdDirs,
11640
+ detectedExistingTraces,
11641
+ dryRun
11642
+ };
11399
11643
  }
11400
- function safeText(value) {
11401
- const compact = value.replace(/\s+/g, " ").trim();
11402
- const redacted = compact.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(
11403
- /\b(?:api[_-]?key|authorization|token|secret|password)\s*[:=]\s*[^,\s]+/gi,
11404
- "$1=[REDACTED]"
11405
- );
11406
- if (redacted.length <= MAX_TEXT) return redacted;
11407
- return `${redacted.slice(0, MAX_TEXT - 12)}...[truncated]`;
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;
11408
11654
  }
11409
- async function ciSummaryCommand(manifestPaths, options = {}) {
11410
- if (manifestPaths.length === 0) {
11411
- console.error("At least one reporter manifest path is required.");
11412
- process.exitCode = 1;
11413
- return;
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;
11414
11660
  }
11415
- let result;
11416
- try {
11417
- const manifests = [];
11418
- for (const manifestPath of manifestPaths) {
11419
- manifests.push(await readReporterManifest(manifestPath));
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)
11420
11685
  }
11421
- manifests.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
11422
- result = summarize3(manifests);
11423
- } catch (error) {
11424
- const message = error instanceof Error ? error.message : String(error);
11425
- console.error(`[AgentInspect] ci-summary failed: ${message}`);
11426
- process.exitCode = 1;
11427
- return;
11428
- }
11429
- const markdown = renderMarkdown2(result);
11430
- const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path10.resolve(options.output.trim()) : void 0;
11431
- if (outputPath !== void 0) {
11432
- await mkdir(path10.dirname(outputPath), { recursive: true });
11433
- await writeFile(outputPath, markdown, "utf-8");
11434
- }
11435
- const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
11436
- if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
11437
- const summaryPath = path10.resolve(summaryTarget);
11438
- await mkdir(path10.dirname(summaryPath), { recursive: true });
11439
- await appendFile(summaryPath, `
11440
- ${markdown}`, "utf-8");
11686
+ };
11687
+ }
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 };
11441
11698
  }
11442
- if (options.json === true) {
11443
- console.log(writeJson4(result).trimEnd());
11444
- } else if (outputPath !== void 0) {
11445
- console.log(`Wrote AgentInspect CI summary to ${outputPath}`);
11446
- console.log(`Status: ${result.status}`);
11447
- } else {
11448
- console.log(markdown.trimEnd());
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 };
11449
11706
  }
11450
- }
11451
- var CONFIG_FILE = "agent-inspect.config.ts";
11452
- var TRACE_DIR = ".agent-inspect";
11453
- var GITKEEP = ".agent-inspect/.gitkeep";
11454
- function normalizeFramework(value) {
11455
- const raw = (value ?? "custom").trim();
11456
- if (raw === "ai-sdk" || raw === "openai-agents" || raw === "langchain" || raw === "custom") {
11457
- return raw;
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 });
11458
11711
  }
11459
- throw new Error(
11460
- "Unsupported --framework value. Use ai-sdk, openai-agents, langchain, or custom."
11461
- );
11462
- }
11463
- function configTemplate(framework) {
11464
- const base = `/**
11465
- * AgentInspect local config (metadata-only capture by default).
11466
- * See https://github.com/rajudandigam/agent-inspect/blob/main/docs/SAFE-TRACE-SHARING.md
11467
- */
11468
- export const agentInspectConfig = {
11469
- traceDir: ".agent-inspect",
11470
- enabled: process.env.AGENT_INSPECT !== "0",
11471
- redactionProfile: "local" as const,
11472
- };
11473
- `;
11474
- if (framework === "custom") return base;
11475
- return `${base}
11476
- export const framework = "${framework}" as const;
11477
- `;
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 };
11478
11770
  }
11479
- function demoTemplate(framework) {
11480
- switch (framework) {
11481
- case "ai-sdk":
11482
- return `/**
11483
- * AI SDK starter \u2014 metadata-only telemetry (no real model calls in this demo).
11484
- * Install: npm install agent-inspect @agent-inspect/ai-sdk ai
11485
- */
11486
- import { inspectRun, step } from "agent-inspect";
11487
-
11488
- async function main() {
11489
- await inspectRun("ai-sdk-demo", async () => {
11490
- await step.tool("mock-generate", async () => ({ text: "ok" }));
11491
- }, { traceDir: ".agent-inspect", silent: true });
11492
- console.log("Trace written to .agent-inspect/");
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
+ }
11795
+ }
11796
+ return { dryRun, removed };
11493
11797
  }
11494
11798
 
11495
- main().catch((error) => {
11496
- console.error(error);
11497
- process.exitCode = 1;
11498
- });
11499
- `;
11500
- case "openai-agents":
11501
- return `/**
11502
- * OpenAI Agents starter \u2014 use @agent-inspect/openai-agents for local-only processors.
11503
- * This demo uses manual steps (no API keys).
11504
- */
11505
- import { inspectRun, step } from "agent-inspect";
11506
-
11507
- async function main() {
11508
- await inspectRun("openai-agents-demo", async () => {
11509
- await step.tool("mock-agent-run", async () => "ok");
11510
- }, { traceDir: ".agent-inspect", silent: true });
11511
- 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
+ );
11512
11808
  }
11513
-
11514
- main().catch((error) => {
11515
- console.error(error);
11516
- process.exitCode = 1;
11517
- });
11809
+ function writeJson4(value) {
11810
+ return `${JSON.stringify(stable6(value), null, 2)}
11518
11811
  `;
11519
- case "langchain":
11520
- return `/**
11521
- * LangChain starter \u2014 wire @agent-inspect/langchain callbacks in your app.
11522
- * This demo uses manual steps (no API keys).
11523
- */
11524
- import { inspectRun, step } from "agent-inspect";
11525
-
11526
- async function main() {
11527
- await inspectRun("langchain-demo", async () => {
11528
- await step.tool("mock-chain", async () => "ok");
11529
- }, { traceDir: ".agent-inspect", silent: true });
11530
- console.log("Trace written to .agent-inspect/");
11531
11812
  }
11532
-
11533
- main().catch((error) => {
11534
- console.error(error);
11535
- process.exitCode = 1;
11536
- });
11537
- `;
11538
- default:
11539
- return `import { observe } from "agent-inspect";
11540
-
11541
- class DemoAgent {
11542
- async run(input: { question: string }) {
11543
- return { answer: \`Echo: \${input.question}\` };
11813
+ function parseBundleProfile(value) {
11814
+ if (value === void 0 || value === "local" || value === "share" || value === "strict") {
11815
+ return value ?? "share";
11544
11816
  }
11817
+ throw new Error(`Unsupported --profile "${value}". Use local, share, or strict.`);
11545
11818
  }
11546
-
11547
- const agent = observe(new DemoAgent(), {
11548
- traceDir: ".agent-inspect",
11549
- silent: true,
11550
- });
11551
-
11552
- await agent.run({ question: "hello" });
11553
- console.log("Trace written to .agent-inspect/");
11554
- `;
11555
- }
11819
+ function toReportProfile(profile) {
11820
+ return profile;
11556
11821
  }
11557
- function githubWorkflowTemplate() {
11558
- return `name: AgentInspect artifacts
11559
-
11560
- on:
11561
- workflow_dispatch:
11562
- push:
11563
- branches: [main]
11564
-
11565
- jobs:
11566
- trace-artifacts:
11567
- runs-on: ubuntu-latest
11568
- steps:
11569
- - uses: actions/checkout@v4
11570
- - uses: actions/setup-node@v4
11571
- with:
11572
- node-version: "22"
11573
- - run: npm ci
11574
- - run: npm test
11575
- - name: Upload AgentInspect traces
11576
- if: always()
11577
- uses: actions/upload-artifact@v4
11578
- with:
11579
- name: agent-inspect-traces
11580
- path: .agent-inspect/**/*.jsonl
11581
- if-no-files-found: ignore
11582
- `;
11822
+ function escapeHtml3(value) {
11823
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
11583
11824
  }
11584
- async function planInit(options = {}) {
11585
- const framework = normalizeFramework(options.framework);
11586
- const cwd = path10.resolve(options.cwd ?? process.cwd());
11587
- const demoPath = framework === "custom" ? path10.join("examples", "agent-inspect-demo.mjs") : path10.join("examples", `agent-inspect-${framework}-demo.mjs`);
11588
- const candidates = [
11589
- { rel: CONFIG_FILE, content: configTemplate(framework) },
11590
- { rel: GITKEEP, content: "" },
11591
- { rel: demoPath, content: demoTemplate(framework) }
11592
- ];
11593
- if (options.ci === "github") {
11594
- candidates.push({
11595
- rel: ".github/workflows/agent-inspect-artifacts.yml",
11596
- content: githubWorkflowTemplate()
11597
- });
11825
+ function safetyStatusFromAssess(status) {
11826
+ if (status === "SAFE" || status === "SAFE WITH WARNINGS" || status === "UNSAFE" || status === "UNKNOWN") {
11827
+ return status;
11598
11828
  }
11599
- const files = [];
11600
- for (const candidate of candidates) {
11601
- const abs = path10.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);
11602
11834
  try {
11603
- await access(abs);
11604
- files.push({
11605
- path: candidate.rel,
11606
- action: "skip",
11607
- reason: "file already exists"
11608
- });
11609
- } catch (error) {
11610
- if (error.code !== "ENOENT") throw error;
11611
- 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 {
11612
11844
  }
11845
+ return normalized;
11613
11846
  }
11614
- return { framework, ...options.ci ? { ci: options.ci } : {}, files };
11615
- }
11616
- async function writePlannedFiles(plan, cwd, options) {
11617
- const written = [];
11618
- for (const entry of plan.files) {
11619
- if (entry.action === "skip") {
11620
- continue;
11621
- }
11622
- const abs = path10.join(cwd, entry.path);
11623
- if (options.dryRun) {
11624
- written.push(entry.path);
11625
- 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
+ );
11626
11857
  }
11627
- await mkdir(path10.dirname(abs), { recursive: true });
11628
- const content = entry.path === CONFIG_FILE ? configTemplate(plan.framework) : entry.path === GITKEEP ? "" : entry.path.endsWith(".yml") ? githubWorkflowTemplate() : demoTemplate(plan.framework);
11629
- await writeFile(abs, content, "utf-8");
11630
- written.push(entry.path);
11858
+ } catch {
11631
11859
  }
11632
- return written;
11860
+ return defaultBundleOutputPath(runIds);
11633
11861
  }
11634
- async function initCommand(options = {}) {
11635
- const cwd = path10.resolve(options.cwd ?? process.cwd());
11636
- try {
11637
- const plan = await planInit({ ...options, cwd });
11638
- const toWrite = plan.files.filter((file) => file.action === "create").map((f) => f.path);
11639
- const skipped = plan.files.filter((file) => file.action === "skip");
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;
11907
+ try {
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);
11640
11921
  if (options.json) {
11641
- const payload = {
11642
- ok: true,
11643
- version,
11644
- framework: plan.framework,
11645
- ci: plan.ci ?? null,
11646
- dryRun: options.dryRun === true,
11647
- planned: plan.files,
11648
- wouldWrite: options.dryRun ? toWrite : void 0
11649
- };
11650
- console.log(JSON.stringify(payload, null, 2));
11651
- if (options.dryRun) return;
11922
+ console.log(writeJson4({ ok: false, error: message }).trimEnd());
11923
+ } else {
11924
+ console.error(`[AgentInspect] bundle failed: ${message}`);
11652
11925
  }
11653
- const written = await writePlannedFiles(plan, cwd, options);
11654
- if (!options.json) {
11655
- console.log("AgentInspect init");
11656
- console.log(`Framework: ${plan.framework}`);
11657
- console.log(`Trace directory: ${TRACE_DIR}/`);
11658
- if (options.dryRun) {
11659
- console.log("Dry run \u2014 would create:");
11660
- for (const file of toWrite) console.log(`- ${file}`);
11661
- for (const file of skipped) {
11662
- console.log(`- ${file.path} (skip: ${file.reason ?? "exists"})`);
11663
- }
11664
- 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}`);
11665
11949
  }
11666
- console.log("Created:");
11667
- for (const file of written) console.log(`- ${file}`);
11668
- for (const file of skipped) {
11669
- 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}`);
11670
11965
  }
11671
- console.log("\nNext: run your demo, then `npx agent-inspect list --dir .agent-inspect`");
11672
- console.log("No dependencies were installed. Add packages manually when ready.");
11966
+ process.exitCode = 1;
11967
+ return;
11673
11968
  }
11674
- } catch (error) {
11675
- 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)) {
11676
12014
  if (options.json) {
11677
- 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
+ );
11678
12022
  } else {
11679
- console.error(`[AgentInspect] init failed: ${msg}`);
12023
+ console.error(
12024
+ `[AgentInspect] bundle refused: safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`
12025
+ );
11680
12026
  }
11681
12027
  process.exitCode = 1;
12028
+ return;
11682
12029
  }
11683
- }
11684
- var OPTIONAL_PACKAGES = {
11685
- custom: [],
11686
- "ai-sdk": ["@agent-inspect/ai-sdk"],
11687
- "openai-agents": ["@agent-inspect/openai-agents"],
11688
- langchain: ["@agent-inspect/langchain"]
11689
- };
11690
- function nodeVersionCheck() {
11691
- const major = Number(process2.versions.node.split(".")[0]);
11692
- if (Number.isNaN(major) || major < 20) {
11693
- return {
11694
- id: "node-version",
11695
- status: "fail",
11696
- message: `Node ${process2.versions.node} is below the supported minimum (20).`,
11697
- remediation: "Upgrade to Node 20 LTS or newer.",
11698
- evidence: process2.versions.node
11699
- };
11700
- }
11701
- return {
11702
- id: "node-version",
11703
- status: "pass",
11704
- message: `Node ${process2.versions.node} meets the minimum (>=20).`,
11705
- evidence: process2.versions.node
12030
+ const redactionReport = {
12031
+ profile,
12032
+ totalFindings: redactionRuns.reduce((sum, run) => sum + run.findings, 0),
12033
+ runs: redactionRuns
11706
12034
  };
11707
- }
11708
- function envCheck(name, optional = true) {
11709
- const value = process2.env[name];
11710
- if (value === void 0 || value.trim() === "") {
11711
- return {
11712
- id: `env-${name.toLowerCase()}`,
11713
- status: optional ? "skipped" : "warn",
11714
- message: `${name} is not set.`,
11715
- remediation: optional ? void 0 : `Set ${name}=1 to enable tracing.`
11716
- };
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
+ );
11717
12046
  }
11718
- return {
11719
- id: `env-${name.toLowerCase()}`,
11720
- status: "pass",
11721
- message: `${name} is set.`,
11722
- evidence: value
11723
- };
11724
- }
11725
- async function traceDirWritable(traceDir) {
11726
- const resolved = path10.resolve(traceDir);
11727
- try {
11728
- await mkdir(resolved, { recursive: true });
11729
- await access(resolved, constants.W_OK);
11730
- return {
11731
- id: "trace-dir-writable",
11732
- status: "pass",
11733
- message: `Trace directory is writable: ${resolved}`,
11734
- evidence: resolved
11735
- };
11736
- } catch (error) {
11737
- return {
11738
- id: "trace-dir-writable",
11739
- status: "fail",
11740
- message: `Trace directory is not writable: ${resolved}`,
11741
- remediation: "Create the directory or set AGENT_INSPECT_TRACE_DIR to a writable path.",
11742
- evidence: error instanceof Error ? error.message : String(error)
11743
- };
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;
11744
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}`);
11745
12107
  }
11746
- function resolvePackage(cwd, name) {
11747
- const require2 = createRequire(path10.join(cwd, "package.json"));
11748
- try {
11749
- const pkgPath = require2.resolve(`${name}/package.json`);
11750
- const pkg = require2(pkgPath);
11751
- return { ok: true, version: pkg.version };
11752
- } catch {
11753
- return { ok: false };
11754
- }
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
+ `;
11755
12122
  }
11756
- function importSmoke(cwd) {
11757
- const results = [];
11758
- const require2 = createRequire(path10.join(cwd, "package.json"));
11759
- try {
11760
- require2.resolve("agent-inspect");
11761
- results.push({
11762
- id: "import-agent-inspect",
11763
- status: "pass",
11764
- message: "agent-inspect resolves from the current project."
11765
- });
11766
- } catch {
11767
- results.push({
11768
- id: "import-agent-inspect",
11769
- status: "warn",
11770
- message: "agent-inspect is not installed in the current project.",
11771
- remediation: "Run npm install agent-inspect (or pnpm add agent-inspect)."
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."
11772
12132
  });
12133
+ return { ok: false, outputDir, diagnostics };
11773
12134
  }
11774
- try {
11775
- require2.resolve("agent-inspect/package.json");
11776
- results.push({
11777
- id: "import-agent-inspect-cjs",
11778
- status: "pass",
11779
- message: "CJS resolution for agent-inspect succeeded."
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
11780
12141
  });
11781
- } catch {
11782
- results.push({
11783
- id: "import-agent-inspect-cjs",
11784
- status: "skipped",
11785
- message: "CJS resolution check skipped (package not installed locally)."
12142
+ return { ok: false, outputDir, diagnostics };
12143
+ }
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
11786
12150
  });
12151
+ return { ok: false, outputDir, diagnostics };
11787
12152
  }
11788
- return results;
11789
- }
11790
- function optionalPackageChecks(cwd, framework) {
11791
- const packages = framework !== void 0 ? OPTIONAL_PACKAGES[framework] : [
11792
- "@agent-inspect/ai-sdk",
11793
- "@agent-inspect/openai-agents",
11794
- "@agent-inspect/langchain",
11795
- "@agent-inspect/redact",
11796
- "@agent-inspect/eval"
11797
- ];
11798
- return packages.map((name) => {
11799
- const resolved = resolvePackage(cwd, name);
11800
- if (!resolved.ok) {
11801
- return {
11802
- id: `optional-package-${name}`,
11803
- status: framework !== void 0 ? "warn" : "skipped",
11804
- message: `${name} is not installed.`,
11805
- remediation: framework !== void 0 ? `npm install ${name}` : void 0
11806
- };
11807
- }
11808
- return {
11809
- id: `optional-package-${name}`,
11810
- status: "pass",
11811
- message: `${name} is available.`,
11812
- evidence: resolved.version
11813
- };
11814
- });
11815
- }
11816
- function versionMismatchCheck(cwd) {
11817
- const root = resolvePackage(cwd, "agent-inspect");
11818
- if (!root.ok || root.version === void 0) {
11819
- return {
11820
- id: "version-alignment",
11821
- status: "skipped",
11822
- message: "Skipped version alignment \u2014 agent-inspect not installed locally."
11823
- };
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 };
11824
12163
  }
11825
- if (root.version !== version) {
11826
- return {
11827
- id: "version-alignment",
11828
- status: "warn",
11829
- message: `CLI version ${version} differs from local agent-inspect@${root.version}.`,
11830
- remediation: "Align versions with npm install agent-inspect@latest",
11831
- evidence: `cli=${version};local=${root.version}`
11832
- };
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 };
11833
12174
  }
11834
12175
  return {
11835
- id: "version-alignment",
11836
- status: "pass",
11837
- message: `CLI and local agent-inspect are aligned at ${version}.`
12176
+ ok: true,
12177
+ outputDir,
12178
+ relativePath: normalized,
12179
+ absolutePath,
12180
+ diagnostics
11838
12181
  };
11839
12182
  }
11840
- async function runDoctorChecks(options = {}) {
11841
- const cwd = path10.resolve(options.cwd ?? process2.cwd());
11842
- const traceDir = options.traceDir?.trim() || process2.env.AGENT_INSPECT_TRACE_DIR?.trim() || ".agent-inspect";
11843
- const checks2 = [
11844
- nodeVersionCheck(),
11845
- {
11846
- id: "cli-version",
11847
- status: "pass",
11848
- message: `agent-inspect CLI ${version}`,
11849
- evidence: version
11850
- },
11851
- await traceDirWritable(traceDir),
11852
- envCheck("AGENT_INSPECT"),
11853
- envCheck("AGENT_INSPECT_TRACE_DIR"),
11854
- versionMismatchCheck(cwd)
11855
- ];
11856
- if (options.checkImports !== false) {
11857
- checks2.push(...importSmoke(cwd));
11858
- }
11859
- checks2.push(...optionalPackageChecks(cwd, options.framework));
11860
- return checks2.sort((a, b) => a.id.localeCompare(b.id));
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
+ );
11861
12199
  }
11862
- async function doctorCommand(options = {}) {
11863
- const checks2 = await runDoctorChecks(options);
11864
- const failed = checks2.filter((check) => check.status === "fail").length;
11865
- const warned = checks2.filter((check) => check.status === "warn").length;
11866
- if (options.json) {
11867
- console.log(
11868
- JSON.stringify(
11869
- {
11870
- ok: failed === 0,
11871
- version,
11872
- summary: { pass: checks2.filter((c) => c.status === "pass").length, warn: warned, fail: failed },
11873
- checks: checks2
11874
- },
11875
- null,
11876
- 2
11877
- )
11878
- );
11879
- if (failed > 0) process2.exitCode = 1;
11880
- return;
11881
- }
11882
- console.log("AgentInspect doctor");
11883
- for (const check of checks2) {
11884
- const tag = check.status.toUpperCase();
11885
- console.log(`[${tag}] ${check.id}: ${check.message}`);
11886
- if (check.remediation) console.log(` \u2192 ${check.remediation}`);
11887
- }
11888
- console.log(`
11889
- Summary: ${failed} failed, ${warned} warnings`);
11890
- if (failed > 0) process2.exitCode = 1;
12200
+ function writeJson5(value) {
12201
+ return `${JSON.stringify(stable7(value), null, 2)}
12202
+ `;
11891
12203
  }
11892
-
11893
- // packages/adapter-sdk/src/indexer.ts
11894
- function defineIndexer(indexer) {
11895
- if (!indexer.id.trim()) throw new Error("indexer id is required");
11896
- return indexer;
12204
+ function isObject(value) {
12205
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11897
12206
  }
11898
- async function indexIsStale(snapshot, traceDir) {
11899
- const builtMs = Date.parse(snapshot.builtAt);
11900
- if (Number.isNaN(builtMs)) return true;
11901
- const td = new TraceDirectory({ dir: traceDir });
11902
- const files = await td.list();
11903
- for (const file of files) {
11904
- const stats = await td.getFileStats(file);
11905
- if (stats.mtimeMs > builtMs) return true;
12207
+ function readString(value, label) {
12208
+ if (typeof value !== "string" || value.trim() === "") {
12209
+ throw new Error(`${label} must be a non-empty string.`);
11906
12210
  }
11907
- return false;
11908
- }
11909
- function createTraceDirectoryIndexer() {
11910
- return defineIndexer({
11911
- id: "trace-directory-metadata",
11912
- async rebuild(traceDir, options = {}) {
11913
- const warnings = [];
11914
- const td = new TraceDirectory({ dir: traceDir });
11915
- const files = await td.list();
11916
- const maxEntries = options.maxEntries ?? 1e4;
11917
- if (files.length > maxEntries) {
11918
- warnings.push(
11919
- `indexer.truncated: trace directory has ${files.length} files; indexing first ${maxEntries}`
11920
- );
11921
- }
11922
- const slice = files.slice(0, maxEntries);
11923
- const metas = await loadTraceMetadataList(
11924
- traceDir,
11925
- slice,
11926
- (fileName) => td.getPath(fileName)
11927
- );
11928
- const entries = metas.map((meta) => ({
11929
- runId: meta.runId,
11930
- path: meta.filePath,
11931
- name: meta.name,
11932
- startedAt: meta.startedAt,
11933
- status: meta.status
11934
- })).sort((a, b) => a.runId.localeCompare(b.runId));
11935
- if (entries.length < slice.length) {
11936
- warnings.push(
11937
- `indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
11938
- );
11939
- }
11940
- return {
11941
- traceDir,
11942
- builtAt: (/* @__PURE__ */ new Date()).toISOString(),
11943
- entries,
11944
- warnings
11945
- };
11946
- }
11947
- });
12211
+ return safeText(value);
11948
12212
  }
11949
-
11950
- // packages/cli/src/index-cmd.ts
11951
- var INDEX_FILENAME = ".agent-inspect-index.json";
11952
- function traceIndexPath(traceDir) {
11953
- return path10.join(traceDir, INDEX_FILENAME);
12213
+ function readOptionalString(value) {
12214
+ return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
11954
12215
  }
11955
- function parseMaxEntries(raw) {
11956
- if (raw === void 0 || raw.trim() === "") return void 0;
11957
- const parsed = Number.parseInt(raw, 10);
11958
- if (!Number.isFinite(parsed) || parsed <= 0) {
11959
- throw new Error("--max-entries must be a positive integer.");
12216
+ function readFramework(value) {
12217
+ const framework = readString(value, "manifest.framework");
12218
+ if (!FRAMEWORKS.has(framework)) {
12219
+ throw new Error(`Unsupported reporter framework: ${framework}.`);
11960
12220
  }
11961
- return parsed;
12221
+ return framework;
11962
12222
  }
11963
- async function readSnapshot(indexPath2) {
11964
- try {
11965
- const raw = await readFile(indexPath2, "utf8");
11966
- return JSON.parse(raw);
11967
- } catch {
11968
- return void 0;
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}.`);
11969
12227
  }
12228
+ return status;
11970
12229
  }
11971
- async function indexBuildCommand(options = {}) {
11972
- try {
11973
- const traceDir = resolveTraceDir({ dir: options.dir });
11974
- await mkdir(traceDir, { recursive: true });
11975
- const indexer = createTraceDirectoryIndexer();
11976
- const snapshot = await indexer.rebuild(traceDir, {
11977
- maxEntries: parseMaxEntries(options.maxEntries)
11978
- });
11979
- const indexPath2 = traceIndexPath(traceDir);
11980
- await writeFile(indexPath2, `${JSON.stringify(snapshot, null, 2)}
11981
- `, "utf8");
11982
- if (options.json) {
11983
- console.log(JSON.stringify({ ok: true, indexPath: indexPath2, ...snapshot }, null, 2));
11984
- return;
11985
- }
11986
- console.log(`Built trace index: ${indexPath2}`);
11987
- console.log(`Entries: ${snapshot.entries.length}`);
11988
- if (snapshot.warnings.length > 0) {
11989
- for (const warning of snapshot.warnings) {
11990
- console.log(`warning: ${warning}`);
11991
- }
11992
- }
11993
- } catch (e) {
11994
- const msg = e instanceof Error ? e.message : String(e);
11995
- console.error(`[AgentInspect] index build failed: ${msg}`);
11996
- process.exitCode = 1;
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}.`);
11997
12242
  }
11998
- }
11999
- async function indexStatusCommand(options = {}) {
12000
- try {
12001
- const traceDir = resolveTraceDir({ dir: options.dir });
12002
- const indexPath2 = traceIndexPath(traceDir);
12003
- const snapshot = await readSnapshot(indexPath2);
12004
- if (!snapshot) {
12005
- const payload2 = { ok: true, exists: false, indexPath: indexPath2, traceDir, stale: true };
12006
- if (options.json) {
12007
- console.log(JSON.stringify(payload2, null, 2));
12008
- } else {
12009
- console.log(`No index at ${indexPath2}`);
12010
- console.log("Run: agent-inspect index build");
12011
- }
12012
- return;
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}.`);
12250
+ }
12251
+ return {
12252
+ kind,
12253
+ path: pathCheck.relativePath,
12254
+ format,
12255
+ redactionProfile
12256
+ };
12257
+ }
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
+ };
12310
+ }
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);
12315
+ }
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;
12013
12364
  }
12014
- const stale = await indexIsStale(snapshot, traceDir);
12015
- const payload = {
12016
- ok: true,
12017
- exists: true,
12018
- indexPath: indexPath2,
12019
- traceDir,
12020
- stale,
12021
- builtAt: snapshot.builtAt,
12022
- entries: snapshot.entries.length,
12023
- warnings: snapshot.warnings
12024
- };
12025
- if (options.json) {
12026
- console.log(JSON.stringify(payload, null, 2));
12027
- return;
12365
+ }
12366
+ return {
12367
+ status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
12368
+ manifests,
12369
+ summary,
12370
+ note: NOTE2
12371
+ };
12372
+ }
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
+ "| --- | --- | --- | --- | --- | --- |"
12398
+ ];
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
+ );
12028
12412
  }
12029
- console.log(`Index: ${indexPath2}`);
12030
- console.log(`Built: ${snapshot.builtAt}`);
12031
- console.log(`Entries: ${snapshot.entries.length}`);
12032
- console.log(`Stale: ${stale ? "yes" : "no"}`);
12033
- if (snapshot.warnings.length > 0) {
12034
- for (const warning of snapshot.warnings) {
12035
- console.log(`warning: ${warning}`);
12036
- }
12413
+ }
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} |`
12418
+ );
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
+ );
12037
12431
  }
12038
- } catch (e) {
12039
- const msg = e instanceof Error ? e.message : String(e);
12040
- console.error(`[AgentInspect] index status failed: ${msg}`);
12041
- process.exitCode = 1;
12042
12432
  }
12433
+ lines.push("");
12434
+ return `${lines.join("\n")}
12435
+ `;
12043
12436
  }
12044
- async function indexCleanCommand(options = {}) {
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;
12450
+ return;
12451
+ }
12452
+ let result;
12045
12453
  try {
12046
- const traceDir = resolveTraceDir({ dir: options.dir });
12047
- const indexPath2 = traceIndexPath(traceDir);
12048
- await rm(indexPath2, { force: true });
12049
- if (options.json) {
12050
- console.log(JSON.stringify({ ok: true, removed: indexPath2 }, null, 2));
12051
- return;
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;
12465
+ }
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
+ `;
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";
12524
+
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/");
12530
+ }
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/");
12549
+ }
12550
+
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/");
12568
+ }
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}\` };
12581
+ }
12582
+ }
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
+ `;
12592
+ }
12593
+ }
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
+ `;
12620
+ }
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
+ });
12635
+ }
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" });
12052
12649
  }
12053
- console.log(`Removed index: ${indexPath2}`);
12054
- } catch (e) {
12055
- const msg = e instanceof Error ? e.message : String(e);
12056
- console.error(`[AgentInspect] index clean failed: ${msg}`);
12057
- process.exitCode = 1;
12058
12650
  }
12651
+ return { framework, ...options.ci ? { ci: options.ci } : {}, files };
12059
12652
  }
12060
- var PACKAGE = "@agent-inspect/index-sqlite";
12061
- function isModuleNotFound2(e) {
12062
- return e !== null && typeof e === "object" && "code" in e && (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND");
12063
- }
12064
- async function loadIndexSqlite() {
12065
- try {
12066
- return await import('./src-DUGEOAZ7.mjs');
12067
- } catch (e) {
12068
- if (isModuleNotFound2(e)) {
12069
- console.error(
12070
- `The optional SQLite index is not installed. Run: npm install ${PACKAGE}`
12071
- );
12072
- process.exitCode = 1;
12073
- 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;
12074
12658
  }
12075
- const msg = e instanceof Error ? e.message : String(e);
12076
- console.error(`[AgentInspect] failed to load ${PACKAGE}: ${msg}`);
12077
- process.exitCode = 1;
12078
- return null;
12079
- }
12080
- }
12081
- function parsePositiveInt(raw, flag) {
12082
- if (raw === void 0 || raw.trim() === "") return void 0;
12083
- const parsed = Number.parseInt(raw, 10);
12084
- if (!Number.isFinite(parsed) || parsed <= 0) {
12085
- 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);
12086
12668
  }
12087
- return parsed;
12669
+ return written;
12088
12670
  }
12089
- async function newestTraceMtimeMs(traceDir) {
12090
- let newest = 0;
12671
+ async function initCommand(options = {}) {
12672
+ const cwd = path13.resolve(options.cwd ?? process.cwd());
12091
12673
  try {
12092
- const files = await readdir(traceDir);
12093
- for (const file of files) {
12094
- if (!file.endsWith(".jsonl")) continue;
12095
- try {
12096
- const s = await stat(path10.join(traceDir, file));
12097
- if (s.mtimeMs > newest) newest = s.mtimeMs;
12098
- } 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"})`);
12099
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.");
12100
12710
  }
12101
- } catch {
12102
- }
12103
- return newest;
12104
- }
12105
- async function indexSqliteBuildCommand(options = {}) {
12106
- const mod = await loadIndexSqlite();
12107
- if (!mod) return;
12108
- const result = await mod.buildIndex({
12109
- traceDir: options.dir,
12110
- maxRuns: parsePositiveInt(options.maxRuns, "--max-runs")
12111
- });
12112
- if (options.json) {
12113
- console.log(JSON.stringify({ ok: true, ...result }, null, 2));
12114
- return;
12115
- }
12116
- console.log(`Built SQLite index: ${result.dbPath}`);
12117
- console.log(`Runs: ${result.runs} Steps: ${result.steps} Errors: ${result.errors}`);
12118
- for (const warning of result.warnings) console.log(`warning: ${warning}`);
12119
- }
12120
- async function indexSqliteStatusCommand(options = {}) {
12121
- const mod = await loadIndexSqlite();
12122
- if (!mod) return;
12123
- const traceDir = resolveTraceDir({ dir: options.dir });
12124
- const dbPath = mod.resolveIndexDbPath(traceDir);
12125
- const status = mod.indexStatus(dbPath);
12126
- const stale = mod.isIndexStale(dbPath, await newestTraceMtimeMs(traceDir));
12127
- if (options.json) {
12128
- console.log(JSON.stringify({ ok: true, traceDir, stale, ...status }, null, 2));
12129
- return;
12130
- }
12131
- if (!status.exists) {
12132
- console.log(`No SQLite index at ${dbPath}`);
12133
- console.log("Run: agent-inspect index sqlite build");
12134
- return;
12135
- }
12136
- console.log(`Index: ${status.dbPath}`);
12137
- console.log(`Healthy: ${status.healthy ? "yes" : "no"}`);
12138
- console.log(`Built: ${status.builtAt ?? "unknown"}`);
12139
- console.log(`Runs: ${status.runs} Steps: ${status.steps}`);
12140
- console.log(`Stale: ${stale ? "yes" : "no"}`);
12141
- }
12142
- async function indexSqliteQueryCommand(options = {}) {
12143
- const mod = await loadIndexSqlite();
12144
- if (!mod) return;
12145
- const traceDir = resolveTraceDir({ dir: options.dir });
12146
- const dbPath = mod.resolveIndexDbPath(traceDir);
12147
- const status = mod.indexStatus(dbPath);
12148
- if (!status.exists || !status.healthy) {
12711
+ } catch (error) {
12712
+ const msg = error instanceof Error ? error.message : String(error);
12149
12713
  if (options.json) {
12150
- console.log(JSON.stringify({ ok: false, reason: "index-missing", dbPath }, null, 2));
12714
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
12151
12715
  } else {
12152
- console.log("No usable SQLite index. Run: agent-inspect index sqlite build");
12716
+ console.error(`[AgentInspect] init failed: ${msg}`);
12153
12717
  }
12154
12718
  process.exitCode = 1;
12155
- return;
12156
- }
12157
- const rows = mod.queryRuns(dbPath, {
12158
- status: options.status,
12159
- sessionId: options.session,
12160
- name: options.name,
12161
- kind: options.kind,
12162
- tool: options.tool,
12163
- limit: parsePositiveInt(options.limit, "--limit")
12164
- });
12165
- if (options.json) {
12166
- console.log(JSON.stringify({ ok: true, count: rows.length, runs: rows }, null, 2));
12167
- return;
12168
- }
12169
- if (rows.length === 0) {
12170
- console.log("No matching runs.");
12171
- return;
12172
- }
12173
- for (const run of rows) {
12174
- const parts = [
12175
- run.runId,
12176
- run.status ?? "unknown",
12177
- run.name ?? "",
12178
- run.durationMs != null ? `${run.durationMs}ms` : ""
12179
- ].filter((p) => p !== "");
12180
- console.log(parts.join(" "));
12181
- }
12182
- }
12183
- async function indexSqliteCleanCommand(options = {}) {
12184
- const mod = await loadIndexSqlite();
12185
- if (!mod) return;
12186
- const traceDir = resolveTraceDir({ dir: options.dir });
12187
- const dbPath = mod.resolveIndexDbPath(traceDir);
12188
- await mod.cleanIndex(dbPath);
12189
- if (options.json) {
12190
- console.log(JSON.stringify({ ok: true, removed: dbPath }, null, 2));
12191
- return;
12192
12719
  }
12193
- console.log(`Removed SQLite index: ${dbPath}`);
12194
12720
  }
12195
-
12196
- // packages/core/src/workspace/types.ts
12197
- var WORKSPACE_SCHEMA_VERSION = "1.0";
12198
- var WORKSPACE_DIR_NAME = ".agent-inspect";
12199
- var WORKSPACE_MANIFEST_FILENAME = "workspace.json";
12200
-
12201
- // packages/core/src/workspace/manifest.ts
12202
- var DEFAULT_WORKSPACE_LAYOUT = {
12203
- traceDirs: ["runs"],
12204
- reportsDir: "reports",
12205
- artifactsDir: "artifacts",
12206
- bundlesDir: "bundles",
12207
- 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"]
12208
12726
  };
12209
- var DEFAULT_REDACTION_PROFILE = "share";
12210
- var REDACTION_PROFILES2 = [
12211
- "local",
12212
- "share",
12213
- "strict"
12214
- ];
12215
- var INDEX_TYPES = ["none", "sqlite", "custom"];
12216
- var MAX_WORKSPACE_MANIFEST_BYTES = 64 * 1024;
12217
- function createDefaultWorkspaceManifest(options) {
12218
- const project = typeof options.project === "string" ? options.project.trim() : "";
12219
- const index = {
12220
- enabled: options.index?.enabled ?? false,
12221
- type: options.index?.type ?? "none",
12222
- ...options.index?.path !== void 0 ? { path: options.index.path } : {}
12223
- };
12224
- return {
12225
- schemaVersion: WORKSPACE_SCHEMA_VERSION,
12226
- project,
12227
- createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
12228
- traceDirs: options.traceDirs ? [...options.traceDirs] : [...DEFAULT_WORKSPACE_LAYOUT.traceDirs],
12229
- reportsDir: options.reportsDir ?? DEFAULT_WORKSPACE_LAYOUT.reportsDir,
12230
- artifactsDir: options.artifactsDir ?? DEFAULT_WORKSPACE_LAYOUT.artifactsDir,
12231
- bundlesDir: options.bundlesDir ?? DEFAULT_WORKSPACE_LAYOUT.bundlesDir,
12232
- notesDir: options.notesDir ?? DEFAULT_WORKSPACE_LAYOUT.notesDir,
12233
- redactionProfile: options.redactionProfile ?? DEFAULT_REDACTION_PROFILE,
12234
- index
12235
- };
12236
- }
12237
- function isPlainObject(value) {
12238
- return typeof value === "object" && value !== null && !Array.isArray(value);
12239
- }
12240
- function isSafeRelativeWorkspacePath(p) {
12241
- if (typeof p !== "string") return false;
12242
- const trimmed = p.trim();
12243
- if (trimmed === "") return false;
12244
- if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return false;
12245
- if (/^[a-zA-Z]:/.test(trimmed)) return false;
12246
- const segments = trimmed.split(/[/\\]+/);
12247
- return !segments.some((seg) => seg === "..");
12248
- }
12249
- function validateDirField(value, field, errors) {
12250
- if (typeof value !== "string" || value.trim() === "") {
12251
- errors.push(`${field} must be a non-empty string`);
12252
- return;
12253
- }
12254
- if (!isSafeRelativeWorkspacePath(value)) {
12255
- errors.push(
12256
- `${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
12257
- );
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
+ };
12258
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
+ };
12259
12744
  }
12260
- function validateIndex(value, errors) {
12261
- if (!isPlainObject(value)) {
12262
- errors.push("index must be an object");
12263
- return void 0;
12264
- }
12265
- if (typeof value.enabled !== "boolean") {
12266
- errors.push("index.enabled must be a boolean");
12267
- }
12268
- if (!INDEX_TYPES.includes(value.type)) {
12269
- errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
12270
- }
12271
- if (value.path !== void 0 && !isSafeRelativeWorkspacePath(value.path)) {
12272
- errors.push(
12273
- 'index.path must be a relative path inside the workspace (no absolute paths or ".." traversal)'
12274
- );
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
+ };
12275
12754
  }
12276
- if (errors.length > 0) return void 0;
12277
12755
  return {
12278
- enabled: value.enabled,
12279
- type: value.type,
12280
- ...value.path !== void 0 ? { path: value.path } : {}
12756
+ id: `env-${name.toLowerCase()}`,
12757
+ status: "pass",
12758
+ message: `${name} is set.`,
12759
+ evidence: value
12281
12760
  };
12282
12761
  }
12283
- function validateWorkspaceManifest(input3) {
12284
- const errors = [];
12285
- const warnings = [];
12286
- if (!isPlainObject(input3)) {
12287
- return { ok: false, errors: ["manifest must be an object"], warnings };
12288
- }
12289
- if (input3.schemaVersion !== WORKSPACE_SCHEMA_VERSION) {
12290
- errors.push(
12291
- `schemaVersion must be "${WORKSPACE_SCHEMA_VERSION}" (received ${JSON.stringify(
12292
- input3.schemaVersion
12293
- )})`
12294
- );
12295
- }
12296
- if (typeof input3.project !== "string" || input3.project.trim() === "") {
12297
- 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
+ };
12298
12781
  }
12299
- if (typeof input3.createdAt !== "string" || input3.createdAt.trim() === "") {
12300
- errors.push("createdAt must be a non-empty ISO-8601 string");
12301
- } else if (Number.isNaN(Date.parse(input3.createdAt))) {
12302
- 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 };
12303
12791
  }
12304
- if (!Array.isArray(input3.traceDirs) || input3.traceDirs.length === 0) {
12305
- errors.push("traceDirs must be a non-empty array");
12306
- } else {
12307
- input3.traceDirs.forEach((dir, i) => {
12308
- if (typeof dir !== "string" || dir.trim() === "") {
12309
- errors.push(`traceDirs[${i}] must be a non-empty string`);
12310
- } else if (!isSafeRelativeWorkspacePath(dir)) {
12311
- errors.push(
12312
- `traceDirs[${i}] must be a relative path inside the workspace (no absolute paths or ".." traversal)`
12313
- );
12314
- }
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)."
12315
12809
  });
12316
12810
  }
12317
- validateDirField(input3.reportsDir, "reportsDir", errors);
12318
- validateDirField(input3.artifactsDir, "artifactsDir", errors);
12319
- validateDirField(input3.bundlesDir, "bundlesDir", errors);
12320
- validateDirField(input3.notesDir, "notesDir", errors);
12321
- if (!REDACTION_PROFILES2.includes(input3.redactionProfile)) {
12322
- 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
+ });
12323
12824
  }
12324
- const indexErrors = [];
12325
- const index = validateIndex(input3.index, indexErrors);
12326
- errors.push(...indexErrors);
12327
- if (index && index.type !== "none" && !index.enabled) {
12328
- 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
+ };
12329
12861
  }
12330
- if (errors.length > 0 || index === void 0) {
12331
- 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
+ };
12332
12870
  }
12333
- const manifest = {
12334
- schemaVersion: WORKSPACE_SCHEMA_VERSION,
12335
- project: input3.project.trim(),
12336
- createdAt: input3.createdAt,
12337
- traceDirs: input3.traceDirs.map((d) => d.trim()),
12338
- reportsDir: input3.reportsDir.trim(),
12339
- artifactsDir: input3.artifactsDir.trim(),
12340
- bundlesDir: input3.bundlesDir.trim(),
12341
- notesDir: input3.notesDir.trim(),
12342
- redactionProfile: input3.redactionProfile,
12343
- index
12871
+ return {
12872
+ id: "version-alignment",
12873
+ status: "pass",
12874
+ message: `CLI and local agent-inspect are aligned at ${version}.`
12344
12875
  };
12345
- return { ok: true, manifest, errors, warnings };
12346
12876
  }
12347
- function parseWorkspaceManifest(json) {
12348
- const warnings = [];
12349
- if (typeof json !== "string") {
12350
- 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));
12351
12895
  }
12352
- if (json.length > MAX_WORKSPACE_MANIFEST_BYTES) {
12353
- return {
12354
- ok: false,
12355
- errors: [
12356
- `manifest exceeds maximum size of ${MAX_WORKSPACE_MANIFEST_BYTES} bytes`
12357
- ],
12358
- warnings
12359
- };
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;
12360
12918
  }
12361
- let parsed;
12362
- try {
12363
- parsed = JSON.parse(json);
12364
- } catch {
12365
- 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}`);
12366
12924
  }
12367
- return validateWorkspaceManifest(parsed);
12368
- }
12369
- function serializeWorkspaceManifest(manifest) {
12370
- return `${JSON.stringify(manifest, null, 2)}
12371
- `;
12925
+ console.log(`
12926
+ Summary: ${failed} failed, ${warned} warnings`);
12927
+ if (failed > 0) process2.exitCode = 1;
12372
12928
  }
12373
- var INDEX_DIR_NAME = "index";
12374
- function resolveWorkspaceLocation(cwd = process.cwd()) {
12375
- const projectRoot = path10.resolve(cwd);
12376
- const workspaceDir = path10.join(projectRoot, WORKSPACE_DIR_NAME);
12377
- return {
12378
- projectRoot,
12379
- workspaceDir,
12380
- manifestPath: path10.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
12381
- };
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;
12382
12934
  }
12383
- function resolveInsideWorkspace(workspaceDir, relative) {
12384
- const base = path10.resolve(workspaceDir);
12385
- const resolved = path10.resolve(base, relative);
12386
- const rel = path10.relative(base, resolved);
12387
- if (rel === "" || rel === "." || !rel.startsWith("..") && !path10.isAbsolute(rel)) {
12388
- 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;
12389
12943
  }
12390
- throw new Error(
12391
- `Workspace path "${relative}" resolves outside the workspace directory`
12392
- );
12944
+ return false;
12393
12945
  }
12394
- async function pathExists(p) {
12395
- try {
12396
- await access(p);
12397
- return true;
12398
- } catch {
12399
- return false;
12400
- }
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
+ });
12401
12985
  }
12402
- async function isWritable(p) {
12403
- try {
12404
- await access(p, constants$1.W_OK);
12405
- return true;
12406
- } catch {
12407
- 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.");
12408
12997
  }
12998
+ return parsed;
12409
12999
  }
12410
- async function listJsonl(dir) {
13000
+ async function readSnapshot(indexPath2) {
12411
13001
  try {
12412
- const entries = await readdir(dir);
12413
- return entries.filter((f) => f.endsWith(".jsonl"));
13002
+ const raw = await readFile(indexPath2, "utf8");
13003
+ return JSON.parse(raw);
12414
13004
  } catch {
12415
- return [];
13005
+ return void 0;
12416
13006
  }
12417
13007
  }
12418
- async function countFiles(dir) {
13008
+ async function indexBuildCommand(options = {}) {
12419
13009
  try {
12420
- const entries = await readdir(dir, { withFileTypes: true });
12421
- return entries.filter((e) => e.isFile()).length;
12422
- } catch {
12423
- 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;
12424
13034
  }
12425
13035
  }
12426
- async function readWorkspaceManifestFile(location) {
12427
- let raw;
13036
+ async function indexStatusCommand(options = {}) {
12428
13037
  try {
12429
- raw = await readFile(location.manifestPath, "utf-8");
12430
- } catch {
12431
- 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;
12432
13079
  }
12433
- const parsed = parseWorkspaceManifest(raw);
12434
- return {
12435
- exists: true,
12436
- ok: parsed.ok,
12437
- ...parsed.manifest ? { manifest: parsed.manifest } : {},
12438
- errors: parsed.errors,
12439
- warnings: parsed.warnings
12440
- };
12441
13080
  }
12442
- async function createWorkspace(options = {}) {
12443
- const location = resolveWorkspaceLocation(options.cwd);
12444
- const dryRun = options.dryRun === true;
12445
- const existing = await readWorkspaceManifestFile(location);
12446
- const topLevelTraces = await listJsonl(location.workspaceDir);
12447
- const detectedExistingTraces = topLevelTraces.length > 0;
12448
- let manifest;
12449
- let created;
12450
- let adopted;
12451
- if (existing.exists && existing.ok && existing.manifest) {
12452
- manifest = existing.manifest;
12453
- created = false;
12454
- adopted = true;
12455
- } else {
12456
- const project = options.project?.trim() || path10.basename(location.projectRoot) || "workspace";
12457
- const traceDirs = detectedExistingTraces ? ["runs", "."] : ["runs"];
12458
- manifest = createDefaultWorkspaceManifest({
12459
- project,
12460
- traceDirs,
12461
- ...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {}
12462
- });
12463
- created = true;
12464
- adopted = detectedExistingTraces || existing.exists && !existing.ok;
12465
- }
12466
- const relDirs = uniqueDirs([
12467
- ...manifest.traceDirs.filter((d) => d !== "."),
12468
- manifest.reportsDir,
12469
- manifest.artifactsDir,
12470
- manifest.bundlesDir,
12471
- manifest.notesDir,
12472
- INDEX_DIR_NAME
12473
- ]);
12474
- const createdDirs = [];
12475
- for (const rel of relDirs) {
12476
- const abs = resolveInsideWorkspace(location.workspaceDir, rel);
12477
- if (await pathExists(abs)) continue;
12478
- createdDirs.push(rel);
12479
- if (!dryRun) await mkdir(abs, { recursive: true });
12480
- }
12481
- if (!dryRun && created) {
12482
- await mkdir(location.workspaceDir, { recursive: true });
12483
- await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
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;
12484
13095
  }
12485
- return {
12486
- location,
12487
- manifest,
12488
- created,
12489
- adopted,
12490
- createdDirs,
12491
- detectedExistingTraces,
12492
- dryRun
12493
- };
12494
13096
  }
12495
- function uniqueDirs(dirs) {
12496
- const seen = /* @__PURE__ */ new Set();
12497
- const out = [];
12498
- for (const d of dirs) {
12499
- const t = d.trim();
12500
- if (t === "" || t === "." || seen.has(t)) continue;
12501
- seen.add(t);
12502
- 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;
12503
13116
  }
12504
- return out;
12505
13117
  }
12506
- async function getWorkspaceStatus(location, manifest) {
12507
- let traceFiles = 0;
12508
- for (const rel of manifest.traceDirs) {
12509
- const abs = resolveInsideWorkspace(location.workspaceDir, rel);
12510
- 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.`);
12511
13123
  }
12512
- const reports = await countFiles(
12513
- resolveInsideWorkspace(location.workspaceDir, manifest.reportsDir)
12514
- );
12515
- const artifacts = await countFiles(
12516
- resolveInsideWorkspace(location.workspaceDir, manifest.artifactsDir)
12517
- );
12518
- const bundles = await countFiles(
12519
- resolveInsideWorkspace(location.workspaceDir, manifest.bundlesDir)
12520
- );
12521
- const notes = await countFiles(
12522
- resolveInsideWorkspace(location.workspaceDir, manifest.notesDir)
12523
- );
12524
- const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
12525
- return {
12526
- project: manifest.project,
12527
- traceFiles,
12528
- reports,
12529
- artifacts,
12530
- bundles,
12531
- notes,
12532
- index: {
12533
- enabled: manifest.index.enabled,
12534
- type: manifest.index.type,
12535
- 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
+ }
12536
13137
  }
12537
- };
13138
+ } catch {
13139
+ }
13140
+ return newest;
12538
13141
  }
12539
- async function doctorWorkspace(location) {
12540
- const checks2 = [];
12541
- const manifestResult = await readWorkspaceManifestFile(location);
12542
- if (!manifestResult.exists) {
12543
- checks2.push({
12544
- id: "manifest",
12545
- status: "fail",
12546
- message: "workspace.json not found (run `agent-inspect workspace init`)"
12547
- });
12548
- 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;
12549
13152
  }
12550
- if (!manifestResult.ok || !manifestResult.manifest) {
12551
- checks2.push({
12552
- id: "manifest",
12553
- status: "fail",
12554
- message: `workspace.json is invalid: ${manifestResult.errors.join("; ")}`
12555
- });
12556
- 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;
12557
13167
  }
12558
- const manifest = manifestResult.manifest;
12559
- checks2.push({ id: "manifest", status: "pass", message: "workspace.json is valid" });
12560
- for (const warning of manifestResult.warnings) {
12561
- 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;
12562
13172
  }
12563
- const dirFields = [
12564
- ...manifest.traceDirs.filter((d) => d !== ".").map((d, i) => [`traceDir[${i}]`, d]),
12565
- ["reportsDir", manifest.reportsDir],
12566
- ["artifactsDir", manifest.artifactsDir],
12567
- ["bundlesDir", manifest.bundlesDir],
12568
- ["notesDir", manifest.notesDir]
12569
- ];
12570
- for (const [id, rel] of dirFields) {
12571
- let abs;
12572
- try {
12573
- abs = resolveInsideWorkspace(location.workspaceDir, rel);
12574
- } catch (error) {
12575
- checks2.push({
12576
- id,
12577
- status: "fail",
12578
- message: error instanceof Error ? error.message : String(error)
12579
- });
12580
- continue;
12581
- }
12582
- if (!await pathExists(abs)) {
12583
- checks2.push({ id, status: "warn", message: `${rel}/ does not exist yet` });
12584
- } else if (!await isWritable(abs)) {
12585
- 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));
12586
13188
  } else {
12587
- checks2.push({ id, status: "pass", message: `${rel}/ is present and writable` });
13189
+ console.log("No usable SQLite index. Run: agent-inspect index sqlite build");
12588
13190
  }
13191
+ process.exitCode = 1;
13192
+ return;
12589
13193
  }
12590
- let newestTraceMtime = 0;
12591
- for (const rel of manifest.traceDirs) {
12592
- const abs = resolveInsideWorkspace(location.workspaceDir, rel);
12593
- for (const file of await listJsonl(abs)) {
12594
- try {
12595
- const s = await stat(path10.join(abs, file));
12596
- newestTraceMtime = Math.max(newestTraceMtime, s.mtimeMs);
12597
- } catch {
12598
- checks2.push({ id: "trace-readability", status: "warn", message: `cannot stat ${rel}/${file}` });
12599
- }
12600
- }
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;
12601
13205
  }
12602
- if (manifest.index.enabled) {
12603
- const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
12604
- if (!await pathExists(indexPath2)) {
12605
- checks2.push({ id: "index", status: "warn", message: "index enabled but not built" });
12606
- } else {
12607
- try {
12608
- const s = await stat(indexPath2);
12609
- if (newestTraceMtime > s.mtimeMs) {
12610
- checks2.push({ id: "index", status: "warn", message: "index is stale (traces are newer)" });
12611
- } else {
12612
- checks2.push({ id: "index", status: "pass", message: "index is present" });
12613
- }
12614
- } catch {
12615
- checks2.push({ id: "index", status: "warn", message: "cannot stat index" });
12616
- }
12617
- }
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(" "));
12618
13218
  }
12619
- const ok = !checks2.some((c) => c.status === "fail");
12620
- return { ok, checks: checks2 };
12621
13219
  }
12622
- async function cleanWorkspace(location, manifest, options = {}) {
12623
- const dryRun = options.confirm !== true;
12624
- const targets = uniqueDirs([
12625
- manifest.reportsDir,
12626
- manifest.artifactsDir,
12627
- manifest.bundlesDir,
12628
- manifest.index.path ?? INDEX_DIR_NAME
12629
- ]);
12630
- const removed = [];
12631
- for (const rel of targets) {
12632
- const abs = resolveInsideWorkspace(location.workspaceDir, rel);
12633
- let entries;
12634
- try {
12635
- entries = await readdir(abs);
12636
- } catch {
12637
- continue;
12638
- }
12639
- for (const entry of entries) {
12640
- const relPath = `${rel}/${entry}`;
12641
- removed.push(relPath);
12642
- if (!dryRun) {
12643
- await rm(path10.join(abs, entry), { recursive: true, force: true });
12644
- }
12645
- }
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;
12646
13229
  }
12647
- return { dryRun, removed };
13230
+ console.log(`Removed SQLite index: ${dbPath}`);
12648
13231
  }
12649
13232
 
12650
13233
  // packages/cli/src/workspace.ts
@@ -12996,6 +13579,11 @@ function createCliProgram() {
12996
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) => {
12997
13580
  runCommand(() => artifactsCommand(target, opts));
12998
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
+ });
12999
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) => {
13000
13588
  runCommand(() => ciSummaryCommand(manifest, opts));
13001
13589
  });
@@ -13046,12 +13634,32 @@ function createCliProgram() {
13046
13634
  ).option("--json", "print results as JSON").action((opts) => {
13047
13635
  runCommand(() => searchCommand(opts));
13048
13636
  });
13049
- program.command("sessions").description("List workflow sessions grouped from local trace metadata (read-only)").option("--dir <path>", "trace directory").option(
13637
+ const sessionsCmd = program.command("sessions").description(
13638
+ "Workflow sessions and activity from local trace metadata (read-only, v4.2+)"
13639
+ ).option("--dir <path>", "trace directory").option(
13050
13640
  "--correlate-group",
13051
13641
  "treat shared groupId as a synthetic session when sessionId is absent"
13052
- ).option("--json", "print sessions index as JSON").action((opts) => {
13642
+ ).option("--json", "print JSON output").option(
13643
+ "--stale-after <duration>",
13644
+ "mark sessions stale after inactivity (e.g. 24h, 7d)"
13645
+ ).action((opts) => {
13053
13646
  runCommand(() => sessionsCommand(opts));
13054
13647
  });
13648
+ sessionsCmd.command("latest").description("Show the most recently active session").option("--dir <path>", "trace directory").option("--correlate-group", "include synthetic group: sessions").option("--stale-after <duration>", "staleness threshold for status derivation").option("--json", "print JSON result").action((opts) => {
13649
+ runCommand(() => sessionsLatestCommand(opts));
13650
+ });
13651
+ sessionsCmd.command("activity").description("Summarize recent session activity").option("--dir <path>", "trace directory").option("--since <duration>", "activity window (default 7d)").option("--correlate-group", "include synthetic group: sessions").option("--stale-after <duration>", "staleness threshold for status derivation").option("--json", "print JSON result").action((opts) => {
13652
+ runCommand(() => sessionsActivityCommand(opts));
13653
+ });
13654
+ sessionsCmd.command("show").description("Show one session (alias for session <id>)").argument("<session-id>", "session id").option("--dir <path>", "trace directory").option("--timeline", "include per-run timelines").option("--critical-path", "include critical path section").option("--diagnostics", "include ambiguity warnings").option("--json", "print JSON result").action((sessionId, opts) => {
13655
+ runCommand(() => sessionsShowCommand(sessionId, opts));
13656
+ });
13657
+ sessionsCmd.command("handoffs").description("List handoff edges across sessions").option("--dir <path>", "trace directory").option("--session <id>", "limit to one session").option("--correlate-group", "include synthetic group: sessions").option("--json", "print JSON result").action((opts) => {
13658
+ runCommand(() => sessionsHandoffsCommand(opts));
13659
+ });
13660
+ sessionsCmd.command("errors").description("List sessions with errors in a time window").option("--dir <path>", "trace directory").option("--since <duration>", "filter by last activity (default: all)").option("--correlate-group", "include synthetic group: sessions").option("--stale-after <duration>", "staleness threshold for status derivation").option("--json", "print JSON result").action((opts) => {
13661
+ runCommand(() => sessionsErrorsCommand(opts));
13662
+ });
13055
13663
  program.command("session").description("Inspect one workflow session: runs, handoffs, retries (read-only)").argument("<session-id>", "session id (from sessions output)").option("--dir <path>", "trace directory").option("--timeline", "include per-run timelines").option("--critical-path", "include critical path section").option("--diagnostics", "include ambiguity warnings").option("--json", "print session view as JSON").action((sessionId, opts) => {
13056
13664
  runCommand(() => sessionCommand(sessionId, opts));
13057
13665
  });
@@ -13180,9 +13788,9 @@ function isPrimaryModule() {
13180
13788
  if (!entry) return false;
13181
13789
  const selfPath = fileURLToPath(import.meta.url);
13182
13790
  try {
13183
- return realpathSync(path10.resolve(entry)) === realpathSync(path10.resolve(selfPath));
13791
+ return realpathSync(path13.resolve(entry)) === realpathSync(path13.resolve(selfPath));
13184
13792
  } catch {
13185
- return path10.resolve(entry) === path10.resolve(selfPath);
13793
+ return path13.resolve(entry) === path13.resolve(selfPath);
13186
13794
  }
13187
13795
  }
13188
13796
  if (isPrimaryModule()) {