@tangle-network/traces 0.23.0 → 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-COSWFESO.js +0 -20979
  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,535 @@
1
+ import {
2
+ commandLines,
3
+ commandTextFromInput,
4
+ computeSessionFacts
5
+ } from "./chunk-UVE5ANVV.js";
6
+ import {
7
+ isInheritedSpan,
8
+ isSubagentSpan,
9
+ isSynthesizedSpan
10
+ } from "./chunk-TR6O4S4T.js";
11
+ import {
12
+ appendAll,
13
+ validateOtlpSpans
14
+ } from "./chunk-NZ5Y7UEL.js";
15
+ import {
16
+ parseIsoToEpochMs,
17
+ toolArgumentsFromAttributes
18
+ } from "./chunk-DU534HAX.js";
19
+
20
+ // src/pipelines.ts
21
+ import { createHash } from "crypto";
22
+ import { basename } from "path";
23
+ import { computeToolUseMetrics } from "@tangle-network/agent-eval";
24
+ import {
25
+ failureClusterView,
26
+ stuckLoopView
27
+ } from "@tangle-network/agent-eval/pipelines";
28
+
29
+ // src/failure-followup.ts
30
+ import { OPENINFERENCE_SPAN_KIND, TOOL_NAME } from "@tangle-network/agent-eval/trace-attributes";
31
+ function argKeyOf(span) {
32
+ const sha = span.attributes["traces.input.sha256"];
33
+ if (typeof sha === "string" && sha.length > 0) return `sha:${sha}`;
34
+ const value = span.attributes["input.value"];
35
+ if (typeof value === "string" && value.length > 0) return `raw:${value}`;
36
+ return null;
37
+ }
38
+ function schemeOf(key) {
39
+ return key.slice(0, key.indexOf(":"));
40
+ }
41
+ var byCallOrder = (a, b) => a.start.localeCompare(b.start) || a.step - b.step;
42
+ function failureFollowUpSequences(spans) {
43
+ const calls = [];
44
+ for (const s of spans) {
45
+ if (s.attributes[OPENINFERENCE_SPAN_KIND] !== "TOOL") continue;
46
+ const toolName2 = s.attributes[TOOL_NAME];
47
+ if (typeof toolName2 !== "string" || toolName2.length === 0) continue;
48
+ calls.push({ span: s, toolName: toolName2, argKey: argKeyOf(s) });
49
+ }
50
+ calls.sort(
51
+ (a, b) => a.span.start_time.localeCompare(b.span.start_time) || (Number(a.span.attributes.step) || 0) - (Number(b.span.attributes.step) || 0)
52
+ );
53
+ const sequences = /* @__PURE__ */ new Map();
54
+ for (const call of calls) {
55
+ const key = `${call.span.trace_id}|${call.toolName}`;
56
+ const seq = sequences.get(key);
57
+ if (seq) seq.push(call);
58
+ else sequences.set(key, [call]);
59
+ }
60
+ const out = [];
61
+ for (const seq of sequences.values()) {
62
+ const items = [];
63
+ for (let i = 0; i < seq.length; i++) {
64
+ const call = seq[i];
65
+ if (call.span.status.code !== "ERROR") continue;
66
+ if (call.span.attributes["traces.expected_blocking"] === true) continue;
67
+ const followUp = seq[i + 1] ?? null;
68
+ let kind;
69
+ if (!followUp) {
70
+ kind = "none";
71
+ } else if (call.argKey !== null && followUp.argKey !== null && schemeOf(call.argKey) === schemeOf(followUp.argKey)) {
72
+ kind = call.argKey === followUp.argKey ? "blind" : "adapted";
73
+ } else {
74
+ kind = "args-unknown";
75
+ }
76
+ items.push({
77
+ traceId: call.span.trace_id,
78
+ toolName: call.toolName,
79
+ failedSpanId: call.span.span_id,
80
+ followUpSpanId: followUp?.span.span_id ?? null,
81
+ kind,
82
+ followUpSucceeded: !followUp || followUp.span.status.code === "UNSET" ? null : followUp.span.status.code === "OK"
83
+ });
84
+ }
85
+ if (items.length > 0) out.push({ firstStart: seq[0].span.start_time, firstStep: Number(seq[0].span.attributes.step) || 0, items });
86
+ }
87
+ return out;
88
+ }
89
+ function failureFollowUpReport(sequences) {
90
+ const ordered = sequences.map((sequence) => ({ sequence, start: sequence.firstStart, step: sequence.firstStep })).sort(byCallOrder);
91
+ const items = ordered.flatMap(({ sequence }) => sequence.items);
92
+ const blindByTool = {};
93
+ for (const item of items) if (item.kind === "blind") blindByTool[item.toolName] = (blindByTool[item.toolName] ?? 0) + 1;
94
+ const followedItems = items.filter((item) => item.kind !== "none");
95
+ return {
96
+ failures: items.length,
97
+ followed: followedItems.length,
98
+ blind: items.filter((item) => item.kind === "blind").length,
99
+ adapted: items.filter((item) => item.kind === "adapted").length,
100
+ argsUnknown: items.filter((item) => item.kind === "args-unknown").length,
101
+ followUpSucceeded: followedItems.filter((item) => item.followUpSucceeded === true).length,
102
+ blindByTool,
103
+ items
104
+ };
105
+ }
106
+
107
+ // src/runtime-store.ts
108
+ import { InMemoryTraceStore } from "@tangle-network/agent-eval";
109
+ var KIND = {
110
+ AGENT: "agent",
111
+ LLM: "llm",
112
+ TOOL: "tool",
113
+ CHAIN: "custom",
114
+ EMBEDDING: "custom",
115
+ RERANKER: "custom",
116
+ GUARDRAIL: "custom",
117
+ PROMPT: "custom",
118
+ EVALUATOR: "custom",
119
+ RETRIEVER: "custom",
120
+ UNKNOWN: "custom"
121
+ };
122
+ async function toRuntimeStore(spans) {
123
+ const validated = validateOtlpSpans(spans, "runtime store input");
124
+ const store = new InMemoryTraceStore();
125
+ const runs = /* @__PURE__ */ new Map();
126
+ for (const item of validated) {
127
+ const startedAt = parseIsoToEpochMs(item.start_time);
128
+ const endedAt = parseIsoToEpochMs(item.end_time);
129
+ const terminalFailure = item.parent_span_id === null && item.status.code === "ERROR";
130
+ const current = runs.get(item.trace_id);
131
+ runs.set(item.trace_id, current ? {
132
+ startedAt: Math.min(current.startedAt, startedAt),
133
+ endedAt: Math.max(current.endedAt, endedAt),
134
+ failed: current.failed || terminalFailure
135
+ } : { startedAt, endedAt, failed: terminalFailure });
136
+ }
137
+ for (const [runId, run] of runs) {
138
+ await store.appendRun({
139
+ runId,
140
+ scenarioId: "session",
141
+ startedAt: run.startedAt,
142
+ endedAt: run.endedAt,
143
+ status: run.failed ? "failed" : "completed"
144
+ });
145
+ }
146
+ for (const s of validated) {
147
+ const kind = KIND[s.attributes["openinference.span.kind"]] ?? "custom";
148
+ const startedAt = parseIsoToEpochMs(s.start_time);
149
+ const endedAt = parseIsoToEpochMs(s.end_time);
150
+ const attributes = {
151
+ ...s.attributes,
152
+ "traces.source.trace_id": s.trace_id,
153
+ "traces.source.span_id": s.span_id,
154
+ ...s.parent_span_id === null ? {} : { "traces.source.parent_span_id": s.parent_span_id }
155
+ };
156
+ const base = {
157
+ spanId: runtimeSpanId(s.trace_id, s.span_id),
158
+ parentSpanId: s.parent_span_id === null ? void 0 : runtimeSpanId(s.trace_id, s.parent_span_id),
159
+ runId: s.trace_id,
160
+ name: s.name,
161
+ startedAt,
162
+ endedAt,
163
+ status: s.status.code === "ERROR" ? "error" : s.status.code === "OK" ? "ok" : void 0,
164
+ error: s.status.message,
165
+ attributes
166
+ };
167
+ const tool = kind === "tool" ? toolArgumentsFromAttributes(s.attributes) : void 0;
168
+ const span = kind === "tool" ? {
169
+ ...base,
170
+ kind: "tool",
171
+ toolName: String(s.attributes["tool.name"] ?? "tool"),
172
+ args: tool.args,
173
+ argsCaptured: tool.argsCaptured,
174
+ latencyMs: Math.max(0, endedAt - startedAt)
175
+ } : { ...base, kind };
176
+ await store.appendSpan(span);
177
+ }
178
+ return { store, runIds: [...runs.keys()] };
179
+ }
180
+ function runtimeSpanId(traceId, spanId) {
181
+ return `${encodeURIComponent(traceId)}:${encodeURIComponent(spanId)}`;
182
+ }
183
+
184
+ // src/pipelines.ts
185
+ async function tracePipelines(spans, opts) {
186
+ const { store, runIds } = await toRuntimeStore(spans);
187
+ const loopEligible = spans.filter((item) => item.attributes["traces.expected_blocking"] !== true);
188
+ const loopStore = loopEligible.length === spans.length ? store : (await toRuntimeStore(loopEligible)).store;
189
+ const [stuckLoops, failureClusters, toolUse] = await Promise.all([
190
+ stuckLoopView(loopStore, { minOccurrences: opts.minLoopOccurrences ?? 3 }),
191
+ failureClusterView(store),
192
+ Promise.all(runIds.map((runId) => computeToolUseMetrics(store, runId)))
193
+ ]);
194
+ return { stuckLoops, failureClusters, toolUse };
195
+ }
196
+ var clusterKey = (cluster) => `${cluster.failureClass}|${cluster.toolName ?? ""}|${cluster.argPrefix ?? ""}|${cluster.dimension ?? ""}`;
197
+ function createPipelineAccumulator(opts = {}) {
198
+ const loops = [];
199
+ let loopRuns = 0;
200
+ const clusters = /* @__PURE__ */ new Map();
201
+ let totalFailures = 0;
202
+ let totalRuns = 0;
203
+ const toolUse = [];
204
+ const sequences = [];
205
+ return {
206
+ async add(spans) {
207
+ if (spans.length === 0) return;
208
+ const groups = /* @__PURE__ */ new Map();
209
+ for (const span of spans) {
210
+ const group = groups.get(span.trace_id);
211
+ if (group) group.push(span);
212
+ else groups.set(span.trace_id, [span]);
213
+ }
214
+ for (const group of groups.values()) {
215
+ const one = await tracePipelines(group, opts);
216
+ appendAll(loops, one.stuckLoops.findings);
217
+ loopRuns += one.stuckLoops.totalRuns;
218
+ for (const cluster of one.failureClusters.clusters) {
219
+ const key = clusterKey(cluster);
220
+ const held = clusters.get(key);
221
+ if (!held) {
222
+ clusters.set(key, { ...cluster, scenarioIds: [...cluster.scenarioIds] });
223
+ continue;
224
+ }
225
+ held.runCount += cluster.runCount;
226
+ for (const id of cluster.scenarioIds) if (!held.scenarioIds.includes(id)) held.scenarioIds.push(id);
227
+ }
228
+ totalFailures += one.failureClusters.totalFailures;
229
+ totalRuns += one.failureClusters.totalRuns;
230
+ appendAll(toolUse, one.toolUse);
231
+ }
232
+ appendAll(sequences, failureFollowUpSequences(spans));
233
+ },
234
+ report() {
235
+ const affected = new Set(loops.map((finding2) => finding2.runId));
236
+ return {
237
+ stuckLoops: { findings: [...loops], affectedRunRatio: loopRuns > 0 ? affected.size / loopRuns : 0, totalRuns: loopRuns },
238
+ failureClusters: { clusters: [...clusters.values()].sort((a, b) => b.runCount - a.runCount), totalFailures, totalRuns },
239
+ toolUse: [...toolUse],
240
+ failureFollowUps: failureFollowUpReport(sequences)
241
+ };
242
+ }
243
+ };
244
+ }
245
+ function hash(parts) {
246
+ return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
247
+ }
248
+ function asString(value) {
249
+ if (typeof value === "string" && value.trim()) return value;
250
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
251
+ return void 0;
252
+ }
253
+ function spanKind(span) {
254
+ return asString(span.attributes["openinference.span.kind"])?.toUpperCase() ?? "UNKNOWN";
255
+ }
256
+ function toolName(span) {
257
+ return asString(span.attributes["tool.name"]) ?? span.name.replace(/^tool\./, "");
258
+ }
259
+ function scriptOf(span) {
260
+ const input = span.attributes["input.value"];
261
+ return typeof input === "string" ? commandTextFromInput(input) : void 0;
262
+ }
263
+ function isTool(span) {
264
+ if (isSynthesizedSpan(span.attributes)) return false;
265
+ return spanKind(span) === "TOOL" || span.attributes["tool.name"] != null || span.name.startsWith("tool.");
266
+ }
267
+ function isOwnAct(span) {
268
+ return !isInheritedSpan(span.attributes) && !isSynthesizedSpan(span.attributes) && !isSubagentSpan(span.attributes);
269
+ }
270
+ var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun", "npx", "pnpx", "bunx"]);
271
+ var CHECK_SCRIPT = /^(?:test|tests|vitest|jest|build|typecheck|tsc|lint|check|verify|e2e|ci|gate|signoff|preflight)(?::[\w:-]+)?$/u;
272
+ var CHECK_PROGRAMS = /* @__PURE__ */ new Set(["vitest", "jest", "pytest", "tsc", "eslint", "biome", "mocha", "playwright", "ruff", "mypy", "tox", "bats", "shellcheck", "beelink-gate"]);
273
+ var INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3", "node", "bash", "sh", "zsh", "bun", "deno", "tsx", "ts-node", "ruby"]);
274
+ var CHECK_SCRIPT_FILE = /(?:^|[_.-])(?:test|tests|spec|verify|check|checks|smoke|lint)(?:[_.-]|$)/u;
275
+ var CHECK_SUBCOMMANDS = {
276
+ cargo: /* @__PURE__ */ new Set(["test", "build", "check", "clippy", "nextest"]),
277
+ go: /* @__PURE__ */ new Set(["test", "build", "vet"]),
278
+ make: /* @__PURE__ */ new Set(["test", "check", "build", "lint", "verify"]),
279
+ forge: /* @__PURE__ */ new Set(["test", "build"]),
280
+ deno: /* @__PURE__ */ new Set(["test", "check", "lint"]),
281
+ node: /* @__PURE__ */ new Set(["--test"]),
282
+ python: /* @__PURE__ */ new Set(["-m"]),
283
+ python3: /* @__PURE__ */ new Set(["-m"])
284
+ };
285
+ function isCheckCommand(line) {
286
+ const words = line.split(" ").filter((word) => word.length > 0);
287
+ const program = basename(words[0] ?? "");
288
+ if (CHECK_PROGRAMS.has(program)) return true;
289
+ if (/[./]/u.test(words[0] ?? "") && CHECK_SCRIPT_FILE.test(program)) return true;
290
+ if (INTERPRETERS.has(program)) {
291
+ const script = words.slice(1).find((word) => !word.startsWith("-"));
292
+ if (script !== void 0 && /\.(?:py|sh|js|mjs|cjs|ts|mts|rb)$/u.test(script) && CHECK_SCRIPT_FILE.test(basename(script))) return true;
293
+ }
294
+ const sub = CHECK_SUBCOMMANDS[program];
295
+ if (sub && words[1] !== void 0 && sub.has(words[1])) {
296
+ return !(program.startsWith("python") && !/^(?:pytest|unittest|mypy)$/u.test(words[2] ?? ""));
297
+ }
298
+ if (!PACKAGE_RUNNERS.has(program)) return false;
299
+ for (let at = 1; at < words.length; at++) {
300
+ const word = words[at];
301
+ if (word === "run" || word === "exec" || word === "dlx") continue;
302
+ if (word.startsWith("-")) {
303
+ if (/^(?:--filter|-F|-C|--dir|--prefix|-w|--workspace)$/u.test(word)) at++;
304
+ continue;
305
+ }
306
+ return CHECK_SCRIPT.test(word) || CHECK_PROGRAMS.has(basename(word));
307
+ }
308
+ return false;
309
+ }
310
+ var WRITE_COMMAND = /^(?:sed (?:-E |-r )?-i|perl -p?i|git apply(?! --(?:check|stat|numstat))|patch |apply_patch)/u;
311
+ var WRITE_TOOL = /^(?:edit|write|multiedit|notebookedit|apply_patch|write_file|create_file|edit_file|str_replace_editor|str_replace_based_edit_tool)$/iu;
312
+ var SCRATCH_PATH = /(?:^|\s)(?:\/private)?\/tmp\/|(?:^|\s)\/var\/folders\/|\/scratchpad\/|\/\.claude\/|\/\.codex[^/]*\//u;
313
+ function writtenPaths(span) {
314
+ const input = span.attributes["input.value"];
315
+ if (typeof input !== "string") return [];
316
+ const patched = [...input.matchAll(/\*\*\* (?:Add|Update|Delete) File: ([^\n"\\]+)/gu)].map((match) => match[1].trim());
317
+ if (patched.length > 0) return patched;
318
+ try {
319
+ const parsed = JSON.parse(input);
320
+ return [parsed.file_path, parsed.path, parsed.notebook_path].filter((value) => typeof value === "string");
321
+ } catch {
322
+ return [];
323
+ }
324
+ }
325
+ function writesWork(span) {
326
+ if (!WRITE_TOOL.test(toolName(span))) return false;
327
+ const paths = writtenPaths(span);
328
+ return paths.length === 0 || paths.some((path) => !SCRATCH_PATH.test(path));
329
+ }
330
+ var READ_COMMAND = /^(?:cat|less|head|tail|grep|rg|sed -n|nl|ls|find|fd|git show|git diff|git log|jq)\b/u;
331
+ var READ_TOOL = /^(?:read|grep|glob|ls|view|read_file|list_dir|search|find_files)$|grep|find_files/iu;
332
+ var WRAPPERS = /* @__PURE__ */ new Set(["timeout", "env", "nice", "nohup", "setsid", "time", "sudo", "stdbuf", "ionice", "caffeinate"]);
333
+ var SSH_VALUE_OPTION = /^-[BbcDEeFIiJLlmOoPpQRSWw]$/u;
334
+ function runCommands(line, depth = 0) {
335
+ const words = line.split(" ").filter((word) => word.length > 0);
336
+ let at = 0;
337
+ while (at < words.length) {
338
+ if (/^\w+=/u.test(words[at])) {
339
+ at++;
340
+ continue;
341
+ }
342
+ if (!WRAPPERS.has(basename(words[at]))) break;
343
+ at++;
344
+ while (at < words.length && (words[at].startsWith("-") || /^\d+(?:\.\d+)?[smhd]?$/u.test(words[at]) || /^\w+=/u.test(words[at]))) at++;
345
+ }
346
+ const rest = words.slice(at);
347
+ if (rest.length === 0) return [];
348
+ if (basename(rest[0]) !== "ssh" || depth >= 2) return [rest.join(" ")];
349
+ let host = 1;
350
+ while (host < rest.length && rest[host].startsWith("-")) host += SSH_VALUE_OPTION.test(rest[host]) ? 2 : 1;
351
+ const remote = rest.slice(host + 1).join(" ");
352
+ return remote ? commandLines(remote).flatMap((command) => runCommands(command, depth + 1)) : [];
353
+ }
354
+ function expand(line, vars) {
355
+ return vars.size === 0 ? line : line.replace(/\$\{?(\w+)\}?/gu, (whole, name) => vars.get(name) ?? whole);
356
+ }
357
+ function expandedLines(lines) {
358
+ const vars = /* @__PURE__ */ new Map();
359
+ return lines.map((line) => {
360
+ const assignment = /^(\w+)=(\S+)$/u.exec(line);
361
+ if (assignment) vars.set(assignment[1], expand(assignment[2], vars));
362
+ return expand(line, vars);
363
+ });
364
+ }
365
+ function actionKind(span, commands) {
366
+ if (isTool(span)) {
367
+ const lines = expandedLines(commands);
368
+ if (lines.flatMap((line) => runCommands(line)).some(isCheckCommand)) return "verify";
369
+ if (writesWork(span) || lines.flatMap((line) => runCommands(line)).some((line) => WRITE_COMMAND.test(line) && !SCRATCH_PATH.test(line))) return "edit";
370
+ if (READ_TOOL.test(toolName(span)) || commands.length > 0 && commands.every((line) => READ_COMMAND.test(line))) return "read";
371
+ return "tool";
372
+ }
373
+ return "other";
374
+ }
375
+ function classifyLiveActions(spans) {
376
+ return orderSpans(spans).filter(isOwnAct).map((span) => {
377
+ const tool = isTool(span) ? toolName(span) : void 0;
378
+ const script = tool ? scriptOf(span) : void 0;
379
+ const commands = script === void 0 ? [] : commandLines(script);
380
+ return {
381
+ kind: actionKind(span, commands),
382
+ spanId: span.span_id,
383
+ span,
384
+ ...tool ? { toolName: tool } : {},
385
+ signature: commands.length > 0 ? commands.join("\n") : tool ?? span.name,
386
+ errored: span.status.code === "ERROR"
387
+ };
388
+ });
389
+ }
390
+ function timeOf(span) {
391
+ const parsed = Date.parse(span.start_time);
392
+ return Number.isFinite(parsed) ? parsed : 0;
393
+ }
394
+ function orderSpans(spans) {
395
+ return [...spans].sort((a, b) => {
396
+ const stepA = typeof a.attributes.step === "number" ? a.attributes.step : void 0;
397
+ const stepB = typeof b.attributes.step === "number" ? b.attributes.step : void 0;
398
+ if (stepA != null && stepB != null && stepA !== stepB) return stepA - stepB;
399
+ return timeOf(a) - timeOf(b);
400
+ });
401
+ }
402
+ function evidence(kind, label, value, spanIds) {
403
+ return { kind, label, value, ...spanIds && spanIds.length > 0 ? { spanIds } : {} };
404
+ }
405
+ function finding(input) {
406
+ const fingerprint = hash([input.ruleId, ...input.signature]);
407
+ return {
408
+ schemaVersion: 1,
409
+ kind: "traces.live_finding",
410
+ id: `live.${input.ruleId}.${fingerprint}`,
411
+ fingerprint,
412
+ ruleId: input.ruleId,
413
+ severity: input.severity,
414
+ title: input.title,
415
+ claim: input.claim,
416
+ action: input.action,
417
+ check: input.check,
418
+ evidence: input.evidence,
419
+ session: input.session,
420
+ observedAt: input.observedAt
421
+ };
422
+ }
423
+ var oneLine = (text, cap) => {
424
+ const line = text.replace(/\s+/gu, " ").trim();
425
+ return line.length > cap ? `${line.slice(0, cap - 1)}\u2026` : line;
426
+ };
427
+ var sameFailingCommandAnalyst = {
428
+ id: "same-failing-command",
429
+ analyze(context) {
430
+ return context.facts().flatMap((facts) => (facts.measurements.waste.failingStreaks.value ?? []).map((streak) => {
431
+ const command = streak.command === null ? `a ${streak.tool} command` : `\`${oneLine(streak.command, 120)}\``;
432
+ return finding({
433
+ ruleId: "same-failing-command",
434
+ severity: "high",
435
+ title: "The same command failed again and again",
436
+ claim: `${command} failed ${streak.runs}\xD7 in a row (${streak.minutes} min).`,
437
+ action: "Stop rerunning it; read the first failure, change state, or choose a different diagnostic path before another run.",
438
+ check: "The next action should read the failing output, edit code or config, or run a narrower command before this one runs again.",
439
+ evidence: [
440
+ evidence("pattern", "command", streak.command ?? streak.tool, streak.spanIds),
441
+ evidence("metric", "failed_runs_in_a_row", String(streak.runs)),
442
+ evidence("metric", "minutes", String(streak.minutes))
443
+ ],
444
+ session: context.session,
445
+ observedAt: context.generatedAt,
446
+ signature: [streak.command ?? streak.tool, streak.at, String(streak.runs)]
447
+ });
448
+ }));
449
+ }
450
+ };
451
+ var RERUN_CHECKS = 3;
452
+ var verificationWithoutChangeAnalyst = {
453
+ id: "verification-without-change",
454
+ analyze(context) {
455
+ let worst;
456
+ let runs = /* @__PURE__ */ new Map();
457
+ const settle = () => {
458
+ for (const entry of runs) if (!worst || entry[1].length > worst[1].length) worst = entry;
459
+ runs = /* @__PURE__ */ new Map();
460
+ };
461
+ for (const action of context.actions) {
462
+ if (action.kind === "edit") settle();
463
+ else if (action.kind === "verify") runs.set(action.signature, [...runs.get(action.signature) ?? [], action.spanId]);
464
+ }
465
+ settle();
466
+ const [signature, spanIds] = worst ?? [];
467
+ if (signature === void 0 || spanIds === void 0 || spanIds.length < RERUN_CHECKS) return [];
468
+ return [finding({
469
+ ruleId: "verification-without-change",
470
+ severity: spanIds.length >= 5 ? "high" : "medium",
471
+ title: "A check is rerun without a change",
472
+ claim: `\`${oneLine(signature, 120)}\` ran ${spanIds.length}\xD7 with no edit between.`,
473
+ action: "Do not run the check again until the agent edits state or says what new signal the rerun will reveal.",
474
+ check: "The next action should be an edit, a targeted read, or a different, narrower check.",
475
+ evidence: [evidence("pattern", "check", signature, spanIds), evidence("metric", "runs_without_an_edit", String(spanIds.length))],
476
+ session: context.session,
477
+ observedAt: context.generatedAt,
478
+ signature: [signature, String(spanIds.length)]
479
+ })];
480
+ }
481
+ };
482
+ var highToolErrorRateAnalyst = {
483
+ id: "high-tool-error-rate",
484
+ analyze(context) {
485
+ if (context.tools.length < 4 || context.erroredTools.length / context.tools.length < 0.5) return [];
486
+ return [finding({
487
+ ruleId: "high-tool-error-rate",
488
+ severity: context.erroredTools.length / context.tools.length >= 0.75 ? "high" : "medium",
489
+ title: "Tool calls are mostly failing",
490
+ claim: `${context.erroredTools.length}/${context.tools.length} tool call(s) ended with ERROR.`,
491
+ action: "Switch from broad execution to diagnosis: classify the dominant error, then run one targeted command to validate the fix path.",
492
+ check: "The next calls should reduce the error ratio or show a new targeted diagnostic result.",
493
+ evidence: [
494
+ evidence("metric", "errored_tool_calls", `${context.erroredTools.length}/${context.tools.length}`, context.erroredTools.slice(0, 20).map((span) => span.span_id))
495
+ ],
496
+ session: context.session,
497
+ observedAt: context.generatedAt,
498
+ signature: [String(context.erroredTools.length), String(context.tools.length)]
499
+ })];
500
+ }
501
+ };
502
+ var defaultTraceLiveAnalysts = [
503
+ sameFailingCommandAnalyst,
504
+ verificationWithoutChangeAnalyst,
505
+ highToolErrorRateAnalyst
506
+ ];
507
+ function ruleContext(spans, opts) {
508
+ const ordered = orderSpans(spans);
509
+ const actions = classifyLiveActions(ordered);
510
+ const tools = actions.filter((action) => action.toolName !== void 0).map((action) => action.span);
511
+ let facts = opts.facts;
512
+ return {
513
+ spans: ordered,
514
+ actions,
515
+ tools,
516
+ erroredTools: tools.filter((span) => span.status.code === "ERROR"),
517
+ verificationActions: actions.filter((action) => action.kind === "verify"),
518
+ changeActions: actions.filter((action) => action.kind === "edit"),
519
+ session: opts.session,
520
+ generatedAt: opts.generatedAt,
521
+ newSpanCount: opts.newSpanCount ?? ordered.length,
522
+ facts: () => facts ??= computeSessionFacts(ordered)
523
+ };
524
+ }
525
+ function runRules(context, opts = {}) {
526
+ return [...opts.analysts ?? defaultTraceLiveAnalysts, ...opts.extraAnalysts ?? []].flatMap((analyst) => [...analyst.analyze(context)]);
527
+ }
528
+
529
+ export {
530
+ createPipelineAccumulator,
531
+ classifyLiveActions,
532
+ orderSpans,
533
+ ruleContext,
534
+ runRules
535
+ };