@tangle-network/traces 0.6.0 → 0.7.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.
- package/README.md +3 -1
- package/dist/{chunk-6TVW7PL6.js → chunk-Q56AP2EY.js} +75 -37
- package/dist/cli.js +9 -4
- package/dist/index.d.ts +25 -8
- package/dist/index.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ That's the command in the demo above. The **deterministic pass** — stuck loops
|
|
|
43
43
|
|
|
44
44
|
Add `--llm` for the **agentic analysts** (failure-mode / knowledge-gap / knowledge-poisoning / improvement); they call OpenAI and respect `--budget <usd>`.
|
|
45
45
|
|
|
46
|
-
Every run also writes an **OTLP-JSONL artifact
|
|
46
|
+
Every run also writes an **OTLP-JSONL artifact**, and you can run external engines like [HALO](https://github.com/context-labs/halo) over it with `--analyzer halo` (traces converts our spans to the canonical OpenInference shape HALO needs) — see [External engines](#external-engines-bring-your-own). Analysis is never locked to one engine.
|
|
47
47
|
|
|
48
48
|
## What it finds
|
|
49
49
|
|
|
@@ -142,6 +142,8 @@ traces analyze --last 1 --analyzer halo --analyzer-prompt "find token waste"
|
|
|
142
142
|
traces analyze --last 1 --analyzer halo --analyzer my-engine # repeatable
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
+
HALO needs canonical OpenInference, so `--analyzer halo` converts our spans automatically (`toCanonicalOpenInference`). HALO runs its *own* LLM (OpenAI client — set `OPENAI_BASE_URL` / `OPENAI_API_KEY`, or use HALO's provider); `--model` is forwarded to it. traces supplies the trace and drives the CLI; it doesn't pay for or configure HALO's model.
|
|
146
|
+
|
|
145
147
|
**Redactors** scrub prompt/response prose with an external PII model (catching names/addresses the regex pass can't), running *after* the built-in redaction:
|
|
146
148
|
|
|
147
149
|
```bash
|
|
@@ -31,11 +31,11 @@ function serializeSpans(spans) {
|
|
|
31
31
|
`;
|
|
32
32
|
}
|
|
33
33
|
async function writeOtlpFile(spans, outPath) {
|
|
34
|
-
const { mkdtemp, writeFile:
|
|
34
|
+
const { mkdtemp, writeFile: writeFile3 } = await import("fs/promises");
|
|
35
35
|
const { tmpdir } = await import("os");
|
|
36
36
|
const { join: join13 } = await import("path");
|
|
37
37
|
const path = outPath ?? join13(await mkdtemp(join13(tmpdir(), "traces-")), "spans.otlp.jsonl");
|
|
38
|
-
await
|
|
38
|
+
await writeFile3(path, serializeSpans(spans), "utf8");
|
|
39
39
|
return path;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -72,6 +72,7 @@ async function analyzeSpans(spans, opts = {}) {
|
|
|
72
72
|
|
|
73
73
|
// src/external.ts
|
|
74
74
|
import { spawn } from "child_process";
|
|
75
|
+
import { readFile, writeFile } from "fs/promises";
|
|
75
76
|
function runCommand(command, args = [], opts = {}) {
|
|
76
77
|
const timeoutMs = opts.timeoutMs ?? 12e4;
|
|
77
78
|
const maxBuffer = opts.maxBuffer ?? 32 * 1024 * 1024;
|
|
@@ -141,14 +142,50 @@ function commandAnalyzer(spec) {
|
|
|
141
142
|
}
|
|
142
143
|
};
|
|
143
144
|
}
|
|
145
|
+
function toCanonicalOpenInference(otlpJsonl, opts = {}) {
|
|
146
|
+
return otlpJsonl.split("\n").filter((l) => l.trim()).map((line) => {
|
|
147
|
+
const s = JSON.parse(line);
|
|
148
|
+
const a = s.attributes ?? {};
|
|
149
|
+
const resourceAttrs = { "service.name": a["service.name"] ?? opts.service ?? "traces" };
|
|
150
|
+
if (a["agent.name"]) resourceAttrs["agent.name"] = a["agent.name"];
|
|
151
|
+
return JSON.stringify({
|
|
152
|
+
trace_id: s.trace_id,
|
|
153
|
+
span_id: s.span_id,
|
|
154
|
+
parent_span_id: s.parent_span_id ?? "",
|
|
155
|
+
trace_state: "",
|
|
156
|
+
name: s.name,
|
|
157
|
+
kind: a["openinference.span.kind"] ?? "CHAIN",
|
|
158
|
+
start_time: s.start_time,
|
|
159
|
+
end_time: s.end_time,
|
|
160
|
+
status: { code: s.status?.code ?? "OK", message: s.status?.message ?? "" },
|
|
161
|
+
resource: { attributes: resourceAttrs },
|
|
162
|
+
scope: { name: opts.scopeName ?? "tangle-traces", version: "" },
|
|
163
|
+
attributes: a
|
|
164
|
+
});
|
|
165
|
+
}).join("\n");
|
|
166
|
+
}
|
|
144
167
|
function haloAnalyzer(opts = {}) {
|
|
168
|
+
const command = opts.command ?? "halo";
|
|
145
169
|
const defaultPrompt = opts.defaultPrompt ?? "diagnose";
|
|
146
|
-
return
|
|
170
|
+
return {
|
|
147
171
|
name: "halo",
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
172
|
+
async analyze(otlpPath, o = {}) {
|
|
173
|
+
try {
|
|
174
|
+
const canonical = toCanonicalOpenInference(await readFile(otlpPath, "utf8"));
|
|
175
|
+
const canonPath = `${otlpPath}.halo.jsonl`;
|
|
176
|
+
await writeFile(canonPath, canonical);
|
|
177
|
+
const args = [canonPath, "-p", o.prompt ?? defaultPrompt];
|
|
178
|
+
if (opts.model) args.push("-m", opts.model);
|
|
179
|
+
const res = await runCommand(command, args, { signal: o.signal, timeoutMs: opts.timeoutMs });
|
|
180
|
+
if (res.code !== 0) {
|
|
181
|
+
return { analyzer: "halo", ok: false, output: res.stdout, error: res.stderr.trim() || `exit ${res.code}` };
|
|
182
|
+
}
|
|
183
|
+
return { analyzer: "halo", ok: true, output: res.stdout.trim() };
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return { analyzer: "halo", ok: false, output: "", error: e instanceof Error ? e.message : String(e) };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
152
189
|
}
|
|
153
190
|
function runExternalAnalyzers(otlpPath, analyzers, opts = {}) {
|
|
154
191
|
return Promise.all(analyzers.map((a) => a.analyze(otlpPath, opts)));
|
|
@@ -270,7 +307,7 @@ function userPromptSpan(o) {
|
|
|
270
307
|
}
|
|
271
308
|
|
|
272
309
|
// src/adapters/amp.ts
|
|
273
|
-
import { readdir, readFile, stat } from "fs/promises";
|
|
310
|
+
import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
274
311
|
import { homedir } from "os";
|
|
275
312
|
import { basename, join } from "path";
|
|
276
313
|
var SERVICE = "amp";
|
|
@@ -312,7 +349,7 @@ var AmpAdapter = class {
|
|
|
312
349
|
async parse(ref) {
|
|
313
350
|
let thread;
|
|
314
351
|
try {
|
|
315
|
-
thread = JSON.parse(await
|
|
352
|
+
thread = JSON.parse(await readFile2(ref.path, "utf8"));
|
|
316
353
|
} catch {
|
|
317
354
|
return [];
|
|
318
355
|
}
|
|
@@ -396,7 +433,7 @@ var AmpAdapter = class {
|
|
|
396
433
|
};
|
|
397
434
|
|
|
398
435
|
// src/adapters/claude.ts
|
|
399
|
-
import { readdir as readdir2, readFile as
|
|
436
|
+
import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
400
437
|
import { homedir as homedir2 } from "os";
|
|
401
438
|
import { basename as basename2, join as join2 } from "path";
|
|
402
439
|
var SERVICE2 = "claude-code";
|
|
@@ -557,7 +594,7 @@ var ClaudeAdapter = class {
|
|
|
557
594
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
558
595
|
}
|
|
559
596
|
async parse(ref) {
|
|
560
|
-
const raw = await
|
|
597
|
+
const raw = await readFile3(ref.path, "utf8");
|
|
561
598
|
const events = parseLines(raw);
|
|
562
599
|
const traceId = events.find((e) => e.sessionId)?.sessionId ?? ref.sessionId;
|
|
563
600
|
const rootId = `root:${traceId}`;
|
|
@@ -600,13 +637,13 @@ var ClaudeAdapter = class {
|
|
|
600
637
|
const hash = basename2(f, ".jsonl");
|
|
601
638
|
let meta = {};
|
|
602
639
|
try {
|
|
603
|
-
meta = JSON.parse(await
|
|
640
|
+
meta = JSON.parse(await readFile3(join2(subDir, `${hash}.meta.json`), "utf8"));
|
|
604
641
|
} catch {
|
|
605
642
|
}
|
|
606
643
|
const parent = meta.toolUseId && main.toolSpanByUseId.get(meta.toolUseId)?.span_id || `root:${traceId}`;
|
|
607
644
|
let raw;
|
|
608
645
|
try {
|
|
609
|
-
raw = await
|
|
646
|
+
raw = await readFile3(join2(subDir, f), "utf8");
|
|
610
647
|
} catch {
|
|
611
648
|
continue;
|
|
612
649
|
}
|
|
@@ -624,7 +661,7 @@ var ClaudeAdapter = class {
|
|
|
624
661
|
};
|
|
625
662
|
|
|
626
663
|
// src/adapters/codex.ts
|
|
627
|
-
import { readdir as readdir3, readFile as
|
|
664
|
+
import { readdir as readdir3, readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
628
665
|
import { homedir as homedir3 } from "os";
|
|
629
666
|
import { basename as basename3, join as join3 } from "path";
|
|
630
667
|
var SERVICE3 = "codex";
|
|
@@ -708,7 +745,7 @@ var CodexAdapter = class {
|
|
|
708
745
|
continue;
|
|
709
746
|
}
|
|
710
747
|
if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
|
|
711
|
-
const head = (await
|
|
748
|
+
const head = (await readFile4(path, "utf8").catch(() => "")).split("\n", 1)[0] ?? "";
|
|
712
749
|
let cwd = null;
|
|
713
750
|
let id = basename3(path).replace(/^rollout-[\dT-]+-/, "").replace(/\.jsonl$/, "");
|
|
714
751
|
try {
|
|
@@ -723,7 +760,7 @@ var CodexAdapter = class {
|
|
|
723
760
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
724
761
|
}
|
|
725
762
|
async parse(ref) {
|
|
726
|
-
const lines = parseLines2(await
|
|
763
|
+
const lines = parseLines2(await readFile4(ref.path, "utf8"));
|
|
727
764
|
const meta = lines.find((l) => l.type === "session_meta");
|
|
728
765
|
const traceId = meta?.payload?.id ?? ref.sessionId;
|
|
729
766
|
const model = lines.find((l) => l.type === "turn_context")?.payload?.model ?? null;
|
|
@@ -839,7 +876,7 @@ var CodexAdapter = class {
|
|
|
839
876
|
};
|
|
840
877
|
|
|
841
878
|
// src/adapters/copilot.ts
|
|
842
|
-
import { readdir as readdir4, readFile as
|
|
879
|
+
import { readdir as readdir4, readFile as readFile5, stat as stat4 } from "fs/promises";
|
|
843
880
|
import { homedir as homedir4 } from "os";
|
|
844
881
|
import { join as join4 } from "path";
|
|
845
882
|
var SERVICE4 = "github-copilot";
|
|
@@ -884,7 +921,7 @@ var CopilotAdapter = class {
|
|
|
884
921
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
885
922
|
}
|
|
886
923
|
async parse(ref) {
|
|
887
|
-
const events = parseLines3(await
|
|
924
|
+
const events = parseLines3(await readFile5(ref.path, "utf8"));
|
|
888
925
|
const traceId = ref.sessionId;
|
|
889
926
|
const rootId = `root:${traceId}`;
|
|
890
927
|
const spans = [
|
|
@@ -965,7 +1002,7 @@ var CopilotAdapter = class {
|
|
|
965
1002
|
};
|
|
966
1003
|
|
|
967
1004
|
// src/adapters/factory.ts
|
|
968
|
-
import { readdir as readdir5, readFile as
|
|
1005
|
+
import { readdir as readdir5, readFile as readFile6, stat as stat5 } from "fs/promises";
|
|
969
1006
|
import { homedir as homedir5 } from "os";
|
|
970
1007
|
import { basename as basename4, join as join5 } from "path";
|
|
971
1008
|
var SERVICE5 = "factory";
|
|
@@ -1026,13 +1063,13 @@ var FactoryAdapter = class {
|
|
|
1026
1063
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1027
1064
|
}
|
|
1028
1065
|
async parse(ref) {
|
|
1029
|
-
const lines = parseLines4(await
|
|
1066
|
+
const lines = parseLines4(await readFile6(ref.path, "utf8"));
|
|
1030
1067
|
const start = lines.find((l) => l.type === "session_start");
|
|
1031
1068
|
const traceId = start?.id ?? ref.sessionId;
|
|
1032
1069
|
const rootId = `root:${traceId}`;
|
|
1033
1070
|
let settings = {};
|
|
1034
1071
|
try {
|
|
1035
|
-
settings = JSON.parse(await
|
|
1072
|
+
settings = JSON.parse(await readFile6(ref.path.replace(/\.jsonl$/, ".settings.json"), "utf8"));
|
|
1036
1073
|
} catch {
|
|
1037
1074
|
}
|
|
1038
1075
|
const spans = [
|
|
@@ -1126,7 +1163,7 @@ var FactoryAdapter = class {
|
|
|
1126
1163
|
};
|
|
1127
1164
|
|
|
1128
1165
|
// src/adapters/forge.ts
|
|
1129
|
-
import { readdir as readdir6, readFile as
|
|
1166
|
+
import { readdir as readdir6, readFile as readFile7, stat as stat6 } from "fs/promises";
|
|
1130
1167
|
import { homedir as homedir6 } from "os";
|
|
1131
1168
|
import { basename as basename5, join as join6 } from "path";
|
|
1132
1169
|
var SERVICE6 = "forge";
|
|
@@ -1172,7 +1209,7 @@ var ForgeAdapter = class {
|
|
|
1172
1209
|
async parse(ref) {
|
|
1173
1210
|
let dump;
|
|
1174
1211
|
try {
|
|
1175
|
-
dump = JSON.parse(await
|
|
1212
|
+
dump = JSON.parse(await readFile7(ref.path, "utf8"));
|
|
1176
1213
|
} catch {
|
|
1177
1214
|
return [];
|
|
1178
1215
|
}
|
|
@@ -1256,7 +1293,7 @@ var ForgeAdapter = class {
|
|
|
1256
1293
|
};
|
|
1257
1294
|
|
|
1258
1295
|
// src/adapters/gemini.ts
|
|
1259
|
-
import { readdir as readdir7, readFile as
|
|
1296
|
+
import { readdir as readdir7, readFile as readFile8, stat as stat7 } from "fs/promises";
|
|
1260
1297
|
import { homedir as homedir7 } from "os";
|
|
1261
1298
|
import { join as join7 } from "path";
|
|
1262
1299
|
function iso(ts, fallback) {
|
|
@@ -1321,7 +1358,7 @@ var GeminiFamilyAdapter = class {
|
|
|
1321
1358
|
async parse(ref) {
|
|
1322
1359
|
let session;
|
|
1323
1360
|
try {
|
|
1324
|
-
session = JSON.parse(await
|
|
1361
|
+
session = JSON.parse(await readFile8(ref.path, "utf8"));
|
|
1325
1362
|
} catch {
|
|
1326
1363
|
return [];
|
|
1327
1364
|
}
|
|
@@ -1415,7 +1452,7 @@ var GeminiAdapter = class extends GeminiFamilyAdapter {
|
|
|
1415
1452
|
};
|
|
1416
1453
|
|
|
1417
1454
|
// src/adapters/opencode.ts
|
|
1418
|
-
import { readdir as readdir8, readFile as
|
|
1455
|
+
import { readdir as readdir8, readFile as readFile9, stat as stat8 } from "fs/promises";
|
|
1419
1456
|
import { homedir as homedir8 } from "os";
|
|
1420
1457
|
import { join as join8 } from "path";
|
|
1421
1458
|
var SERVICE7 = "opencode";
|
|
@@ -1470,7 +1507,7 @@ var OpencodeAdapter = class {
|
|
|
1470
1507
|
const messages = [];
|
|
1471
1508
|
for (const f of msgFiles) {
|
|
1472
1509
|
try {
|
|
1473
|
-
messages.push({ msg: JSON.parse(await
|
|
1510
|
+
messages.push({ msg: JSON.parse(await readFile9(join8(ref.path, f), "utf8")), file: f });
|
|
1474
1511
|
} catch {
|
|
1475
1512
|
}
|
|
1476
1513
|
}
|
|
@@ -1498,7 +1535,7 @@ var OpencodeAdapter = class {
|
|
|
1498
1535
|
const partFiles = (await readdir8(pdir)).filter((f) => f.endsWith(".json"));
|
|
1499
1536
|
for (const pf of partFiles) {
|
|
1500
1537
|
try {
|
|
1501
|
-
parts.push(JSON.parse(await
|
|
1538
|
+
parts.push(JSON.parse(await readFile9(join8(pdir, pf), "utf8")));
|
|
1502
1539
|
} catch {
|
|
1503
1540
|
}
|
|
1504
1541
|
}
|
|
@@ -1568,7 +1605,7 @@ var OpencodeAdapter = class {
|
|
|
1568
1605
|
};
|
|
1569
1606
|
|
|
1570
1607
|
// src/adapters/pi.ts
|
|
1571
|
-
import { readdir as readdir9, readFile as
|
|
1608
|
+
import { readdir as readdir9, readFile as readFile10, stat as stat9 } from "fs/promises";
|
|
1572
1609
|
import { homedir as homedir9 } from "os";
|
|
1573
1610
|
import { basename as basename6, join as join9 } from "path";
|
|
1574
1611
|
var SERVICE8 = "pi";
|
|
@@ -1636,7 +1673,7 @@ var PiAdapter = class {
|
|
|
1636
1673
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1637
1674
|
}
|
|
1638
1675
|
async parse(ref) {
|
|
1639
|
-
const lines = parseLines5(await
|
|
1676
|
+
const lines = parseLines5(await readFile10(ref.path, "utf8"));
|
|
1640
1677
|
const sessionLine = lines.find((l) => l.type === "session");
|
|
1641
1678
|
const traceId = sessionLine?.id ?? ref.sessionId;
|
|
1642
1679
|
const rootId = `root:${traceId}`;
|
|
@@ -1737,7 +1774,7 @@ var PiAdapter = class {
|
|
|
1737
1774
|
};
|
|
1738
1775
|
|
|
1739
1776
|
// src/adapters/qwen.ts
|
|
1740
|
-
import { readdir as readdir10, readFile as
|
|
1777
|
+
import { readdir as readdir10, readFile as readFile11, stat as stat10 } from "fs/promises";
|
|
1741
1778
|
import { homedir as homedir10 } from "os";
|
|
1742
1779
|
import { basename as basename7, join as join10 } from "path";
|
|
1743
1780
|
var SERVICE9 = "qwen";
|
|
@@ -1796,7 +1833,7 @@ var QwenAdapter = class {
|
|
|
1796
1833
|
return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1797
1834
|
}
|
|
1798
1835
|
async parse(ref) {
|
|
1799
|
-
const records = parseLines6(await
|
|
1836
|
+
const records = parseLines6(await readFile11(ref.path, "utf8"));
|
|
1800
1837
|
const traceId = records.find((r) => r.sessionId)?.sessionId ?? ref.sessionId;
|
|
1801
1838
|
const rootId = `root:${traceId}`;
|
|
1802
1839
|
const spans = [
|
|
@@ -2121,7 +2158,7 @@ async function applyRedactor(spans, redactor) {
|
|
|
2121
2158
|
|
|
2122
2159
|
// src/upload-state.ts
|
|
2123
2160
|
import { createHash } from "crypto";
|
|
2124
|
-
import { mkdir, readFile as
|
|
2161
|
+
import { mkdir, readFile as readFile12, writeFile as writeFile2 } from "fs/promises";
|
|
2125
2162
|
import { homedir as homedir11 } from "os";
|
|
2126
2163
|
import { dirname, join as join11 } from "path";
|
|
2127
2164
|
function stateFile() {
|
|
@@ -2141,7 +2178,7 @@ function sessionHash(spans) {
|
|
|
2141
2178
|
}
|
|
2142
2179
|
async function loadState() {
|
|
2143
2180
|
try {
|
|
2144
|
-
return JSON.parse(await
|
|
2181
|
+
return JSON.parse(await readFile12(stateFile(), "utf8"));
|
|
2145
2182
|
} catch {
|
|
2146
2183
|
return {};
|
|
2147
2184
|
}
|
|
@@ -2149,7 +2186,7 @@ async function loadState() {
|
|
|
2149
2186
|
async function saveState(state) {
|
|
2150
2187
|
const f = stateFile();
|
|
2151
2188
|
await mkdir(dirname(f), { recursive: true });
|
|
2152
|
-
await
|
|
2189
|
+
await writeFile2(f, `${JSON.stringify(state, null, 2)}
|
|
2153
2190
|
`, "utf8");
|
|
2154
2191
|
}
|
|
2155
2192
|
function alreadyUploaded(state, harness, sessionId, hash) {
|
|
@@ -2157,7 +2194,7 @@ function alreadyUploaded(state, harness, sessionId, hash) {
|
|
|
2157
2194
|
}
|
|
2158
2195
|
|
|
2159
2196
|
// src/upload.ts
|
|
2160
|
-
import { readFile as
|
|
2197
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
2161
2198
|
import { createRequire } from "module";
|
|
2162
2199
|
import { hostname } from "os";
|
|
2163
2200
|
import { basename as basename8, join as join12 } from "path";
|
|
@@ -2184,7 +2221,7 @@ async function planUpload(opts = {}) {
|
|
|
2184
2221
|
async function gitBranch(cwd) {
|
|
2185
2222
|
if (!cwd) return void 0;
|
|
2186
2223
|
try {
|
|
2187
|
-
const head = await
|
|
2224
|
+
const head = await readFile13(join12(cwd, ".git", "HEAD"), "utf8");
|
|
2188
2225
|
const m = head.match(/^ref:\s*refs\/heads\/(.+)$/m);
|
|
2189
2226
|
return m?.[1]?.trim();
|
|
2190
2227
|
} catch {
|
|
@@ -2298,6 +2335,7 @@ export {
|
|
|
2298
2335
|
analyzeSpans,
|
|
2299
2336
|
runCommand,
|
|
2300
2337
|
commandAnalyzer,
|
|
2338
|
+
toCanonicalOpenInference,
|
|
2301
2339
|
haloAnalyzer,
|
|
2302
2340
|
runExternalAnalyzers,
|
|
2303
2341
|
commandRedactor,
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
selectAdapters,
|
|
15
15
|
watchSessions,
|
|
16
16
|
writeOtlpFile
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-Q56AP2EY.js";
|
|
18
18
|
|
|
19
19
|
// src/cli.ts
|
|
20
20
|
import { stat, writeFile } from "fs/promises";
|
|
@@ -72,7 +72,7 @@ function renderReport(result, meta) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
lines.push("---");
|
|
75
|
-
lines.push(`OTLP artifact: \`${meta.otlpPath}\` \u2014
|
|
75
|
+
lines.push(`OTLP artifact: \`${meta.otlpPath}\` \u2014 run external engines with \`traces analyze --analyzer halo\`.`);
|
|
76
76
|
lines.push("");
|
|
77
77
|
return lines.join("\n");
|
|
78
78
|
}
|
|
@@ -148,6 +148,9 @@ function parseArgs(argv) {
|
|
|
148
148
|
case "--budget":
|
|
149
149
|
a.budget = Number(next());
|
|
150
150
|
break;
|
|
151
|
+
case "--model":
|
|
152
|
+
a.model = next();
|
|
153
|
+
break;
|
|
151
154
|
case "--interval":
|
|
152
155
|
a.interval = Number(next());
|
|
153
156
|
break;
|
|
@@ -270,6 +273,7 @@ async function cmdAnalyze(args) {
|
|
|
270
273
|
const ai = args.llm ? await buildAxService() : void 0;
|
|
271
274
|
const { otlpPath, result } = await analyzeSpans(spans, {
|
|
272
275
|
ai,
|
|
276
|
+
model: args.model,
|
|
273
277
|
budgetUsd: args.budget,
|
|
274
278
|
otlpOutPath: args.otlp,
|
|
275
279
|
log: (msg) => process.stderr.write(`${msg}
|
|
@@ -280,7 +284,7 @@ async function cmdAnalyze(args) {
|
|
|
280
284
|
${renderPipelines(pipelines)}`;
|
|
281
285
|
if (args.analyzers.length > 0 && otlpPath) {
|
|
282
286
|
const engines = args.analyzers.map(
|
|
283
|
-
(spec) => spec === "halo" ? haloAnalyzer({ defaultPrompt: args.analyzerPrompt }) : commandAnalyzer({ name: spec, command: spec, args: (p, prompt) => prompt ? [p, prompt] : [p] })
|
|
287
|
+
(spec) => spec === "halo" ? haloAnalyzer({ defaultPrompt: args.analyzerPrompt, model: args.model }) : commandAnalyzer({ name: spec, command: spec, args: (p, prompt) => prompt ? [p, prompt] : [p] })
|
|
284
288
|
);
|
|
285
289
|
const results = await runExternalAnalyzers(otlpPath, engines, { prompt: args.analyzerPrompt });
|
|
286
290
|
for (const r of results) {
|
|
@@ -384,7 +388,7 @@ function usage() {
|
|
|
384
388
|
Commands:
|
|
385
389
|
list List discovered sessions
|
|
386
390
|
analyze Run analyst suite + loop/waste pipelines, write a markdown report
|
|
387
|
-
convert Emit OTLP-JSONL only (
|
|
391
|
+
convert Emit OTLP-JSONL only (HALO: use analyze --analyzer halo)
|
|
388
392
|
watch Online observer: tail active sessions, notify on stuck loops (read-only)
|
|
389
393
|
upload Redact + upload sessions in a time window to the Tangle Intelligence Platform
|
|
390
394
|
|
|
@@ -398,6 +402,7 @@ Options:
|
|
|
398
402
|
--out <path> Write report to a file
|
|
399
403
|
--otlp <path> OTLP artifact path (also the dry-run upload preview path)
|
|
400
404
|
--llm Enable agentic RLM analysts (needs OPENAI_API_KEY / OPENAI_BASE_URL)
|
|
405
|
+
--model <id> --llm model id (e.g. a router model like glm-5.2); default is agent-eval's
|
|
401
406
|
--budget <usd> USD cap for agentic analysts
|
|
402
407
|
--analyzer <cmd> analyze: also run an external engine over the OTLP (repeatable; "halo" or any command)
|
|
403
408
|
--analyzer-prompt <p> analyze: prompt passed to external analyzers (default: diagnose)
|
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,10 @@ export { HostedClient, createHostedClient, hostedClientFromEnv } from '@tangle-n
|
|
|
14
14
|
* Every harness adapter projects its native session log onto `OtlpSpan[]`
|
|
15
15
|
* and we serialize one span per JSONL line. This is the exact wire shape
|
|
16
16
|
* `@tangle-network/agent-eval`'s `OtlpFileTraceStore` indexes
|
|
17
|
-
* (`projectOtlpFlatLine` + `extractOtlpAttributes`)
|
|
18
|
-
*
|
|
17
|
+
* (`projectOtlpFlatLine` + `extractOtlpAttributes`). It is a flat subset, NOT
|
|
18
|
+
* canonical OpenInference (kind lives in attributes; no resource/scope; root
|
|
19
|
+
* parent is null) — external OpenInference engines like HALO need the canonical
|
|
20
|
+
* form, which `external.ts` `toCanonicalOpenInference` produces.
|
|
19
21
|
*
|
|
20
22
|
* Attribute vocabulary the downstream analysts key off:
|
|
21
23
|
* - `openinference.span.kind` → span kind (AGENT | LLM | TOOL | CHAIN)
|
|
@@ -524,8 +526,10 @@ declare function toRuntimeStore(spans: readonly OtlpSpan[]): Promise<RuntimeTrac
|
|
|
524
526
|
* LLM, model-agnostic). Supply `ai` to add the agentic RLM kinds
|
|
525
527
|
* (failure-mode / knowledge-gap / knowledge-poisoning / improvement).
|
|
526
528
|
*
|
|
527
|
-
* The written OTLP file is the
|
|
528
|
-
*
|
|
529
|
+
* The written OTLP file is the artifact our pipeline reads. External engines can
|
|
530
|
+
* analyze it too: HALO needs canonical OpenInference, so `--analyzer halo`
|
|
531
|
+
* converts it first (see external.ts `toCanonicalOpenInference`) — analysis is
|
|
532
|
+
* never locked to one engine.
|
|
529
533
|
*/
|
|
530
534
|
|
|
531
535
|
interface AnalyzeOptions {
|
|
@@ -547,7 +551,7 @@ interface AnalyzeOptions {
|
|
|
547
551
|
log?: (msg: string, fields?: Record<string, unknown>) => void;
|
|
548
552
|
}
|
|
549
553
|
interface AnalyzeResult {
|
|
550
|
-
/** Path to the OTLP-JSONL artifact (
|
|
554
|
+
/** Path to the OTLP-JSONL artifact (convert to canonical for HALO). */
|
|
551
555
|
otlpPath: string;
|
|
552
556
|
result: Awaited<ReturnType<ReturnType<typeof buildDefaultAnalystRegistry>['run']>>;
|
|
553
557
|
}
|
|
@@ -607,11 +611,24 @@ declare function commandAnalyzer(spec: {
|
|
|
607
611
|
parse?: (stdout: string) => AnalystFinding[] | undefined;
|
|
608
612
|
timeoutMs?: number;
|
|
609
613
|
}): ExternalAnalyzer;
|
|
610
|
-
/**
|
|
611
|
-
*
|
|
614
|
+
/** Convert our OTLP-JSONL (the flat shape our pipeline reads) into the canonical
|
|
615
|
+
* OpenInference span schema HALO's `SpanRecord` requires: top-level `kind`,
|
|
616
|
+
* `resource`, `scope`, and a string (never null) `parent_span_id`. Our own
|
|
617
|
+
* writer keeps the simpler shape for the agent-eval reader; this is the bridge
|
|
618
|
+
* to OpenInference consumers. Input + output are JSONL strings (one span/line). */
|
|
619
|
+
declare function toCanonicalOpenInference(otlpJsonl: string, opts?: {
|
|
620
|
+
service?: string;
|
|
621
|
+
scopeName?: string;
|
|
622
|
+
}): string;
|
|
623
|
+
/** HALO (github.com/context-labs/halo) over the OTLP artifact. HALO requires
|
|
624
|
+
* canonical OpenInference, so this converts our OTLP to a `<path>.halo.jsonl`
|
|
625
|
+
* first, then runs `halo <file> -p <prompt> [-m <model>]`. Install HALO yourself
|
|
626
|
+
* and configure its LLM provider (it uses the OpenAI client — `OPENAI_BASE_URL`
|
|
627
|
+
* / `OPENAI_API_KEY`); this just drives it. */
|
|
612
628
|
declare function haloAnalyzer(opts?: {
|
|
613
629
|
command?: string;
|
|
614
630
|
defaultPrompt?: string;
|
|
631
|
+
model?: string;
|
|
615
632
|
timeoutMs?: number;
|
|
616
633
|
}): ExternalAnalyzer;
|
|
617
634
|
/** Run external analyzers over one OTLP artifact, concurrently. Never throws —
|
|
@@ -850,4 +867,4 @@ interface ExecuteOptions {
|
|
|
850
867
|
/** Send the NEW items (or, on dryRun, write the redacted OTLP that would send). */
|
|
851
868
|
declare function executeUpload(plan: UploadPlan, opts?: ExecuteOptions): Promise<UploadResult>;
|
|
852
869
|
|
|
853
|
-
export { ATTR, AmpAdapter, type AnalyzeOptions, type AnalyzeResult, CODING_REDACTION_RULES, CONTENT_CAP, ClaudeAdapter, CodexAdapter, type CollectOptions, CopilotAdapter, DEFAULT_HARNESS, type ExecuteOptions, type ExternalAnalysisResult, type ExternalAnalyzer, FactoryAdapter, ForgeAdapter, GeminiAdapter, type HarnessTraceAdapter, INGEST_SOURCE_CLI, type LocateOptions, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpSpan, type OtlpSpanKind, type OtlpStatusCode, PiAdapter, type PipelineOptions, type PipelineReport, type PlanOptions, QwenAdapter, type Redactor, type RunResult, type RuntimeTrace, type ScanOptions, type ScannedSession, type SessionBatch, type SessionRef, type SpanInput, type SpanRedaction, TRACES_REDACTION_RULES, type UploadBackend, type UploadItem, type UploadPlan, type UploadRecord, type UploadResult, type UploadState, alreadyUploaded, analyzeSpans, applyRedactor, capText, collectSessions, commandAnalyzer, commandRedactor, executeUpload, haloAnalyzer, knownHarnesses, listAdapters, loadState, parseIsoToEpochMs, parseSince, planUpload, redactSpans, resolveAdapter, runCommand, runExternalAnalyzers, runPipelines, saveState, scanSessions, selectAdapters, serializeSpans, sessionHash, span, toRuntimeStore, toTraceSpanEvents, uploadKey, userPromptSpan, watchSessions, writeOtlpFile };
|
|
870
|
+
export { ATTR, AmpAdapter, type AnalyzeOptions, type AnalyzeResult, CODING_REDACTION_RULES, CONTENT_CAP, ClaudeAdapter, CodexAdapter, type CollectOptions, CopilotAdapter, DEFAULT_HARNESS, type ExecuteOptions, type ExternalAnalysisResult, type ExternalAnalyzer, FactoryAdapter, ForgeAdapter, GeminiAdapter, type HarnessTraceAdapter, INGEST_SOURCE_CLI, type LocateOptions, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpSpan, type OtlpSpanKind, type OtlpStatusCode, PiAdapter, type PipelineOptions, type PipelineReport, type PlanOptions, QwenAdapter, type Redactor, type RunResult, type RuntimeTrace, type ScanOptions, type ScannedSession, type SessionBatch, type SessionRef, type SpanInput, type SpanRedaction, TRACES_REDACTION_RULES, type UploadBackend, type UploadItem, type UploadPlan, type UploadRecord, type UploadResult, type UploadState, alreadyUploaded, analyzeSpans, applyRedactor, capText, collectSessions, commandAnalyzer, commandRedactor, executeUpload, haloAnalyzer, knownHarnesses, listAdapters, loadState, parseIsoToEpochMs, parseSince, planUpload, redactSpans, resolveAdapter, runCommand, runExternalAnalyzers, runPipelines, saveState, scanSessions, selectAdapters, serializeSpans, sessionHash, span, toCanonicalOpenInference, toRuntimeStore, toTraceSpanEvents, uploadKey, userPromptSpan, watchSessions, writeOtlpFile };
|
package/dist/index.js
CHANGED
|
@@ -40,13 +40,14 @@ import {
|
|
|
40
40
|
serializeSpans,
|
|
41
41
|
sessionHash,
|
|
42
42
|
span,
|
|
43
|
+
toCanonicalOpenInference,
|
|
43
44
|
toRuntimeStore,
|
|
44
45
|
toTraceSpanEvents,
|
|
45
46
|
uploadKey,
|
|
46
47
|
userPromptSpan,
|
|
47
48
|
watchSessions,
|
|
48
49
|
writeOtlpFile
|
|
49
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-Q56AP2EY.js";
|
|
50
51
|
|
|
51
52
|
// src/collect.ts
|
|
52
53
|
async function collectSessions(opts = {}) {
|
|
@@ -121,6 +122,7 @@ export {
|
|
|
121
122
|
serializeSpans,
|
|
122
123
|
sessionHash,
|
|
123
124
|
span,
|
|
125
|
+
toCanonicalOpenInference,
|
|
124
126
|
toRuntimeStore,
|
|
125
127
|
toTraceSpanEvents,
|
|
126
128
|
uploadKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/traces",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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",
|