@agent-inspect/viewer 6.9.0 → 6.10.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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createServer } from 'http';
2
- import path13 from 'path';
2
+ import path15 from 'path';
3
3
  import { AsyncLocalStorage } from 'async_hooks';
4
4
  import 'crypto';
5
5
  import { readFile, stat, readdir, access } from 'fs/promises';
@@ -518,7 +518,7 @@ function persistedInspectEventsToTraceEvents(events, options) {
518
518
  }
519
519
  var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
520
520
  var RUNS_DIR_NAME = "runs";
521
- var FALLBACK_TRACE_DIR = path13.join(
521
+ var FALLBACK_TRACE_DIR = path15.join(
522
522
  os.tmpdir(),
523
523
  "agent-inspect",
524
524
  RUNS_DIR_NAME
@@ -533,7 +533,7 @@ function getDefaultTraceDir() {
533
533
  if (typeof home !== "string" || home.trim() === "") {
534
534
  return FALLBACK_TRACE_DIR;
535
535
  }
536
- return path13.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
536
+ return path15.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
537
537
  } catch {
538
538
  return FALLBACK_TRACE_DIR;
539
539
  }
@@ -541,11 +541,11 @@ function getDefaultTraceDir() {
541
541
  function getTraceFilePath(runId, traceDir) {
542
542
  const baseDir = traceDir ?? getDefaultTraceDir();
543
543
  let safeId = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "run_unknown";
544
- safeId = path13.basename(safeId);
544
+ safeId = path15.basename(safeId);
545
545
  if (safeId === "" || safeId === "." || safeId === "..") {
546
546
  safeId = "run_unknown";
547
547
  }
548
- return path13.join(baseDir, `${safeId}.jsonl`);
548
+ return path15.join(baseDir, `${safeId}.jsonl`);
549
549
  }
550
550
  function formatError(error) {
551
551
  if (error instanceof Error) {
@@ -823,7 +823,7 @@ var TraceDirectory = class {
823
823
  this.#dir = resolveTraceDir(options);
824
824
  }
825
825
  getPath(filename) {
826
- return filename ? path13.join(this.#dir, filename) : this.#dir;
826
+ return filename ? path15.join(this.#dir, filename) : this.#dir;
827
827
  }
828
828
  async list() {
829
829
  try {
@@ -850,7 +850,7 @@ function parseIsoToMs2(value) {
850
850
  }
851
851
  async function extractMetadata(filePath, _quickScan) {
852
852
  const stats = await stat(filePath);
853
- let runIdFromFile = path13.basename(filePath);
853
+ let runIdFromFile = path15.basename(filePath);
854
854
  if (runIdFromFile.endsWith(".jsonl")) {
855
855
  runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
856
856
  }
@@ -1460,12 +1460,12 @@ function buildCriticalPath(runs, handoffs) {
1460
1460
  handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
1461
1461
  );
1462
1462
  const ordered = [...runs].sort(compareRuns);
1463
- const path16 = [];
1463
+ const path18 = [];
1464
1464
  const visited = /* @__PURE__ */ new Set();
1465
1465
  const pushRun = (run, confidence, source) => {
1466
1466
  if (visited.has(run.runId)) return;
1467
1467
  visited.add(run.runId);
1468
- path16.push({
1468
+ path18.push({
1469
1469
  runId: run.runId,
1470
1470
  name: run.name,
1471
1471
  startedAt: run.startedAt,
@@ -1490,7 +1490,7 @@ function buildCriticalPath(runs, handoffs) {
1490
1490
  const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
1491
1491
  pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
1492
1492
  }
1493
- return path16;
1493
+ return path18;
1494
1494
  }
1495
1495
  function metaRunIdMatches(run, token, runById) {
1496
1496
  const meta = extractSessionWorkflowMetadata(run.metadata);
@@ -1567,524 +1567,915 @@ function buildSessionIndex(inputRuns, options = {}) {
1567
1567
  };
1568
1568
  }
1569
1569
 
1570
- // packages/core/src/suite/types.ts
1571
- var DEFAULT_SUITE_CONFIG_NAMES = [
1572
- "agent-inspect.suite.json",
1573
- "agent-inspect.suite.js",
1574
- "agent-inspect.suite.mjs",
1575
- "agent-inspect.suite.cjs"
1576
- ];
1577
- function diagnostic(code, message, severity = "error", caseId) {
1578
- return { code, message, severity, ...{} };
1579
- }
1580
- function asString(value, label) {
1581
- if (value === void 0) return void 0;
1582
- if (typeof value !== "string" || value.trim() === "") {
1583
- throw new Error(`${label} must be a non-empty string.`);
1570
+ // packages/core/src/exporters/helpers.ts
1571
+ function sortKeysDeep(input) {
1572
+ if (input === null || typeof input !== "object") return input;
1573
+ if (Array.isArray(input)) return input.map(sortKeysDeep);
1574
+ const o = input;
1575
+ const out = {};
1576
+ for (const k of Object.keys(o).sort()) {
1577
+ out[k] = sortKeysDeep(o[k]);
1584
1578
  }
1585
- return value.trim();
1579
+ return out;
1586
1580
  }
1587
- function asStringArray(value, label) {
1588
- if (value === void 0) return void 0;
1589
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
1590
- throw new Error(`${label} must be an array of strings.`);
1591
- }
1592
- return value;
1581
+ function stableJson(value, pretty) {
1582
+ const sorted = sortKeysDeep(value);
1583
+ return JSON.stringify(sorted);
1593
1584
  }
1594
- function asPositiveNumber(value, label) {
1595
- if (value === void 0) return void 0;
1596
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1597
- throw new Error(`${label} must be a non-negative number.`);
1598
- }
1599
- return value;
1585
+
1586
+ // packages/core/src/diff/comparable.ts
1587
+ function extractOutputPreview(meta) {
1588
+ if (meta === void 0) return void 0;
1589
+ if ("outputPreview" in meta) return meta.outputPreview;
1590
+ if ("resultPreview" in meta) return meta.resultPreview;
1591
+ return void 0;
1600
1592
  }
1601
- function validateCaseConfig(value, index) {
1602
- const diagnostics = [];
1603
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
1604
- diagnostics.push(
1605
- diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}] must be an object.`)
1606
- );
1607
- return { diagnostics };
1608
- }
1609
- const raw = value;
1610
- try {
1611
- const id = asString(raw.id, `cases[${index}].id`);
1612
- if (id === void 0) {
1613
- diagnostics.push(
1614
- diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}].id is required.`)
1615
- );
1616
- return { diagnostics };
1617
- }
1618
- const trace = asString(raw.trace, `cases[${index}].trace`);
1619
- const runId = asString(raw.runId, `cases[${index}].runId`);
1620
- const input = asString(raw.input, `cases[${index}].input`);
1621
- return {
1622
- caseConfig: {
1623
- id,
1624
- ...trace !== void 0 ? { trace } : {},
1625
- ...runId !== void 0 ? { runId } : {},
1626
- ...input !== void 0 ? { input } : {},
1627
- ...asStringArray(raw.requireTools, `cases[${index}].requireTools`) !== void 0 ? { requireTools: asStringArray(raw.requireTools, `cases[${index}].requireTools`) } : {},
1628
- ...asStringArray(raw.forbidTools, `cases[${index}].forbidTools`) !== void 0 ? { forbidTools: asStringArray(raw.forbidTools, `cases[${index}].forbidTools`) } : {},
1629
- ...asPositiveNumber(raw.maxDurationMs, `cases[${index}].maxDurationMs`) !== void 0 ? {
1630
- maxDurationMs: asPositiveNumber(
1631
- raw.maxDurationMs,
1632
- `cases[${index}].maxDurationMs`
1633
- )
1634
- } : {},
1635
- ...asStringArray(
1636
- raw.expectedObservations,
1637
- `cases[${index}].expectedObservations`
1638
- ) !== void 0 ? {
1639
- expectedObservations: asStringArray(
1640
- raw.expectedObservations,
1641
- `cases[${index}].expectedObservations`
1642
- )
1643
- } : {}
1644
- },
1645
- diagnostics
1646
- };
1647
- } catch (error) {
1648
- const message = error instanceof Error ? error.message : String(error);
1649
- diagnostics.push(diagnostic("AI_SUITE_CONFIG_INVALID", message));
1650
- return { diagnostics };
1651
- }
1593
+ function mapStepStatus(s) {
1594
+ if (s === void 0) return "running";
1595
+ return s;
1652
1596
  }
1653
- function normalizeSuiteConfig(value) {
1654
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
1655
- throw new Error("Suite config must export an object.");
1656
- }
1657
- const raw = value;
1658
- const name = asString(raw.name, "name");
1659
- const traces = asString(raw.traces, "traces");
1660
- if (name === void 0) throw new Error("name is required.");
1661
- if (traces === void 0) throw new Error("traces is required.");
1662
- if (!Array.isArray(raw.cases) || raw.cases.length === 0) {
1663
- throw new Error("cases must be a non-empty array.");
1597
+ function manualTraceEventsToComparableRun(events) {
1598
+ const started = events.find((e) => e.event === "run_started");
1599
+ if (!started || started.event !== "run_started") {
1600
+ throw new Error("Invalid trace: missing run_started");
1664
1601
  }
1665
- const cases = [];
1666
- for (let index = 0; index < raw.cases.length; index += 1) {
1667
- const { caseConfig, diagnostics } = validateCaseConfig(raw.cases[index], index);
1668
- if (diagnostics.length > 0) {
1669
- throw new Error(diagnostics.map((item) => item.message).join("; "));
1670
- }
1671
- if (caseConfig !== void 0) cases.push(caseConfig);
1602
+ const rs = started;
1603
+ const runId = rs.runId;
1604
+ const completedAll = events.filter((e) => e.event === "run_completed");
1605
+ const lastCompleted = completedAll[completedAll.length - 1];
1606
+ let runStatus;
1607
+ if (lastCompleted === void 0) runStatus = "running";
1608
+ else runStatus = lastCompleted.status;
1609
+ const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
1610
+ const steps = /* @__PURE__ */ new Map();
1611
+ let order = 0;
1612
+ for (const e of events) {
1613
+ if (e.event !== "step_started") continue;
1614
+ const s = e;
1615
+ const meta = s.metadata ? { ...s.metadata } : void 0;
1616
+ steps.set(s.stepId, {
1617
+ id: s.stepId,
1618
+ parentId: s.parentId,
1619
+ name: s.name,
1620
+ type: s.type,
1621
+ order: order++,
1622
+ timestamp: s.timestamp,
1623
+ metadata: meta
1624
+ });
1672
1625
  }
1673
- const ids = /* @__PURE__ */ new Set();
1674
- for (const suiteCase of cases) {
1675
- if (ids.has(suiteCase.id)) {
1676
- throw new Error(`Duplicate case id "${suiteCase.id}".`);
1626
+ for (const e of events) {
1627
+ if (e.event !== "step_completed") continue;
1628
+ const acc = steps.get(e.stepId);
1629
+ if (!acc) continue;
1630
+ acc.status = e.status;
1631
+ acc.durationMs = e.durationMs;
1632
+ if (e.error?.message) acc.errorMsg = e.error.message;
1633
+ const extra = e;
1634
+ if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
1635
+ acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
1677
1636
  }
1678
- ids.add(suiteCase.id);
1679
1637
  }
1680
- const redactionProfile = raw.redactionProfile === "local" || raw.redactionProfile === "share" || raw.redactionProfile === "strict" ? raw.redactionProfile : void 0;
1681
- if (raw.redactionProfile !== void 0 && redactionProfile === void 0) {
1682
- throw new Error('redactionProfile must be "local", "share", or "strict".');
1683
- }
1684
- const config = { name, traces, cases };
1685
- if (raw.checks !== void 0) {
1686
- if (typeof raw.checks !== "object" || Array.isArray(raw.checks)) {
1687
- throw new Error("checks must be an object.");
1638
+ const nodes = /* @__PURE__ */ new Map();
1639
+ for (const acc of steps.values()) {
1640
+ let meta = acc.metadata ? { ...acc.metadata } : void 0;
1641
+ if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
1642
+ meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
1688
1643
  }
1689
- config.checks = raw.checks;
1690
- }
1691
- if (raw.eval !== void 0) {
1692
- if (typeof raw.eval !== "object" || Array.isArray(raw.eval)) {
1693
- throw new Error("eval must be an object.");
1644
+ const outputPreview = extractOutputPreview(meta);
1645
+ if (meta !== void 0 && ("outputPreview" in meta || "resultPreview" in meta)) {
1646
+ delete meta.outputPreview;
1647
+ delete meta.resultPreview;
1694
1648
  }
1695
- config.eval = raw.eval;
1649
+ const sc = {
1650
+ id: acc.id,
1651
+ name: acc.name,
1652
+ type: acc.type,
1653
+ status: mapStepStatus(acc.status),
1654
+ durationMs: acc.durationMs,
1655
+ error: acc.errorMsg,
1656
+ metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
1657
+ outputPreview,
1658
+ children: []
1659
+ };
1660
+ nodes.set(acc.id, sc);
1696
1661
  }
1697
- if (raw.artifacts !== void 0) {
1698
- if (typeof raw.artifacts !== "object" || Array.isArray(raw.artifacts)) {
1699
- throw new Error("artifacts must be an object.");
1662
+ const roots = [];
1663
+ const sortByOrder = (a, b) => {
1664
+ const oa = steps.get(a.id)?.order ?? 0;
1665
+ const ob = steps.get(b.id)?.order ?? 0;
1666
+ return oa - ob;
1667
+ };
1668
+ for (const acc of steps.values()) {
1669
+ const node = nodes.get(acc.id);
1670
+ if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
1671
+ nodes.get(acc.parentId).children.push(node);
1672
+ } else {
1673
+ roots.push(node);
1700
1674
  }
1701
- const outputDir = asString(
1702
- raw.artifacts.outputDir,
1703
- "artifacts.outputDir"
1704
- );
1705
- config.artifacts = outputDir !== void 0 ? { outputDir } : {};
1706
- }
1707
- if (raw.baseline !== void 0) {
1708
- const baseline = asString(raw.baseline, "baseline");
1709
- if (baseline !== void 0) config.baseline = baseline;
1710
1675
  }
1711
- if (raw.candidate !== void 0) {
1712
- const candidate = asString(raw.candidate, "candidate");
1713
- if (candidate !== void 0) config.candidate = candidate;
1676
+ roots.sort(sortByOrder);
1677
+ for (const n of nodes.values()) {
1678
+ n.children.sort(sortByOrder);
1714
1679
  }
1715
- if (redactionProfile !== void 0) config.redactionProfile = redactionProfile;
1716
- return config;
1680
+ return {
1681
+ runId,
1682
+ name: rs.name,
1683
+ status: runStatus,
1684
+ durationMs,
1685
+ steps: roots
1686
+ };
1717
1687
  }
1718
1688
 
1719
- // packages/core/src/suite/load.ts
1720
- var CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
1721
- var TS_CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mts", ".cts"]);
1722
- function diagnostic2(code, message) {
1723
- return { code, message, severity: "error" };
1689
+ // packages/core/src/diff/engine.ts
1690
+ var DEFAULT_THRESHOLD_MS = 0;
1691
+ function pathSeg(step, index) {
1692
+ return { index, name: step.name, stepId: step.id };
1724
1693
  }
1725
- async function fileExists(filePath) {
1726
- try {
1727
- await access(filePath);
1728
- return true;
1729
- } catch {
1730
- return false;
1731
- }
1694
+ function buildPath(segments) {
1695
+ return { path: [...segments] };
1732
1696
  }
1733
- async function resolveSuiteConfigPath(options = {}) {
1734
- const cwd = path13.resolve(options.cwd ?? process.cwd());
1735
- if (options.configPath !== void 0 && options.configPath.trim() !== "") {
1736
- return path13.resolve(cwd, options.configPath.trim());
1737
- }
1738
- for (const name of DEFAULT_SUITE_CONFIG_NAMES) {
1739
- const candidate = path13.join(cwd, name);
1740
- if (await fileExists(candidate)) return candidate;
1697
+ function pairSteps(left, right) {
1698
+ const usedRight = /* @__PURE__ */ new Set();
1699
+ const pairs = [];
1700
+ for (let i = 0; i < left.length; i++) {
1701
+ const L = left[i];
1702
+ let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
1703
+ if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
1704
+ const cand = right[i];
1705
+ if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
1706
+ R = cand;
1707
+ }
1708
+ }
1709
+ if (R === void 0) {
1710
+ R = right.find(
1711
+ (r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
1712
+ );
1713
+ }
1714
+ if (R !== void 0) {
1715
+ usedRight.add(R.id);
1716
+ pairs.push([L, R]);
1717
+ } else {
1718
+ pairs.push([L, void 0]);
1719
+ }
1741
1720
  }
1742
- throw new Error(
1743
- `No suite config found. Create one with \`agent-inspect suite init\` or pass --config.`
1744
- );
1721
+ for (const R of right) {
1722
+ if (!usedRight.has(R.id)) {
1723
+ pairs.push([void 0, R]);
1724
+ }
1725
+ }
1726
+ return pairs;
1745
1727
  }
1746
- async function loadSuiteConfig(options = {}) {
1747
- let configPath;
1748
- try {
1749
- configPath = await resolveSuiteConfigPath(options);
1750
- } catch (error) {
1751
- const message = error instanceof Error ? error.message : String(error);
1752
- throw Object.assign(new Error(message), {
1753
- diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
1728
+ function compareLeafSteps(L, R, segments, opts, out) {
1729
+ const path18 = buildPath(segments);
1730
+ if (L.name !== R.name) {
1731
+ out.push({
1732
+ kind: "structure",
1733
+ severity: "warning",
1734
+ message: "Step name differs",
1735
+ path: path18,
1736
+ left: L.name,
1737
+ right: R.name
1754
1738
  });
1755
1739
  }
1756
- const extension = path13.extname(configPath);
1757
- if (TS_CONFIG_EXTENSIONS.has(extension)) {
1758
- const message = "TypeScript suite configs require an explicit precompiled JavaScript config or future --config-loader support.";
1759
- throw Object.assign(new Error(message), {
1760
- diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
1740
+ if ((L.type ?? "") !== (R.type ?? "")) {
1741
+ out.push({
1742
+ kind: "step-type",
1743
+ severity: "warning",
1744
+ message: "Step type differs",
1745
+ path: path18,
1746
+ left: L.type,
1747
+ right: R.type
1761
1748
  });
1762
1749
  }
1763
- if (!CONFIG_EXTENSIONS.has(extension)) {
1764
- const message = "Unsupported suite config extension. Use .json, .js, .mjs, or .cjs.";
1765
- throw Object.assign(new Error(message), {
1766
- diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
1750
+ if ((L.status ?? "") !== (R.status ?? "")) {
1751
+ out.push({
1752
+ kind: "step-status",
1753
+ severity: "warning",
1754
+ message: "Step status differs",
1755
+ path: path18,
1756
+ left: L.status,
1757
+ right: R.status
1767
1758
  });
1768
1759
  }
1769
- try {
1770
- let raw;
1771
- if (extension === ".json") {
1772
- raw = JSON.parse(await readFile(configPath, "utf-8"));
1773
- } else {
1774
- const mod = await import(pathToFileURL(configPath).href);
1775
- raw = "default" in mod ? mod.default : mod;
1776
- }
1777
- const config = normalizeSuiteConfig(raw);
1778
- return {
1779
- config,
1780
- configPath,
1781
- configDir: path13.dirname(configPath)
1782
- };
1783
- } catch (error) {
1784
- const message = error instanceof Error ? error.message : String(error);
1785
- throw Object.assign(new Error(message), {
1786
- diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
1760
+ const le = L.error ?? "";
1761
+ const re = R.error ?? "";
1762
+ if (le !== re) {
1763
+ out.push({
1764
+ kind: "error",
1765
+ severity: "error",
1766
+ message: "Step error message differs",
1767
+ path: path18,
1768
+ left: le || void 0,
1769
+ right: re || void 0
1787
1770
  });
1788
1771
  }
1789
- }
1790
- async function exists(filePath) {
1791
- try {
1792
- await access(filePath);
1793
- return true;
1794
- } catch {
1795
- return false;
1796
- }
1797
- }
1798
- async function resolveSuiteCaseTrace(suiteCase, options) {
1799
- if (suiteCase.trace !== void 0) {
1800
- const tracePath = path13.resolve(options.configDir, suiteCase.trace);
1801
- if (await exists(tracePath)) {
1802
- return { caseId: suiteCase.id, tracePath, missing: false };
1772
+ if (!opts.ignoreDuration) {
1773
+ const ld = L.durationMs;
1774
+ const rd = R.durationMs;
1775
+ const th = opts.durationThresholdMs;
1776
+ let differs = false;
1777
+ if (ld === void 0 && rd === void 0) differs = false;
1778
+ else if (ld === void 0 || rd === void 0) differs = true;
1779
+ else differs = Math.abs(ld - rd) > th;
1780
+ if (differs) {
1781
+ out.push({
1782
+ kind: "duration",
1783
+ severity: "info",
1784
+ message: "Step duration differs",
1785
+ path: path18,
1786
+ left: ld,
1787
+ right: rd
1788
+ });
1803
1789
  }
1804
- return {
1805
- caseId: suiteCase.id,
1806
- tracePath,
1807
- missing: true,
1808
- reason: `trace file not found: ${suiteCase.trace}`
1809
- };
1810
1790
  }
1811
- const runKey = suiteCase.runId ?? suiteCase.id;
1812
- const directPath = getTraceFilePath(runKey, options.tracesDir);
1813
- if (await exists(directPath)) {
1814
- return { caseId: suiteCase.id, tracePath: directPath, runId: runKey, missing: false };
1791
+ const lm = stableJson(L.metadata ?? {});
1792
+ const rm2 = stableJson(R.metadata ?? {});
1793
+ if (lm !== rm2) {
1794
+ out.push({
1795
+ kind: "metadata",
1796
+ severity: "info",
1797
+ message: "Step metadata differs",
1798
+ path: path18,
1799
+ left: L.metadata,
1800
+ right: R.metadata
1801
+ });
1815
1802
  }
1816
- const nestedPath = path13.join(options.tracesDir, `${path13.basename(runKey)}.jsonl`);
1817
- if (await exists(nestedPath)) {
1818
- return { caseId: suiteCase.id, tracePath: nestedPath, runId: runKey, missing: false };
1803
+ const lo = stableJson(L.outputPreview ?? null);
1804
+ const ro = stableJson(R.outputPreview ?? null);
1805
+ if (lo !== ro) {
1806
+ out.push({
1807
+ kind: "output",
1808
+ severity: "info",
1809
+ message: "Output preview differs",
1810
+ path: path18,
1811
+ left: L.outputPreview,
1812
+ right: R.outputPreview
1813
+ });
1819
1814
  }
1820
- return {
1821
- caseId: suiteCase.id,
1822
- runId: runKey,
1823
- missing: true,
1824
- reason: `no trace found for run id "${runKey}" under ${options.tracesDir}`
1825
- };
1826
1815
  }
1827
-
1828
- // packages/core/src/checks/index.ts
1829
- var SEVERITY_RANK = {
1830
- error: 0,
1831
- warning: 1,
1832
- info: 2
1833
- };
1834
- var STATUS_RANK = {
1835
- fail: 0,
1836
- warning: 1,
1837
- pass: 2
1838
- };
1839
- function compareStrings(a, b) {
1840
- return (a ?? "").localeCompare(b ?? "");
1816
+ function compareRecursive(L, R, segments, opts, out) {
1817
+ compareLeafSteps(L, R, segments, opts, out);
1818
+ const pairs = pairSteps(L.children, R.children);
1819
+ let ci = 0;
1820
+ for (const [lch, rch] of pairs) {
1821
+ if (lch !== void 0 && rch !== void 0) {
1822
+ compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
1823
+ } else if (lch !== void 0) {
1824
+ out.push({
1825
+ kind: "step-removed",
1826
+ severity: "warning",
1827
+ message: `Step only in left run: ${lch.name}`,
1828
+ path: buildPath([...segments, pathSeg(lch, ci)]),
1829
+ left: lch.id,
1830
+ right: void 0
1831
+ });
1832
+ } else if (rch !== void 0) {
1833
+ out.push({
1834
+ kind: "step-added",
1835
+ severity: "warning",
1836
+ message: `Step only in right run: ${rch.name}`,
1837
+ path: buildPath([...segments, pathSeg(rch, ci)]),
1838
+ left: void 0,
1839
+ right: rch.id
1840
+ });
1841
+ }
1842
+ ci += 1;
1843
+ }
1841
1844
  }
1842
- function diagnostic3(code, message, ruleId) {
1845
+ function mergeDiffDefaults(options) {
1843
1846
  return {
1844
- code,
1845
- message,
1846
- severity: "error",
1847
- ...ruleId ? { ruleId } : {}
1847
+ ignoreDuration: false,
1848
+ durationThresholdMs: DEFAULT_THRESHOLD_MS,
1849
+ focus: "all",
1850
+ check: "all"
1848
1851
  };
1849
1852
  }
1850
- function emptySummary() {
1851
- return {
1852
- passed: 0,
1853
- failed: 0,
1854
- warnings: 0,
1855
- errors: 0
1856
- };
1853
+ function kindMatchesFilter(kind, merged) {
1854
+ return true;
1857
1855
  }
1858
- function errorResult(input, diagnostics, selectedRun) {
1859
- return {
1860
- ok: false,
1861
- status: "error",
1862
- format: input.read.format,
1863
- ...selectedRun ? { runId: selectedRun.runId } : {},
1864
- summary: {
1865
- ...emptySummary(),
1866
- errors: diagnostics.filter((item) => item.severity === "error").length
1867
- },
1868
- findings: [],
1869
- diagnostics: [...diagnostics]
1856
+ function diffRuns(left, right, options) {
1857
+ const merged = mergeDiffDefaults();
1858
+ const opts = {
1859
+ ignoreDuration: merged.ignoreDuration,
1860
+ durationThresholdMs: merged.durationThresholdMs
1870
1861
  };
1871
- }
1872
- function flattenNodes(nodes) {
1873
- return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
1874
- }
1875
- function buildFacts(input, selectedRun) {
1876
- const scopedRuns = selectedRun ? [selectedRun] : input.read.runs;
1877
- const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
1878
- const scopedEvents = selectedRun === void 0 ? input.read.events : input.read.events.filter((event) => scopedRunIds.has(event.runId));
1879
- const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
1880
- const nodesByEventId = /* @__PURE__ */ new Map();
1881
- const childrenByParentId = /* @__PURE__ */ new Map();
1882
- for (const node of nodes) {
1883
- nodesByEventId.set(node.event.eventId, node);
1884
- const parentId = node.event.parentId;
1885
- if (parentId) {
1886
- const children = childrenByParentId.get(parentId) ?? [];
1887
- children.push(node);
1888
- childrenByParentId.set(parentId, children);
1889
- }
1862
+ const raw = [];
1863
+ if ((left.status ?? "") !== (right.status ?? "")) {
1864
+ raw.push({
1865
+ kind: "run-status",
1866
+ severity: "warning",
1867
+ message: "Run completion status differs",
1868
+ left: left.status,
1869
+ right: right.status
1870
+ });
1890
1871
  }
1891
- return {
1892
- format: input.read.format,
1893
- runs: Object.freeze([...input.read.runs]),
1894
- events: Object.freeze([...scopedEvents]),
1895
- readerWarnings: Object.freeze([...input.read.warnings]),
1896
- unsupportedFields: Object.freeze([...input.read.unsupportedFields]),
1897
- sourceFiles: Object.freeze([...input.read.sourceFiles]),
1898
- nodesByEventId,
1899
- childrenByParentId,
1900
- rootNodes: Object.freeze(scopedRuns.flatMap((run) => run.children))
1901
- };
1902
- }
1903
- function resolveSelectedRun(input, runId) {
1904
- if (input.selectedRun) {
1905
- if (runId && input.selectedRun.runId !== runId) {
1906
- return {
1907
- diagnostics: [
1908
- diagnostic3(
1909
- "AI_CHECK_INVALID_ARGUMENTS",
1910
- `Selected run ${input.selectedRun.runId} does not match requested run ${runId}.`
1911
- )
1912
- ]
1913
- };
1872
+ {
1873
+ const ld = left.durationMs;
1874
+ const rd = right.durationMs;
1875
+ const th = merged.durationThresholdMs;
1876
+ let differs = false;
1877
+ if (ld === void 0 && rd === void 0) differs = false;
1878
+ else if (ld === void 0 || rd === void 0) differs = true;
1879
+ else differs = Math.abs(ld - rd) > th;
1880
+ if (differs) {
1881
+ raw.push({
1882
+ kind: "duration",
1883
+ severity: "info",
1884
+ message: "Run duration differs",
1885
+ left: ld,
1886
+ right: rd
1887
+ });
1914
1888
  }
1915
- return { run: input.selectedRun, diagnostics: [] };
1916
1889
  }
1917
- if (runId) {
1918
- const run = input.read.runs.find((candidate) => candidate.runId === runId);
1919
- if (!run) {
1920
- return {
1921
- diagnostics: [
1922
- diagnostic3("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
1923
- ]
1924
- };
1890
+ const pairs = pairSteps(left.steps, right.steps);
1891
+ let idx = 0;
1892
+ for (const [ls, rs] of pairs) {
1893
+ if (ls !== void 0 && rs !== void 0) {
1894
+ compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
1895
+ idx += 1;
1896
+ } else if (ls !== void 0) {
1897
+ raw.push({
1898
+ kind: "step-removed",
1899
+ severity: "warning",
1900
+ message: `Step only in left run: ${ls.name}`,
1901
+ path: buildPath([pathSeg(ls, idx)]),
1902
+ left: ls.id,
1903
+ right: void 0
1904
+ });
1905
+ idx += 1;
1906
+ } else if (rs !== void 0) {
1907
+ raw.push({
1908
+ kind: "step-added",
1909
+ severity: "warning",
1910
+ message: `Step only in right run: ${rs.name}`,
1911
+ path: buildPath([pathSeg(rs, idx)]),
1912
+ left: void 0,
1913
+ right: rs.id
1914
+ });
1915
+ idx += 1;
1925
1916
  }
1926
- return { run, diagnostics: [] };
1927
- }
1928
- if (input.read.runs.length === 1) {
1929
- return { run: input.read.runs[0], diagnostics: [] };
1930
1917
  }
1931
- if (input.read.runs.length === 0) {
1932
- return {
1933
- diagnostics: [
1934
- diagnostic3("AI_CHECK_RUN_SELECTION_REQUIRED", "No runs are available for checks.")
1935
- ]
1936
- };
1918
+ const differences = raw.filter((d) => kindMatchesFilter(d.kind));
1919
+ let errors = 0;
1920
+ let warnings = 0;
1921
+ let info = 0;
1922
+ for (const d of differences) {
1923
+ if (d.severity === "error") errors += 1;
1924
+ else if (d.severity === "warning") warnings += 1;
1925
+ else info += 1;
1937
1926
  }
1938
- return {
1939
- diagnostics: [
1940
- diagnostic3(
1941
- "AI_CHECK_RUN_SELECTION_REQUIRED",
1942
- "Multiple runs are available; select a run before executing checks."
1943
- )
1944
- ]
1927
+ const firstVisible = differences[0];
1928
+ const firstDivergence = firstVisible !== void 0 ? {
1929
+ kind: "first-divergence",
1930
+ severity: firstVisible.severity,
1931
+ message: `First divergence: ${firstVisible.message}`,
1932
+ path: firstVisible.path,
1933
+ left: firstVisible.left,
1934
+ right: firstVisible.right
1935
+ } : void 0;
1936
+ const summary = {
1937
+ leftRunId: left.runId,
1938
+ rightRunId: right.runId,
1939
+ totalDifferences: differences.length,
1940
+ errors,
1941
+ warnings,
1942
+ info,
1943
+ firstDivergence
1945
1944
  };
1945
+ return { summary, differences };
1946
1946
  }
1947
- function selectRules(rules, selectedIds) {
1947
+
1948
+ // packages/core/src/evidence/zip.ts
1949
+ (() => {
1950
+ const table = new Uint32Array(256);
1951
+ for (let n = 0; n < 256; n += 1) {
1952
+ let c = n;
1953
+ for (let k = 0; k < 8; k += 1) {
1954
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1955
+ }
1956
+ table[n] = c >>> 0;
1957
+ }
1958
+ return table;
1959
+ })();
1960
+
1961
+ // packages/core/src/suite/types.ts
1962
+ var DEFAULT_SUITE_CONFIG_NAMES = [
1963
+ "agent-inspect.suite.json",
1964
+ "agent-inspect.suite.js",
1965
+ "agent-inspect.suite.mjs",
1966
+ "agent-inspect.suite.cjs"
1967
+ ];
1968
+ function diagnostic(code, message, severity = "error", caseId) {
1969
+ return { code, message, severity, ...{} };
1970
+ }
1971
+ function asString(value, label) {
1972
+ if (value === void 0) return void 0;
1973
+ if (typeof value !== "string" || value.trim() === "") {
1974
+ throw new Error(`${label} must be a non-empty string.`);
1975
+ }
1976
+ return value.trim();
1977
+ }
1978
+ function asStringArray(value, label) {
1979
+ if (value === void 0) return void 0;
1980
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
1981
+ throw new Error(`${label} must be an array of strings.`);
1982
+ }
1983
+ return value;
1984
+ }
1985
+ function asPositiveNumber(value, label) {
1986
+ if (value === void 0) return void 0;
1987
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1988
+ throw new Error(`${label} must be a non-negative number.`);
1989
+ }
1990
+ return value;
1991
+ }
1992
+ function validateCaseConfig(value, index) {
1948
1993
  const diagnostics = [];
1949
- const byId = /* @__PURE__ */ new Map();
1950
- for (const rule of rules) {
1951
- if (byId.has(rule.id)) {
1994
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1995
+ diagnostics.push(
1996
+ diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}] must be an object.`)
1997
+ );
1998
+ return { diagnostics };
1999
+ }
2000
+ const raw = value;
2001
+ try {
2002
+ const id = asString(raw.id, `cases[${index}].id`);
2003
+ if (id === void 0) {
1952
2004
  diagnostics.push(
1953
- diagnostic3("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
2005
+ diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}].id is required.`)
1954
2006
  );
1955
- continue;
1956
- }
1957
- byId.set(rule.id, rule);
1958
- }
1959
- if (selectedIds && selectedIds.length > 0) {
1960
- const selected = new Set(selectedIds);
1961
- for (const id of selected) {
1962
- if (!byId.has(id)) {
1963
- diagnostics.push(
1964
- diagnostic3("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
1965
- );
1966
- }
2007
+ return { diagnostics };
1967
2008
  }
2009
+ const trace = asString(raw.trace, `cases[${index}].trace`);
2010
+ const runId = asString(raw.runId, `cases[${index}].runId`);
2011
+ const input = asString(raw.input, `cases[${index}].input`);
1968
2012
  return {
1969
- rules: [...byId.values()].filter((rule) => selected.has(rule.id)).sort(compareRules),
2013
+ caseConfig: {
2014
+ id,
2015
+ ...trace !== void 0 ? { trace } : {},
2016
+ ...runId !== void 0 ? { runId } : {},
2017
+ ...input !== void 0 ? { input } : {},
2018
+ ...asStringArray(raw.requireTools, `cases[${index}].requireTools`) !== void 0 ? { requireTools: asStringArray(raw.requireTools, `cases[${index}].requireTools`) } : {},
2019
+ ...asStringArray(raw.forbidTools, `cases[${index}].forbidTools`) !== void 0 ? { forbidTools: asStringArray(raw.forbidTools, `cases[${index}].forbidTools`) } : {},
2020
+ ...asPositiveNumber(raw.maxDurationMs, `cases[${index}].maxDurationMs`) !== void 0 ? {
2021
+ maxDurationMs: asPositiveNumber(
2022
+ raw.maxDurationMs,
2023
+ `cases[${index}].maxDurationMs`
2024
+ )
2025
+ } : {},
2026
+ ...asStringArray(
2027
+ raw.expectedObservations,
2028
+ `cases[${index}].expectedObservations`
2029
+ ) !== void 0 ? {
2030
+ expectedObservations: asStringArray(
2031
+ raw.expectedObservations,
2032
+ `cases[${index}].expectedObservations`
2033
+ )
2034
+ } : {}
2035
+ },
1970
2036
  diagnostics
1971
2037
  };
2038
+ } catch (error) {
2039
+ const message = error instanceof Error ? error.message : String(error);
2040
+ diagnostics.push(diagnostic("AI_SUITE_CONFIG_INVALID", message));
2041
+ return { diagnostics };
1972
2042
  }
1973
- return { rules: [...byId.values()].sort(compareRules), diagnostics };
1974
- }
1975
- function compareRules(a, b) {
1976
- return a.id.localeCompare(b.id);
1977
- }
1978
- function eventTimestamp(finding, eventById) {
1979
- const eventId = finding.evidence[0]?.eventId;
1980
- return eventId ? eventById.get(eventId)?.timestamp ?? "" : "";
1981
2043
  }
1982
- function compareFindings(eventById) {
1983
- return (a, b) => {
1984
- if (SEVERITY_RANK[a.severity] !== SEVERITY_RANK[b.severity]) {
1985
- return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
2044
+ function normalizeSuiteConfig(value) {
2045
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2046
+ throw new Error("Suite config must export an object.");
2047
+ }
2048
+ const raw = value;
2049
+ const name = asString(raw.name, "name");
2050
+ const traces = asString(raw.traces, "traces");
2051
+ if (name === void 0) throw new Error("name is required.");
2052
+ if (traces === void 0) throw new Error("traces is required.");
2053
+ if (!Array.isArray(raw.cases) || raw.cases.length === 0) {
2054
+ throw new Error("cases must be a non-empty array.");
2055
+ }
2056
+ const cases = [];
2057
+ for (let index = 0; index < raw.cases.length; index += 1) {
2058
+ const { caseConfig, diagnostics } = validateCaseConfig(raw.cases[index], index);
2059
+ if (diagnostics.length > 0) {
2060
+ throw new Error(diagnostics.map((item) => item.message).join("; "));
1986
2061
  }
1987
- const byRule = a.ruleId.localeCompare(b.ruleId);
1988
- if (byRule !== 0) return byRule;
1989
- if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
1990
- return STATUS_RANK[a.status] - STATUS_RANK[b.status];
2062
+ if (caseConfig !== void 0) cases.push(caseConfig);
2063
+ }
2064
+ const ids = /* @__PURE__ */ new Set();
2065
+ for (const suiteCase of cases) {
2066
+ if (ids.has(suiteCase.id)) {
2067
+ throw new Error(`Duplicate case id "${suiteCase.id}".`);
1991
2068
  }
1992
- const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
1993
- if (byRun !== 0) return byRun;
1994
- const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
1995
- if (byTime !== 0) return byTime;
1996
- const byEvent = compareStrings(a.evidence[0]?.eventId, b.evidence[0]?.eventId);
1997
- if (byEvent !== 0) return byEvent;
1998
- return compareStrings(a.evidence[0]?.path, b.evidence[0]?.path);
1999
- };
2069
+ ids.add(suiteCase.id);
2070
+ }
2071
+ const redactionProfile = raw.redactionProfile === "local" || raw.redactionProfile === "share" || raw.redactionProfile === "strict" ? raw.redactionProfile : void 0;
2072
+ if (raw.redactionProfile !== void 0 && redactionProfile === void 0) {
2073
+ throw new Error('redactionProfile must be "local", "share", or "strict".');
2074
+ }
2075
+ const config = { name, traces, cases };
2076
+ if (raw.checks !== void 0) {
2077
+ if (typeof raw.checks !== "object" || Array.isArray(raw.checks)) {
2078
+ throw new Error("checks must be an object.");
2079
+ }
2080
+ config.checks = raw.checks;
2081
+ }
2082
+ if (raw.eval !== void 0) {
2083
+ if (typeof raw.eval !== "object" || Array.isArray(raw.eval)) {
2084
+ throw new Error("eval must be an object.");
2085
+ }
2086
+ config.eval = raw.eval;
2087
+ }
2088
+ if (raw.artifacts !== void 0) {
2089
+ if (typeof raw.artifacts !== "object" || Array.isArray(raw.artifacts)) {
2090
+ throw new Error("artifacts must be an object.");
2091
+ }
2092
+ const outputDir = asString(
2093
+ raw.artifacts.outputDir,
2094
+ "artifacts.outputDir"
2095
+ );
2096
+ config.artifacts = outputDir !== void 0 ? { outputDir } : {};
2097
+ }
2098
+ if (raw.baseline !== void 0) {
2099
+ const baseline = asString(raw.baseline, "baseline");
2100
+ if (baseline !== void 0) config.baseline = baseline;
2101
+ }
2102
+ if (raw.candidate !== void 0) {
2103
+ const candidate = asString(raw.candidate, "candidate");
2104
+ if (candidate !== void 0) config.candidate = candidate;
2105
+ }
2106
+ if (redactionProfile !== void 0) config.redactionProfile = redactionProfile;
2107
+ return config;
2000
2108
  }
2001
- function normalizeFinding(rule, finding) {
2002
- return {
2003
- ruleId: finding.ruleId || rule.id,
2004
- severity: finding.severity ?? rule.defaultSeverity,
2005
- status: finding.status,
2006
- message: finding.message,
2007
- ...finding.expected !== void 0 ? { expected: finding.expected } : {},
2008
- ...finding.actual !== void 0 ? { actual: finding.actual } : {},
2009
- evidence: [...finding.evidence ?? []],
2010
- ...finding.category !== void 0 ? { category: finding.category } : {},
2011
- ...finding.confidence !== void 0 ? { confidence: finding.confidence } : {},
2012
- ...finding.detector !== void 0 ? { detector: finding.detector } : {},
2013
- ...finding.action !== void 0 ? { action: finding.action } : {}
2014
- };
2109
+
2110
+ // packages/core/src/suite/load.ts
2111
+ var CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
2112
+ var TS_CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mts", ".cts"]);
2113
+ function diagnostic2(code, message) {
2114
+ return { code, message, severity: "error" };
2015
2115
  }
2016
- function summarize(findings, diagnostics) {
2017
- return {
2018
- passed: findings.filter((finding) => finding.status === "pass").length,
2019
- failed: findings.filter(
2020
- (finding) => finding.status === "fail" && finding.severity === "error"
2021
- ).length,
2022
- warnings: findings.filter(
2023
- (finding) => finding.status === "warning" || finding.severity === "warning"
2024
- ).length,
2025
- errors: diagnostics.filter((item) => item.severity === "error").length
2026
- };
2116
+ async function fileExists(filePath) {
2117
+ try {
2118
+ await access(filePath);
2119
+ return true;
2120
+ } catch {
2121
+ return false;
2122
+ }
2027
2123
  }
2028
- function stringAttr(event, keys) {
2029
- for (const key of keys) {
2030
- const value = event.attributes?.[key];
2031
- if (typeof value === "string" && value.trim() !== "") return value;
2124
+ async function resolveSuiteConfigPath(options = {}) {
2125
+ const cwd = path15.resolve(options.cwd ?? process.cwd());
2126
+ if (options.configPath !== void 0 && options.configPath.trim() !== "") {
2127
+ return path15.resolve(cwd, options.configPath.trim());
2032
2128
  }
2033
- return void 0;
2129
+ for (const name of DEFAULT_SUITE_CONFIG_NAMES) {
2130
+ const candidate = path15.join(cwd, name);
2131
+ if (await fileExists(candidate)) return candidate;
2132
+ }
2133
+ throw new Error(
2134
+ `No suite config found. Create one with \`agent-inspect suite init\` or pass --config.`
2135
+ );
2034
2136
  }
2035
- function stripPrefix(name, prefixes) {
2036
- for (const prefix of prefixes) {
2037
- if (name.startsWith(prefix)) return name.slice(prefix.length);
2137
+ async function loadSuiteConfig(options = {}) {
2138
+ let configPath;
2139
+ try {
2140
+ configPath = await resolveSuiteConfigPath(options);
2141
+ } catch (error) {
2142
+ const message = error instanceof Error ? error.message : String(error);
2143
+ throw Object.assign(new Error(message), {
2144
+ diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
2145
+ });
2146
+ }
2147
+ const extension = path15.extname(configPath);
2148
+ if (TS_CONFIG_EXTENSIONS.has(extension)) {
2149
+ const message = "TypeScript suite configs require an explicit precompiled JavaScript config or future --config-loader support.";
2150
+ throw Object.assign(new Error(message), {
2151
+ diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
2152
+ });
2153
+ }
2154
+ if (!CONFIG_EXTENSIONS.has(extension)) {
2155
+ const message = "Unsupported suite config extension. Use .json, .js, .mjs, or .cjs.";
2156
+ throw Object.assign(new Error(message), {
2157
+ diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
2158
+ });
2159
+ }
2160
+ try {
2161
+ let raw;
2162
+ if (extension === ".json") {
2163
+ raw = JSON.parse(await readFile(configPath, "utf-8"));
2164
+ } else {
2165
+ const mod = await import(pathToFileURL(configPath).href);
2166
+ raw = "default" in mod ? mod.default : mod;
2167
+ }
2168
+ const config = normalizeSuiteConfig(raw);
2169
+ return {
2170
+ config,
2171
+ configPath,
2172
+ configDir: path15.dirname(configPath)
2173
+ };
2174
+ } catch (error) {
2175
+ const message = error instanceof Error ? error.message : String(error);
2176
+ throw Object.assign(new Error(message), {
2177
+ diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
2178
+ });
2179
+ }
2180
+ }
2181
+ async function exists(filePath) {
2182
+ try {
2183
+ await access(filePath);
2184
+ return true;
2185
+ } catch {
2186
+ return false;
2038
2187
  }
2039
- return name;
2040
2188
  }
2041
- function eventEvidence(event, path16) {
2189
+ async function resolveSuiteCaseTrace(suiteCase, options) {
2190
+ if (suiteCase.trace !== void 0) {
2191
+ const tracePath = path15.resolve(options.configDir, suiteCase.trace);
2192
+ if (await exists(tracePath)) {
2193
+ return { caseId: suiteCase.id, tracePath, missing: false };
2194
+ }
2195
+ return {
2196
+ caseId: suiteCase.id,
2197
+ tracePath,
2198
+ missing: true,
2199
+ reason: `trace file not found: ${suiteCase.trace}`
2200
+ };
2201
+ }
2202
+ const runKey = suiteCase.runId ?? suiteCase.id;
2203
+ const directPath = getTraceFilePath(runKey, options.tracesDir);
2204
+ if (await exists(directPath)) {
2205
+ return { caseId: suiteCase.id, tracePath: directPath, runId: runKey, missing: false };
2206
+ }
2207
+ const nestedPath = path15.join(options.tracesDir, `${path15.basename(runKey)}.jsonl`);
2208
+ if (await exists(nestedPath)) {
2209
+ return { caseId: suiteCase.id, tracePath: nestedPath, runId: runKey, missing: false };
2210
+ }
2042
2211
  return {
2043
- runId: event.runId,
2044
- eventId: event.eventId,
2045
- parentId: event.parentId,
2046
- traceId: event.trace?.traceId,
2047
- spanId: event.trace?.spanId,
2048
- kind: event.kind,
2049
- name: event.name,
2050
- status: event.status,
2051
- ...path16 ? { path: path16 } : {}
2212
+ caseId: suiteCase.id,
2213
+ runId: runKey,
2214
+ missing: true,
2215
+ reason: `no trace found for run id "${runKey}" under ${options.tracesDir}`
2052
2216
  };
2053
2217
  }
2054
- function runEvidence(run) {
2055
- return run ? [{ runId: run.runId, name: run.name, status: run.status }] : [];
2218
+
2219
+ // packages/core/src/checks/index.ts
2220
+ var SEVERITY_RANK = {
2221
+ error: 0,
2222
+ warning: 1,
2223
+ info: 2
2224
+ };
2225
+ var STATUS_RANK = {
2226
+ fail: 0,
2227
+ warning: 1,
2228
+ pass: 2
2229
+ };
2230
+ function compareStrings(a, b) {
2231
+ return (a ?? "").localeCompare(b ?? "");
2056
2232
  }
2057
- function failFinding(ruleId, message, evidence, expected, actual, meta) {
2233
+ function diagnostic3(code, message, ruleId) {
2058
2234
  return {
2059
- ruleId,
2060
- severity: meta?.severity ?? "error",
2061
- status: meta?.status ?? "fail",
2235
+ code,
2062
2236
  message,
2063
- ...expected !== void 0 ? { expected } : {},
2064
- ...actual !== void 0 ? { actual } : {},
2065
- evidence: [...evidence],
2066
- ...{},
2067
- ...{},
2068
- ...{},
2069
- ...{}
2237
+ severity: "error",
2238
+ ...ruleId ? { ruleId } : {}
2070
2239
  };
2071
2240
  }
2072
- function toolName(event) {
2073
- return stringAttr(event, ["toolName", "tool"]) ?? stripPrefix(event.name, ["tool:", "function:", "mcp-tools:"]);
2074
- }
2075
- function llmModel(event) {
2076
- return stringAttr(event, ["model", "modelId", "responseModelId", "modelName", "model_name"]) ?? stripPrefix(event.name, ["llm:", "generation:", "transcription:", "speech:"]);
2241
+ function emptySummary() {
2242
+ return {
2243
+ passed: 0,
2244
+ failed: 0,
2245
+ warnings: 0,
2246
+ errors: 0
2247
+ };
2077
2248
  }
2078
- function llmProvider(event) {
2079
- return stringAttr(event, ["provider", "providerName", "provider_name"]);
2249
+ function errorResult(input, diagnostics, selectedRun) {
2250
+ return {
2251
+ ok: false,
2252
+ status: "error",
2253
+ format: input.read.format,
2254
+ ...selectedRun ? { runId: selectedRun.runId } : {},
2255
+ summary: {
2256
+ ...emptySummary(),
2257
+ errors: diagnostics.filter((item) => item.severity === "error").length
2258
+ },
2259
+ findings: [],
2260
+ diagnostics: [...diagnostics]
2261
+ };
2080
2262
  }
2081
- function llmFinishReason(event) {
2082
- return stringAttr(event, ["finishReason", "rawFinishReason", "finish_reason"]);
2263
+ function flattenNodes(nodes) {
2264
+ return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
2083
2265
  }
2084
- function finishedEvents(context, kind) {
2085
- return context.events.filter(
2086
- (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
2087
- );
2266
+ function buildFacts(input, selectedRun) {
2267
+ const scopedRuns = selectedRun ? [selectedRun] : input.read.runs;
2268
+ const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
2269
+ const scopedEvents = selectedRun === void 0 ? input.read.events : input.read.events.filter((event) => scopedRunIds.has(event.runId));
2270
+ const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
2271
+ const nodesByEventId = /* @__PURE__ */ new Map();
2272
+ const childrenByParentId = /* @__PURE__ */ new Map();
2273
+ for (const node of nodes) {
2274
+ nodesByEventId.set(node.event.eventId, node);
2275
+ const parentId = node.event.parentId;
2276
+ if (parentId) {
2277
+ const children = childrenByParentId.get(parentId) ?? [];
2278
+ children.push(node);
2279
+ childrenByParentId.set(parentId, children);
2280
+ }
2281
+ }
2282
+ return {
2283
+ format: input.read.format,
2284
+ runs: Object.freeze([...input.read.runs]),
2285
+ events: Object.freeze([...scopedEvents]),
2286
+ readerWarnings: Object.freeze([...input.read.warnings]),
2287
+ unsupportedFields: Object.freeze([...input.read.unsupportedFields]),
2288
+ sourceFiles: Object.freeze([...input.read.sourceFiles]),
2289
+ nodesByEventId,
2290
+ childrenByParentId,
2291
+ rootNodes: Object.freeze(scopedRuns.flatMap((run) => run.children))
2292
+ };
2293
+ }
2294
+ function resolveSelectedRun(input, runId) {
2295
+ if (input.selectedRun) {
2296
+ if (runId && input.selectedRun.runId !== runId) {
2297
+ return {
2298
+ diagnostics: [
2299
+ diagnostic3(
2300
+ "AI_CHECK_INVALID_ARGUMENTS",
2301
+ `Selected run ${input.selectedRun.runId} does not match requested run ${runId}.`
2302
+ )
2303
+ ]
2304
+ };
2305
+ }
2306
+ return { run: input.selectedRun, diagnostics: [] };
2307
+ }
2308
+ if (runId) {
2309
+ const run = input.read.runs.find((candidate) => candidate.runId === runId);
2310
+ if (!run) {
2311
+ return {
2312
+ diagnostics: [
2313
+ diagnostic3("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
2314
+ ]
2315
+ };
2316
+ }
2317
+ return { run, diagnostics: [] };
2318
+ }
2319
+ if (input.read.runs.length === 1) {
2320
+ return { run: input.read.runs[0], diagnostics: [] };
2321
+ }
2322
+ if (input.read.runs.length === 0) {
2323
+ return {
2324
+ diagnostics: [
2325
+ diagnostic3("AI_CHECK_RUN_SELECTION_REQUIRED", "No runs are available for checks.")
2326
+ ]
2327
+ };
2328
+ }
2329
+ return {
2330
+ diagnostics: [
2331
+ diagnostic3(
2332
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
2333
+ "Multiple runs are available; select a run before executing checks."
2334
+ )
2335
+ ]
2336
+ };
2337
+ }
2338
+ function selectRules(rules, selectedIds) {
2339
+ const diagnostics = [];
2340
+ const byId = /* @__PURE__ */ new Map();
2341
+ for (const rule of rules) {
2342
+ if (byId.has(rule.id)) {
2343
+ diagnostics.push(
2344
+ diagnostic3("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
2345
+ );
2346
+ continue;
2347
+ }
2348
+ byId.set(rule.id, rule);
2349
+ }
2350
+ if (selectedIds && selectedIds.length > 0) {
2351
+ const selected = new Set(selectedIds);
2352
+ for (const id of selected) {
2353
+ if (!byId.has(id)) {
2354
+ diagnostics.push(
2355
+ diagnostic3("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
2356
+ );
2357
+ }
2358
+ }
2359
+ return {
2360
+ rules: [...byId.values()].filter((rule) => selected.has(rule.id)).sort(compareRules),
2361
+ diagnostics
2362
+ };
2363
+ }
2364
+ return { rules: [...byId.values()].sort(compareRules), diagnostics };
2365
+ }
2366
+ function compareRules(a, b) {
2367
+ return a.id.localeCompare(b.id);
2368
+ }
2369
+ function eventTimestamp(finding, eventById) {
2370
+ const eventId = finding.evidence[0]?.eventId;
2371
+ return eventId ? eventById.get(eventId)?.timestamp ?? "" : "";
2372
+ }
2373
+ function compareFindings(eventById) {
2374
+ return (a, b) => {
2375
+ if (SEVERITY_RANK[a.severity] !== SEVERITY_RANK[b.severity]) {
2376
+ return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
2377
+ }
2378
+ const byRule = a.ruleId.localeCompare(b.ruleId);
2379
+ if (byRule !== 0) return byRule;
2380
+ if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
2381
+ return STATUS_RANK[a.status] - STATUS_RANK[b.status];
2382
+ }
2383
+ const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
2384
+ if (byRun !== 0) return byRun;
2385
+ const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
2386
+ if (byTime !== 0) return byTime;
2387
+ const byEvent = compareStrings(a.evidence[0]?.eventId, b.evidence[0]?.eventId);
2388
+ if (byEvent !== 0) return byEvent;
2389
+ return compareStrings(a.evidence[0]?.path, b.evidence[0]?.path);
2390
+ };
2391
+ }
2392
+ function normalizeFinding(rule, finding) {
2393
+ return {
2394
+ ruleId: finding.ruleId || rule.id,
2395
+ severity: finding.severity ?? rule.defaultSeverity,
2396
+ status: finding.status,
2397
+ message: finding.message,
2398
+ ...finding.expected !== void 0 ? { expected: finding.expected } : {},
2399
+ ...finding.actual !== void 0 ? { actual: finding.actual } : {},
2400
+ evidence: [...finding.evidence ?? []],
2401
+ ...finding.category !== void 0 ? { category: finding.category } : {},
2402
+ ...finding.confidence !== void 0 ? { confidence: finding.confidence } : {},
2403
+ ...finding.detector !== void 0 ? { detector: finding.detector } : {},
2404
+ ...finding.action !== void 0 ? { action: finding.action } : {}
2405
+ };
2406
+ }
2407
+ function summarize(findings, diagnostics) {
2408
+ return {
2409
+ passed: findings.filter((finding) => finding.status === "pass").length,
2410
+ failed: findings.filter(
2411
+ (finding) => finding.status === "fail" && finding.severity === "error"
2412
+ ).length,
2413
+ warnings: findings.filter(
2414
+ (finding) => finding.status === "warning" || finding.severity === "warning"
2415
+ ).length,
2416
+ errors: diagnostics.filter((item) => item.severity === "error").length
2417
+ };
2418
+ }
2419
+ function stringAttr(event, keys) {
2420
+ for (const key of keys) {
2421
+ const value = event.attributes?.[key];
2422
+ if (typeof value === "string" && value.trim() !== "") return value;
2423
+ }
2424
+ return void 0;
2425
+ }
2426
+ function stripPrefix(name, prefixes) {
2427
+ for (const prefix of prefixes) {
2428
+ if (name.startsWith(prefix)) return name.slice(prefix.length);
2429
+ }
2430
+ return name;
2431
+ }
2432
+ function eventEvidence(event, path18) {
2433
+ return {
2434
+ runId: event.runId,
2435
+ eventId: event.eventId,
2436
+ parentId: event.parentId,
2437
+ traceId: event.trace?.traceId,
2438
+ spanId: event.trace?.spanId,
2439
+ kind: event.kind,
2440
+ name: event.name,
2441
+ status: event.status,
2442
+ ...path18 ? { path: path18 } : {}
2443
+ };
2444
+ }
2445
+ function runEvidence(run) {
2446
+ return run ? [{ runId: run.runId, name: run.name, status: run.status }] : [];
2447
+ }
2448
+ function failFinding(ruleId, message, evidence, expected, actual, meta) {
2449
+ return {
2450
+ ruleId,
2451
+ severity: meta?.severity ?? "error",
2452
+ status: meta?.status ?? "fail",
2453
+ message,
2454
+ ...expected !== void 0 ? { expected } : {},
2455
+ ...actual !== void 0 ? { actual } : {},
2456
+ evidence: [...evidence],
2457
+ ...{},
2458
+ ...{},
2459
+ ...{},
2460
+ ...{}
2461
+ };
2462
+ }
2463
+ function toolName(event) {
2464
+ return stringAttr(event, ["toolName", "tool"]) ?? stripPrefix(event.name, ["tool:", "function:", "mcp-tools:"]);
2465
+ }
2466
+ function llmModel(event) {
2467
+ return stringAttr(event, ["model", "modelId", "responseModelId", "modelName", "model_name"]) ?? stripPrefix(event.name, ["llm:", "generation:", "transcription:", "speech:"]);
2468
+ }
2469
+ function llmProvider(event) {
2470
+ return stringAttr(event, ["provider", "providerName", "provider_name"]);
2471
+ }
2472
+ function llmFinishReason(event) {
2473
+ return stringAttr(event, ["finishReason", "rawFinishReason", "finish_reason"]);
2474
+ }
2475
+ function finishedEvents(context, kind) {
2476
+ return context.events.filter(
2477
+ (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
2478
+ );
2088
2479
  }
2089
2480
  function createRunStatusRule(options = {}) {
2090
2481
  const expected = options.expected ?? "ok";
@@ -2972,7 +3363,7 @@ function findReaderByFormat(format, readers) {
2972
3363
  }
2973
3364
  async function jsonlFilesInDirectory(dirPath) {
2974
3365
  const entries = await readdir(dirPath, { withFileTypes: true });
2975
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path13.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
3366
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path15.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
2976
3367
  }
2977
3368
  async function resolveInput(input) {
2978
3369
  const cached = resolvedInputCache.get(input);
@@ -4592,7 +4983,7 @@ async function runSuiteCase(suiteCase, config, options) {
4592
4983
  async function runSuite(options = {}) {
4593
4984
  const startedAt = new Date(options.nowMs ?? Date.now()).toISOString();
4594
4985
  const { config, configPath, configDir } = await loadSuiteConfig(options);
4595
- const tracesDir = path13.resolve(configDir, config.traces);
4986
+ const tracesDir = path15.resolve(configDir, config.traces);
4596
4987
  const cases = [];
4597
4988
  const diagnostics = [];
4598
4989
  for (const suiteCase of config.cases) {
@@ -4627,22 +5018,6 @@ async function runSuite(options = {}) {
4627
5018
  };
4628
5019
  }
4629
5020
 
4630
- // packages/core/src/exporters/helpers.ts
4631
- function sortKeysDeep(input) {
4632
- if (input === null || typeof input !== "object") return input;
4633
- if (Array.isArray(input)) return input.map(sortKeysDeep);
4634
- const o = input;
4635
- const out = {};
4636
- for (const k of Object.keys(o).sort()) {
4637
- out[k] = sortKeysDeep(o[k]);
4638
- }
4639
- return out;
4640
- }
4641
- function stableJson(value, pretty) {
4642
- const sorted = sortKeysDeep(value);
4643
- return JSON.stringify(sorted);
4644
- }
4645
-
4646
5021
  // packages/viewer/src/html.ts
4647
5022
  var viewerIndexHtml = `<!DOCTYPE html>
4648
5023
  <html lang="en">
@@ -4732,370 +5107,6 @@ var viewerIndexHtml = `<!DOCTYPE html>
4732
5107
  </body>
4733
5108
  </html>
4734
5109
  `;
4735
-
4736
- // packages/core/src/diff/comparable.ts
4737
- function extractOutputPreview(meta) {
4738
- if (meta === void 0) return void 0;
4739
- if ("outputPreview" in meta) return meta.outputPreview;
4740
- if ("resultPreview" in meta) return meta.resultPreview;
4741
- return void 0;
4742
- }
4743
- function mapStepStatus(s) {
4744
- if (s === void 0) return "running";
4745
- return s;
4746
- }
4747
- function manualTraceEventsToComparableRun(events) {
4748
- const started = events.find((e) => e.event === "run_started");
4749
- if (!started || started.event !== "run_started") {
4750
- throw new Error("Invalid trace: missing run_started");
4751
- }
4752
- const rs = started;
4753
- const runId = rs.runId;
4754
- const completedAll = events.filter((e) => e.event === "run_completed");
4755
- const lastCompleted = completedAll[completedAll.length - 1];
4756
- let runStatus;
4757
- if (lastCompleted === void 0) runStatus = "running";
4758
- else runStatus = lastCompleted.status;
4759
- const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
4760
- const steps = /* @__PURE__ */ new Map();
4761
- let order = 0;
4762
- for (const e of events) {
4763
- if (e.event !== "step_started") continue;
4764
- const s = e;
4765
- const meta = s.metadata ? { ...s.metadata } : void 0;
4766
- steps.set(s.stepId, {
4767
- id: s.stepId,
4768
- parentId: s.parentId,
4769
- name: s.name,
4770
- type: s.type,
4771
- order: order++,
4772
- timestamp: s.timestamp,
4773
- metadata: meta
4774
- });
4775
- }
4776
- for (const e of events) {
4777
- if (e.event !== "step_completed") continue;
4778
- const acc = steps.get(e.stepId);
4779
- if (!acc) continue;
4780
- acc.status = e.status;
4781
- acc.durationMs = e.durationMs;
4782
- if (e.error?.message) acc.errorMsg = e.error.message;
4783
- const extra = e;
4784
- if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
4785
- acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
4786
- }
4787
- }
4788
- const nodes = /* @__PURE__ */ new Map();
4789
- for (const acc of steps.values()) {
4790
- let meta = acc.metadata ? { ...acc.metadata } : void 0;
4791
- if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
4792
- meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
4793
- }
4794
- const outputPreview = extractOutputPreview(meta);
4795
- if (meta !== void 0 && ("outputPreview" in meta || "resultPreview" in meta)) {
4796
- delete meta.outputPreview;
4797
- delete meta.resultPreview;
4798
- }
4799
- const sc = {
4800
- id: acc.id,
4801
- name: acc.name,
4802
- type: acc.type,
4803
- status: mapStepStatus(acc.status),
4804
- durationMs: acc.durationMs,
4805
- error: acc.errorMsg,
4806
- metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
4807
- outputPreview,
4808
- children: []
4809
- };
4810
- nodes.set(acc.id, sc);
4811
- }
4812
- const roots = [];
4813
- const sortByOrder = (a, b) => {
4814
- const oa = steps.get(a.id)?.order ?? 0;
4815
- const ob = steps.get(b.id)?.order ?? 0;
4816
- return oa - ob;
4817
- };
4818
- for (const acc of steps.values()) {
4819
- const node = nodes.get(acc.id);
4820
- if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
4821
- nodes.get(acc.parentId).children.push(node);
4822
- } else {
4823
- roots.push(node);
4824
- }
4825
- }
4826
- roots.sort(sortByOrder);
4827
- for (const n of nodes.values()) {
4828
- n.children.sort(sortByOrder);
4829
- }
4830
- return {
4831
- runId,
4832
- name: rs.name,
4833
- status: runStatus,
4834
- durationMs,
4835
- steps: roots
4836
- };
4837
- }
4838
-
4839
- // packages/core/src/diff/engine.ts
4840
- var DEFAULT_THRESHOLD_MS = 0;
4841
- function pathSeg(step, index) {
4842
- return { index, name: step.name, stepId: step.id };
4843
- }
4844
- function buildPath(segments) {
4845
- return { path: [...segments] };
4846
- }
4847
- function pairSteps(left, right) {
4848
- const usedRight = /* @__PURE__ */ new Set();
4849
- const pairs = [];
4850
- for (let i = 0; i < left.length; i++) {
4851
- const L = left[i];
4852
- let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
4853
- if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
4854
- const cand = right[i];
4855
- if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
4856
- R = cand;
4857
- }
4858
- }
4859
- if (R === void 0) {
4860
- R = right.find(
4861
- (r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
4862
- );
4863
- }
4864
- if (R !== void 0) {
4865
- usedRight.add(R.id);
4866
- pairs.push([L, R]);
4867
- } else {
4868
- pairs.push([L, void 0]);
4869
- }
4870
- }
4871
- for (const R of right) {
4872
- if (!usedRight.has(R.id)) {
4873
- pairs.push([void 0, R]);
4874
- }
4875
- }
4876
- return pairs;
4877
- }
4878
- function compareLeafSteps(L, R, segments, opts, out) {
4879
- const path16 = buildPath(segments);
4880
- if (L.name !== R.name) {
4881
- out.push({
4882
- kind: "structure",
4883
- severity: "warning",
4884
- message: "Step name differs",
4885
- path: path16,
4886
- left: L.name,
4887
- right: R.name
4888
- });
4889
- }
4890
- if ((L.type ?? "") !== (R.type ?? "")) {
4891
- out.push({
4892
- kind: "step-type",
4893
- severity: "warning",
4894
- message: "Step type differs",
4895
- path: path16,
4896
- left: L.type,
4897
- right: R.type
4898
- });
4899
- }
4900
- if ((L.status ?? "") !== (R.status ?? "")) {
4901
- out.push({
4902
- kind: "step-status",
4903
- severity: "warning",
4904
- message: "Step status differs",
4905
- path: path16,
4906
- left: L.status,
4907
- right: R.status
4908
- });
4909
- }
4910
- const le = L.error ?? "";
4911
- const re = R.error ?? "";
4912
- if (le !== re) {
4913
- out.push({
4914
- kind: "error",
4915
- severity: "error",
4916
- message: "Step error message differs",
4917
- path: path16,
4918
- left: le || void 0,
4919
- right: re || void 0
4920
- });
4921
- }
4922
- if (!opts.ignoreDuration) {
4923
- const ld = L.durationMs;
4924
- const rd = R.durationMs;
4925
- const th = opts.durationThresholdMs;
4926
- let differs = false;
4927
- if (ld === void 0 && rd === void 0) differs = false;
4928
- else if (ld === void 0 || rd === void 0) differs = true;
4929
- else differs = Math.abs(ld - rd) > th;
4930
- if (differs) {
4931
- out.push({
4932
- kind: "duration",
4933
- severity: "info",
4934
- message: "Step duration differs",
4935
- path: path16,
4936
- left: ld,
4937
- right: rd
4938
- });
4939
- }
4940
- }
4941
- const lm = stableJson(L.metadata ?? {});
4942
- const rm2 = stableJson(R.metadata ?? {});
4943
- if (lm !== rm2) {
4944
- out.push({
4945
- kind: "metadata",
4946
- severity: "info",
4947
- message: "Step metadata differs",
4948
- path: path16,
4949
- left: L.metadata,
4950
- right: R.metadata
4951
- });
4952
- }
4953
- const lo = stableJson(L.outputPreview ?? null);
4954
- const ro = stableJson(R.outputPreview ?? null);
4955
- if (lo !== ro) {
4956
- out.push({
4957
- kind: "output",
4958
- severity: "info",
4959
- message: "Output preview differs",
4960
- path: path16,
4961
- left: L.outputPreview,
4962
- right: R.outputPreview
4963
- });
4964
- }
4965
- }
4966
- function compareRecursive(L, R, segments, opts, out) {
4967
- compareLeafSteps(L, R, segments, opts, out);
4968
- const pairs = pairSteps(L.children, R.children);
4969
- let ci = 0;
4970
- for (const [lch, rch] of pairs) {
4971
- if (lch !== void 0 && rch !== void 0) {
4972
- compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
4973
- } else if (lch !== void 0) {
4974
- out.push({
4975
- kind: "step-removed",
4976
- severity: "warning",
4977
- message: `Step only in left run: ${lch.name}`,
4978
- path: buildPath([...segments, pathSeg(lch, ci)]),
4979
- left: lch.id,
4980
- right: void 0
4981
- });
4982
- } else if (rch !== void 0) {
4983
- out.push({
4984
- kind: "step-added",
4985
- severity: "warning",
4986
- message: `Step only in right run: ${rch.name}`,
4987
- path: buildPath([...segments, pathSeg(rch, ci)]),
4988
- left: void 0,
4989
- right: rch.id
4990
- });
4991
- }
4992
- ci += 1;
4993
- }
4994
- }
4995
- function mergeDiffDefaults(options) {
4996
- return {
4997
- ignoreDuration: false,
4998
- durationThresholdMs: DEFAULT_THRESHOLD_MS,
4999
- focus: "all",
5000
- check: "all"
5001
- };
5002
- }
5003
- function kindMatchesFilter(kind, merged) {
5004
- return true;
5005
- }
5006
- function diffRuns(left, right, options) {
5007
- const merged = mergeDiffDefaults();
5008
- const opts = {
5009
- ignoreDuration: merged.ignoreDuration,
5010
- durationThresholdMs: merged.durationThresholdMs
5011
- };
5012
- const raw = [];
5013
- if ((left.status ?? "") !== (right.status ?? "")) {
5014
- raw.push({
5015
- kind: "run-status",
5016
- severity: "warning",
5017
- message: "Run completion status differs",
5018
- left: left.status,
5019
- right: right.status
5020
- });
5021
- }
5022
- {
5023
- const ld = left.durationMs;
5024
- const rd = right.durationMs;
5025
- const th = merged.durationThresholdMs;
5026
- let differs = false;
5027
- if (ld === void 0 && rd === void 0) differs = false;
5028
- else if (ld === void 0 || rd === void 0) differs = true;
5029
- else differs = Math.abs(ld - rd) > th;
5030
- if (differs) {
5031
- raw.push({
5032
- kind: "duration",
5033
- severity: "info",
5034
- message: "Run duration differs",
5035
- left: ld,
5036
- right: rd
5037
- });
5038
- }
5039
- }
5040
- const pairs = pairSteps(left.steps, right.steps);
5041
- let idx = 0;
5042
- for (const [ls, rs] of pairs) {
5043
- if (ls !== void 0 && rs !== void 0) {
5044
- compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
5045
- idx += 1;
5046
- } else if (ls !== void 0) {
5047
- raw.push({
5048
- kind: "step-removed",
5049
- severity: "warning",
5050
- message: `Step only in left run: ${ls.name}`,
5051
- path: buildPath([pathSeg(ls, idx)]),
5052
- left: ls.id,
5053
- right: void 0
5054
- });
5055
- idx += 1;
5056
- } else if (rs !== void 0) {
5057
- raw.push({
5058
- kind: "step-added",
5059
- severity: "warning",
5060
- message: `Step only in right run: ${rs.name}`,
5061
- path: buildPath([pathSeg(rs, idx)]),
5062
- left: void 0,
5063
- right: rs.id
5064
- });
5065
- idx += 1;
5066
- }
5067
- }
5068
- const differences = raw.filter((d) => kindMatchesFilter(d.kind));
5069
- let errors = 0;
5070
- let warnings = 0;
5071
- let info = 0;
5072
- for (const d of differences) {
5073
- if (d.severity === "error") errors += 1;
5074
- else if (d.severity === "warning") warnings += 1;
5075
- else info += 1;
5076
- }
5077
- const firstVisible = differences[0];
5078
- const firstDivergence = firstVisible !== void 0 ? {
5079
- kind: "first-divergence",
5080
- severity: firstVisible.severity,
5081
- message: `First divergence: ${firstVisible.message}`,
5082
- path: firstVisible.path,
5083
- left: firstVisible.left,
5084
- right: firstVisible.right
5085
- } : void 0;
5086
- const summary = {
5087
- leftRunId: left.runId,
5088
- rightRunId: right.runId,
5089
- totalDifferences: differences.length,
5090
- errors,
5091
- warnings,
5092
- info,
5093
- firstDivergence
5094
- };
5095
- return { summary, differences };
5096
- }
5097
-
5098
- // packages/viewer/src/suite-data.ts
5099
5110
  async function enrichCase(suiteCase, baselineTracePath) {
5100
5111
  const base = {
5101
5112
  id: suiteCase.id,
@@ -5155,7 +5166,7 @@ async function loadSuiteViewerData(options) {
5155
5166
  for (const suiteCase of result.cases) {
5156
5167
  cases.push(await enrichCase(suiteCase, baselinePath));
5157
5168
  }
5158
- const artifactsDir = path13.join(path13.dirname(result.configPath), ".agent-inspect/suite-runs");
5169
+ const artifactsDir = path15.join(path15.dirname(result.configPath), ".agent-inspect/suite-runs");
5159
5170
  return {
5160
5171
  suiteName: result.suiteName,
5161
5172
  configPath: result.configPath,
@@ -5316,19 +5327,19 @@ function parseWorkspaceManifest(json) {
5316
5327
  }
5317
5328
  var INDEX_DIR_NAME = "index";
5318
5329
  function resolveWorkspaceLocation(cwd = process.cwd()) {
5319
- const projectRoot = path13.resolve(cwd);
5320
- const workspaceDir = path13.join(projectRoot, WORKSPACE_DIR_NAME);
5330
+ const projectRoot = path15.resolve(cwd);
5331
+ const workspaceDir = path15.join(projectRoot, WORKSPACE_DIR_NAME);
5321
5332
  return {
5322
5333
  projectRoot,
5323
5334
  workspaceDir,
5324
- manifestPath: path13.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
5335
+ manifestPath: path15.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
5325
5336
  };
5326
5337
  }
5327
5338
  function resolveInsideWorkspace(workspaceDir, relative) {
5328
- const base = path13.resolve(workspaceDir);
5329
- const resolved = path13.resolve(base, relative);
5330
- const rel = path13.relative(base, resolved);
5331
- if (rel === "" || rel === "." || !rel.startsWith("..") && !path13.isAbsolute(rel)) {
5339
+ const base = path15.resolve(workspaceDir);
5340
+ const resolved = path15.resolve(base, relative);
5341
+ const rel = path15.relative(base, resolved);
5342
+ if (rel === "" || rel === "." || !rel.startsWith("..") && !path15.isAbsolute(rel)) {
5332
5343
  return resolved;
5333
5344
  }
5334
5345
  throw new Error(
@@ -5472,7 +5483,7 @@ async function doctorWorkspace(location) {
5472
5483
  const abs = resolveInsideWorkspace(location.workspaceDir, rel);
5473
5484
  for (const file of await listJsonl(abs)) {
5474
5485
  try {
5475
- const s = await stat(path13.join(abs, file));
5486
+ const s = await stat(path15.join(abs, file));
5476
5487
  newestTraceMtime = Math.max(newestTraceMtime, s.mtimeMs);
5477
5488
  } catch {
5478
5489
  checks.push({ id: "trace-readability", status: "warn", message: `cannot stat ${rel}/${file}` });
@@ -5502,7 +5513,7 @@ async function doctorWorkspace(location) {
5502
5513
 
5503
5514
  // packages/viewer/src/workspace-data.ts
5504
5515
  async function loadWorkspaceViewerData(options) {
5505
- const cwd = path13.resolve(options.cwd ?? process.cwd());
5516
+ const cwd = path15.resolve(options.cwd ?? process.cwd());
5506
5517
  const location = resolveWorkspaceLocation(cwd);
5507
5518
  const manifestRead = await readWorkspaceManifestFile(location);
5508
5519
  if (!manifestRead.ok || manifestRead.manifest === void 0) {
@@ -5515,7 +5526,7 @@ async function loadWorkspaceViewerData(options) {
5515
5526
  const runs = [];
5516
5527
  for (const rel of manifestRead.manifest.traceDirs) {
5517
5528
  const traceDir = resolveTraceDir({
5518
- dir: path13.join(location.workspaceDir, rel)
5529
+ dir: path15.join(location.workspaceDir, rel)
5519
5530
  });
5520
5531
  const td = new TraceDirectory({ dir: traceDir });
5521
5532
  const files = await td.list();
@@ -5529,7 +5540,7 @@ async function loadWorkspaceViewerData(options) {
5529
5540
  runId: meta.runId,
5530
5541
  ...meta.name !== void 0 ? { name: meta.name } : {},
5531
5542
  status: meta.status,
5532
- file: path13.basename(meta.filePath)
5543
+ file: path15.basename(meta.filePath)
5533
5544
  });
5534
5545
  }
5535
5546
  }
@@ -5540,8 +5551,8 @@ async function loadWorkspaceViewerData(options) {
5540
5551
  doctor,
5541
5552
  runs,
5542
5553
  bundleDirs: [
5543
- path13.join(location.workspaceDir, manifestRead.manifest.bundlesDir),
5544
- path13.join(location.workspaceDir, manifestRead.manifest.artifactsDir)
5554
+ path15.join(location.workspaceDir, manifestRead.manifest.bundlesDir),
5555
+ path15.join(location.workspaceDir, manifestRead.manifest.artifactsDir)
5545
5556
  ]
5546
5557
  };
5547
5558
  }
@@ -5604,7 +5615,7 @@ function createViewerServer(options = {}) {
5604
5615
  ok: true,
5605
5616
  readOnly: true,
5606
5617
  mode,
5607
- traceDir: path13.resolve(traceDir)
5618
+ traceDir: path15.resolve(traceDir)
5608
5619
  });
5609
5620
  }
5610
5621
  if (pathname === "/api/suite" && mode === "suite") {
@@ -5633,7 +5644,7 @@ function createViewerServer(options = {}) {
5633
5644
  runId: meta.runId,
5634
5645
  name: meta.name,
5635
5646
  status: meta.status,
5636
- file: path13.basename(meta.filePath),
5647
+ file: path15.basename(meta.filePath),
5637
5648
  startedAt: meta.startedAt,
5638
5649
  durationMs: meta.durationMs
5639
5650
  }))
@@ -5750,7 +5761,7 @@ function startViewerServer(options = {}) {
5750
5761
  resolve({
5751
5762
  host,
5752
5763
  port: resolvedPort,
5753
- traceDir: path13.resolve(traceDir),
5764
+ traceDir: path15.resolve(traceDir),
5754
5765
  url: `http://${host}:${resolvedPort}/${modeQuery}`,
5755
5766
  mode
5756
5767
  });