@tangle-network/traces 0.9.5 → 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") {
@@ -5760,7 +5844,7 @@ async function saveReport(path, report) {
5760
5844
  }
5761
5845
 
5762
5846
  // src/live.ts
5763
- import { createHash as createHash5 } from "crypto";
5847
+ import { createHash as createHash6 } from "crypto";
5764
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;
5765
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;
5766
5850
  var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
@@ -5780,7 +5864,7 @@ var sleep = (ms, signal) => new Promise((resolve2) => {
5780
5864
  signal?.addEventListener("abort", onAbort, { once: true });
5781
5865
  });
5782
5866
  function hash(parts) {
5783
- 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);
5784
5868
  }
5785
5869
  function asString(value) {
5786
5870
  if (typeof value === "string" && value.trim()) return value;
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  writePolicyEvidenceFile,
41
41
  writeSessionIndexFile,
42
42
  writeTraceEvidenceExportFile
43
- } from "./chunk-S4TEBT7Q.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-S4TEBT7Q.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.5",
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",