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
|
@@ -4,7 +4,7 @@ import path13 from 'path';
|
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
5
5
|
import { Command, Option } from 'commander';
|
|
6
6
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
7
|
-
import crypto, { webcrypto } from 'crypto';
|
|
7
|
+
import crypto, { webcrypto, createHash } from 'crypto';
|
|
8
8
|
import { unlink, stat, mkdir, writeFile, appendFile, readdir, readFile, access, open } from 'fs/promises';
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import process2, { stdin, stdout } from 'process';
|
|
@@ -12,7 +12,7 @@ import tty from 'tty';
|
|
|
12
12
|
import { createInterface } from 'readline';
|
|
13
13
|
|
|
14
14
|
// package.json
|
|
15
|
-
var version = "2.
|
|
15
|
+
var version = "2.5.0";
|
|
16
16
|
|
|
17
17
|
// packages/core/src/correlation-metadata.ts
|
|
18
18
|
var TRACE_CORRELATION_KEYS = [
|
|
@@ -1429,18 +1429,18 @@ var proto = Object.defineProperties(() => {
|
|
|
1429
1429
|
}
|
|
1430
1430
|
}
|
|
1431
1431
|
});
|
|
1432
|
-
var createStyler = (
|
|
1432
|
+
var createStyler = (open3, close, parent) => {
|
|
1433
1433
|
let openAll;
|
|
1434
1434
|
let closeAll;
|
|
1435
1435
|
if (parent === void 0) {
|
|
1436
|
-
openAll =
|
|
1436
|
+
openAll = open3;
|
|
1437
1437
|
closeAll = close;
|
|
1438
1438
|
} else {
|
|
1439
|
-
openAll = parent.openAll +
|
|
1439
|
+
openAll = parent.openAll + open3;
|
|
1440
1440
|
closeAll = close + parent.closeAll;
|
|
1441
1441
|
}
|
|
1442
1442
|
return {
|
|
1443
|
-
open:
|
|
1443
|
+
open: open3,
|
|
1444
1444
|
close,
|
|
1445
1445
|
openAll,
|
|
1446
1446
|
closeAll,
|
|
@@ -1505,15 +1505,15 @@ function getStatusIcon(status) {
|
|
|
1505
1505
|
if (status === "error") return source_default.red("\u2716");
|
|
1506
1506
|
return source_default.yellow("\u23F3");
|
|
1507
1507
|
}
|
|
1508
|
-
function renderStepLine(name,
|
|
1508
|
+
function renderStepLine(name, durationMs2, status, depth) {
|
|
1509
1509
|
try {
|
|
1510
1510
|
const nm = formatTerminalName(name);
|
|
1511
1511
|
const ind = getIndent(depth ?? 0);
|
|
1512
|
-
if (status === "running" &&
|
|
1512
|
+
if (status === "running" && durationMs2 === void 0) {
|
|
1513
1513
|
return `${ind}${source_default.yellow("\u23F3")} ${nm}`;
|
|
1514
1514
|
}
|
|
1515
|
-
const hasDur =
|
|
1516
|
-
const dur = hasDur ? formatDuration2(
|
|
1515
|
+
const hasDur = durationMs2 !== void 0 && Number.isFinite(durationMs2);
|
|
1516
|
+
const dur = hasDur ? formatDuration2(durationMs2) : void 0;
|
|
1517
1517
|
if (status === "running") {
|
|
1518
1518
|
return dur !== void 0 ? `${ind}${source_default.yellow("\u23F3")} ${nm} (${dur})` : `${ind}${source_default.yellow("\u23F3")} ${nm}`;
|
|
1519
1519
|
}
|
|
@@ -1666,14 +1666,14 @@ async function extractMetadata(filePath, _quickScan) {
|
|
|
1666
1666
|
} else {
|
|
1667
1667
|
status = "unknown";
|
|
1668
1668
|
}
|
|
1669
|
-
const
|
|
1669
|
+
const durationMs2 = explicitDurationMs ?? (startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt ? endedAt - startedAt : void 0);
|
|
1670
1670
|
return {
|
|
1671
1671
|
runId: resolvedRunId,
|
|
1672
1672
|
name,
|
|
1673
1673
|
status,
|
|
1674
1674
|
startedAt,
|
|
1675
1675
|
endedAt,
|
|
1676
|
-
durationMs,
|
|
1676
|
+
durationMs: durationMs2,
|
|
1677
1677
|
eventCount: parsedTrace.sourceEventCount,
|
|
1678
1678
|
filePath,
|
|
1679
1679
|
fileSize: stats.size,
|
|
@@ -1694,7 +1694,7 @@ function buildRunSummary(events) {
|
|
|
1694
1694
|
const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
|
|
1695
1695
|
const name = typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0;
|
|
1696
1696
|
const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
|
|
1697
|
-
const
|
|
1697
|
+
const durationMs2 = lastCompleted && isFiniteNumber(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
1698
1698
|
started && isFiniteNumber(started.startTime) ? started.startTime : void 0;
|
|
1699
1699
|
const steps = /* @__PURE__ */ new Map();
|
|
1700
1700
|
for (const e of events) {
|
|
@@ -1784,7 +1784,7 @@ function buildRunSummary(events) {
|
|
|
1784
1784
|
runId,
|
|
1785
1785
|
name,
|
|
1786
1786
|
status,
|
|
1787
|
-
durationMs,
|
|
1787
|
+
durationMs: durationMs2,
|
|
1788
1788
|
totalSteps,
|
|
1789
1789
|
llmSteps,
|
|
1790
1790
|
toolSteps,
|
|
@@ -2529,10 +2529,12 @@ async function searchTraces(metas, options) {
|
|
|
2529
2529
|
durationFilter = parseDurationFilter(options.duration);
|
|
2530
2530
|
}
|
|
2531
2531
|
const limit = options.limit ?? 50;
|
|
2532
|
+
const sessionId = options.session?.trim();
|
|
2532
2533
|
const hasContentFilter = Boolean(
|
|
2533
2534
|
options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter
|
|
2534
2535
|
);
|
|
2535
2536
|
const results = [];
|
|
2537
|
+
const sessionLabel = sessionId && sessionId !== "" ? sessionId : void 0;
|
|
2536
2538
|
if (!hasContentFilter) {
|
|
2537
2539
|
for (const m of filtered) {
|
|
2538
2540
|
results.push({
|
|
@@ -2541,9 +2543,10 @@ async function searchTraces(metas, options) {
|
|
|
2541
2543
|
runStatus: m.status,
|
|
2542
2544
|
timestamp: m.startedAt,
|
|
2543
2545
|
durationMs: m.durationMs,
|
|
2544
|
-
matchReason: "trace in directory",
|
|
2545
|
-
matchedFields: ["run"],
|
|
2546
|
-
filePath: m.filePath
|
|
2546
|
+
matchReason: sessionLabel ? `trace in session ${sessionLabel}` : "trace in directory",
|
|
2547
|
+
matchedFields: sessionLabel ? ["run", "session"] : ["run"],
|
|
2548
|
+
filePath: m.filePath,
|
|
2549
|
+
...sessionLabel ? { sessionId: sessionLabel } : {}
|
|
2547
2550
|
});
|
|
2548
2551
|
}
|
|
2549
2552
|
return results.slice(0, limit);
|
|
@@ -2682,6 +2685,478 @@ async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
|
|
|
2682
2685
|
}
|
|
2683
2686
|
return metas;
|
|
2684
2687
|
}
|
|
2688
|
+
|
|
2689
|
+
// packages/core/src/sessions/metadata.ts
|
|
2690
|
+
function isNonEmptyString3(value) {
|
|
2691
|
+
return typeof value === "string" && value.trim() !== "";
|
|
2692
|
+
}
|
|
2693
|
+
function finitePositiveInt(value) {
|
|
2694
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
2695
|
+
return void 0;
|
|
2696
|
+
}
|
|
2697
|
+
return Math.trunc(value);
|
|
2698
|
+
}
|
|
2699
|
+
function extractSessionWorkflowMetadata(record) {
|
|
2700
|
+
if (!record) return void 0;
|
|
2701
|
+
const out = {};
|
|
2702
|
+
let found = false;
|
|
2703
|
+
const assignString = (key, value) => {
|
|
2704
|
+
if (isNonEmptyString3(value)) {
|
|
2705
|
+
out[key] = value.trim();
|
|
2706
|
+
found = true;
|
|
2707
|
+
}
|
|
2708
|
+
};
|
|
2709
|
+
assignString("sessionId", record.sessionId);
|
|
2710
|
+
assignString("conversationId", record.conversationId);
|
|
2711
|
+
assignString("groupId", record.groupId);
|
|
2712
|
+
assignString("parentGroupId", record.parentGroupId);
|
|
2713
|
+
assignString("retryOf", record.retryOf);
|
|
2714
|
+
assignString("retryReason", record.retryReason);
|
|
2715
|
+
assignString("handoffFrom", record.handoffFrom);
|
|
2716
|
+
assignString("handoffTo", record.handoffTo);
|
|
2717
|
+
assignString("subAgentId", record.subAgentId);
|
|
2718
|
+
assignString("subAgentName", record.subAgentName);
|
|
2719
|
+
assignString("jobId", record.jobId);
|
|
2720
|
+
assignString("queueName", record.queueName);
|
|
2721
|
+
assignString("workflowName", record.workflowName);
|
|
2722
|
+
assignString("workflowStep", record.workflowStep);
|
|
2723
|
+
assignString("toolCallId", record.toolCallId);
|
|
2724
|
+
assignString("mcpToolCallId", record.mcpToolCallId);
|
|
2725
|
+
assignString("linkedStepId", record.linkedStepId);
|
|
2726
|
+
assignString("correlationId", record.correlationId);
|
|
2727
|
+
assignString("requestId", record.requestId);
|
|
2728
|
+
assignString("decisionId", record.decisionId);
|
|
2729
|
+
const attempt = finitePositiveInt(record.attempt);
|
|
2730
|
+
if (attempt !== void 0) {
|
|
2731
|
+
out.attempt = attempt;
|
|
2732
|
+
found = true;
|
|
2733
|
+
}
|
|
2734
|
+
return found ? out : void 0;
|
|
2735
|
+
}
|
|
2736
|
+
function sessionKeyForRun(meta, options) {
|
|
2737
|
+
if (meta?.sessionId) return meta.sessionId;
|
|
2738
|
+
if (options?.correlateByGroupId && meta?.groupId) {
|
|
2739
|
+
return `group:${meta.groupId}`;
|
|
2740
|
+
}
|
|
2741
|
+
return void 0;
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
// packages/core/src/sessions/load.ts
|
|
2745
|
+
async function enrichSessionRunRecord(meta) {
|
|
2746
|
+
let metadata;
|
|
2747
|
+
try {
|
|
2748
|
+
const events = await readTraceEventsFromFile(meta.filePath);
|
|
2749
|
+
for (const event of events) {
|
|
2750
|
+
if (event.event !== "run_started") continue;
|
|
2751
|
+
if (event.metadata && typeof event.metadata === "object") {
|
|
2752
|
+
metadata = event.metadata;
|
|
2753
|
+
}
|
|
2754
|
+
break;
|
|
2755
|
+
}
|
|
2756
|
+
} catch {
|
|
2757
|
+
}
|
|
2758
|
+
return {
|
|
2759
|
+
runId: meta.runId,
|
|
2760
|
+
name: meta.name,
|
|
2761
|
+
status: meta.status,
|
|
2762
|
+
startedAt: meta.startedAt,
|
|
2763
|
+
endedAt: meta.endedAt,
|
|
2764
|
+
durationMs: meta.durationMs,
|
|
2765
|
+
filePath: meta.filePath,
|
|
2766
|
+
metadata
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
async function loadSessionRunRecords(metas) {
|
|
2770
|
+
const out = [];
|
|
2771
|
+
for (const meta of metas) {
|
|
2772
|
+
out.push(await enrichSessionRunRecord(meta));
|
|
2773
|
+
}
|
|
2774
|
+
return out;
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// packages/core/src/sessions/scope.ts
|
|
2778
|
+
function filterMetasBySessionScope(metas, records, options) {
|
|
2779
|
+
const sessionId = options.sessionId?.trim();
|
|
2780
|
+
const groupId = options.groupId?.trim();
|
|
2781
|
+
const warnings = [];
|
|
2782
|
+
if (sessionId) {
|
|
2783
|
+
const index = buildSessionIndex(records, {
|
|
2784
|
+
correlateByGroupId: options.correlateByGroupId === true
|
|
2785
|
+
});
|
|
2786
|
+
warnings.push(...index.warnings);
|
|
2787
|
+
const session = index.sessions.find((item) => item.sessionId === sessionId);
|
|
2788
|
+
if (!session) {
|
|
2789
|
+
return {
|
|
2790
|
+
metas: [],
|
|
2791
|
+
scopeLabel: sessionId,
|
|
2792
|
+
scopeKind: "session",
|
|
2793
|
+
runIds: [],
|
|
2794
|
+
warnings,
|
|
2795
|
+
notFound: true
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
const runIdSet = new Set(session.runIds);
|
|
2799
|
+
const filtered = metas.filter((meta) => runIdSet.has(meta.runId));
|
|
2800
|
+
return {
|
|
2801
|
+
metas: filtered,
|
|
2802
|
+
scopeLabel: sessionId,
|
|
2803
|
+
scopeKind: "session",
|
|
2804
|
+
runIds: session.runIds,
|
|
2805
|
+
warnings,
|
|
2806
|
+
notFound: false
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
if (groupId) {
|
|
2810
|
+
const runIds = records.filter((run) => extractSessionWorkflowMetadata(run.metadata)?.groupId === groupId).map((run) => run.runId).sort();
|
|
2811
|
+
if (runIds.length === 0) {
|
|
2812
|
+
return {
|
|
2813
|
+
metas: [],
|
|
2814
|
+
scopeLabel: groupId,
|
|
2815
|
+
scopeKind: "group",
|
|
2816
|
+
runIds: [],
|
|
2817
|
+
warnings,
|
|
2818
|
+
notFound: true
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
const runIdSet = new Set(runIds);
|
|
2822
|
+
return {
|
|
2823
|
+
metas: metas.filter((meta) => runIdSet.has(meta.runId)),
|
|
2824
|
+
scopeLabel: groupId,
|
|
2825
|
+
scopeKind: "group",
|
|
2826
|
+
runIds,
|
|
2827
|
+
warnings,
|
|
2828
|
+
notFound: false
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
return {
|
|
2832
|
+
metas: [...metas],
|
|
2833
|
+
scopeLabel: "",
|
|
2834
|
+
scopeKind: "session",
|
|
2835
|
+
runIds: [],
|
|
2836
|
+
warnings,
|
|
2837
|
+
notFound: false
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// packages/core/src/sessions/checks.ts
|
|
2842
|
+
function emptySummary() {
|
|
2843
|
+
return { passed: 0, failed: 0, warnings: 0, errors: 0 };
|
|
2844
|
+
}
|
|
2845
|
+
function mergeSummary(target, source) {
|
|
2846
|
+
return {
|
|
2847
|
+
passed: target.passed + source.passed,
|
|
2848
|
+
failed: target.failed + source.failed,
|
|
2849
|
+
warnings: target.warnings + source.warnings,
|
|
2850
|
+
errors: target.errors + source.errors
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
function sessionDiagnostic(code, message) {
|
|
2854
|
+
return { code, message, severity: "error" };
|
|
2855
|
+
}
|
|
2856
|
+
function aggregateSessionCheckResults(perRun, scope) {
|
|
2857
|
+
if (scope.notFound) {
|
|
2858
|
+
return {
|
|
2859
|
+
ok: false,
|
|
2860
|
+
status: "error",
|
|
2861
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
2862
|
+
scopeKind: scope.scopeKind,
|
|
2863
|
+
scopeLabel: scope.scopeLabel,
|
|
2864
|
+
runIds: [],
|
|
2865
|
+
runResults: [],
|
|
2866
|
+
summary: { ...emptySummary(), errors: 1 },
|
|
2867
|
+
findings: [],
|
|
2868
|
+
diagnostics: [
|
|
2869
|
+
sessionDiagnostic(
|
|
2870
|
+
"AI_CHECK_INVALID_ARGUMENTS",
|
|
2871
|
+
`${scope.scopeKind} not found: ${scope.scopeLabel}`
|
|
2872
|
+
)
|
|
2873
|
+
],
|
|
2874
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
if (scope.empty || perRun.length === 0) {
|
|
2878
|
+
return {
|
|
2879
|
+
ok: false,
|
|
2880
|
+
status: "error",
|
|
2881
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
2882
|
+
scopeKind: scope.scopeKind,
|
|
2883
|
+
scopeLabel: scope.scopeLabel,
|
|
2884
|
+
runIds: scope.runIds,
|
|
2885
|
+
runResults: [],
|
|
2886
|
+
summary: { ...emptySummary(), errors: 1 },
|
|
2887
|
+
findings: [],
|
|
2888
|
+
diagnostics: [
|
|
2889
|
+
sessionDiagnostic(
|
|
2890
|
+
"AI_CHECK_TRACE_UNREADABLE",
|
|
2891
|
+
`No readable traces in ${scope.scopeKind}: ${scope.scopeLabel}`
|
|
2892
|
+
)
|
|
2893
|
+
],
|
|
2894
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
let summary = emptySummary();
|
|
2898
|
+
const findings = [];
|
|
2899
|
+
const diagnostics = [];
|
|
2900
|
+
const runResults = [];
|
|
2901
|
+
for (const result of perRun) {
|
|
2902
|
+
summary = mergeSummary(summary, result.summary);
|
|
2903
|
+
findings.push(...result.findings);
|
|
2904
|
+
diagnostics.push(...result.diagnostics);
|
|
2905
|
+
if (result.runId) {
|
|
2906
|
+
runResults.push({ runId: result.runId, status: result.status });
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
runResults.sort((a, b) => a.runId.localeCompare(b.runId));
|
|
2910
|
+
findings.sort((a, b) => {
|
|
2911
|
+
const runCmp = (a.evidence[0]?.runId ?? "").localeCompare(
|
|
2912
|
+
b.evidence[0]?.runId ?? ""
|
|
2913
|
+
);
|
|
2914
|
+
if (runCmp !== 0) return runCmp;
|
|
2915
|
+
return a.ruleId.localeCompare(b.ruleId);
|
|
2916
|
+
});
|
|
2917
|
+
const hasErrors = diagnostics.some((item) => item.severity === "error");
|
|
2918
|
+
const status = hasErrors ? "error" : summary.failed > 0 ? "fail" : "pass";
|
|
2919
|
+
return {
|
|
2920
|
+
ok: status === "pass",
|
|
2921
|
+
status,
|
|
2922
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
2923
|
+
scopeKind: scope.scopeKind,
|
|
2924
|
+
scopeLabel: scope.scopeLabel,
|
|
2925
|
+
runIds: scope.runIds,
|
|
2926
|
+
runResults,
|
|
2927
|
+
summary,
|
|
2928
|
+
findings,
|
|
2929
|
+
diagnostics,
|
|
2930
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
// packages/core/src/sessions/index.ts
|
|
2935
|
+
function compareRuns(a, b) {
|
|
2936
|
+
const aStart = a.startedAt ?? 0;
|
|
2937
|
+
const bStart = b.startedAt ?? 0;
|
|
2938
|
+
if (aStart !== bStart) return aStart - bStart;
|
|
2939
|
+
return a.runId.localeCompare(b.runId);
|
|
2940
|
+
}
|
|
2941
|
+
function buildGroups(runIds, metaByRunId) {
|
|
2942
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
2943
|
+
for (const runId of runIds) {
|
|
2944
|
+
const groupId = metaByRunId.get(runId)?.groupId;
|
|
2945
|
+
if (!groupId) continue;
|
|
2946
|
+
const existing = byGroup.get(groupId);
|
|
2947
|
+
if (existing) {
|
|
2948
|
+
existing.runIds.push(runId);
|
|
2949
|
+
} else {
|
|
2950
|
+
byGroup.set(groupId, {
|
|
2951
|
+
groupId,
|
|
2952
|
+
parentGroupId: metaByRunId.get(runId)?.parentGroupId,
|
|
2953
|
+
runIds: [runId]
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
return [...byGroup.values()].map((group) => ({
|
|
2958
|
+
...group,
|
|
2959
|
+
runIds: [...group.runIds].sort((a, b) => a.localeCompare(b))
|
|
2960
|
+
}));
|
|
2961
|
+
}
|
|
2962
|
+
function buildHandoffs(runIds, metaByRunId, warnings, sessionId) {
|
|
2963
|
+
const edges = [];
|
|
2964
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2965
|
+
const pushEdge = (edge) => {
|
|
2966
|
+
const key = `${edge.from}->${edge.to}:${edge.confidence}`;
|
|
2967
|
+
if (seen.has(key)) return;
|
|
2968
|
+
seen.add(key);
|
|
2969
|
+
edges.push(edge);
|
|
2970
|
+
};
|
|
2971
|
+
for (const runId of runIds) {
|
|
2972
|
+
const meta = metaByRunId.get(runId);
|
|
2973
|
+
if (!meta) continue;
|
|
2974
|
+
if (meta.handoffFrom && meta.handoffTo) {
|
|
2975
|
+
pushEdge({
|
|
2976
|
+
from: meta.handoffFrom,
|
|
2977
|
+
to: meta.handoffTo,
|
|
2978
|
+
source: "manual",
|
|
2979
|
+
confidence: "explicit"
|
|
2980
|
+
});
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2983
|
+
if (meta.handoffFrom) {
|
|
2984
|
+
pushEdge({
|
|
2985
|
+
from: meta.handoffFrom,
|
|
2986
|
+
to: runId,
|
|
2987
|
+
source: "manual",
|
|
2988
|
+
confidence: "explicit"
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
if (meta.handoffTo) {
|
|
2992
|
+
pushEdge({
|
|
2993
|
+
from: runId,
|
|
2994
|
+
to: meta.handoffTo,
|
|
2995
|
+
source: "manual",
|
|
2996
|
+
confidence: "explicit"
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2999
|
+
if (meta.subAgentId && meta.parentGroupId && !meta.handoffFrom && !meta.handoffTo) {
|
|
3000
|
+
pushEdge({
|
|
3001
|
+
from: meta.parentGroupId,
|
|
3002
|
+
to: meta.subAgentId,
|
|
3003
|
+
source: "inferred",
|
|
3004
|
+
confidence: "correlated"
|
|
3005
|
+
});
|
|
3006
|
+
warnings.push({
|
|
3007
|
+
code: "ambiguous-handoff-endpoints",
|
|
3008
|
+
message: "Handoff inferred from parentGroupId and subAgentId without explicit handoffFrom/handoffTo.",
|
|
3009
|
+
runId,
|
|
3010
|
+
sessionId
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
return edges.sort((a, b) => {
|
|
3015
|
+
const from = a.from.localeCompare(b.from);
|
|
3016
|
+
if (from !== 0) return from;
|
|
3017
|
+
return a.to.localeCompare(b.to);
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
function buildRetries(runIds, metaByRunId, warnings, sessionId) {
|
|
3021
|
+
const retries = [];
|
|
3022
|
+
for (const runId of runIds) {
|
|
3023
|
+
const meta = metaByRunId.get(runId);
|
|
3024
|
+
if (!meta) continue;
|
|
3025
|
+
if (meta.retryOf) {
|
|
3026
|
+
retries.push({
|
|
3027
|
+
runId,
|
|
3028
|
+
retryOf: meta.retryOf,
|
|
3029
|
+
attempt: meta.attempt,
|
|
3030
|
+
source: "manual",
|
|
3031
|
+
confidence: "explicit"
|
|
3032
|
+
});
|
|
3033
|
+
continue;
|
|
3034
|
+
}
|
|
3035
|
+
if (meta.attempt !== void 0 && meta.attempt > 1) {
|
|
3036
|
+
retries.push({
|
|
3037
|
+
runId,
|
|
3038
|
+
attempt: meta.attempt,
|
|
3039
|
+
source: "inferred",
|
|
3040
|
+
confidence: "correlated"
|
|
3041
|
+
});
|
|
3042
|
+
warnings.push({
|
|
3043
|
+
code: "ambiguous-retry-link",
|
|
3044
|
+
message: "attempt > 1 without retryOf; retry link is correlated only.",
|
|
3045
|
+
runId,
|
|
3046
|
+
sessionId
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
return retries.sort((a, b) => a.runId.localeCompare(b.runId));
|
|
3051
|
+
}
|
|
3052
|
+
function buildCriticalPath(runs, handoffs) {
|
|
3053
|
+
const runById = new Map(runs.map((run) => [run.runId, run]));
|
|
3054
|
+
const explicitTargets = new Set(
|
|
3055
|
+
handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.to)
|
|
3056
|
+
);
|
|
3057
|
+
const explicitSources = new Set(
|
|
3058
|
+
handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
|
|
3059
|
+
);
|
|
3060
|
+
const ordered = [...runs].sort(compareRuns);
|
|
3061
|
+
const path15 = [];
|
|
3062
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3063
|
+
const pushRun = (run, confidence, source) => {
|
|
3064
|
+
if (visited.has(run.runId)) return;
|
|
3065
|
+
visited.add(run.runId);
|
|
3066
|
+
path15.push({
|
|
3067
|
+
runId: run.runId,
|
|
3068
|
+
name: run.name,
|
|
3069
|
+
startedAt: run.startedAt,
|
|
3070
|
+
durationMs: run.durationMs,
|
|
3071
|
+
confidence,
|
|
3072
|
+
source
|
|
3073
|
+
});
|
|
3074
|
+
};
|
|
3075
|
+
for (const edge of handoffs) {
|
|
3076
|
+
if (edge.confidence !== "explicit") continue;
|
|
3077
|
+
const fromRun = [...runById.values()].find(
|
|
3078
|
+
(run) => run.runId === edge.from || metaRunIdMatches(run, edge.from, runById)
|
|
3079
|
+
);
|
|
3080
|
+
const toRun = [...runById.values()].find(
|
|
3081
|
+
(run) => run.runId === edge.to || metaRunIdMatches(run, edge.to, runById)
|
|
3082
|
+
);
|
|
3083
|
+
if (fromRun) pushRun(fromRun, "explicit", "manual");
|
|
3084
|
+
if (toRun) pushRun(toRun, "explicit", "manual");
|
|
3085
|
+
}
|
|
3086
|
+
for (const run of ordered) {
|
|
3087
|
+
if (visited.has(run.runId)) continue;
|
|
3088
|
+
const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
|
|
3089
|
+
pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
|
|
3090
|
+
}
|
|
3091
|
+
return path15;
|
|
3092
|
+
}
|
|
3093
|
+
function metaRunIdMatches(run, token, runById) {
|
|
3094
|
+
const meta = extractSessionWorkflowMetadata(run.metadata);
|
|
3095
|
+
return meta?.subAgentId === token || meta?.groupId === token || runById.has(token);
|
|
3096
|
+
}
|
|
3097
|
+
function buildSessionIndex(inputRuns, options = {}) {
|
|
3098
|
+
const warnings = [];
|
|
3099
|
+
const runs = [...inputRuns].sort(compareRuns);
|
|
3100
|
+
const metaByRunId = /* @__PURE__ */ new Map();
|
|
3101
|
+
for (const run of runs) {
|
|
3102
|
+
metaByRunId.set(run.runId, extractSessionWorkflowMetadata(run.metadata));
|
|
3103
|
+
}
|
|
3104
|
+
const sessionsByKey = /* @__PURE__ */ new Map();
|
|
3105
|
+
const unscopedRunIds = [];
|
|
3106
|
+
for (const run of runs) {
|
|
3107
|
+
const meta = metaByRunId.get(run.runId);
|
|
3108
|
+
const key = sessionKeyForRun(meta, {
|
|
3109
|
+
correlateByGroupId: options.correlateByGroupId === true
|
|
3110
|
+
});
|
|
3111
|
+
if (!key) {
|
|
3112
|
+
unscopedRunIds.push(run.runId);
|
|
3113
|
+
continue;
|
|
3114
|
+
}
|
|
3115
|
+
const bucket = sessionsByKey.get(key) ?? [];
|
|
3116
|
+
bucket.push(run);
|
|
3117
|
+
sessionsByKey.set(key, bucket);
|
|
3118
|
+
}
|
|
3119
|
+
const sessions = [...sessionsByKey.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([sessionId, sessionRuns]) => {
|
|
3120
|
+
const runIds = sessionRuns.map((run) => run.runId).sort();
|
|
3121
|
+
const handoffs = buildHandoffs(runIds, metaByRunId, warnings, sessionId);
|
|
3122
|
+
const retries = buildRetries(runIds, metaByRunId, warnings, sessionId);
|
|
3123
|
+
const groups = buildGroups(runIds, metaByRunId);
|
|
3124
|
+
const criticalPath = buildCriticalPath(sessionRuns, handoffs);
|
|
3125
|
+
const confidences = new Set(handoffs.map((edge) => edge.confidence));
|
|
3126
|
+
if (confidences.has("explicit") && confidences.has("correlated")) {
|
|
3127
|
+
warnings.push({
|
|
3128
|
+
code: "mixed-confidence-group",
|
|
3129
|
+
message: "Session aggregates explicit and correlated handoff edges.",
|
|
3130
|
+
sessionId
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
return {
|
|
3134
|
+
sessionId,
|
|
3135
|
+
runIds,
|
|
3136
|
+
groups,
|
|
3137
|
+
handoffs,
|
|
3138
|
+
retries,
|
|
3139
|
+
criticalPath
|
|
3140
|
+
};
|
|
3141
|
+
});
|
|
3142
|
+
if (sessions.length === 0 && runs.length > 0) {
|
|
3143
|
+
warnings.push({
|
|
3144
|
+
code: "missing-session-id",
|
|
3145
|
+
message: "No sessionId (or correlated groupId) found on input runs."
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
warnings.sort((a, b) => {
|
|
3149
|
+
const code = a.code.localeCompare(b.code);
|
|
3150
|
+
if (code !== 0) return code;
|
|
3151
|
+
return (a.runId ?? "").localeCompare(b.runId ?? "");
|
|
3152
|
+
});
|
|
3153
|
+
return {
|
|
3154
|
+
runs,
|
|
3155
|
+
sessions,
|
|
3156
|
+
unscopedRunIds: unscopedRunIds.sort(),
|
|
3157
|
+
warnings
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
2685
3160
|
var KNOWN_EVENTS = /* @__PURE__ */ new Set([
|
|
2686
3161
|
"run_started",
|
|
2687
3162
|
"run_completed",
|
|
@@ -2740,10 +3215,10 @@ function statusIcon(status) {
|
|
|
2740
3215
|
if (status === "running") return "\u23F3";
|
|
2741
3216
|
return "?";
|
|
2742
3217
|
}
|
|
2743
|
-
function durationCell(status,
|
|
3218
|
+
function durationCell(status, durationMs2) {
|
|
2744
3219
|
if (status === "running" || status === "unknown") return "-";
|
|
2745
|
-
if (
|
|
2746
|
-
return formatDuration2(
|
|
3220
|
+
if (durationMs2 !== void 0 && Number.isFinite(durationMs2)) {
|
|
3221
|
+
return formatDuration2(durationMs2);
|
|
2747
3222
|
}
|
|
2748
3223
|
return "-";
|
|
2749
3224
|
}
|
|
@@ -3400,7 +3875,7 @@ var TreeBuilder = class {
|
|
|
3400
3875
|
const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
|
|
3401
3876
|
const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
|
|
3402
3877
|
const status = computeRunStatus(sorted);
|
|
3403
|
-
const
|
|
3878
|
+
const durationMs2 = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
|
|
3404
3879
|
const name = sorted.find((e) => e.kind === "RUN")?.name;
|
|
3405
3880
|
out.push({
|
|
3406
3881
|
runId,
|
|
@@ -3408,7 +3883,7 @@ var TreeBuilder = class {
|
|
|
3408
3883
|
status,
|
|
3409
3884
|
startedAt,
|
|
3410
3885
|
endedAt: status === "running" ? void 0 : endedAt,
|
|
3411
|
-
durationMs,
|
|
3886
|
+
durationMs: durationMs2,
|
|
3412
3887
|
children: roots,
|
|
3413
3888
|
metadata: {
|
|
3414
3889
|
totalEvents: sorted.length,
|
|
@@ -3703,13 +4178,13 @@ function persistedEventsForParsedTrace(parsed) {
|
|
|
3703
4178
|
function isRecord8(value) {
|
|
3704
4179
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3705
4180
|
}
|
|
3706
|
-
function
|
|
4181
|
+
function isNonEmptyString4(value) {
|
|
3707
4182
|
return typeof value === "string" && value.trim() !== "";
|
|
3708
4183
|
}
|
|
3709
4184
|
function readStringField(record, keys) {
|
|
3710
4185
|
for (const key of keys) {
|
|
3711
4186
|
const value = record[key];
|
|
3712
|
-
if (
|
|
4187
|
+
if (isNonEmptyString4(value)) return value;
|
|
3713
4188
|
}
|
|
3714
4189
|
return void 0;
|
|
3715
4190
|
}
|
|
@@ -3843,7 +4318,7 @@ function parseUnixNanoToIso(value) {
|
|
|
3843
4318
|
return void 0;
|
|
3844
4319
|
}
|
|
3845
4320
|
function parseIsoTime(value) {
|
|
3846
|
-
if (!
|
|
4321
|
+
if (!isNonEmptyString4(value)) return void 0;
|
|
3847
4322
|
const ms = Date.parse(value);
|
|
3848
4323
|
if (!Number.isFinite(ms)) return void 0;
|
|
3849
4324
|
return new Date(ms).toISOString();
|
|
@@ -4103,9 +4578,9 @@ function mapOpenInferenceSpan(span, index, version2) {
|
|
|
4103
4578
|
if (endedAt !== void 0) {
|
|
4104
4579
|
event.endedAt = endedAt;
|
|
4105
4580
|
}
|
|
4106
|
-
const
|
|
4107
|
-
if (
|
|
4108
|
-
event.durationMs =
|
|
4581
|
+
const durationMs2 = durationBetweenIso(startedAt, endedAt);
|
|
4582
|
+
if (durationMs2 !== void 0) {
|
|
4583
|
+
event.durationMs = durationMs2;
|
|
4109
4584
|
}
|
|
4110
4585
|
if (tokenUsage !== void 0) {
|
|
4111
4586
|
event.tokenUsage = tokenUsage;
|
|
@@ -4690,9 +5165,9 @@ function mapOtlpSpan(context) {
|
|
|
4690
5165
|
if (endedAt !== void 0) {
|
|
4691
5166
|
event.endedAt = endedAt;
|
|
4692
5167
|
}
|
|
4693
|
-
const
|
|
4694
|
-
if (
|
|
4695
|
-
event.durationMs =
|
|
5168
|
+
const durationMs2 = durationBetweenIso(startedAt, endedAt);
|
|
5169
|
+
if (durationMs2 !== void 0) {
|
|
5170
|
+
event.durationMs = durationMs2;
|
|
4696
5171
|
}
|
|
4697
5172
|
if (tokenUsage !== void 0) {
|
|
4698
5173
|
event.tokenUsage = tokenUsage;
|
|
@@ -5697,13 +6172,13 @@ var EventNormalizer = class {
|
|
|
5697
6172
|
const timestampMissing = parsedTs === void 0;
|
|
5698
6173
|
const parentIdKey = cfg.parentIdKey;
|
|
5699
6174
|
const parentId = parentIdKey ? safeString(raw[parentIdKey]) : void 0;
|
|
5700
|
-
let
|
|
6175
|
+
let durationMs2;
|
|
5701
6176
|
const durationKey = cfg.durationKey;
|
|
5702
6177
|
if (durationKey) {
|
|
5703
6178
|
const v = raw[durationKey];
|
|
5704
|
-
if (isFiniteNumber2(v))
|
|
6179
|
+
if (isFiniteNumber2(v)) durationMs2 = v;
|
|
5705
6180
|
} else if (isFiniteNumber2(raw.durationMs)) {
|
|
5706
|
-
|
|
6181
|
+
durationMs2 = raw.durationMs;
|
|
5707
6182
|
}
|
|
5708
6183
|
let status;
|
|
5709
6184
|
const statusKey = cfg.statusKey;
|
|
@@ -5750,7 +6225,7 @@ var EventNormalizer = class {
|
|
|
5750
6225
|
kind,
|
|
5751
6226
|
timestamp,
|
|
5752
6227
|
...status ? { status } : {},
|
|
5753
|
-
...
|
|
6228
|
+
...durationMs2 !== void 0 ? { durationMs: durationMs2 } : {},
|
|
5754
6229
|
...Object.keys(attributes).length > 0 ? { attributes } : {},
|
|
5755
6230
|
confidence,
|
|
5756
6231
|
source: {
|
|
@@ -7338,7 +7813,7 @@ function manualTraceEventsToRunTree(events) {
|
|
|
7338
7813
|
}
|
|
7339
7814
|
const startedAt = started.startTime;
|
|
7340
7815
|
const endedAt = lastCompleted !== void 0 && runStatus !== "running" ? lastCompleted.endTime : void 0;
|
|
7341
|
-
const
|
|
7816
|
+
const durationMs2 = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
7342
7817
|
const steps = /* @__PURE__ */ new Map();
|
|
7343
7818
|
for (const e of events) {
|
|
7344
7819
|
if (e.event !== "step_started") continue;
|
|
@@ -7427,7 +7902,7 @@ function manualTraceEventsToRunTree(events) {
|
|
|
7427
7902
|
status: runStatus,
|
|
7428
7903
|
startedAt,
|
|
7429
7904
|
endedAt,
|
|
7430
|
-
durationMs,
|
|
7905
|
+
durationMs: durationMs2,
|
|
7431
7906
|
children: roots,
|
|
7432
7907
|
metadata: {
|
|
7433
7908
|
totalEvents: inspectNodes.size,
|
|
@@ -7740,7 +8215,7 @@ function manualTraceEventsToComparableRun(events) {
|
|
|
7740
8215
|
let runStatus;
|
|
7741
8216
|
if (lastCompleted === void 0) runStatus = "running";
|
|
7742
8217
|
else runStatus = lastCompleted.status;
|
|
7743
|
-
const
|
|
8218
|
+
const durationMs2 = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
7744
8219
|
const steps = /* @__PURE__ */ new Map();
|
|
7745
8220
|
let order = 0;
|
|
7746
8221
|
for (const e of events) {
|
|
@@ -7811,7 +8286,7 @@ function manualTraceEventsToComparableRun(events) {
|
|
|
7811
8286
|
runId,
|
|
7812
8287
|
name: rs.name,
|
|
7813
8288
|
status: runStatus,
|
|
7814
|
-
durationMs,
|
|
8289
|
+
durationMs: durationMs2,
|
|
7815
8290
|
steps: roots
|
|
7816
8291
|
};
|
|
7817
8292
|
}
|
|
@@ -8397,11 +8872,29 @@ async function searchCommand(options = {}) {
|
|
|
8397
8872
|
parseDurationFilter(options.duration);
|
|
8398
8873
|
}
|
|
8399
8874
|
const files = await td.list();
|
|
8400
|
-
|
|
8875
|
+
let metas = await loadTraceMetadataList(
|
|
8401
8876
|
traceDir,
|
|
8402
8877
|
files,
|
|
8403
8878
|
(f) => td.getPath(f)
|
|
8404
8879
|
);
|
|
8880
|
+
const sessionId = options.session?.trim();
|
|
8881
|
+
if (sessionId) {
|
|
8882
|
+
const records = await loadSessionRunRecords(metas);
|
|
8883
|
+
const scoped = filterMetasBySessionScope(metas, records, {
|
|
8884
|
+
sessionId,
|
|
8885
|
+
correlateByGroupId: options.correlateGroup === true
|
|
8886
|
+
});
|
|
8887
|
+
if (scoped.notFound) {
|
|
8888
|
+
if (options.json) {
|
|
8889
|
+
console.log(JSON.stringify([], null, 2));
|
|
8890
|
+
} else {
|
|
8891
|
+
console.log(`Session not found: ${sessionId}`);
|
|
8892
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
8893
|
+
}
|
|
8894
|
+
return;
|
|
8895
|
+
}
|
|
8896
|
+
metas = scoped.metas;
|
|
8897
|
+
}
|
|
8405
8898
|
const status = options.status === "success" || options.status === "error" || options.status === "running" || options.status === "unknown" ? options.status : void 0;
|
|
8406
8899
|
const results = await searchTraces(metas, {
|
|
8407
8900
|
traceDir,
|
|
@@ -8412,7 +8905,9 @@ async function searchCommand(options = {}) {
|
|
|
8412
8905
|
name: options.name,
|
|
8413
8906
|
tool: options.tool,
|
|
8414
8907
|
duration: options.duration,
|
|
8415
|
-
limit: parseLimit2(options.limit)
|
|
8908
|
+
limit: parseLimit2(options.limit),
|
|
8909
|
+
...sessionId ? { session: sessionId } : {},
|
|
8910
|
+
...options.correlateGroup ? { correlateGroup: true } : {}
|
|
8416
8911
|
});
|
|
8417
8912
|
if (options.json) {
|
|
8418
8913
|
console.log(JSON.stringify(results, null, 2));
|
|
@@ -8440,6 +8935,184 @@ async function searchCommand(options = {}) {
|
|
|
8440
8935
|
}
|
|
8441
8936
|
}
|
|
8442
8937
|
|
|
8938
|
+
// packages/cli/src/sessions.ts
|
|
8939
|
+
async function loadSessionIndex(traceDir, correlateGroup) {
|
|
8940
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
8941
|
+
const files = await td.list();
|
|
8942
|
+
const metas = await loadTraceMetadataList(
|
|
8943
|
+
traceDir,
|
|
8944
|
+
files,
|
|
8945
|
+
(fileName) => td.getPath(fileName)
|
|
8946
|
+
);
|
|
8947
|
+
const runs = await loadSessionRunRecords(metas);
|
|
8948
|
+
return buildSessionIndex(runs, { correlateByGroupId: correlateGroup === true });
|
|
8949
|
+
}
|
|
8950
|
+
function findSession(index, sessionId) {
|
|
8951
|
+
return index.sessions.find((session) => session.sessionId === sessionId);
|
|
8952
|
+
}
|
|
8953
|
+
function renderSessionsHuman(index, traceDir) {
|
|
8954
|
+
if (index.sessions.length === 0) {
|
|
8955
|
+
console.log("No sessions found");
|
|
8956
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
8957
|
+
if (index.unscopedRunIds.length > 0) {
|
|
8958
|
+
console.log(
|
|
8959
|
+
`Unscoped runs (no sessionId): ${index.unscopedRunIds.join(", ")}`
|
|
8960
|
+
);
|
|
8961
|
+
}
|
|
8962
|
+
return;
|
|
8963
|
+
}
|
|
8964
|
+
console.log("Sessions:");
|
|
8965
|
+
for (const session of index.sessions) {
|
|
8966
|
+
const firstRun = index.runs.find(
|
|
8967
|
+
(run) => session.runIds.includes(run.runId)
|
|
8968
|
+
);
|
|
8969
|
+
const workflowName = firstRun?.metadata && typeof firstRun.metadata.workflowName === "string" ? firstRun.metadata.workflowName : void 0;
|
|
8970
|
+
const suffix = workflowName ? ` workflow=${workflowName}` : "";
|
|
8971
|
+
console.log(
|
|
8972
|
+
` ${session.sessionId} (${session.runIds.length} run${session.runIds.length === 1 ? "" : "s"})${suffix}`
|
|
8973
|
+
);
|
|
8974
|
+
}
|
|
8975
|
+
if (index.unscopedRunIds.length > 0) {
|
|
8976
|
+
console.log("");
|
|
8977
|
+
console.log(
|
|
8978
|
+
`Unscoped runs (no sessionId): ${index.unscopedRunIds.join(", ")}`
|
|
8979
|
+
);
|
|
8980
|
+
}
|
|
8981
|
+
}
|
|
8982
|
+
function renderSessionHuman(session, index, options) {
|
|
8983
|
+
console.log(`Session: ${session.sessionId}`);
|
|
8984
|
+
console.log(`Runs: ${session.runIds.join(", ")}`);
|
|
8985
|
+
if (session.handoffs.length > 0) {
|
|
8986
|
+
console.log("");
|
|
8987
|
+
console.log("Handoffs:");
|
|
8988
|
+
for (const edge of session.handoffs) {
|
|
8989
|
+
console.log(
|
|
8990
|
+
` ${edge.from} -> ${edge.to} (${edge.confidence}, ${edge.source})`
|
|
8991
|
+
);
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
if (session.retries.length > 0) {
|
|
8995
|
+
console.log("");
|
|
8996
|
+
console.log("Retries:");
|
|
8997
|
+
for (const retry of session.retries) {
|
|
8998
|
+
const attempt = retry.attempt !== void 0 ? ` attempt=${retry.attempt}` : "";
|
|
8999
|
+
const of = retry.retryOf !== void 0 ? ` retryOf=${retry.retryOf}` : "";
|
|
9000
|
+
console.log(
|
|
9001
|
+
` ${retry.runId}${attempt}${of} (${retry.confidence})`
|
|
9002
|
+
);
|
|
9003
|
+
}
|
|
9004
|
+
}
|
|
9005
|
+
if (options.criticalPath && session.criticalPath.length > 0) {
|
|
9006
|
+
console.log("");
|
|
9007
|
+
console.log("Critical path:");
|
|
9008
|
+
for (const step of session.criticalPath) {
|
|
9009
|
+
const duration = step.durationMs !== void 0 ? ` ${step.durationMs}ms` : "";
|
|
9010
|
+
console.log(
|
|
9011
|
+
` ${step.runId}${step.name ? ` (${step.name})` : ""}${duration} [${step.confidence}]`
|
|
9012
|
+
);
|
|
9013
|
+
}
|
|
9014
|
+
}
|
|
9015
|
+
if (options.diagnostics && index.warnings.length > 0) {
|
|
9016
|
+
const scoped = index.warnings.filter(
|
|
9017
|
+
(warning) => warning.sessionId === session.sessionId || !warning.sessionId
|
|
9018
|
+
);
|
|
9019
|
+
if (scoped.length > 0) {
|
|
9020
|
+
console.log("");
|
|
9021
|
+
console.log("Diagnostics:");
|
|
9022
|
+
for (const warning of scoped) {
|
|
9023
|
+
const run = warning.runId ? ` run=${warning.runId}` : "";
|
|
9024
|
+
console.log(` [${warning.code}]${run} ${warning.message}`);
|
|
9025
|
+
}
|
|
9026
|
+
}
|
|
9027
|
+
}
|
|
9028
|
+
}
|
|
9029
|
+
async function sessionsCommand(options = {}) {
|
|
9030
|
+
try {
|
|
9031
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
9032
|
+
const index = await loadSessionIndex(traceDir, options.correlateGroup);
|
|
9033
|
+
if (options.json) {
|
|
9034
|
+
console.log(
|
|
9035
|
+
JSON.stringify(
|
|
9036
|
+
{
|
|
9037
|
+
traceDir,
|
|
9038
|
+
sessions: index.sessions,
|
|
9039
|
+
unscopedRunIds: index.unscopedRunIds,
|
|
9040
|
+
warnings: index.warnings
|
|
9041
|
+
},
|
|
9042
|
+
null,
|
|
9043
|
+
2
|
|
9044
|
+
)
|
|
9045
|
+
);
|
|
9046
|
+
return;
|
|
9047
|
+
}
|
|
9048
|
+
renderSessionsHuman(index, traceDir);
|
|
9049
|
+
} catch (e) {
|
|
9050
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9051
|
+
console.error(`[AgentInspect] sessions failed: ${msg}`);
|
|
9052
|
+
process.exitCode = 1;
|
|
9053
|
+
}
|
|
9054
|
+
}
|
|
9055
|
+
async function sessionCommand(sessionId, options = {}) {
|
|
9056
|
+
const id = typeof sessionId === "string" && sessionId.trim() !== "" ? sessionId.trim() : "";
|
|
9057
|
+
if (id === "") {
|
|
9058
|
+
console.error("Session id is required");
|
|
9059
|
+
process.exitCode = 1;
|
|
9060
|
+
return;
|
|
9061
|
+
}
|
|
9062
|
+
try {
|
|
9063
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
9064
|
+
const index = await loadSessionIndex(traceDir);
|
|
9065
|
+
const session = findSession(index, id);
|
|
9066
|
+
if (!session) {
|
|
9067
|
+
console.log(`Session not found: ${id}`);
|
|
9068
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
9069
|
+
process.exitCode = 1;
|
|
9070
|
+
return;
|
|
9071
|
+
}
|
|
9072
|
+
if (options.json) {
|
|
9073
|
+
const timelines = {};
|
|
9074
|
+
if (options.timeline) {
|
|
9075
|
+
for (const runId of session.runIds) {
|
|
9076
|
+
const result = await readRunTraceEvents(runId, traceDir);
|
|
9077
|
+
if (result && result.events.length > 0) {
|
|
9078
|
+
timelines[runId] = buildRunTimeline(result.events);
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
9082
|
+
console.log(
|
|
9083
|
+
JSON.stringify(
|
|
9084
|
+
{
|
|
9085
|
+
traceDir,
|
|
9086
|
+
session,
|
|
9087
|
+
warnings: index.warnings.filter(
|
|
9088
|
+
(warning) => warning.sessionId === id || warning.sessionId === void 0
|
|
9089
|
+
),
|
|
9090
|
+
...options.timeline ? { timelines } : {}
|
|
9091
|
+
},
|
|
9092
|
+
null,
|
|
9093
|
+
2
|
|
9094
|
+
)
|
|
9095
|
+
);
|
|
9096
|
+
return;
|
|
9097
|
+
}
|
|
9098
|
+
renderSessionHuman(session, index, options);
|
|
9099
|
+
if (options.timeline) {
|
|
9100
|
+
for (const runId of session.runIds) {
|
|
9101
|
+
const result = await readRunTraceEvents(runId, traceDir);
|
|
9102
|
+
if (!result || result.events.length === 0) continue;
|
|
9103
|
+
const timeline = buildRunTimeline(result.events);
|
|
9104
|
+
console.log("");
|
|
9105
|
+
console.log(`Timeline: ${runId}`);
|
|
9106
|
+
console.log(renderTimeline(timeline));
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
} catch (e) {
|
|
9110
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9111
|
+
console.error(`[AgentInspect] session failed: ${msg}`);
|
|
9112
|
+
process.exitCode = 1;
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
|
|
8443
9116
|
// packages/cli/src/what.ts
|
|
8444
9117
|
async function whatCommand(runId, options = {}) {
|
|
8445
9118
|
const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
|
|
@@ -9586,7 +10259,7 @@ function diagnostic(code, message, ruleId) {
|
|
|
9586
10259
|
...ruleId ? { ruleId } : {}
|
|
9587
10260
|
};
|
|
9588
10261
|
}
|
|
9589
|
-
function
|
|
10262
|
+
function emptySummary2() {
|
|
9590
10263
|
return {
|
|
9591
10264
|
passed: 0,
|
|
9592
10265
|
failed: 0,
|
|
@@ -9601,7 +10274,7 @@ function errorResult(input3, diagnostics, selectedRun) {
|
|
|
9601
10274
|
format: input3.read.format,
|
|
9602
10275
|
...selectedRun ? { runId: selectedRun.runId } : {},
|
|
9603
10276
|
summary: {
|
|
9604
|
-
...
|
|
10277
|
+
...emptySummary2(),
|
|
9605
10278
|
errors: diagnostics.filter((item) => item.severity === "error").length
|
|
9606
10279
|
},
|
|
9607
10280
|
findings: [],
|
|
@@ -10507,7 +11180,7 @@ function createSafetyRawContentRule(options = {}) {
|
|
|
10507
11180
|
}
|
|
10508
11181
|
function createSafetySecretPatternRule(options = {}) {
|
|
10509
11182
|
const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
|
|
10510
|
-
const
|
|
11183
|
+
const maxStringLength2 = options.maxStringLength ?? 4096;
|
|
10511
11184
|
return {
|
|
10512
11185
|
id: "safety.secretPattern",
|
|
10513
11186
|
category: "safety",
|
|
@@ -10517,7 +11190,7 @@ function createSafetySecretPatternRule(options = {}) {
|
|
|
10517
11190
|
for (const event of context.events) {
|
|
10518
11191
|
for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
|
|
10519
11192
|
if (typeof entry.value !== "string") continue;
|
|
10520
|
-
const sample = entry.value.slice(0,
|
|
11193
|
+
const sample = entry.value.slice(0, maxStringLength2);
|
|
10521
11194
|
for (const pattern of patterns) {
|
|
10522
11195
|
pattern.pattern.lastIndex = 0;
|
|
10523
11196
|
if (!pattern.pattern.test(sample)) continue;
|
|
@@ -10773,6 +11446,621 @@ function runTraceChecks(input3, options = {}) {
|
|
|
10773
11446
|
diagnostics
|
|
10774
11447
|
};
|
|
10775
11448
|
}
|
|
11449
|
+
var ALL_RULES = [
|
|
11450
|
+
"circuit.same-tool-repetition",
|
|
11451
|
+
"circuit.same-args-repetition",
|
|
11452
|
+
"circuit.max-loop-iterations",
|
|
11453
|
+
"circuit.max-retries",
|
|
11454
|
+
"circuit.tool-timeout",
|
|
11455
|
+
"circuit.runaway-llm-loop",
|
|
11456
|
+
"circuit.excessive-branch-width"
|
|
11457
|
+
];
|
|
11458
|
+
function closed(ruleId, message) {
|
|
11459
|
+
return { ruleId, status: "closed", severity: "info", message, evidence: [] };
|
|
11460
|
+
}
|
|
11461
|
+
function open2(ruleId, message, evidence, severity = "error") {
|
|
11462
|
+
return {
|
|
11463
|
+
ruleId,
|
|
11464
|
+
status: severity === "warning" ? "warn" : "open",
|
|
11465
|
+
severity,
|
|
11466
|
+
message,
|
|
11467
|
+
evidence
|
|
11468
|
+
};
|
|
11469
|
+
}
|
|
11470
|
+
function isToolEvent(event) {
|
|
11471
|
+
const name = event.name.toLowerCase();
|
|
11472
|
+
return event.kind === "tool" || name.startsWith("tool:") || name.startsWith("function:") || name.includes(".tool.") || name.startsWith("mcp:");
|
|
11473
|
+
}
|
|
11474
|
+
function isLlmEvent(event) {
|
|
11475
|
+
const name = event.name.toLowerCase();
|
|
11476
|
+
return event.kind === "llm" || name.startsWith("llm:") || name.includes(".llm.") || name.includes("generation");
|
|
11477
|
+
}
|
|
11478
|
+
function toolLabel(event) {
|
|
11479
|
+
const attrs = event.attributes ?? {};
|
|
11480
|
+
const fromAttr = attrs.toolName ?? attrs.tool ?? attrs.function;
|
|
11481
|
+
if (typeof fromAttr === "string" && fromAttr.length > 0) return fromAttr;
|
|
11482
|
+
return event.name.replace(/^(tool:|function:|mcp:)/i, "");
|
|
11483
|
+
}
|
|
11484
|
+
function argsHash(toolName2, args) {
|
|
11485
|
+
return createHash("sha256").update(`${toolName2}:${JSON.stringify(args ?? null)}`).digest("hex").slice(0, 16);
|
|
11486
|
+
}
|
|
11487
|
+
function toolArgs(event) {
|
|
11488
|
+
const attrs = event.attributes ?? {};
|
|
11489
|
+
return attrs.arguments ?? attrs.args ?? attrs.input ?? attrs.parameters;
|
|
11490
|
+
}
|
|
11491
|
+
function durationMs(event) {
|
|
11492
|
+
if (typeof event.durationMs === "number") return event.durationMs;
|
|
11493
|
+
const attrs = event.attributes ?? {};
|
|
11494
|
+
const fromAttr = attrs.durationMs ?? attrs.duration;
|
|
11495
|
+
return typeof fromAttr === "number" ? fromAttr : void 0;
|
|
11496
|
+
}
|
|
11497
|
+
function attemptNumber(event) {
|
|
11498
|
+
const attrs = event.attributes ?? {};
|
|
11499
|
+
const value = attrs.attempt ?? attrs.retryAttempt ?? attrs.retryCount;
|
|
11500
|
+
return typeof value === "number" ? value : void 0;
|
|
11501
|
+
}
|
|
11502
|
+
function evaluateSameToolRepetition(events, maxRepeats) {
|
|
11503
|
+
const ruleId = "circuit.same-tool-repetition";
|
|
11504
|
+
const counts = /* @__PURE__ */ new Map();
|
|
11505
|
+
for (const event of events.filter(isToolEvent)) {
|
|
11506
|
+
const label = toolLabel(event);
|
|
11507
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
11508
|
+
}
|
|
11509
|
+
const evidence = [];
|
|
11510
|
+
for (const [toolName2, count] of counts) {
|
|
11511
|
+
if (count > maxRepeats) {
|
|
11512
|
+
evidence.push({ ruleId, toolName: toolName2, count, threshold: maxRepeats });
|
|
11513
|
+
}
|
|
11514
|
+
}
|
|
11515
|
+
if (evidence.length === 0) {
|
|
11516
|
+
return closed(ruleId, "Tool repetition within threshold.");
|
|
11517
|
+
}
|
|
11518
|
+
return open2(ruleId, "Same tool repeated beyond threshold.", evidence);
|
|
11519
|
+
}
|
|
11520
|
+
function evaluateSameArgsRepetition(events, maxRepeats) {
|
|
11521
|
+
const ruleId = "circuit.same-args-repetition";
|
|
11522
|
+
const counts = /* @__PURE__ */ new Map();
|
|
11523
|
+
for (const event of events.filter(isToolEvent)) {
|
|
11524
|
+
const label = toolLabel(event);
|
|
11525
|
+
const hash = argsHash(label, toolArgs(event));
|
|
11526
|
+
const key = `${label}:${hash}`;
|
|
11527
|
+
const current = counts.get(key) ?? { toolName: label, count: 0 };
|
|
11528
|
+
current.count += 1;
|
|
11529
|
+
counts.set(key, current);
|
|
11530
|
+
}
|
|
11531
|
+
const evidence = [];
|
|
11532
|
+
for (const entry of counts.values()) {
|
|
11533
|
+
if (entry.count > maxRepeats) {
|
|
11534
|
+
evidence.push({ ruleId, toolName: entry.toolName, count: entry.count, threshold: maxRepeats });
|
|
11535
|
+
}
|
|
11536
|
+
}
|
|
11537
|
+
if (evidence.length === 0) {
|
|
11538
|
+
return closed(ruleId, "Tool argument repetition within threshold.");
|
|
11539
|
+
}
|
|
11540
|
+
return open2(ruleId, "Same tool arguments repeated beyond threshold.", evidence);
|
|
11541
|
+
}
|
|
11542
|
+
function evaluateMaxLoopIterations(events, maxIterations) {
|
|
11543
|
+
const ruleId = "circuit.max-loop-iterations";
|
|
11544
|
+
const iterationEvents = events.filter((event) => {
|
|
11545
|
+
const attrs = event.attributes ?? {};
|
|
11546
|
+
return typeof attrs.iteration === "number" || event.name.toLowerCase().includes("loop");
|
|
11547
|
+
});
|
|
11548
|
+
const maxSeen = iterationEvents.reduce((max, event) => {
|
|
11549
|
+
const attrs = event.attributes ?? {};
|
|
11550
|
+
const iteration = typeof attrs.iteration === "number" ? attrs.iteration : max;
|
|
11551
|
+
return Math.max(max, iteration);
|
|
11552
|
+
}, iterationEvents.length);
|
|
11553
|
+
if (maxSeen <= maxIterations) {
|
|
11554
|
+
return closed(ruleId, "Loop iterations within threshold.");
|
|
11555
|
+
}
|
|
11556
|
+
return open2(ruleId, "Loop iterations exceeded threshold.", [
|
|
11557
|
+
{ ruleId, count: maxSeen, threshold: maxIterations }
|
|
11558
|
+
]);
|
|
11559
|
+
}
|
|
11560
|
+
function evaluateMaxRetries(events, maxRetries) {
|
|
11561
|
+
const ruleId = "circuit.max-retries";
|
|
11562
|
+
const attempts = events.map(attemptNumber).filter((value) => value !== void 0);
|
|
11563
|
+
const maxAttempt = attempts.length > 0 ? Math.max(...attempts) : 0;
|
|
11564
|
+
if (maxAttempt <= maxRetries) {
|
|
11565
|
+
return closed(ruleId, "Retry count within threshold.");
|
|
11566
|
+
}
|
|
11567
|
+
return open2(ruleId, "Retry count exceeded threshold.", [
|
|
11568
|
+
{ ruleId, count: maxAttempt, threshold: maxRetries }
|
|
11569
|
+
]);
|
|
11570
|
+
}
|
|
11571
|
+
function evaluateToolTimeout(events, maxDurationMs) {
|
|
11572
|
+
const ruleId = "circuit.tool-timeout";
|
|
11573
|
+
const evidence = [];
|
|
11574
|
+
for (const event of events.filter(isToolEvent)) {
|
|
11575
|
+
const duration = durationMs(event);
|
|
11576
|
+
if (duration !== void 0 && duration > maxDurationMs) {
|
|
11577
|
+
evidence.push({
|
|
11578
|
+
ruleId,
|
|
11579
|
+
toolName: toolLabel(event),
|
|
11580
|
+
count: duration,
|
|
11581
|
+
threshold: maxDurationMs,
|
|
11582
|
+
eventId: event.eventId
|
|
11583
|
+
});
|
|
11584
|
+
}
|
|
11585
|
+
}
|
|
11586
|
+
if (evidence.length === 0) {
|
|
11587
|
+
return closed(ruleId, "Tool durations within timeout.");
|
|
11588
|
+
}
|
|
11589
|
+
return open2(ruleId, "Tool call exceeded configured timeout.", evidence, "warning");
|
|
11590
|
+
}
|
|
11591
|
+
function evaluateRunawayLlmLoop(events, maxLlmCalls) {
|
|
11592
|
+
const ruleId = "circuit.runaway-llm-loop";
|
|
11593
|
+
const llmCount = events.filter(isLlmEvent).length;
|
|
11594
|
+
const hasTerminal = events.some((event) => {
|
|
11595
|
+
const status = (event.status ?? event.attributes?.status ?? "").toString().toLowerCase();
|
|
11596
|
+
return status === "ok" || status === "success" || status === "completed";
|
|
11597
|
+
});
|
|
11598
|
+
if (llmCount <= maxLlmCalls || hasTerminal) {
|
|
11599
|
+
return closed(ruleId, "LLM call count within threshold or run completed.");
|
|
11600
|
+
}
|
|
11601
|
+
return open2(ruleId, "Runaway LLM loop detected.", [
|
|
11602
|
+
{ ruleId, count: llmCount, threshold: maxLlmCalls }
|
|
11603
|
+
]);
|
|
11604
|
+
}
|
|
11605
|
+
function evaluateExcessiveBranchWidth(events, maxWidth) {
|
|
11606
|
+
const ruleId = "circuit.excessive-branch-width";
|
|
11607
|
+
const children = /* @__PURE__ */ new Map();
|
|
11608
|
+
for (const event of events) {
|
|
11609
|
+
const parentId = event.parentId;
|
|
11610
|
+
if (!parentId) continue;
|
|
11611
|
+
children.set(parentId, (children.get(parentId) ?? 0) + 1);
|
|
11612
|
+
}
|
|
11613
|
+
const evidence = [];
|
|
11614
|
+
for (const [parentId, count] of children) {
|
|
11615
|
+
if (count > maxWidth) {
|
|
11616
|
+
evidence.push({ ruleId, path: parentId, count, threshold: maxWidth });
|
|
11617
|
+
}
|
|
11618
|
+
}
|
|
11619
|
+
if (evidence.length === 0) {
|
|
11620
|
+
return closed(ruleId, "Branch width within threshold.");
|
|
11621
|
+
}
|
|
11622
|
+
return open2(ruleId, "Excessive parallel branch width detected.", evidence, "warning");
|
|
11623
|
+
}
|
|
11624
|
+
function runRule(ruleId, events, options) {
|
|
11625
|
+
switch (ruleId) {
|
|
11626
|
+
case "circuit.same-tool-repetition":
|
|
11627
|
+
if (options.sameToolRepetition === void 0) return void 0;
|
|
11628
|
+
return evaluateSameToolRepetition(events, options.sameToolRepetition.maxRepeats);
|
|
11629
|
+
case "circuit.same-args-repetition":
|
|
11630
|
+
if (options.sameArgsRepetition === void 0) return void 0;
|
|
11631
|
+
return evaluateSameArgsRepetition(events, options.sameArgsRepetition.maxRepeats);
|
|
11632
|
+
case "circuit.max-loop-iterations":
|
|
11633
|
+
if (options.maxLoopIterations === void 0) return void 0;
|
|
11634
|
+
return evaluateMaxLoopIterations(events, options.maxLoopIterations.maxIterations);
|
|
11635
|
+
case "circuit.max-retries":
|
|
11636
|
+
if (options.maxRetries === void 0) return void 0;
|
|
11637
|
+
return evaluateMaxRetries(events, options.maxRetries.maxRetries);
|
|
11638
|
+
case "circuit.tool-timeout":
|
|
11639
|
+
if (options.toolTimeout === void 0) return void 0;
|
|
11640
|
+
return evaluateToolTimeout(events, options.toolTimeout.maxDurationMs);
|
|
11641
|
+
case "circuit.runaway-llm-loop":
|
|
11642
|
+
if (options.runawayLlmLoop === void 0) return void 0;
|
|
11643
|
+
return evaluateRunawayLlmLoop(events, options.runawayLlmLoop.maxLlmCalls);
|
|
11644
|
+
case "circuit.excessive-branch-width":
|
|
11645
|
+
if (options.excessiveBranchWidth === void 0) return void 0;
|
|
11646
|
+
return evaluateExcessiveBranchWidth(events, options.excessiveBranchWidth.maxWidth);
|
|
11647
|
+
default:
|
|
11648
|
+
return void 0;
|
|
11649
|
+
}
|
|
11650
|
+
}
|
|
11651
|
+
function runCircuits(events, options = {}) {
|
|
11652
|
+
const selected = options.rules ?? ALL_RULES;
|
|
11653
|
+
const results = [];
|
|
11654
|
+
for (const ruleId of selected) {
|
|
11655
|
+
const result = runRule(ruleId, events, options);
|
|
11656
|
+
if (result) results.push(result);
|
|
11657
|
+
}
|
|
11658
|
+
const ok = !results.some((result) => result.status === "open" && result.severity === "error");
|
|
11659
|
+
return { ok, results };
|
|
11660
|
+
}
|
|
11661
|
+
|
|
11662
|
+
// packages/guardrails/src/rules.ts
|
|
11663
|
+
var DEFAULT_INJECTION_PATTERNS = [
|
|
11664
|
+
"ignore previous instructions",
|
|
11665
|
+
"ignore all prior",
|
|
11666
|
+
"disregard your instructions",
|
|
11667
|
+
"system prompt",
|
|
11668
|
+
"you are now",
|
|
11669
|
+
"jailbreak"
|
|
11670
|
+
];
|
|
11671
|
+
function pass(ruleId, message) {
|
|
11672
|
+
return { ruleId, status: "pass", severity: "info", message, evidence: [] };
|
|
11673
|
+
}
|
|
11674
|
+
function fail(ruleId, message, evidence, severity = "error") {
|
|
11675
|
+
return { ruleId, status: severity === "warning" ? "warn" : "fail", severity, message, evidence };
|
|
11676
|
+
}
|
|
11677
|
+
function boundedPreview(value, max = 80) {
|
|
11678
|
+
if (value.length <= max) return value;
|
|
11679
|
+
return `${value.slice(0, max - 3)}...`;
|
|
11680
|
+
}
|
|
11681
|
+
function evaluateBannedPhrase(text, options) {
|
|
11682
|
+
const ruleId = "guardrail.banned-phrase";
|
|
11683
|
+
const haystack = options.caseInsensitive !== false ? text.toLowerCase() : text;
|
|
11684
|
+
const evidence = [];
|
|
11685
|
+
for (const phrase of options.phrases) {
|
|
11686
|
+
const needle = options.caseInsensitive !== false ? phrase.toLowerCase() : phrase;
|
|
11687
|
+
if (needle.length > 0 && haystack.includes(needle)) {
|
|
11688
|
+
evidence.push({ ruleId, match: phrase, preview: boundedPreview(text) });
|
|
11689
|
+
}
|
|
11690
|
+
}
|
|
11691
|
+
if (evidence.length === 0) {
|
|
11692
|
+
return pass(ruleId, "No banned phrases matched.");
|
|
11693
|
+
}
|
|
11694
|
+
return fail(ruleId, `Matched ${evidence.length} banned phrase(s).`, evidence);
|
|
11695
|
+
}
|
|
11696
|
+
var SEVERITY_RANK2 = { info: 0, warning: 1, error: 2 };
|
|
11697
|
+
function evaluatePiiLeak(value, options = {}) {
|
|
11698
|
+
const ruleId = "guardrail.pii-leak";
|
|
11699
|
+
const minSeverity = options.minSeverity ?? "warning";
|
|
11700
|
+
const result = redact(value, { profile: options.profile ?? "share", collectFindings: true });
|
|
11701
|
+
const findings = result.findings.filter(
|
|
11702
|
+
(finding) => SEVERITY_RANK2[finding.severity] >= SEVERITY_RANK2[minSeverity]
|
|
11703
|
+
);
|
|
11704
|
+
if (findings.length === 0) {
|
|
11705
|
+
return pass(ruleId, "No PII-style redaction findings.");
|
|
11706
|
+
}
|
|
11707
|
+
const evidence = findings.map((finding) => ({
|
|
11708
|
+
ruleId,
|
|
11709
|
+
path: finding.path,
|
|
11710
|
+
detector: finding.detector,
|
|
11711
|
+
preview: finding.preview
|
|
11712
|
+
}));
|
|
11713
|
+
return fail(ruleId, `Detected ${findings.length} PII-style finding(s).`, evidence);
|
|
11714
|
+
}
|
|
11715
|
+
function measureDepth(value) {
|
|
11716
|
+
if (value === null || typeof value !== "object") return 0;
|
|
11717
|
+
if (Array.isArray(value)) {
|
|
11718
|
+
return 1 + Math.max(0, ...value.map((item) => measureDepth(item)));
|
|
11719
|
+
}
|
|
11720
|
+
const depths = Object.values(value).map((item) => measureDepth(item));
|
|
11721
|
+
return 1 + (depths.length === 0 ? 0 : Math.max(...depths));
|
|
11722
|
+
}
|
|
11723
|
+
function maxStringLength(value) {
|
|
11724
|
+
if (typeof value === "string") return value.length;
|
|
11725
|
+
if (value === null || typeof value !== "object") return 0;
|
|
11726
|
+
if (Array.isArray(value)) {
|
|
11727
|
+
return Math.max(0, ...value.map((item) => maxStringLength(item)));
|
|
11728
|
+
}
|
|
11729
|
+
return Math.max(0, ...Object.values(value).map((item) => maxStringLength(item)));
|
|
11730
|
+
}
|
|
11731
|
+
function evaluateUnsafeToolArgs(toolName2, toolArgs2, options = {}) {
|
|
11732
|
+
const ruleId = "guardrail.unsafe-tool-args";
|
|
11733
|
+
const blocked = new Set((options.blockedTools ?? []).map((name) => name.toLowerCase()));
|
|
11734
|
+
const evidence = [];
|
|
11735
|
+
if (blocked.has(toolName2.toLowerCase())) {
|
|
11736
|
+
evidence.push({ ruleId, preview: toolName2, match: toolName2 });
|
|
11737
|
+
}
|
|
11738
|
+
const maxDepth = options.maxDepth ?? 12;
|
|
11739
|
+
const depth = measureDepth(toolArgs2);
|
|
11740
|
+
if (depth > maxDepth) {
|
|
11741
|
+
evidence.push({ ruleId, path: "args", preview: `depth=${depth}` });
|
|
11742
|
+
}
|
|
11743
|
+
const maxLen = options.maxStringLength ?? 16384;
|
|
11744
|
+
const longest = maxStringLength(toolArgs2);
|
|
11745
|
+
if (longest > maxLen) {
|
|
11746
|
+
evidence.push({ ruleId, path: "args", preview: `maxStringLength=${longest}` });
|
|
11747
|
+
}
|
|
11748
|
+
if (evidence.length === 0) {
|
|
11749
|
+
return pass(ruleId, "Tool arguments within configured bounds.");
|
|
11750
|
+
}
|
|
11751
|
+
return fail(ruleId, "Unsafe or oversized tool arguments detected.", evidence);
|
|
11752
|
+
}
|
|
11753
|
+
function evaluatePromptInjection(text, options = {}) {
|
|
11754
|
+
const ruleId = "guardrail.prompt-injection";
|
|
11755
|
+
const patterns = options.patterns ?? DEFAULT_INJECTION_PATTERNS;
|
|
11756
|
+
const haystack = text.toLowerCase();
|
|
11757
|
+
const evidence = [];
|
|
11758
|
+
for (const pattern of patterns) {
|
|
11759
|
+
const needle = pattern.toLowerCase();
|
|
11760
|
+
if (needle.length > 0 && haystack.includes(needle)) {
|
|
11761
|
+
evidence.push({ ruleId, match: pattern, preview: boundedPreview(text) });
|
|
11762
|
+
}
|
|
11763
|
+
}
|
|
11764
|
+
if (evidence.length === 0) {
|
|
11765
|
+
return pass(ruleId, "No prompt-injection patterns matched.");
|
|
11766
|
+
}
|
|
11767
|
+
return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
|
|
11768
|
+
}
|
|
11769
|
+
function validateSchemaField(value, field, path15, evidence) {
|
|
11770
|
+
const ruleId = "guardrail.structured-output";
|
|
11771
|
+
if (field.type) {
|
|
11772
|
+
const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
11773
|
+
if (actual !== field.type) {
|
|
11774
|
+
evidence.push({ ruleId, path: path15, preview: `expected ${field.type}, got ${actual}` });
|
|
11775
|
+
return;
|
|
11776
|
+
}
|
|
11777
|
+
}
|
|
11778
|
+
if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
|
|
11779
|
+
evidence.push({ ruleId, path: path15, preview: "value not in enum" });
|
|
11780
|
+
}
|
|
11781
|
+
if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
|
|
11782
|
+
const record = value;
|
|
11783
|
+
for (const key of field.required) {
|
|
11784
|
+
if (!(key in record)) {
|
|
11785
|
+
evidence.push({ ruleId, path: `${path15}.${key}`, preview: "missing required key" });
|
|
11786
|
+
}
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
}
|
|
11790
|
+
function evaluateStructuredOutput(value, options) {
|
|
11791
|
+
const ruleId = "guardrail.structured-output";
|
|
11792
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11793
|
+
return fail(ruleId, "Structured output must be an object.", [
|
|
11794
|
+
{ ruleId, preview: typeof value }
|
|
11795
|
+
]);
|
|
11796
|
+
}
|
|
11797
|
+
const record = value;
|
|
11798
|
+
const evidence = [];
|
|
11799
|
+
for (const [key, field] of Object.entries(options.schema)) {
|
|
11800
|
+
validateSchemaField(record[key], field, key, evidence);
|
|
11801
|
+
}
|
|
11802
|
+
if (evidence.length === 0) {
|
|
11803
|
+
return pass(ruleId, "Structured output matches schema subset.");
|
|
11804
|
+
}
|
|
11805
|
+
return fail(ruleId, "Structured output schema violation.", evidence);
|
|
11806
|
+
}
|
|
11807
|
+
function evaluateOversizeOutput(value, options = {}) {
|
|
11808
|
+
const ruleId = "guardrail.oversize-output";
|
|
11809
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
11810
|
+
const maxLength = options.maxLength ?? options.maxSerializedLength ?? 32768;
|
|
11811
|
+
if (text.length <= maxLength) {
|
|
11812
|
+
return pass(ruleId, "Output within size limits.");
|
|
11813
|
+
}
|
|
11814
|
+
return fail(ruleId, `Output exceeds max length (${text.length} > ${maxLength}).`, [
|
|
11815
|
+
{ ruleId, preview: `length=${text.length}` }
|
|
11816
|
+
]);
|
|
11817
|
+
}
|
|
11818
|
+
function evaluateRequiredJsonShape(value, options) {
|
|
11819
|
+
const ruleId = "guardrail.required-json-shape";
|
|
11820
|
+
let parsed = value;
|
|
11821
|
+
if (typeof value === "string") {
|
|
11822
|
+
try {
|
|
11823
|
+
parsed = JSON.parse(value);
|
|
11824
|
+
} catch {
|
|
11825
|
+
return fail(ruleId, "Value is not valid JSON.", [{ ruleId, preview: boundedPreview(value) }]);
|
|
11826
|
+
}
|
|
11827
|
+
}
|
|
11828
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11829
|
+
return fail(ruleId, "JSON value must be an object.", [{ ruleId, preview: typeof parsed }]);
|
|
11830
|
+
}
|
|
11831
|
+
const record = parsed;
|
|
11832
|
+
const evidence = [];
|
|
11833
|
+
for (const key of options.requiredKeys) {
|
|
11834
|
+
if (!(key in record)) {
|
|
11835
|
+
evidence.push({ ruleId, path: key, preview: "missing required key" });
|
|
11836
|
+
}
|
|
11837
|
+
}
|
|
11838
|
+
if (evidence.length === 0) {
|
|
11839
|
+
return pass(ruleId, "Required JSON keys present.");
|
|
11840
|
+
}
|
|
11841
|
+
return fail(ruleId, "Missing required JSON keys.", evidence);
|
|
11842
|
+
}
|
|
11843
|
+
|
|
11844
|
+
// packages/guardrails/src/run.ts
|
|
11845
|
+
var ALL_RULES2 = [
|
|
11846
|
+
"guardrail.banned-phrase",
|
|
11847
|
+
"guardrail.pii-leak",
|
|
11848
|
+
"guardrail.unsafe-tool-args",
|
|
11849
|
+
"guardrail.prompt-injection",
|
|
11850
|
+
"guardrail.structured-output",
|
|
11851
|
+
"guardrail.oversize-output",
|
|
11852
|
+
"guardrail.required-json-shape"
|
|
11853
|
+
];
|
|
11854
|
+
function isErrorFailure(result) {
|
|
11855
|
+
return result.status === "fail" && result.severity === "error";
|
|
11856
|
+
}
|
|
11857
|
+
function runRule2(ruleId, input3, options) {
|
|
11858
|
+
switch (ruleId) {
|
|
11859
|
+
case "guardrail.banned-phrase": {
|
|
11860
|
+
if (!options.bannedPhrase || input3.text === void 0) return void 0;
|
|
11861
|
+
return evaluateBannedPhrase(input3.text, options.bannedPhrase);
|
|
11862
|
+
}
|
|
11863
|
+
case "guardrail.pii-leak": {
|
|
11864
|
+
if (input3.value === void 0 && input3.text === void 0) return void 0;
|
|
11865
|
+
return evaluatePiiLeak(input3.value ?? input3.text, options.piiLeak);
|
|
11866
|
+
}
|
|
11867
|
+
case "guardrail.unsafe-tool-args": {
|
|
11868
|
+
if (!input3.toolName) return void 0;
|
|
11869
|
+
return evaluateUnsafeToolArgs(input3.toolName, input3.toolArgs ?? {}, options.unsafeToolArgs);
|
|
11870
|
+
}
|
|
11871
|
+
case "guardrail.prompt-injection": {
|
|
11872
|
+
if (input3.text === void 0) return void 0;
|
|
11873
|
+
return evaluatePromptInjection(input3.text, options.promptInjection);
|
|
11874
|
+
}
|
|
11875
|
+
case "guardrail.structured-output": {
|
|
11876
|
+
if (!options.structuredOutput || input3.value === void 0) return void 0;
|
|
11877
|
+
return evaluateStructuredOutput(input3.value, options.structuredOutput);
|
|
11878
|
+
}
|
|
11879
|
+
case "guardrail.oversize-output": {
|
|
11880
|
+
if (input3.value === void 0 && input3.text === void 0) return void 0;
|
|
11881
|
+
return evaluateOversizeOutput(input3.value ?? input3.text, options.oversizeOutput);
|
|
11882
|
+
}
|
|
11883
|
+
case "guardrail.required-json-shape": {
|
|
11884
|
+
if (!options.requiredJsonShape || input3.value === void 0 && input3.text === void 0) return void 0;
|
|
11885
|
+
return evaluateRequiredJsonShape(input3.value ?? input3.text, options.requiredJsonShape);
|
|
11886
|
+
}
|
|
11887
|
+
default:
|
|
11888
|
+
return void 0;
|
|
11889
|
+
}
|
|
11890
|
+
}
|
|
11891
|
+
function runGuardrails(input3, options = {}) {
|
|
11892
|
+
const selected = options.rules ?? ALL_RULES2;
|
|
11893
|
+
const results = [];
|
|
11894
|
+
for (const ruleId of selected) {
|
|
11895
|
+
const result = runRule2(ruleId, input3, options);
|
|
11896
|
+
if (result) results.push(result);
|
|
11897
|
+
}
|
|
11898
|
+
const ok = !results.some(isErrorFailure);
|
|
11899
|
+
return { ok, results };
|
|
11900
|
+
}
|
|
11901
|
+
|
|
11902
|
+
// packages/cli/src/safety-extensions.ts
|
|
11903
|
+
var GUARDRAIL_ALIASES = {
|
|
11904
|
+
"banned-phrase": "guardrail.banned-phrase",
|
|
11905
|
+
"pii-leak": "guardrail.pii-leak",
|
|
11906
|
+
"unsafe-tool-args": "guardrail.unsafe-tool-args",
|
|
11907
|
+
"prompt-injection": "guardrail.prompt-injection",
|
|
11908
|
+
"structured-output": "guardrail.structured-output",
|
|
11909
|
+
"oversize-output": "guardrail.oversize-output",
|
|
11910
|
+
"required-json-shape": "guardrail.required-json-shape"
|
|
11911
|
+
};
|
|
11912
|
+
var CIRCUIT_ALIASES = {
|
|
11913
|
+
"same-tool-repetition": "circuit.same-tool-repetition",
|
|
11914
|
+
"same-args-repetition": "circuit.same-args-repetition",
|
|
11915
|
+
"max-loop-iterations": "circuit.max-loop-iterations",
|
|
11916
|
+
"max-retries": "circuit.max-retries",
|
|
11917
|
+
"tool-timeout": "circuit.tool-timeout",
|
|
11918
|
+
"runaway-llm-loop": "circuit.runaway-llm-loop",
|
|
11919
|
+
"excessive-branch-width": "circuit.excessive-branch-width"
|
|
11920
|
+
};
|
|
11921
|
+
function parseGuardrailRules(values) {
|
|
11922
|
+
if (!values?.length) return void 0;
|
|
11923
|
+
return values.map((value) => {
|
|
11924
|
+
const rule = GUARDRAIL_ALIASES[value] ?? value;
|
|
11925
|
+
return rule;
|
|
11926
|
+
});
|
|
11927
|
+
}
|
|
11928
|
+
function parseCircuitRules(values) {
|
|
11929
|
+
if (!values?.length) return void 0;
|
|
11930
|
+
return values.map((value) => {
|
|
11931
|
+
const rule = CIRCUIT_ALIASES[value] ?? value;
|
|
11932
|
+
return rule;
|
|
11933
|
+
});
|
|
11934
|
+
}
|
|
11935
|
+
function toSeverity(severity) {
|
|
11936
|
+
return severity;
|
|
11937
|
+
}
|
|
11938
|
+
function guardrailFinding(result) {
|
|
11939
|
+
return {
|
|
11940
|
+
ruleId: result.ruleId,
|
|
11941
|
+
severity: toSeverity(result.severity),
|
|
11942
|
+
status: result.status === "pass" ? "pass" : result.status === "warn" ? "warning" : "fail",
|
|
11943
|
+
message: result.message,
|
|
11944
|
+
evidence: result.evidence.map(
|
|
11945
|
+
(item) => ({
|
|
11946
|
+
path: item.preview ? `${item.path ?? "value"} (${item.preview})` : item.path
|
|
11947
|
+
})
|
|
11948
|
+
)
|
|
11949
|
+
};
|
|
11950
|
+
}
|
|
11951
|
+
function circuitFinding(result) {
|
|
11952
|
+
return {
|
|
11953
|
+
ruleId: result.ruleId,
|
|
11954
|
+
severity: toSeverity(result.severity),
|
|
11955
|
+
status: result.status === "closed" ? "pass" : result.status === "warn" ? "warning" : "fail",
|
|
11956
|
+
message: result.message,
|
|
11957
|
+
evidence: result.evidence.map(
|
|
11958
|
+
(item) => ({
|
|
11959
|
+
runId: item.runId,
|
|
11960
|
+
eventId: item.eventId,
|
|
11961
|
+
path: item.path,
|
|
11962
|
+
name: item.toolName
|
|
11963
|
+
})
|
|
11964
|
+
),
|
|
11965
|
+
actual: result.evidence[0]?.count,
|
|
11966
|
+
expected: result.evidence[0]?.threshold
|
|
11967
|
+
};
|
|
11968
|
+
}
|
|
11969
|
+
function eventToCircuit(event) {
|
|
11970
|
+
return {
|
|
11971
|
+
eventId: event.eventId,
|
|
11972
|
+
runId: event.runId,
|
|
11973
|
+
name: event.name,
|
|
11974
|
+
kind: event.kind,
|
|
11975
|
+
parentId: event.parentId,
|
|
11976
|
+
startedAt: event.startedAt,
|
|
11977
|
+
endedAt: event.endedAt,
|
|
11978
|
+
durationMs: event.durationMs,
|
|
11979
|
+
attributes: event.attributes,
|
|
11980
|
+
status: event.status
|
|
11981
|
+
};
|
|
11982
|
+
}
|
|
11983
|
+
function collectGuardrailInputs(read) {
|
|
11984
|
+
const inputs = [];
|
|
11985
|
+
for (const event of read.events) {
|
|
11986
|
+
const attrs = event.attributes ?? {};
|
|
11987
|
+
for (const key of ["output", "answer", "text", "content", "message"]) {
|
|
11988
|
+
const value = attrs[key];
|
|
11989
|
+
if (typeof value === "string") inputs.push({ text: value });
|
|
11990
|
+
else if (value !== void 0) inputs.push({ value });
|
|
11991
|
+
}
|
|
11992
|
+
if (event.kind === "TOOL" || event.name.startsWith("tool:")) {
|
|
11993
|
+
inputs.push({
|
|
11994
|
+
toolName: String(attrs.toolName ?? attrs.tool ?? event.name),
|
|
11995
|
+
toolArgs: attrs.arguments ?? attrs.args ?? attrs.input
|
|
11996
|
+
});
|
|
11997
|
+
}
|
|
11998
|
+
}
|
|
11999
|
+
return inputs;
|
|
12000
|
+
}
|
|
12001
|
+
var DEFAULT_GUARDRAIL_OPTIONS = {
|
|
12002
|
+
bannedPhrase: { phrases: ["delete all data", "ignore all instructions"] },
|
|
12003
|
+
promptInjection: {},
|
|
12004
|
+
piiLeak: { profile: "share" }
|
|
12005
|
+
};
|
|
12006
|
+
var DEFAULT_CIRCUIT_OPTIONS = {
|
|
12007
|
+
sameToolRepetition: { maxRepeats: 3 },
|
|
12008
|
+
sameArgsRepetition: { maxRepeats: 2 },
|
|
12009
|
+
maxLoopIterations: { maxIterations: 20 },
|
|
12010
|
+
maxRetries: { maxRetries: 3 },
|
|
12011
|
+
toolTimeout: { maxDurationMs: 6e4 },
|
|
12012
|
+
runawayLlmLoop: { maxLlmCalls: 12 },
|
|
12013
|
+
excessiveBranchWidth: { maxWidth: 8 }
|
|
12014
|
+
};
|
|
12015
|
+
function mergeSafetyExtensions(result, read, options) {
|
|
12016
|
+
const findings = [...result.findings];
|
|
12017
|
+
let failed = result.summary.failed;
|
|
12018
|
+
let warnings = result.summary.warnings;
|
|
12019
|
+
let passed = result.summary.passed;
|
|
12020
|
+
const guardrailRules = parseGuardrailRules(options.guardrails);
|
|
12021
|
+
if (guardrailRules) {
|
|
12022
|
+
for (const input3 of collectGuardrailInputs(read)) {
|
|
12023
|
+
const run = runGuardrails(input3, {
|
|
12024
|
+
...DEFAULT_GUARDRAIL_OPTIONS,
|
|
12025
|
+
rules: guardrailRules
|
|
12026
|
+
});
|
|
12027
|
+
for (const item of run.results) {
|
|
12028
|
+
const finding = guardrailFinding(item);
|
|
12029
|
+
findings.push(finding);
|
|
12030
|
+
if (finding.status === "fail") failed += 1;
|
|
12031
|
+
else if (finding.status === "warning") warnings += 1;
|
|
12032
|
+
else passed += 1;
|
|
12033
|
+
}
|
|
12034
|
+
}
|
|
12035
|
+
}
|
|
12036
|
+
const circuitRules = parseCircuitRules(options.circuits);
|
|
12037
|
+
if (circuitRules) {
|
|
12038
|
+
const circuitRun = runCircuits(
|
|
12039
|
+
read.events.map(eventToCircuit),
|
|
12040
|
+
{ ...DEFAULT_CIRCUIT_OPTIONS, rules: circuitRules }
|
|
12041
|
+
);
|
|
12042
|
+
for (const item of circuitRun.results) {
|
|
12043
|
+
const finding = circuitFinding(item);
|
|
12044
|
+
findings.push(finding);
|
|
12045
|
+
if (finding.status === "fail") failed += 1;
|
|
12046
|
+
else if (finding.status === "warning") warnings += 1;
|
|
12047
|
+
else passed += 1;
|
|
12048
|
+
}
|
|
12049
|
+
}
|
|
12050
|
+
const ok = failed === 0 && result.summary.errors === 0;
|
|
12051
|
+
return {
|
|
12052
|
+
...result,
|
|
12053
|
+
ok,
|
|
12054
|
+
status: failed > 0 ? "fail" : result.status,
|
|
12055
|
+
summary: {
|
|
12056
|
+
passed,
|
|
12057
|
+
failed,
|
|
12058
|
+
warnings,
|
|
12059
|
+
errors: result.summary.errors
|
|
12060
|
+
},
|
|
12061
|
+
findings
|
|
12062
|
+
};
|
|
12063
|
+
}
|
|
10776
12064
|
|
|
10777
12065
|
// packages/cli/src/check.ts
|
|
10778
12066
|
var DEFAULT_SELECT = ["run.status"];
|
|
@@ -10942,6 +12230,13 @@ function printJson(result) {
|
|
|
10942
12230
|
console.log(JSON.stringify(stable2(result), null, 2));
|
|
10943
12231
|
}
|
|
10944
12232
|
function printHuman(result) {
|
|
12233
|
+
const scoped = result;
|
|
12234
|
+
if (scoped.scopeLabel) {
|
|
12235
|
+
console.log(`Scope: ${scoped.scopeKind} ${scoped.scopeLabel}`);
|
|
12236
|
+
if (scoped.runIds?.length) {
|
|
12237
|
+
console.log(`Runs: ${scoped.runIds.join(", ")}`);
|
|
12238
|
+
}
|
|
12239
|
+
}
|
|
10945
12240
|
console.log(`Check status: ${result.status}`);
|
|
10946
12241
|
console.log(`Format: ${result.format}`);
|
|
10947
12242
|
if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
|
|
@@ -10953,7 +12248,9 @@ function printHuman(result) {
|
|
|
10953
12248
|
}
|
|
10954
12249
|
for (const finding of result.findings) {
|
|
10955
12250
|
const path15 = finding.evidence[0]?.path;
|
|
10956
|
-
|
|
12251
|
+
const run = finding.evidence[0]?.runId;
|
|
12252
|
+
const runPrefix = run ? `[${run}] ` : "";
|
|
12253
|
+
console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
|
|
10957
12254
|
}
|
|
10958
12255
|
}
|
|
10959
12256
|
function readErrorResult(error) {
|
|
@@ -10969,24 +12266,84 @@ function readErrorResult(error) {
|
|
|
10969
12266
|
async function checkCommand(target, options = {}, stdin = process.stdin) {
|
|
10970
12267
|
let result;
|
|
10971
12268
|
let phase = "config";
|
|
12269
|
+
const sessionId = options.session?.trim();
|
|
12270
|
+
const groupId = options.group?.trim();
|
|
12271
|
+
const useSessionScope = Boolean(sessionId || groupId);
|
|
10972
12272
|
try {
|
|
10973
12273
|
const config = await loadConfig(options.config);
|
|
10974
12274
|
const built = buildRules(config, options);
|
|
10975
12275
|
if (built.diagnostics.some((item) => item.severity === "error")) {
|
|
10976
12276
|
result = errorResult2("AI_CHECK_INVALID_CONFIG", "Invalid check configuration.");
|
|
10977
12277
|
result.diagnostics = [...built.diagnostics];
|
|
12278
|
+
} else if (useSessionScope) {
|
|
12279
|
+
phase = "read";
|
|
12280
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
12281
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
12282
|
+
const files = await td.list();
|
|
12283
|
+
const metas = await loadTraceMetadataList(
|
|
12284
|
+
traceDir,
|
|
12285
|
+
files,
|
|
12286
|
+
(fileName) => td.getPath(fileName)
|
|
12287
|
+
);
|
|
12288
|
+
const records = await loadSessionRunRecords(metas);
|
|
12289
|
+
const scoped = filterMetasBySessionScope(metas, records, {
|
|
12290
|
+
...sessionId ? { sessionId } : {},
|
|
12291
|
+
...groupId ? { groupId } : {},
|
|
12292
|
+
correlateByGroupId: options.correlateGroup === true
|
|
12293
|
+
});
|
|
12294
|
+
const perRun = [];
|
|
12295
|
+
for (const meta of scoped.metas) {
|
|
12296
|
+
const read = await openTrace(
|
|
12297
|
+
{ type: "file", path: meta.filePath },
|
|
12298
|
+
{
|
|
12299
|
+
...options.format !== void 0 ? { format: options.format } : {}
|
|
12300
|
+
}
|
|
12301
|
+
);
|
|
12302
|
+
perRun.push(
|
|
12303
|
+
mergeSafetyExtensions(
|
|
12304
|
+
runTraceChecks(
|
|
12305
|
+
{ read },
|
|
12306
|
+
{
|
|
12307
|
+
rules: built.rules,
|
|
12308
|
+
select: built.select,
|
|
12309
|
+
runId: meta.runId
|
|
12310
|
+
}
|
|
12311
|
+
),
|
|
12312
|
+
read,
|
|
12313
|
+
{
|
|
12314
|
+
...options.guardrails ? { guardrails: options.guardrails } : {},
|
|
12315
|
+
...options.circuit ? { circuits: options.circuit } : {}
|
|
12316
|
+
}
|
|
12317
|
+
)
|
|
12318
|
+
);
|
|
12319
|
+
}
|
|
12320
|
+
result = aggregateSessionCheckResults(perRun, {
|
|
12321
|
+
scopeKind: scoped.scopeKind,
|
|
12322
|
+
scopeLabel: scoped.scopeLabel,
|
|
12323
|
+
runIds: scoped.runIds,
|
|
12324
|
+
sessionWarnings: scoped.warnings,
|
|
12325
|
+
notFound: scoped.notFound,
|
|
12326
|
+
empty: scoped.metas.length === 0
|
|
12327
|
+
});
|
|
10978
12328
|
} else {
|
|
10979
12329
|
phase = "read";
|
|
10980
12330
|
const input3 = await inputFromTarget(target, options, stdin);
|
|
10981
12331
|
const read = await openTrace(input3, {
|
|
10982
12332
|
...options.format !== void 0 ? { format: options.format } : {}
|
|
10983
12333
|
});
|
|
10984
|
-
result =
|
|
10985
|
-
|
|
12334
|
+
result = mergeSafetyExtensions(
|
|
12335
|
+
runTraceChecks(
|
|
12336
|
+
{ read },
|
|
12337
|
+
{
|
|
12338
|
+
rules: built.rules,
|
|
12339
|
+
select: built.select,
|
|
12340
|
+
...options.run !== void 0 ? { runId: options.run } : {}
|
|
12341
|
+
}
|
|
12342
|
+
),
|
|
12343
|
+
read,
|
|
10986
12344
|
{
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
...options.run !== void 0 ? { runId: options.run } : {}
|
|
12345
|
+
...options.guardrails ? { guardrails: options.guardrails } : {},
|
|
12346
|
+
...options.circuit ? { circuits: options.circuit } : {}
|
|
10990
12347
|
}
|
|
10991
12348
|
);
|
|
10992
12349
|
}
|
|
@@ -11190,7 +12547,7 @@ function evidenceForEvent(event, path15) {
|
|
|
11190
12547
|
}
|
|
11191
12548
|
];
|
|
11192
12549
|
}
|
|
11193
|
-
function
|
|
12550
|
+
function fail2(ruleId, message, evidence, expected, actual) {
|
|
11194
12551
|
return {
|
|
11195
12552
|
ruleId,
|
|
11196
12553
|
status: "fail",
|
|
@@ -11386,7 +12743,7 @@ var checks = {
|
|
|
11386
12743
|
"eval.requireSuccess",
|
|
11387
12744
|
"run",
|
|
11388
12745
|
(context) => context.run.status === "ok" ? [] : [
|
|
11389
|
-
|
|
12746
|
+
fail2(
|
|
11390
12747
|
"eval.requireSuccess",
|
|
11391
12748
|
"Run did not complete successfully.",
|
|
11392
12749
|
evidenceForRun(context.run, "status"),
|
|
@@ -11401,7 +12758,7 @@ var checks = {
|
|
|
11401
12758
|
return createRule("eval.requiredTools", "tool", (context) => {
|
|
11402
12759
|
const tools = new Set(nodeNames(context.nodes, "TOOL"));
|
|
11403
12760
|
return expected.filter((name) => !tools.has(name)).map(
|
|
11404
|
-
(name) =>
|
|
12761
|
+
(name) => fail2(
|
|
11405
12762
|
"eval.requiredTools",
|
|
11406
12763
|
`Required tool ${name} did not appear.`,
|
|
11407
12764
|
evidenceForRun(context.run, "children"),
|
|
@@ -11417,7 +12774,7 @@ var checks = {
|
|
|
11417
12774
|
"eval.forbiddenTools",
|
|
11418
12775
|
"tool",
|
|
11419
12776
|
(context) => context.nodes.filter((node) => node.event.kind === "TOOL" && blocked.includes(node.event.name)).map(
|
|
11420
|
-
(node) =>
|
|
12777
|
+
(node) => fail2(
|
|
11421
12778
|
"eval.forbiddenTools",
|
|
11422
12779
|
`Forbidden tool ${node.event.name} appeared.`,
|
|
11423
12780
|
evidenceForEvent(node.event, "name"),
|
|
@@ -11432,7 +12789,7 @@ var checks = {
|
|
|
11432
12789
|
"eval.maxDurationMs",
|
|
11433
12790
|
"run",
|
|
11434
12791
|
(context) => context.run.durationMs !== void 0 && context.run.durationMs > maxDurationMs ? [
|
|
11435
|
-
|
|
12792
|
+
fail2(
|
|
11436
12793
|
"eval.maxDurationMs",
|
|
11437
12794
|
`Run duration exceeded ${maxDurationMs}ms.`,
|
|
11438
12795
|
evidenceForRun(context.run, "durationMs"),
|
|
@@ -11449,7 +12806,7 @@ var checks = {
|
|
|
11449
12806
|
void 0
|
|
11450
12807
|
);
|
|
11451
12808
|
return deepest !== void 0 && deepest.depth > maxDepth ? [
|
|
11452
|
-
|
|
12809
|
+
fail2(
|
|
11453
12810
|
"eval.maxDepth",
|
|
11454
12811
|
`Run tree depth exceeded ${maxDepth}.`,
|
|
11455
12812
|
evidenceForEvent(deepest.event, "depth"),
|
|
@@ -11466,7 +12823,7 @@ var checks = {
|
|
|
11466
12823
|
(context) => context.nodes.flatMap((node) => {
|
|
11467
12824
|
const retries = numericAttribute(node, ["retryCount", "retries", "attempt"]);
|
|
11468
12825
|
return retries !== void 0 && retries > maxRetries ? [
|
|
11469
|
-
|
|
12826
|
+
fail2(
|
|
11470
12827
|
"eval.maxRetries",
|
|
11471
12828
|
`Retry count exceeded ${maxRetries}.`,
|
|
11472
12829
|
evidenceForEvent(node.event, "attributes.retryCount"),
|
|
@@ -11481,7 +12838,7 @@ var checks = {
|
|
|
11481
12838
|
return createRule("eval.maxTotalTokens", "llm", (context) => {
|
|
11482
12839
|
const total = totalTokenCount(context.events);
|
|
11483
12840
|
return total > maxTotalTokens ? [
|
|
11484
|
-
|
|
12841
|
+
fail2(
|
|
11485
12842
|
"eval.maxTotalTokens",
|
|
11486
12843
|
`Total token usage exceeded ${maxTotalTokens}.`,
|
|
11487
12844
|
evidenceForRun(context.run, "tokenUsage.total"),
|
|
@@ -11496,7 +12853,7 @@ var checks = {
|
|
|
11496
12853
|
"eval.noFailedSteps",
|
|
11497
12854
|
"run",
|
|
11498
12855
|
(context) => context.nodes.filter((node) => node.event.status === "error" || node.event.kind === "ERROR").map(
|
|
11499
|
-
(node) =>
|
|
12856
|
+
(node) => fail2(
|
|
11500
12857
|
"eval.noFailedSteps",
|
|
11501
12858
|
"Run contains a failed step or error node.",
|
|
11502
12859
|
evidenceForEvent(node.event, "status"),
|
|
@@ -11514,7 +12871,7 @@ var checks = {
|
|
|
11514
12871
|
(node, index) => index < firstLlmIndex && node.event.kind === "RETRIEVER"
|
|
11515
12872
|
);
|
|
11516
12873
|
return retrievalIndex === -1 ? [
|
|
11517
|
-
|
|
12874
|
+
fail2(
|
|
11518
12875
|
"eval.requiredRetrievalBeforeGeneration",
|
|
11519
12876
|
"No retrieval step appeared before the first LLM generation.",
|
|
11520
12877
|
evidenceForEvent(context.nodes[firstLlmIndex].event, "kind"),
|
|
@@ -11530,7 +12887,7 @@ var checks = {
|
|
|
11530
12887
|
const decisions = context.nodes.filter((node) => node.event.kind === "DECISION");
|
|
11531
12888
|
if (decisions.length === 0) {
|
|
11532
12889
|
return [
|
|
11533
|
-
|
|
12890
|
+
fail2(
|
|
11534
12891
|
"eval.requiredDecisionMetadata",
|
|
11535
12892
|
"No decision node is available for required metadata.",
|
|
11536
12893
|
evidenceForRun(context.run, "children"),
|
|
@@ -11541,7 +12898,7 @@ var checks = {
|
|
|
11541
12898
|
}
|
|
11542
12899
|
return decisions.flatMap(
|
|
11543
12900
|
(node) => required.filter((key) => !hasAttribute(node, key)).map(
|
|
11544
|
-
(key) =>
|
|
12901
|
+
(key) => fail2(
|
|
11545
12902
|
"eval.requiredDecisionMetadata",
|
|
11546
12903
|
`Decision metadata ${key} is missing.`,
|
|
11547
12904
|
evidenceForEvent(node.event, `attributes.${key}`),
|
|
@@ -11562,7 +12919,7 @@ var checks = {
|
|
|
11562
12919
|
const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
|
|
11563
12920
|
if (answers.length === 0 || contexts.length === 0) {
|
|
11564
12921
|
return [
|
|
11565
|
-
|
|
12922
|
+
fail2(
|
|
11566
12923
|
"eval.contextOverlap",
|
|
11567
12924
|
"Answer and context text are required for overlap evaluation.",
|
|
11568
12925
|
firstEvidence(answers.length > 0 ? answers : contexts, context.run, "children"),
|
|
@@ -11576,7 +12933,7 @@ var checks = {
|
|
|
11576
12933
|
const sharedTerms = [...answerTerms].filter((term) => contextTerms.has(term)).length;
|
|
11577
12934
|
const overlap = answerTerms.size === 0 ? 0 : sharedTerms / answerTerms.size;
|
|
11578
12935
|
return sharedTerms < minSharedTerms || overlap < minOverlap ? [
|
|
11579
|
-
|
|
12936
|
+
fail2(
|
|
11580
12937
|
"eval.contextOverlap",
|
|
11581
12938
|
"Answer text did not sufficiently overlap retrieved context.",
|
|
11582
12939
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11604,7 +12961,7 @@ var checks = {
|
|
|
11604
12961
|
const quotes = quotedSnippets(answerText, minQuoteLength);
|
|
11605
12962
|
if (quotes.length === 0) {
|
|
11606
12963
|
return requireQuote ? [
|
|
11607
|
-
|
|
12964
|
+
fail2(
|
|
11608
12965
|
"eval.quoteOverlap",
|
|
11609
12966
|
"Answer did not contain a quote for overlap evaluation.",
|
|
11610
12967
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11615,7 +12972,7 @@ var checks = {
|
|
|
11615
12972
|
}
|
|
11616
12973
|
const missing = quotes.filter((quote) => !contextText.includes(quote.toLowerCase()));
|
|
11617
12974
|
return missing.length > 0 ? [
|
|
11618
|
-
|
|
12975
|
+
fail2(
|
|
11619
12976
|
"eval.quoteOverlap",
|
|
11620
12977
|
"Quoted answer text did not appear in retrieved context.",
|
|
11621
12978
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11633,7 +12990,7 @@ var checks = {
|
|
|
11633
12990
|
const citations = collectTextFields(context.nodes, citationKeys);
|
|
11634
12991
|
const count = citationCount(answers.map((field) => field.text).join(" "), citations);
|
|
11635
12992
|
return count === 0 ? [
|
|
11636
|
-
|
|
12993
|
+
fail2(
|
|
11637
12994
|
"eval.citationPresence",
|
|
11638
12995
|
"Answer did not include citations or source references.",
|
|
11639
12996
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11651,7 +13008,7 @@ var checks = {
|
|
|
11651
13008
|
const availableSet = new Set(available);
|
|
11652
13009
|
const missing = expected.filter((id) => !availableSet.has(id));
|
|
11653
13010
|
return missing.length > 0 ? [
|
|
11654
|
-
|
|
13011
|
+
fail2(
|
|
11655
13012
|
"eval.requiredSourceIds",
|
|
11656
13013
|
"Required source IDs were not present in trace context or citations.",
|
|
11657
13014
|
evidenceForRun(context.run, "children"),
|
|
@@ -11671,7 +13028,7 @@ var checks = {
|
|
|
11671
13028
|
const tooShort = options.minCharacters !== void 0 && characters < options.minCharacters || options.minWords !== void 0 && words < options.minWords;
|
|
11672
13029
|
const tooLong = options.maxCharacters !== void 0 && characters > options.maxCharacters || options.maxWords !== void 0 && words > options.maxWords;
|
|
11673
13030
|
return answer.length === 0 || tooShort || tooLong ? [
|
|
11674
|
-
|
|
13031
|
+
fail2(
|
|
11675
13032
|
"eval.answerLengthBounds",
|
|
11676
13033
|
"Answer length fell outside required bounds.",
|
|
11677
13034
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -11694,7 +13051,7 @@ var checks = {
|
|
|
11694
13051
|
const answer = answers.map((field) => field.text).join(" ").toLowerCase();
|
|
11695
13052
|
const matches = banned.filter((phrase) => answer.includes(phrase));
|
|
11696
13053
|
return matches.length > 0 ? [
|
|
11697
|
-
|
|
13054
|
+
fail2(
|
|
11698
13055
|
"eval.bannedUnsupportedPhrases",
|
|
11699
13056
|
"Answer contained banned unsupported-answer phrasing.",
|
|
11700
13057
|
firstEvidence(answers, context.run, "attributes.answer"),
|
|
@@ -12072,7 +13429,7 @@ function invalidArgumentResult(command, error) {
|
|
|
12072
13429
|
});
|
|
12073
13430
|
}
|
|
12074
13431
|
function buildSafetyRules(options) {
|
|
12075
|
-
const
|
|
13432
|
+
const maxStringLength2 = parseLimit3(options.maxStringLength, "--max-string-length") ?? DEFAULT_MAX_STRING_LENGTH;
|
|
12076
13433
|
const maxArrayLength = parseLimit3(options.maxArrayLength, "--max-array-length") ?? DEFAULT_MAX_ARRAY_LENGTH;
|
|
12077
13434
|
const maxObjectKeys = parseLimit3(options.maxObjectKeys, "--max-object-keys") ?? DEFAULT_MAX_OBJECT_KEYS;
|
|
12078
13435
|
const maxSerializedBytes = parseLimit3(options.maxSerializedBytes, "--max-serialized-bytes") ?? DEFAULT_MAX_SERIALIZED_BYTES;
|
|
@@ -12081,7 +13438,7 @@ function buildSafetyRules(options) {
|
|
|
12081
13438
|
createSafetyRedactionRule(),
|
|
12082
13439
|
createSafetySecretPatternRule(),
|
|
12083
13440
|
createSafetyOversizedAttributeRule({
|
|
12084
|
-
maxStringLength,
|
|
13441
|
+
maxStringLength: maxStringLength2,
|
|
12085
13442
|
maxArrayLength,
|
|
12086
13443
|
maxObjectKeys,
|
|
12087
13444
|
maxSerializedBytes
|
|
@@ -12962,7 +14319,18 @@ function createCliProgram() {
|
|
|
12962
14319
|
]).option("--allowed-model <model>", "allow an LLM model (repeatable)", (value, previous = []) => [
|
|
12963
14320
|
...previous,
|
|
12964
14321
|
value
|
|
12965
|
-
]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").
|
|
14322
|
+
]).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(
|
|
14323
|
+
"--correlate-group",
|
|
14324
|
+
"when using --session, also match synthetic group: session keys"
|
|
14325
|
+
).option(
|
|
14326
|
+
"--guardrails <rule>",
|
|
14327
|
+
"run optional guardrail rules (repeatable): banned-phrase, pii-leak, prompt-injection, ...",
|
|
14328
|
+
(value, previous = []) => [...previous, value]
|
|
14329
|
+
).option(
|
|
14330
|
+
"--circuit <rule>",
|
|
14331
|
+
"run optional circuit rules (repeatable): same-tool-repetition, max-retries, ...",
|
|
14332
|
+
(value, previous = []) => [...previous, value]
|
|
14333
|
+
).action((target, opts) => {
|
|
12966
14334
|
runCommand(() => checkCommand(target, opts));
|
|
12967
14335
|
});
|
|
12968
14336
|
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(
|
|
@@ -13066,9 +14434,21 @@ function createCliProgram() {
|
|
|
13066
14434
|
).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(
|
|
13067
14435
|
"--duration <expr>",
|
|
13068
14436
|
"duration filter on run or step (e.g. >5s, >=500ms)"
|
|
13069
|
-
).option("--limit <number>", "max results (default 50)").option("--
|
|
14437
|
+
).option("--limit <number>", "max results (default 50)").option("--session <id>", "limit search to one workflow session").option(
|
|
14438
|
+
"--correlate-group",
|
|
14439
|
+
"when using --session, also match synthetic group: keys"
|
|
14440
|
+
).option("--json", "print results as JSON").action((opts) => {
|
|
13070
14441
|
runCommand(() => searchCommand(opts));
|
|
13071
14442
|
});
|
|
14443
|
+
program.command("sessions").description("List workflow sessions grouped from local trace metadata (read-only)").option("--dir <path>", "trace directory").option(
|
|
14444
|
+
"--correlate-group",
|
|
14445
|
+
"treat shared groupId as a synthetic session when sessionId is absent"
|
|
14446
|
+
).option("--json", "print sessions index as JSON").action((opts) => {
|
|
14447
|
+
runCommand(() => sessionsCommand(opts));
|
|
14448
|
+
});
|
|
14449
|
+
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) => {
|
|
14450
|
+
runCommand(() => sessionCommand(sessionId, opts));
|
|
14451
|
+
});
|
|
13072
14452
|
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) => {
|
|
13073
14453
|
runCommand(() => whatCommand(runId, opts));
|
|
13074
14454
|
});
|