@tangle-network/traces 0.8.16 → 0.8.18

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.
@@ -751,10 +751,10 @@ var ClaudeAdapter = class {
751
751
  let step = main.nextStep;
752
752
  for (const f of files) {
753
753
  if (!f.endsWith(".jsonl")) continue;
754
- const hash = basename2(f, ".jsonl");
754
+ const hash2 = basename2(f, ".jsonl");
755
755
  let meta = {};
756
756
  try {
757
- meta = JSON.parse(await readFile3(join3(subDir, `${hash}.meta.json`), "utf8"));
757
+ meta = JSON.parse(await readFile3(join3(subDir, `${hash2}.meta.json`), "utf8"));
758
758
  } catch {
759
759
  }
760
760
  const parent = meta.toolUseId && main.toolSpanByUseId.get(meta.toolUseId)?.span_id || `root:${traceId}`;
@@ -768,7 +768,7 @@ var ClaudeAdapter = class {
768
768
  traceId,
769
769
  agent: meta.agentType ? `subagent:${meta.agentType}` : "subagent",
770
770
  startStep: step,
771
- idPrefix: `${hash}:`,
771
+ idPrefix: `${hash2}:`,
772
772
  rootParent: parent
773
773
  });
774
774
  out.push(...parsed.spans);
@@ -2286,13 +2286,13 @@ function extractAbsolutePaths(spans) {
2286
2286
  let scannedChars = 0;
2287
2287
  for (const span2 of spans) {
2288
2288
  for (const value of [span2.name, span2.status.message, ...Object.values(span2.attributes)]) {
2289
- for (const evidence2 of pathsFromUnknown(value)) {
2290
- scannedChars += evidence2.path.length;
2289
+ for (const evidence3 of pathsFromUnknown(value)) {
2290
+ scannedChars += evidence3.path.length;
2291
2291
  if (scannedChars > MAX_SPAN_TEXT_CHARS) return paths;
2292
- const key = `${evidence2.source}:${evidence2.path}`;
2292
+ const key = `${evidence3.source}:${evidence3.path}`;
2293
2293
  if (seen.has(key)) continue;
2294
2294
  seen.add(key);
2295
- paths.push(evidence2);
2295
+ paths.push(evidence3);
2296
2296
  if (paths.length >= MAX_SPAN_PATH_CANDIDATES) return paths;
2297
2297
  }
2298
2298
  }
@@ -2346,8 +2346,8 @@ async function resolveSessionRepoAttrs(cwd, spans) {
2346
2346
  add(await repairDashedPath(cwd), "repaired-cwd");
2347
2347
  add(cwd, "ref-cwd");
2348
2348
  }
2349
- for (const evidence2 of extractAbsolutePaths(spans)) {
2350
- add(await repoCandidateForPath(evidence2.path), evidence2.source, evidence2.weight);
2349
+ for (const evidence3 of extractAbsolutePaths(spans)) {
2350
+ add(await repoCandidateForPath(evidence3.path), evidence3.source, evidence3.weight);
2351
2351
  }
2352
2352
  const resolved = [];
2353
2353
  for (const candidate of candidates.values()) {
@@ -2492,9 +2492,9 @@ async function buildPolicyEvidenceRecord(ref, spans, opts = {}) {
2492
2492
  signals: {
2493
2493
  stuckLoopCount: loopFindings.length,
2494
2494
  affectedRunRatio: pipelines.stuckLoops.affectedRunRatio,
2495
- stuckLoops: loopFindings.slice(0, loopLimit).map((finding) => ({
2496
- toolName: finding.toolName,
2497
- occurrences: finding.occurrences
2495
+ stuckLoops: loopFindings.slice(0, loopLimit).map((finding2) => ({
2496
+ toolName: finding2.toolName,
2497
+ occurrences: finding2.occurrences
2498
2498
  })),
2499
2499
  stuckLoopsOmitted: Math.max(0, loopFindings.length - loopLimit),
2500
2500
  toolErrorRate: toolSpans.length === 0 ? 0 : erroredToolCallCount / toolSpans.length
@@ -2726,7 +2726,9 @@ function hashId(value, chars = 16) {
2726
2726
  }
2727
2727
  function isoTime(value) {
2728
2728
  if (typeof value === "string") {
2729
- const ms = Date.parse(value);
2729
+ const trimmed = value.trim();
2730
+ const normalized = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(trimmed) ? `${trimmed.replace(" ", "T")}Z` : trimmed;
2731
+ const ms = Date.parse(normalized);
2730
2732
  return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
2731
2733
  }
2732
2734
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -2768,14 +2770,19 @@ function parseJsonRows(text) {
2768
2770
  const trimmed = text.trim();
2769
2771
  if (!trimmed) throw new Error("input file is empty");
2770
2772
  if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
2771
- const parsed = JSON.parse(trimmed);
2772
- if (Array.isArray(parsed)) return { rows: parsed };
2773
- if (isObject(parsed)) {
2774
- const events = parsed.events;
2775
- if (Array.isArray(events)) return { rows: events, wrapper: parsed };
2776
- return { rows: [parsed] };
2773
+ try {
2774
+ const parsed = JSON.parse(trimmed);
2775
+ if (Array.isArray(parsed)) return { rows: parsed };
2776
+ if (isObject(parsed)) {
2777
+ const events = parsed.events;
2778
+ if (Array.isArray(events)) return { rows: events, wrapper: parsed };
2779
+ return { rows: [parsed] };
2780
+ }
2781
+ throw new Error("input JSON must be an object, array, or JSONL rows");
2782
+ } catch (error) {
2783
+ const hasMultipleRows = /\r?\n/.test(trimmed);
2784
+ if (!hasMultipleRows) throw error;
2777
2785
  }
2778
- throw new Error("input JSON must be an object, array, or JSONL rows");
2779
2786
  }
2780
2787
  return {
2781
2788
  rows: trimmed.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line))
@@ -2788,6 +2795,10 @@ function isOpenInferenceRow(row) {
2788
2795
  if (!isObject(row)) return false;
2789
2796
  return typeof row.trace_id === "string" && typeof row.span_id === "string" && typeof row.name === "string" && typeof row.start_time === "string" && typeof row.end_time === "string" && isObject(row.status) && isObject(row.attributes) && isObject(row.resource) && isObject(row.scope);
2790
2797
  }
2798
+ function isIntelligenceSpanRow(row) {
2799
+ if (!isObject(row)) return false;
2800
+ return typeof row.trace_id === "string" && typeof row.name === "string" && isObject(row.attributes) && (row.start_unix_nano != null || row.start_time != null || row.received_at != null);
2801
+ }
2791
2802
  function eventType(row) {
2792
2803
  return stringValue(row.type) ?? stringValue(row.event) ?? stringValue(row.name) ?? stringValue(row.kind);
2793
2804
  }
@@ -2801,8 +2812,9 @@ function detectFormat(rows, requested) {
2801
2812
  if (rows.length === 0) throw new Error("input contains no rows");
2802
2813
  if (rows.every(isPolicyEvidenceRow)) return "policy-evidence";
2803
2814
  if (rows.every(isOpenInferenceRow)) return "openinference";
2815
+ if (rows.every(isIntelligenceSpanRow)) return "intelligence-spans";
2804
2816
  if (rows.some(looksLikeSandboxEvent)) return "sandbox-events";
2805
- throw new Error("could not detect input format; use --format policy-evidence, sandbox-events, or openinference");
2817
+ throw new Error("could not detect input format; use --format policy-evidence, sandbox-events, openinference, or intelligence-spans");
2806
2818
  }
2807
2819
  function requirePolicyEvidenceRows(rows) {
2808
2820
  if (!rows.every(isPolicyEvidenceRow)) throw new Error("policy-evidence input must contain only traces.policy_evidence.session rows");
@@ -2812,6 +2824,10 @@ function requireOpenInferenceRows(rows) {
2812
2824
  if (!rows.every(isOpenInferenceRow)) throw new Error("openinference input must contain only complete OpenInference span rows");
2813
2825
  return rows;
2814
2826
  }
2827
+ function requireIntelligenceSpanRows(rows) {
2828
+ if (!rows.every(isIntelligenceSpanRow)) throw new Error("intelligence-spans input must contain only Intelligence span rows");
2829
+ return rows;
2830
+ }
2815
2831
  function requireObjectRows(rows) {
2816
2832
  if (!rows.every(isObject)) throw new Error("sandbox-events input must contain only JSON object events");
2817
2833
  return rows;
@@ -3008,6 +3024,112 @@ function sandboxEventsToSpans(rows, wrapper) {
3008
3024
  function statusCode(value) {
3009
3025
  return value === "OK" || value === "ERROR" || value === "UNSET" ? value : "UNSET";
3010
3026
  }
3027
+ function numberValue(value) {
3028
+ if (typeof value === "number" && Number.isFinite(value)) return value;
3029
+ if (typeof value === "string" && value.trim().length > 0) {
3030
+ const parsed = Number(value);
3031
+ return Number.isFinite(parsed) ? parsed : void 0;
3032
+ }
3033
+ return void 0;
3034
+ }
3035
+ function unixNanoTime(value) {
3036
+ if (value == null) return void 0;
3037
+ try {
3038
+ const ns = typeof value === "bigint" ? value : BigInt(String(value));
3039
+ return new Date(Number(ns / 1000000n)).toISOString();
3040
+ } catch {
3041
+ return void 0;
3042
+ }
3043
+ }
3044
+ function firstNumber(row, attrs, keys) {
3045
+ for (const key of keys) {
3046
+ const value = numberValue(row[key]) ?? numberValue(attrs[key]);
3047
+ if (value != null) return value;
3048
+ }
3049
+ return void 0;
3050
+ }
3051
+ var COMPUTED_SPAN_ATTRIBUTES = /* @__PURE__ */ new Set([
3052
+ "openinference.span.kind",
3053
+ "service.name",
3054
+ "agent.name",
3055
+ "llm.model_name",
3056
+ "tool.name",
3057
+ "llm.input_tokens",
3058
+ "llm.output_tokens",
3059
+ "step",
3060
+ "content"
3061
+ ]);
3062
+ function preserveRawAttributes(attrs) {
3063
+ const preserved = {};
3064
+ for (const [key, value] of Object.entries(attrs)) {
3065
+ if (COMPUTED_SPAN_ATTRIBUTES.has(key)) {
3066
+ preserved[`traces.raw_attribute.${key}`] = value;
3067
+ } else {
3068
+ preserved[key] = value;
3069
+ }
3070
+ }
3071
+ return preserved;
3072
+ }
3073
+ function intelligenceSpanKind(name, attrs) {
3074
+ const lowered = name.toLowerCase();
3075
+ const spanType = stringValue(attrs["span.type"])?.toLowerCase();
3076
+ if (spanType === "llm_request" || lowered.includes("llm")) return "LLM";
3077
+ if (spanType === "tool" || lowered.includes("tool") || attrs["tool.name"] != null || attrs.tool_name != null || attrs["mcp.tool.name"] != null || attrs["tool.input"] != null || attrs["tool.output"] != null) {
3078
+ return "TOOL";
3079
+ }
3080
+ if (spanType === "interaction" || lowered.includes("interaction") || lowered.includes("agent")) return "AGENT";
3081
+ return "CHAIN";
3082
+ }
3083
+ function intelligenceSpansToSpans(rows) {
3084
+ return rows.map((row, index) => {
3085
+ const attrs = { ...objectValue(row.attributes) ?? {} };
3086
+ const name = stringValue(row.name) ?? "intelligence.span";
3087
+ const startTime = unixNanoTime(row.start_unix_nano) ?? isoTime(row.start_time) ?? stringValue(row.start_time) ?? isoTime(row.received_at) ?? (/* @__PURE__ */ new Date(0)).toISOString();
3088
+ const endTime = unixNanoTime(row.end_unix_nano) ?? isoTime(row.end_time) ?? stringValue(row.end_time) ?? startTime;
3089
+ const model = stringValue(row.model) ?? stringValue(attrs["llm.model_name"]) ?? stringValue(attrs.model) ?? stringValue(attrs["gen_ai.request.model"]);
3090
+ const tool = stringValue(attrs["tool.name"]) ?? stringValue(attrs.tool_name) ?? stringValue(attrs["mcp.tool.name"]);
3091
+ const sessionId = stringValue(row.session_id) ?? stringValue(attrs["session.id"]);
3092
+ const extra = {
3093
+ ...preserveRawAttributes(attrs),
3094
+ "traces.source_format": "intelligence-spans",
3095
+ "traces.row.index": index
3096
+ };
3097
+ copyDefined(extra, {
3098
+ "traces.row.id": stringValue(row.id),
3099
+ [ATTR.SESSION_ID]: sessionId,
3100
+ "session.id": sessionId,
3101
+ "run.id": stringValue(row.run_id),
3102
+ "thread.id": stringValue(row.thread_id),
3103
+ "scenario.id": stringValue(row.scenario_id),
3104
+ "generation.id": stringValue(row.generation),
3105
+ "cell.id": stringValue(row.cell_id),
3106
+ "project.key": stringValue(row.project_key),
3107
+ "llm.cost_usd": numberValue(row.cost_usd),
3108
+ "redaction.version": stringValue(row.redaction_version),
3109
+ "traces.received_at": isoTime(row.received_at)
3110
+ });
3111
+ return span({
3112
+ traceId: stringValue(row.trace_id) ?? hashId(row, 32),
3113
+ spanId: stringValue(row.id) ?? hashId({ row, field: "span" }),
3114
+ parentSpanId: stringValue(row.parent_span_id) ?? null,
3115
+ name,
3116
+ kind: intelligenceSpanKind(name, attrs),
3117
+ startTime,
3118
+ endTime,
3119
+ status: statusCode(row.status_code),
3120
+ statusMessage: stringValue(row.status_message),
3121
+ service: stringValue(attrs["service.name"]) ?? stringValue(attrs.service_name) ?? "tangle-intelligence",
3122
+ agent: stringValue(attrs["agent.name"]) ?? stringValue(attrs["service.name"]) ?? null,
3123
+ model,
3124
+ tool,
3125
+ inputTokens: firstNumber(row, attrs, ["input_tokens", "llm.input_tokens"]),
3126
+ outputTokens: firstNumber(row, attrs, ["output_tokens", "llm.output_tokens"]),
3127
+ step: numberValue(attrs["interaction.sequence"]) ?? index,
3128
+ content: stringValue(attrs.user_prompt) ?? stringValue(attrs["gen_ai.prompt.messages"]) ?? stringValue(attrs["log.content.task"]) ?? null,
3129
+ extra
3130
+ });
3131
+ });
3132
+ }
3011
3133
  function openInferenceToSpans(rows) {
3012
3134
  return rows.map((row) => {
3013
3135
  const attrs = isObject(row.attributes) ? { ...row.attributes } : {};
@@ -3053,7 +3175,7 @@ function withExportAttributes(spans, attributes) {
3053
3175
  }
3054
3176
  function exportTraceEvidenceRows(rows, opts = {}, wrapper) {
3055
3177
  const format = detectFormat(rows, opts.format ?? "auto");
3056
- const converted = format === "policy-evidence" ? policyEvidenceToSpans(requirePolicyEvidenceRows(rows)) : format === "openinference" ? openInferenceToSpans(requireOpenInferenceRows(rows)) : sandboxEventsToSpans(requireObjectRows(rows), wrapper);
3178
+ const converted = format === "policy-evidence" ? policyEvidenceToSpans(requirePolicyEvidenceRows(rows)) : format === "openinference" ? openInferenceToSpans(requireOpenInferenceRows(rows)) : format === "intelligence-spans" ? intelligenceSpansToSpans(requireIntelligenceSpanRows(rows)) : sandboxEventsToSpans(requireObjectRows(rows), wrapper);
3057
3179
  const spans = withExportAttributes(converted, opts.attributes);
3058
3180
  if (spans.length === 0) throw new Error(`no spans exported from ${format} input`);
3059
3181
  const redacted = redactSpans(spans);
@@ -3113,12 +3235,12 @@ function topLoopTools(row) {
3113
3235
  current.maxOccurrences = Math.max(current.maxOccurrences, loop.occurrences);
3114
3236
  byTool.set(loop.toolName, current);
3115
3237
  }
3116
- const loops = [...byTool.entries()].sort(([, a], [, b]) => b.groups - a.groups || b.maxOccurrences - a.maxOccurrences).slice(0, 3).map(([toolName2, stats]) => `${toolName2} ${stats.groups} group(s), max x${stats.maxOccurrences}`);
3238
+ const loops = [...byTool.entries()].sort(([, a], [, b]) => b.groups - a.groups || b.maxOccurrences - a.maxOccurrences).slice(0, 3).map(([toolName3, stats]) => `${toolName3} ${stats.groups} group(s), max x${stats.maxOccurrences}`);
3117
3239
  const suffix = row.signals.stuckLoopsOmitted > 0 ? `, +${row.signals.stuckLoopsOmitted} omitted` : "";
3118
3240
  return loops.length > 0 ? `${loops.join(", ")}${suffix}` : `${row.signals.stuckLoopCount} loop(s)`;
3119
3241
  }
3120
3242
  function sortFindings(findings) {
3121
- return findings.sort((a, b) => severityRank[a.severity] - severityRank[b.severity] || b.impact - a.impact || a.id.localeCompare(b.id)).map((finding) => ({ ...finding }));
3243
+ return findings.sort((a, b) => severityRank[a.severity] - severityRank[b.severity] || b.impact - a.impact || a.id.localeCompare(b.id)).map((finding2) => ({ ...finding2 }));
3122
3244
  }
3123
3245
  function contextSourceCounts(index) {
3124
3246
  return { contextFiles: allContextFiles(index).length };
@@ -3313,9 +3435,9 @@ function inspectSessionIndex(index, opts = {}) {
3313
3435
  },
3314
3436
  totals: {
3315
3437
  findings: ranked.length,
3316
- high: ranked.filter((finding) => finding.severity === "high").length,
3317
- medium: ranked.filter((finding) => finding.severity === "medium").length,
3318
- low: ranked.filter((finding) => finding.severity === "low").length
3438
+ high: ranked.filter((finding2) => finding2.severity === "high").length,
3439
+ medium: ranked.filter((finding2) => finding2.severity === "medium").length,
3440
+ low: ranked.filter((finding2) => finding2.severity === "low").length
3319
3441
  },
3320
3442
  findings: ranked
3321
3443
  };
@@ -3334,16 +3456,16 @@ function renderInspectionReport(report) {
3334
3456
  return `${lines.join("\n")}
3335
3457
  `;
3336
3458
  }
3337
- report.findings.forEach((finding, i) => {
3459
+ report.findings.forEach((finding2, i) => {
3338
3460
  lines.push("");
3339
- lines.push(`## ${i + 1}. [${finding.severity}] ${finding.title}`);
3340
- lines.push(`Area: ${finding.area} Impact: ${finding.impact}`);
3461
+ lines.push(`## ${i + 1}. [${finding2.severity}] ${finding2.title}`);
3462
+ lines.push(`Area: ${finding2.area} Impact: ${finding2.impact}`);
3341
3463
  lines.push("Evidence:");
3342
- for (const item of finding.evidence) lines.push(`- ${item}`);
3343
- lines.push(`Next: ${finding.next}`);
3344
- if (finding.refs && finding.refs.length > 0) {
3464
+ for (const item of finding2.evidence) lines.push(`- ${item}`);
3465
+ lines.push(`Next: ${finding2.next}`);
3466
+ if (finding2.refs && finding2.refs.length > 0) {
3345
3467
  lines.push("Refs:");
3346
- for (const ref of finding.refs.slice(0, 5)) lines.push(`- ${ref}`);
3468
+ for (const ref of finding2.refs.slice(0, 5)) lines.push(`- ${ref}`);
3347
3469
  }
3348
3470
  });
3349
3471
  return `${lines.join("\n")}
@@ -3804,55 +3926,55 @@ function normalizeExternalFindings(results) {
3804
3926
  for (const result of results) {
3805
3927
  if (result.findings) out.push(...result.findings);
3806
3928
  externalRows(result.output).forEach((row, index) => {
3807
- const finding = coerceExternalFinding(result.analyzer, row, index);
3808
- if (finding) out.push(finding);
3929
+ const finding2 = coerceExternalFinding(result.analyzer, row, index);
3930
+ if (finding2) out.push(finding2);
3809
3931
  });
3810
3932
  }
3811
3933
  return out;
3812
3934
  }
3813
- function sourceOf(finding) {
3814
- if (finding.analyst_id.startsWith("external:")) return "external";
3815
- if (finding.analyst_id === "traces-deterministic") return "deterministic";
3935
+ function sourceOf(finding2) {
3936
+ if (finding2.analyst_id.startsWith("external:")) return "external";
3937
+ if (finding2.analyst_id === "traces-deterministic") return "deterministic";
3816
3938
  return "analyst";
3817
3939
  }
3818
- function recommendationTitle(finding) {
3819
- if (finding.recommended_action) return finding.recommended_action.replace(/\.$/, "");
3820
- return `Investigate ${finding.area}: ${finding.claim}`.replace(/\.$/, "");
3940
+ function recommendationTitle(finding2) {
3941
+ if (finding2.recommended_action) return finding2.recommended_action.replace(/\.$/, "");
3942
+ return `Investigate ${finding2.area}: ${finding2.claim}`.replace(/\.$/, "");
3821
3943
  }
3822
3944
  function buildRecommendations(findings) {
3823
- return [...findings].filter((finding) => finding.severity !== "info").sort((a, b) => severityRank2[a.severity] - severityRank2[b.severity] || b.confidence - a.confidence || a.finding_id.localeCompare(b.finding_id)).slice(0, 20).map((finding, index) => ({
3945
+ return [...findings].filter((finding2) => finding2.severity !== "info").sort((a, b) => severityRank2[a.severity] - severityRank2[b.severity] || b.confidence - a.confidence || a.finding_id.localeCompare(b.finding_id)).slice(0, 20).map((finding2, index) => ({
3824
3946
  schemaVersion: 1,
3825
- id: `rec-${index + 1}-${slug(finding.area)}`,
3826
- severity: finding.severity,
3827
- title: recommendationTitle(finding),
3828
- action: finding.recommended_action ?? `Review and fix: ${finding.claim}`,
3829
- rationale: finding.rationale ?? finding.claim,
3830
- evidenceRefs: finding.evidence_refs,
3831
- findingIds: [finding.finding_id],
3832
- validationPlan: finding.validation_plan ?? "Rerun `traces improve` on a fresh comparable session window and confirm this finding disappears or drops in severity.",
3833
- source: sourceOf(finding)
3947
+ id: `rec-${index + 1}-${slug(finding2.area)}`,
3948
+ severity: finding2.severity,
3949
+ title: recommendationTitle(finding2),
3950
+ action: finding2.recommended_action ?? `Review and fix: ${finding2.claim}`,
3951
+ rationale: finding2.rationale ?? finding2.claim,
3952
+ evidenceRefs: finding2.evidence_refs,
3953
+ findingIds: [finding2.finding_id],
3954
+ validationPlan: finding2.validation_plan ?? "Rerun `traces improve` on a fresh comparable session window and confirm this finding disappears or drops in severity.",
3955
+ source: sourceOf(finding2)
3834
3956
  }));
3835
3957
  }
3836
3958
  function buildClaims(findings) {
3837
- return findings.map((finding, index) => ({
3959
+ return findings.map((finding2, index) => ({
3838
3960
  schemaVersion: 1,
3839
- id: `claim-${index + 1}-${slug(finding.area)}`,
3840
- severity: finding.severity,
3841
- text: finding.claim,
3842
- area: finding.area,
3843
- sourceFindingId: finding.finding_id,
3844
- evidenceRefs: finding.evidence_refs,
3845
- validationPlan: finding.validation_plan
3961
+ id: `claim-${index + 1}-${slug(finding2.area)}`,
3962
+ severity: finding2.severity,
3963
+ text: finding2.claim,
3964
+ area: finding2.area,
3965
+ sourceFindingId: finding2.finding_id,
3966
+ evidenceRefs: finding2.evidence_refs,
3967
+ validationPlan: finding2.validation_plan
3846
3968
  }));
3847
3969
  }
3848
3970
  function buildEvidenceRows(findings) {
3849
- return findings.flatMap((finding) => finding.evidence_refs.map((ref) => ({
3971
+ return findings.flatMap((finding2) => finding2.evidence_refs.map((ref) => ({
3850
3972
  schemaVersion: 1,
3851
3973
  kind: "traces.improvement_evidence",
3852
- findingId: finding.finding_id,
3853
- severity: finding.severity,
3854
- area: finding.area,
3855
- claim: finding.claim,
3974
+ findingId: finding2.finding_id,
3975
+ severity: finding2.severity,
3976
+ area: finding2.area,
3977
+ claim: finding2.claim,
3856
3978
  evidence: ref
3857
3979
  })));
3858
3980
  }
@@ -3884,14 +4006,14 @@ function renderFindingPacket(packet, title = "Trace finding packet") {
3884
4006
  if (packet.findings.length > 0) {
3885
4007
  lines.push("## findings");
3886
4008
  lines.push("");
3887
- for (const finding of [...packet.findings].sort((a, b) => severityRank2[a.severity] - severityRank2[b.severity] || a.area.localeCompare(b.area) || a.claim.localeCompare(b.claim))) {
3888
- lines.push(`### ${finding.severity.toUpperCase()} \u2014 ${finding.claim}`);
4009
+ for (const finding2 of [...packet.findings].sort((a, b) => severityRank2[a.severity] - severityRank2[b.severity] || a.area.localeCompare(b.area) || a.claim.localeCompare(b.claim))) {
4010
+ lines.push(`### ${finding2.severity.toUpperCase()} \u2014 ${finding2.claim}`);
3889
4011
  lines.push("");
3890
- lines.push(`- **Area:** ${finding.area}`);
3891
- lines.push(`- **Analyst:** ${finding.analyst_id}`);
3892
- if (finding.recommended_action) lines.push(`- **Action:** ${finding.recommended_action}`);
3893
- if (finding.validation_plan) lines.push(`- **Check:** ${finding.validation_plan}`);
3894
- for (const ref of finding.evidence_refs.slice(0, 3)) {
4012
+ lines.push(`- **Area:** ${finding2.area}`);
4013
+ lines.push(`- **Analyst:** ${finding2.analyst_id}`);
4014
+ if (finding2.recommended_action) lines.push(`- **Action:** ${finding2.recommended_action}`);
4015
+ if (finding2.validation_plan) lines.push(`- **Check:** ${finding2.validation_plan}`);
4016
+ for (const ref of finding2.evidence_refs.slice(0, 3)) {
3895
4017
  lines.push(`- **Evidence:** ${ref.kind} ${ref.uri}${ref.excerpt ? ` \u2014 \`${ref.excerpt.slice(0, 180)}\`` : ""}`);
3896
4018
  }
3897
4019
  lines.push("");
@@ -4120,8 +4242,16 @@ async function saveReport(path, report) {
4120
4242
  await writeFile4(path, report, "utf8");
4121
4243
  }
4122
4244
 
4123
- // src/observer.ts
4245
+ // src/live.ts
4246
+ import { createHash as createHash2 } from "crypto";
4247
+ var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
4248
+ var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
4249
+ var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
4124
4250
  var sleep = (ms, signal) => new Promise((resolve2) => {
4251
+ if (signal?.aborted) {
4252
+ resolve2();
4253
+ return;
4254
+ }
4125
4255
  const onAbort = () => {
4126
4256
  clearTimeout(t);
4127
4257
  resolve2();
@@ -4132,14 +4262,329 @@ var sleep = (ms, signal) => new Promise((resolve2) => {
4132
4262
  }, ms);
4133
4263
  signal?.addEventListener("abort", onAbort, { once: true });
4134
4264
  });
4135
- function loopKey(sessionId, toolName2, argHash) {
4136
- return `${sessionId}\0${toolName2}\0${argHash}`;
4265
+ function hash(parts) {
4266
+ return createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
4267
+ }
4268
+ function asString(value) {
4269
+ if (typeof value === "string" && value.trim()) return value;
4270
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
4271
+ return void 0;
4272
+ }
4273
+ function spanKind2(span2) {
4274
+ return asString(span2.attributes["openinference.span.kind"])?.toUpperCase() ?? "SPAN";
4275
+ }
4276
+ function spanContent(span2) {
4277
+ return asString(span2.attributes.content) ?? asString(span2.attributes["input.value"]) ?? asString(span2.attributes["llm.input"]) ?? asString(span2.attributes["gen_ai.prompt"]) ?? "";
4278
+ }
4279
+ function toolName2(span2) {
4280
+ return asString(span2.attributes["tool.name"]) ?? span2.name.replace(/^tool\./, "");
4281
+ }
4282
+ function toolSignature(span2) {
4283
+ const content = spanContent(span2).replace(/\s+/g, " ").trim();
4284
+ return `${toolName2(span2)}:${content || span2.name}`;
4285
+ }
4286
+ function isTool(span2) {
4287
+ return spanKind2(span2) === "TOOL" || span2.attributes["tool.name"] != null || span2.name.startsWith("tool.");
4288
+ }
4289
+ function isTextSpan(span2) {
4290
+ return !isTool(span2) && spanContent(span2).length > 0;
4291
+ }
4292
+ function isVerification(span2) {
4293
+ if (!isTool(span2)) return false;
4294
+ return VERIFY_RE.test(`${toolName2(span2)} ${span2.name} ${spanContent(span2)}`);
4295
+ }
4296
+ function isChange(span2) {
4297
+ if (!isTool(span2)) return false;
4298
+ return CHANGE_RE.test(`${toolName2(span2)} ${span2.name} ${spanContent(span2)}`);
4299
+ }
4300
+ function timeOf(span2) {
4301
+ const parsed = Date.parse(span2.start_time);
4302
+ return Number.isFinite(parsed) ? parsed : 0;
4303
+ }
4304
+ function orderSpans(spans) {
4305
+ return [...spans].sort((a, b) => {
4306
+ const stepA = typeof a.attributes.step === "number" ? a.attributes.step : void 0;
4307
+ const stepB = typeof b.attributes.step === "number" ? b.attributes.step : void 0;
4308
+ if (stepA != null && stepB != null && stepA !== stepB) return stepA - stepB;
4309
+ return timeOf(a) - timeOf(b);
4310
+ });
4311
+ }
4312
+ function sessionFrom(spans, opts) {
4313
+ const first = spans[0];
4314
+ return {
4315
+ harness: opts.session?.harness ?? asString(first?.attributes["service.name"]) ?? "unknown",
4316
+ sessionId: opts.session?.sessionId ?? first?.trace_id ?? "unknown",
4317
+ cwd: opts.session?.cwd ?? asString(first?.attributes["tangle.cwd"]) ?? null,
4318
+ ...opts.session?.path ? { path: opts.session.path } : {}
4319
+ };
4320
+ }
4321
+ function evidence2(kind, label, value, spanIds) {
4322
+ return { kind, label, value, ...spanIds && spanIds.length > 0 ? { spanIds } : {} };
4323
+ }
4324
+ function finding(input) {
4325
+ const fingerprint = hash([input.ruleId, ...input.signature]);
4326
+ return {
4327
+ schemaVersion: 1,
4328
+ kind: "traces.live_finding",
4329
+ id: `live.${input.ruleId}.${fingerprint}`,
4330
+ fingerprint,
4331
+ ruleId: input.ruleId,
4332
+ severity: input.severity,
4333
+ title: input.title,
4334
+ claim: input.claim,
4335
+ action: input.action,
4336
+ check: input.check,
4337
+ evidence: input.evidence,
4338
+ session: input.session,
4339
+ observedAt: input.observedAt
4340
+ };
4341
+ }
4342
+ function analyzeLiveBatch(spans, opts = {}) {
4343
+ const ordered2 = orderSpans(spans);
4344
+ const generatedAt = opts.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
4345
+ const session = sessionFrom(ordered2, opts);
4346
+ const tools = ordered2.filter(isTool);
4347
+ const erroredTools = tools.filter((span2) => span2.status.code === "ERROR");
4348
+ const verificationCalls = tools.filter(isVerification);
4349
+ const changeCalls = tools.filter(isChange);
4350
+ const findings = [];
4351
+ const failedBySignature = /* @__PURE__ */ new Map();
4352
+ for (const span2 of erroredTools) {
4353
+ const signature = toolSignature(span2);
4354
+ failedBySignature.set(signature, [...failedBySignature.get(signature) ?? [], span2]);
4355
+ }
4356
+ for (const [signature, rows] of failedBySignature) {
4357
+ if (rows.length < 2) continue;
4358
+ findings.push(finding({
4359
+ ruleId: "same-failing-command",
4360
+ severity: rows.length >= 3 ? "high" : "medium",
4361
+ title: "Same failing command is repeating",
4362
+ claim: `${rows.length} failed tool call(s) repeated the same command or arguments.`,
4363
+ action: "Stop rerunning it; inspect the first failure, change state, or choose a different diagnostic path before another retry.",
4364
+ check: "The next action should read the failing file, inspect the stack/output, or edit code before this command runs again.",
4365
+ evidence: [
4366
+ evidence2("pattern", "command", signature, rows.map((span2) => span2.span_id)),
4367
+ evidence2("metric", "failed_repeats", String(rows.length))
4368
+ ],
4369
+ session,
4370
+ observedAt: generatedAt,
4371
+ signature: [signature, String(rows.length)]
4372
+ }));
4373
+ }
4374
+ let lastChangeIndex = -1;
4375
+ let verificationSinceChange = 0;
4376
+ const verificationSpanIds = [];
4377
+ for (let i = 0; i < ordered2.length; i++) {
4378
+ const span2 = ordered2[i];
4379
+ if (isChange(span2)) {
4380
+ lastChangeIndex = i;
4381
+ verificationSinceChange = 0;
4382
+ verificationSpanIds.length = 0;
4383
+ } else if (isVerification(span2)) {
4384
+ verificationSinceChange += 1;
4385
+ verificationSpanIds.push(span2.span_id);
4386
+ }
4387
+ }
4388
+ if (verificationSinceChange >= 2) {
4389
+ findings.push(finding({
4390
+ ruleId: "verification-without-change",
4391
+ severity: verificationSinceChange >= 3 ? "high" : "medium",
4392
+ title: "Verification is repeating without a state change",
4393
+ claim: `${verificationSinceChange} verification command(s) ran after the last detected code/config change.`,
4394
+ action: "Do not spend another verification run until the agent either edits state or explains what new signal the rerun will reveal.",
4395
+ check: "The next action should be a code/config edit, targeted file read, or a different narrow diagnostic command.",
4396
+ evidence: [
4397
+ evidence2("metric", "verification_calls_since_change", String(verificationSinceChange), verificationSpanIds),
4398
+ evidence2("metric", "change_calls", String(changeCalls.length))
4399
+ ],
4400
+ session,
4401
+ observedAt: generatedAt,
4402
+ signature: [String(verificationSinceChange), verificationSpanIds.join(",")]
4403
+ }));
4404
+ }
4405
+ const lastClaim = [...ordered2].reverse().find((span2) => isTextSpan(span2) && CLAIM_DONE_RE.test(spanContent(span2)));
4406
+ if (lastClaim) {
4407
+ const claimTime = timeOf(lastClaim);
4408
+ const verificationAfterClaim = verificationCalls.some((span2) => timeOf(span2) > claimTime);
4409
+ if (!verificationAfterClaim) {
4410
+ findings.push(finding({
4411
+ ruleId: "completion-claim-without-verification",
4412
+ severity: "medium",
4413
+ title: "Completion claim has no later verification",
4414
+ claim: "The agent claimed completion, correctness, or verification without a later verification tool call in the observed trace.",
4415
+ action: "Require a concrete check after the claim, or downgrade the claim to an unverified hypothesis.",
4416
+ check: "A later test/build/typecheck/lint/smoke command should appear after the claim.",
4417
+ evidence: [
4418
+ evidence2("span", "claim_span", spanContent(lastClaim).slice(0, 240), [lastClaim.span_id]),
4419
+ evidence2("metric", "verification_after_claim", "0")
4420
+ ],
4421
+ session,
4422
+ observedAt: generatedAt,
4423
+ signature: [lastClaim.span_id, spanContent(lastClaim).slice(0, 120)]
4424
+ }));
4425
+ }
4426
+ }
4427
+ if (tools.length >= 4 && erroredTools.length / tools.length >= 0.5) {
4428
+ findings.push(finding({
4429
+ ruleId: "high-tool-error-rate",
4430
+ severity: erroredTools.length / tools.length >= 0.75 ? "high" : "medium",
4431
+ title: "Tool calls are mostly failing",
4432
+ claim: `${erroredTools.length}/${tools.length} tool call(s) in this live batch ended with ERROR.`,
4433
+ action: "Switch from broad execution to diagnosis: classify the dominant error, then run one targeted command to validate the fix path.",
4434
+ check: "The next batch should reduce the error ratio or show a new targeted diagnostic result.",
4435
+ evidence: [
4436
+ evidence2("metric", "errored_tool_calls", `${erroredTools.length}/${tools.length}`, erroredTools.map((span2) => span2.span_id))
4437
+ ],
4438
+ session,
4439
+ observedAt: generatedAt,
4440
+ signature: [String(erroredTools.length), String(tools.length)]
4441
+ }));
4442
+ }
4443
+ return {
4444
+ schemaVersion: 1,
4445
+ kind: "traces.live_batch",
4446
+ generatedAt,
4447
+ session,
4448
+ spanCount: ordered2.length,
4449
+ newSpanCount: opts.newSpanCount ?? ordered2.length,
4450
+ toolCallCount: tools.length,
4451
+ erroredToolCallCount: erroredTools.length,
4452
+ verificationCallCount: verificationCalls.length,
4453
+ changeCallCount: changeCalls.length,
4454
+ findingCount: findings.length,
4455
+ findings
4456
+ };
4457
+ }
4458
+ function sessionEvent(session, spans, generatedAt) {
4459
+ const ordered2 = orderSpans(spans);
4460
+ return {
4461
+ schemaVersion: 1,
4462
+ kind: "traces.stream.session",
4463
+ event: "session",
4464
+ generatedAt,
4465
+ session,
4466
+ spanCount: ordered2.length,
4467
+ startedAt: ordered2[0]?.start_time ?? null,
4468
+ endedAt: ordered2.at(-1)?.end_time ?? null
4469
+ };
4470
+ }
4471
+ function traceStreamEventsFromSpans(spans, opts = {}) {
4472
+ const generatedAt = opts.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
4473
+ const session = sessionFrom(spans, {
4474
+ ...opts,
4475
+ session: opts.ref ? { harness: opts.ref.harness, sessionId: opts.ref.sessionId, cwd: opts.ref.cwd, path: opts.ref.path } : opts.session
4476
+ });
4477
+ const ordered2 = orderSpans(spans);
4478
+ const batch = analyzeLiveBatch(ordered2, { generatedAt, session, newSpanCount: ordered2.length });
4479
+ const events = [sessionEvent(session, ordered2, generatedAt)];
4480
+ if (opts.includeSpans !== false) {
4481
+ for (const span2 of ordered2) {
4482
+ events.push({ schemaVersion: 1, kind: "traces.stream.span", event: "span", generatedAt, session, span: span2 });
4483
+ }
4484
+ }
4485
+ events.push({ schemaVersion: 1, kind: "traces.stream.batch", event: "analysis_batch", generatedAt, batch });
4486
+ if (opts.includeFindings !== false) {
4487
+ for (const liveFinding of batch.findings) {
4488
+ events.push({ schemaVersion: 1, kind: "traces.stream.finding", event: "finding", generatedAt, finding: liveFinding });
4489
+ }
4490
+ }
4491
+ return events;
4492
+ }
4493
+ function serializeTraceStreamEvent(event) {
4494
+ return `${JSON.stringify(event)}
4495
+ `;
4496
+ }
4497
+ async function streamSessions(opts) {
4498
+ const intervalMs = Math.max(250, opts.intervalMs ?? 5e3);
4499
+ const windowMs = opts.windowMs ?? 30 * 6e4;
4500
+ const includeSpans = opts.includeSpans !== false;
4501
+ const includeFindings = opts.includeFindings !== false;
4502
+ const includeBatches = opts.includeBatches !== false;
4503
+ const seenSessions = /* @__PURE__ */ new Set();
4504
+ const seenSpans = /* @__PURE__ */ new Set();
4505
+ const seenFindings = /* @__PURE__ */ new Set();
4506
+ while (!opts.signal?.aborted) {
4507
+ let sessions = 0;
4508
+ let newSpans = 0;
4509
+ let findings = 0;
4510
+ for await (const { adapter, ref, spans } of scanSessions({
4511
+ ...opts,
4512
+ sinceMs: opts.sinceMs ?? Date.now() - windowMs,
4513
+ signal: opts.signal
4514
+ })) {
4515
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4516
+ const session = { harness: adapter.harness, sessionId: ref.sessionId, cwd: ref.cwd, path: ref.path };
4517
+ const ordered2 = orderSpans(spans);
4518
+ const sessionKey = `${session.harness}:${session.sessionId}`;
4519
+ const unseen = ordered2.filter((span2) => {
4520
+ const key = `${sessionKey}:${span2.span_id}`;
4521
+ if (seenSpans.has(key)) return false;
4522
+ seenSpans.add(key);
4523
+ return true;
4524
+ });
4525
+ sessions += 1;
4526
+ newSpans += unseen.length;
4527
+ if (!seenSessions.has(sessionKey)) {
4528
+ seenSessions.add(sessionKey);
4529
+ await opts.onEvent(sessionEvent(session, ordered2, generatedAt));
4530
+ }
4531
+ if (includeSpans) {
4532
+ for (const span2 of unseen) {
4533
+ await opts.onEvent({ schemaVersion: 1, kind: "traces.stream.span", event: "span", generatedAt, session, span: span2 });
4534
+ }
4535
+ }
4536
+ const batch = analyzeLiveBatch(ordered2, { generatedAt, session, newSpanCount: unseen.length });
4537
+ if (includeBatches) {
4538
+ await opts.onEvent({ schemaVersion: 1, kind: "traces.stream.batch", event: "analysis_batch", generatedAt, batch });
4539
+ }
4540
+ if (includeFindings) {
4541
+ for (const liveFinding of batch.findings) {
4542
+ if (seenFindings.has(liveFinding.id)) continue;
4543
+ seenFindings.add(liveFinding.id);
4544
+ findings += 1;
4545
+ await opts.onEvent({ schemaVersion: 1, kind: "traces.stream.finding", event: "finding", generatedAt, finding: liveFinding });
4546
+ }
4547
+ }
4548
+ }
4549
+ await opts.onEvent({
4550
+ schemaVersion: 1,
4551
+ kind: "traces.stream.tick",
4552
+ event: "tick",
4553
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4554
+ sessions,
4555
+ newSpans,
4556
+ findings
4557
+ });
4558
+ if (opts.once || opts.signal?.aborted) break;
4559
+ await sleep(intervalMs, opts.signal);
4560
+ }
4561
+ }
4562
+
4563
+ // src/observer.ts
4564
+ var sleep2 = (ms, signal) => new Promise((resolve2) => {
4565
+ if (signal?.aborted) {
4566
+ resolve2();
4567
+ return;
4568
+ }
4569
+ const onAbort = () => {
4570
+ clearTimeout(t);
4571
+ resolve2();
4572
+ };
4573
+ const t = setTimeout(() => {
4574
+ signal?.removeEventListener("abort", onAbort);
4575
+ resolve2();
4576
+ }, ms);
4577
+ signal?.addEventListener("abort", onAbort, { once: true });
4578
+ });
4579
+ function loopKey(sessionId, toolName3, argHash) {
4580
+ return `${sessionId}:${toolName3}:${argHash}`;
4137
4581
  }
4138
4582
  async function watchSessions(opts) {
4139
4583
  const windowMs = opts.windowMs ?? 30 * 6e4;
4140
4584
  const intervalMs = Math.max(250, opts.intervalMs ?? 5e3);
4141
4585
  const minLoop = opts.minLoopOccurrences ?? 3;
4142
4586
  const seen = /* @__PURE__ */ new Map();
4587
+ const seenFindings = /* @__PURE__ */ new Set();
4143
4588
  while (!opts.signal?.aborted) {
4144
4589
  let sessionCount = 0;
4145
4590
  for await (const { adapter, ref, spans } of scanSessions({
@@ -4166,6 +4611,27 @@ async function watchSessions(opts) {
4166
4611
  opts.onError?.(err, ref);
4167
4612
  }
4168
4613
  }
4614
+ const batch = analyzeLiveBatch(spans, {
4615
+ session: { harness: adapter.harness, sessionId: ref.sessionId, cwd: ref.cwd, path: ref.path }
4616
+ });
4617
+ if (opts.onBatch) {
4618
+ try {
4619
+ await opts.onBatch(ref, batch);
4620
+ } catch (err) {
4621
+ opts.onError?.(err, ref);
4622
+ }
4623
+ }
4624
+ if (opts.onFinding) {
4625
+ for (const finding2 of batch.findings) {
4626
+ if (seenFindings.has(finding2.id)) continue;
4627
+ seenFindings.add(finding2.id);
4628
+ try {
4629
+ await opts.onFinding(finding2);
4630
+ } catch (err) {
4631
+ opts.onError?.(err, ref);
4632
+ }
4633
+ }
4634
+ }
4169
4635
  for (const f of report.stuckLoops.findings) {
4170
4636
  const key = loopKey(ref.sessionId, f.toolName, f.argHash);
4171
4637
  if (f.occurrences > (seen.get(key) ?? 0)) {
@@ -4194,7 +4660,7 @@ async function watchSessions(opts) {
4194
4660
  opts.onError?.(err);
4195
4661
  }
4196
4662
  if (opts.signal?.aborted) break;
4197
- await sleep(intervalMs, opts.signal);
4663
+ await sleep2(intervalMs, opts.signal);
4198
4664
  }
4199
4665
  }
4200
4666
 
@@ -4421,7 +4887,7 @@ async function writeSessionIndexFile(index, outPath) {
4421
4887
  }
4422
4888
 
4423
4889
  // src/upload-state.ts
4424
- import { createHash as createHash2 } from "crypto";
4890
+ import { createHash as createHash3 } from "crypto";
4425
4891
  import { mkdir as mkdir2, readFile as readFile15, writeFile as writeFile6 } from "fs/promises";
4426
4892
  import { homedir as homedir11 } from "os";
4427
4893
  import { dirname as dirname4, join as join18 } from "path";
@@ -4433,7 +4899,7 @@ function uploadKey(harness, sessionId) {
4433
4899
  return `${harness}:${sessionId}`;
4434
4900
  }
4435
4901
  function sessionHash(spans) {
4436
- const h = createHash2("sha256");
4902
+ const h = createHash3("sha256");
4437
4903
  for (const s of spans) {
4438
4904
  h.update(`${s.span_id}\0${s.name}\0${s.start_time}\0${s.status.code}
4439
4905
  `);
@@ -4453,8 +4919,8 @@ async function saveState(state) {
4453
4919
  await writeFile6(f, `${JSON.stringify(state, null, 2)}
4454
4920
  `, "utf8");
4455
4921
  }
4456
- function alreadyUploaded(state, harness, sessionId, hash) {
4457
- return state[uploadKey(harness, sessionId)]?.hash === hash;
4922
+ function alreadyUploaded(state, harness, sessionId, hash2) {
4923
+ return state[uploadKey(harness, sessionId)]?.hash === hash2;
4458
4924
  }
4459
4925
 
4460
4926
  // src/upload.ts
@@ -4471,13 +4937,13 @@ async function planUpload(opts = {}) {
4471
4937
  const items = [];
4472
4938
  for await (const { ref, spans: raw } of scanSessions(opts)) {
4473
4939
  const { spans, report } = redactSpans(raw);
4474
- const hash = sessionHash(spans);
4940
+ const hash2 = sessionHash(spans);
4475
4941
  items.push({
4476
4942
  ref,
4477
4943
  spans,
4478
4944
  redaction: report,
4479
- hash,
4480
- isNew: !alreadyUploaded(state, ref.harness, ref.sessionId, hash)
4945
+ hash: hash2,
4946
+ isNew: !alreadyUploaded(state, ref.harness, ref.sessionId, hash2)
4481
4947
  });
4482
4948
  }
4483
4949
  return { items, state, sinceMs: opts.sinceMs };
@@ -4674,6 +5140,10 @@ export {
4674
5140
  writeTraceImprovementArtifacts,
4675
5141
  runTraceImprovementLoop,
4676
5142
  saveReport,
5143
+ analyzeLiveBatch,
5144
+ traceStreamEventsFromSpans,
5145
+ serializeTraceStreamEvent,
5146
+ streamSessions,
4677
5147
  watchSessions,
4678
5148
  buildSessionIndex,
4679
5149
  buildSessionIndexFromRows,