@tangle-network/traces 0.9.4 → 0.9.6

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.
@@ -920,10 +920,12 @@ function classifyReaction(text) {
920
920
  var CORRECTIVE_REACTIONS = ["correction", "frustration", "jargon", "structure"];
921
921
 
922
922
  // src/adapters/claude.ts
923
+ import { createHash as createHash4 } from "crypto";
923
924
  import { readdir as readdir3, stat as stat2 } from "fs/promises";
924
925
  import { homedir as homedir2 } from "os";
925
926
  import { basename as basename2, join as join2 } from "path";
926
927
  var SERVICE2 = "claude-code";
928
+ var EPOCH = (/* @__PURE__ */ new Date(0)).toISOString();
927
929
  function asBlocks(content) {
928
930
  return Array.isArray(content) ? content : [];
929
931
  }
@@ -943,10 +945,93 @@ function stringifyToolResult(content) {
943
945
  function createClaudeStream(startStep) {
944
946
  return { spans: [], toolSpanByUseId: /* @__PURE__ */ new Map(), step: startStep, sawUserTurn: false };
945
947
  }
946
- function consumeClaudeEvent(ev, ctx, state) {
947
- const ts = ev.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
948
- const uid = ev.uuid ?? `${ctx.idPrefix}step${state.step}`;
949
- if (ev.type === "assistant" && ev.message) {
948
+ var ClaudeEventConflictError = class extends Error {
949
+ sourcePath;
950
+ eventId;
951
+ constructor(sourcePath, eventId) {
952
+ super(`${sourcePath}: Claude transcript event ${JSON.stringify(eventId)} has conflicting payloads`);
953
+ this.name = "ClaudeEventConflictError";
954
+ this.sourcePath = sourcePath;
955
+ this.eventId = eventId;
956
+ }
957
+ };
958
+ function projectToolResult(toolUseId, isError, output) {
959
+ return {
960
+ toolUseId,
961
+ isError,
962
+ attributes: toolIoAttributes({ output }),
963
+ message: stringifyToolResult(output).slice(0, 500)
964
+ };
965
+ }
966
+ function projectClaudeEvent(event) {
967
+ const timestamp = event.timestamp ?? EPOCH;
968
+ if (event.type === "assistant" && event.message) {
969
+ const tools = [];
970
+ for (const block of asBlocks(event.message.content)) {
971
+ if (block.type !== "tool_use" || !block.name) continue;
972
+ tools.push({ id: block.id || null, name: block.name, attributes: toolIoAttributes({ input: block.input }) });
973
+ }
974
+ return {
975
+ kind: "assistant",
976
+ timestamp,
977
+ model: event.message.model ?? null,
978
+ inputTokens: event.message.usage?.input_tokens ?? null,
979
+ outputTokens: event.message.usage?.output_tokens ?? null,
980
+ cachedInputTokens: event.message.usage?.cache_read_input_tokens ?? null,
981
+ cacheWriteInputTokens: event.message.usage?.cache_creation_input_tokens ?? null,
982
+ content: textOf2(event.message.content) || null,
983
+ tools
984
+ };
985
+ }
986
+ if (event.type === "user" && event.message) {
987
+ const results = [];
988
+ for (const block of asBlocks(event.message.content)) {
989
+ if (block.type !== "tool_result" || !block.tool_use_id) continue;
990
+ results.push(projectToolResult(block.tool_use_id, block.is_error === true, block.content));
991
+ }
992
+ const prompt = textOf2(event.message.content);
993
+ return {
994
+ kind: "user",
995
+ timestamp,
996
+ prompt: prompt || null,
997
+ ...prompt ? { isSidechain: event.isSidechain === true, userType: event.userType ?? null } : {},
998
+ results
999
+ };
1000
+ }
1001
+ if (event.type === "attachment" && event.attachment?.type === "tool_result" && event.attachment.toolUseID) {
1002
+ return {
1003
+ kind: "attachment",
1004
+ timestamp,
1005
+ result: projectToolResult(
1006
+ event.attachment.toolUseID,
1007
+ typeof event.attachment.exitCode === "number" && event.attachment.exitCode !== 0,
1008
+ event.attachment.stderr ?? ""
1009
+ )
1010
+ };
1011
+ }
1012
+ return { kind: "ignored" };
1013
+ }
1014
+ function fingerprintClaudeEvent(event) {
1015
+ return createHash4("sha256").update(JSON.stringify(event)).digest("hex");
1016
+ }
1017
+ function consumeDistinctClaudeEvent(event, ctx, state, seen, sourcePath) {
1018
+ const projection = projectClaudeEvent(event);
1019
+ if (event.uuid) {
1020
+ const fingerprint = fingerprintClaudeEvent(projection);
1021
+ const previous = seen.get(event.uuid);
1022
+ if (previous !== void 0) {
1023
+ if (previous !== fingerprint) {
1024
+ throw new ClaudeEventConflictError(sourcePath, event.uuid);
1025
+ }
1026
+ return false;
1027
+ }
1028
+ seen.set(event.uuid, fingerprint);
1029
+ }
1030
+ consumeClaudeEvent(projection, event.uuid ?? `${ctx.idPrefix}step${state.step}`, ctx, state);
1031
+ return true;
1032
+ }
1033
+ function consumeClaudeEvent(event, uid, ctx, state) {
1034
+ if (event.kind === "assistant") {
950
1035
  const llmId = `${ctx.idPrefix}${uid}`;
951
1036
  state.spans.push(
952
1037
  span({
@@ -955,47 +1040,45 @@ function consumeClaudeEvent(ev, ctx, state) {
955
1040
  parentSpanId: ctx.rootParent,
956
1041
  name: "llm.turn",
957
1042
  kind: "LLM",
958
- startTime: ts,
1043
+ startTime: event.timestamp,
959
1044
  service: SERVICE2,
960
1045
  agent: ctx.agent,
961
- model: ev.message.model ?? null,
962
- inputTokens: ev.message.usage?.input_tokens ?? null,
963
- outputTokens: ev.message.usage?.output_tokens ?? null,
964
- cachedInputTokens: ev.message.usage?.cache_read_input_tokens ?? null,
965
- cacheWriteInputTokens: ev.message.usage?.cache_creation_input_tokens ?? null,
1046
+ model: event.model,
1047
+ inputTokens: event.inputTokens,
1048
+ outputTokens: event.outputTokens,
1049
+ cachedInputTokens: event.cachedInputTokens,
1050
+ cacheWriteInputTokens: event.cacheWriteInputTokens,
966
1051
  step: state.step,
967
- content: textOf2(ev.message.content) || null
1052
+ content: event.content
968
1053
  })
969
1054
  );
970
1055
  state.step += 1;
971
1056
  let toolIdx = 0;
972
- for (const block of asBlocks(ev.message.content)) {
973
- if (block.type !== "tool_use" || !block.name) continue;
1057
+ for (const tool of event.tools) {
974
1058
  const toolSpan = span({
975
1059
  traceId: ctx.traceId,
976
1060
  spanId: `${ctx.idPrefix}${uid}:tool:${toolIdx}`,
977
1061
  parentSpanId: llmId,
978
- name: `tool.${block.name}`,
1062
+ name: `tool.${tool.name}`,
979
1063
  kind: "TOOL",
980
- startTime: ts,
1064
+ startTime: event.timestamp,
981
1065
  service: SERVICE2,
982
1066
  agent: ctx.agent,
983
- tool: block.name,
1067
+ tool: tool.name,
984
1068
  step: state.step,
985
- extra: toolIoAttributes({ input: block.input })
1069
+ extra: tool.attributes
986
1070
  });
987
1071
  state.spans.push(toolSpan);
988
- if (block.id) state.toolSpanByUseId.set(block.id, toolSpan);
1072
+ if (tool.id) state.toolSpanByUseId.set(tool.id, toolSpan);
989
1073
  toolIdx += 1;
990
1074
  state.step += 1;
991
1075
  }
992
- } else if (ev.type === "user" && ev.message) {
993
- const prompt = textOf2(ev.message.content);
994
- if (prompt) {
1076
+ } else if (event.kind === "user") {
1077
+ if (event.prompt) {
995
1078
  const actor = claudeActor({
996
- text: prompt,
997
- isSidechain: ev.isSidechain,
998
- userType: ev.userType ?? null,
1079
+ text: event.prompt,
1080
+ isSidechain: event.isSidechain,
1081
+ userType: event.userType ?? null,
999
1082
  isFirstUserTurn: !state.sawUserTurn
1000
1083
  });
1001
1084
  state.sawUserTurn = true;
@@ -1004,41 +1087,49 @@ function consumeClaudeEvent(ev, ctx, state) {
1004
1087
  traceId: ctx.traceId,
1005
1088
  spanId: `${ctx.idPrefix}${uid}:user`,
1006
1089
  parentSpanId: ctx.rootParent,
1007
- startTime: ts,
1090
+ startTime: event.timestamp,
1008
1091
  service: SERVICE2,
1009
1092
  agent: ctx.agent,
1010
1093
  step: state.step,
1011
- content: prompt,
1094
+ content: event.prompt,
1012
1095
  actor
1013
1096
  })
1014
1097
  );
1015
1098
  state.step += 1;
1016
1099
  }
1017
- for (const block of asBlocks(ev.message.content)) {
1018
- if (block.type !== "tool_result" || !block.tool_use_id) continue;
1019
- backfillResult(
1020
- state.toolSpanByUseId.get(block.tool_use_id),
1021
- ts,
1022
- block.is_error === true,
1023
- block.content
1024
- );
1100
+ for (const result of event.results) {
1101
+ backfillResult(state.toolSpanByUseId.get(result.toolUseId), event.timestamp, result);
1025
1102
  }
1026
- } else if (ev.type === "attachment" && ev.attachment?.type === "tool_result" && ev.attachment.toolUseID) {
1027
- const err = typeof ev.attachment.exitCode === "number" && ev.attachment.exitCode !== 0;
1028
- backfillResult(state.toolSpanByUseId.get(ev.attachment.toolUseID), ts, err, ev.attachment.stderr ?? "");
1103
+ } else if (event.kind === "attachment") {
1104
+ backfillResult(state.toolSpanByUseId.get(event.result.toolUseId), event.timestamp, event.result);
1029
1105
  }
1030
1106
  }
1031
1107
  function finishClaudeStream(state) {
1032
1108
  return { spans: state.spans, toolSpanByUseId: state.toolSpanByUseId, nextStep: state.step };
1033
1109
  }
1034
- function backfillResult(s, endTime, isError, output) {
1110
+ function setRootTimeBounds(root, spans) {
1111
+ let firstTimestamp;
1112
+ let lastTimestamp;
1113
+ for (const item of spans) {
1114
+ if (item === root) continue;
1115
+ for (const source of [item.start_time, item.end_time]) {
1116
+ if (!source) continue;
1117
+ const value = Date.parse(source);
1118
+ if (!Number.isFinite(value)) continue;
1119
+ if (!firstTimestamp || value < firstTimestamp.value) firstTimestamp = { value, source };
1120
+ if (!lastTimestamp || value > lastTimestamp.value) lastTimestamp = { value, source };
1121
+ }
1122
+ }
1123
+ if (firstTimestamp) root.start_time = firstTimestamp.source;
1124
+ if (lastTimestamp) root.end_time = lastTimestamp.source;
1125
+ }
1126
+ function backfillResult(s, endTime, result) {
1035
1127
  if (!s) return;
1036
1128
  s.end_time = endTime;
1037
- const code = isError ? "ERROR" : "OK";
1129
+ const code = result.isError ? "ERROR" : "OK";
1038
1130
  s.status = { code };
1039
- recordToolOutput(s, output);
1040
- const message = stringifyToolResult(output);
1041
- if (isError && message) s.status.message = message.slice(0, 500);
1131
+ Object.assign(s.attributes, result.attributes);
1132
+ if (result.isError && result.message) s.status.message = result.message;
1042
1133
  }
1043
1134
  var ClaudeAdapter = class {
1044
1135
  harness = "claude-code";
@@ -1096,17 +1187,10 @@ var ClaudeAdapter = class {
1096
1187
  };
1097
1188
  const state = createClaudeStream(ctx.startStep);
1098
1189
  let discoveredTraceId;
1099
- let firstTimestamp;
1100
- let lastTimestamp;
1101
- let sawEvent = false;
1190
+ const seen = /* @__PURE__ */ new Map();
1102
1191
  for await (const event of readJsonl(ref.path, sessionJsonlOptions(ref, options))) {
1103
- if (!sawEvent) {
1104
- firstTimestamp = event.timestamp;
1105
- sawEvent = true;
1106
- }
1107
- lastTimestamp = event.timestamp;
1108
1192
  if (!discoveredTraceId && event.sessionId) discoveredTraceId = event.sessionId;
1109
- consumeClaudeEvent(event, ctx, state);
1193
+ if (!consumeDistinctClaudeEvent(event, ctx, state, seen, ref.path)) continue;
1110
1194
  }
1111
1195
  const main = finishClaudeStream(state);
1112
1196
  const traceId = discoveredTraceId ?? sourceTraceId;
@@ -1115,21 +1199,20 @@ var ClaudeAdapter = class {
1115
1199
  item.trace_id = traceId;
1116
1200
  if (item.parent_span_id === sourceRootId) item.parent_span_id = rootId;
1117
1201
  }
1118
- const spans = [
1119
- span({
1120
- traceId,
1121
- spanId: rootId,
1122
- parentSpanId: null,
1123
- name: "session",
1124
- kind: "AGENT",
1125
- startTime: firstTimestamp ?? (/* @__PURE__ */ new Date(0)).toISOString(),
1126
- endTime: lastTimestamp,
1127
- service: SERVICE2,
1128
- agent: SERVICE2
1129
- })
1130
- ];
1202
+ const root = span({
1203
+ traceId,
1204
+ spanId: rootId,
1205
+ parentSpanId: null,
1206
+ name: "session",
1207
+ kind: "AGENT",
1208
+ startTime: (/* @__PURE__ */ new Date(0)).toISOString(),
1209
+ service: SERVICE2,
1210
+ agent: SERVICE2
1211
+ });
1212
+ const spans = [root];
1131
1213
  appendAll(spans, main.spans);
1132
1214
  await this.foldSubagents(ref, traceId, main, spans, options);
1215
+ setRootTimeBounds(root, spans);
1133
1216
  return spans;
1134
1217
  }
1135
1218
  /** Parse `<session>/subagents/agent-*.jsonl`, parenting each under its Agent call. */
@@ -1162,11 +1245,12 @@ var ClaudeAdapter = class {
1162
1245
  rootParent: parent
1163
1246
  };
1164
1247
  const state = createClaudeStream(ctx.startStep);
1248
+ const seen = /* @__PURE__ */ new Map();
1165
1249
  for await (const event of readJsonl(
1166
1250
  join2(subDir, file),
1167
1251
  sessionJsonlOptions(ref, options)
1168
1252
  )) {
1169
- consumeClaudeEvent(event, ctx, state);
1253
+ consumeDistinctClaudeEvent(event, ctx, state, seen, join2(subDir, file));
1170
1254
  }
1171
1255
  const parsed = finishClaudeStream(state);
1172
1256
  appendAll(out, parsed.spans);
@@ -3483,7 +3567,7 @@ async function applyRedactor(spans, redactor) {
3483
3567
  }
3484
3568
 
3485
3569
  // src/file-export.ts
3486
- import { createHash as createHash4 } from "crypto";
3570
+ import { createHash as createHash5 } from "crypto";
3487
3571
  import { mkdtemp as mkdtemp2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3488
3572
  import { tmpdir as tmpdir2 } from "os";
3489
3573
  import { join as join13 } from "path";
@@ -3521,7 +3605,7 @@ function stableJson(value) {
3521
3605
  });
3522
3606
  }
3523
3607
  function hashId(value, chars = 16) {
3524
- return createHash4("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
3608
+ return createHash5("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
3525
3609
  }
3526
3610
  function isoTime(value) {
3527
3611
  if (typeof value === "string") {
@@ -4455,9 +4539,9 @@ function skillDocumentNames(s) {
4455
4539
  }
4456
4540
  return [...names];
4457
4541
  }
4458
- function telemetryStatus(measured, unsupported, unknown) {
4459
- if (measured > 0 && unsupported === 0 && unknown === 0) return "measured";
4460
- if (measured > 0) return "partial";
4542
+ function telemetryStatus(measured2, unsupported, unknown) {
4543
+ if (measured2 > 0 && unsupported === 0 && unknown === 0) return "measured";
4544
+ if (measured2 > 0) return "partial";
4461
4545
  if (unsupported > 0 && unknown === 0) return "unsupported";
4462
4546
  return "unknown";
4463
4547
  }
@@ -4684,6 +4768,12 @@ import {
4684
4768
  } from "@tangle-network/agent-eval/analyst";
4685
4769
  import { OtlpFileTraceStore } from "@tangle-network/agent-eval/traces";
4686
4770
  var GENERATED_TRACE_FILE_CEILING = 512 * 1024 * 1024;
4771
+ function mergeCostProvenance(first, second, totalCostUsd) {
4772
+ if (!first || !second || first.kind === "uncaptured" || second.kind === "uncaptured") {
4773
+ return { kind: "uncaptured", usd: null };
4774
+ }
4775
+ return first.kind === "estimated" || second.kind === "estimated" ? { kind: "estimated", usd: totalCostUsd } : { kind: "observed", usd: totalCostUsd };
4776
+ }
4687
4777
  async function analyzeSpans(spans, opts = {}) {
4688
4778
  if (spans.length === 0) throw new Error("analyzeSpans: no spans to analyze");
4689
4779
  const otlpPath = await writeOtlpFile(spans, opts.otlpOutPath);
@@ -4716,7 +4806,13 @@ async function analyzeSpans(spans, opts = {}) {
4716
4806
  });
4717
4807
  result.findings.push(...agResult.findings);
4718
4808
  result.per_analyst.push(...agResult.per_analyst);
4719
- result.total_cost_usd += agResult.total_cost_usd;
4809
+ const totalCostUsd = result.total_cost_usd + agResult.total_cost_usd;
4810
+ result.total_cost_provenance = mergeCostProvenance(
4811
+ result.total_cost_provenance,
4812
+ agResult.total_cost_provenance,
4813
+ totalCostUsd
4814
+ );
4815
+ result.total_cost_usd = totalCostUsd;
4720
4816
  }
4721
4817
  return { otlpPath, execution, result };
4722
4818
  }
@@ -4842,9 +4938,12 @@ function decimal(value, digits = 1) {
4842
4938
  maximumFractionDigits: digits
4843
4939
  });
4844
4940
  }
4941
+ function measured(value, render) {
4942
+ return value === null ? "not captured" : render(value);
4943
+ }
4845
4944
  function distributionRow(label, distribution, unit = "") {
4846
4945
  if (distribution.n === 0) return `| ${label} | 0 | not captured | not captured | not captured | not captured | not captured |`;
4847
- const value = (number) => `${decimal(number)}${unit}`;
4946
+ const value = (number) => measured(number, (captured) => `${decimal(captured)}${unit}`);
4848
4947
  return `| ${label} | ${count(distribution.n)} | ${value(distribution.min)} | ${value(distribution.mean)} | ${value(distribution.p50)} | ${value(distribution.p95)} | ${value(distribution.max)} |`;
4849
4948
  }
4850
4949
  function tokenRows(usage) {
@@ -4856,14 +4955,23 @@ function tokenRows(usage) {
4856
4955
  ["Cache write", usage.totals.cacheWrite, usage.cacheWrite]
4857
4956
  ];
4858
4957
  return rows.map(
4859
- ([label, total, values]) => `| ${label} | ${count(total)} | ${count(values.n)} | ${decimal(values.mean)} | ${decimal(values.p50)} | ${decimal(values.p95)} | ${count(values.max)} |`
4958
+ ([label, total, values]) => `| ${label} | ${count(total)} | ${count(values.n)} | ${measured(values.mean, decimal)} | ${measured(values.p50, decimal)} | ${measured(values.p95, decimal)} | ${measured(values.max, count)} |`
4860
4959
  );
4861
4960
  }
4961
+ function analysisCost(result) {
4962
+ const cost = `$${result.total_cost_usd.toFixed(4)}`;
4963
+ if (result.total_cost_provenance?.kind === "observed") return `Analysis cost: ${cost} observed.`;
4964
+ if (result.total_cost_provenance?.kind === "estimated") return `Analysis cost: ${cost} estimated.`;
4965
+ return `Known analysis cost: ${cost}; additional cost was not captured.`;
4966
+ }
4862
4967
  function renderExecution(report) {
4863
4968
  const execution = report.execution;
4864
4969
  const lines = ["## execution facts", ""];
4865
4970
  lines.push(
4866
- `- **Sessions:** ${count(execution.durationMs.n)} | **Model-call runs:** ${count(execution.modelCalls.runs)} | **Model-call events:** ${count(execution.modelCalls.events)} | **Sessions with tool errors:** ${count(execution.failures.runs)}/${count(execution.failures.reportingRuns)} (${decimal(execution.failures.fraction * 100, 2)}%)`
4971
+ `- **Sessions:** ${count(execution.durationMs.n)} | **Model-call runs:** ${count(execution.modelCalls.runs)} | **Model-call events:** ${count(execution.modelCalls.events)} | **Sessions with execution errors:** ${count(execution.executionErrors.runs)}/${count(execution.executionErrors.reportingRuns)} (${measured(execution.executionErrors.fraction, (fraction) => `${decimal(fraction * 100, 2)}%`)})`
4972
+ );
4973
+ lines.push(
4974
+ `- **Terminal outcomes:** ${count(execution.terminalOutcomes.succeeded)} succeeded | ${count(execution.terminalOutcomes.failed)} failed | ${count(execution.terminalOutcomes.cancelled)} cancelled | ${count(execution.terminalOutcomes.incomplete)} incomplete | ${count(execution.terminalOutcomes.unknown)} unknown`
4867
4975
  );
4868
4976
  lines.push("- **Task quality:** not measured; these traces do not include comparable task outcome labels.");
4869
4977
  lines.push("");
@@ -4936,7 +5044,7 @@ function renderReport(result, meta) {
4936
5044
  lines.push("");
4937
5045
  const sessionSummary = meta.unassignedTraceCount ? `${meta.sessionCount} identified session(s) + ${meta.unassignedTraceCount} ${plural(meta.unassignedTraceCount, "trace")} without a single stable session identity` : `${meta.sessionCount} session(s)`;
4938
5046
  lines.push(
4939
- `${sessionSummary}, ${meta.spanCount} spans \u2192 **${findingSummary}** across ${result.per_analyst.length} analyst(s). Analysis cost: $${result.total_cost_usd.toFixed(4)}.`
5047
+ `${sessionSummary}, ${meta.spanCount} spans \u2192 **${findingSummary}** across ${result.per_analyst.length} analyst(s). ${analysisCost(result)}`
4940
5048
  );
4941
5049
  lines.push("");
4942
5050
  if (meta.unassignedTraceCount) {
@@ -5736,7 +5844,7 @@ async function saveReport(path, report) {
5736
5844
  }
5737
5845
 
5738
5846
  // src/live.ts
5739
- import { createHash as createHash5 } from "crypto";
5847
+ import { createHash as createHash6 } from "crypto";
5740
5848
  var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
5741
5849
  var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
5742
5850
  var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
@@ -5756,7 +5864,7 @@ var sleep = (ms, signal) => new Promise((resolve2) => {
5756
5864
  signal?.addEventListener("abort", onAbort, { once: true });
5757
5865
  });
5758
5866
  function hash(parts) {
5759
- return createHash5("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
5867
+ return createHash6("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
5760
5868
  }
5761
5869
  function asString(value) {
5762
5870
  if (typeof value === "string" && value.trim()) return value;
@@ -6516,7 +6624,7 @@ async function sessionMeta(item) {
6516
6624
  if (branch) meta[ATTR.GIT_BRANCH] = branch;
6517
6625
  return meta;
6518
6626
  }
6519
- var msToNano = (iso2) => parseIsoToEpochMs(iso2) * 1e6;
6627
+ var msToNano = (epochMs) => (BigInt(epochMs) * 1000000n).toString();
6520
6628
  function toTraceSpanEvents(spans, rootMeta) {
6521
6629
  return spans.map((s) => {
6522
6630
  const attributes = {};
@@ -6527,14 +6635,15 @@ function toTraceSpanEvents(spans, rootMeta) {
6527
6635
  attributes[ATTR.SESSION_ID] = s.trace_id;
6528
6636
  attributes[ATTR.INGEST_SOURCE] = INGEST_SOURCE_CLI;
6529
6637
  if (s.parent_span_id === null) Object.assign(attributes, rootMeta);
6530
- const startNano = msToNano(s.start_time);
6638
+ const startMs = parseIsoToEpochMs(s.start_time);
6639
+ const endMs = Math.max(startMs, parseIsoToEpochMs(s.end_time));
6531
6640
  return {
6532
6641
  traceId: s.trace_id,
6533
6642
  spanId: s.span_id,
6534
6643
  ...s.parent_span_id ? { parentSpanId: s.parent_span_id } : {},
6535
6644
  name: s.name,
6536
- startTimeUnixNano: startNano,
6537
- endTimeUnixNano: Math.max(startNano, msToNano(s.end_time)),
6645
+ startTimeUnixNano: msToNano(startMs),
6646
+ endTimeUnixNano: msToNano(endMs),
6538
6647
  attributes,
6539
6648
  ...s.status.code === "UNSET" ? {} : { status: { code: s.status.code, ...s.status.message ? { message: s.status.message } : {} } }
6540
6649
  };
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  writePolicyEvidenceFile,
41
41
  writeSessionIndexFile,
42
42
  writeTraceEvidenceExportFile
43
- } from "./chunk-BKGWYQR4.js";
43
+ } from "./chunk-NP3ZIUR2.js";
44
44
 
45
45
  // src/cli.ts
46
46
  import { readFileSync } from "fs";
package/dist/index.js CHANGED
@@ -117,7 +117,7 @@ import {
117
117
  writeSessionIndexFile,
118
118
  writeTraceEvidenceExportFile,
119
119
  writeTraceImprovementArtifacts
120
- } from "./chunk-BKGWYQR4.js";
120
+ } from "./chunk-NP3ZIUR2.js";
121
121
 
122
122
  // src/collect.ts
123
123
  async function collectSessions(opts = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/traces",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Point it at your coding-agent session traces (Claude Code, Codex, OpenCode, Gemini, Pi, …) and get failure-mode + efficiency findings. CLI + SDK over the @tangle-network/agent-eval analyst suite — observe live sessions, run your own analysts, redact, and upload to the Tangle Intelligence Platform.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@ax-llm/ax": "^23.0.1",
48
- "@tangle-network/agent-eval": "^0.124.0"
48
+ "@tangle-network/agent-eval": "^0.133.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.0.0",