@agent-inspect/viewer 6.8.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,528 +1567,927 @@ 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
- };
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" };
2011
2115
  }
2012
- function summarize(findings, diagnostics) {
2013
- return {
2014
- passed: findings.filter((finding) => finding.status === "pass").length,
2015
- failed: findings.filter(
2016
- (finding) => finding.status === "fail" && finding.severity === "error"
2017
- ).length,
2018
- warnings: findings.filter(
2019
- (finding) => finding.status === "warning" || finding.severity === "warning"
2020
- ).length,
2021
- errors: diagnostics.filter((item) => item.severity === "error").length
2022
- };
2116
+ async function fileExists(filePath) {
2117
+ try {
2118
+ await access(filePath);
2119
+ return true;
2120
+ } catch {
2121
+ return false;
2122
+ }
2023
2123
  }
2024
- function stringAttr(event, keys) {
2025
- for (const key of keys) {
2026
- const value = event.attributes?.[key];
2027
- 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());
2028
2128
  }
2029
- 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
+ );
2030
2136
  }
2031
- function stripPrefix(name, prefixes) {
2032
- for (const prefix of prefixes) {
2033
- 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;
2034
2187
  }
2035
- return name;
2036
2188
  }
2037
- 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
+ }
2038
2211
  return {
2039
- runId: event.runId,
2040
- eventId: event.eventId,
2041
- parentId: event.parentId,
2042
- traceId: event.trace?.traceId,
2043
- spanId: event.trace?.spanId,
2044
- kind: event.kind,
2045
- name: event.name,
2046
- status: event.status,
2047
- ...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}`
2048
2216
  };
2049
2217
  }
2050
- function runEvidence(run) {
2051
- 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 ?? "");
2052
2232
  }
2053
- function failFinding(ruleId, message, evidence, expected, actual) {
2233
+ function diagnostic3(code, message, ruleId) {
2054
2234
  return {
2055
- ruleId,
2056
- severity: "error",
2057
- status: "fail",
2235
+ code,
2058
2236
  message,
2059
- ...expected !== void 0 ? { expected } : {},
2060
- ...actual !== void 0 ? { actual } : {},
2061
- evidence: [...evidence]
2237
+ severity: "error",
2238
+ ...ruleId ? { ruleId } : {}
2062
2239
  };
2063
2240
  }
2064
- function toolName(event) {
2065
- return stringAttr(event, ["toolName", "tool"]) ?? stripPrefix(event.name, ["tool:", "function:", "mcp-tools:"]);
2066
- }
2067
- function llmModel(event) {
2068
- return stringAttr(event, ["model", "modelId", "responseModelId", "modelName", "model_name"]) ?? stripPrefix(event.name, ["llm:", "generation:", "transcription:", "speech:"]);
2069
- }
2070
- function llmProvider(event) {
2071
- return stringAttr(event, ["provider", "providerName", "provider_name"]);
2241
+ function emptySummary() {
2242
+ return {
2243
+ passed: 0,
2244
+ failed: 0,
2245
+ warnings: 0,
2246
+ errors: 0
2247
+ };
2072
2248
  }
2073
- function llmFinishReason(event) {
2074
- return stringAttr(event, ["finishReason", "rawFinishReason", "finish_reason"]);
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
+ };
2075
2262
  }
2076
- function finishedEvents(context, kind) {
2077
- return context.events.filter(
2078
- (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
2079
- );
2263
+ function flattenNodes(nodes) {
2264
+ return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
2080
2265
  }
2081
- function createRunStatusRule(options = {}) {
2082
- const expected = options.expected ?? "ok";
2083
- const allowIncomplete = options.allowIncomplete === true;
2084
- return {
2085
- id: "run.status",
2086
- category: "run",
2087
- defaultSeverity: "error",
2088
- evaluate(context) {
2089
- const findings = [];
2090
- const actual = context.selectedRun?.status ?? "unknown";
2091
- if (actual !== expected) {
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
+ );
2479
+ }
2480
+ function createRunStatusRule(options = {}) {
2481
+ const expected = options.expected ?? "ok";
2482
+ const allowIncomplete = options.allowIncomplete === true;
2483
+ return {
2484
+ id: "run.status",
2485
+ category: "run",
2486
+ defaultSeverity: "error",
2487
+ evaluate(context) {
2488
+ const findings = [];
2489
+ const actual = context.selectedRun?.status ?? "unknown";
2490
+ if (actual !== expected) {
2092
2491
  findings.push(
2093
2492
  failFinding(
2094
2493
  "run.status",
@@ -2964,7 +3363,7 @@ function findReaderByFormat(format, readers) {
2964
3363
  }
2965
3364
  async function jsonlFilesInDirectory(dirPath) {
2966
3365
  const entries = await readdir(dirPath, { withFileTypes: true });
2967
- 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));
2968
3367
  }
2969
3368
  async function resolveInput(input) {
2970
3369
  const cached = resolvedInputCache.get(input);
@@ -4584,7 +4983,7 @@ async function runSuiteCase(suiteCase, config, options) {
4584
4983
  async function runSuite(options = {}) {
4585
4984
  const startedAt = new Date(options.nowMs ?? Date.now()).toISOString();
4586
4985
  const { config, configPath, configDir } = await loadSuiteConfig(options);
4587
- const tracesDir = path13.resolve(configDir, config.traces);
4986
+ const tracesDir = path15.resolve(configDir, config.traces);
4588
4987
  const cases = [];
4589
4988
  const diagnostics = [];
4590
4989
  for (const suiteCase of config.cases) {
@@ -4619,22 +5018,6 @@ async function runSuite(options = {}) {
4619
5018
  };
4620
5019
  }
4621
5020
 
4622
- // packages/core/src/exporters/helpers.ts
4623
- function sortKeysDeep(input) {
4624
- if (input === null || typeof input !== "object") return input;
4625
- if (Array.isArray(input)) return input.map(sortKeysDeep);
4626
- const o = input;
4627
- const out = {};
4628
- for (const k of Object.keys(o).sort()) {
4629
- out[k] = sortKeysDeep(o[k]);
4630
- }
4631
- return out;
4632
- }
4633
- function stableJson(value, pretty) {
4634
- const sorted = sortKeysDeep(value);
4635
- return JSON.stringify(sorted);
4636
- }
4637
-
4638
5021
  // packages/viewer/src/html.ts
4639
5022
  var viewerIndexHtml = `<!DOCTYPE html>
4640
5023
  <html lang="en">
@@ -4724,370 +5107,6 @@ var viewerIndexHtml = `<!DOCTYPE html>
4724
5107
  </body>
4725
5108
  </html>
4726
5109
  `;
4727
-
4728
- // packages/core/src/diff/comparable.ts
4729
- function extractOutputPreview(meta) {
4730
- if (meta === void 0) return void 0;
4731
- if ("outputPreview" in meta) return meta.outputPreview;
4732
- if ("resultPreview" in meta) return meta.resultPreview;
4733
- return void 0;
4734
- }
4735
- function mapStepStatus(s) {
4736
- if (s === void 0) return "running";
4737
- return s;
4738
- }
4739
- function manualTraceEventsToComparableRun(events) {
4740
- const started = events.find((e) => e.event === "run_started");
4741
- if (!started || started.event !== "run_started") {
4742
- throw new Error("Invalid trace: missing run_started");
4743
- }
4744
- const rs = started;
4745
- const runId = rs.runId;
4746
- const completedAll = events.filter((e) => e.event === "run_completed");
4747
- const lastCompleted = completedAll[completedAll.length - 1];
4748
- let runStatus;
4749
- if (lastCompleted === void 0) runStatus = "running";
4750
- else runStatus = lastCompleted.status;
4751
- const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
4752
- const steps = /* @__PURE__ */ new Map();
4753
- let order = 0;
4754
- for (const e of events) {
4755
- if (e.event !== "step_started") continue;
4756
- const s = e;
4757
- const meta = s.metadata ? { ...s.metadata } : void 0;
4758
- steps.set(s.stepId, {
4759
- id: s.stepId,
4760
- parentId: s.parentId,
4761
- name: s.name,
4762
- type: s.type,
4763
- order: order++,
4764
- timestamp: s.timestamp,
4765
- metadata: meta
4766
- });
4767
- }
4768
- for (const e of events) {
4769
- if (e.event !== "step_completed") continue;
4770
- const acc = steps.get(e.stepId);
4771
- if (!acc) continue;
4772
- acc.status = e.status;
4773
- acc.durationMs = e.durationMs;
4774
- if (e.error?.message) acc.errorMsg = e.error.message;
4775
- const extra = e;
4776
- if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
4777
- acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
4778
- }
4779
- }
4780
- const nodes = /* @__PURE__ */ new Map();
4781
- for (const acc of steps.values()) {
4782
- let meta = acc.metadata ? { ...acc.metadata } : void 0;
4783
- if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
4784
- meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
4785
- }
4786
- const outputPreview = extractOutputPreview(meta);
4787
- if (meta !== void 0 && ("outputPreview" in meta || "resultPreview" in meta)) {
4788
- delete meta.outputPreview;
4789
- delete meta.resultPreview;
4790
- }
4791
- const sc = {
4792
- id: acc.id,
4793
- name: acc.name,
4794
- type: acc.type,
4795
- status: mapStepStatus(acc.status),
4796
- durationMs: acc.durationMs,
4797
- error: acc.errorMsg,
4798
- metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
4799
- outputPreview,
4800
- children: []
4801
- };
4802
- nodes.set(acc.id, sc);
4803
- }
4804
- const roots = [];
4805
- const sortByOrder = (a, b) => {
4806
- const oa = steps.get(a.id)?.order ?? 0;
4807
- const ob = steps.get(b.id)?.order ?? 0;
4808
- return oa - ob;
4809
- };
4810
- for (const acc of steps.values()) {
4811
- const node = nodes.get(acc.id);
4812
- if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
4813
- nodes.get(acc.parentId).children.push(node);
4814
- } else {
4815
- roots.push(node);
4816
- }
4817
- }
4818
- roots.sort(sortByOrder);
4819
- for (const n of nodes.values()) {
4820
- n.children.sort(sortByOrder);
4821
- }
4822
- return {
4823
- runId,
4824
- name: rs.name,
4825
- status: runStatus,
4826
- durationMs,
4827
- steps: roots
4828
- };
4829
- }
4830
-
4831
- // packages/core/src/diff/engine.ts
4832
- var DEFAULT_THRESHOLD_MS = 0;
4833
- function pathSeg(step, index) {
4834
- return { index, name: step.name, stepId: step.id };
4835
- }
4836
- function buildPath(segments) {
4837
- return { path: [...segments] };
4838
- }
4839
- function pairSteps(left, right) {
4840
- const usedRight = /* @__PURE__ */ new Set();
4841
- const pairs = [];
4842
- for (let i = 0; i < left.length; i++) {
4843
- const L = left[i];
4844
- let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
4845
- if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
4846
- const cand = right[i];
4847
- if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
4848
- R = cand;
4849
- }
4850
- }
4851
- if (R === void 0) {
4852
- R = right.find(
4853
- (r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
4854
- );
4855
- }
4856
- if (R !== void 0) {
4857
- usedRight.add(R.id);
4858
- pairs.push([L, R]);
4859
- } else {
4860
- pairs.push([L, void 0]);
4861
- }
4862
- }
4863
- for (const R of right) {
4864
- if (!usedRight.has(R.id)) {
4865
- pairs.push([void 0, R]);
4866
- }
4867
- }
4868
- return pairs;
4869
- }
4870
- function compareLeafSteps(L, R, segments, opts, out) {
4871
- const path16 = buildPath(segments);
4872
- if (L.name !== R.name) {
4873
- out.push({
4874
- kind: "structure",
4875
- severity: "warning",
4876
- message: "Step name differs",
4877
- path: path16,
4878
- left: L.name,
4879
- right: R.name
4880
- });
4881
- }
4882
- if ((L.type ?? "") !== (R.type ?? "")) {
4883
- out.push({
4884
- kind: "step-type",
4885
- severity: "warning",
4886
- message: "Step type differs",
4887
- path: path16,
4888
- left: L.type,
4889
- right: R.type
4890
- });
4891
- }
4892
- if ((L.status ?? "") !== (R.status ?? "")) {
4893
- out.push({
4894
- kind: "step-status",
4895
- severity: "warning",
4896
- message: "Step status differs",
4897
- path: path16,
4898
- left: L.status,
4899
- right: R.status
4900
- });
4901
- }
4902
- const le = L.error ?? "";
4903
- const re = R.error ?? "";
4904
- if (le !== re) {
4905
- out.push({
4906
- kind: "error",
4907
- severity: "error",
4908
- message: "Step error message differs",
4909
- path: path16,
4910
- left: le || void 0,
4911
- right: re || void 0
4912
- });
4913
- }
4914
- if (!opts.ignoreDuration) {
4915
- const ld = L.durationMs;
4916
- const rd = R.durationMs;
4917
- const th = opts.durationThresholdMs;
4918
- let differs = false;
4919
- if (ld === void 0 && rd === void 0) differs = false;
4920
- else if (ld === void 0 || rd === void 0) differs = true;
4921
- else differs = Math.abs(ld - rd) > th;
4922
- if (differs) {
4923
- out.push({
4924
- kind: "duration",
4925
- severity: "info",
4926
- message: "Step duration differs",
4927
- path: path16,
4928
- left: ld,
4929
- right: rd
4930
- });
4931
- }
4932
- }
4933
- const lm = stableJson(L.metadata ?? {});
4934
- const rm2 = stableJson(R.metadata ?? {});
4935
- if (lm !== rm2) {
4936
- out.push({
4937
- kind: "metadata",
4938
- severity: "info",
4939
- message: "Step metadata differs",
4940
- path: path16,
4941
- left: L.metadata,
4942
- right: R.metadata
4943
- });
4944
- }
4945
- const lo = stableJson(L.outputPreview ?? null);
4946
- const ro = stableJson(R.outputPreview ?? null);
4947
- if (lo !== ro) {
4948
- out.push({
4949
- kind: "output",
4950
- severity: "info",
4951
- message: "Output preview differs",
4952
- path: path16,
4953
- left: L.outputPreview,
4954
- right: R.outputPreview
4955
- });
4956
- }
4957
- }
4958
- function compareRecursive(L, R, segments, opts, out) {
4959
- compareLeafSteps(L, R, segments, opts, out);
4960
- const pairs = pairSteps(L.children, R.children);
4961
- let ci = 0;
4962
- for (const [lch, rch] of pairs) {
4963
- if (lch !== void 0 && rch !== void 0) {
4964
- compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
4965
- } else if (lch !== void 0) {
4966
- out.push({
4967
- kind: "step-removed",
4968
- severity: "warning",
4969
- message: `Step only in left run: ${lch.name}`,
4970
- path: buildPath([...segments, pathSeg(lch, ci)]),
4971
- left: lch.id,
4972
- right: void 0
4973
- });
4974
- } else if (rch !== void 0) {
4975
- out.push({
4976
- kind: "step-added",
4977
- severity: "warning",
4978
- message: `Step only in right run: ${rch.name}`,
4979
- path: buildPath([...segments, pathSeg(rch, ci)]),
4980
- left: void 0,
4981
- right: rch.id
4982
- });
4983
- }
4984
- ci += 1;
4985
- }
4986
- }
4987
- function mergeDiffDefaults(options) {
4988
- return {
4989
- ignoreDuration: false,
4990
- durationThresholdMs: DEFAULT_THRESHOLD_MS,
4991
- focus: "all",
4992
- check: "all"
4993
- };
4994
- }
4995
- function kindMatchesFilter(kind, merged) {
4996
- return true;
4997
- }
4998
- function diffRuns(left, right, options) {
4999
- const merged = mergeDiffDefaults();
5000
- const opts = {
5001
- ignoreDuration: merged.ignoreDuration,
5002
- durationThresholdMs: merged.durationThresholdMs
5003
- };
5004
- const raw = [];
5005
- if ((left.status ?? "") !== (right.status ?? "")) {
5006
- raw.push({
5007
- kind: "run-status",
5008
- severity: "warning",
5009
- message: "Run completion status differs",
5010
- left: left.status,
5011
- right: right.status
5012
- });
5013
- }
5014
- {
5015
- const ld = left.durationMs;
5016
- const rd = right.durationMs;
5017
- const th = merged.durationThresholdMs;
5018
- let differs = false;
5019
- if (ld === void 0 && rd === void 0) differs = false;
5020
- else if (ld === void 0 || rd === void 0) differs = true;
5021
- else differs = Math.abs(ld - rd) > th;
5022
- if (differs) {
5023
- raw.push({
5024
- kind: "duration",
5025
- severity: "info",
5026
- message: "Run duration differs",
5027
- left: ld,
5028
- right: rd
5029
- });
5030
- }
5031
- }
5032
- const pairs = pairSteps(left.steps, right.steps);
5033
- let idx = 0;
5034
- for (const [ls, rs] of pairs) {
5035
- if (ls !== void 0 && rs !== void 0) {
5036
- compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
5037
- idx += 1;
5038
- } else if (ls !== void 0) {
5039
- raw.push({
5040
- kind: "step-removed",
5041
- severity: "warning",
5042
- message: `Step only in left run: ${ls.name}`,
5043
- path: buildPath([pathSeg(ls, idx)]),
5044
- left: ls.id,
5045
- right: void 0
5046
- });
5047
- idx += 1;
5048
- } else if (rs !== void 0) {
5049
- raw.push({
5050
- kind: "step-added",
5051
- severity: "warning",
5052
- message: `Step only in right run: ${rs.name}`,
5053
- path: buildPath([pathSeg(rs, idx)]),
5054
- left: void 0,
5055
- right: rs.id
5056
- });
5057
- idx += 1;
5058
- }
5059
- }
5060
- const differences = raw.filter((d) => kindMatchesFilter(d.kind));
5061
- let errors = 0;
5062
- let warnings = 0;
5063
- let info = 0;
5064
- for (const d of differences) {
5065
- if (d.severity === "error") errors += 1;
5066
- else if (d.severity === "warning") warnings += 1;
5067
- else info += 1;
5068
- }
5069
- const firstVisible = differences[0];
5070
- const firstDivergence = firstVisible !== void 0 ? {
5071
- kind: "first-divergence",
5072
- severity: firstVisible.severity,
5073
- message: `First divergence: ${firstVisible.message}`,
5074
- path: firstVisible.path,
5075
- left: firstVisible.left,
5076
- right: firstVisible.right
5077
- } : void 0;
5078
- const summary = {
5079
- leftRunId: left.runId,
5080
- rightRunId: right.runId,
5081
- totalDifferences: differences.length,
5082
- errors,
5083
- warnings,
5084
- info,
5085
- firstDivergence
5086
- };
5087
- return { summary, differences };
5088
- }
5089
-
5090
- // packages/viewer/src/suite-data.ts
5091
5110
  async function enrichCase(suiteCase, baselineTracePath) {
5092
5111
  const base = {
5093
5112
  id: suiteCase.id,
@@ -5147,7 +5166,7 @@ async function loadSuiteViewerData(options) {
5147
5166
  for (const suiteCase of result.cases) {
5148
5167
  cases.push(await enrichCase(suiteCase, baselinePath));
5149
5168
  }
5150
- 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");
5151
5170
  return {
5152
5171
  suiteName: result.suiteName,
5153
5172
  configPath: result.configPath,
@@ -5308,19 +5327,19 @@ function parseWorkspaceManifest(json) {
5308
5327
  }
5309
5328
  var INDEX_DIR_NAME = "index";
5310
5329
  function resolveWorkspaceLocation(cwd = process.cwd()) {
5311
- const projectRoot = path13.resolve(cwd);
5312
- const workspaceDir = path13.join(projectRoot, WORKSPACE_DIR_NAME);
5330
+ const projectRoot = path15.resolve(cwd);
5331
+ const workspaceDir = path15.join(projectRoot, WORKSPACE_DIR_NAME);
5313
5332
  return {
5314
5333
  projectRoot,
5315
5334
  workspaceDir,
5316
- manifestPath: path13.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
5335
+ manifestPath: path15.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
5317
5336
  };
5318
5337
  }
5319
5338
  function resolveInsideWorkspace(workspaceDir, relative) {
5320
- const base = path13.resolve(workspaceDir);
5321
- const resolved = path13.resolve(base, relative);
5322
- const rel = path13.relative(base, resolved);
5323
- 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)) {
5324
5343
  return resolved;
5325
5344
  }
5326
5345
  throw new Error(
@@ -5464,7 +5483,7 @@ async function doctorWorkspace(location) {
5464
5483
  const abs = resolveInsideWorkspace(location.workspaceDir, rel);
5465
5484
  for (const file of await listJsonl(abs)) {
5466
5485
  try {
5467
- const s = await stat(path13.join(abs, file));
5486
+ const s = await stat(path15.join(abs, file));
5468
5487
  newestTraceMtime = Math.max(newestTraceMtime, s.mtimeMs);
5469
5488
  } catch {
5470
5489
  checks.push({ id: "trace-readability", status: "warn", message: `cannot stat ${rel}/${file}` });
@@ -5494,7 +5513,7 @@ async function doctorWorkspace(location) {
5494
5513
 
5495
5514
  // packages/viewer/src/workspace-data.ts
5496
5515
  async function loadWorkspaceViewerData(options) {
5497
- const cwd = path13.resolve(options.cwd ?? process.cwd());
5516
+ const cwd = path15.resolve(options.cwd ?? process.cwd());
5498
5517
  const location = resolveWorkspaceLocation(cwd);
5499
5518
  const manifestRead = await readWorkspaceManifestFile(location);
5500
5519
  if (!manifestRead.ok || manifestRead.manifest === void 0) {
@@ -5507,7 +5526,7 @@ async function loadWorkspaceViewerData(options) {
5507
5526
  const runs = [];
5508
5527
  for (const rel of manifestRead.manifest.traceDirs) {
5509
5528
  const traceDir = resolveTraceDir({
5510
- dir: path13.join(location.workspaceDir, rel)
5529
+ dir: path15.join(location.workspaceDir, rel)
5511
5530
  });
5512
5531
  const td = new TraceDirectory({ dir: traceDir });
5513
5532
  const files = await td.list();
@@ -5521,7 +5540,7 @@ async function loadWorkspaceViewerData(options) {
5521
5540
  runId: meta.runId,
5522
5541
  ...meta.name !== void 0 ? { name: meta.name } : {},
5523
5542
  status: meta.status,
5524
- file: path13.basename(meta.filePath)
5543
+ file: path15.basename(meta.filePath)
5525
5544
  });
5526
5545
  }
5527
5546
  }
@@ -5532,8 +5551,8 @@ async function loadWorkspaceViewerData(options) {
5532
5551
  doctor,
5533
5552
  runs,
5534
5553
  bundleDirs: [
5535
- path13.join(location.workspaceDir, manifestRead.manifest.bundlesDir),
5536
- path13.join(location.workspaceDir, manifestRead.manifest.artifactsDir)
5554
+ path15.join(location.workspaceDir, manifestRead.manifest.bundlesDir),
5555
+ path15.join(location.workspaceDir, manifestRead.manifest.artifactsDir)
5537
5556
  ]
5538
5557
  };
5539
5558
  }
@@ -5596,7 +5615,7 @@ function createViewerServer(options = {}) {
5596
5615
  ok: true,
5597
5616
  readOnly: true,
5598
5617
  mode,
5599
- traceDir: path13.resolve(traceDir)
5618
+ traceDir: path15.resolve(traceDir)
5600
5619
  });
5601
5620
  }
5602
5621
  if (pathname === "/api/suite" && mode === "suite") {
@@ -5625,7 +5644,7 @@ function createViewerServer(options = {}) {
5625
5644
  runId: meta.runId,
5626
5645
  name: meta.name,
5627
5646
  status: meta.status,
5628
- file: path13.basename(meta.filePath),
5647
+ file: path15.basename(meta.filePath),
5629
5648
  startedAt: meta.startedAt,
5630
5649
  durationMs: meta.durationMs
5631
5650
  }))
@@ -5742,7 +5761,7 @@ function startViewerServer(options = {}) {
5742
5761
  resolve({
5743
5762
  host,
5744
5763
  port: resolvedPort,
5745
- traceDir: path13.resolve(traceDir),
5764
+ traceDir: path15.resolve(traceDir),
5746
5765
  url: `http://${host}:${resolvedPort}/${modeQuery}`,
5747
5766
  mode
5748
5767
  });