agent-inspect 2.3.0 → 2.5.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/CHANGELOG.md +24 -0
- package/README.md +33 -4
- package/docs/ADAPTERS.md +47 -0
- package/docs/CLI.md +57 -1
- package/docs/LIMITATIONS.md +6 -0
- package/docs/SCHEMA.md +1 -0
- package/package.json +2 -2
- package/packages/cli/dist/index.cjs +1454 -74
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +1455 -75
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/core/dist/advanced.cjs +581 -3
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +172 -8
- package/packages/core/dist/advanced.d.ts +172 -8
- package/packages/core/dist/advanced.mjs +572 -4
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/core/dist/checks.d.cts +2 -2
- package/packages/core/dist/checks.d.ts +2 -2
- package/packages/core/dist/{context-yv2VSDQF.d.ts → context-BDZmi53V.d.ts} +3 -3
- package/packages/core/dist/{context-CSKnzpXR.d.cts → context-DjY-jvQk.d.cts} +3 -3
- package/packages/core/dist/diff.d.cts +3 -3
- package/packages/core/dist/diff.d.ts +3 -3
- package/packages/core/dist/exporters.d.cts +3 -3
- package/packages/core/dist/exporters.d.ts +3 -3
- package/packages/core/dist/index.d.cts +6 -6
- package/packages/core/dist/index.d.ts +6 -6
- package/packages/core/dist/{inspect-event-CevRYp58.d.cts → inspect-event-CYAV7Hxm.d.cts} +1 -1
- package/packages/core/dist/{inspect-event-CevRYp58.d.ts → inspect-event-CYAV7Hxm.d.ts} +1 -1
- package/packages/core/dist/{log-config-8aE5Kxtr.d.cts → log-config-BNQ9UTmP.d.cts} +1 -1
- package/packages/core/dist/{log-config-DVimQJho.d.ts → log-config-D3Yp1SZN.d.ts} +1 -1
- package/packages/core/dist/logs.d.cts +3 -3
- package/packages/core/dist/logs.d.ts +3 -3
- package/packages/core/dist/{persisted-inspect-event-D-WpXeZX.d.ts → persisted-inspect-event-BLvb0jSX.d.ts} +1 -1
- package/packages/core/dist/{persisted-inspect-event-DHcHPUKv.d.cts → persisted-inspect-event-D5SjBz9b.d.cts} +1 -1
- package/packages/core/dist/persisted.d.cts +5 -5
- package/packages/core/dist/persisted.d.ts +5 -5
- package/packages/core/dist/readers.d.cts +2 -2
- package/packages/core/dist/readers.d.ts +2 -2
- package/packages/core/dist/{types-DgMN3qow.d.cts → types-C-llnPH0.d.cts} +1 -1
- package/packages/core/dist/{types-D-Y1kOU-.d.ts → types-CQun9LW6.d.ts} +1 -1
- package/packages/core/dist/writers.d.cts +2 -2
- package/packages/core/dist/writers.d.ts +2 -2
|
@@ -23,7 +23,7 @@ var process2__default = /*#__PURE__*/_interopDefault(process2);
|
|
|
23
23
|
var tty__default = /*#__PURE__*/_interopDefault(tty);
|
|
24
24
|
|
|
25
25
|
// package.json
|
|
26
|
-
var version = "2.
|
|
26
|
+
var version = "2.5.0";
|
|
27
27
|
|
|
28
28
|
// packages/core/src/correlation-metadata.ts
|
|
29
29
|
var TRACE_CORRELATION_KEYS = [
|
|
@@ -1440,18 +1440,18 @@ var proto = Object.defineProperties(() => {
|
|
|
1440
1440
|
}
|
|
1441
1441
|
}
|
|
1442
1442
|
});
|
|
1443
|
-
var createStyler = (
|
|
1443
|
+
var createStyler = (open3, close, parent) => {
|
|
1444
1444
|
let openAll;
|
|
1445
1445
|
let closeAll;
|
|
1446
1446
|
if (parent === void 0) {
|
|
1447
|
-
openAll =
|
|
1447
|
+
openAll = open3;
|
|
1448
1448
|
closeAll = close;
|
|
1449
1449
|
} else {
|
|
1450
|
-
openAll = parent.openAll +
|
|
1450
|
+
openAll = parent.openAll + open3;
|
|
1451
1451
|
closeAll = close + parent.closeAll;
|
|
1452
1452
|
}
|
|
1453
1453
|
return {
|
|
1454
|
-
open:
|
|
1454
|
+
open: open3,
|
|
1455
1455
|
close,
|
|
1456
1456
|
openAll,
|
|
1457
1457
|
closeAll,
|
|
@@ -1516,15 +1516,15 @@ function getStatusIcon(status) {
|
|
|
1516
1516
|
if (status === "error") return source_default.red("\u2716");
|
|
1517
1517
|
return source_default.yellow("\u23F3");
|
|
1518
1518
|
}
|
|
1519
|
-
function renderStepLine(name,
|
|
1519
|
+
function renderStepLine(name, durationMs2, status, depth) {
|
|
1520
1520
|
try {
|
|
1521
1521
|
const nm = formatTerminalName(name);
|
|
1522
1522
|
const ind = getIndent(depth ?? 0);
|
|
1523
|
-
if (status === "running" &&
|
|
1523
|
+
if (status === "running" && durationMs2 === void 0) {
|
|
1524
1524
|
return `${ind}${source_default.yellow("\u23F3")} ${nm}`;
|
|
1525
1525
|
}
|
|
1526
|
-
const hasDur =
|
|
1527
|
-
const dur = hasDur ? formatDuration2(
|
|
1526
|
+
const hasDur = durationMs2 !== void 0 && Number.isFinite(durationMs2);
|
|
1527
|
+
const dur = hasDur ? formatDuration2(durationMs2) : void 0;
|
|
1528
1528
|
if (status === "running") {
|
|
1529
1529
|
return dur !== void 0 ? `${ind}${source_default.yellow("\u23F3")} ${nm} (${dur})` : `${ind}${source_default.yellow("\u23F3")} ${nm}`;
|
|
1530
1530
|
}
|
|
@@ -1677,14 +1677,14 @@ async function extractMetadata(filePath, _quickScan) {
|
|
|
1677
1677
|
} else {
|
|
1678
1678
|
status = "unknown";
|
|
1679
1679
|
}
|
|
1680
|
-
const
|
|
1680
|
+
const durationMs2 = explicitDurationMs ?? (startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt ? endedAt - startedAt : void 0);
|
|
1681
1681
|
return {
|
|
1682
1682
|
runId: resolvedRunId,
|
|
1683
1683
|
name,
|
|
1684
1684
|
status,
|
|
1685
1685
|
startedAt,
|
|
1686
1686
|
endedAt,
|
|
1687
|
-
durationMs,
|
|
1687
|
+
durationMs: durationMs2,
|
|
1688
1688
|
eventCount: parsedTrace.sourceEventCount,
|
|
1689
1689
|
filePath,
|
|
1690
1690
|
fileSize: stats.size,
|
|
@@ -1705,7 +1705,7 @@ function buildRunSummary(events) {
|
|
|
1705
1705
|
const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
|
|
1706
1706
|
const name = typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0;
|
|
1707
1707
|
const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
|
|
1708
|
-
const
|
|
1708
|
+
const durationMs2 = lastCompleted && isFiniteNumber(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
1709
1709
|
started && isFiniteNumber(started.startTime) ? started.startTime : void 0;
|
|
1710
1710
|
const steps = /* @__PURE__ */ new Map();
|
|
1711
1711
|
for (const e of events) {
|
|
@@ -1795,7 +1795,7 @@ function buildRunSummary(events) {
|
|
|
1795
1795
|
runId,
|
|
1796
1796
|
name,
|
|
1797
1797
|
status,
|
|
1798
|
-
durationMs,
|
|
1798
|
+
durationMs: durationMs2,
|
|
1799
1799
|
totalSteps,
|
|
1800
1800
|
llmSteps,
|
|
1801
1801
|
toolSteps,
|
|
@@ -2540,10 +2540,12 @@ async function searchTraces(metas, options) {
|
|
|
2540
2540
|
durationFilter = parseDurationFilter(options.duration);
|
|
2541
2541
|
}
|
|
2542
2542
|
const limit = options.limit ?? 50;
|
|
2543
|
+
const sessionId = options.session?.trim();
|
|
2543
2544
|
const hasContentFilter = Boolean(
|
|
2544
2545
|
options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter
|
|
2545
2546
|
);
|
|
2546
2547
|
const results = [];
|
|
2548
|
+
const sessionLabel = sessionId && sessionId !== "" ? sessionId : void 0;
|
|
2547
2549
|
if (!hasContentFilter) {
|
|
2548
2550
|
for (const m of filtered) {
|
|
2549
2551
|
results.push({
|
|
@@ -2552,9 +2554,10 @@ async function searchTraces(metas, options) {
|
|
|
2552
2554
|
runStatus: m.status,
|
|
2553
2555
|
timestamp: m.startedAt,
|
|
2554
2556
|
durationMs: m.durationMs,
|
|
2555
|
-
matchReason: "trace in directory",
|
|
2556
|
-
matchedFields: ["run"],
|
|
2557
|
-
filePath: m.filePath
|
|
2557
|
+
matchReason: sessionLabel ? `trace in session ${sessionLabel}` : "trace in directory",
|
|
2558
|
+
matchedFields: sessionLabel ? ["run", "session"] : ["run"],
|
|
2559
|
+
filePath: m.filePath,
|
|
2560
|
+
...sessionLabel ? { sessionId: sessionLabel } : {}
|
|
2558
2561
|
});
|
|
2559
2562
|
}
|
|
2560
2563
|
return results.slice(0, limit);
|
|
@@ -2693,6 +2696,478 @@ async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
|
|
|
2693
2696
|
}
|
|
2694
2697
|
return metas;
|
|
2695
2698
|
}
|
|
2699
|
+
|
|
2700
|
+
// packages/core/src/sessions/metadata.ts
|
|
2701
|
+
function isNonEmptyString3(value) {
|
|
2702
|
+
return typeof value === "string" && value.trim() !== "";
|
|
2703
|
+
}
|
|
2704
|
+
function finitePositiveInt(value) {
|
|
2705
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
2706
|
+
return void 0;
|
|
2707
|
+
}
|
|
2708
|
+
return Math.trunc(value);
|
|
2709
|
+
}
|
|
2710
|
+
function extractSessionWorkflowMetadata(record) {
|
|
2711
|
+
if (!record) return void 0;
|
|
2712
|
+
const out = {};
|
|
2713
|
+
let found = false;
|
|
2714
|
+
const assignString = (key, value) => {
|
|
2715
|
+
if (isNonEmptyString3(value)) {
|
|
2716
|
+
out[key] = value.trim();
|
|
2717
|
+
found = true;
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
assignString("sessionId", record.sessionId);
|
|
2721
|
+
assignString("conversationId", record.conversationId);
|
|
2722
|
+
assignString("groupId", record.groupId);
|
|
2723
|
+
assignString("parentGroupId", record.parentGroupId);
|
|
2724
|
+
assignString("retryOf", record.retryOf);
|
|
2725
|
+
assignString("retryReason", record.retryReason);
|
|
2726
|
+
assignString("handoffFrom", record.handoffFrom);
|
|
2727
|
+
assignString("handoffTo", record.handoffTo);
|
|
2728
|
+
assignString("subAgentId", record.subAgentId);
|
|
2729
|
+
assignString("subAgentName", record.subAgentName);
|
|
2730
|
+
assignString("jobId", record.jobId);
|
|
2731
|
+
assignString("queueName", record.queueName);
|
|
2732
|
+
assignString("workflowName", record.workflowName);
|
|
2733
|
+
assignString("workflowStep", record.workflowStep);
|
|
2734
|
+
assignString("toolCallId", record.toolCallId);
|
|
2735
|
+
assignString("mcpToolCallId", record.mcpToolCallId);
|
|
2736
|
+
assignString("linkedStepId", record.linkedStepId);
|
|
2737
|
+
assignString("correlationId", record.correlationId);
|
|
2738
|
+
assignString("requestId", record.requestId);
|
|
2739
|
+
assignString("decisionId", record.decisionId);
|
|
2740
|
+
const attempt = finitePositiveInt(record.attempt);
|
|
2741
|
+
if (attempt !== void 0) {
|
|
2742
|
+
out.attempt = attempt;
|
|
2743
|
+
found = true;
|
|
2744
|
+
}
|
|
2745
|
+
return found ? out : void 0;
|
|
2746
|
+
}
|
|
2747
|
+
function sessionKeyForRun(meta, options) {
|
|
2748
|
+
if (meta?.sessionId) return meta.sessionId;
|
|
2749
|
+
if (options?.correlateByGroupId && meta?.groupId) {
|
|
2750
|
+
return `group:${meta.groupId}`;
|
|
2751
|
+
}
|
|
2752
|
+
return void 0;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
// packages/core/src/sessions/load.ts
|
|
2756
|
+
async function enrichSessionRunRecord(meta) {
|
|
2757
|
+
let metadata;
|
|
2758
|
+
try {
|
|
2759
|
+
const events = await readTraceEventsFromFile(meta.filePath);
|
|
2760
|
+
for (const event of events) {
|
|
2761
|
+
if (event.event !== "run_started") continue;
|
|
2762
|
+
if (event.metadata && typeof event.metadata === "object") {
|
|
2763
|
+
metadata = event.metadata;
|
|
2764
|
+
}
|
|
2765
|
+
break;
|
|
2766
|
+
}
|
|
2767
|
+
} catch {
|
|
2768
|
+
}
|
|
2769
|
+
return {
|
|
2770
|
+
runId: meta.runId,
|
|
2771
|
+
name: meta.name,
|
|
2772
|
+
status: meta.status,
|
|
2773
|
+
startedAt: meta.startedAt,
|
|
2774
|
+
endedAt: meta.endedAt,
|
|
2775
|
+
durationMs: meta.durationMs,
|
|
2776
|
+
filePath: meta.filePath,
|
|
2777
|
+
metadata
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
async function loadSessionRunRecords(metas) {
|
|
2781
|
+
const out = [];
|
|
2782
|
+
for (const meta of metas) {
|
|
2783
|
+
out.push(await enrichSessionRunRecord(meta));
|
|
2784
|
+
}
|
|
2785
|
+
return out;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
// packages/core/src/sessions/scope.ts
|
|
2789
|
+
function filterMetasBySessionScope(metas, records, options) {
|
|
2790
|
+
const sessionId = options.sessionId?.trim();
|
|
2791
|
+
const groupId = options.groupId?.trim();
|
|
2792
|
+
const warnings = [];
|
|
2793
|
+
if (sessionId) {
|
|
2794
|
+
const index = buildSessionIndex(records, {
|
|
2795
|
+
correlateByGroupId: options.correlateByGroupId === true
|
|
2796
|
+
});
|
|
2797
|
+
warnings.push(...index.warnings);
|
|
2798
|
+
const session = index.sessions.find((item) => item.sessionId === sessionId);
|
|
2799
|
+
if (!session) {
|
|
2800
|
+
return {
|
|
2801
|
+
metas: [],
|
|
2802
|
+
scopeLabel: sessionId,
|
|
2803
|
+
scopeKind: "session",
|
|
2804
|
+
runIds: [],
|
|
2805
|
+
warnings,
|
|
2806
|
+
notFound: true
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
const runIdSet = new Set(session.runIds);
|
|
2810
|
+
const filtered = metas.filter((meta) => runIdSet.has(meta.runId));
|
|
2811
|
+
return {
|
|
2812
|
+
metas: filtered,
|
|
2813
|
+
scopeLabel: sessionId,
|
|
2814
|
+
scopeKind: "session",
|
|
2815
|
+
runIds: session.runIds,
|
|
2816
|
+
warnings,
|
|
2817
|
+
notFound: false
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
if (groupId) {
|
|
2821
|
+
const runIds = records.filter((run) => extractSessionWorkflowMetadata(run.metadata)?.groupId === groupId).map((run) => run.runId).sort();
|
|
2822
|
+
if (runIds.length === 0) {
|
|
2823
|
+
return {
|
|
2824
|
+
metas: [],
|
|
2825
|
+
scopeLabel: groupId,
|
|
2826
|
+
scopeKind: "group",
|
|
2827
|
+
runIds: [],
|
|
2828
|
+
warnings,
|
|
2829
|
+
notFound: true
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
const runIdSet = new Set(runIds);
|
|
2833
|
+
return {
|
|
2834
|
+
metas: metas.filter((meta) => runIdSet.has(meta.runId)),
|
|
2835
|
+
scopeLabel: groupId,
|
|
2836
|
+
scopeKind: "group",
|
|
2837
|
+
runIds,
|
|
2838
|
+
warnings,
|
|
2839
|
+
notFound: false
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
return {
|
|
2843
|
+
metas: [...metas],
|
|
2844
|
+
scopeLabel: "",
|
|
2845
|
+
scopeKind: "session",
|
|
2846
|
+
runIds: [],
|
|
2847
|
+
warnings,
|
|
2848
|
+
notFound: false
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
// packages/core/src/sessions/checks.ts
|
|
2853
|
+
function emptySummary() {
|
|
2854
|
+
return { passed: 0, failed: 0, warnings: 0, errors: 0 };
|
|
2855
|
+
}
|
|
2856
|
+
function mergeSummary(target, source) {
|
|
2857
|
+
return {
|
|
2858
|
+
passed: target.passed + source.passed,
|
|
2859
|
+
failed: target.failed + source.failed,
|
|
2860
|
+
warnings: target.warnings + source.warnings,
|
|
2861
|
+
errors: target.errors + source.errors
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
function sessionDiagnostic(code, message) {
|
|
2865
|
+
return { code, message, severity: "error" };
|
|
2866
|
+
}
|
|
2867
|
+
function aggregateSessionCheckResults(perRun, scope) {
|
|
2868
|
+
if (scope.notFound) {
|
|
2869
|
+
return {
|
|
2870
|
+
ok: false,
|
|
2871
|
+
status: "error",
|
|
2872
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
2873
|
+
scopeKind: scope.scopeKind,
|
|
2874
|
+
scopeLabel: scope.scopeLabel,
|
|
2875
|
+
runIds: [],
|
|
2876
|
+
runResults: [],
|
|
2877
|
+
summary: { ...emptySummary(), errors: 1 },
|
|
2878
|
+
findings: [],
|
|
2879
|
+
diagnostics: [
|
|
2880
|
+
sessionDiagnostic(
|
|
2881
|
+
"AI_CHECK_INVALID_ARGUMENTS",
|
|
2882
|
+
`${scope.scopeKind} not found: ${scope.scopeLabel}`
|
|
2883
|
+
)
|
|
2884
|
+
],
|
|
2885
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
if (scope.empty || perRun.length === 0) {
|
|
2889
|
+
return {
|
|
2890
|
+
ok: false,
|
|
2891
|
+
status: "error",
|
|
2892
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
2893
|
+
scopeKind: scope.scopeKind,
|
|
2894
|
+
scopeLabel: scope.scopeLabel,
|
|
2895
|
+
runIds: scope.runIds,
|
|
2896
|
+
runResults: [],
|
|
2897
|
+
summary: { ...emptySummary(), errors: 1 },
|
|
2898
|
+
findings: [],
|
|
2899
|
+
diagnostics: [
|
|
2900
|
+
sessionDiagnostic(
|
|
2901
|
+
"AI_CHECK_TRACE_UNREADABLE",
|
|
2902
|
+
`No readable traces in ${scope.scopeKind}: ${scope.scopeLabel}`
|
|
2903
|
+
)
|
|
2904
|
+
],
|
|
2905
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
let summary = emptySummary();
|
|
2909
|
+
const findings = [];
|
|
2910
|
+
const diagnostics = [];
|
|
2911
|
+
const runResults = [];
|
|
2912
|
+
for (const result of perRun) {
|
|
2913
|
+
summary = mergeSummary(summary, result.summary);
|
|
2914
|
+
findings.push(...result.findings);
|
|
2915
|
+
diagnostics.push(...result.diagnostics);
|
|
2916
|
+
if (result.runId) {
|
|
2917
|
+
runResults.push({ runId: result.runId, status: result.status });
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
runResults.sort((a, b) => a.runId.localeCompare(b.runId));
|
|
2921
|
+
findings.sort((a, b) => {
|
|
2922
|
+
const runCmp = (a.evidence[0]?.runId ?? "").localeCompare(
|
|
2923
|
+
b.evidence[0]?.runId ?? ""
|
|
2924
|
+
);
|
|
2925
|
+
if (runCmp !== 0) return runCmp;
|
|
2926
|
+
return a.ruleId.localeCompare(b.ruleId);
|
|
2927
|
+
});
|
|
2928
|
+
const hasErrors = diagnostics.some((item) => item.severity === "error");
|
|
2929
|
+
const status = hasErrors ? "error" : summary.failed > 0 ? "fail" : "pass";
|
|
2930
|
+
return {
|
|
2931
|
+
ok: status === "pass",
|
|
2932
|
+
status,
|
|
2933
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
2934
|
+
scopeKind: scope.scopeKind,
|
|
2935
|
+
scopeLabel: scope.scopeLabel,
|
|
2936
|
+
runIds: scope.runIds,
|
|
2937
|
+
runResults,
|
|
2938
|
+
summary,
|
|
2939
|
+
findings,
|
|
2940
|
+
diagnostics,
|
|
2941
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
// packages/core/src/sessions/index.ts
|
|
2946
|
+
function compareRuns(a, b) {
|
|
2947
|
+
const aStart = a.startedAt ?? 0;
|
|
2948
|
+
const bStart = b.startedAt ?? 0;
|
|
2949
|
+
if (aStart !== bStart) return aStart - bStart;
|
|
2950
|
+
return a.runId.localeCompare(b.runId);
|
|
2951
|
+
}
|
|
2952
|
+
function buildGroups(runIds, metaByRunId) {
|
|
2953
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
2954
|
+
for (const runId of runIds) {
|
|
2955
|
+
const groupId = metaByRunId.get(runId)?.groupId;
|
|
2956
|
+
if (!groupId) continue;
|
|
2957
|
+
const existing = byGroup.get(groupId);
|
|
2958
|
+
if (existing) {
|
|
2959
|
+
existing.runIds.push(runId);
|
|
2960
|
+
} else {
|
|
2961
|
+
byGroup.set(groupId, {
|
|
2962
|
+
groupId,
|
|
2963
|
+
parentGroupId: metaByRunId.get(runId)?.parentGroupId,
|
|
2964
|
+
runIds: [runId]
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
return [...byGroup.values()].map((group) => ({
|
|
2969
|
+
...group,
|
|
2970
|
+
runIds: [...group.runIds].sort((a, b) => a.localeCompare(b))
|
|
2971
|
+
}));
|
|
2972
|
+
}
|
|
2973
|
+
function buildHandoffs(runIds, metaByRunId, warnings, sessionId) {
|
|
2974
|
+
const edges = [];
|
|
2975
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2976
|
+
const pushEdge = (edge) => {
|
|
2977
|
+
const key = `${edge.from}->${edge.to}:${edge.confidence}`;
|
|
2978
|
+
if (seen.has(key)) return;
|
|
2979
|
+
seen.add(key);
|
|
2980
|
+
edges.push(edge);
|
|
2981
|
+
};
|
|
2982
|
+
for (const runId of runIds) {
|
|
2983
|
+
const meta = metaByRunId.get(runId);
|
|
2984
|
+
if (!meta) continue;
|
|
2985
|
+
if (meta.handoffFrom && meta.handoffTo) {
|
|
2986
|
+
pushEdge({
|
|
2987
|
+
from: meta.handoffFrom,
|
|
2988
|
+
to: meta.handoffTo,
|
|
2989
|
+
source: "manual",
|
|
2990
|
+
confidence: "explicit"
|
|
2991
|
+
});
|
|
2992
|
+
continue;
|
|
2993
|
+
}
|
|
2994
|
+
if (meta.handoffFrom) {
|
|
2995
|
+
pushEdge({
|
|
2996
|
+
from: meta.handoffFrom,
|
|
2997
|
+
to: runId,
|
|
2998
|
+
source: "manual",
|
|
2999
|
+
confidence: "explicit"
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
if (meta.handoffTo) {
|
|
3003
|
+
pushEdge({
|
|
3004
|
+
from: runId,
|
|
3005
|
+
to: meta.handoffTo,
|
|
3006
|
+
source: "manual",
|
|
3007
|
+
confidence: "explicit"
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
if (meta.subAgentId && meta.parentGroupId && !meta.handoffFrom && !meta.handoffTo) {
|
|
3011
|
+
pushEdge({
|
|
3012
|
+
from: meta.parentGroupId,
|
|
3013
|
+
to: meta.subAgentId,
|
|
3014
|
+
source: "inferred",
|
|
3015
|
+
confidence: "correlated"
|
|
3016
|
+
});
|
|
3017
|
+
warnings.push({
|
|
3018
|
+
code: "ambiguous-handoff-endpoints",
|
|
3019
|
+
message: "Handoff inferred from parentGroupId and subAgentId without explicit handoffFrom/handoffTo.",
|
|
3020
|
+
runId,
|
|
3021
|
+
sessionId
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return edges.sort((a, b) => {
|
|
3026
|
+
const from = a.from.localeCompare(b.from);
|
|
3027
|
+
if (from !== 0) return from;
|
|
3028
|
+
return a.to.localeCompare(b.to);
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
function buildRetries(runIds, metaByRunId, warnings, sessionId) {
|
|
3032
|
+
const retries = [];
|
|
3033
|
+
for (const runId of runIds) {
|
|
3034
|
+
const meta = metaByRunId.get(runId);
|
|
3035
|
+
if (!meta) continue;
|
|
3036
|
+
if (meta.retryOf) {
|
|
3037
|
+
retries.push({
|
|
3038
|
+
runId,
|
|
3039
|
+
retryOf: meta.retryOf,
|
|
3040
|
+
attempt: meta.attempt,
|
|
3041
|
+
source: "manual",
|
|
3042
|
+
confidence: "explicit"
|
|
3043
|
+
});
|
|
3044
|
+
continue;
|
|
3045
|
+
}
|
|
3046
|
+
if (meta.attempt !== void 0 && meta.attempt > 1) {
|
|
3047
|
+
retries.push({
|
|
3048
|
+
runId,
|
|
3049
|
+
attempt: meta.attempt,
|
|
3050
|
+
source: "inferred",
|
|
3051
|
+
confidence: "correlated"
|
|
3052
|
+
});
|
|
3053
|
+
warnings.push({
|
|
3054
|
+
code: "ambiguous-retry-link",
|
|
3055
|
+
message: "attempt > 1 without retryOf; retry link is correlated only.",
|
|
3056
|
+
runId,
|
|
3057
|
+
sessionId
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
return retries.sort((a, b) => a.runId.localeCompare(b.runId));
|
|
3062
|
+
}
|
|
3063
|
+
function buildCriticalPath(runs, handoffs) {
|
|
3064
|
+
const runById = new Map(runs.map((run) => [run.runId, run]));
|
|
3065
|
+
const explicitTargets = new Set(
|
|
3066
|
+
handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.to)
|
|
3067
|
+
);
|
|
3068
|
+
const explicitSources = new Set(
|
|
3069
|
+
handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
|
|
3070
|
+
);
|
|
3071
|
+
const ordered = [...runs].sort(compareRuns);
|
|
3072
|
+
const path15 = [];
|
|
3073
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3074
|
+
const pushRun = (run, confidence, source) => {
|
|
3075
|
+
if (visited.has(run.runId)) return;
|
|
3076
|
+
visited.add(run.runId);
|
|
3077
|
+
path15.push({
|
|
3078
|
+
runId: run.runId,
|
|
3079
|
+
name: run.name,
|
|
3080
|
+
startedAt: run.startedAt,
|
|
3081
|
+
durationMs: run.durationMs,
|
|
3082
|
+
confidence,
|
|
3083
|
+
source
|
|
3084
|
+
});
|
|
3085
|
+
};
|
|
3086
|
+
for (const edge of handoffs) {
|
|
3087
|
+
if (edge.confidence !== "explicit") continue;
|
|
3088
|
+
const fromRun = [...runById.values()].find(
|
|
3089
|
+
(run) => run.runId === edge.from || metaRunIdMatches(run, edge.from, runById)
|
|
3090
|
+
);
|
|
3091
|
+
const toRun = [...runById.values()].find(
|
|
3092
|
+
(run) => run.runId === edge.to || metaRunIdMatches(run, edge.to, runById)
|
|
3093
|
+
);
|
|
3094
|
+
if (fromRun) pushRun(fromRun, "explicit", "manual");
|
|
3095
|
+
if (toRun) pushRun(toRun, "explicit", "manual");
|
|
3096
|
+
}
|
|
3097
|
+
for (const run of ordered) {
|
|
3098
|
+
if (visited.has(run.runId)) continue;
|
|
3099
|
+
const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
|
|
3100
|
+
pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
|
|
3101
|
+
}
|
|
3102
|
+
return path15;
|
|
3103
|
+
}
|
|
3104
|
+
function metaRunIdMatches(run, token, runById) {
|
|
3105
|
+
const meta = extractSessionWorkflowMetadata(run.metadata);
|
|
3106
|
+
return meta?.subAgentId === token || meta?.groupId === token || runById.has(token);
|
|
3107
|
+
}
|
|
3108
|
+
function buildSessionIndex(inputRuns, options = {}) {
|
|
3109
|
+
const warnings = [];
|
|
3110
|
+
const runs = [...inputRuns].sort(compareRuns);
|
|
3111
|
+
const metaByRunId = /* @__PURE__ */ new Map();
|
|
3112
|
+
for (const run of runs) {
|
|
3113
|
+
metaByRunId.set(run.runId, extractSessionWorkflowMetadata(run.metadata));
|
|
3114
|
+
}
|
|
3115
|
+
const sessionsByKey = /* @__PURE__ */ new Map();
|
|
3116
|
+
const unscopedRunIds = [];
|
|
3117
|
+
for (const run of runs) {
|
|
3118
|
+
const meta = metaByRunId.get(run.runId);
|
|
3119
|
+
const key = sessionKeyForRun(meta, {
|
|
3120
|
+
correlateByGroupId: options.correlateByGroupId === true
|
|
3121
|
+
});
|
|
3122
|
+
if (!key) {
|
|
3123
|
+
unscopedRunIds.push(run.runId);
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
const bucket = sessionsByKey.get(key) ?? [];
|
|
3127
|
+
bucket.push(run);
|
|
3128
|
+
sessionsByKey.set(key, bucket);
|
|
3129
|
+
}
|
|
3130
|
+
const sessions = [...sessionsByKey.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([sessionId, sessionRuns]) => {
|
|
3131
|
+
const runIds = sessionRuns.map((run) => run.runId).sort();
|
|
3132
|
+
const handoffs = buildHandoffs(runIds, metaByRunId, warnings, sessionId);
|
|
3133
|
+
const retries = buildRetries(runIds, metaByRunId, warnings, sessionId);
|
|
3134
|
+
const groups = buildGroups(runIds, metaByRunId);
|
|
3135
|
+
const criticalPath = buildCriticalPath(sessionRuns, handoffs);
|
|
3136
|
+
const confidences = new Set(handoffs.map((edge) => edge.confidence));
|
|
3137
|
+
if (confidences.has("explicit") && confidences.has("correlated")) {
|
|
3138
|
+
warnings.push({
|
|
3139
|
+
code: "mixed-confidence-group",
|
|
3140
|
+
message: "Session aggregates explicit and correlated handoff edges.",
|
|
3141
|
+
sessionId
|
|
3142
|
+
});
|
|
3143
|
+
}
|
|
3144
|
+
return {
|
|
3145
|
+
sessionId,
|
|
3146
|
+
runIds,
|
|
3147
|
+
groups,
|
|
3148
|
+
handoffs,
|
|
3149
|
+
retries,
|
|
3150
|
+
criticalPath
|
|
3151
|
+
};
|
|
3152
|
+
});
|
|
3153
|
+
if (sessions.length === 0 && runs.length > 0) {
|
|
3154
|
+
warnings.push({
|
|
3155
|
+
code: "missing-session-id",
|
|
3156
|
+
message: "No sessionId (or correlated groupId) found on input runs."
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
3159
|
+
warnings.sort((a, b) => {
|
|
3160
|
+
const code = a.code.localeCompare(b.code);
|
|
3161
|
+
if (code !== 0) return code;
|
|
3162
|
+
return (a.runId ?? "").localeCompare(b.runId ?? "");
|
|
3163
|
+
});
|
|
3164
|
+
return {
|
|
3165
|
+
runs,
|
|
3166
|
+
sessions,
|
|
3167
|
+
unscopedRunIds: unscopedRunIds.sort(),
|
|
3168
|
+
warnings
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
2696
3171
|
var KNOWN_EVENTS = /* @__PURE__ */ new Set([
|
|
2697
3172
|
"run_started",
|
|
2698
3173
|
"run_completed",
|
|
@@ -2751,10 +3226,10 @@ function statusIcon(status) {
|
|
|
2751
3226
|
if (status === "running") return "\u23F3";
|
|
2752
3227
|
return "?";
|
|
2753
3228
|
}
|
|
2754
|
-
function durationCell(status,
|
|
3229
|
+
function durationCell(status, durationMs2) {
|
|
2755
3230
|
if (status === "running" || status === "unknown") return "-";
|
|
2756
|
-
if (
|
|
2757
|
-
return formatDuration2(
|
|
3231
|
+
if (durationMs2 !== void 0 && Number.isFinite(durationMs2)) {
|
|
3232
|
+
return formatDuration2(durationMs2);
|
|
2758
3233
|
}
|
|
2759
3234
|
return "-";
|
|
2760
3235
|
}
|
|
@@ -3411,7 +3886,7 @@ var TreeBuilder = class {
|
|
|
3411
3886
|
const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
|
|
3412
3887
|
const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
|
|
3413
3888
|
const status = computeRunStatus(sorted);
|
|
3414
|
-
const
|
|
3889
|
+
const durationMs2 = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
|
|
3415
3890
|
const name = sorted.find((e) => e.kind === "RUN")?.name;
|
|
3416
3891
|
out.push({
|
|
3417
3892
|
runId,
|
|
@@ -3419,7 +3894,7 @@ var TreeBuilder = class {
|
|
|
3419
3894
|
status,
|
|
3420
3895
|
startedAt,
|
|
3421
3896
|
endedAt: status === "running" ? void 0 : endedAt,
|
|
3422
|
-
durationMs,
|
|
3897
|
+
durationMs: durationMs2,
|
|
3423
3898
|
children: roots,
|
|
3424
3899
|
metadata: {
|
|
3425
3900
|
totalEvents: sorted.length,
|
|
@@ -3714,13 +4189,13 @@ function persistedEventsForParsedTrace(parsed) {
|
|
|
3714
4189
|
function isRecord8(value) {
|
|
3715
4190
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3716
4191
|
}
|
|
3717
|
-
function
|
|
4192
|
+
function isNonEmptyString4(value) {
|
|
3718
4193
|
return typeof value === "string" && value.trim() !== "";
|
|
3719
4194
|
}
|
|
3720
4195
|
function readStringField(record, keys) {
|
|
3721
4196
|
for (const key of keys) {
|
|
3722
4197
|
const value = record[key];
|
|
3723
|
-
if (
|
|
4198
|
+
if (isNonEmptyString4(value)) return value;
|
|
3724
4199
|
}
|
|
3725
4200
|
return void 0;
|
|
3726
4201
|
}
|
|
@@ -3854,7 +4329,7 @@ function parseUnixNanoToIso(value) {
|
|
|
3854
4329
|
return void 0;
|
|
3855
4330
|
}
|
|
3856
4331
|
function parseIsoTime(value) {
|
|
3857
|
-
if (!
|
|
4332
|
+
if (!isNonEmptyString4(value)) return void 0;
|
|
3858
4333
|
const ms = Date.parse(value);
|
|
3859
4334
|
if (!Number.isFinite(ms)) return void 0;
|
|
3860
4335
|
return new Date(ms).toISOString();
|
|
@@ -4114,9 +4589,9 @@ function mapOpenInferenceSpan(span, index, version2) {
|
|
|
4114
4589
|
if (endedAt !== void 0) {
|
|
4115
4590
|
event.endedAt = endedAt;
|
|
4116
4591
|
}
|
|
4117
|
-
const
|
|
4118
|
-
if (
|
|
4119
|
-
event.durationMs =
|
|
4592
|
+
const durationMs2 = durationBetweenIso(startedAt, endedAt);
|
|
4593
|
+
if (durationMs2 !== void 0) {
|
|
4594
|
+
event.durationMs = durationMs2;
|
|
4120
4595
|
}
|
|
4121
4596
|
if (tokenUsage !== void 0) {
|
|
4122
4597
|
event.tokenUsage = tokenUsage;
|
|
@@ -4701,9 +5176,9 @@ function mapOtlpSpan(context) {
|
|
|
4701
5176
|
if (endedAt !== void 0) {
|
|
4702
5177
|
event.endedAt = endedAt;
|
|
4703
5178
|
}
|
|
4704
|
-
const
|
|
4705
|
-
if (
|
|
4706
|
-
event.durationMs =
|
|
5179
|
+
const durationMs2 = durationBetweenIso(startedAt, endedAt);
|
|
5180
|
+
if (durationMs2 !== void 0) {
|
|
5181
|
+
event.durationMs = durationMs2;
|
|
4707
5182
|
}
|
|
4708
5183
|
if (tokenUsage !== void 0) {
|
|
4709
5184
|
event.tokenUsage = tokenUsage;
|
|
@@ -5708,13 +6183,13 @@ var EventNormalizer = class {
|
|
|
5708
6183
|
const timestampMissing = parsedTs === void 0;
|
|
5709
6184
|
const parentIdKey = cfg.parentIdKey;
|
|
5710
6185
|
const parentId = parentIdKey ? safeString(raw[parentIdKey]) : void 0;
|
|
5711
|
-
let
|
|
6186
|
+
let durationMs2;
|
|
5712
6187
|
const durationKey = cfg.durationKey;
|
|
5713
6188
|
if (durationKey) {
|
|
5714
6189
|
const v = raw[durationKey];
|
|
5715
|
-
if (isFiniteNumber2(v))
|
|
6190
|
+
if (isFiniteNumber2(v)) durationMs2 = v;
|
|
5716
6191
|
} else if (isFiniteNumber2(raw.durationMs)) {
|
|
5717
|
-
|
|
6192
|
+
durationMs2 = raw.durationMs;
|
|
5718
6193
|
}
|
|
5719
6194
|
let status;
|
|
5720
6195
|
const statusKey = cfg.statusKey;
|
|
@@ -5761,7 +6236,7 @@ var EventNormalizer = class {
|
|
|
5761
6236
|
kind,
|
|
5762
6237
|
timestamp,
|
|
5763
6238
|
...status ? { status } : {},
|
|
5764
|
-
...
|
|
6239
|
+
...durationMs2 !== void 0 ? { durationMs: durationMs2 } : {},
|
|
5765
6240
|
...Object.keys(attributes).length > 0 ? { attributes } : {},
|
|
5766
6241
|
confidence,
|
|
5767
6242
|
source: {
|
|
@@ -7349,7 +7824,7 @@ function manualTraceEventsToRunTree(events) {
|
|
|
7349
7824
|
}
|
|
7350
7825
|
const startedAt = started.startTime;
|
|
7351
7826
|
const endedAt = lastCompleted !== void 0 && runStatus !== "running" ? lastCompleted.endTime : void 0;
|
|
7352
|
-
const
|
|
7827
|
+
const durationMs2 = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
7353
7828
|
const steps = /* @__PURE__ */ new Map();
|
|
7354
7829
|
for (const e of events) {
|
|
7355
7830
|
if (e.event !== "step_started") continue;
|
|
@@ -7438,7 +7913,7 @@ function manualTraceEventsToRunTree(events) {
|
|
|
7438
7913
|
status: runStatus,
|
|
7439
7914
|
startedAt,
|
|
7440
7915
|
endedAt,
|
|
7441
|
-
durationMs,
|
|
7916
|
+
durationMs: durationMs2,
|
|
7442
7917
|
children: roots,
|
|
7443
7918
|
metadata: {
|
|
7444
7919
|
totalEvents: inspectNodes.size,
|
|
@@ -7751,7 +8226,7 @@ function manualTraceEventsToComparableRun(events) {
|
|
|
7751
8226
|
let runStatus;
|
|
7752
8227
|
if (lastCompleted === void 0) runStatus = "running";
|
|
7753
8228
|
else runStatus = lastCompleted.status;
|
|
7754
|
-
const
|
|
8229
|
+
const durationMs2 = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
7755
8230
|
const steps = /* @__PURE__ */ new Map();
|
|
7756
8231
|
let order = 0;
|
|
7757
8232
|
for (const e of events) {
|
|
@@ -7822,7 +8297,7 @@ function manualTraceEventsToComparableRun(events) {
|
|
|
7822
8297
|
runId,
|
|
7823
8298
|
name: rs.name,
|
|
7824
8299
|
status: runStatus,
|
|
7825
|
-
durationMs,
|
|
8300
|
+
durationMs: durationMs2,
|
|
7826
8301
|
steps: roots
|
|
7827
8302
|
};
|
|
7828
8303
|
}
|
|
@@ -8408,11 +8883,29 @@ async function searchCommand(options = {}) {
|
|
|
8408
8883
|
parseDurationFilter(options.duration);
|
|
8409
8884
|
}
|
|
8410
8885
|
const files = await td.list();
|
|
8411
|
-
|
|
8886
|
+
let metas = await loadTraceMetadataList(
|
|
8412
8887
|
traceDir,
|
|
8413
8888
|
files,
|
|
8414
8889
|
(f) => td.getPath(f)
|
|
8415
8890
|
);
|
|
8891
|
+
const sessionId = options.session?.trim();
|
|
8892
|
+
if (sessionId) {
|
|
8893
|
+
const records = await loadSessionRunRecords(metas);
|
|
8894
|
+
const scoped = filterMetasBySessionScope(metas, records, {
|
|
8895
|
+
sessionId,
|
|
8896
|
+
correlateByGroupId: options.correlateGroup === true
|
|
8897
|
+
});
|
|
8898
|
+
if (scoped.notFound) {
|
|
8899
|
+
if (options.json) {
|
|
8900
|
+
console.log(JSON.stringify([], null, 2));
|
|
8901
|
+
} else {
|
|
8902
|
+
console.log(`Session not found: ${sessionId}`);
|
|
8903
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
8904
|
+
}
|
|
8905
|
+
return;
|
|
8906
|
+
}
|
|
8907
|
+
metas = scoped.metas;
|
|
8908
|
+
}
|
|
8416
8909
|
const status = options.status === "success" || options.status === "error" || options.status === "running" || options.status === "unknown" ? options.status : void 0;
|
|
8417
8910
|
const results = await searchTraces(metas, {
|
|
8418
8911
|
traceDir,
|
|
@@ -8423,7 +8916,9 @@ async function searchCommand(options = {}) {
|
|
|
8423
8916
|
name: options.name,
|
|
8424
8917
|
tool: options.tool,
|
|
8425
8918
|
duration: options.duration,
|
|
8426
|
-
limit: parseLimit2(options.limit)
|
|
8919
|
+
limit: parseLimit2(options.limit),
|
|
8920
|
+
...sessionId ? { session: sessionId } : {},
|
|
8921
|
+
...options.correlateGroup ? { correlateGroup: true } : {}
|
|
8427
8922
|
});
|
|
8428
8923
|
if (options.json) {
|
|
8429
8924
|
console.log(JSON.stringify(results, null, 2));
|
|
@@ -8451,6 +8946,184 @@ async function searchCommand(options = {}) {
|
|
|
8451
8946
|
}
|
|
8452
8947
|
}
|
|
8453
8948
|
|
|
8949
|
+
// packages/cli/src/sessions.ts
|
|
8950
|
+
async function loadSessionIndex(traceDir, correlateGroup) {
|
|
8951
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
8952
|
+
const files = await td.list();
|
|
8953
|
+
const metas = await loadTraceMetadataList(
|
|
8954
|
+
traceDir,
|
|
8955
|
+
files,
|
|
8956
|
+
(fileName) => td.getPath(fileName)
|
|
8957
|
+
);
|
|
8958
|
+
const runs = await loadSessionRunRecords(metas);
|
|
8959
|
+
return buildSessionIndex(runs, { correlateByGroupId: correlateGroup === true });
|
|
8960
|
+
}
|
|
8961
|
+
function findSession(index, sessionId) {
|
|
8962
|
+
return index.sessions.find((session) => session.sessionId === sessionId);
|
|
8963
|
+
}
|
|
8964
|
+
function renderSessionsHuman(index, traceDir) {
|
|
8965
|
+
if (index.sessions.length === 0) {
|
|
8966
|
+
console.log("No sessions found");
|
|
8967
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
8968
|
+
if (index.unscopedRunIds.length > 0) {
|
|
8969
|
+
console.log(
|
|
8970
|
+
`Unscoped runs (no sessionId): ${index.unscopedRunIds.join(", ")}`
|
|
8971
|
+
);
|
|
8972
|
+
}
|
|
8973
|
+
return;
|
|
8974
|
+
}
|
|
8975
|
+
console.log("Sessions:");
|
|
8976
|
+
for (const session of index.sessions) {
|
|
8977
|
+
const firstRun = index.runs.find(
|
|
8978
|
+
(run) => session.runIds.includes(run.runId)
|
|
8979
|
+
);
|
|
8980
|
+
const workflowName = firstRun?.metadata && typeof firstRun.metadata.workflowName === "string" ? firstRun.metadata.workflowName : void 0;
|
|
8981
|
+
const suffix = workflowName ? ` workflow=${workflowName}` : "";
|
|
8982
|
+
console.log(
|
|
8983
|
+
` ${session.sessionId} (${session.runIds.length} run${session.runIds.length === 1 ? "" : "s"})${suffix}`
|
|
8984
|
+
);
|
|
8985
|
+
}
|
|
8986
|
+
if (index.unscopedRunIds.length > 0) {
|
|
8987
|
+
console.log("");
|
|
8988
|
+
console.log(
|
|
8989
|
+
`Unscoped runs (no sessionId): ${index.unscopedRunIds.join(", ")}`
|
|
8990
|
+
);
|
|
8991
|
+
}
|
|
8992
|
+
}
|
|
8993
|
+
function renderSessionHuman(session, index, options) {
|
|
8994
|
+
console.log(`Session: ${session.sessionId}`);
|
|
8995
|
+
console.log(`Runs: ${session.runIds.join(", ")}`);
|
|
8996
|
+
if (session.handoffs.length > 0) {
|
|
8997
|
+
console.log("");
|
|
8998
|
+
console.log("Handoffs:");
|
|
8999
|
+
for (const edge of session.handoffs) {
|
|
9000
|
+
console.log(
|
|
9001
|
+
` ${edge.from} -> ${edge.to} (${edge.confidence}, ${edge.source})`
|
|
9002
|
+
);
|
|
9003
|
+
}
|
|
9004
|
+
}
|
|
9005
|
+
if (session.retries.length > 0) {
|
|
9006
|
+
console.log("");
|
|
9007
|
+
console.log("Retries:");
|
|
9008
|
+
for (const retry of session.retries) {
|
|
9009
|
+
const attempt = retry.attempt !== void 0 ? ` attempt=${retry.attempt}` : "";
|
|
9010
|
+
const of = retry.retryOf !== void 0 ? ` retryOf=${retry.retryOf}` : "";
|
|
9011
|
+
console.log(
|
|
9012
|
+
` ${retry.runId}${attempt}${of} (${retry.confidence})`
|
|
9013
|
+
);
|
|
9014
|
+
}
|
|
9015
|
+
}
|
|
9016
|
+
if (options.criticalPath && session.criticalPath.length > 0) {
|
|
9017
|
+
console.log("");
|
|
9018
|
+
console.log("Critical path:");
|
|
9019
|
+
for (const step of session.criticalPath) {
|
|
9020
|
+
const duration = step.durationMs !== void 0 ? ` ${step.durationMs}ms` : "";
|
|
9021
|
+
console.log(
|
|
9022
|
+
` ${step.runId}${step.name ? ` (${step.name})` : ""}${duration} [${step.confidence}]`
|
|
9023
|
+
);
|
|
9024
|
+
}
|
|
9025
|
+
}
|
|
9026
|
+
if (options.diagnostics && index.warnings.length > 0) {
|
|
9027
|
+
const scoped = index.warnings.filter(
|
|
9028
|
+
(warning) => warning.sessionId === session.sessionId || !warning.sessionId
|
|
9029
|
+
);
|
|
9030
|
+
if (scoped.length > 0) {
|
|
9031
|
+
console.log("");
|
|
9032
|
+
console.log("Diagnostics:");
|
|
9033
|
+
for (const warning of scoped) {
|
|
9034
|
+
const run = warning.runId ? ` run=${warning.runId}` : "";
|
|
9035
|
+
console.log(` [${warning.code}]${run} ${warning.message}`);
|
|
9036
|
+
}
|
|
9037
|
+
}
|
|
9038
|
+
}
|
|
9039
|
+
}
|
|
9040
|
+
async function sessionsCommand(options = {}) {
|
|
9041
|
+
try {
|
|
9042
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
9043
|
+
const index = await loadSessionIndex(traceDir, options.correlateGroup);
|
|
9044
|
+
if (options.json) {
|
|
9045
|
+
console.log(
|
|
9046
|
+
JSON.stringify(
|
|
9047
|
+
{
|
|
9048
|
+
traceDir,
|
|
9049
|
+
sessions: index.sessions,
|
|
9050
|
+
unscopedRunIds: index.unscopedRunIds,
|
|
9051
|
+
warnings: index.warnings
|
|
9052
|
+
},
|
|
9053
|
+
null,
|
|
9054
|
+
2
|
|
9055
|
+
)
|
|
9056
|
+
);
|
|
9057
|
+
return;
|
|
9058
|
+
}
|
|
9059
|
+
renderSessionsHuman(index, traceDir);
|
|
9060
|
+
} catch (e) {
|
|
9061
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9062
|
+
console.error(`[AgentInspect] sessions failed: ${msg}`);
|
|
9063
|
+
process.exitCode = 1;
|
|
9064
|
+
}
|
|
9065
|
+
}
|
|
9066
|
+
async function sessionCommand(sessionId, options = {}) {
|
|
9067
|
+
const id = typeof sessionId === "string" && sessionId.trim() !== "" ? sessionId.trim() : "";
|
|
9068
|
+
if (id === "") {
|
|
9069
|
+
console.error("Session id is required");
|
|
9070
|
+
process.exitCode = 1;
|
|
9071
|
+
return;
|
|
9072
|
+
}
|
|
9073
|
+
try {
|
|
9074
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
9075
|
+
const index = await loadSessionIndex(traceDir);
|
|
9076
|
+
const session = findSession(index, id);
|
|
9077
|
+
if (!session) {
|
|
9078
|
+
console.log(`Session not found: ${id}`);
|
|
9079
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
9080
|
+
process.exitCode = 1;
|
|
9081
|
+
return;
|
|
9082
|
+
}
|
|
9083
|
+
if (options.json) {
|
|
9084
|
+
const timelines = {};
|
|
9085
|
+
if (options.timeline) {
|
|
9086
|
+
for (const runId of session.runIds) {
|
|
9087
|
+
const result = await readRunTraceEvents(runId, traceDir);
|
|
9088
|
+
if (result && result.events.length > 0) {
|
|
9089
|
+
timelines[runId] = buildRunTimeline(result.events);
|
|
9090
|
+
}
|
|
9091
|
+
}
|
|
9092
|
+
}
|
|
9093
|
+
console.log(
|
|
9094
|
+
JSON.stringify(
|
|
9095
|
+
{
|
|
9096
|
+
traceDir,
|
|
9097
|
+
session,
|
|
9098
|
+
warnings: index.warnings.filter(
|
|
9099
|
+
(warning) => warning.sessionId === id || warning.sessionId === void 0
|
|
9100
|
+
),
|
|
9101
|
+
...options.timeline ? { timelines } : {}
|
|
9102
|
+
},
|
|
9103
|
+
null,
|
|
9104
|
+
2
|
|
9105
|
+
)
|
|
9106
|
+
);
|
|
9107
|
+
return;
|
|
9108
|
+
}
|
|
9109
|
+
renderSessionHuman(session, index, options);
|
|
9110
|
+
if (options.timeline) {
|
|
9111
|
+
for (const runId of session.runIds) {
|
|
9112
|
+
const result = await readRunTraceEvents(runId, traceDir);
|
|
9113
|
+
if (!result || result.events.length === 0) continue;
|
|
9114
|
+
const timeline = buildRunTimeline(result.events);
|
|
9115
|
+
console.log("");
|
|
9116
|
+
console.log(`Timeline: ${runId}`);
|
|
9117
|
+
console.log(renderTimeline(timeline));
|
|
9118
|
+
}
|
|
9119
|
+
}
|
|
9120
|
+
} catch (e) {
|
|
9121
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9122
|
+
console.error(`[AgentInspect] session failed: ${msg}`);
|
|
9123
|
+
process.exitCode = 1;
|
|
9124
|
+
}
|
|
9125
|
+
}
|
|
9126
|
+
|
|
8454
9127
|
// packages/cli/src/what.ts
|
|
8455
9128
|
async function whatCommand(runId, options = {}) {
|
|
8456
9129
|
const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
|
|
@@ -9597,7 +10270,7 @@ function diagnostic(code, message, ruleId) {
|
|
|
9597
10270
|
...ruleId ? { ruleId } : {}
|
|
9598
10271
|
};
|
|
9599
10272
|
}
|
|
9600
|
-
function
|
|
10273
|
+
function emptySummary2() {
|
|
9601
10274
|
return {
|
|
9602
10275
|
passed: 0,
|
|
9603
10276
|
failed: 0,
|
|
@@ -9612,7 +10285,7 @@ function errorResult(input3, diagnostics, selectedRun) {
|
|
|
9612
10285
|
format: input3.read.format,
|
|
9613
10286
|
...selectedRun ? { runId: selectedRun.runId } : {},
|
|
9614
10287
|
summary: {
|
|
9615
|
-
...
|
|
10288
|
+
...emptySummary2(),
|
|
9616
10289
|
errors: diagnostics.filter((item) => item.severity === "error").length
|
|
9617
10290
|
},
|
|
9618
10291
|
findings: [],
|
|
@@ -10518,7 +11191,7 @@ function createSafetyRawContentRule(options = {}) {
|
|
|
10518
11191
|
}
|
|
10519
11192
|
function createSafetySecretPatternRule(options = {}) {
|
|
10520
11193
|
const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
|
|
10521
|
-
const
|
|
11194
|
+
const maxStringLength2 = options.maxStringLength ?? 4096;
|
|
10522
11195
|
return {
|
|
10523
11196
|
id: "safety.secretPattern",
|
|
10524
11197
|
category: "safety",
|
|
@@ -10528,7 +11201,7 @@ function createSafetySecretPatternRule(options = {}) {
|
|
|
10528
11201
|
for (const event of context.events) {
|
|
10529
11202
|
for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
|
|
10530
11203
|
if (typeof entry.value !== "string") continue;
|
|
10531
|
-
const sample = entry.value.slice(0,
|
|
11204
|
+
const sample = entry.value.slice(0, maxStringLength2);
|
|
10532
11205
|
for (const pattern of patterns) {
|
|
10533
11206
|
pattern.pattern.lastIndex = 0;
|
|
10534
11207
|
if (!pattern.pattern.test(sample)) continue;
|
|
@@ -10784,6 +11457,621 @@ function runTraceChecks(input3, options = {}) {
|
|
|
10784
11457
|
diagnostics
|
|
10785
11458
|
};
|
|
10786
11459
|
}
|
|
11460
|
+
var ALL_RULES = [
|
|
11461
|
+
"circuit.same-tool-repetition",
|
|
11462
|
+
"circuit.same-args-repetition",
|
|
11463
|
+
"circuit.max-loop-iterations",
|
|
11464
|
+
"circuit.max-retries",
|
|
11465
|
+
"circuit.tool-timeout",
|
|
11466
|
+
"circuit.runaway-llm-loop",
|
|
11467
|
+
"circuit.excessive-branch-width"
|
|
11468
|
+
];
|
|
11469
|
+
function closed(ruleId, message) {
|
|
11470
|
+
return { ruleId, status: "closed", severity: "info", message, evidence: [] };
|
|
11471
|
+
}
|
|
11472
|
+
function open2(ruleId, message, evidence, severity = "error") {
|
|
11473
|
+
return {
|
|
11474
|
+
ruleId,
|
|
11475
|
+
status: severity === "warning" ? "warn" : "open",
|
|
11476
|
+
severity,
|
|
11477
|
+
message,
|
|
11478
|
+
evidence
|
|
11479
|
+
};
|
|
11480
|
+
}
|
|
11481
|
+
function isToolEvent(event) {
|
|
11482
|
+
const name = event.name.toLowerCase();
|
|
11483
|
+
return event.kind === "tool" || name.startsWith("tool:") || name.startsWith("function:") || name.includes(".tool.") || name.startsWith("mcp:");
|
|
11484
|
+
}
|
|
11485
|
+
function isLlmEvent(event) {
|
|
11486
|
+
const name = event.name.toLowerCase();
|
|
11487
|
+
return event.kind === "llm" || name.startsWith("llm:") || name.includes(".llm.") || name.includes("generation");
|
|
11488
|
+
}
|
|
11489
|
+
function toolLabel(event) {
|
|
11490
|
+
const attrs = event.attributes ?? {};
|
|
11491
|
+
const fromAttr = attrs.toolName ?? attrs.tool ?? attrs.function;
|
|
11492
|
+
if (typeof fromAttr === "string" && fromAttr.length > 0) return fromAttr;
|
|
11493
|
+
return event.name.replace(/^(tool:|function:|mcp:)/i, "");
|
|
11494
|
+
}
|
|
11495
|
+
function argsHash(toolName2, args) {
|
|
11496
|
+
return crypto.createHash("sha256").update(`${toolName2}:${JSON.stringify(args ?? null)}`).digest("hex").slice(0, 16);
|
|
11497
|
+
}
|
|
11498
|
+
function toolArgs(event) {
|
|
11499
|
+
const attrs = event.attributes ?? {};
|
|
11500
|
+
return attrs.arguments ?? attrs.args ?? attrs.input ?? attrs.parameters;
|
|
11501
|
+
}
|
|
11502
|
+
function durationMs(event) {
|
|
11503
|
+
if (typeof event.durationMs === "number") return event.durationMs;
|
|
11504
|
+
const attrs = event.attributes ?? {};
|
|
11505
|
+
const fromAttr = attrs.durationMs ?? attrs.duration;
|
|
11506
|
+
return typeof fromAttr === "number" ? fromAttr : void 0;
|
|
11507
|
+
}
|
|
11508
|
+
function attemptNumber(event) {
|
|
11509
|
+
const attrs = event.attributes ?? {};
|
|
11510
|
+
const value = attrs.attempt ?? attrs.retryAttempt ?? attrs.retryCount;
|
|
11511
|
+
return typeof value === "number" ? value : void 0;
|
|
11512
|
+
}
|
|
11513
|
+
function evaluateSameToolRepetition(events, maxRepeats) {
|
|
11514
|
+
const ruleId = "circuit.same-tool-repetition";
|
|
11515
|
+
const counts = /* @__PURE__ */ new Map();
|
|
11516
|
+
for (const event of events.filter(isToolEvent)) {
|
|
11517
|
+
const label = toolLabel(event);
|
|
11518
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
11519
|
+
}
|
|
11520
|
+
const evidence = [];
|
|
11521
|
+
for (const [toolName2, count] of counts) {
|
|
11522
|
+
if (count > maxRepeats) {
|
|
11523
|
+
evidence.push({ ruleId, toolName: toolName2, count, threshold: maxRepeats });
|
|
11524
|
+
}
|
|
11525
|
+
}
|
|
11526
|
+
if (evidence.length === 0) {
|
|
11527
|
+
return closed(ruleId, "Tool repetition within threshold.");
|
|
11528
|
+
}
|
|
11529
|
+
return open2(ruleId, "Same tool repeated beyond threshold.", evidence);
|
|
11530
|
+
}
|
|
11531
|
+
function evaluateSameArgsRepetition(events, maxRepeats) {
|
|
11532
|
+
const ruleId = "circuit.same-args-repetition";
|
|
11533
|
+
const counts = /* @__PURE__ */ new Map();
|
|
11534
|
+
for (const event of events.filter(isToolEvent)) {
|
|
11535
|
+
const label = toolLabel(event);
|
|
11536
|
+
const hash = argsHash(label, toolArgs(event));
|
|
11537
|
+
const key = `${label}:${hash}`;
|
|
11538
|
+
const current = counts.get(key) ?? { toolName: label, count: 0 };
|
|
11539
|
+
current.count += 1;
|
|
11540
|
+
counts.set(key, current);
|
|
11541
|
+
}
|
|
11542
|
+
const evidence = [];
|
|
11543
|
+
for (const entry of counts.values()) {
|
|
11544
|
+
if (entry.count > maxRepeats) {
|
|
11545
|
+
evidence.push({ ruleId, toolName: entry.toolName, count: entry.count, threshold: maxRepeats });
|
|
11546
|
+
}
|
|
11547
|
+
}
|
|
11548
|
+
if (evidence.length === 0) {
|
|
11549
|
+
return closed(ruleId, "Tool argument repetition within threshold.");
|
|
11550
|
+
}
|
|
11551
|
+
return open2(ruleId, "Same tool arguments repeated beyond threshold.", evidence);
|
|
11552
|
+
}
|
|
11553
|
+
function evaluateMaxLoopIterations(events, maxIterations) {
|
|
11554
|
+
const ruleId = "circuit.max-loop-iterations";
|
|
11555
|
+
const iterationEvents = events.filter((event) => {
|
|
11556
|
+
const attrs = event.attributes ?? {};
|
|
11557
|
+
return typeof attrs.iteration === "number" || event.name.toLowerCase().includes("loop");
|
|
11558
|
+
});
|
|
11559
|
+
const maxSeen = iterationEvents.reduce((max, event) => {
|
|
11560
|
+
const attrs = event.attributes ?? {};
|
|
11561
|
+
const iteration = typeof attrs.iteration === "number" ? attrs.iteration : max;
|
|
11562
|
+
return Math.max(max, iteration);
|
|
11563
|
+
}, iterationEvents.length);
|
|
11564
|
+
if (maxSeen <= maxIterations) {
|
|
11565
|
+
return closed(ruleId, "Loop iterations within threshold.");
|
|
11566
|
+
}
|
|
11567
|
+
return open2(ruleId, "Loop iterations exceeded threshold.", [
|
|
11568
|
+
{ ruleId, count: maxSeen, threshold: maxIterations }
|
|
11569
|
+
]);
|
|
11570
|
+
}
|
|
11571
|
+
function evaluateMaxRetries(events, maxRetries) {
|
|
11572
|
+
const ruleId = "circuit.max-retries";
|
|
11573
|
+
const attempts = events.map(attemptNumber).filter((value) => value !== void 0);
|
|
11574
|
+
const maxAttempt = attempts.length > 0 ? Math.max(...attempts) : 0;
|
|
11575
|
+
if (maxAttempt <= maxRetries) {
|
|
11576
|
+
return closed(ruleId, "Retry count within threshold.");
|
|
11577
|
+
}
|
|
11578
|
+
return open2(ruleId, "Retry count exceeded threshold.", [
|
|
11579
|
+
{ ruleId, count: maxAttempt, threshold: maxRetries }
|
|
11580
|
+
]);
|
|
11581
|
+
}
|
|
11582
|
+
function evaluateToolTimeout(events, maxDurationMs) {
|
|
11583
|
+
const ruleId = "circuit.tool-timeout";
|
|
11584
|
+
const evidence = [];
|
|
11585
|
+
for (const event of events.filter(isToolEvent)) {
|
|
11586
|
+
const duration = durationMs(event);
|
|
11587
|
+
if (duration !== void 0 && duration > maxDurationMs) {
|
|
11588
|
+
evidence.push({
|
|
11589
|
+
ruleId,
|
|
11590
|
+
toolName: toolLabel(event),
|
|
11591
|
+
count: duration,
|
|
11592
|
+
threshold: maxDurationMs,
|
|
11593
|
+
eventId: event.eventId
|
|
11594
|
+
});
|
|
11595
|
+
}
|
|
11596
|
+
}
|
|
11597
|
+
if (evidence.length === 0) {
|
|
11598
|
+
return closed(ruleId, "Tool durations within timeout.");
|
|
11599
|
+
}
|
|
11600
|
+
return open2(ruleId, "Tool call exceeded configured timeout.", evidence, "warning");
|
|
11601
|
+
}
|
|
11602
|
+
function evaluateRunawayLlmLoop(events, maxLlmCalls) {
|
|
11603
|
+
const ruleId = "circuit.runaway-llm-loop";
|
|
11604
|
+
const llmCount = events.filter(isLlmEvent).length;
|
|
11605
|
+
const hasTerminal = events.some((event) => {
|
|
11606
|
+
const status = (event.status ?? event.attributes?.status ?? "").toString().toLowerCase();
|
|
11607
|
+
return status === "ok" || status === "success" || status === "completed";
|
|
11608
|
+
});
|
|
11609
|
+
if (llmCount <= maxLlmCalls || hasTerminal) {
|
|
11610
|
+
return closed(ruleId, "LLM call count within threshold or run completed.");
|
|
11611
|
+
}
|
|
11612
|
+
return open2(ruleId, "Runaway LLM loop detected.", [
|
|
11613
|
+
{ ruleId, count: llmCount, threshold: maxLlmCalls }
|
|
11614
|
+
]);
|
|
11615
|
+
}
|
|
11616
|
+
function evaluateExcessiveBranchWidth(events, maxWidth) {
|
|
11617
|
+
const ruleId = "circuit.excessive-branch-width";
|
|
11618
|
+
const children = /* @__PURE__ */ new Map();
|
|
11619
|
+
for (const event of events) {
|
|
11620
|
+
const parentId = event.parentId;
|
|
11621
|
+
if (!parentId) continue;
|
|
11622
|
+
children.set(parentId, (children.get(parentId) ?? 0) + 1);
|
|
11623
|
+
}
|
|
11624
|
+
const evidence = [];
|
|
11625
|
+
for (const [parentId, count] of children) {
|
|
11626
|
+
if (count > maxWidth) {
|
|
11627
|
+
evidence.push({ ruleId, path: parentId, count, threshold: maxWidth });
|
|
11628
|
+
}
|
|
11629
|
+
}
|
|
11630
|
+
if (evidence.length === 0) {
|
|
11631
|
+
return closed(ruleId, "Branch width within threshold.");
|
|
11632
|
+
}
|
|
11633
|
+
return open2(ruleId, "Excessive parallel branch width detected.", evidence, "warning");
|
|
11634
|
+
}
|
|
11635
|
+
function runRule(ruleId, events, options) {
|
|
11636
|
+
switch (ruleId) {
|
|
11637
|
+
case "circuit.same-tool-repetition":
|
|
11638
|
+
if (options.sameToolRepetition === void 0) return void 0;
|
|
11639
|
+
return evaluateSameToolRepetition(events, options.sameToolRepetition.maxRepeats);
|
|
11640
|
+
case "circuit.same-args-repetition":
|
|
11641
|
+
if (options.sameArgsRepetition === void 0) return void 0;
|
|
11642
|
+
return evaluateSameArgsRepetition(events, options.sameArgsRepetition.maxRepeats);
|
|
11643
|
+
case "circuit.max-loop-iterations":
|
|
11644
|
+
if (options.maxLoopIterations === void 0) return void 0;
|
|
11645
|
+
return evaluateMaxLoopIterations(events, options.maxLoopIterations.maxIterations);
|
|
11646
|
+
case "circuit.max-retries":
|
|
11647
|
+
if (options.maxRetries === void 0) return void 0;
|
|
11648
|
+
return evaluateMaxRetries(events, options.maxRetries.maxRetries);
|
|
11649
|
+
case "circuit.tool-timeout":
|
|
11650
|
+
if (options.toolTimeout === void 0) return void 0;
|
|
11651
|
+
return evaluateToolTimeout(events, options.toolTimeout.maxDurationMs);
|
|
11652
|
+
case "circuit.runaway-llm-loop":
|
|
11653
|
+
if (options.runawayLlmLoop === void 0) return void 0;
|
|
11654
|
+
return evaluateRunawayLlmLoop(events, options.runawayLlmLoop.maxLlmCalls);
|
|
11655
|
+
case "circuit.excessive-branch-width":
|
|
11656
|
+
if (options.excessiveBranchWidth === void 0) return void 0;
|
|
11657
|
+
return evaluateExcessiveBranchWidth(events, options.excessiveBranchWidth.maxWidth);
|
|
11658
|
+
default:
|
|
11659
|
+
return void 0;
|
|
11660
|
+
}
|
|
11661
|
+
}
|
|
11662
|
+
function runCircuits(events, options = {}) {
|
|
11663
|
+
const selected = options.rules ?? ALL_RULES;
|
|
11664
|
+
const results = [];
|
|
11665
|
+
for (const ruleId of selected) {
|
|
11666
|
+
const result = runRule(ruleId, events, options);
|
|
11667
|
+
if (result) results.push(result);
|
|
11668
|
+
}
|
|
11669
|
+
const ok = !results.some((result) => result.status === "open" && result.severity === "error");
|
|
11670
|
+
return { ok, results };
|
|
11671
|
+
}
|
|
11672
|
+
|
|
11673
|
+
// packages/guardrails/src/rules.ts
|
|
11674
|
+
var DEFAULT_INJECTION_PATTERNS = [
|
|
11675
|
+
"ignore previous instructions",
|
|
11676
|
+
"ignore all prior",
|
|
11677
|
+
"disregard your instructions",
|
|
11678
|
+
"system prompt",
|
|
11679
|
+
"you are now",
|
|
11680
|
+
"jailbreak"
|
|
11681
|
+
];
|
|
11682
|
+
function pass(ruleId, message) {
|
|
11683
|
+
return { ruleId, status: "pass", severity: "info", message, evidence: [] };
|
|
11684
|
+
}
|
|
11685
|
+
function fail(ruleId, message, evidence, severity = "error") {
|
|
11686
|
+
return { ruleId, status: severity === "warning" ? "warn" : "fail", severity, message, evidence };
|
|
11687
|
+
}
|
|
11688
|
+
function boundedPreview(value, max = 80) {
|
|
11689
|
+
if (value.length <= max) return value;
|
|
11690
|
+
return `${value.slice(0, max - 3)}...`;
|
|
11691
|
+
}
|
|
11692
|
+
function evaluateBannedPhrase(text, options) {
|
|
11693
|
+
const ruleId = "guardrail.banned-phrase";
|
|
11694
|
+
const haystack = options.caseInsensitive !== false ? text.toLowerCase() : text;
|
|
11695
|
+
const evidence = [];
|
|
11696
|
+
for (const phrase of options.phrases) {
|
|
11697
|
+
const needle = options.caseInsensitive !== false ? phrase.toLowerCase() : phrase;
|
|
11698
|
+
if (needle.length > 0 && haystack.includes(needle)) {
|
|
11699
|
+
evidence.push({ ruleId, match: phrase, preview: boundedPreview(text) });
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
if (evidence.length === 0) {
|
|
11703
|
+
return pass(ruleId, "No banned phrases matched.");
|
|
11704
|
+
}
|
|
11705
|
+
return fail(ruleId, `Matched ${evidence.length} banned phrase(s).`, evidence);
|
|
11706
|
+
}
|
|
11707
|
+
var SEVERITY_RANK2 = { info: 0, warning: 1, error: 2 };
|
|
11708
|
+
function evaluatePiiLeak(value, options = {}) {
|
|
11709
|
+
const ruleId = "guardrail.pii-leak";
|
|
11710
|
+
const minSeverity = options.minSeverity ?? "warning";
|
|
11711
|
+
const result = redact(value, { profile: options.profile ?? "share", collectFindings: true });
|
|
11712
|
+
const findings = result.findings.filter(
|
|
11713
|
+
(finding) => SEVERITY_RANK2[finding.severity] >= SEVERITY_RANK2[minSeverity]
|
|
11714
|
+
);
|
|
11715
|
+
if (findings.length === 0) {
|
|
11716
|
+
return pass(ruleId, "No PII-style redaction findings.");
|
|
11717
|
+
}
|
|
11718
|
+
const evidence = findings.map((finding) => ({
|
|
11719
|
+
ruleId,
|
|
11720
|
+
path: finding.path,
|
|
11721
|
+
detector: finding.detector,
|
|
11722
|
+
preview: finding.preview
|
|
11723
|
+
}));
|
|
11724
|
+
return fail(ruleId, `Detected ${findings.length} PII-style finding(s).`, evidence);
|
|
11725
|
+
}
|
|
11726
|
+
function measureDepth(value) {
|
|
11727
|
+
if (value === null || typeof value !== "object") return 0;
|
|
11728
|
+
if (Array.isArray(value)) {
|
|
11729
|
+
return 1 + Math.max(0, ...value.map((item) => measureDepth(item)));
|
|
11730
|
+
}
|
|
11731
|
+
const depths = Object.values(value).map((item) => measureDepth(item));
|
|
11732
|
+
return 1 + (depths.length === 0 ? 0 : Math.max(...depths));
|
|
11733
|
+
}
|
|
11734
|
+
function maxStringLength(value) {
|
|
11735
|
+
if (typeof value === "string") return value.length;
|
|
11736
|
+
if (value === null || typeof value !== "object") return 0;
|
|
11737
|
+
if (Array.isArray(value)) {
|
|
11738
|
+
return Math.max(0, ...value.map((item) => maxStringLength(item)));
|
|
11739
|
+
}
|
|
11740
|
+
return Math.max(0, ...Object.values(value).map((item) => maxStringLength(item)));
|
|
11741
|
+
}
|
|
11742
|
+
function evaluateUnsafeToolArgs(toolName2, toolArgs2, options = {}) {
|
|
11743
|
+
const ruleId = "guardrail.unsafe-tool-args";
|
|
11744
|
+
const blocked = new Set((options.blockedTools ?? []).map((name) => name.toLowerCase()));
|
|
11745
|
+
const evidence = [];
|
|
11746
|
+
if (blocked.has(toolName2.toLowerCase())) {
|
|
11747
|
+
evidence.push({ ruleId, preview: toolName2, match: toolName2 });
|
|
11748
|
+
}
|
|
11749
|
+
const maxDepth = options.maxDepth ?? 12;
|
|
11750
|
+
const depth = measureDepth(toolArgs2);
|
|
11751
|
+
if (depth > maxDepth) {
|
|
11752
|
+
evidence.push({ ruleId, path: "args", preview: `depth=${depth}` });
|
|
11753
|
+
}
|
|
11754
|
+
const maxLen = options.maxStringLength ?? 16384;
|
|
11755
|
+
const longest = maxStringLength(toolArgs2);
|
|
11756
|
+
if (longest > maxLen) {
|
|
11757
|
+
evidence.push({ ruleId, path: "args", preview: `maxStringLength=${longest}` });
|
|
11758
|
+
}
|
|
11759
|
+
if (evidence.length === 0) {
|
|
11760
|
+
return pass(ruleId, "Tool arguments within configured bounds.");
|
|
11761
|
+
}
|
|
11762
|
+
return fail(ruleId, "Unsafe or oversized tool arguments detected.", evidence);
|
|
11763
|
+
}
|
|
11764
|
+
function evaluatePromptInjection(text, options = {}) {
|
|
11765
|
+
const ruleId = "guardrail.prompt-injection";
|
|
11766
|
+
const patterns = options.patterns ?? DEFAULT_INJECTION_PATTERNS;
|
|
11767
|
+
const haystack = text.toLowerCase();
|
|
11768
|
+
const evidence = [];
|
|
11769
|
+
for (const pattern of patterns) {
|
|
11770
|
+
const needle = pattern.toLowerCase();
|
|
11771
|
+
if (needle.length > 0 && haystack.includes(needle)) {
|
|
11772
|
+
evidence.push({ ruleId, match: pattern, preview: boundedPreview(text) });
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11775
|
+
if (evidence.length === 0) {
|
|
11776
|
+
return pass(ruleId, "No prompt-injection patterns matched.");
|
|
11777
|
+
}
|
|
11778
|
+
return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
|
|
11779
|
+
}
|
|
11780
|
+
function validateSchemaField(value, field, path15, evidence) {
|
|
11781
|
+
const ruleId = "guardrail.structured-output";
|
|
11782
|
+
if (field.type) {
|
|
11783
|
+
const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
11784
|
+
if (actual !== field.type) {
|
|
11785
|
+
evidence.push({ ruleId, path: path15, preview: `expected ${field.type}, got ${actual}` });
|
|
11786
|
+
return;
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
|
|
11790
|
+
evidence.push({ ruleId, path: path15, preview: "value not in enum" });
|
|
11791
|
+
}
|
|
11792
|
+
if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
|
|
11793
|
+
const record = value;
|
|
11794
|
+
for (const key of field.required) {
|
|
11795
|
+
if (!(key in record)) {
|
|
11796
|
+
evidence.push({ ruleId, path: `${path15}.${key}`, preview: "missing required key" });
|
|
11797
|
+
}
|
|
11798
|
+
}
|
|
11799
|
+
}
|
|
11800
|
+
}
|
|
11801
|
+
function evaluateStructuredOutput(value, options) {
|
|
11802
|
+
const ruleId = "guardrail.structured-output";
|
|
11803
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11804
|
+
return fail(ruleId, "Structured output must be an object.", [
|
|
11805
|
+
{ ruleId, preview: typeof value }
|
|
11806
|
+
]);
|
|
11807
|
+
}
|
|
11808
|
+
const record = value;
|
|
11809
|
+
const evidence = [];
|
|
11810
|
+
for (const [key, field] of Object.entries(options.schema)) {
|
|
11811
|
+
validateSchemaField(record[key], field, key, evidence);
|
|
11812
|
+
}
|
|
11813
|
+
if (evidence.length === 0) {
|
|
11814
|
+
return pass(ruleId, "Structured output matches schema subset.");
|
|
11815
|
+
}
|
|
11816
|
+
return fail(ruleId, "Structured output schema violation.", evidence);
|
|
11817
|
+
}
|
|
11818
|
+
function evaluateOversizeOutput(value, options = {}) {
|
|
11819
|
+
const ruleId = "guardrail.oversize-output";
|
|
11820
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
11821
|
+
const maxLength = options.maxLength ?? options.maxSerializedLength ?? 32768;
|
|
11822
|
+
if (text.length <= maxLength) {
|
|
11823
|
+
return pass(ruleId, "Output within size limits.");
|
|
11824
|
+
}
|
|
11825
|
+
return fail(ruleId, `Output exceeds max length (${text.length} > ${maxLength}).`, [
|
|
11826
|
+
{ ruleId, preview: `length=${text.length}` }
|
|
11827
|
+
]);
|
|
11828
|
+
}
|
|
11829
|
+
function evaluateRequiredJsonShape(value, options) {
|
|
11830
|
+
const ruleId = "guardrail.required-json-shape";
|
|
11831
|
+
let parsed = value;
|
|
11832
|
+
if (typeof value === "string") {
|
|
11833
|
+
try {
|
|
11834
|
+
parsed = JSON.parse(value);
|
|
11835
|
+
} catch {
|
|
11836
|
+
return fail(ruleId, "Value is not valid JSON.", [{ ruleId, preview: boundedPreview(value) }]);
|
|
11837
|
+
}
|
|
11838
|
+
}
|
|
11839
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11840
|
+
return fail(ruleId, "JSON value must be an object.", [{ ruleId, preview: typeof parsed }]);
|
|
11841
|
+
}
|
|
11842
|
+
const record = parsed;
|
|
11843
|
+
const evidence = [];
|
|
11844
|
+
for (const key of options.requiredKeys) {
|
|
11845
|
+
if (!(key in record)) {
|
|
11846
|
+
evidence.push({ ruleId, path: key, preview: "missing required key" });
|
|
11847
|
+
}
|
|
11848
|
+
}
|
|
11849
|
+
if (evidence.length === 0) {
|
|
11850
|
+
return pass(ruleId, "Required JSON keys present.");
|
|
11851
|
+
}
|
|
11852
|
+
return fail(ruleId, "Missing required JSON keys.", evidence);
|
|
11853
|
+
}
|
|
11854
|
+
|
|
11855
|
+
// packages/guardrails/src/run.ts
|
|
11856
|
+
var ALL_RULES2 = [
|
|
11857
|
+
"guardrail.banned-phrase",
|
|
11858
|
+
"guardrail.pii-leak",
|
|
11859
|
+
"guardrail.unsafe-tool-args",
|
|
11860
|
+
"guardrail.prompt-injection",
|
|
11861
|
+
"guardrail.structured-output",
|
|
11862
|
+
"guardrail.oversize-output",
|
|
11863
|
+
"guardrail.required-json-shape"
|
|
11864
|
+
];
|
|
11865
|
+
function isErrorFailure(result) {
|
|
11866
|
+
return result.status === "fail" && result.severity === "error";
|
|
11867
|
+
}
|
|
11868
|
+
function runRule2(ruleId, input3, options) {
|
|
11869
|
+
switch (ruleId) {
|
|
11870
|
+
case "guardrail.banned-phrase": {
|
|
11871
|
+
if (!options.bannedPhrase || input3.text === void 0) return void 0;
|
|
11872
|
+
return evaluateBannedPhrase(input3.text, options.bannedPhrase);
|
|
11873
|
+
}
|
|
11874
|
+
case "guardrail.pii-leak": {
|
|
11875
|
+
if (input3.value === void 0 && input3.text === void 0) return void 0;
|
|
11876
|
+
return evaluatePiiLeak(input3.value ?? input3.text, options.piiLeak);
|
|
11877
|
+
}
|
|
11878
|
+
case "guardrail.unsafe-tool-args": {
|
|
11879
|
+
if (!input3.toolName) return void 0;
|
|
11880
|
+
return evaluateUnsafeToolArgs(input3.toolName, input3.toolArgs ?? {}, options.unsafeToolArgs);
|
|
11881
|
+
}
|
|
11882
|
+
case "guardrail.prompt-injection": {
|
|
11883
|
+
if (input3.text === void 0) return void 0;
|
|
11884
|
+
return evaluatePromptInjection(input3.text, options.promptInjection);
|
|
11885
|
+
}
|
|
11886
|
+
case "guardrail.structured-output": {
|
|
11887
|
+
if (!options.structuredOutput || input3.value === void 0) return void 0;
|
|
11888
|
+
return evaluateStructuredOutput(input3.value, options.structuredOutput);
|
|
11889
|
+
}
|
|
11890
|
+
case "guardrail.oversize-output": {
|
|
11891
|
+
if (input3.value === void 0 && input3.text === void 0) return void 0;
|
|
11892
|
+
return evaluateOversizeOutput(input3.value ?? input3.text, options.oversizeOutput);
|
|
11893
|
+
}
|
|
11894
|
+
case "guardrail.required-json-shape": {
|
|
11895
|
+
if (!options.requiredJsonShape || input3.value === void 0 && input3.text === void 0) return void 0;
|
|
11896
|
+
return evaluateRequiredJsonShape(input3.value ?? input3.text, options.requiredJsonShape);
|
|
11897
|
+
}
|
|
11898
|
+
default:
|
|
11899
|
+
return void 0;
|
|
11900
|
+
}
|
|
11901
|
+
}
|
|
11902
|
+
function runGuardrails(input3, options = {}) {
|
|
11903
|
+
const selected = options.rules ?? ALL_RULES2;
|
|
11904
|
+
const results = [];
|
|
11905
|
+
for (const ruleId of selected) {
|
|
11906
|
+
const result = runRule2(ruleId, input3, options);
|
|
11907
|
+
if (result) results.push(result);
|
|
11908
|
+
}
|
|
11909
|
+
const ok = !results.some(isErrorFailure);
|
|
11910
|
+
return { ok, results };
|
|
11911
|
+
}
|
|
11912
|
+
|
|
11913
|
+
// packages/cli/src/safety-extensions.ts
|
|
11914
|
+
var GUARDRAIL_ALIASES = {
|
|
11915
|
+
"banned-phrase": "guardrail.banned-phrase",
|
|
11916
|
+
"pii-leak": "guardrail.pii-leak",
|
|
11917
|
+
"unsafe-tool-args": "guardrail.unsafe-tool-args",
|
|
11918
|
+
"prompt-injection": "guardrail.prompt-injection",
|
|
11919
|
+
"structured-output": "guardrail.structured-output",
|
|
11920
|
+
"oversize-output": "guardrail.oversize-output",
|
|
11921
|
+
"required-json-shape": "guardrail.required-json-shape"
|
|
11922
|
+
};
|
|
11923
|
+
var CIRCUIT_ALIASES = {
|
|
11924
|
+
"same-tool-repetition": "circuit.same-tool-repetition",
|
|
11925
|
+
"same-args-repetition": "circuit.same-args-repetition",
|
|
11926
|
+
"max-loop-iterations": "circuit.max-loop-iterations",
|
|
11927
|
+
"max-retries": "circuit.max-retries",
|
|
11928
|
+
"tool-timeout": "circuit.tool-timeout",
|
|
11929
|
+
"runaway-llm-loop": "circuit.runaway-llm-loop",
|
|
11930
|
+
"excessive-branch-width": "circuit.excessive-branch-width"
|
|
11931
|
+
};
|
|
11932
|
+
function parseGuardrailRules(values) {
|
|
11933
|
+
if (!values?.length) return void 0;
|
|
11934
|
+
return values.map((value) => {
|
|
11935
|
+
const rule = GUARDRAIL_ALIASES[value] ?? value;
|
|
11936
|
+
return rule;
|
|
11937
|
+
});
|
|
11938
|
+
}
|
|
11939
|
+
function parseCircuitRules(values) {
|
|
11940
|
+
if (!values?.length) return void 0;
|
|
11941
|
+
return values.map((value) => {
|
|
11942
|
+
const rule = CIRCUIT_ALIASES[value] ?? value;
|
|
11943
|
+
return rule;
|
|
11944
|
+
});
|
|
11945
|
+
}
|
|
11946
|
+
function toSeverity(severity) {
|
|
11947
|
+
return severity;
|
|
11948
|
+
}
|
|
11949
|
+
function guardrailFinding(result) {
|
|
11950
|
+
return {
|
|
11951
|
+
ruleId: result.ruleId,
|
|
11952
|
+
severity: toSeverity(result.severity),
|
|
11953
|
+
status: result.status === "pass" ? "pass" : result.status === "warn" ? "warning" : "fail",
|
|
11954
|
+
message: result.message,
|
|
11955
|
+
evidence: result.evidence.map(
|
|
11956
|
+
(item) => ({
|
|
11957
|
+
path: item.preview ? `${item.path ?? "value"} (${item.preview})` : item.path
|
|
11958
|
+
})
|
|
11959
|
+
)
|
|
11960
|
+
};
|
|
11961
|
+
}
|
|
11962
|
+
function circuitFinding(result) {
|
|
11963
|
+
return {
|
|
11964
|
+
ruleId: result.ruleId,
|
|
11965
|
+
severity: toSeverity(result.severity),
|
|
11966
|
+
status: result.status === "closed" ? "pass" : result.status === "warn" ? "warning" : "fail",
|
|
11967
|
+
message: result.message,
|
|
11968
|
+
evidence: result.evidence.map(
|
|
11969
|
+
(item) => ({
|
|
11970
|
+
runId: item.runId,
|
|
11971
|
+
eventId: item.eventId,
|
|
11972
|
+
path: item.path,
|
|
11973
|
+
name: item.toolName
|
|
11974
|
+
})
|
|
11975
|
+
),
|
|
11976
|
+
actual: result.evidence[0]?.count,
|
|
11977
|
+
expected: result.evidence[0]?.threshold
|
|
11978
|
+
};
|
|
11979
|
+
}
|
|
11980
|
+
function eventToCircuit(event) {
|
|
11981
|
+
return {
|
|
11982
|
+
eventId: event.eventId,
|
|
11983
|
+
runId: event.runId,
|
|
11984
|
+
name: event.name,
|
|
11985
|
+
kind: event.kind,
|
|
11986
|
+
parentId: event.parentId,
|
|
11987
|
+
startedAt: event.startedAt,
|
|
11988
|
+
endedAt: event.endedAt,
|
|
11989
|
+
durationMs: event.durationMs,
|
|
11990
|
+
attributes: event.attributes,
|
|
11991
|
+
status: event.status
|
|
11992
|
+
};
|
|
11993
|
+
}
|
|
11994
|
+
function collectGuardrailInputs(read) {
|
|
11995
|
+
const inputs = [];
|
|
11996
|
+
for (const event of read.events) {
|
|
11997
|
+
const attrs = event.attributes ?? {};
|
|
11998
|
+
for (const key of ["output", "answer", "text", "content", "message"]) {
|
|
11999
|
+
const value = attrs[key];
|
|
12000
|
+
if (typeof value === "string") inputs.push({ text: value });
|
|
12001
|
+
else if (value !== void 0) inputs.push({ value });
|
|
12002
|
+
}
|
|
12003
|
+
if (event.kind === "TOOL" || event.name.startsWith("tool:")) {
|
|
12004
|
+
inputs.push({
|
|
12005
|
+
toolName: String(attrs.toolName ?? attrs.tool ?? event.name),
|
|
12006
|
+
toolArgs: attrs.arguments ?? attrs.args ?? attrs.input
|
|
12007
|
+
});
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
12010
|
+
return inputs;
|
|
12011
|
+
}
|
|
12012
|
+
var DEFAULT_GUARDRAIL_OPTIONS = {
|
|
12013
|
+
bannedPhrase: { phrases: ["delete all data", "ignore all instructions"] },
|
|
12014
|
+
promptInjection: {},
|
|
12015
|
+
piiLeak: { profile: "share" }
|
|
12016
|
+
};
|
|
12017
|
+
var DEFAULT_CIRCUIT_OPTIONS = {
|
|
12018
|
+
sameToolRepetition: { maxRepeats: 3 },
|
|
12019
|
+
sameArgsRepetition: { maxRepeats: 2 },
|
|
12020
|
+
maxLoopIterations: { maxIterations: 20 },
|
|
12021
|
+
maxRetries: { maxRetries: 3 },
|
|
12022
|
+
toolTimeout: { maxDurationMs: 6e4 },
|
|
12023
|
+
runawayLlmLoop: { maxLlmCalls: 12 },
|
|
12024
|
+
excessiveBranchWidth: { maxWidth: 8 }
|
|
12025
|
+
};
|
|
12026
|
+
function mergeSafetyExtensions(result, read, options) {
|
|
12027
|
+
const findings = [...result.findings];
|
|
12028
|
+
let failed = result.summary.failed;
|
|
12029
|
+
let warnings = result.summary.warnings;
|
|
12030
|
+
let passed = result.summary.passed;
|
|
12031
|
+
const guardrailRules = parseGuardrailRules(options.guardrails);
|
|
12032
|
+
if (guardrailRules) {
|
|
12033
|
+
for (const input3 of collectGuardrailInputs(read)) {
|
|
12034
|
+
const run = runGuardrails(input3, {
|
|
12035
|
+
...DEFAULT_GUARDRAIL_OPTIONS,
|
|
12036
|
+
rules: guardrailRules
|
|
12037
|
+
});
|
|
12038
|
+
for (const item of run.results) {
|
|
12039
|
+
const finding = guardrailFinding(item);
|
|
12040
|
+
findings.push(finding);
|
|
12041
|
+
if (finding.status === "fail") failed += 1;
|
|
12042
|
+
else if (finding.status === "warning") warnings += 1;
|
|
12043
|
+
else passed += 1;
|
|
12044
|
+
}
|
|
12045
|
+
}
|
|
12046
|
+
}
|
|
12047
|
+
const circuitRules = parseCircuitRules(options.circuits);
|
|
12048
|
+
if (circuitRules) {
|
|
12049
|
+
const circuitRun = runCircuits(
|
|
12050
|
+
read.events.map(eventToCircuit),
|
|
12051
|
+
{ ...DEFAULT_CIRCUIT_OPTIONS, rules: circuitRules }
|
|
12052
|
+
);
|
|
12053
|
+
for (const item of circuitRun.results) {
|
|
12054
|
+
const finding = circuitFinding(item);
|
|
12055
|
+
findings.push(finding);
|
|
12056
|
+
if (finding.status === "fail") failed += 1;
|
|
12057
|
+
else if (finding.status === "warning") warnings += 1;
|
|
12058
|
+
else passed += 1;
|
|
12059
|
+
}
|
|
12060
|
+
}
|
|
12061
|
+
const ok = failed === 0 && result.summary.errors === 0;
|
|
12062
|
+
return {
|
|
12063
|
+
...result,
|
|
12064
|
+
ok,
|
|
12065
|
+
status: failed > 0 ? "fail" : result.status,
|
|
12066
|
+
summary: {
|
|
12067
|
+
passed,
|
|
12068
|
+
failed,
|
|
12069
|
+
warnings,
|
|
12070
|
+
errors: result.summary.errors
|
|
12071
|
+
},
|
|
12072
|
+
findings
|
|
12073
|
+
};
|
|
12074
|
+
}
|
|
10787
12075
|
|
|
10788
12076
|
// packages/cli/src/check.ts
|
|
10789
12077
|
var DEFAULT_SELECT = ["run.status"];
|
|
@@ -10953,6 +12241,13 @@ function printJson(result) {
|
|
|
10953
12241
|
console.log(JSON.stringify(stable2(result), null, 2));
|
|
10954
12242
|
}
|
|
10955
12243
|
function printHuman(result) {
|
|
12244
|
+
const scoped = result;
|
|
12245
|
+
if (scoped.scopeLabel) {
|
|
12246
|
+
console.log(`Scope: ${scoped.scopeKind} ${scoped.scopeLabel}`);
|
|
12247
|
+
if (scoped.runIds?.length) {
|
|
12248
|
+
console.log(`Runs: ${scoped.runIds.join(", ")}`);
|
|
12249
|
+
}
|
|
12250
|
+
}
|
|
10956
12251
|
console.log(`Check status: ${result.status}`);
|
|
10957
12252
|
console.log(`Format: ${result.format}`);
|
|
10958
12253
|
if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
|
|
@@ -10964,7 +12259,9 @@ function printHuman(result) {
|
|
|
10964
12259
|
}
|
|
10965
12260
|
for (const finding of result.findings) {
|
|
10966
12261
|
const path15 = finding.evidence[0]?.path;
|
|
10967
|
-
|
|
12262
|
+
const run = finding.evidence[0]?.runId;
|
|
12263
|
+
const runPrefix = run ? `[${run}] ` : "";
|
|
12264
|
+
console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
|
|
10968
12265
|
}
|
|
10969
12266
|
}
|
|
10970
12267
|
function readErrorResult(error) {
|
|
@@ -10980,24 +12277,84 @@ function readErrorResult(error) {
|
|
|
10980
12277
|
async function checkCommand(target, options = {}, stdin = process.stdin) {
|
|
10981
12278
|
let result;
|
|
10982
12279
|
let phase = "config";
|
|
12280
|
+
const sessionId = options.session?.trim();
|
|
12281
|
+
const groupId = options.group?.trim();
|
|
12282
|
+
const useSessionScope = Boolean(sessionId || groupId);
|
|
10983
12283
|
try {
|
|
10984
12284
|
const config = await loadConfig(options.config);
|
|
10985
12285
|
const built = buildRules(config, options);
|
|
10986
12286
|
if (built.diagnostics.some((item) => item.severity === "error")) {
|
|
10987
12287
|
result = errorResult2("AI_CHECK_INVALID_CONFIG", "Invalid check configuration.");
|
|
10988
12288
|
result.diagnostics = [...built.diagnostics];
|
|
12289
|
+
} else if (useSessionScope) {
|
|
12290
|
+
phase = "read";
|
|
12291
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
12292
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
12293
|
+
const files = await td.list();
|
|
12294
|
+
const metas = await loadTraceMetadataList(
|
|
12295
|
+
traceDir,
|
|
12296
|
+
files,
|
|
12297
|
+
(fileName) => td.getPath(fileName)
|
|
12298
|
+
);
|
|
12299
|
+
const records = await loadSessionRunRecords(metas);
|
|
12300
|
+
const scoped = filterMetasBySessionScope(metas, records, {
|
|
12301
|
+
...sessionId ? { sessionId } : {},
|
|
12302
|
+
...groupId ? { groupId } : {},
|
|
12303
|
+
correlateByGroupId: options.correlateGroup === true
|
|
12304
|
+
});
|
|
12305
|
+
const perRun = [];
|
|
12306
|
+
for (const meta of scoped.metas) {
|
|
12307
|
+
const read = await openTrace(
|
|
12308
|
+
{ type: "file", path: meta.filePath },
|
|
12309
|
+
{
|
|
12310
|
+
...options.format !== void 0 ? { format: options.format } : {}
|
|
12311
|
+
}
|
|
12312
|
+
);
|
|
12313
|
+
perRun.push(
|
|
12314
|
+
mergeSafetyExtensions(
|
|
12315
|
+
runTraceChecks(
|
|
12316
|
+
{ read },
|
|
12317
|
+
{
|
|
12318
|
+
rules: built.rules,
|
|
12319
|
+
select: built.select,
|
|
12320
|
+
runId: meta.runId
|
|
12321
|
+
}
|
|
12322
|
+
),
|
|
12323
|
+
read,
|
|
12324
|
+
{
|
|
12325
|
+
...options.guardrails ? { guardrails: options.guardrails } : {},
|
|
12326
|
+
...options.circuit ? { circuits: options.circuit } : {}
|
|
12327
|
+
}
|
|
12328
|
+
)
|
|
12329
|
+
);
|
|
12330
|
+
}
|
|
12331
|
+
result = aggregateSessionCheckResults(perRun, {
|
|
12332
|
+
scopeKind: scoped.scopeKind,
|
|
12333
|
+
scopeLabel: scoped.scopeLabel,
|
|
12334
|
+
runIds: scoped.runIds,
|
|
12335
|
+
sessionWarnings: scoped.warnings,
|
|
12336
|
+
notFound: scoped.notFound,
|
|
12337
|
+
empty: scoped.metas.length === 0
|
|
12338
|
+
});
|
|
10989
12339
|
} else {
|
|
10990
12340
|
phase = "read";
|
|
10991
12341
|
const input3 = await inputFromTarget(target, options, stdin);
|
|
10992
12342
|
const read = await openTrace(input3, {
|
|
10993
12343
|
...options.format !== void 0 ? { format: options.format } : {}
|
|
10994
12344
|
});
|
|
10995
|
-
result =
|
|
10996
|
-
|
|
12345
|
+
result = mergeSafetyExtensions(
|
|
12346
|
+
runTraceChecks(
|
|
12347
|
+
{ read },
|
|
12348
|
+
{
|
|
12349
|
+
rules: built.rules,
|
|
12350
|
+
select: built.select,
|
|
12351
|
+
...options.run !== void 0 ? { runId: options.run } : {}
|
|
12352
|
+
}
|
|
12353
|
+
),
|
|
12354
|
+
read,
|
|
10997
12355
|
{
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
...options.run !== void 0 ? { runId: options.run } : {}
|
|
12356
|
+
...options.guardrails ? { guardrails: options.guardrails } : {},
|
|
12357
|
+
...options.circuit ? { circuits: options.circuit } : {}
|
|
11001
12358
|
}
|
|
11002
12359
|
);
|
|
11003
12360
|
}
|
|
@@ -11201,7 +12558,7 @@ function evidenceForEvent(event, path15) {
|
|
|
11201
12558
|
}
|
|
11202
12559
|
];
|
|
11203
12560
|
}
|
|
11204
|
-
function
|
|
12561
|
+
function fail2(ruleId, message, evidence, expected, actual) {
|
|
11205
12562
|
return {
|
|
11206
12563
|
ruleId,
|
|
11207
12564
|
status: "fail",
|
|
@@ -11397,7 +12754,7 @@ var checks = {
|
|
|
11397
12754
|
"eval.requireSuccess",
|
|
11398
12755
|
"run",
|
|
11399
12756
|
(context) => context.run.status === "ok" ? [] : [
|
|
11400
|
-
|
|
12757
|
+
fail2(
|
|
11401
12758
|
"eval.requireSuccess",
|
|
11402
12759
|
"Run did not complete successfully.",
|
|
11403
12760
|
evidenceForRun(context.run, "status"),
|
|
@@ -11412,7 +12769,7 @@ var checks = {
|
|
|
11412
12769
|
return createRule("eval.requiredTools", "tool", (context) => {
|
|
11413
12770
|
const tools = new Set(nodeNames(context.nodes, "TOOL"));
|
|
11414
12771
|
return expected.filter((name) => !tools.has(name)).map(
|
|
11415
|
-
(name) =>
|
|
12772
|
+
(name) => fail2(
|
|
11416
12773
|
"eval.requiredTools",
|
|
11417
12774
|
`Required tool ${name} did not appear.`,
|
|
11418
12775
|
evidenceForRun(context.run, "children"),
|
|
@@ -11428,7 +12785,7 @@ var checks = {
|
|
|
11428
12785
|
"eval.forbiddenTools",
|
|
11429
12786
|
"tool",
|
|
11430
12787
|
(context) => context.nodes.filter((node) => node.event.kind === "TOOL" && blocked.includes(node.event.name)).map(
|
|
11431
|
-
(node) =>
|
|
12788
|
+
(node) => fail2(
|
|
11432
12789
|
"eval.forbiddenTools",
|
|
11433
12790
|
`Forbidden tool ${node.event.name} appeared.`,
|
|
11434
12791
|
evidenceForEvent(node.event, "name"),
|
|
@@ -11443,7 +12800,7 @@ var checks = {
|
|
|
11443
12800
|
"eval.maxDurationMs",
|
|
11444
12801
|
"run",
|
|
11445
12802
|
(context) => context.run.durationMs !== void 0 && context.run.durationMs > maxDurationMs ? [
|
|
11446
|
-
|
|
12803
|
+
fail2(
|
|
11447
12804
|
"eval.maxDurationMs",
|
|
11448
12805
|
`Run duration exceeded ${maxDurationMs}ms.`,
|
|
11449
12806
|
evidenceForRun(context.run, "durationMs"),
|
|
@@ -11460,7 +12817,7 @@ var checks = {
|
|
|
11460
12817
|
void 0
|
|
11461
12818
|
);
|
|
11462
12819
|
return deepest !== void 0 && deepest.depth > maxDepth ? [
|
|
11463
|
-
|
|
12820
|
+
fail2(
|
|
11464
12821
|
"eval.maxDepth",
|
|
11465
12822
|
`Run tree depth exceeded ${maxDepth}.`,
|
|
11466
12823
|
evidenceForEvent(deepest.event, "depth"),
|
|
@@ -11477,7 +12834,7 @@ var checks = {
|
|
|
11477
12834
|
(context) => context.nodes.flatMap((node) => {
|
|
11478
12835
|
const retries = numericAttribute(node, ["retryCount", "retries", "attempt"]);
|
|
11479
12836
|
return retries !== void 0 && retries > maxRetries ? [
|
|
11480
|
-
|
|
12837
|
+
fail2(
|
|
11481
12838
|
"eval.maxRetries",
|
|
11482
12839
|
`Retry count exceeded ${maxRetries}.`,
|
|
11483
12840
|
evidenceForEvent(node.event, "attributes.retryCount"),
|
|
@@ -11492,7 +12849,7 @@ var checks = {
|
|
|
11492
12849
|
return createRule("eval.maxTotalTokens", "llm", (context) => {
|
|
11493
12850
|
const total = totalTokenCount(context.events);
|
|
11494
12851
|
return total > maxTotalTokens ? [
|
|
11495
|
-
|
|
12852
|
+
fail2(
|
|
11496
12853
|
"eval.maxTotalTokens",
|
|
11497
12854
|
`Total token usage exceeded ${maxTotalTokens}.`,
|
|
11498
12855
|
evidenceForRun(context.run, "tokenUsage.total"),
|
|
@@ -11507,7 +12864,7 @@ var checks = {
|
|
|
11507
12864
|
"eval.noFailedSteps",
|
|
11508
12865
|
"run",
|
|
11509
12866
|
(context) => context.nodes.filter((node) => node.event.status === "error" || node.event.kind === "ERROR").map(
|
|
11510
|
-
(node) =>
|
|
12867
|
+
(node) => fail2(
|
|
11511
12868
|
"eval.noFailedSteps",
|
|
11512
12869
|
"Run contains a failed step or error node.",
|
|
11513
12870
|
evidenceForEvent(node.event, "status"),
|
|
@@ -11525,7 +12882,7 @@ var checks = {
|
|
|
11525
12882
|
(node, index) => index < firstLlmIndex && node.event.kind === "RETRIEVER"
|
|
11526
12883
|
);
|
|
11527
12884
|
return retrievalIndex === -1 ? [
|
|
11528
|
-
|
|
12885
|
+
fail2(
|
|
11529
12886
|
"eval.requiredRetrievalBeforeGeneration",
|
|
11530
12887
|
"No retrieval step appeared before the first LLM generation.",
|
|
11531
12888
|
evidenceForEvent(context.nodes[firstLlmIndex].event, "kind"),
|
|
@@ -11541,7 +12898,7 @@ var checks = {
|
|
|
11541
12898
|
const decisions = context.nodes.filter((node) => node.event.kind === "DECISION");
|
|
11542
12899
|
if (decisions.length === 0) {
|
|
11543
12900
|
return [
|
|
11544
|
-
|
|
12901
|
+
fail2(
|
|
11545
12902
|
"eval.requiredDecisionMetadata",
|
|
11546
12903
|
"No decision node is available for required metadata.",
|
|
11547
12904
|
evidenceForRun(context.run, "children"),
|
|
@@ -11552,7 +12909,7 @@ var checks = {
|
|
|
11552
12909
|
}
|
|
11553
12910
|
return decisions.flatMap(
|
|
11554
12911
|
(node) => required.filter((key) => !hasAttribute(node, key)).map(
|
|
11555
|
-
(key) =>
|
|
12912
|
+
(key) => fail2(
|
|
11556
12913
|
"eval.requiredDecisionMetadata",
|
|
11557
12914
|
`Decision metadata ${key} is missing.`,
|
|
11558
12915
|
evidenceForEvent(node.event, `attributes.${key}`),
|
|
@@ -11573,7 +12930,7 @@ var checks = {
|
|
|
11573
12930
|
const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
|
|
11574
12931
|
if (answers.length === 0 || contexts.length === 0) {
|
|
11575
12932
|
return [
|
|
11576
|
-
|
|
12933
|
+
fail2(
|
|
11577
12934
|
"eval.contextOverlap",
|
|
11578
12935
|
"Answer and context text are required for overlap evaluation.",
|
|
11579
12936
|
firstEvidence(answers.length > 0 ? answers : contexts, context.run, "children"),
|
|
@@ -11587,7 +12944,7 @@ var checks = {
|
|
|
11587
12944
|
const sharedTerms = [...answerTerms].filter((term) => contextTerms.has(term)).length;
|
|
11588
12945
|
const overlap = answerTerms.size === 0 ? 0 : sharedTerms / answerTerms.size;
|
|
11589
12946
|
return sharedTerms < minSharedTerms || overlap < minOverlap ? [
|
|
11590
|
-
|
|
12947
|
+
fail2(
|
|
11591
12948
|
"eval.contextOverlap",
|
|
11592
12949
|
"Answer text did not sufficiently overlap retrieved context.",
|
|
11593
12950
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11615,7 +12972,7 @@ var checks = {
|
|
|
11615
12972
|
const quotes = quotedSnippets(answerText, minQuoteLength);
|
|
11616
12973
|
if (quotes.length === 0) {
|
|
11617
12974
|
return requireQuote ? [
|
|
11618
|
-
|
|
12975
|
+
fail2(
|
|
11619
12976
|
"eval.quoteOverlap",
|
|
11620
12977
|
"Answer did not contain a quote for overlap evaluation.",
|
|
11621
12978
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11626,7 +12983,7 @@ var checks = {
|
|
|
11626
12983
|
}
|
|
11627
12984
|
const missing = quotes.filter((quote) => !contextText.includes(quote.toLowerCase()));
|
|
11628
12985
|
return missing.length > 0 ? [
|
|
11629
|
-
|
|
12986
|
+
fail2(
|
|
11630
12987
|
"eval.quoteOverlap",
|
|
11631
12988
|
"Quoted answer text did not appear in retrieved context.",
|
|
11632
12989
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11644,7 +13001,7 @@ var checks = {
|
|
|
11644
13001
|
const citations = collectTextFields(context.nodes, citationKeys);
|
|
11645
13002
|
const count = citationCount(answers.map((field) => field.text).join(" "), citations);
|
|
11646
13003
|
return count === 0 ? [
|
|
11647
|
-
|
|
13004
|
+
fail2(
|
|
11648
13005
|
"eval.citationPresence",
|
|
11649
13006
|
"Answer did not include citations or source references.",
|
|
11650
13007
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11662,7 +13019,7 @@ var checks = {
|
|
|
11662
13019
|
const availableSet = new Set(available);
|
|
11663
13020
|
const missing = expected.filter((id) => !availableSet.has(id));
|
|
11664
13021
|
return missing.length > 0 ? [
|
|
11665
|
-
|
|
13022
|
+
fail2(
|
|
11666
13023
|
"eval.requiredSourceIds",
|
|
11667
13024
|
"Required source IDs were not present in trace context or citations.",
|
|
11668
13025
|
evidenceForRun(context.run, "children"),
|
|
@@ -11682,7 +13039,7 @@ var checks = {
|
|
|
11682
13039
|
const tooShort = options.minCharacters !== void 0 && characters < options.minCharacters || options.minWords !== void 0 && words < options.minWords;
|
|
11683
13040
|
const tooLong = options.maxCharacters !== void 0 && characters > options.maxCharacters || options.maxWords !== void 0 && words > options.maxWords;
|
|
11684
13041
|
return answer.length === 0 || tooShort || tooLong ? [
|
|
11685
|
-
|
|
13042
|
+
fail2(
|
|
11686
13043
|
"eval.answerLengthBounds",
|
|
11687
13044
|
"Answer length fell outside required bounds.",
|
|
11688
13045
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11705,7 +13062,7 @@ var checks = {
|
|
|
11705
13062
|
const answer = answers.map((field) => field.text).join(" ").toLowerCase();
|
|
11706
13063
|
const matches = banned.filter((phrase) => answer.includes(phrase));
|
|
11707
13064
|
return matches.length > 0 ? [
|
|
11708
|
-
|
|
13065
|
+
fail2(
|
|
11709
13066
|
"eval.bannedUnsupportedPhrases",
|
|
11710
13067
|
"Answer contained banned unsupported-answer phrasing.",
|
|
11711
13068
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -12083,7 +13440,7 @@ function invalidArgumentResult(command, error) {
|
|
|
12083
13440
|
});
|
|
12084
13441
|
}
|
|
12085
13442
|
function buildSafetyRules(options) {
|
|
12086
|
-
const
|
|
13443
|
+
const maxStringLength2 = parseLimit3(options.maxStringLength, "--max-string-length") ?? DEFAULT_MAX_STRING_LENGTH;
|
|
12087
13444
|
const maxArrayLength = parseLimit3(options.maxArrayLength, "--max-array-length") ?? DEFAULT_MAX_ARRAY_LENGTH;
|
|
12088
13445
|
const maxObjectKeys = parseLimit3(options.maxObjectKeys, "--max-object-keys") ?? DEFAULT_MAX_OBJECT_KEYS;
|
|
12089
13446
|
const maxSerializedBytes = parseLimit3(options.maxSerializedBytes, "--max-serialized-bytes") ?? DEFAULT_MAX_SERIALIZED_BYTES;
|
|
@@ -12092,7 +13449,7 @@ function buildSafetyRules(options) {
|
|
|
12092
13449
|
createSafetyRedactionRule(),
|
|
12093
13450
|
createSafetySecretPatternRule(),
|
|
12094
13451
|
createSafetyOversizedAttributeRule({
|
|
12095
|
-
maxStringLength,
|
|
13452
|
+
maxStringLength: maxStringLength2,
|
|
12096
13453
|
maxArrayLength,
|
|
12097
13454
|
maxObjectKeys,
|
|
12098
13455
|
maxSerializedBytes
|
|
@@ -12973,7 +14330,18 @@ function createCliProgram() {
|
|
|
12973
14330
|
]).option("--allowed-model <model>", "allow an LLM model (repeatable)", (value, previous = []) => [
|
|
12974
14331
|
...previous,
|
|
12975
14332
|
value
|
|
12976
|
-
]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").
|
|
14333
|
+
]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").option("--session <id>", "check all runs in a workflow session (requires --dir)").option("--group <id>", "check all runs sharing a groupId (requires --dir)").option(
|
|
14334
|
+
"--correlate-group",
|
|
14335
|
+
"when using --session, also match synthetic group: session keys"
|
|
14336
|
+
).option(
|
|
14337
|
+
"--guardrails <rule>",
|
|
14338
|
+
"run optional guardrail rules (repeatable): banned-phrase, pii-leak, prompt-injection, ...",
|
|
14339
|
+
(value, previous = []) => [...previous, value]
|
|
14340
|
+
).option(
|
|
14341
|
+
"--circuit <rule>",
|
|
14342
|
+
"run optional circuit rules (repeatable): same-tool-repetition, max-retries, ...",
|
|
14343
|
+
(value, previous = []) => [...previous, value]
|
|
14344
|
+
).action((target, opts) => {
|
|
12977
14345
|
runCommand(() => checkCommand(target, opts));
|
|
12978
14346
|
});
|
|
12979
14347
|
program.command("eval").description("Run deterministic local evals against a trace").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
|
|
@@ -13077,9 +14445,21 @@ function createCliProgram() {
|
|
|
13077
14445
|
).option("--kind <kind>", "filter by step kind/type (llm, tool, logic, \u2026)").option("--type <type>", "alias for --kind on manual trace step type").option("--name <query>", "substring match on run or step name").option("--tool <query>", "substring match on tool step name or metadata.toolName").option(
|
|
13078
14446
|
"--duration <expr>",
|
|
13079
14447
|
"duration filter on run or step (e.g. >5s, >=500ms)"
|
|
13080
|
-
).option("--limit <number>", "max results (default 50)").option("--
|
|
14448
|
+
).option("--limit <number>", "max results (default 50)").option("--session <id>", "limit search to one workflow session").option(
|
|
14449
|
+
"--correlate-group",
|
|
14450
|
+
"when using --session, also match synthetic group: keys"
|
|
14451
|
+
).option("--json", "print results as JSON").action((opts) => {
|
|
13081
14452
|
runCommand(() => searchCommand(opts));
|
|
13082
14453
|
});
|
|
14454
|
+
program.command("sessions").description("List workflow sessions grouped from local trace metadata (read-only)").option("--dir <path>", "trace directory").option(
|
|
14455
|
+
"--correlate-group",
|
|
14456
|
+
"treat shared groupId as a synthetic session when sessionId is absent"
|
|
14457
|
+
).option("--json", "print sessions index as JSON").action((opts) => {
|
|
14458
|
+
runCommand(() => sessionsCommand(opts));
|
|
14459
|
+
});
|
|
14460
|
+
program.command("session").description("Inspect one workflow session: runs, handoffs, retries (read-only)").argument("<session-id>", "session id (from sessions output)").option("--dir <path>", "trace directory").option("--timeline", "include per-run timelines").option("--critical-path", "include critical path section").option("--diagnostics", "include ambiguity warnings").option("--json", "print session view as JSON").action((sessionId, opts) => {
|
|
14461
|
+
runCommand(() => sessionCommand(sessionId, opts));
|
|
14462
|
+
});
|
|
13083
14463
|
program.command("what").description("Concise human-readable summary of a single run (local JSONL)").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").option("--json", "print structured summary as JSON").option("--no-correlation", "omit correlation metadata from human output").action((runId, opts) => {
|
|
13084
14464
|
runCommand(() => whatCommand(runId, opts));
|
|
13085
14465
|
});
|