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.
Files changed (43) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +33 -4
  3. package/docs/ADAPTERS.md +47 -0
  4. package/docs/CLI.md +57 -1
  5. package/docs/LIMITATIONS.md +6 -0
  6. package/docs/SCHEMA.md +1 -0
  7. package/package.json +2 -2
  8. package/packages/cli/dist/index.cjs +1454 -74
  9. package/packages/cli/dist/index.cjs.map +1 -1
  10. package/packages/cli/dist/index.mjs +1455 -75
  11. package/packages/cli/dist/index.mjs.map +1 -1
  12. package/packages/core/dist/advanced.cjs +581 -3
  13. package/packages/core/dist/advanced.cjs.map +1 -1
  14. package/packages/core/dist/advanced.d.cts +172 -8
  15. package/packages/core/dist/advanced.d.ts +172 -8
  16. package/packages/core/dist/advanced.mjs +572 -4
  17. package/packages/core/dist/advanced.mjs.map +1 -1
  18. package/packages/core/dist/checks.d.cts +2 -2
  19. package/packages/core/dist/checks.d.ts +2 -2
  20. package/packages/core/dist/{context-yv2VSDQF.d.ts → context-BDZmi53V.d.ts} +3 -3
  21. package/packages/core/dist/{context-CSKnzpXR.d.cts → context-DjY-jvQk.d.cts} +3 -3
  22. package/packages/core/dist/diff.d.cts +3 -3
  23. package/packages/core/dist/diff.d.ts +3 -3
  24. package/packages/core/dist/exporters.d.cts +3 -3
  25. package/packages/core/dist/exporters.d.ts +3 -3
  26. package/packages/core/dist/index.d.cts +6 -6
  27. package/packages/core/dist/index.d.ts +6 -6
  28. package/packages/core/dist/{inspect-event-CevRYp58.d.cts → inspect-event-CYAV7Hxm.d.cts} +1 -1
  29. package/packages/core/dist/{inspect-event-CevRYp58.d.ts → inspect-event-CYAV7Hxm.d.ts} +1 -1
  30. package/packages/core/dist/{log-config-8aE5Kxtr.d.cts → log-config-BNQ9UTmP.d.cts} +1 -1
  31. package/packages/core/dist/{log-config-DVimQJho.d.ts → log-config-D3Yp1SZN.d.ts} +1 -1
  32. package/packages/core/dist/logs.d.cts +3 -3
  33. package/packages/core/dist/logs.d.ts +3 -3
  34. package/packages/core/dist/{persisted-inspect-event-D-WpXeZX.d.ts → persisted-inspect-event-BLvb0jSX.d.ts} +1 -1
  35. package/packages/core/dist/{persisted-inspect-event-DHcHPUKv.d.cts → persisted-inspect-event-D5SjBz9b.d.cts} +1 -1
  36. package/packages/core/dist/persisted.d.cts +5 -5
  37. package/packages/core/dist/persisted.d.ts +5 -5
  38. package/packages/core/dist/readers.d.cts +2 -2
  39. package/packages/core/dist/readers.d.ts +2 -2
  40. package/packages/core/dist/{types-DgMN3qow.d.cts → types-C-llnPH0.d.cts} +1 -1
  41. package/packages/core/dist/{types-D-Y1kOU-.d.ts → types-CQun9LW6.d.ts} +1 -1
  42. package/packages/core/dist/writers.d.cts +2 -2
  43. 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.3.0";
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 = (open2, close, parent) => {
1432
+ var createStyler = (open3, close, parent) => {
1433
1433
  let openAll;
1434
1434
  let closeAll;
1435
1435
  if (parent === void 0) {
1436
- openAll = open2;
1436
+ openAll = open3;
1437
1437
  closeAll = close;
1438
1438
  } else {
1439
- openAll = parent.openAll + open2;
1439
+ openAll = parent.openAll + open3;
1440
1440
  closeAll = close + parent.closeAll;
1441
1441
  }
1442
1442
  return {
1443
- open: open2,
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, durationMs, status, depth) {
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" && durationMs === void 0) {
1512
+ if (status === "running" && durationMs2 === void 0) {
1513
1513
  return `${ind}${source_default.yellow("\u23F3")} ${nm}`;
1514
1514
  }
1515
- const hasDur = durationMs !== void 0 && Number.isFinite(durationMs);
1516
- const dur = hasDur ? formatDuration2(durationMs) : void 0;
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 durationMs = explicitDurationMs ?? (startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt ? endedAt - startedAt : void 0);
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 durationMs = lastCompleted && isFiniteNumber(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
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, durationMs) {
3218
+ function durationCell(status, durationMs2) {
2744
3219
  if (status === "running" || status === "unknown") return "-";
2745
- if (durationMs !== void 0 && Number.isFinite(durationMs)) {
2746
- return formatDuration2(durationMs);
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 durationMs = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
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 isNonEmptyString3(value) {
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 (isNonEmptyString3(value)) return value;
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 (!isNonEmptyString3(value)) return void 0;
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 durationMs = durationBetweenIso(startedAt, endedAt);
4107
- if (durationMs !== void 0) {
4108
- event.durationMs = 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 durationMs = durationBetweenIso(startedAt, endedAt);
4694
- if (durationMs !== void 0) {
4695
- event.durationMs = 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 durationMs;
6175
+ let durationMs2;
5701
6176
  const durationKey = cfg.durationKey;
5702
6177
  if (durationKey) {
5703
6178
  const v = raw[durationKey];
5704
- if (isFiniteNumber2(v)) durationMs = v;
6179
+ if (isFiniteNumber2(v)) durationMs2 = v;
5705
6180
  } else if (isFiniteNumber2(raw.durationMs)) {
5706
- durationMs = raw.durationMs;
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
- ...durationMs !== void 0 ? { durationMs } : {},
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 durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
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 durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
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
- const metas = await loadTraceMetadataList(
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 emptySummary() {
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
- ...emptySummary(),
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 maxStringLength = options.maxStringLength ?? 4096;
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, maxStringLength);
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
- console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
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 = runTraceChecks(
10985
- { read },
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
- rules: built.rules,
10988
- select: built.select,
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 fail(ruleId, message, evidence, expected, actual) {
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
- fail(
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) => fail(
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) => fail(
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
- fail(
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
- fail(
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
- fail(
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
- fail(
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) => fail(
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
- fail(
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
- fail(
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) => fail(
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
- fail(
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
- fail(
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
- fail(
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
- fail(
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
- fail(
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
- fail(
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
- fail(
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
- fail(
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 maxStringLength = parseLimit3(options.maxStringLength, "--max-string-length") ?? DEFAULT_MAX_STRING_LENGTH;
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").action((target, opts) => {
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("--json", "print results as JSON").action((opts) => {
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
  });