@tangle-network/traces 0.23.1 → 1.0.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 (74) hide show
  1. package/README.md +61 -1078
  2. package/dist/analyst-model-call-JVUOYZ5V.js +20 -0
  3. package/dist/ask-XLS3VANY.js +39 -0
  4. package/dist/check-YF6ZHPGG.js +322 -0
  5. package/dist/chunk-3I4PJ5Q7.js +25 -0
  6. package/dist/chunk-3T62R7ON.js +5695 -0
  7. package/dist/chunk-4VKPN432.js +138 -0
  8. package/dist/chunk-6CLC7DKP.js +492 -0
  9. package/dist/chunk-7CBV5RJ2.js +535 -0
  10. package/dist/chunk-B62DOFGI.js +273 -0
  11. package/dist/chunk-CL3OQOWX.js +407 -0
  12. package/dist/chunk-DLGAKCQJ.js +782 -0
  13. package/dist/chunk-DU534HAX.js +263 -0
  14. package/dist/chunk-GLUDGUGJ.js +232 -0
  15. package/dist/chunk-J2PM5SVN.js +503 -0
  16. package/dist/chunk-M3W6TZHQ.js +381 -0
  17. package/dist/chunk-NH7HCDLB.js +759 -0
  18. package/dist/chunk-NZ5Y7UEL.js +1217 -0
  19. package/dist/chunk-O52XK7OB.js +19 -0
  20. package/dist/chunk-PYH3PL3N.js +62 -0
  21. package/dist/chunk-RZL5GAWH.js +60 -0
  22. package/dist/chunk-TR6O4S4T.js +208 -0
  23. package/dist/chunk-TW43MR5D.js +259 -0
  24. package/dist/chunk-TYLFUX42.js +228 -0
  25. package/dist/chunk-UOLRCQHC.js +120 -0
  26. package/dist/chunk-UVE5ANVV.js +3921 -0
  27. package/dist/chunk-V2OCGX57.js +53 -0
  28. package/dist/cli.js +2086 -2050
  29. package/dist/diff-ZZ6KYBPP.js +16 -0
  30. package/dist/improvement-OJQ6365E.js +25 -0
  31. package/dist/index.d.ts +1235 -4357
  32. package/dist/index.js +53 -1204
  33. package/dist/live-Y64AJ6MA.js +14 -0
  34. package/dist/mcp-ZPGTJQYQ.js +85 -0
  35. package/dist/report-JAMXRTCW.js +338 -0
  36. package/dist/run-watch-PLWTIRSV.js +649 -0
  37. package/dist/supervisor-report-RMPLLBGZ.js +29 -0
  38. package/dist/supervisor-run-context-WO6VOTGV.js +30 -0
  39. package/dist/upload-R6NMKSGQ.js +20 -0
  40. package/dist/verify-safe-C5ZXZOZ2.js +8 -0
  41. package/docs/analyze.md +76 -0
  42. package/docs/ask.md +88 -0
  43. package/docs/bundle.md +40 -0
  44. package/docs/check.md +77 -0
  45. package/docs/convert.md +44 -0
  46. package/docs/develop.md +32 -0
  47. package/docs/diff.md +38 -0
  48. package/docs/facts.md +114 -0
  49. package/docs/mcp.md +36 -0
  50. package/docs/migrate-1.0.md +33 -0
  51. package/docs/parsers.md +66 -0
  52. package/docs/release-1.0.0.md +147 -0
  53. package/docs/sdk.md +77 -0
  54. package/docs/select.md +70 -0
  55. package/docs/skills.md +46 -0
  56. package/docs/upload.md +47 -0
  57. package/docs/watch-jsonl.v1.example.jsonl +5 -0
  58. package/docs/watch.md +66 -0
  59. package/examples/README.md +9 -0
  60. package/examples/custom-analyst.ts +65 -74
  61. package/install.sh +22 -2
  62. package/package.json +8 -7
  63. package/skills/inspect-agent-traces/SKILL.md +47 -110
  64. package/dist/chunk-7DVZCCZI.js +0 -20983
  65. package/docs/replay-verify.md +0 -123
  66. package/docs/stream-findings.svg +0 -39
  67. package/docs/trace-analysts.md +0 -643
  68. package/examples/custom-backend.ts +0 -21
  69. package/examples/external-engines.ts +0 -62
  70. package/examples/improvement-config.mjs +0 -63
  71. package/examples/observe-and-alert.ts +0 -19
  72. package/examples/register-harness.ts +0 -26
  73. package/skills/build-trace-analyst/SKILL.md +0 -76
  74. package/skills/build-trace-analyst/agents/openai.yaml +0 -4
@@ -0,0 +1,228 @@
1
+ import {
2
+ readRefSpans
3
+ } from "./chunk-CL3OQOWX.js";
4
+ import {
5
+ commandTextFromInput,
6
+ isSynthesizedSpan
7
+ } from "./chunk-UVE5ANVV.js";
8
+ import {
9
+ ATTR,
10
+ SESSION_CWD_ATTR
11
+ } from "./chunk-TR6O4S4T.js";
12
+ import {
13
+ canonicalJson
14
+ } from "./chunk-DU534HAX.js";
15
+
16
+ // src/diff.ts
17
+ import { createHash } from "crypto";
18
+ import { epochMillis } from "@tangle-network/agent-eval/diagnosis";
19
+ import { diffSteps, diffStepsFromSpans } from "@tangle-network/agent-eval/pipelines";
20
+ import { OPENINFERENCE_SPAN_KIND, TOOL_NAME } from "@tangle-network/agent-eval/trace-attributes";
21
+ var TEXT_LIMIT = 300;
22
+ var WORKDIR_KEYS = ["workdir", "cwd"];
23
+ var FILE_KEYS = ["file_path", "filePath", "notebook_path"];
24
+ var RECORDED_CWD_KEYS = [SESSION_CWD_ATTR, ATTR.ENVIRONMENT_CWD];
25
+ var PATCH_HEADER = /\*\*\* (Add|Update|Delete|Move to) File: ([^\n"\\]+)/g;
26
+ var WORKTREE_PARENT = /^(.*?\/(?:_wt|\.worktrees|worktrees)\/[^/]+)\/./;
27
+ var TEMP_DIR = /(?<![\w.~-])(?:\/private)?(?:\/tmp|\/var\/tmp|\/var\/folders\/[^/\s]+\/[^/\s]+\/T)\/[^/\s'"`;|&<>(){}]+/g;
28
+ var HEX_RUN = /(?<![0-9A-Za-z])(?:[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}|[0-9A-F]{8}(?:-[0-9A-F]{4}){3}-[0-9A-F]{12}|[0-9a-f]{7,}|[0-9A-F]{7,})(?![0-9A-Za-z])/g;
29
+ function hexId(run) {
30
+ return run.includes("-") || /[0-9]/.test(run) && /[a-f]/i.test(run) ? "<hex>" : run;
31
+ }
32
+ function stringOf(record, keys) {
33
+ for (const key of keys) {
34
+ const value = record[key];
35
+ if (typeof value === "string" && value.length > 0) return value;
36
+ }
37
+ return null;
38
+ }
39
+ function jsonObject(text) {
40
+ if (!text.trimStart().startsWith("{")) return void 0;
41
+ try {
42
+ const parsed = JSON.parse(text);
43
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
44
+ } catch {
45
+ return void 0;
46
+ }
47
+ }
48
+ function patchedFiles(text) {
49
+ if (!text.includes("*** Begin Patch")) return null;
50
+ const headers = [...text.matchAll(PATCH_HEADER)].map((match) => `${match[1]} File: ${match[2].trim()}`);
51
+ return headers.length > 0 ? headers.join("; ") : null;
52
+ }
53
+ function callArgument(span) {
54
+ const raw = span.attributes["input.value"];
55
+ if (raw === void 0 || raw === null || raw === "") return { argument: null, workdir: null };
56
+ const text = typeof raw === "string" ? raw : canonicalJson(raw);
57
+ const input = jsonObject(text);
58
+ const workdir = input ? stringOf(input, WORKDIR_KEYS) : null;
59
+ const command = commandTextFromInput(text);
60
+ if (command !== void 0) return { argument: command, workdir };
61
+ if (!input) return { argument: patchedFiles(text) ?? text, workdir: null };
62
+ const file = stringOf(input, FILE_KEYS);
63
+ if (file) return { argument: file, workdir };
64
+ const patch = Object.values(input).find((value) => typeof value === "string" && value.includes("*** Begin Patch"));
65
+ const patched = patch === void 0 ? null : patchedFiles(patch);
66
+ if (patched) return { argument: patched, workdir };
67
+ const rest = Object.fromEntries(Object.entries(input).filter(([key]) => !WORKDIR_KEYS.includes(key)));
68
+ return { argument: canonicalJson(rest), workdir };
69
+ }
70
+ function escapeRegExp(text) {
71
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
72
+ }
73
+ function normalizer(cwds) {
74
+ const places = /* @__PURE__ */ new Map();
75
+ const add = (path, placeholder) => {
76
+ if (path.length < 2 || places.has(path)) return;
77
+ places.set(path, placeholder);
78
+ places.set(path.replace(/[^A-Za-z0-9]/g, "-"), placeholder);
79
+ };
80
+ for (const cwd of cwds) add(cwd, "<cwd>");
81
+ for (const cwd of cwds) {
82
+ const worktree = WORKTREE_PARENT.exec(cwd)?.[1];
83
+ if (worktree) add(worktree, "<worktree>");
84
+ }
85
+ const ordered = [...places.keys()].sort((x, y) => y.length - x.length);
86
+ const place = ordered.length === 0 ? null : new RegExp(`(?<![\\w.~-])(?:${ordered.map(escapeRegExp).join("|")})(?![\\w.@%+=,~-])`, "g");
87
+ return (text) => {
88
+ const placed = place ? text.replace(place, (match) => places.get(match)) : text;
89
+ return placed.replace(TEMP_DIR, "<tmp>").replace(HEX_RUN, hexId);
90
+ };
91
+ }
92
+ function isToolCall(span) {
93
+ return span.attributes[OPENINFERENCE_SPAN_KIND] === "TOOL" && !isSynthesizedSpan(span);
94
+ }
95
+ function toolName(span) {
96
+ const name = span.attributes[TOOL_NAME];
97
+ return typeof name === "string" && name.length > 0 ? name : span.name.replace(/^tool\./, "");
98
+ }
99
+ function oneLine(text, limit) {
100
+ const line = text.replace(/\s+/g, " ").trim();
101
+ return line.length > limit ? `${line.slice(0, limit)}\u2026` : line;
102
+ }
103
+ function containmentOrder(spans) {
104
+ const byId = /* @__PURE__ */ new Map();
105
+ for (const span of spans) if (!byId.has(span.span_id)) byId.set(span.span_id, span);
106
+ const shapes = [...byId.values()].map((span) => ({
107
+ traceId: span.trace_id,
108
+ spanId: span.span_id,
109
+ parentSpanId: span.parent_span_id,
110
+ name: span.name,
111
+ kind: String(span.attributes[OPENINFERENCE_SPAN_KIND] ?? "UNKNOWN"),
112
+ startMs: epochMillis(span.start_time),
113
+ endMs: epochMillis(span.end_time),
114
+ status: span.status.code,
115
+ statusMessage: null,
116
+ toolName: null,
117
+ model: null,
118
+ inputDigest: null,
119
+ attributes: {}
120
+ }));
121
+ return diffStepsFromSpans(shapes).map((step) => byId.get(step.id));
122
+ }
123
+ async function readRunSide(ref) {
124
+ const spans = await readRefSpans(ref);
125
+ const traces = [...new Set(spans.map((span) => span.trace_id))];
126
+ if (traces.length !== 1) {
127
+ throw new Error(traces.length === 0 ? `${ref} holds no spans` : `${ref} holds ${traces.length} runs; pick one with ${ref}#<trace id>: ${traces.slice(0, 10).join(", ")}`);
128
+ }
129
+ const calls = containmentOrder(spans).filter(isToolCall).map((span) => ({ span, ...callArgument(span) }));
130
+ const cwds = /* @__PURE__ */ new Set();
131
+ const add = (value) => {
132
+ if (typeof value === "string" && value.startsWith("/")) cwds.add(value.replace(/\/+$/, ""));
133
+ };
134
+ for (const span of spans) for (const key of RECORDED_CWD_KEYS) add(span.attributes[key]);
135
+ if (cwds.size === 0) for (const span of spans) add(span.attributes[ATTR.CWD]);
136
+ for (const call of calls) add(call.workdir);
137
+ const normalize = normalizer([...cwds]);
138
+ const steps = calls.map(({ span, argument }) => {
139
+ const normalized = argument === null ? null : normalize(argument);
140
+ return {
141
+ id: span.span_id,
142
+ name: toolName(span),
143
+ kind: "TOOL",
144
+ fields: {
145
+ status: span.status.code,
146
+ args: normalized === null ? null : createHash("sha256").update(normalized).digest("hex").slice(0, 16)
147
+ },
148
+ text: normalized === null ? null : oneLine(normalized, TEXT_LIMIT)
149
+ };
150
+ });
151
+ return { ref, traceId: traces[0], spans: spans.length, cwds: [...cwds].sort(), steps };
152
+ }
153
+ function agreementCaveat(a, b) {
154
+ if (a.length === 0 && b.length === 0) return "neither run made a tool call";
155
+ const decided = (steps) => steps.some((step) => step.fields.status === "OK" || step.fields.status === "ERROR");
156
+ if (!decided(a) && !decided(b)) return "no tool call on either side records an OK or ERROR status (all UNSET)";
157
+ return void 0;
158
+ }
159
+ async function diffRuns(a, b) {
160
+ const [left, right] = await Promise.all([readRunSide(a), readRunSide(b)]);
161
+ const keyed = (side) => left.traceId === right.traceId ? [...side.steps] : side.steps.map((step) => ({ ...step, id: `${side.traceId}\0${step.id}` }));
162
+ const diff = diffSteps(keyed(left), keyed(right));
163
+ const caveat = diff.firstDivergence ? void 0 : agreementCaveat(left.steps, right.steps);
164
+ return { kind: "traces.run_diff", a: left, b: right, diff, ...caveat ? { caveat } : {} };
165
+ }
166
+ var SHOWN = 120;
167
+ function shown(text, other) {
168
+ if (text === null) return "(input not recorded)";
169
+ if (other === null || other === text || text.length <= SHOWN) return oneLine(text, SHOWN);
170
+ let at = 0;
171
+ while (at < text.length && text[at] === other[at]) at += 1;
172
+ if (at < SHOWN - 20) return oneLine(text, SHOWN);
173
+ const start = at - 40;
174
+ return `\u2026${oneLine(text.slice(start), SHOWN)}`;
175
+ }
176
+ function stepLine(side, step, other, count) {
177
+ if (!step) return ` ${side} (no more tool calls: it made ${count})`;
178
+ const status = step.fields.status === "OK" ? "" : ` [${String(step.fields.status)}]`;
179
+ return ` ${side} ${step.name}${status} ${shown(step.text, other?.text ?? null)}`;
180
+ }
181
+ function why(first) {
182
+ switch (first.kind) {
183
+ case "changed":
184
+ return first.differences.map((d) => d.field === "args" ? "argument" : d.field).join(", ");
185
+ case "replaced":
186
+ return "tool";
187
+ case "only-in-a":
188
+ return "a call only A makes";
189
+ case "only-in-b":
190
+ return "a call only B makes";
191
+ case "reordered":
192
+ return "order: each call is made elsewhere in the other run";
193
+ }
194
+ }
195
+ function sideLine(label, side) {
196
+ const cwd = side.cwds.length === 0 ? "" : ` \xB7 cwd ${side.cwds[0]}${side.cwds.length > 1 ? ` (+${side.cwds.length - 1})` : ""}`;
197
+ return `${label} ${side.ref} ${side.steps.length} tool call${side.steps.length === 1 ? "" : "s"}${cwd}`;
198
+ }
199
+ function renderRunDiff(report) {
200
+ const { a, b, diff } = report;
201
+ const lines = [sideLine("A", a), sideLine("B", b)];
202
+ const first = diff.firstDivergence;
203
+ if (!first) {
204
+ lines.push(report.caveat ? `no difference in the steps, but no agreement on outcome either: ${report.caveat}` : `no difference: all ${a.steps.length} tool calls agree on tool, status and argument`);
205
+ return `${lines.join("\n")}
206
+ `;
207
+ }
208
+ const stepA = first.a === null ? void 0 : a.steps[first.a];
209
+ const stepB = first.b === null ? void 0 : b.steps[first.b];
210
+ const agreed = first.index === 0 ? "no step before it" : first.index === 1 ? "step 1 agrees" : `steps 1-${first.index} agree`;
211
+ lines.push(
212
+ `first difference at step ${first.index + 1} (${why(first)}); ${agreed}:`,
213
+ stepLine("A", stepA, stepB, a.steps.length),
214
+ stepLine("B", stepB, stepA, b.steps.length)
215
+ );
216
+ if (stepA && stepB && stepA.text === stepB.text && stepA.fields.args !== stepB.fields.args) {
217
+ lines.push(` (the arguments differ after their first ${TEXT_LIMIT} characters)`);
218
+ }
219
+ const changed = diff.pairs.filter((pair) => pair.differences.length > 0).length;
220
+ lines.push(`overall: ${diff.pairs.length} paired (${changed} differ), ${diff.onlyInA.length} only in A, ${diff.onlyInB.length} only in B`);
221
+ return `${lines.join("\n")}
222
+ `;
223
+ }
224
+
225
+ export {
226
+ diffRuns,
227
+ renderRunDiff
228
+ };
@@ -0,0 +1,120 @@
1
+ import {
2
+ orderSpans,
3
+ ruleContext,
4
+ runRules
5
+ } from "./chunk-7CBV5RJ2.js";
6
+ import {
7
+ parseSession,
8
+ scanSessions
9
+ } from "./chunk-3T62R7ON.js";
10
+ import {
11
+ sessionIdFromAttributes
12
+ } from "./chunk-TR6O4S4T.js";
13
+
14
+ // src/live.ts
15
+ var sleep = (ms, signal) => new Promise((resolve) => {
16
+ if (signal?.aborted) {
17
+ resolve();
18
+ return;
19
+ }
20
+ const onAbort = () => {
21
+ clearTimeout(t);
22
+ resolve();
23
+ };
24
+ const t = setTimeout(() => {
25
+ signal?.removeEventListener("abort", onAbort);
26
+ resolve();
27
+ }, ms);
28
+ signal?.addEventListener("abort", onAbort, { once: true });
29
+ });
30
+ function errorText(error) {
31
+ return error instanceof Error ? error.message : String(error);
32
+ }
33
+ async function watchSource(source, opts) {
34
+ const intervalMs = Math.max(250, opts.intervalMs ?? 5e3);
35
+ const seenSessions = /* @__PURE__ */ new Set();
36
+ const spanCounts = /* @__PURE__ */ new Map();
37
+ const seenFindings = /* @__PURE__ */ new Set();
38
+ while (!opts.signal?.aborted) {
39
+ let sessions = 0;
40
+ let newSpans = 0;
41
+ let findings = 0;
42
+ const unread = [];
43
+ for await (const { session, spans } of source(unread)) {
44
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
45
+ const ordered = orderSpans(spans);
46
+ const key = `${session.harness}:${session.sessionId}`;
47
+ sessions += 1;
48
+ newSpans += Math.max(0, ordered.length - (spanCounts.get(key) ?? 0));
49
+ spanCounts.set(key, ordered.length);
50
+ if (!seenSessions.has(key)) {
51
+ seenSessions.add(key);
52
+ await opts.onEvent({
53
+ schemaVersion: 1,
54
+ kind: "traces.stream.session",
55
+ event: "session",
56
+ generatedAt,
57
+ session,
58
+ spanCount: ordered.length,
59
+ startedAt: ordered[0]?.start_time ?? null,
60
+ endedAt: ordered.at(-1)?.end_time ?? null
61
+ });
62
+ }
63
+ for (const finding of runRules(ruleContext(ordered, { session, generatedAt }), { extraAnalysts: opts.extraAnalysts })) {
64
+ if (seenFindings.has(finding.id)) continue;
65
+ seenFindings.add(finding.id);
66
+ findings += 1;
67
+ await opts.onEvent({ schemaVersion: 1, kind: "traces.stream.finding", event: "finding", generatedAt, finding });
68
+ }
69
+ }
70
+ await opts.onEvent({
71
+ schemaVersion: 1,
72
+ kind: "traces.stream.tick",
73
+ event: "tick",
74
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
75
+ sessions,
76
+ newSpans,
77
+ findings,
78
+ unreadSessions: unread
79
+ });
80
+ if (opts.once || opts.signal?.aborted) break;
81
+ await sleep(intervalMs, opts.signal);
82
+ }
83
+ }
84
+ function sessionSource(opts) {
85
+ return async function* (unread) {
86
+ const skip = (error, ref) => {
87
+ unread.push({ sessionId: ref?.sessionId ?? null, source: ref?.path ?? "a session store", reason: errorText(error) });
88
+ opts.onError?.(error, ref);
89
+ };
90
+ const named = opts.sessions;
91
+ if (named) {
92
+ for (const { adapter, ref } of named) {
93
+ if (opts.signal?.aborted) return;
94
+ let spans;
95
+ try {
96
+ spans = await parseSession(adapter, ref, { corruptionMode: opts.corruptionMode, signal: opts.signal });
97
+ } catch (error) {
98
+ skip(error, ref);
99
+ continue;
100
+ }
101
+ const root = spans.find((span) => span.parent_span_id === null) ?? spans[0];
102
+ const sessionId = (root && sessionIdFromAttributes(root.attributes)) ?? ref.sessionId;
103
+ yield { session: { harness: adapter.harness, sessionId, cwd: ref.cwd, path: ref.path }, spans };
104
+ }
105
+ return;
106
+ }
107
+ const scanned = scanSessions({ ...opts, sinceMs: opts.sinceMs ?? Date.now() - (opts.windowMs ?? 30 * 6e4), onError: skip });
108
+ for await (const { adapter, ref, spans } of scanned) {
109
+ yield { session: { harness: adapter.harness, sessionId: ref.sessionId, cwd: ref.cwd, path: ref.path }, spans };
110
+ }
111
+ };
112
+ }
113
+ async function streamSessions(opts) {
114
+ await watchSource(sessionSource(opts), opts);
115
+ }
116
+
117
+ export {
118
+ watchSource,
119
+ streamSessions
120
+ };