@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.cjs +874 -855
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +873 -854
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
|
-
import
|
|
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 =
|
|
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
|
|
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 =
|
|
544
|
+
safeId = path15.basename(safeId);
|
|
545
545
|
if (safeId === "" || safeId === "." || safeId === "..") {
|
|
546
546
|
safeId = "run_unknown";
|
|
547
547
|
}
|
|
548
|
-
return
|
|
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 ?
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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/
|
|
1571
|
-
|
|
1572
|
-
"
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
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
|
|
1579
|
+
return out;
|
|
1586
1580
|
}
|
|
1587
|
-
function
|
|
1588
|
-
|
|
1589
|
-
|
|
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
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
return
|
|
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
|
|
1602
|
-
|
|
1603
|
-
|
|
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
|
|
1654
|
-
|
|
1655
|
-
|
|
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
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
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
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
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
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
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
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1676
|
+
roots.sort(sortByOrder);
|
|
1677
|
+
for (const n of nodes.values()) {
|
|
1678
|
+
n.children.sort(sortByOrder);
|
|
1714
1679
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
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/
|
|
1720
|
-
var
|
|
1721
|
-
|
|
1722
|
-
|
|
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
|
-
|
|
1726
|
-
|
|
1727
|
-
await access(filePath);
|
|
1728
|
-
return true;
|
|
1729
|
-
} catch {
|
|
1730
|
-
return false;
|
|
1731
|
-
}
|
|
1694
|
+
function buildPath(segments) {
|
|
1695
|
+
return { path: [...segments] };
|
|
1732
1696
|
}
|
|
1733
|
-
|
|
1734
|
-
const
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
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
|
-
|
|
1743
|
-
|
|
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
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
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
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
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 (
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
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
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
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
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
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
|
|
1812
|
-
const
|
|
1813
|
-
if (
|
|
1814
|
-
|
|
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
|
|
1817
|
-
|
|
1818
|
-
|
|
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
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
|
|
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
|
|
1845
|
+
function mergeDiffDefaults(options) {
|
|
1843
1846
|
return {
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1847
|
+
ignoreDuration: false,
|
|
1848
|
+
durationThresholdMs: DEFAULT_THRESHOLD_MS,
|
|
1849
|
+
focus: "all",
|
|
1850
|
+
check: "all"
|
|
1848
1851
|
};
|
|
1849
1852
|
}
|
|
1850
|
-
function
|
|
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
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
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
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
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
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
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
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
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
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
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
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
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
|
-
|
|
2005
|
+
diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}].id is required.`)
|
|
1954
2006
|
);
|
|
1955
|
-
|
|
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
|
-
|
|
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
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
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
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
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
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
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
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
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
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
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
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
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
|
-
|
|
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
|
|
2032
|
-
|
|
2033
|
-
|
|
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
|
|
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
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
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
|
-
|
|
2051
|
-
|
|
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
|
|
2233
|
+
function diagnostic3(code, message, ruleId) {
|
|
2054
2234
|
return {
|
|
2055
|
-
|
|
2056
|
-
severity: "error",
|
|
2057
|
-
status: "fail",
|
|
2235
|
+
code,
|
|
2058
2236
|
message,
|
|
2059
|
-
|
|
2060
|
-
...
|
|
2061
|
-
evidence: [...evidence]
|
|
2237
|
+
severity: "error",
|
|
2238
|
+
...ruleId ? { ruleId } : {}
|
|
2062
2239
|
};
|
|
2063
2240
|
}
|
|
2064
|
-
function
|
|
2065
|
-
return
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
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
|
|
2074
|
-
return
|
|
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
|
|
2077
|
-
return
|
|
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
|
|
2082
|
-
const
|
|
2083
|
-
const
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5312
|
-
const workspaceDir =
|
|
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:
|
|
5335
|
+
manifestPath: path15.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
|
|
5317
5336
|
};
|
|
5318
5337
|
}
|
|
5319
5338
|
function resolveInsideWorkspace(workspaceDir, relative) {
|
|
5320
|
-
const base =
|
|
5321
|
-
const resolved =
|
|
5322
|
-
const rel =
|
|
5323
|
-
if (rel === "" || rel === "." || !rel.startsWith("..") && !
|
|
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(
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
-
|
|
5536
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
5764
|
+
traceDir: path15.resolve(traceDir),
|
|
5746
5765
|
url: `http://${host}:${resolvedPort}/${modeQuery}`,
|
|
5747
5766
|
mode
|
|
5748
5767
|
});
|