agent-inspect 2.3.0 → 2.4.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 (42) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +33 -4
  3. package/docs/ADAPTERS.md +47 -0
  4. package/docs/CLI.md +54 -1
  5. package/docs/SCHEMA.md +1 -0
  6. package/package.json +2 -2
  7. package/packages/cli/dist/index.cjs +757 -14
  8. package/packages/cli/dist/index.cjs.map +1 -1
  9. package/packages/cli/dist/index.mjs +757 -14
  10. package/packages/cli/dist/index.mjs.map +1 -1
  11. package/packages/core/dist/advanced.cjs +581 -3
  12. package/packages/core/dist/advanced.cjs.map +1 -1
  13. package/packages/core/dist/advanced.d.cts +172 -8
  14. package/packages/core/dist/advanced.d.ts +172 -8
  15. package/packages/core/dist/advanced.mjs +572 -4
  16. package/packages/core/dist/advanced.mjs.map +1 -1
  17. package/packages/core/dist/checks.d.cts +2 -2
  18. package/packages/core/dist/checks.d.ts +2 -2
  19. package/packages/core/dist/{context-yv2VSDQF.d.ts → context-BDZmi53V.d.ts} +3 -3
  20. package/packages/core/dist/{context-CSKnzpXR.d.cts → context-DjY-jvQk.d.cts} +3 -3
  21. package/packages/core/dist/diff.d.cts +3 -3
  22. package/packages/core/dist/diff.d.ts +3 -3
  23. package/packages/core/dist/exporters.d.cts +3 -3
  24. package/packages/core/dist/exporters.d.ts +3 -3
  25. package/packages/core/dist/index.d.cts +6 -6
  26. package/packages/core/dist/index.d.ts +6 -6
  27. package/packages/core/dist/{inspect-event-CevRYp58.d.cts → inspect-event-CYAV7Hxm.d.cts} +1 -1
  28. package/packages/core/dist/{inspect-event-CevRYp58.d.ts → inspect-event-CYAV7Hxm.d.ts} +1 -1
  29. package/packages/core/dist/{log-config-8aE5Kxtr.d.cts → log-config-BNQ9UTmP.d.cts} +1 -1
  30. package/packages/core/dist/{log-config-DVimQJho.d.ts → log-config-D3Yp1SZN.d.ts} +1 -1
  31. package/packages/core/dist/logs.d.cts +3 -3
  32. package/packages/core/dist/logs.d.ts +3 -3
  33. package/packages/core/dist/{persisted-inspect-event-D-WpXeZX.d.ts → persisted-inspect-event-BLvb0jSX.d.ts} +1 -1
  34. package/packages/core/dist/{persisted-inspect-event-DHcHPUKv.d.cts → persisted-inspect-event-D5SjBz9b.d.cts} +1 -1
  35. package/packages/core/dist/persisted.d.cts +5 -5
  36. package/packages/core/dist/persisted.d.ts +5 -5
  37. package/packages/core/dist/readers.d.cts +2 -2
  38. package/packages/core/dist/readers.d.ts +2 -2
  39. package/packages/core/dist/{types-DgMN3qow.d.cts → types-C-llnPH0.d.cts} +1 -1
  40. package/packages/core/dist/{types-D-Y1kOU-.d.ts → types-CQun9LW6.d.ts} +1 -1
  41. package/packages/core/dist/writers.d.cts +2 -2
  42. package/packages/core/dist/writers.d.ts +2 -2
@@ -23,7 +23,7 @@ var process2__default = /*#__PURE__*/_interopDefault(process2);
23
23
  var tty__default = /*#__PURE__*/_interopDefault(tty);
24
24
 
25
25
  // package.json
26
- var version = "2.3.0";
26
+ var version = "2.4.0";
27
27
 
28
28
  // packages/core/src/correlation-metadata.ts
29
29
  var TRACE_CORRELATION_KEYS = [
@@ -2540,10 +2540,12 @@ async function searchTraces(metas, options) {
2540
2540
  durationFilter = parseDurationFilter(options.duration);
2541
2541
  }
2542
2542
  const limit = options.limit ?? 50;
2543
+ const sessionId = options.session?.trim();
2543
2544
  const hasContentFilter = Boolean(
2544
2545
  options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter
2545
2546
  );
2546
2547
  const results = [];
2548
+ const sessionLabel = sessionId && sessionId !== "" ? sessionId : void 0;
2547
2549
  if (!hasContentFilter) {
2548
2550
  for (const m of filtered) {
2549
2551
  results.push({
@@ -2552,9 +2554,10 @@ async function searchTraces(metas, options) {
2552
2554
  runStatus: m.status,
2553
2555
  timestamp: m.startedAt,
2554
2556
  durationMs: m.durationMs,
2555
- matchReason: "trace in directory",
2556
- matchedFields: ["run"],
2557
- filePath: m.filePath
2557
+ matchReason: sessionLabel ? `trace in session ${sessionLabel}` : "trace in directory",
2558
+ matchedFields: sessionLabel ? ["run", "session"] : ["run"],
2559
+ filePath: m.filePath,
2560
+ ...sessionLabel ? { sessionId: sessionLabel } : {}
2558
2561
  });
2559
2562
  }
2560
2563
  return results.slice(0, limit);
@@ -2693,6 +2696,478 @@ async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
2693
2696
  }
2694
2697
  return metas;
2695
2698
  }
2699
+
2700
+ // packages/core/src/sessions/metadata.ts
2701
+ function isNonEmptyString3(value) {
2702
+ return typeof value === "string" && value.trim() !== "";
2703
+ }
2704
+ function finitePositiveInt(value) {
2705
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
2706
+ return void 0;
2707
+ }
2708
+ return Math.trunc(value);
2709
+ }
2710
+ function extractSessionWorkflowMetadata(record) {
2711
+ if (!record) return void 0;
2712
+ const out = {};
2713
+ let found = false;
2714
+ const assignString = (key, value) => {
2715
+ if (isNonEmptyString3(value)) {
2716
+ out[key] = value.trim();
2717
+ found = true;
2718
+ }
2719
+ };
2720
+ assignString("sessionId", record.sessionId);
2721
+ assignString("conversationId", record.conversationId);
2722
+ assignString("groupId", record.groupId);
2723
+ assignString("parentGroupId", record.parentGroupId);
2724
+ assignString("retryOf", record.retryOf);
2725
+ assignString("retryReason", record.retryReason);
2726
+ assignString("handoffFrom", record.handoffFrom);
2727
+ assignString("handoffTo", record.handoffTo);
2728
+ assignString("subAgentId", record.subAgentId);
2729
+ assignString("subAgentName", record.subAgentName);
2730
+ assignString("jobId", record.jobId);
2731
+ assignString("queueName", record.queueName);
2732
+ assignString("workflowName", record.workflowName);
2733
+ assignString("workflowStep", record.workflowStep);
2734
+ assignString("toolCallId", record.toolCallId);
2735
+ assignString("mcpToolCallId", record.mcpToolCallId);
2736
+ assignString("linkedStepId", record.linkedStepId);
2737
+ assignString("correlationId", record.correlationId);
2738
+ assignString("requestId", record.requestId);
2739
+ assignString("decisionId", record.decisionId);
2740
+ const attempt = finitePositiveInt(record.attempt);
2741
+ if (attempt !== void 0) {
2742
+ out.attempt = attempt;
2743
+ found = true;
2744
+ }
2745
+ return found ? out : void 0;
2746
+ }
2747
+ function sessionKeyForRun(meta, options) {
2748
+ if (meta?.sessionId) return meta.sessionId;
2749
+ if (options?.correlateByGroupId && meta?.groupId) {
2750
+ return `group:${meta.groupId}`;
2751
+ }
2752
+ return void 0;
2753
+ }
2754
+
2755
+ // packages/core/src/sessions/load.ts
2756
+ async function enrichSessionRunRecord(meta) {
2757
+ let metadata;
2758
+ try {
2759
+ const events = await readTraceEventsFromFile(meta.filePath);
2760
+ for (const event of events) {
2761
+ if (event.event !== "run_started") continue;
2762
+ if (event.metadata && typeof event.metadata === "object") {
2763
+ metadata = event.metadata;
2764
+ }
2765
+ break;
2766
+ }
2767
+ } catch {
2768
+ }
2769
+ return {
2770
+ runId: meta.runId,
2771
+ name: meta.name,
2772
+ status: meta.status,
2773
+ startedAt: meta.startedAt,
2774
+ endedAt: meta.endedAt,
2775
+ durationMs: meta.durationMs,
2776
+ filePath: meta.filePath,
2777
+ metadata
2778
+ };
2779
+ }
2780
+ async function loadSessionRunRecords(metas) {
2781
+ const out = [];
2782
+ for (const meta of metas) {
2783
+ out.push(await enrichSessionRunRecord(meta));
2784
+ }
2785
+ return out;
2786
+ }
2787
+
2788
+ // packages/core/src/sessions/scope.ts
2789
+ function filterMetasBySessionScope(metas, records, options) {
2790
+ const sessionId = options.sessionId?.trim();
2791
+ const groupId = options.groupId?.trim();
2792
+ const warnings = [];
2793
+ if (sessionId) {
2794
+ const index = buildSessionIndex(records, {
2795
+ correlateByGroupId: options.correlateByGroupId === true
2796
+ });
2797
+ warnings.push(...index.warnings);
2798
+ const session = index.sessions.find((item) => item.sessionId === sessionId);
2799
+ if (!session) {
2800
+ return {
2801
+ metas: [],
2802
+ scopeLabel: sessionId,
2803
+ scopeKind: "session",
2804
+ runIds: [],
2805
+ warnings,
2806
+ notFound: true
2807
+ };
2808
+ }
2809
+ const runIdSet = new Set(session.runIds);
2810
+ const filtered = metas.filter((meta) => runIdSet.has(meta.runId));
2811
+ return {
2812
+ metas: filtered,
2813
+ scopeLabel: sessionId,
2814
+ scopeKind: "session",
2815
+ runIds: session.runIds,
2816
+ warnings,
2817
+ notFound: false
2818
+ };
2819
+ }
2820
+ if (groupId) {
2821
+ const runIds = records.filter((run) => extractSessionWorkflowMetadata(run.metadata)?.groupId === groupId).map((run) => run.runId).sort();
2822
+ if (runIds.length === 0) {
2823
+ return {
2824
+ metas: [],
2825
+ scopeLabel: groupId,
2826
+ scopeKind: "group",
2827
+ runIds: [],
2828
+ warnings,
2829
+ notFound: true
2830
+ };
2831
+ }
2832
+ const runIdSet = new Set(runIds);
2833
+ return {
2834
+ metas: metas.filter((meta) => runIdSet.has(meta.runId)),
2835
+ scopeLabel: groupId,
2836
+ scopeKind: "group",
2837
+ runIds,
2838
+ warnings,
2839
+ notFound: false
2840
+ };
2841
+ }
2842
+ return {
2843
+ metas: [...metas],
2844
+ scopeLabel: "",
2845
+ scopeKind: "session",
2846
+ runIds: [],
2847
+ warnings,
2848
+ notFound: false
2849
+ };
2850
+ }
2851
+
2852
+ // packages/core/src/sessions/checks.ts
2853
+ function emptySummary() {
2854
+ return { passed: 0, failed: 0, warnings: 0, errors: 0 };
2855
+ }
2856
+ function mergeSummary(target, source) {
2857
+ return {
2858
+ passed: target.passed + source.passed,
2859
+ failed: target.failed + source.failed,
2860
+ warnings: target.warnings + source.warnings,
2861
+ errors: target.errors + source.errors
2862
+ };
2863
+ }
2864
+ function sessionDiagnostic(code, message) {
2865
+ return { code, message, severity: "error" };
2866
+ }
2867
+ function aggregateSessionCheckResults(perRun, scope) {
2868
+ if (scope.notFound) {
2869
+ return {
2870
+ ok: false,
2871
+ status: "error",
2872
+ format: perRun[0]?.format ?? "agent-inspect-jsonl",
2873
+ scopeKind: scope.scopeKind,
2874
+ scopeLabel: scope.scopeLabel,
2875
+ runIds: [],
2876
+ runResults: [],
2877
+ summary: { ...emptySummary(), errors: 1 },
2878
+ findings: [],
2879
+ diagnostics: [
2880
+ sessionDiagnostic(
2881
+ "AI_CHECK_INVALID_ARGUMENTS",
2882
+ `${scope.scopeKind} not found: ${scope.scopeLabel}`
2883
+ )
2884
+ ],
2885
+ ...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
2886
+ };
2887
+ }
2888
+ if (scope.empty || perRun.length === 0) {
2889
+ return {
2890
+ ok: false,
2891
+ status: "error",
2892
+ format: perRun[0]?.format ?? "agent-inspect-jsonl",
2893
+ scopeKind: scope.scopeKind,
2894
+ scopeLabel: scope.scopeLabel,
2895
+ runIds: scope.runIds,
2896
+ runResults: [],
2897
+ summary: { ...emptySummary(), errors: 1 },
2898
+ findings: [],
2899
+ diagnostics: [
2900
+ sessionDiagnostic(
2901
+ "AI_CHECK_TRACE_UNREADABLE",
2902
+ `No readable traces in ${scope.scopeKind}: ${scope.scopeLabel}`
2903
+ )
2904
+ ],
2905
+ ...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
2906
+ };
2907
+ }
2908
+ let summary = emptySummary();
2909
+ const findings = [];
2910
+ const diagnostics = [];
2911
+ const runResults = [];
2912
+ for (const result of perRun) {
2913
+ summary = mergeSummary(summary, result.summary);
2914
+ findings.push(...result.findings);
2915
+ diagnostics.push(...result.diagnostics);
2916
+ if (result.runId) {
2917
+ runResults.push({ runId: result.runId, status: result.status });
2918
+ }
2919
+ }
2920
+ runResults.sort((a, b) => a.runId.localeCompare(b.runId));
2921
+ findings.sort((a, b) => {
2922
+ const runCmp = (a.evidence[0]?.runId ?? "").localeCompare(
2923
+ b.evidence[0]?.runId ?? ""
2924
+ );
2925
+ if (runCmp !== 0) return runCmp;
2926
+ return a.ruleId.localeCompare(b.ruleId);
2927
+ });
2928
+ const hasErrors = diagnostics.some((item) => item.severity === "error");
2929
+ const status = hasErrors ? "error" : summary.failed > 0 ? "fail" : "pass";
2930
+ return {
2931
+ ok: status === "pass",
2932
+ status,
2933
+ format: perRun[0]?.format ?? "agent-inspect-jsonl",
2934
+ scopeKind: scope.scopeKind,
2935
+ scopeLabel: scope.scopeLabel,
2936
+ runIds: scope.runIds,
2937
+ runResults,
2938
+ summary,
2939
+ findings,
2940
+ diagnostics,
2941
+ ...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
2942
+ };
2943
+ }
2944
+
2945
+ // packages/core/src/sessions/index.ts
2946
+ function compareRuns(a, b) {
2947
+ const aStart = a.startedAt ?? 0;
2948
+ const bStart = b.startedAt ?? 0;
2949
+ if (aStart !== bStart) return aStart - bStart;
2950
+ return a.runId.localeCompare(b.runId);
2951
+ }
2952
+ function buildGroups(runIds, metaByRunId) {
2953
+ const byGroup = /* @__PURE__ */ new Map();
2954
+ for (const runId of runIds) {
2955
+ const groupId = metaByRunId.get(runId)?.groupId;
2956
+ if (!groupId) continue;
2957
+ const existing = byGroup.get(groupId);
2958
+ if (existing) {
2959
+ existing.runIds.push(runId);
2960
+ } else {
2961
+ byGroup.set(groupId, {
2962
+ groupId,
2963
+ parentGroupId: metaByRunId.get(runId)?.parentGroupId,
2964
+ runIds: [runId]
2965
+ });
2966
+ }
2967
+ }
2968
+ return [...byGroup.values()].map((group) => ({
2969
+ ...group,
2970
+ runIds: [...group.runIds].sort((a, b) => a.localeCompare(b))
2971
+ }));
2972
+ }
2973
+ function buildHandoffs(runIds, metaByRunId, warnings, sessionId) {
2974
+ const edges = [];
2975
+ const seen = /* @__PURE__ */ new Set();
2976
+ const pushEdge = (edge) => {
2977
+ const key = `${edge.from}->${edge.to}:${edge.confidence}`;
2978
+ if (seen.has(key)) return;
2979
+ seen.add(key);
2980
+ edges.push(edge);
2981
+ };
2982
+ for (const runId of runIds) {
2983
+ const meta = metaByRunId.get(runId);
2984
+ if (!meta) continue;
2985
+ if (meta.handoffFrom && meta.handoffTo) {
2986
+ pushEdge({
2987
+ from: meta.handoffFrom,
2988
+ to: meta.handoffTo,
2989
+ source: "manual",
2990
+ confidence: "explicit"
2991
+ });
2992
+ continue;
2993
+ }
2994
+ if (meta.handoffFrom) {
2995
+ pushEdge({
2996
+ from: meta.handoffFrom,
2997
+ to: runId,
2998
+ source: "manual",
2999
+ confidence: "explicit"
3000
+ });
3001
+ }
3002
+ if (meta.handoffTo) {
3003
+ pushEdge({
3004
+ from: runId,
3005
+ to: meta.handoffTo,
3006
+ source: "manual",
3007
+ confidence: "explicit"
3008
+ });
3009
+ }
3010
+ if (meta.subAgentId && meta.parentGroupId && !meta.handoffFrom && !meta.handoffTo) {
3011
+ pushEdge({
3012
+ from: meta.parentGroupId,
3013
+ to: meta.subAgentId,
3014
+ source: "inferred",
3015
+ confidence: "correlated"
3016
+ });
3017
+ warnings.push({
3018
+ code: "ambiguous-handoff-endpoints",
3019
+ message: "Handoff inferred from parentGroupId and subAgentId without explicit handoffFrom/handoffTo.",
3020
+ runId,
3021
+ sessionId
3022
+ });
3023
+ }
3024
+ }
3025
+ return edges.sort((a, b) => {
3026
+ const from = a.from.localeCompare(b.from);
3027
+ if (from !== 0) return from;
3028
+ return a.to.localeCompare(b.to);
3029
+ });
3030
+ }
3031
+ function buildRetries(runIds, metaByRunId, warnings, sessionId) {
3032
+ const retries = [];
3033
+ for (const runId of runIds) {
3034
+ const meta = metaByRunId.get(runId);
3035
+ if (!meta) continue;
3036
+ if (meta.retryOf) {
3037
+ retries.push({
3038
+ runId,
3039
+ retryOf: meta.retryOf,
3040
+ attempt: meta.attempt,
3041
+ source: "manual",
3042
+ confidence: "explicit"
3043
+ });
3044
+ continue;
3045
+ }
3046
+ if (meta.attempt !== void 0 && meta.attempt > 1) {
3047
+ retries.push({
3048
+ runId,
3049
+ attempt: meta.attempt,
3050
+ source: "inferred",
3051
+ confidence: "correlated"
3052
+ });
3053
+ warnings.push({
3054
+ code: "ambiguous-retry-link",
3055
+ message: "attempt > 1 without retryOf; retry link is correlated only.",
3056
+ runId,
3057
+ sessionId
3058
+ });
3059
+ }
3060
+ }
3061
+ return retries.sort((a, b) => a.runId.localeCompare(b.runId));
3062
+ }
3063
+ function buildCriticalPath(runs, handoffs) {
3064
+ const runById = new Map(runs.map((run) => [run.runId, run]));
3065
+ const explicitTargets = new Set(
3066
+ handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.to)
3067
+ );
3068
+ const explicitSources = new Set(
3069
+ handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
3070
+ );
3071
+ const ordered = [...runs].sort(compareRuns);
3072
+ const path15 = [];
3073
+ const visited = /* @__PURE__ */ new Set();
3074
+ const pushRun = (run, confidence, source) => {
3075
+ if (visited.has(run.runId)) return;
3076
+ visited.add(run.runId);
3077
+ path15.push({
3078
+ runId: run.runId,
3079
+ name: run.name,
3080
+ startedAt: run.startedAt,
3081
+ durationMs: run.durationMs,
3082
+ confidence,
3083
+ source
3084
+ });
3085
+ };
3086
+ for (const edge of handoffs) {
3087
+ if (edge.confidence !== "explicit") continue;
3088
+ const fromRun = [...runById.values()].find(
3089
+ (run) => run.runId === edge.from || metaRunIdMatches(run, edge.from, runById)
3090
+ );
3091
+ const toRun = [...runById.values()].find(
3092
+ (run) => run.runId === edge.to || metaRunIdMatches(run, edge.to, runById)
3093
+ );
3094
+ if (fromRun) pushRun(fromRun, "explicit", "manual");
3095
+ if (toRun) pushRun(toRun, "explicit", "manual");
3096
+ }
3097
+ for (const run of ordered) {
3098
+ if (visited.has(run.runId)) continue;
3099
+ const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
3100
+ pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
3101
+ }
3102
+ return path15;
3103
+ }
3104
+ function metaRunIdMatches(run, token, runById) {
3105
+ const meta = extractSessionWorkflowMetadata(run.metadata);
3106
+ return meta?.subAgentId === token || meta?.groupId === token || runById.has(token);
3107
+ }
3108
+ function buildSessionIndex(inputRuns, options = {}) {
3109
+ const warnings = [];
3110
+ const runs = [...inputRuns].sort(compareRuns);
3111
+ const metaByRunId = /* @__PURE__ */ new Map();
3112
+ for (const run of runs) {
3113
+ metaByRunId.set(run.runId, extractSessionWorkflowMetadata(run.metadata));
3114
+ }
3115
+ const sessionsByKey = /* @__PURE__ */ new Map();
3116
+ const unscopedRunIds = [];
3117
+ for (const run of runs) {
3118
+ const meta = metaByRunId.get(run.runId);
3119
+ const key = sessionKeyForRun(meta, {
3120
+ correlateByGroupId: options.correlateByGroupId === true
3121
+ });
3122
+ if (!key) {
3123
+ unscopedRunIds.push(run.runId);
3124
+ continue;
3125
+ }
3126
+ const bucket = sessionsByKey.get(key) ?? [];
3127
+ bucket.push(run);
3128
+ sessionsByKey.set(key, bucket);
3129
+ }
3130
+ const sessions = [...sessionsByKey.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([sessionId, sessionRuns]) => {
3131
+ const runIds = sessionRuns.map((run) => run.runId).sort();
3132
+ const handoffs = buildHandoffs(runIds, metaByRunId, warnings, sessionId);
3133
+ const retries = buildRetries(runIds, metaByRunId, warnings, sessionId);
3134
+ const groups = buildGroups(runIds, metaByRunId);
3135
+ const criticalPath = buildCriticalPath(sessionRuns, handoffs);
3136
+ const confidences = new Set(handoffs.map((edge) => edge.confidence));
3137
+ if (confidences.has("explicit") && confidences.has("correlated")) {
3138
+ warnings.push({
3139
+ code: "mixed-confidence-group",
3140
+ message: "Session aggregates explicit and correlated handoff edges.",
3141
+ sessionId
3142
+ });
3143
+ }
3144
+ return {
3145
+ sessionId,
3146
+ runIds,
3147
+ groups,
3148
+ handoffs,
3149
+ retries,
3150
+ criticalPath
3151
+ };
3152
+ });
3153
+ if (sessions.length === 0 && runs.length > 0) {
3154
+ warnings.push({
3155
+ code: "missing-session-id",
3156
+ message: "No sessionId (or correlated groupId) found on input runs."
3157
+ });
3158
+ }
3159
+ warnings.sort((a, b) => {
3160
+ const code = a.code.localeCompare(b.code);
3161
+ if (code !== 0) return code;
3162
+ return (a.runId ?? "").localeCompare(b.runId ?? "");
3163
+ });
3164
+ return {
3165
+ runs,
3166
+ sessions,
3167
+ unscopedRunIds: unscopedRunIds.sort(),
3168
+ warnings
3169
+ };
3170
+ }
2696
3171
  var KNOWN_EVENTS = /* @__PURE__ */ new Set([
2697
3172
  "run_started",
2698
3173
  "run_completed",
@@ -3714,13 +4189,13 @@ function persistedEventsForParsedTrace(parsed) {
3714
4189
  function isRecord8(value) {
3715
4190
  return typeof value === "object" && value !== null && !Array.isArray(value);
3716
4191
  }
3717
- function isNonEmptyString3(value) {
4192
+ function isNonEmptyString4(value) {
3718
4193
  return typeof value === "string" && value.trim() !== "";
3719
4194
  }
3720
4195
  function readStringField(record, keys) {
3721
4196
  for (const key of keys) {
3722
4197
  const value = record[key];
3723
- if (isNonEmptyString3(value)) return value;
4198
+ if (isNonEmptyString4(value)) return value;
3724
4199
  }
3725
4200
  return void 0;
3726
4201
  }
@@ -3854,7 +4329,7 @@ function parseUnixNanoToIso(value) {
3854
4329
  return void 0;
3855
4330
  }
3856
4331
  function parseIsoTime(value) {
3857
- if (!isNonEmptyString3(value)) return void 0;
4332
+ if (!isNonEmptyString4(value)) return void 0;
3858
4333
  const ms = Date.parse(value);
3859
4334
  if (!Number.isFinite(ms)) return void 0;
3860
4335
  return new Date(ms).toISOString();
@@ -8408,11 +8883,29 @@ async function searchCommand(options = {}) {
8408
8883
  parseDurationFilter(options.duration);
8409
8884
  }
8410
8885
  const files = await td.list();
8411
- const metas = await loadTraceMetadataList(
8886
+ let metas = await loadTraceMetadataList(
8412
8887
  traceDir,
8413
8888
  files,
8414
8889
  (f) => td.getPath(f)
8415
8890
  );
8891
+ const sessionId = options.session?.trim();
8892
+ if (sessionId) {
8893
+ const records = await loadSessionRunRecords(metas);
8894
+ const scoped = filterMetasBySessionScope(metas, records, {
8895
+ sessionId,
8896
+ correlateByGroupId: options.correlateGroup === true
8897
+ });
8898
+ if (scoped.notFound) {
8899
+ if (options.json) {
8900
+ console.log(JSON.stringify([], null, 2));
8901
+ } else {
8902
+ console.log(`Session not found: ${sessionId}`);
8903
+ console.log(`Trace directory: ${traceDir}`);
8904
+ }
8905
+ return;
8906
+ }
8907
+ metas = scoped.metas;
8908
+ }
8416
8909
  const status = options.status === "success" || options.status === "error" || options.status === "running" || options.status === "unknown" ? options.status : void 0;
8417
8910
  const results = await searchTraces(metas, {
8418
8911
  traceDir,
@@ -8423,7 +8916,9 @@ async function searchCommand(options = {}) {
8423
8916
  name: options.name,
8424
8917
  tool: options.tool,
8425
8918
  duration: options.duration,
8426
- limit: parseLimit2(options.limit)
8919
+ limit: parseLimit2(options.limit),
8920
+ ...sessionId ? { session: sessionId } : {},
8921
+ ...options.correlateGroup ? { correlateGroup: true } : {}
8427
8922
  });
8428
8923
  if (options.json) {
8429
8924
  console.log(JSON.stringify(results, null, 2));
@@ -8451,6 +8946,184 @@ async function searchCommand(options = {}) {
8451
8946
  }
8452
8947
  }
8453
8948
 
8949
+ // packages/cli/src/sessions.ts
8950
+ async function loadSessionIndex(traceDir, correlateGroup) {
8951
+ const td = new TraceDirectory({ dir: traceDir });
8952
+ const files = await td.list();
8953
+ const metas = await loadTraceMetadataList(
8954
+ traceDir,
8955
+ files,
8956
+ (fileName) => td.getPath(fileName)
8957
+ );
8958
+ const runs = await loadSessionRunRecords(metas);
8959
+ return buildSessionIndex(runs, { correlateByGroupId: correlateGroup === true });
8960
+ }
8961
+ function findSession(index, sessionId) {
8962
+ return index.sessions.find((session) => session.sessionId === sessionId);
8963
+ }
8964
+ function renderSessionsHuman(index, traceDir) {
8965
+ if (index.sessions.length === 0) {
8966
+ console.log("No sessions found");
8967
+ console.log(`Trace directory: ${traceDir}`);
8968
+ if (index.unscopedRunIds.length > 0) {
8969
+ console.log(
8970
+ `Unscoped runs (no sessionId): ${index.unscopedRunIds.join(", ")}`
8971
+ );
8972
+ }
8973
+ return;
8974
+ }
8975
+ console.log("Sessions:");
8976
+ for (const session of index.sessions) {
8977
+ const firstRun = index.runs.find(
8978
+ (run) => session.runIds.includes(run.runId)
8979
+ );
8980
+ const workflowName = firstRun?.metadata && typeof firstRun.metadata.workflowName === "string" ? firstRun.metadata.workflowName : void 0;
8981
+ const suffix = workflowName ? ` workflow=${workflowName}` : "";
8982
+ console.log(
8983
+ ` ${session.sessionId} (${session.runIds.length} run${session.runIds.length === 1 ? "" : "s"})${suffix}`
8984
+ );
8985
+ }
8986
+ if (index.unscopedRunIds.length > 0) {
8987
+ console.log("");
8988
+ console.log(
8989
+ `Unscoped runs (no sessionId): ${index.unscopedRunIds.join(", ")}`
8990
+ );
8991
+ }
8992
+ }
8993
+ function renderSessionHuman(session, index, options) {
8994
+ console.log(`Session: ${session.sessionId}`);
8995
+ console.log(`Runs: ${session.runIds.join(", ")}`);
8996
+ if (session.handoffs.length > 0) {
8997
+ console.log("");
8998
+ console.log("Handoffs:");
8999
+ for (const edge of session.handoffs) {
9000
+ console.log(
9001
+ ` ${edge.from} -> ${edge.to} (${edge.confidence}, ${edge.source})`
9002
+ );
9003
+ }
9004
+ }
9005
+ if (session.retries.length > 0) {
9006
+ console.log("");
9007
+ console.log("Retries:");
9008
+ for (const retry of session.retries) {
9009
+ const attempt = retry.attempt !== void 0 ? ` attempt=${retry.attempt}` : "";
9010
+ const of = retry.retryOf !== void 0 ? ` retryOf=${retry.retryOf}` : "";
9011
+ console.log(
9012
+ ` ${retry.runId}${attempt}${of} (${retry.confidence})`
9013
+ );
9014
+ }
9015
+ }
9016
+ if (options.criticalPath && session.criticalPath.length > 0) {
9017
+ console.log("");
9018
+ console.log("Critical path:");
9019
+ for (const step of session.criticalPath) {
9020
+ const duration = step.durationMs !== void 0 ? ` ${step.durationMs}ms` : "";
9021
+ console.log(
9022
+ ` ${step.runId}${step.name ? ` (${step.name})` : ""}${duration} [${step.confidence}]`
9023
+ );
9024
+ }
9025
+ }
9026
+ if (options.diagnostics && index.warnings.length > 0) {
9027
+ const scoped = index.warnings.filter(
9028
+ (warning) => warning.sessionId === session.sessionId || !warning.sessionId
9029
+ );
9030
+ if (scoped.length > 0) {
9031
+ console.log("");
9032
+ console.log("Diagnostics:");
9033
+ for (const warning of scoped) {
9034
+ const run = warning.runId ? ` run=${warning.runId}` : "";
9035
+ console.log(` [${warning.code}]${run} ${warning.message}`);
9036
+ }
9037
+ }
9038
+ }
9039
+ }
9040
+ async function sessionsCommand(options = {}) {
9041
+ try {
9042
+ const traceDir = resolveTraceDir({ dir: options.dir });
9043
+ const index = await loadSessionIndex(traceDir, options.correlateGroup);
9044
+ if (options.json) {
9045
+ console.log(
9046
+ JSON.stringify(
9047
+ {
9048
+ traceDir,
9049
+ sessions: index.sessions,
9050
+ unscopedRunIds: index.unscopedRunIds,
9051
+ warnings: index.warnings
9052
+ },
9053
+ null,
9054
+ 2
9055
+ )
9056
+ );
9057
+ return;
9058
+ }
9059
+ renderSessionsHuman(index, traceDir);
9060
+ } catch (e) {
9061
+ const msg = e instanceof Error ? e.message : String(e);
9062
+ console.error(`[AgentInspect] sessions failed: ${msg}`);
9063
+ process.exitCode = 1;
9064
+ }
9065
+ }
9066
+ async function sessionCommand(sessionId, options = {}) {
9067
+ const id = typeof sessionId === "string" && sessionId.trim() !== "" ? sessionId.trim() : "";
9068
+ if (id === "") {
9069
+ console.error("Session id is required");
9070
+ process.exitCode = 1;
9071
+ return;
9072
+ }
9073
+ try {
9074
+ const traceDir = resolveTraceDir({ dir: options.dir });
9075
+ const index = await loadSessionIndex(traceDir);
9076
+ const session = findSession(index, id);
9077
+ if (!session) {
9078
+ console.log(`Session not found: ${id}`);
9079
+ console.log(`Trace directory: ${traceDir}`);
9080
+ process.exitCode = 1;
9081
+ return;
9082
+ }
9083
+ if (options.json) {
9084
+ const timelines = {};
9085
+ if (options.timeline) {
9086
+ for (const runId of session.runIds) {
9087
+ const result = await readRunTraceEvents(runId, traceDir);
9088
+ if (result && result.events.length > 0) {
9089
+ timelines[runId] = buildRunTimeline(result.events);
9090
+ }
9091
+ }
9092
+ }
9093
+ console.log(
9094
+ JSON.stringify(
9095
+ {
9096
+ traceDir,
9097
+ session,
9098
+ warnings: index.warnings.filter(
9099
+ (warning) => warning.sessionId === id || warning.sessionId === void 0
9100
+ ),
9101
+ ...options.timeline ? { timelines } : {}
9102
+ },
9103
+ null,
9104
+ 2
9105
+ )
9106
+ );
9107
+ return;
9108
+ }
9109
+ renderSessionHuman(session, index, options);
9110
+ if (options.timeline) {
9111
+ for (const runId of session.runIds) {
9112
+ const result = await readRunTraceEvents(runId, traceDir);
9113
+ if (!result || result.events.length === 0) continue;
9114
+ const timeline = buildRunTimeline(result.events);
9115
+ console.log("");
9116
+ console.log(`Timeline: ${runId}`);
9117
+ console.log(renderTimeline(timeline));
9118
+ }
9119
+ }
9120
+ } catch (e) {
9121
+ const msg = e instanceof Error ? e.message : String(e);
9122
+ console.error(`[AgentInspect] session failed: ${msg}`);
9123
+ process.exitCode = 1;
9124
+ }
9125
+ }
9126
+
8454
9127
  // packages/cli/src/what.ts
8455
9128
  async function whatCommand(runId, options = {}) {
8456
9129
  const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
@@ -9597,7 +10270,7 @@ function diagnostic(code, message, ruleId) {
9597
10270
  ...ruleId ? { ruleId } : {}
9598
10271
  };
9599
10272
  }
9600
- function emptySummary() {
10273
+ function emptySummary2() {
9601
10274
  return {
9602
10275
  passed: 0,
9603
10276
  failed: 0,
@@ -9612,7 +10285,7 @@ function errorResult(input3, diagnostics, selectedRun) {
9612
10285
  format: input3.read.format,
9613
10286
  ...selectedRun ? { runId: selectedRun.runId } : {},
9614
10287
  summary: {
9615
- ...emptySummary(),
10288
+ ...emptySummary2(),
9616
10289
  errors: diagnostics.filter((item) => item.severity === "error").length
9617
10290
  },
9618
10291
  findings: [],
@@ -10953,6 +11626,13 @@ function printJson(result) {
10953
11626
  console.log(JSON.stringify(stable2(result), null, 2));
10954
11627
  }
10955
11628
  function printHuman(result) {
11629
+ const scoped = result;
11630
+ if (scoped.scopeLabel) {
11631
+ console.log(`Scope: ${scoped.scopeKind} ${scoped.scopeLabel}`);
11632
+ if (scoped.runIds?.length) {
11633
+ console.log(`Runs: ${scoped.runIds.join(", ")}`);
11634
+ }
11635
+ }
10956
11636
  console.log(`Check status: ${result.status}`);
10957
11637
  console.log(`Format: ${result.format}`);
10958
11638
  if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
@@ -10964,7 +11644,9 @@ function printHuman(result) {
10964
11644
  }
10965
11645
  for (const finding of result.findings) {
10966
11646
  const path15 = finding.evidence[0]?.path;
10967
- console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
11647
+ const run = finding.evidence[0]?.runId;
11648
+ const runPrefix = run ? `[${run}] ` : "";
11649
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
10968
11650
  }
10969
11651
  }
10970
11652
  function readErrorResult(error) {
@@ -10980,12 +11662,58 @@ function readErrorResult(error) {
10980
11662
  async function checkCommand(target, options = {}, stdin = process.stdin) {
10981
11663
  let result;
10982
11664
  let phase = "config";
11665
+ const sessionId = options.session?.trim();
11666
+ const groupId = options.group?.trim();
11667
+ const useSessionScope = Boolean(sessionId || groupId);
10983
11668
  try {
10984
11669
  const config = await loadConfig(options.config);
10985
11670
  const built = buildRules(config, options);
10986
11671
  if (built.diagnostics.some((item) => item.severity === "error")) {
10987
11672
  result = errorResult2("AI_CHECK_INVALID_CONFIG", "Invalid check configuration.");
10988
11673
  result.diagnostics = [...built.diagnostics];
11674
+ } else if (useSessionScope) {
11675
+ phase = "read";
11676
+ const traceDir = resolveTraceDir({ dir: options.dir });
11677
+ const td = new TraceDirectory({ dir: traceDir });
11678
+ const files = await td.list();
11679
+ const metas = await loadTraceMetadataList(
11680
+ traceDir,
11681
+ files,
11682
+ (fileName) => td.getPath(fileName)
11683
+ );
11684
+ const records = await loadSessionRunRecords(metas);
11685
+ const scoped = filterMetasBySessionScope(metas, records, {
11686
+ ...sessionId ? { sessionId } : {},
11687
+ ...groupId ? { groupId } : {},
11688
+ correlateByGroupId: options.correlateGroup === true
11689
+ });
11690
+ const perRun = [];
11691
+ for (const meta of scoped.metas) {
11692
+ const read = await openTrace(
11693
+ { type: "file", path: meta.filePath },
11694
+ {
11695
+ ...options.format !== void 0 ? { format: options.format } : {}
11696
+ }
11697
+ );
11698
+ perRun.push(
11699
+ runTraceChecks(
11700
+ { read },
11701
+ {
11702
+ rules: built.rules,
11703
+ select: built.select,
11704
+ runId: meta.runId
11705
+ }
11706
+ )
11707
+ );
11708
+ }
11709
+ result = aggregateSessionCheckResults(perRun, {
11710
+ scopeKind: scoped.scopeKind,
11711
+ scopeLabel: scoped.scopeLabel,
11712
+ runIds: scoped.runIds,
11713
+ sessionWarnings: scoped.warnings,
11714
+ notFound: scoped.notFound,
11715
+ empty: scoped.metas.length === 0
11716
+ });
10989
11717
  } else {
10990
11718
  phase = "read";
10991
11719
  const input3 = await inputFromTarget(target, options, stdin);
@@ -12973,7 +13701,10 @@ function createCliProgram() {
12973
13701
  ]).option("--allowed-model <model>", "allow an LLM model (repeatable)", (value, previous = []) => [
12974
13702
  ...previous,
12975
13703
  value
12976
- ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").action((target, opts) => {
13704
+ ]).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(
13705
+ "--correlate-group",
13706
+ "when using --session, also match synthetic group: session keys"
13707
+ ).action((target, opts) => {
12977
13708
  runCommand(() => checkCommand(target, opts));
12978
13709
  });
12979
13710
  program.command("eval").description("Run deterministic local evals against a trace").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
@@ -13077,9 +13808,21 @@ function createCliProgram() {
13077
13808
  ).option("--kind <kind>", "filter by step kind/type (llm, tool, logic, \u2026)").option("--type <type>", "alias for --kind on manual trace step type").option("--name <query>", "substring match on run or step name").option("--tool <query>", "substring match on tool step name or metadata.toolName").option(
13078
13809
  "--duration <expr>",
13079
13810
  "duration filter on run or step (e.g. >5s, >=500ms)"
13080
- ).option("--limit <number>", "max results (default 50)").option("--json", "print results as JSON").action((opts) => {
13811
+ ).option("--limit <number>", "max results (default 50)").option("--session <id>", "limit search to one workflow session").option(
13812
+ "--correlate-group",
13813
+ "when using --session, also match synthetic group: keys"
13814
+ ).option("--json", "print results as JSON").action((opts) => {
13081
13815
  runCommand(() => searchCommand(opts));
13082
13816
  });
13817
+ program.command("sessions").description("List workflow sessions grouped from local trace metadata (read-only)").option("--dir <path>", "trace directory").option(
13818
+ "--correlate-group",
13819
+ "treat shared groupId as a synthetic session when sessionId is absent"
13820
+ ).option("--json", "print sessions index as JSON").action((opts) => {
13821
+ runCommand(() => sessionsCommand(opts));
13822
+ });
13823
+ 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) => {
13824
+ runCommand(() => sessionCommand(sessionId, opts));
13825
+ });
13083
13826
  program.command("what").description("Concise human-readable summary of a single run (local JSONL)").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").option("--json", "print structured summary as JSON").option("--no-correlation", "omit correlation metadata from human output").action((runId, opts) => {
13084
13827
  runCommand(() => whatCommand(runId, opts));
13085
13828
  });