agent-inspect 2.5.0 → 2.6.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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync, createReadStream } from 'fs';
3
- import path13 from 'path';
3
+ import path14 from 'path';
4
4
  import { fileURLToPath, pathToFileURL } from 'url';
5
5
  import { Command, Option } from 'commander';
6
6
  import { AsyncLocalStorage } from 'async_hooks';
@@ -10,9 +10,10 @@ import os from 'os';
10
10
  import process2, { stdin, stdout } from 'process';
11
11
  import tty from 'tty';
12
12
  import { createInterface } from 'readline';
13
+ import { createServer } from 'http';
13
14
 
14
15
  // package.json
15
- var version = "2.5.0";
16
+ var version = "2.6.0";
16
17
 
17
18
  // packages/core/src/correlation-metadata.ts
18
19
  var TRACE_CORRELATION_KEYS = [
@@ -744,7 +745,7 @@ function formatDuration(ms) {
744
745
  // packages/core/src/utils.ts
745
746
  var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
746
747
  var RUNS_DIR_NAME = "runs";
747
- var FALLBACK_TRACE_DIR = path13.join(
748
+ var FALLBACK_TRACE_DIR = path14.join(
748
749
  os.tmpdir(),
749
750
  "agent-inspect",
750
751
  RUNS_DIR_NAME
@@ -779,7 +780,7 @@ function getDefaultTraceDir() {
779
780
  if (typeof home !== "string" || home.trim() === "") {
780
781
  return FALLBACK_TRACE_DIR;
781
782
  }
782
- return path13.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
783
+ return path14.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
783
784
  } catch {
784
785
  return FALLBACK_TRACE_DIR;
785
786
  }
@@ -787,11 +788,11 @@ function getDefaultTraceDir() {
787
788
  function getTraceFilePath(runId, traceDir) {
788
789
  const baseDir = traceDir ?? getDefaultTraceDir();
789
790
  let safeId = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "run_unknown";
790
- safeId = path13.basename(safeId);
791
+ safeId = path14.basename(safeId);
791
792
  if (safeId === "" || safeId === "." || safeId === "..") {
792
793
  safeId = "run_unknown";
793
794
  }
794
- return path13.join(baseDir, `${safeId}.jsonl`);
795
+ return path14.join(baseDir, `${safeId}.jsonl`);
795
796
  }
796
797
  function formatError(error) {
797
798
  if (error instanceof Error) {
@@ -1553,7 +1554,7 @@ var TraceDirectory = class {
1553
1554
  this.#dir = resolveTraceDir(options);
1554
1555
  }
1555
1556
  getPath(filename) {
1556
- return filename ? path13.join(this.#dir, filename) : this.#dir;
1557
+ return filename ? path14.join(this.#dir, filename) : this.#dir;
1557
1558
  }
1558
1559
  async list() {
1559
1560
  try {
@@ -1580,7 +1581,7 @@ function parseIsoToMs2(value) {
1580
1581
  }
1581
1582
  async function extractMetadata(filePath, _quickScan) {
1582
1583
  const stats = await stat(filePath);
1583
- let runIdFromFile = path13.basename(filePath);
1584
+ let runIdFromFile = path14.basename(filePath);
1584
1585
  if (runIdFromFile.endsWith(".jsonl")) {
1585
1586
  runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
1586
1587
  }
@@ -3058,12 +3059,12 @@ function buildCriticalPath(runs, handoffs) {
3058
3059
  handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
3059
3060
  );
3060
3061
  const ordered = [...runs].sort(compareRuns);
3061
- const path15 = [];
3062
+ const path16 = [];
3062
3063
  const visited = /* @__PURE__ */ new Set();
3063
3064
  const pushRun = (run, confidence, source) => {
3064
3065
  if (visited.has(run.runId)) return;
3065
3066
  visited.add(run.runId);
3066
- path15.push({
3067
+ path16.push({
3067
3068
  runId: run.runId,
3068
3069
  name: run.name,
3069
3070
  startedAt: run.startedAt,
@@ -3088,7 +3089,7 @@ function buildCriticalPath(runs, handoffs) {
3088
3089
  const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
3089
3090
  pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
3090
3091
  }
3091
- return path15;
3092
+ return path16;
3092
3093
  }
3093
3094
  function metaRunIdMatches(run, token, runById) {
3094
3095
  const meta = extractSessionWorkflowMetadata(run.metadata);
@@ -4026,7 +4027,7 @@ function findReaderByFormat(format, readers) {
4026
4027
  }
4027
4028
  async function jsonlFilesInDirectory(dirPath) {
4028
4029
  const entries = await readdir(dirPath, { withFileTypes: true });
4029
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path13.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
4030
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path14.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
4030
4031
  }
4031
4032
  async function resolveInput(input3) {
4032
4033
  const cached = resolvedInputCache.get(input3);
@@ -8152,9 +8153,9 @@ Trace directory: ${traceDir}`);
8152
8153
  if (validation !== void 0 && !validation.ok) {
8153
8154
  process.exitCode = 1;
8154
8155
  }
8155
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
8156
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path14.resolve(options.output.trim()) : void 0;
8156
8157
  if (outPath !== void 0) {
8157
- await mkdir(path13.dirname(outPath), { recursive: true });
8158
+ await mkdir(path14.dirname(outPath), { recursive: true });
8158
8159
  await writeFile(outPath, result.content, "utf-8");
8159
8160
  const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
8160
8161
  console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
@@ -8331,13 +8332,13 @@ function pairSteps(left, right) {
8331
8332
  return pairs;
8332
8333
  }
8333
8334
  function compareLeafSteps(L, R, segments, opts, out) {
8334
- const path15 = buildPath(segments);
8335
+ const path16 = buildPath(segments);
8335
8336
  if (L.name !== R.name) {
8336
8337
  out.push({
8337
8338
  kind: "structure",
8338
8339
  severity: "warning",
8339
8340
  message: "Step name differs",
8340
- path: path15,
8341
+ path: path16,
8341
8342
  left: L.name,
8342
8343
  right: R.name
8343
8344
  });
@@ -8347,7 +8348,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8347
8348
  kind: "step-type",
8348
8349
  severity: "warning",
8349
8350
  message: "Step type differs",
8350
- path: path15,
8351
+ path: path16,
8351
8352
  left: L.type,
8352
8353
  right: R.type
8353
8354
  });
@@ -8357,7 +8358,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8357
8358
  kind: "step-status",
8358
8359
  severity: "warning",
8359
8360
  message: "Step status differs",
8360
- path: path15,
8361
+ path: path16,
8361
8362
  left: L.status,
8362
8363
  right: R.status
8363
8364
  });
@@ -8369,7 +8370,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8369
8370
  kind: "error",
8370
8371
  severity: "error",
8371
8372
  message: "Step error message differs",
8372
- path: path15,
8373
+ path: path16,
8373
8374
  left: le || void 0,
8374
8375
  right: re || void 0
8375
8376
  });
@@ -8387,7 +8388,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8387
8388
  kind: "duration",
8388
8389
  severity: "info",
8389
8390
  message: "Step duration differs",
8390
- path: path15,
8391
+ path: path16,
8391
8392
  left: ld,
8392
8393
  right: rd
8393
8394
  });
@@ -8400,7 +8401,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8400
8401
  kind: "metadata",
8401
8402
  severity: "info",
8402
8403
  message: "Step metadata differs",
8403
- path: path15,
8404
+ path: path16,
8404
8405
  left: L.metadata,
8405
8406
  right: R.metadata
8406
8407
  });
@@ -8412,7 +8413,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8412
8413
  kind: "output",
8413
8414
  severity: "info",
8414
8415
  message: "Output preview differs",
8415
- path: path15,
8416
+ path: path16,
8416
8417
  left: L.outputPreview,
8417
8418
  right: R.outputPreview
8418
8419
  });
@@ -8572,11 +8573,11 @@ function diffRuns(left, right, options) {
8572
8573
  }
8573
8574
 
8574
8575
  // packages/core/src/diff/renderer.ts
8575
- function formatPath(path15) {
8576
- if (path15 === void 0 || path15.path.length === 0) {
8576
+ function formatPath(path16) {
8577
+ if (path16 === void 0 || path16.path.length === 0) {
8577
8578
  return "(run)";
8578
8579
  }
8579
- return path15.path.map((s) => s.name).join(" > ");
8580
+ return path16.path.map((s) => s.name).join(" > ");
8580
8581
  }
8581
8582
  function formatValue(v, verbose) {
8582
8583
  if (v === void 0) return "(undefined)";
@@ -9205,9 +9206,9 @@ async function reportCommand(runId, options = {}) {
9205
9206
  redactionProfile,
9206
9207
  correlation: !options.noCorrelation
9207
9208
  });
9208
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
9209
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path14.resolve(options.output.trim()) : void 0;
9209
9210
  if (outPath !== void 0) {
9210
- await mkdir(path13.dirname(outPath), { recursive: true });
9211
+ await mkdir(path14.dirname(outPath), { recursive: true });
9211
9212
  await writeFile(outPath, result.content, "utf-8");
9212
9213
  console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
9213
9214
  }
@@ -9454,17 +9455,17 @@ function applyRule(rule, value, replacement) {
9454
9455
  }
9455
9456
  return value;
9456
9457
  }
9457
- function childPath(path15, key) {
9458
+ function childPath(path16, key) {
9458
9459
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
9459
- return path15 ? `${path15}.${key}` : key;
9460
+ return path16 ? `${path16}.${key}` : key;
9460
9461
  }
9461
- return `${path15 || "$"}[${JSON.stringify(key)}]`;
9462
+ return `${path16 || "$"}[${JSON.stringify(key)}]`;
9462
9463
  }
9463
- function indexPath(path15, index) {
9464
- return `${path15 || "$"}[${index}]`;
9464
+ function indexPath(path16, index) {
9465
+ return `${path16 || "$"}[${index}]`;
9465
9466
  }
9466
- function makeFinding(path15, detector, action, matchKind, severity = "warning", preview) {
9467
- return preview === void 0 ? { path: path15, detector, action, severity, matchKind } : { path: path15, detector, action, severity, matchKind, preview };
9467
+ function makeFinding(path16, detector, action, matchKind, severity = "warning", preview) {
9468
+ return preview === void 0 ? { path: path16, detector, action, severity, matchKind } : { path: path16, detector, action, severity, matchKind, preview };
9468
9469
  }
9469
9470
  function createRedactionProfile(profile = "local") {
9470
9471
  switch (profile) {
@@ -9533,11 +9534,11 @@ var Redactor2 = class {
9533
9534
  #recordFinding(state, finding) {
9534
9535
  if (this.#collectFindings) state.findings.push(finding);
9535
9536
  }
9536
- #redactValue(value, key, path15, depth, state) {
9537
+ #redactValue(value, key, path16, depth, state) {
9537
9538
  if (depth > this.#maxDepth) {
9538
9539
  this.#recordFinding(
9539
9540
  state,
9540
- makeFinding(path15, "structure.maxDepth", "truncate", "value", "warning")
9541
+ makeFinding(path16, "structure.maxDepth", "truncate", "value", "warning")
9541
9542
  );
9542
9543
  return "[Truncated]";
9543
9544
  }
@@ -9546,19 +9547,19 @@ var Redactor2 = class {
9546
9547
  if (rule) {
9547
9548
  this.#recordFinding(
9548
9549
  state,
9549
- makeFinding(path15, `key.${rule.key}`, actionForRule(rule), "key", "warning")
9550
+ makeFinding(path16, `key.${rule.key}`, actionForRule(rule), "key", "warning")
9550
9551
  );
9551
9552
  return applyRule(rule, value, this.#replacement);
9552
9553
  }
9553
9554
  }
9554
9555
  for (const detector of this.#detectors) {
9555
- const detections = detector.detect({ path: path15, key, value });
9556
+ const detections = detector.detect({ path: path16, key, value });
9556
9557
  for (const detection of detections) {
9557
9558
  const action = detection.action ?? "replace";
9558
9559
  this.#recordFinding(
9559
9560
  state,
9560
9561
  makeFinding(
9561
- path15,
9562
+ path16,
9562
9563
  detector.id,
9563
9564
  action,
9564
9565
  detection.matchKind ?? detector.matchKind ?? "custom",
@@ -9576,7 +9577,7 @@ var Redactor2 = class {
9576
9577
  const out = [];
9577
9578
  state.seen.set(value, out);
9578
9579
  value.forEach((item, index) => {
9579
- out[index] = this.#redactValue(item, void 0, indexPath(path15, index), depth + 1, state);
9580
+ out[index] = this.#redactValue(item, void 0, indexPath(path16, index), depth + 1, state);
9580
9581
  });
9581
9582
  return out;
9582
9583
  }
@@ -9588,7 +9589,7 @@ var Redactor2 = class {
9588
9589
  out[entryKey] = this.#redactValue(
9589
9590
  entryValue,
9590
9591
  entryKey,
9591
- childPath(path15 === "$" ? "" : path15, entryKey),
9592
+ childPath(path16 === "$" ? "" : path16, entryKey),
9592
9593
  depth + 1,
9593
9594
  state
9594
9595
  );
@@ -10035,14 +10036,14 @@ function uniqueSorted(values) {
10035
10036
  return [...new Set(values)].sort();
10036
10037
  }
10037
10038
  function isWithinDirectory(child, parent) {
10038
- const relative = path13.relative(parent, child);
10039
- return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
10039
+ const relative = path14.relative(parent, child);
10040
+ return relative === "" || !relative.startsWith("..") && !path14.isAbsolute(relative);
10040
10041
  }
10041
10042
  async function resolveOutputPath(inputPath, output2, force) {
10042
10043
  if (output2 === void 0 || output2.trim() === "") return void 0;
10043
- const inputAbs = path13.resolve(inputPath);
10044
- const outputAbs = path13.resolve(output2.trim());
10045
- const inputDir = path13.dirname(inputAbs);
10044
+ const inputAbs = path14.resolve(inputPath);
10045
+ const outputAbs = path14.resolve(output2.trim());
10046
+ const inputDir = path14.dirname(inputAbs);
10046
10047
  if (!isWithinDirectory(outputAbs, inputDir)) {
10047
10048
  throw new Error("Refusing to write migrated output outside the input directory.");
10048
10049
  }
@@ -10163,7 +10164,7 @@ async function migrateCommand(input3, options = {}) {
10163
10164
  process.exitCode = 1;
10164
10165
  return;
10165
10166
  }
10166
- const inputPath = path13.resolve(input3.trim());
10167
+ const inputPath = path14.resolve(input3.trim());
10167
10168
  const dryRun = options.dryRun === true;
10168
10169
  if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
10169
10170
  console.error("migrate requires --dry-run or --output <path>.");
@@ -10182,7 +10183,7 @@ async function migrateCommand(input3, options = {}) {
10182
10183
  );
10183
10184
  const result = await buildMigration(inputPath, outputPath);
10184
10185
  if (!dryRun && outputPath !== void 0) {
10185
- await mkdir(path13.dirname(outputPath), { recursive: true });
10186
+ await mkdir(path14.dirname(outputPath), { recursive: true });
10186
10187
  await writeFile(outputPath, result.content, "utf-8");
10187
10188
  }
10188
10189
  printSummary2(result, dryRun);
@@ -10460,7 +10461,7 @@ function stripPrefix(name, prefixes) {
10460
10461
  }
10461
10462
  return name;
10462
10463
  }
10463
- function eventEvidence(event, path15) {
10464
+ function eventEvidence(event, path16) {
10464
10465
  return {
10465
10466
  runId: event.runId,
10466
10467
  eventId: event.eventId,
@@ -10470,7 +10471,7 @@ function eventEvidence(event, path15) {
10470
10471
  kind: event.kind,
10471
10472
  name: event.name,
10472
10473
  status: event.status,
10473
- ...path15 ? { path: path15 } : {}
10474
+ ...path16 ? { path: path16 } : {}
10474
10475
  };
10475
10476
  }
10476
10477
  function runEvidence(run) {
@@ -10533,9 +10534,9 @@ function eventEndMs(event) {
10533
10534
  function normalizedKey(value) {
10534
10535
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
10535
10536
  }
10536
- function lastPathSegment(path15) {
10537
- const parts = path15.split(".");
10538
- return parts[parts.length - 1] ?? path15;
10537
+ function lastPathSegment(path16) {
10538
+ const parts = path16.split(".");
10539
+ return parts[parts.length - 1] ?? path16;
10539
10540
  }
10540
10541
  function valueType(value) {
10541
10542
  if (Array.isArray(value)) return "array";
@@ -10549,12 +10550,12 @@ function serializedByteLength(value) {
10549
10550
  return void 0;
10550
10551
  }
10551
10552
  }
10552
- function pushValueEntries(entries, event, value, path15, key, depth = 0) {
10553
- entries.push({ event, path: path15, key, value });
10553
+ function pushValueEntries(entries, event, value, path16, key, depth = 0) {
10554
+ entries.push({ event, path: path16, key, value });
10554
10555
  if (depth >= 8) return;
10555
10556
  if (Array.isArray(value)) {
10556
10557
  for (const [index, item] of value.entries()) {
10557
- pushValueEntries(entries, event, item, `${path15}.${index}`, String(index), depth + 1);
10558
+ pushValueEntries(entries, event, item, `${path16}.${index}`, String(index), depth + 1);
10558
10559
  }
10559
10560
  return;
10560
10561
  }
@@ -10564,7 +10565,7 @@ function pushValueEntries(entries, event, value, path15, key, depth = 0) {
10564
10565
  entries,
10565
10566
  event,
10566
10567
  value[nestedKey],
10567
- `${path15}.${nestedKey}`,
10568
+ `${path16}.${nestedKey}`,
10568
10569
  nestedKey,
10569
10570
  depth + 1
10570
10571
  );
@@ -10645,9 +10646,9 @@ function eventDurationMs(event) {
10645
10646
  }
10646
10647
  function treeShape(nodes) {
10647
10648
  const lines = [];
10648
- const visit = (node, path15) => {
10649
- lines.push(`${path15}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
10650
- node.children.forEach((child, index) => visit(child, `${path15}.${index}`));
10649
+ const visit = (node, path16) => {
10650
+ lines.push(`${path16}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
10651
+ node.children.forEach((child, index) => visit(child, `${path16}.${index}`));
10651
10652
  };
10652
10653
  nodes.forEach((node, index) => visit(node, String(index)));
10653
10654
  return lines;
@@ -10696,9 +10697,9 @@ function retrievalShape(context) {
10696
10697
  function guardrailShape(context) {
10697
10698
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
10698
10699
  }
10699
- function firstEvidenceForKind(context, kind, path15) {
10700
+ function firstEvidenceForKind(context, kind, path16) {
10700
10701
  const event = context.events.find((candidate) => candidate.kind === kind);
10701
- return event ? [eventEvidence(event, path15)] : runEvidence(context.selectedRun);
10702
+ return event ? [eventEvidence(event, path16)] : runEvidence(context.selectedRun);
10702
10703
  }
10703
10704
  function baselineDiffFinding(message, evidence, expected, actual) {
10704
10705
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -10946,13 +10947,13 @@ function createStructureCycleRule() {
10946
10947
  const seenCycles = /* @__PURE__ */ new Set();
10947
10948
  const findings = [];
10948
10949
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
10949
- const path15 = [];
10950
+ const path16 = [];
10950
10951
  const seenAt = /* @__PURE__ */ new Map();
10951
10952
  let current = event;
10952
10953
  while (current) {
10953
10954
  const existing = seenAt.get(current.eventId);
10954
10955
  if (existing !== void 0) {
10955
- const cycle = path15.slice(existing);
10956
+ const cycle = path16.slice(existing);
10956
10957
  const key = cycle.map((item) => item.eventId).sort().join("\0");
10957
10958
  if (!seenCycles.has(key)) {
10958
10959
  seenCycles.add(key);
@@ -10968,8 +10969,8 @@ function createStructureCycleRule() {
10968
10969
  }
10969
10970
  break;
10970
10971
  }
10971
- seenAt.set(current.eventId, path15.length);
10972
- path15.push(current);
10972
+ seenAt.set(current.eventId, path16.length);
10973
+ path16.push(current);
10973
10974
  current = current.parentId ? byId.get(current.parentId) : void 0;
10974
10975
  }
10975
10976
  }
@@ -11766,23 +11767,23 @@ function evaluatePromptInjection(text, options = {}) {
11766
11767
  }
11767
11768
  return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
11768
11769
  }
11769
- function validateSchemaField(value, field, path15, evidence) {
11770
+ function validateSchemaField(value, field, path16, evidence) {
11770
11771
  const ruleId = "guardrail.structured-output";
11771
11772
  if (field.type) {
11772
11773
  const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
11773
11774
  if (actual !== field.type) {
11774
- evidence.push({ ruleId, path: path15, preview: `expected ${field.type}, got ${actual}` });
11775
+ evidence.push({ ruleId, path: path16, preview: `expected ${field.type}, got ${actual}` });
11775
11776
  return;
11776
11777
  }
11777
11778
  }
11778
11779
  if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
11779
- evidence.push({ ruleId, path: path15, preview: "value not in enum" });
11780
+ evidence.push({ ruleId, path: path16, preview: "value not in enum" });
11780
11781
  }
11781
11782
  if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
11782
11783
  const record = value;
11783
11784
  for (const key of field.required) {
11784
11785
  if (!(key in record)) {
11785
- evidence.push({ ruleId, path: `${path15}.${key}`, preview: "missing required key" });
11786
+ evidence.push({ ruleId, path: `${path16}.${key}`, preview: "missing required key" });
11786
11787
  }
11787
11788
  }
11788
11789
  }
@@ -12109,7 +12110,7 @@ function asConfig(value) {
12109
12110
  }
12110
12111
  async function loadConfig(configPath) {
12111
12112
  if (configPath === void 0) return {};
12112
- const extension = path13.extname(configPath);
12113
+ const extension = path14.extname(configPath);
12113
12114
  if (TS_CONFIG_EXTENSIONS.has(extension)) {
12114
12115
  throw new Error(
12115
12116
  "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -12118,7 +12119,7 @@ async function loadConfig(configPath) {
12118
12119
  if (!CONFIG_EXTENSIONS.has(extension)) {
12119
12120
  throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
12120
12121
  }
12121
- const absolute = path13.resolve(configPath);
12122
+ const absolute = path14.resolve(configPath);
12122
12123
  if (extension === ".json") {
12123
12124
  const raw = await readFile(absolute, "utf-8");
12124
12125
  return asConfig(JSON.parse(raw));
@@ -12247,10 +12248,10 @@ function printHuman(result) {
12247
12248
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
12248
12249
  }
12249
12250
  for (const finding of result.findings) {
12250
- const path15 = finding.evidence[0]?.path;
12251
+ const path16 = finding.evidence[0]?.path;
12251
12252
  const run = finding.evidence[0]?.runId;
12252
12253
  const runPrefix = run ? `[${run}] ` : "";
12253
- console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
12254
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path16 ? ` (${path16})` : ""}`);
12254
12255
  }
12255
12256
  }
12256
12257
  function readErrorResult(error) {
@@ -12364,6 +12365,262 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
12364
12365
  else printHuman(result);
12365
12366
  }
12366
12367
 
12368
+ // packages/viewer/src/html.ts
12369
+ var viewerIndexHtml = `<!DOCTYPE html>
12370
+ <html lang="en">
12371
+ <head>
12372
+ <meta charset="utf-8" />
12373
+ <title>AgentInspect Viewer</title>
12374
+ <style>
12375
+ body { font-family: system-ui, sans-serif; margin: 1.5rem; line-height: 1.4; }
12376
+ h1 { font-size: 1.25rem; }
12377
+ pre { background: #f4f4f5; padding: 1rem; overflow: auto; max-height: 70vh; }
12378
+ a { color: #0b57d0; }
12379
+ .muted { color: #666; }
12380
+ </style>
12381
+ </head>
12382
+ <body>
12383
+ <h1>AgentInspect local viewer</h1>
12384
+ <p class="muted">Read-only. JSONL on disk remains canonical.</p>
12385
+ <p><a href="/api/health">/api/health</a> \xB7 <a href="/api/traces">/api/traces</a> \xB7 <a href="/api/sessions">/api/sessions</a></p>
12386
+ <pre id="out">Loading traces\u2026</pre>
12387
+ <script>
12388
+ fetch("/api/traces").then((r) => r.json()).then((data) => {
12389
+ document.getElementById("out").textContent = JSON.stringify(data, null, 2);
12390
+ }).catch((err) => {
12391
+ document.getElementById("out").textContent = String(err);
12392
+ });
12393
+ </script>
12394
+ </body>
12395
+ </html>
12396
+ `;
12397
+
12398
+ // packages/viewer/src/server.ts
12399
+ var DEFAULT_HOST = "127.0.0.1";
12400
+ var DEFAULT_PORT = 7337;
12401
+ var DEFAULT_MAX_EVENTS = 500;
12402
+ function sendJson(res, status, body) {
12403
+ const payload = JSON.stringify(body);
12404
+ res.writeHead(status, {
12405
+ "content-type": "application/json; charset=utf-8",
12406
+ "cache-control": "no-store"
12407
+ });
12408
+ res.end(payload);
12409
+ }
12410
+ function notFound(res, message) {
12411
+ sendJson(res, 404, { error: message });
12412
+ }
12413
+ function badRequest(res, message) {
12414
+ sendJson(res, 400, { error: message });
12415
+ }
12416
+ function decodeId(segment) {
12417
+ if (!segment) return "";
12418
+ try {
12419
+ return decodeURIComponent(segment);
12420
+ } catch {
12421
+ return segment;
12422
+ }
12423
+ }
12424
+ function boundedEvents(events, maxEvents) {
12425
+ if (events.length <= maxEvents) return [...events];
12426
+ return events.slice(0, maxEvents);
12427
+ }
12428
+ function createViewerServer(options = {}) {
12429
+ const traceDir = resolveTraceDir({ dir: options.traceDir });
12430
+ const host = options.host ?? DEFAULT_HOST;
12431
+ const port = options.port ?? DEFAULT_PORT;
12432
+ const maxEvents = options.maxEvents ?? DEFAULT_MAX_EVENTS;
12433
+ if (host === "0.0.0.0") {
12434
+ console.warn(
12435
+ "[AgentInspect viewer] Binding to 0.0.0.0 exposes traces on the network. Use 127.0.0.1 unless you accept that risk."
12436
+ );
12437
+ }
12438
+ const server = createServer(async (req, res) => {
12439
+ try {
12440
+ if (req.method !== "GET" && req.method !== "HEAD") {
12441
+ return badRequest(res, "Only GET is supported.");
12442
+ }
12443
+ const url = new URL(req.url ?? "/", `http://${host}:${port}`);
12444
+ const pathname = url.pathname;
12445
+ if (pathname === "/" || pathname === "/index.html") {
12446
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
12447
+ res.end(viewerIndexHtml);
12448
+ return;
12449
+ }
12450
+ if (pathname === "/api/health") {
12451
+ return sendJson(res, 200, {
12452
+ ok: true,
12453
+ readOnly: true,
12454
+ traceDir: path14.resolve(traceDir)
12455
+ });
12456
+ }
12457
+ const td = new TraceDirectory({ dir: traceDir });
12458
+ if (pathname === "/api/traces") {
12459
+ const files = await td.list();
12460
+ const metas = await loadTraceMetadataList(
12461
+ traceDir,
12462
+ files,
12463
+ (fileName) => td.getPath(fileName)
12464
+ );
12465
+ return sendJson(
12466
+ res,
12467
+ 200,
12468
+ metas.map((meta) => ({
12469
+ runId: meta.runId,
12470
+ name: meta.name,
12471
+ status: meta.status,
12472
+ file: path14.basename(meta.filePath),
12473
+ startedAt: meta.startedAt,
12474
+ durationMs: meta.durationMs
12475
+ }))
12476
+ );
12477
+ }
12478
+ if (pathname === "/api/sessions") {
12479
+ const files = await td.list();
12480
+ const metas = await loadTraceMetadataList(
12481
+ traceDir,
12482
+ files,
12483
+ (fileName) => td.getPath(fileName)
12484
+ );
12485
+ const runs = await loadSessionRunRecords(metas);
12486
+ const index = buildSessionIndex(runs, {
12487
+ correlateByGroupId: url.searchParams.get("correlateGroup") === "true"
12488
+ });
12489
+ return sendJson(res, 200, index);
12490
+ }
12491
+ const sessionMatch = pathname.match(/^\/api\/session\/([^/]+)$/);
12492
+ if (sessionMatch) {
12493
+ const sessionId = decodeId(sessionMatch[1]);
12494
+ const files = await td.list();
12495
+ const metas = await loadTraceMetadataList(
12496
+ traceDir,
12497
+ files,
12498
+ (fileName) => td.getPath(fileName)
12499
+ );
12500
+ const runs = await loadSessionRunRecords(metas);
12501
+ const index = buildSessionIndex(runs, {
12502
+ correlateByGroupId: url.searchParams.get("correlateGroup") === "true"
12503
+ });
12504
+ const session = index.sessions.find((item) => item.sessionId === sessionId);
12505
+ if (!session) return notFound(res, `Session not found: ${sessionId}`);
12506
+ return sendJson(res, 200, session);
12507
+ }
12508
+ const traceMatch = pathname.match(/^\/api\/trace\/([^/]+)$/);
12509
+ if (traceMatch) {
12510
+ const runId = decodeId(traceMatch[1]);
12511
+ const files = await td.list();
12512
+ const metas = await loadTraceMetadataList(
12513
+ traceDir,
12514
+ files,
12515
+ (fileName) => td.getPath(fileName)
12516
+ );
12517
+ const meta = metas.find((item) => item.runId === runId);
12518
+ if (!meta) return notFound(res, `Run not found: ${runId}`);
12519
+ const read = await openTrace({ type: "file", path: meta.filePath });
12520
+ const run = read.runs.find((item) => item.runId === runId) ?? read.runs[0];
12521
+ return sendJson(res, 200, {
12522
+ runId,
12523
+ format: read.format,
12524
+ run,
12525
+ events: boundedEvents(read.events, maxEvents),
12526
+ warnings: read.warnings,
12527
+ truncated: read.events.length > maxEvents
12528
+ });
12529
+ }
12530
+ const timelineMatch = pathname.match(/^\/api\/trace\/([^/]+)\/timeline$/);
12531
+ if (timelineMatch) {
12532
+ const runId = decodeId(timelineMatch[1]);
12533
+ const files = await td.list();
12534
+ const metas = await loadTraceMetadataList(
12535
+ traceDir,
12536
+ files,
12537
+ (fileName) => td.getPath(fileName)
12538
+ );
12539
+ const meta = metas.find((item) => item.runId === runId);
12540
+ if (!meta) return notFound(res, `Run not found: ${runId}`);
12541
+ const read = await openTrace({ type: "file", path: meta.filePath });
12542
+ const legacyEvents = persistedInspectEventsToTraceEvents(
12543
+ boundedEvents(read.events, maxEvents)
12544
+ );
12545
+ const timeline = buildRunTimeline(legacyEvents, { focus: "all" });
12546
+ return sendJson(res, 200, { runId, timeline });
12547
+ }
12548
+ const checkMatch = pathname.match(/^\/api\/trace\/([^/]+)\/check$/);
12549
+ if (checkMatch) {
12550
+ const runId = decodeId(checkMatch[1]);
12551
+ const files = await td.list();
12552
+ const metas = await loadTraceMetadataList(
12553
+ traceDir,
12554
+ files,
12555
+ (fileName) => td.getPath(fileName)
12556
+ );
12557
+ const meta = metas.find((item) => item.runId === runId);
12558
+ if (!meta) return notFound(res, `Run not found: ${runId}`);
12559
+ const read = await openTrace({ type: "file", path: meta.filePath });
12560
+ const result = runTraceChecks(
12561
+ { read },
12562
+ { rules: [createRunStatusRule()], select: ["run.status"], runId }
12563
+ );
12564
+ return sendJson(res, 200, result);
12565
+ }
12566
+ return notFound(res, `Unknown route: ${pathname}`);
12567
+ } catch (error) {
12568
+ const message = error instanceof Error ? error.message : String(error);
12569
+ sendJson(res, 500, { error: message });
12570
+ }
12571
+ });
12572
+ return server;
12573
+ }
12574
+ function startViewerServer(options = {}) {
12575
+ const host = options.host ?? DEFAULT_HOST;
12576
+ const port = options.port ?? DEFAULT_PORT;
12577
+ const traceDir = resolveTraceDir({ dir: options.traceDir });
12578
+ const server = createViewerServer(options);
12579
+ return new Promise((resolve, reject) => {
12580
+ server.once("error", reject);
12581
+ server.listen(port, host, () => {
12582
+ const address = server.address();
12583
+ const resolvedPort = typeof address === "object" && address ? address.port : port;
12584
+ resolve({
12585
+ host,
12586
+ port: resolvedPort,
12587
+ traceDir: path14.resolve(traceDir),
12588
+ url: `http://${host}:${resolvedPort}`
12589
+ });
12590
+ });
12591
+ });
12592
+ }
12593
+
12594
+ // packages/cli/src/serve.ts
12595
+ function parsePort(value) {
12596
+ if (value === void 0) return void 0;
12597
+ const parsed = Number(value);
12598
+ if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
12599
+ throw new Error("--port must be an integer between 1 and 65535.");
12600
+ }
12601
+ return parsed;
12602
+ }
12603
+ async function serveCommand(options = {}) {
12604
+ const port = parsePort(options.port);
12605
+ const host = options.host?.trim() || "127.0.0.1";
12606
+ const info = await startViewerServer({
12607
+ ...options.dir !== void 0 ? { traceDir: options.dir } : {},
12608
+ host,
12609
+ ...port !== void 0 ? { port } : {}
12610
+ });
12611
+ console.log(`AgentInspect viewer (read-only): ${info.url}`);
12612
+ console.log(`Trace directory: ${info.traceDir}`);
12613
+ if (options.open === true && host !== "127.0.0.1" && host !== "localhost") {
12614
+ console.warn("Skipping browser open for non-local host binding.");
12615
+ } else if (options.open === true) {
12616
+ const openMod = await import('child_process');
12617
+ openMod.exec(`open ${info.url}`, () => {
12618
+ });
12619
+ }
12620
+ await new Promise(() => {
12621
+ });
12622
+ }
12623
+
12367
12624
  // packages/eval/src/index.ts
12368
12625
  function isTraceReadResult(value) {
12369
12626
  return value !== null && typeof value === "object" && "runs" in value && "events" in value && "format" in value;
@@ -12532,10 +12789,10 @@ async function evalRun(input3, options = {}) {
12532
12789
  diagnostics: []
12533
12790
  };
12534
12791
  }
12535
- function evidenceForRun(run, path15) {
12536
- return [{ runId: run.runId, ...path15 !== void 0 ? { path: path15 } : {} }];
12792
+ function evidenceForRun(run, path16) {
12793
+ return [{ runId: run.runId, ...path16 !== void 0 ? { path: path16 } : {} }];
12537
12794
  }
12538
- function evidenceForEvent(event, path15) {
12795
+ function evidenceForEvent(event, path16) {
12539
12796
  return [
12540
12797
  {
12541
12798
  runId: event.runId,
@@ -12543,7 +12800,7 @@ function evidenceForEvent(event, path15) {
12543
12800
  ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
12544
12801
  kind: event.kind,
12545
12802
  name: event.name,
12546
- ...path15 !== void 0 ? { path: path15 } : {}
12803
+ ...path16 !== void 0 ? { path: path16 } : {}
12547
12804
  }
12548
12805
  ];
12549
12806
  }
@@ -12701,9 +12958,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
12701
12958
  function tokenize(text) {
12702
12959
  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));
12703
12960
  }
12704
- function firstEvidence(fields, run, path15) {
12961
+ function firstEvidence(fields, run, path16) {
12705
12962
  const first = fields[0];
12706
- return first === void 0 ? evidenceForRun(run, path15) : evidenceForEvent(first.node.event, first.path);
12963
+ return first === void 0 ? evidenceForRun(run, path16) : evidenceForEvent(first.node.event, first.path);
12707
12964
  }
12708
12965
  function collectSourceIds(nodes, keys) {
12709
12966
  const wanted = keySet(keys);
@@ -13080,8 +13337,8 @@ function renderEvalMarkdown(result) {
13080
13337
  if (result.findings.length > 0) {
13081
13338
  lines.push("", "## Findings");
13082
13339
  for (const finding of result.findings) {
13083
- const path15 = finding.evidence[0]?.path;
13084
- lines.push(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
13340
+ const path16 = finding.evidence[0]?.path;
13341
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path16 ? ` (${path16})` : ""}`);
13085
13342
  }
13086
13343
  }
13087
13344
  return `${lines.join("\n")}
@@ -13128,7 +13385,7 @@ function asConfig2(value) {
13128
13385
  }
13129
13386
  async function loadConfig2(configPath) {
13130
13387
  if (configPath === void 0) return {};
13131
- const extension = path13.extname(configPath);
13388
+ const extension = path14.extname(configPath);
13132
13389
  if (TS_CONFIG_EXTENSIONS2.has(extension)) {
13133
13390
  throw new Error(
13134
13391
  "TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -13137,7 +13394,7 @@ async function loadConfig2(configPath) {
13137
13394
  if (!CONFIG_EXTENSIONS2.has(extension)) {
13138
13395
  throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
13139
13396
  }
13140
- const absolute = path13.resolve(configPath);
13397
+ const absolute = path14.resolve(configPath);
13141
13398
  if (extension === ".json") {
13142
13399
  const raw = await readFile(absolute, "utf-8");
13143
13400
  return asConfig2(JSON.parse(raw));
@@ -13274,8 +13531,8 @@ function printHuman2(result) {
13274
13531
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
13275
13532
  }
13276
13533
  for (const finding of result.findings) {
13277
- const path15 = finding.evidence[0]?.path;
13278
- console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
13534
+ const path16 = finding.evidence[0]?.path;
13535
+ console.log(`- ${finding.ruleId}: ${finding.message}${path16 ? ` (${path16})` : ""}`);
13279
13536
  }
13280
13537
  }
13281
13538
  function readErrorResult2(error) {
@@ -13513,8 +13770,8 @@ function printHuman3(result) {
13513
13770
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
13514
13771
  }
13515
13772
  for (const finding of result.findings) {
13516
- const path15 = finding.evidence[0]?.path;
13517
- console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
13773
+ const path16 = finding.evidence[0]?.path;
13774
+ console.log(`- ${finding.ruleId}: ${finding.message}${path16 ? ` (${path16})` : ""}`);
13518
13775
  }
13519
13776
  console.log(`Note: ${result.note}`);
13520
13777
  }
@@ -13633,8 +13890,8 @@ function renderCheckSection(result) {
13633
13890
  `Diagnostics: ${result.diagnostics.length}`
13634
13891
  ];
13635
13892
  for (const finding of result.findings.slice(0, 10)) {
13636
- const path15 = finding.evidence[0]?.path ?? "(run)";
13637
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path15})`);
13893
+ const path16 = finding.evidence[0]?.path ?? "(run)";
13894
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path16})`);
13638
13895
  }
13639
13896
  for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
13640
13897
  lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
@@ -13712,8 +13969,8 @@ function renderHtml(trace, check, diff) {
13712
13969
  `;
13713
13970
  }
13714
13971
  async function writeArtifact(outputDir, relativePath, content, files) {
13715
- const outPath = path13.join(outputDir, relativePath);
13716
- await mkdir(path13.dirname(outPath), { recursive: true });
13972
+ const outPath = path14.join(outputDir, relativePath);
13973
+ await mkdir(path14.dirname(outPath), { recursive: true });
13717
13974
  await writeFile(outPath, content, "utf-8");
13718
13975
  files.push(relativePath);
13719
13976
  }
@@ -13729,7 +13986,7 @@ function manifestStatus(check, diff) {
13729
13986
  return "ok";
13730
13987
  }
13731
13988
  async function artifactsCommand(target, options = {}, stdin = process.stdin) {
13732
- const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path13.resolve(options.outputDir.trim()) : "";
13989
+ const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path14.resolve(options.outputDir.trim()) : "";
13733
13990
  if (outputDir === "") {
13734
13991
  console.error("--output-dir is required.");
13735
13992
  process.exitCode = 1;
@@ -13795,8 +14052,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
13795
14052
  await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
13796
14053
  const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
13797
14054
  if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
13798
- await mkdir(path13.dirname(path13.resolve(summaryTarget)), { recursive: true });
13799
- await appendFile(path13.resolve(summaryTarget), `
14055
+ await mkdir(path14.dirname(path14.resolve(summaryTarget)), { recursive: true });
14056
+ await appendFile(path14.resolve(summaryTarget), `
13800
14057
  ${renderMarkdown(trace, check, diff)}`, "utf-8");
13801
14058
  }
13802
14059
  const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
@@ -13815,10 +14072,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
13815
14072
  findings: diff?.findings.length ?? 0,
13816
14073
  diagnostics: diff?.diagnostics.length ?? 0
13817
14074
  },
13818
- ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path13.resolve(summaryTarget) } : {},
14075
+ ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path14.resolve(summaryTarget) } : {},
13819
14076
  note: NOTE
13820
14077
  };
13821
- await writeFile(path13.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
14078
+ await writeFile(path14.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
13822
14079
  if (options.json === true) {
13823
14080
  console.log(writeJson3(manifest).trimEnd());
13824
14081
  } else {
@@ -13830,7 +14087,7 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
13830
14087
  }
13831
14088
  }
13832
14089
  function validateReporterArtifactPath(options) {
13833
- const outputDir = path13.resolve(options.outputDir);
14090
+ const outputDir = path14.resolve(options.outputDir);
13834
14091
  const diagnostics = [];
13835
14092
  const rawPath = options.relativePath;
13836
14093
  if (rawPath.length === 0) {
@@ -13850,7 +14107,7 @@ function validateReporterArtifactPath(options) {
13850
14107
  });
13851
14108
  return { ok: false, outputDir, diagnostics };
13852
14109
  }
13853
- if (path13.isAbsolute(rawPath) || path13.win32.isAbsolute(rawPath)) {
14110
+ if (path14.isAbsolute(rawPath) || path14.win32.isAbsolute(rawPath)) {
13854
14111
  diagnostics.push({
13855
14112
  code: "artifact_path_absolute",
13856
14113
  severity: "error",
@@ -13859,7 +14116,7 @@ function validateReporterArtifactPath(options) {
13859
14116
  });
13860
14117
  return { ok: false, outputDir, diagnostics };
13861
14118
  }
13862
- const normalized = path13.posix.normalize(rawPath.replace(/\\/g, "/"));
14119
+ const normalized = path14.posix.normalize(rawPath.replace(/\\/g, "/"));
13863
14120
  const segments = normalized.split("/");
13864
14121
  if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
13865
14122
  diagnostics.push({
@@ -13870,9 +14127,9 @@ function validateReporterArtifactPath(options) {
13870
14127
  });
13871
14128
  return { ok: false, outputDir, diagnostics };
13872
14129
  }
13873
- const absolutePath = path13.resolve(outputDir, normalized);
13874
- const relFromOutput = path13.relative(outputDir, absolutePath);
13875
- if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path13.isAbsolute(relFromOutput)) {
14130
+ const absolutePath = path14.resolve(outputDir, normalized);
14131
+ const relFromOutput = path14.relative(outputDir, absolutePath);
14132
+ if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path14.isAbsolute(relFromOutput)) {
13876
14133
  diagnostics.push({
13877
14134
  code: "artifact_path_escape",
13878
14135
  severity: "error",
@@ -14018,23 +14275,23 @@ function readManifestDocument(value) {
14018
14275
  };
14019
14276
  }
14020
14277
  function cwdRelative(filePath) {
14021
- const relative = path13.relative(process.cwd(), path13.resolve(filePath)).replace(/\\/g, "/");
14022
- if (relative === "" || relative.startsWith("../") || path13.isAbsolute(relative)) {
14023
- return path13.basename(filePath);
14278
+ const relative = path14.relative(process.cwd(), path14.resolve(filePath)).replace(/\\/g, "/");
14279
+ if (relative === "" || relative.startsWith("../") || path14.isAbsolute(relative)) {
14280
+ return path14.basename(filePath);
14024
14281
  }
14025
14282
  return relative;
14026
14283
  }
14027
14284
  async function readReporterManifest(filePath) {
14028
- const absolute = path13.resolve(filePath);
14285
+ const absolute = path14.resolve(filePath);
14029
14286
  const raw = await readFile(absolute, "utf-8");
14030
14287
  const document = readManifestDocument(JSON.parse(raw));
14031
14288
  const manifest = document.manifest;
14032
14289
  const results = manifest.results.map((result) => ({
14033
14290
  testId: safeText(result.testId),
14034
14291
  name: safeText(result.name),
14035
- ...result.file === void 0 ? {} : { file: safeText(path13.basename(result.file)) },
14292
+ ...result.file === void 0 ? {} : { file: safeText(path14.basename(result.file)) },
14036
14293
  status: result.status,
14037
- ...result.tracePath === void 0 ? {} : { tracePath: safeText(path13.basename(result.tracePath)) },
14294
+ ...result.tracePath === void 0 ? {} : { tracePath: safeText(path14.basename(result.tracePath)) },
14038
14295
  artifacts: result.artifacts,
14039
14296
  diagnostics: result.diagnostics
14040
14297
  }));
@@ -14173,15 +14430,15 @@ async function ciSummaryCommand(manifestPaths, options = {}) {
14173
14430
  return;
14174
14431
  }
14175
14432
  const markdown = renderMarkdown2(result);
14176
- const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
14433
+ const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path14.resolve(options.output.trim()) : void 0;
14177
14434
  if (outputPath !== void 0) {
14178
- await mkdir(path13.dirname(outputPath), { recursive: true });
14435
+ await mkdir(path14.dirname(outputPath), { recursive: true });
14179
14436
  await writeFile(outputPath, markdown, "utf-8");
14180
14437
  }
14181
14438
  const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
14182
14439
  if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
14183
- const summaryPath = path13.resolve(summaryTarget);
14184
- await mkdir(path13.dirname(summaryPath), { recursive: true });
14440
+ const summaryPath = path14.resolve(summaryTarget);
14441
+ await mkdir(path14.dirname(summaryPath), { recursive: true });
14185
14442
  await appendFile(summaryPath, `
14186
14443
  ${markdown}`, "utf-8");
14187
14444
  }
@@ -14333,6 +14590,9 @@ function createCliProgram() {
14333
14590
  ).action((target, opts) => {
14334
14591
  runCommand(() => checkCommand(target, opts));
14335
14592
  });
14593
+ program.command("serve").description("Start optional localhost read-only trace viewer").option("--dir <path>", "trace directory to serve").option("--host <host>", "bind host (default 127.0.0.1)", "127.0.0.1").option("--port <number>", "bind port (default 7337)", "7337").option("--open", "open browser locally when host is localhost").action((opts) => {
14594
+ runCommand(() => serveCommand(opts));
14595
+ });
14336
14596
  program.command("eval").description("Run deterministic local evals against a trace").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
14337
14597
  new Option("--format <format>", "trace input format").choices([
14338
14598
  "agent-inspect-jsonl",
@@ -14497,9 +14757,9 @@ function isPrimaryModule() {
14497
14757
  if (!entry) return false;
14498
14758
  const selfPath = fileURLToPath(import.meta.url);
14499
14759
  try {
14500
- return realpathSync(path13.resolve(entry)) === realpathSync(path13.resolve(selfPath));
14760
+ return realpathSync(path14.resolve(entry)) === realpathSync(path14.resolve(selfPath));
14501
14761
  } catch {
14502
- return path13.resolve(entry) === path13.resolve(selfPath);
14762
+ return path14.resolve(entry) === path14.resolve(selfPath);
14503
14763
  }
14504
14764
  }
14505
14765
  if (isPrimaryModule()) {