agent-inspect 2.2.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 (43) hide show
  1. package/CHANGELOG.md +31 -4
  2. package/README.md +47 -4
  3. package/docs/ADAPTER-CONFORMANCE.md +25 -5
  4. package/docs/ADAPTERS.md +102 -11
  5. package/docs/CLI.md +54 -1
  6. package/docs/SCHEMA.md +1 -0
  7. package/package.json +2 -2
  8. package/packages/cli/dist/index.cjs +757 -14
  9. package/packages/cli/dist/index.cjs.map +1 -1
  10. package/packages/cli/dist/index.mjs +757 -14
  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
@@ -12,7 +12,7 @@ import tty from 'tty';
12
12
  import { createInterface } from 'readline';
13
13
 
14
14
  // package.json
15
- var version = "2.2.0";
15
+ var version = "2.4.0";
16
16
 
17
17
  // packages/core/src/correlation-metadata.ts
18
18
  var TRACE_CORRELATION_KEYS = [
@@ -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",
@@ -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();
@@ -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: [],
@@ -10942,6 +11615,13 @@ function printJson(result) {
10942
11615
  console.log(JSON.stringify(stable2(result), null, 2));
10943
11616
  }
10944
11617
  function printHuman(result) {
11618
+ const scoped = result;
11619
+ if (scoped.scopeLabel) {
11620
+ console.log(`Scope: ${scoped.scopeKind} ${scoped.scopeLabel}`);
11621
+ if (scoped.runIds?.length) {
11622
+ console.log(`Runs: ${scoped.runIds.join(", ")}`);
11623
+ }
11624
+ }
10945
11625
  console.log(`Check status: ${result.status}`);
10946
11626
  console.log(`Format: ${result.format}`);
10947
11627
  if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
@@ -10953,7 +11633,9 @@ function printHuman(result) {
10953
11633
  }
10954
11634
  for (const finding of result.findings) {
10955
11635
  const path15 = finding.evidence[0]?.path;
10956
- console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
11636
+ const run = finding.evidence[0]?.runId;
11637
+ const runPrefix = run ? `[${run}] ` : "";
11638
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
10957
11639
  }
10958
11640
  }
10959
11641
  function readErrorResult(error) {
@@ -10969,12 +11651,58 @@ function readErrorResult(error) {
10969
11651
  async function checkCommand(target, options = {}, stdin = process.stdin) {
10970
11652
  let result;
10971
11653
  let phase = "config";
11654
+ const sessionId = options.session?.trim();
11655
+ const groupId = options.group?.trim();
11656
+ const useSessionScope = Boolean(sessionId || groupId);
10972
11657
  try {
10973
11658
  const config = await loadConfig(options.config);
10974
11659
  const built = buildRules(config, options);
10975
11660
  if (built.diagnostics.some((item) => item.severity === "error")) {
10976
11661
  result = errorResult2("AI_CHECK_INVALID_CONFIG", "Invalid check configuration.");
10977
11662
  result.diagnostics = [...built.diagnostics];
11663
+ } else if (useSessionScope) {
11664
+ phase = "read";
11665
+ const traceDir = resolveTraceDir({ dir: options.dir });
11666
+ const td = new TraceDirectory({ dir: traceDir });
11667
+ const files = await td.list();
11668
+ const metas = await loadTraceMetadataList(
11669
+ traceDir,
11670
+ files,
11671
+ (fileName) => td.getPath(fileName)
11672
+ );
11673
+ const records = await loadSessionRunRecords(metas);
11674
+ const scoped = filterMetasBySessionScope(metas, records, {
11675
+ ...sessionId ? { sessionId } : {},
11676
+ ...groupId ? { groupId } : {},
11677
+ correlateByGroupId: options.correlateGroup === true
11678
+ });
11679
+ const perRun = [];
11680
+ for (const meta of scoped.metas) {
11681
+ const read = await openTrace(
11682
+ { type: "file", path: meta.filePath },
11683
+ {
11684
+ ...options.format !== void 0 ? { format: options.format } : {}
11685
+ }
11686
+ );
11687
+ perRun.push(
11688
+ runTraceChecks(
11689
+ { read },
11690
+ {
11691
+ rules: built.rules,
11692
+ select: built.select,
11693
+ runId: meta.runId
11694
+ }
11695
+ )
11696
+ );
11697
+ }
11698
+ result = aggregateSessionCheckResults(perRun, {
11699
+ scopeKind: scoped.scopeKind,
11700
+ scopeLabel: scoped.scopeLabel,
11701
+ runIds: scoped.runIds,
11702
+ sessionWarnings: scoped.warnings,
11703
+ notFound: scoped.notFound,
11704
+ empty: scoped.metas.length === 0
11705
+ });
10978
11706
  } else {
10979
11707
  phase = "read";
10980
11708
  const input3 = await inputFromTarget(target, options, stdin);
@@ -12962,7 +13690,10 @@ function createCliProgram() {
12962
13690
  ]).option("--allowed-model <model>", "allow an LLM model (repeatable)", (value, previous = []) => [
12963
13691
  ...previous,
12964
13692
  value
12965
- ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").action((target, opts) => {
13693
+ ]).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(
13694
+ "--correlate-group",
13695
+ "when using --session, also match synthetic group: session keys"
13696
+ ).action((target, opts) => {
12966
13697
  runCommand(() => checkCommand(target, opts));
12967
13698
  });
12968
13699
  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 +13797,21 @@ function createCliProgram() {
13066
13797
  ).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
13798
  "--duration <expr>",
13068
13799
  "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) => {
13800
+ ).option("--limit <number>", "max results (default 50)").option("--session <id>", "limit search to one workflow session").option(
13801
+ "--correlate-group",
13802
+ "when using --session, also match synthetic group: keys"
13803
+ ).option("--json", "print results as JSON").action((opts) => {
13070
13804
  runCommand(() => searchCommand(opts));
13071
13805
  });
13806
+ program.command("sessions").description("List workflow sessions grouped from local trace metadata (read-only)").option("--dir <path>", "trace directory").option(
13807
+ "--correlate-group",
13808
+ "treat shared groupId as a synthetic session when sessionId is absent"
13809
+ ).option("--json", "print sessions index as JSON").action((opts) => {
13810
+ runCommand(() => sessionsCommand(opts));
13811
+ });
13812
+ 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) => {
13813
+ runCommand(() => sessionCommand(sessionId, opts));
13814
+ });
13072
13815
  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
13816
  runCommand(() => whatCommand(runId, opts));
13074
13817
  });