@tangle-network/traces 0.2.0 → 0.2.2
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 +61 -74
- package/dist/{chunk-HPBKRTTP.js → chunk-4PC7MQXT.js} +99 -86
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,28 +1,44 @@
|
|
|
1
1
|
# traces
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Analyze your coding-agent session logs for failure modes — stuck loops, token bloat, dropped self-verification — with zero instrumentation. It reads the logs your harness (Claude Code, Codex, OpenCode, …) already writes to disk.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`traces` is a CLI and an SDK: run it ad-hoc, or build on it.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
|
|
10
|
+
npm i -g @tangle-network/traces # the `traces` CLI
|
|
11
|
+
npx @tangle-network/traces analyze # or run without installing
|
|
12
|
+
npm i @tangle-network/traces # or use it as a library
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
##
|
|
15
|
+
## Quick start
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
```bash
|
|
18
|
+
traces analyze --harness claude-code --last 1
|
|
19
|
+
```
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
```
|
|
22
|
+
# Trace report — claude-code · 1 session · 2,429 spans
|
|
23
|
+
|
|
24
|
+
## Stuck loops
|
|
25
|
+
- `bash` ×7 with identical args in 48s (same git status retried, no state change)
|
|
26
|
+
|
|
27
|
+
## Tool use
|
|
28
|
+
- 1,204 tool calls · duplicate-call 22% · retry 6% · error 8%
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The deterministic pass (stuck loops, token growth, tool monoculture, missing self-verification) needs no API key and costs nothing. Add `--llm` for the agentic analysts (failure-mode / knowledge-gap / knowledge-poisoning / improvement); they call OpenAI and respect `--budget`.
|
|
32
|
+
|
|
33
|
+
The written OTLP-JSONL artifact also feeds [HALO](https://github.com/context-labs/halo) (`halo spans.jsonl -p "diagnose"`) — analysis is never locked to one engine.
|
|
18
34
|
|
|
19
35
|
## Supported harnesses
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
"Verified" = tested against real sessions; "fixture" = tested against schema-accurate fixtures (no real sessions available).
|
|
22
38
|
|
|
23
39
|
| Harness (aliases) | Reads from | Status |
|
|
24
40
|
|---|---|---|
|
|
25
|
-
| `claude-code` (`claudish`, `openclaw`, `nanoclaw`) | `~/.claude/projects/<cwd>/*.jsonl` (+ subagent sidechains) | verified |
|
|
41
|
+
| `claude-code` (`claude`, `claudish`, `openclaw`, `nanoclaw`) | `~/.claude/projects/<cwd>/*.jsonl` (+ subagent sidechains) | verified |
|
|
26
42
|
| `codex` (`codex-acp`) | `~/.codex/sessions/**/rollout-*.jsonl` | verified |
|
|
27
43
|
| `opencode` | `~/.local/share/opencode/storage/` | verified |
|
|
28
44
|
| `gemini` (`gemini-cli`) | `~/.gemini/tmp/<hash>/chats/session-*.json` | verified |
|
|
@@ -31,97 +47,68 @@ Covers the coding-agent CLIs in the agent-dev-container nix profile. "Verified"
|
|
|
31
47
|
| `qwen` (`qwen-code`) | `~/.qwen/projects/<cwd>/chats/*.jsonl` | fixture |
|
|
32
48
|
| `amp` | `~/.local/share/amp/threads/T-*.json` | fixture |
|
|
33
49
|
| `github-copilot` (`copilot`) | `~/.copilot/session-state/<id>/events.jsonl` | fixture |
|
|
34
|
-
| `forge` (`forgecode`) | `/dump` JSON exports
|
|
35
|
-
|
|
36
|
-
Notes: Factory's token totals live in the `.settings.json` sidecar (no per-turn usage). Forge's primary store is SQLite; v1 reads its dependency-free `/dump` exports. ACP-only bridges (`claude-agent-acp`) may not persist a local transcript at all.
|
|
50
|
+
| `forge` (`forgecode`) | `/dump` JSON exports | fixture |
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
Factory stores token totals in `.settings.json`, not per turn. Forge reads `/dump` JSON exports (live SQLite is a follow-up). ACP-only bridges may not persist a local transcript.
|
|
39
53
|
|
|
40
54
|
## Commands
|
|
41
55
|
|
|
42
56
|
```bash
|
|
43
|
-
traces list --harness claude-code --last 20
|
|
44
|
-
traces analyze --harness codex --last 1
|
|
57
|
+
traces list --harness claude-code --last 20 # discover sessions
|
|
58
|
+
traces analyze --harness codex --last 1 # $0 deterministic report
|
|
45
59
|
traces analyze --all --since 2026-06-18 --out report.md
|
|
46
|
-
traces convert --harness claude-code --last 1 --otlp spans.jsonl # OTLP only
|
|
47
|
-
traces
|
|
48
|
-
traces
|
|
49
|
-
traces upload --since
|
|
50
|
-
traces upload --since 24h # upload last day to the Intelligence Platform
|
|
60
|
+
traces convert --harness claude-code --last 1 --otlp spans.jsonl # OTLP only
|
|
61
|
+
traces watch --all # live observer; notify on stuck loops
|
|
62
|
+
traces upload --since 1h --dry-run # redact + dedup + preview, no network
|
|
63
|
+
traces upload --since 24h # upload last day to the Intelligence Platform
|
|
51
64
|
```
|
|
52
65
|
|
|
53
|
-
### Options
|
|
54
|
-
|
|
55
66
|
| Flag | Meaning |
|
|
56
67
|
|---|---|
|
|
57
68
|
| `--harness <id>` | Harness or alias (default: `claude-code`) |
|
|
58
|
-
| `--all` |
|
|
69
|
+
| `--all` | Every known harness |
|
|
59
70
|
| `--last <n>` | Most-recent N sessions |
|
|
60
|
-
| `--session <path>` |
|
|
61
|
-
| `--cwd <dir>` | Filter
|
|
71
|
+
| `--session <path>` | One explicit session file |
|
|
72
|
+
| `--cwd <dir>` | Filter by working directory |
|
|
62
73
|
| `--since <t>` | `upload`: window — `30m`/`2h`/`7d` or ISO (default 24h); `analyze`: ISO cutoff |
|
|
63
|
-
| `--out <path>` | Write report to a file |
|
|
74
|
+
| `--out <path>` | Write the report to a file |
|
|
64
75
|
| `--otlp <path>` | OTLP artifact path (also the dry-run upload preview) |
|
|
65
|
-
| `--llm` | Enable agentic
|
|
66
|
-
| `--
|
|
67
|
-
| `--dry-run` | `upload`: redact + dedup + preview, do not send |
|
|
68
|
-
| `--yes`, `-y` | `upload`: skip the confirmation prompt |
|
|
69
|
-
| `--interval <s>` / `--window <m>` | `watch`: poll interval / active-session window |
|
|
76
|
+
| `--llm` / `--budget <usd>` | Enable agentic analysts (needs `OPENAI_API_KEY`) / cap their spend |
|
|
77
|
+
| `--interval <s>` / `--window <m>` | `watch`: poll seconds (default 5) / active-session window minutes (default 30) |
|
|
70
78
|
| `--min-loop <n>` | Identical repeated calls before flagging a loop (default 3) |
|
|
79
|
+
| `--dry-run` / `--yes` | `upload`: preview without sending / skip the confirm prompt |
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
`upload` redacts PII/secrets locally before anything leaves the machine, dedups against already-uploaded sessions, and tags each with metadata (harness, cwd, git branch, host). It needs `TANGLE_INGEST_URL` (or `TANGLE_ORCHESTRATOR_URL`), `TANGLE_INGEST_API_KEY` (or `TANGLE_API_KEY`), and `TANGLE_TENANT_ID`; without them, `--dry-run` still works.
|
|
73
82
|
|
|
74
83
|
## Use as a library
|
|
75
84
|
|
|
76
|
-
The CLI is a thin
|
|
85
|
+
The CLI is a thin consumer of these exports. Full runnable examples in [`examples/`](./examples).
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
| Export | Signature | Use |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `analyzeSpans` | `(spans, { registry?, ai?, budgetUsd? }) → AnalyzeResult` | run analysts — built-in, or **your own** via `registry` |
|
|
90
|
+
| `watchSessions` | `(ObserverOptions) → Promise<void>` | live observer; `onLoop` / `onReport` / `signal` / `adapters` |
|
|
91
|
+
| `collectSessions` | `(CollectOptions) → SessionBatch[]` | redacted per-session batches for your own pipeline |
|
|
92
|
+
| `redactSpans` | `(spans, rules?) → { spans, report }` | PII/secret redaction (`TRACES_REDACTION_RULES`) |
|
|
93
|
+
| `planUpload` / `executeUpload` | `(…, { backend? }) → …` | redact + dedup + send to any sink |
|
|
94
|
+
| `listAdapters` / `resolveAdapter` | `() → […]` / `(id) → adapter` | the harness registry |
|
|
95
|
+
| `HarnessTraceAdapter` | interface (`locate` + `parse`) | implement to add a harness |
|
|
86
96
|
|
|
87
97
|
```ts
|
|
88
|
-
|
|
89
|
-
await watchSessions({
|
|
90
|
-
all: true,
|
|
91
|
-
signal: controller.signal,
|
|
92
|
-
onLoop: (loop) => alertSlack(`stuck loop: ${loop.toolName} ×${loop.occurrences} in ${loop.sessionId}`),
|
|
93
|
-
onReport: (ref, report) => metrics.record(ref.harness, report.toolUse),
|
|
94
|
-
})
|
|
95
|
-
```
|
|
98
|
+
import { watchSessions, analyzeSpans, AnalystRegistry, makeFinding } from '@tangle-network/traces'
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
// Observe live sessions, feed findings anywhere (read-only, cancellable):
|
|
101
|
+
const c = new AbortController()
|
|
102
|
+
await watchSessions({ all: true, signal: c.signal, onLoop: (l) => alert(l.toolName, l.occurrences) })
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
// Run your own analyst instead of the built-ins:
|
|
101
105
|
const registry = new AnalystRegistry()
|
|
102
|
-
registry.register({
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
async analyze(store, ctx) { /* your logic */ return [makeFinding({ /* … */ })] },
|
|
106
|
-
})
|
|
107
|
-
const { result } = await analyzeSpans(spans, { registry })
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
**Collect redacted batches** to feed a vector store / fine-tune corpus / another pipeline:
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
const batches = await collectSessions({ all: true, sinceMs: Date.now() - 3_600_000 })
|
|
114
|
-
// → [{ ref, spans /* redacted */, redaction }]
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**Upload to your own backend** (anything implementing `ingestTraces`), not just Tangle:
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
await executeUpload(plan, { backend: { async ingestTraces(spans, key) { /* POST anywhere */ return { accepted: spans.length } } } })
|
|
106
|
+
registry.register({ id: 'mine', description: '…', inputKind: 'trace-store', cost: { kind: 'deterministic' }, version: '1.0.0',
|
|
107
|
+
async analyze() { return [makeFinding({ analyst_id: 'mine', area: 'custom', claim: '…', severity: 'info', evidence_refs: [], confidence: 0.9 })] } })
|
|
108
|
+
await analyzeSpans(spans, { registry })
|
|
121
109
|
```
|
|
122
110
|
|
|
123
|
-
|
|
124
|
-
`adapters: [...]` to `watchSessions`/`collectSessions`, or drop a file in `src/adapters/`.
|
|
111
|
+
Add a new harness: implement `HarnessTraceAdapter` and pass it via `adapters: [...]` to `watchSessions`/`collectSessions`, or drop a file in `src/adapters/`. See [`examples/register-harness.ts`](./examples/register-harness.ts).
|
|
125
112
|
|
|
126
113
|
## Develop
|
|
127
114
|
|
|
@@ -31,29 +31,21 @@ function serializeSpans(spans) {
|
|
|
31
31
|
`;
|
|
32
32
|
}
|
|
33
33
|
async function writeOtlpFile(spans, outPath) {
|
|
34
|
-
const { mkdtemp
|
|
35
|
-
const { tmpdir
|
|
36
|
-
const { join:
|
|
37
|
-
const path = outPath ??
|
|
38
|
-
await
|
|
34
|
+
const { mkdtemp, writeFile: writeFile2 } = await import("fs/promises");
|
|
35
|
+
const { tmpdir } = await import("os");
|
|
36
|
+
const { join: join13 } = await import("path");
|
|
37
|
+
const path = outPath ?? join13(await mkdtemp(join13(tmpdir(), "traces-")), "spans.otlp.jsonl");
|
|
38
|
+
await writeFile2(path, serializeSpans(spans), "utf8");
|
|
39
39
|
return path;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// src/analyze.ts
|
|
43
|
-
import { mkdtemp, writeFile } from "fs/promises";
|
|
44
|
-
import { tmpdir } from "os";
|
|
45
|
-
import { join } from "path";
|
|
46
43
|
import { buildDefaultAnalystRegistry } from "@tangle-network/agent-eval/analyst";
|
|
47
44
|
import { OtlpFileTraceStore } from "@tangle-network/agent-eval/traces";
|
|
48
|
-
async function writeOtlp(spans, outPath) {
|
|
49
|
-
const path = outPath ?? join(await mkdtemp(join(tmpdir(), "traces-")), "spans.otlp.jsonl");
|
|
50
|
-
await writeFile(path, serializeSpans(spans), "utf8");
|
|
51
|
-
return path;
|
|
52
|
-
}
|
|
53
45
|
var DETERMINISTIC_VIEW_CEILING = 256 * 1024 * 1024;
|
|
54
46
|
async function analyzeSpans(spans, opts = {}) {
|
|
55
47
|
if (spans.length === 0) throw new Error("analyzeSpans: no spans to analyze");
|
|
56
|
-
const otlpPath = await
|
|
48
|
+
const otlpPath = await writeOtlpFile(spans, opts.otlpOutPath);
|
|
57
49
|
const runId = opts.runId ?? `traces-${Date.now()}`;
|
|
58
50
|
const detStore = new OtlpFileTraceStore({ path: otlpPath, perCallByteCeiling: DETERMINISTIC_VIEW_CEILING });
|
|
59
51
|
await detStore.ensureIndexed();
|
|
@@ -135,13 +127,13 @@ async function runPipelines(spans, opts = {}) {
|
|
|
135
127
|
// src/adapters/amp.ts
|
|
136
128
|
import { readdir, readFile, stat } from "fs/promises";
|
|
137
129
|
import { homedir } from "os";
|
|
138
|
-
import { basename, join
|
|
130
|
+
import { basename, join } from "path";
|
|
139
131
|
var SERVICE = "amp";
|
|
140
132
|
var AmpAdapter = class {
|
|
141
133
|
harness = "amp";
|
|
142
134
|
threadsRoot() {
|
|
143
|
-
const base = process.env.XDG_DATA_HOME ??
|
|
144
|
-
return
|
|
135
|
+
const base = process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
|
|
136
|
+
return join(base, "amp", "threads");
|
|
145
137
|
}
|
|
146
138
|
async locate(opts = {}) {
|
|
147
139
|
let files;
|
|
@@ -153,7 +145,7 @@ var AmpAdapter = class {
|
|
|
153
145
|
const refs = [];
|
|
154
146
|
for (const f of files) {
|
|
155
147
|
if (!f.startsWith("T-") || !f.endsWith(".json")) continue;
|
|
156
|
-
const path =
|
|
148
|
+
const path = join(this.threadsRoot(), f);
|
|
157
149
|
let st;
|
|
158
150
|
try {
|
|
159
151
|
st = await stat(path);
|
|
@@ -235,7 +227,7 @@ var AmpAdapter = class {
|
|
|
235
227
|
// src/adapters/claude.ts
|
|
236
228
|
import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
237
229
|
import { homedir as homedir2 } from "os";
|
|
238
|
-
import { basename as basename2, join as
|
|
230
|
+
import { basename as basename2, join as join2 } from "path";
|
|
239
231
|
var SERVICE2 = "claude-code";
|
|
240
232
|
function parseLines(raw) {
|
|
241
233
|
const out = [];
|
|
@@ -333,7 +325,7 @@ var ClaudeAdapter = class {
|
|
|
333
325
|
harness = "claude-code";
|
|
334
326
|
aliases = ["claude", "claudish", "openclaw", "nanoclaw"];
|
|
335
327
|
root() {
|
|
336
|
-
return
|
|
328
|
+
return join2(homedir2(), ".claude", "projects");
|
|
337
329
|
}
|
|
338
330
|
async locate(opts = {}) {
|
|
339
331
|
const root = this.root();
|
|
@@ -345,7 +337,7 @@ var ClaudeAdapter = class {
|
|
|
345
337
|
}
|
|
346
338
|
const refs = [];
|
|
347
339
|
for (const dir of projectDirs) {
|
|
348
|
-
const dirPath =
|
|
340
|
+
const dirPath = join2(root, dir);
|
|
349
341
|
let files;
|
|
350
342
|
try {
|
|
351
343
|
files = await readdir2(dirPath);
|
|
@@ -354,7 +346,7 @@ var ClaudeAdapter = class {
|
|
|
354
346
|
}
|
|
355
347
|
for (const f of files) {
|
|
356
348
|
if (!f.endsWith(".jsonl")) continue;
|
|
357
|
-
const path =
|
|
349
|
+
const path = join2(dirPath, f);
|
|
358
350
|
let st;
|
|
359
351
|
try {
|
|
360
352
|
st = await stat2(path);
|
|
@@ -401,7 +393,7 @@ var ClaudeAdapter = class {
|
|
|
401
393
|
}
|
|
402
394
|
/** Parse `<session>/subagents/agent-*.jsonl`, parenting each under its Agent call. */
|
|
403
395
|
async foldSubagents(ref, traceId, main, out) {
|
|
404
|
-
const subDir =
|
|
396
|
+
const subDir = join2(ref.path.replace(/\.jsonl$/, ""), "subagents");
|
|
405
397
|
let files;
|
|
406
398
|
try {
|
|
407
399
|
files = await readdir2(subDir);
|
|
@@ -414,13 +406,13 @@ var ClaudeAdapter = class {
|
|
|
414
406
|
const hash = basename2(f, ".jsonl");
|
|
415
407
|
let meta = {};
|
|
416
408
|
try {
|
|
417
|
-
meta = JSON.parse(await readFile2(
|
|
409
|
+
meta = JSON.parse(await readFile2(join2(subDir, `${hash}.meta.json`), "utf8"));
|
|
418
410
|
} catch {
|
|
419
411
|
}
|
|
420
412
|
const parent = meta.toolUseId && main.toolSpanByUseId.get(meta.toolUseId)?.span_id || `root:${traceId}`;
|
|
421
413
|
let raw;
|
|
422
414
|
try {
|
|
423
|
-
raw = await readFile2(
|
|
415
|
+
raw = await readFile2(join2(subDir, f), "utf8");
|
|
424
416
|
} catch {
|
|
425
417
|
continue;
|
|
426
418
|
}
|
|
@@ -440,7 +432,7 @@ var ClaudeAdapter = class {
|
|
|
440
432
|
// src/adapters/codex.ts
|
|
441
433
|
import { readdir as readdir3, readFile as readFile3, stat as stat3 } from "fs/promises";
|
|
442
434
|
import { homedir as homedir3 } from "os";
|
|
443
|
-
import { basename as basename3, join as
|
|
435
|
+
import { basename as basename3, join as join3 } from "path";
|
|
444
436
|
var SERVICE3 = "codex";
|
|
445
437
|
function parseLines2(raw) {
|
|
446
438
|
const out = [];
|
|
@@ -473,7 +465,7 @@ async function* walkRollouts(root) {
|
|
|
473
465
|
return;
|
|
474
466
|
}
|
|
475
467
|
for (const y of years) {
|
|
476
|
-
const yp =
|
|
468
|
+
const yp = join3(root, y);
|
|
477
469
|
let months;
|
|
478
470
|
try {
|
|
479
471
|
months = await readdir3(yp);
|
|
@@ -481,7 +473,7 @@ async function* walkRollouts(root) {
|
|
|
481
473
|
continue;
|
|
482
474
|
}
|
|
483
475
|
for (const m of months) {
|
|
484
|
-
const mp =
|
|
476
|
+
const mp = join3(yp, m);
|
|
485
477
|
let days;
|
|
486
478
|
try {
|
|
487
479
|
days = await readdir3(mp);
|
|
@@ -489,7 +481,7 @@ async function* walkRollouts(root) {
|
|
|
489
481
|
continue;
|
|
490
482
|
}
|
|
491
483
|
for (const d of days) {
|
|
492
|
-
const dp =
|
|
484
|
+
const dp = join3(mp, d);
|
|
493
485
|
let files;
|
|
494
486
|
try {
|
|
495
487
|
files = await readdir3(dp);
|
|
@@ -497,7 +489,7 @@ async function* walkRollouts(root) {
|
|
|
497
489
|
continue;
|
|
498
490
|
}
|
|
499
491
|
for (const f of files) {
|
|
500
|
-
if (f.startsWith("rollout-") && f.endsWith(".jsonl")) yield
|
|
492
|
+
if (f.startsWith("rollout-") && f.endsWith(".jsonl")) yield join3(dp, f);
|
|
501
493
|
}
|
|
502
494
|
}
|
|
503
495
|
}
|
|
@@ -507,7 +499,7 @@ var CodexAdapter = class {
|
|
|
507
499
|
harness = "codex";
|
|
508
500
|
aliases = ["codex-acp"];
|
|
509
501
|
root() {
|
|
510
|
-
return process.env.CODEX_HOME ?
|
|
502
|
+
return process.env.CODEX_HOME ? join3(process.env.CODEX_HOME, "sessions") : join3(homedir3(), ".codex", "sessions");
|
|
511
503
|
}
|
|
512
504
|
async locate(opts = {}) {
|
|
513
505
|
const refs = [];
|
|
@@ -635,7 +627,7 @@ var CodexAdapter = class {
|
|
|
635
627
|
// src/adapters/copilot.ts
|
|
636
628
|
import { readdir as readdir4, readFile as readFile4, stat as stat4 } from "fs/promises";
|
|
637
629
|
import { homedir as homedir4 } from "os";
|
|
638
|
-
import { join as
|
|
630
|
+
import { join as join4 } from "path";
|
|
639
631
|
var SERVICE4 = "github-copilot";
|
|
640
632
|
function parseLines3(raw) {
|
|
641
633
|
const out = [];
|
|
@@ -652,7 +644,7 @@ var CopilotAdapter = class {
|
|
|
652
644
|
harness = "github-copilot";
|
|
653
645
|
aliases = ["copilot"];
|
|
654
646
|
root() {
|
|
655
|
-
return
|
|
647
|
+
return join4(process.env.COPILOT_HOME ?? join4(homedir4(), ".copilot"), "session-state");
|
|
656
648
|
}
|
|
657
649
|
async locate(opts = {}) {
|
|
658
650
|
let dirs;
|
|
@@ -663,7 +655,7 @@ var CopilotAdapter = class {
|
|
|
663
655
|
}
|
|
664
656
|
const refs = [];
|
|
665
657
|
for (const id of dirs) {
|
|
666
|
-
const path =
|
|
658
|
+
const path = join4(this.root(), id, "events.jsonl");
|
|
667
659
|
let st;
|
|
668
660
|
try {
|
|
669
661
|
st = await stat4(path);
|
|
@@ -761,7 +753,7 @@ var CopilotAdapter = class {
|
|
|
761
753
|
// src/adapters/factory.ts
|
|
762
754
|
import { readdir as readdir5, readFile as readFile5, stat as stat5 } from "fs/promises";
|
|
763
755
|
import { homedir as homedir5 } from "os";
|
|
764
|
-
import { basename as basename4, join as
|
|
756
|
+
import { basename as basename4, join as join5 } from "path";
|
|
765
757
|
var SERVICE5 = "factory";
|
|
766
758
|
function parseLines4(raw) {
|
|
767
759
|
const out = [];
|
|
@@ -778,7 +770,7 @@ var FactoryAdapter = class {
|
|
|
778
770
|
harness = "factory";
|
|
779
771
|
aliases = ["factory-droids", "droid"];
|
|
780
772
|
root() {
|
|
781
|
-
return
|
|
773
|
+
return join5(homedir5(), ".factory", "sessions");
|
|
782
774
|
}
|
|
783
775
|
async locate(opts = {}) {
|
|
784
776
|
const root = this.root();
|
|
@@ -790,7 +782,7 @@ var FactoryAdapter = class {
|
|
|
790
782
|
}
|
|
791
783
|
const refs = [];
|
|
792
784
|
for (const dir of dirs) {
|
|
793
|
-
const dp =
|
|
785
|
+
const dp = join5(root, dir);
|
|
794
786
|
let files;
|
|
795
787
|
try {
|
|
796
788
|
files = await readdir5(dp);
|
|
@@ -801,7 +793,7 @@ var FactoryAdapter = class {
|
|
|
801
793
|
if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
|
|
802
794
|
for (const f of files) {
|
|
803
795
|
if (!f.endsWith(".jsonl")) continue;
|
|
804
|
-
const path =
|
|
796
|
+
const path = join5(dp, f);
|
|
805
797
|
let st;
|
|
806
798
|
try {
|
|
807
799
|
st = await stat5(path);
|
|
@@ -899,7 +891,7 @@ var FactoryAdapter = class {
|
|
|
899
891
|
// src/adapters/forge.ts
|
|
900
892
|
import { readdir as readdir6, readFile as readFile6, stat as stat6 } from "fs/promises";
|
|
901
893
|
import { homedir as homedir6 } from "os";
|
|
902
|
-
import { basename as basename5, join as
|
|
894
|
+
import { basename as basename5, join as join6 } from "path";
|
|
903
895
|
var SERVICE6 = "forge";
|
|
904
896
|
function tokens(t) {
|
|
905
897
|
if (!t) return null;
|
|
@@ -910,7 +902,7 @@ var ForgeAdapter = class {
|
|
|
910
902
|
aliases = ["forgecode"];
|
|
911
903
|
/** Directories scanned for `*-dump.json` exports. */
|
|
912
904
|
dumpDirs() {
|
|
913
|
-
return [process.cwd(),
|
|
905
|
+
return [process.cwd(), join6(homedir6(), ".forge")];
|
|
914
906
|
}
|
|
915
907
|
async locate(opts = {}) {
|
|
916
908
|
const refs = [];
|
|
@@ -924,7 +916,7 @@ var ForgeAdapter = class {
|
|
|
924
916
|
}
|
|
925
917
|
for (const f of files) {
|
|
926
918
|
if (!f.endsWith("-dump.json")) continue;
|
|
927
|
-
const path =
|
|
919
|
+
const path = join6(dir, f);
|
|
928
920
|
let st;
|
|
929
921
|
try {
|
|
930
922
|
st = await stat6(path);
|
|
@@ -1009,7 +1001,7 @@ var ForgeAdapter = class {
|
|
|
1009
1001
|
// src/adapters/gemini.ts
|
|
1010
1002
|
import { readdir as readdir7, readFile as readFile7, stat as stat7 } from "fs/promises";
|
|
1011
1003
|
import { homedir as homedir7 } from "os";
|
|
1012
|
-
import { join as
|
|
1004
|
+
import { join as join7 } from "path";
|
|
1013
1005
|
function iso(ts, fallback) {
|
|
1014
1006
|
if (typeof ts === "number") return new Date(ts).toISOString();
|
|
1015
1007
|
if (typeof ts === "string" && ts.length > 0) return ts;
|
|
@@ -1030,7 +1022,7 @@ var GeminiFamilyAdapter = class {
|
|
|
1030
1022
|
this.homeDirName = cfg.homeDirName;
|
|
1031
1023
|
}
|
|
1032
1024
|
chatsRoot() {
|
|
1033
|
-
return
|
|
1025
|
+
return join7(homedir7(), this.homeDirName, "tmp");
|
|
1034
1026
|
}
|
|
1035
1027
|
async locate(opts = {}) {
|
|
1036
1028
|
const root = this.chatsRoot();
|
|
@@ -1042,7 +1034,7 @@ var GeminiFamilyAdapter = class {
|
|
|
1042
1034
|
}
|
|
1043
1035
|
const refs = [];
|
|
1044
1036
|
for (const pd of projectDirs) {
|
|
1045
|
-
const chatsDir =
|
|
1037
|
+
const chatsDir = join7(root, pd, "chats");
|
|
1046
1038
|
let files;
|
|
1047
1039
|
try {
|
|
1048
1040
|
files = await readdir7(chatsDir);
|
|
@@ -1051,7 +1043,7 @@ var GeminiFamilyAdapter = class {
|
|
|
1051
1043
|
}
|
|
1052
1044
|
for (const f of files) {
|
|
1053
1045
|
if (!f.startsWith("session-") || !f.endsWith(".json")) continue;
|
|
1054
|
-
const path =
|
|
1046
|
+
const path = join7(chatsDir, f);
|
|
1055
1047
|
let st;
|
|
1056
1048
|
try {
|
|
1057
1049
|
st = await stat7(path);
|
|
@@ -1147,7 +1139,7 @@ var GeminiAdapter = class extends GeminiFamilyAdapter {
|
|
|
1147
1139
|
// src/adapters/opencode.ts
|
|
1148
1140
|
import { readdir as readdir8, readFile as readFile8, stat as stat8 } from "fs/promises";
|
|
1149
1141
|
import { homedir as homedir8 } from "os";
|
|
1150
|
-
import { join as
|
|
1142
|
+
import { join as join8 } from "path";
|
|
1151
1143
|
var SERVICE7 = "opencode";
|
|
1152
1144
|
function isoOf(ms2) {
|
|
1153
1145
|
return new Date(ms2 ?? 0).toISOString();
|
|
@@ -1155,11 +1147,11 @@ function isoOf(ms2) {
|
|
|
1155
1147
|
var OpencodeAdapter = class {
|
|
1156
1148
|
harness = "opencode";
|
|
1157
1149
|
storage() {
|
|
1158
|
-
const base = process.env.XDG_DATA_HOME ??
|
|
1159
|
-
return
|
|
1150
|
+
const base = process.env.XDG_DATA_HOME ?? join8(homedir8(), ".local", "share");
|
|
1151
|
+
return join8(base, "opencode", "storage");
|
|
1160
1152
|
}
|
|
1161
1153
|
async locate(opts = {}) {
|
|
1162
|
-
const msgRoot =
|
|
1154
|
+
const msgRoot = join8(this.storage(), "message");
|
|
1163
1155
|
let sessionDirs;
|
|
1164
1156
|
try {
|
|
1165
1157
|
sessionDirs = await readdir8(msgRoot);
|
|
@@ -1168,7 +1160,7 @@ var OpencodeAdapter = class {
|
|
|
1168
1160
|
}
|
|
1169
1161
|
const refs = [];
|
|
1170
1162
|
for (const sid of sessionDirs) {
|
|
1171
|
-
const dir =
|
|
1163
|
+
const dir = join8(msgRoot, sid);
|
|
1172
1164
|
let st;
|
|
1173
1165
|
try {
|
|
1174
1166
|
st = await stat8(dir);
|
|
@@ -1185,7 +1177,7 @@ var OpencodeAdapter = class {
|
|
|
1185
1177
|
async parse(ref) {
|
|
1186
1178
|
const traceId = ref.sessionId;
|
|
1187
1179
|
const rootId = `root:${traceId}`;
|
|
1188
|
-
const partRoot =
|
|
1180
|
+
const partRoot = join8(this.storage(), "part");
|
|
1189
1181
|
let msgFiles;
|
|
1190
1182
|
try {
|
|
1191
1183
|
msgFiles = (await readdir8(ref.path)).filter((f) => f.endsWith(".json"));
|
|
@@ -1195,7 +1187,7 @@ var OpencodeAdapter = class {
|
|
|
1195
1187
|
const messages = [];
|
|
1196
1188
|
for (const f of msgFiles) {
|
|
1197
1189
|
try {
|
|
1198
|
-
messages.push({ msg: JSON.parse(await readFile8(
|
|
1190
|
+
messages.push({ msg: JSON.parse(await readFile8(join8(ref.path, f), "utf8")), file: f });
|
|
1199
1191
|
} catch {
|
|
1200
1192
|
}
|
|
1201
1193
|
}
|
|
@@ -1235,7 +1227,7 @@ var OpencodeAdapter = class {
|
|
|
1235
1227
|
})
|
|
1236
1228
|
);
|
|
1237
1229
|
step += 1;
|
|
1238
|
-
const pdir =
|
|
1230
|
+
const pdir = join8(partRoot, mid);
|
|
1239
1231
|
let partFiles;
|
|
1240
1232
|
try {
|
|
1241
1233
|
partFiles = (await readdir8(pdir)).filter((f) => f.endsWith(".json"));
|
|
@@ -1245,7 +1237,7 @@ var OpencodeAdapter = class {
|
|
|
1245
1237
|
for (const pf of partFiles) {
|
|
1246
1238
|
let part;
|
|
1247
1239
|
try {
|
|
1248
|
-
part = JSON.parse(await readFile8(
|
|
1240
|
+
part = JSON.parse(await readFile8(join8(pdir, pf), "utf8"));
|
|
1249
1241
|
} catch {
|
|
1250
1242
|
continue;
|
|
1251
1243
|
}
|
|
@@ -1279,7 +1271,7 @@ var OpencodeAdapter = class {
|
|
|
1279
1271
|
// src/adapters/pi.ts
|
|
1280
1272
|
import { readdir as readdir9, readFile as readFile9, stat as stat9 } from "fs/promises";
|
|
1281
1273
|
import { homedir as homedir9 } from "os";
|
|
1282
|
-
import { basename as basename6, join as
|
|
1274
|
+
import { basename as basename6, join as join9 } from "path";
|
|
1283
1275
|
var SERVICE8 = "pi";
|
|
1284
1276
|
function parseLines5(raw) {
|
|
1285
1277
|
const out = [];
|
|
@@ -1305,7 +1297,7 @@ function textOf(content) {
|
|
|
1305
1297
|
var PiAdapter = class {
|
|
1306
1298
|
harness = "pi";
|
|
1307
1299
|
root() {
|
|
1308
|
-
return
|
|
1300
|
+
return join9(homedir9(), ".pi", "agent", "sessions");
|
|
1309
1301
|
}
|
|
1310
1302
|
async locate(opts = {}) {
|
|
1311
1303
|
const root = this.root();
|
|
@@ -1317,7 +1309,7 @@ var PiAdapter = class {
|
|
|
1317
1309
|
}
|
|
1318
1310
|
const refs = [];
|
|
1319
1311
|
for (const dir of dirs) {
|
|
1320
|
-
const dp =
|
|
1312
|
+
const dp = join9(root, dir);
|
|
1321
1313
|
let files;
|
|
1322
1314
|
try {
|
|
1323
1315
|
files = await readdir9(dp);
|
|
@@ -1328,7 +1320,7 @@ var PiAdapter = class {
|
|
|
1328
1320
|
if (opts.cwd && !cwd.startsWith(opts.cwd)) continue;
|
|
1329
1321
|
for (const f of files) {
|
|
1330
1322
|
if (!f.endsWith(".jsonl")) continue;
|
|
1331
|
-
const path =
|
|
1323
|
+
const path = join9(dp, f);
|
|
1332
1324
|
let st;
|
|
1333
1325
|
try {
|
|
1334
1326
|
st = await stat9(path);
|
|
@@ -1427,7 +1419,7 @@ var PiAdapter = class {
|
|
|
1427
1419
|
// src/adapters/qwen.ts
|
|
1428
1420
|
import { readdir as readdir10, readFile as readFile10, stat as stat10 } from "fs/promises";
|
|
1429
1421
|
import { homedir as homedir10 } from "os";
|
|
1430
|
-
import { basename as basename7, join as
|
|
1422
|
+
import { basename as basename7, join as join10 } from "path";
|
|
1431
1423
|
var SERVICE9 = "qwen";
|
|
1432
1424
|
function parseLines6(raw) {
|
|
1433
1425
|
const out = [];
|
|
@@ -1448,7 +1440,7 @@ var QwenAdapter = class {
|
|
|
1448
1440
|
harness = "qwen";
|
|
1449
1441
|
aliases = ["qwen-code"];
|
|
1450
1442
|
root() {
|
|
1451
|
-
return
|
|
1443
|
+
return join10(homedir10(), ".qwen", "projects");
|
|
1452
1444
|
}
|
|
1453
1445
|
async locate(opts = {}) {
|
|
1454
1446
|
const root = this.root();
|
|
@@ -1460,7 +1452,7 @@ var QwenAdapter = class {
|
|
|
1460
1452
|
}
|
|
1461
1453
|
const refs = [];
|
|
1462
1454
|
for (const pd of projectDirs) {
|
|
1463
|
-
const chatsDir =
|
|
1455
|
+
const chatsDir = join10(root, pd, "chats");
|
|
1464
1456
|
let files;
|
|
1465
1457
|
try {
|
|
1466
1458
|
files = await readdir10(chatsDir);
|
|
@@ -1470,7 +1462,7 @@ var QwenAdapter = class {
|
|
|
1470
1462
|
if (opts.cwd) continue;
|
|
1471
1463
|
for (const f of files) {
|
|
1472
1464
|
if (!f.endsWith(".jsonl")) continue;
|
|
1473
|
-
const path =
|
|
1465
|
+
const path = join10(chatsDir, f);
|
|
1474
1466
|
let st;
|
|
1475
1467
|
try {
|
|
1476
1468
|
st = await stat10(path);
|
|
@@ -1596,11 +1588,15 @@ function knownHarnesses() {
|
|
|
1596
1588
|
|
|
1597
1589
|
// src/observer.ts
|
|
1598
1590
|
var sleep = (ms2, signal) => new Promise((resolve) => {
|
|
1599
|
-
const
|
|
1600
|
-
signal?.addEventListener("abort", () => {
|
|
1591
|
+
const onAbort = () => {
|
|
1601
1592
|
clearTimeout(t);
|
|
1602
1593
|
resolve();
|
|
1603
|
-
}
|
|
1594
|
+
};
|
|
1595
|
+
const t = setTimeout(() => {
|
|
1596
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1597
|
+
resolve();
|
|
1598
|
+
}, ms2);
|
|
1599
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1604
1600
|
});
|
|
1605
1601
|
function adaptersFor(opts) {
|
|
1606
1602
|
if (opts.adapters && opts.adapters.length > 0) return opts.adapters;
|
|
@@ -1645,27 +1641,41 @@ async function watchSessions(opts) {
|
|
|
1645
1641
|
continue;
|
|
1646
1642
|
}
|
|
1647
1643
|
sessionCount += 1;
|
|
1648
|
-
if (opts.onReport)
|
|
1644
|
+
if (opts.onReport) {
|
|
1645
|
+
try {
|
|
1646
|
+
await opts.onReport(ref, report);
|
|
1647
|
+
} catch (err) {
|
|
1648
|
+
opts.onError?.(err, ref);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1649
1651
|
for (const f of report.stuckLoops.findings) {
|
|
1650
1652
|
const key = loopKey(ref.sessionId, f.toolName, f.argHash);
|
|
1651
1653
|
if (f.occurrences > (seen.get(key) ?? 0)) {
|
|
1652
1654
|
seen.set(key, f.occurrences);
|
|
1653
1655
|
if (opts.onLoop) {
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1656
|
+
try {
|
|
1657
|
+
await opts.onLoop({
|
|
1658
|
+
harness: adapter.harness,
|
|
1659
|
+
sessionId: ref.sessionId,
|
|
1660
|
+
cwd: ref.cwd,
|
|
1661
|
+
toolName: f.toolName,
|
|
1662
|
+
argHash: f.argHash,
|
|
1663
|
+
occurrences: f.occurrences,
|
|
1664
|
+
windowMs: f.windowMs
|
|
1665
|
+
});
|
|
1666
|
+
} catch (err) {
|
|
1667
|
+
opts.onError?.(err, ref);
|
|
1668
|
+
}
|
|
1663
1669
|
}
|
|
1664
1670
|
}
|
|
1665
1671
|
}
|
|
1666
1672
|
}
|
|
1667
1673
|
}
|
|
1668
|
-
|
|
1674
|
+
try {
|
|
1675
|
+
opts.onTick?.({ sessions: sessionCount });
|
|
1676
|
+
} catch (err) {
|
|
1677
|
+
opts.onError?.(err);
|
|
1678
|
+
}
|
|
1669
1679
|
if (opts.signal?.aborted) break;
|
|
1670
1680
|
await sleep(intervalMs, opts.signal);
|
|
1671
1681
|
}
|
|
@@ -1705,12 +1715,12 @@ function redactSpans(spans, rules = TRACES_REDACTION_RULES) {
|
|
|
1705
1715
|
|
|
1706
1716
|
// src/upload-state.ts
|
|
1707
1717
|
import { createHash } from "crypto";
|
|
1708
|
-
import { mkdir, readFile as readFile11, writeFile
|
|
1718
|
+
import { mkdir, readFile as readFile11, writeFile } from "fs/promises";
|
|
1709
1719
|
import { homedir as homedir11 } from "os";
|
|
1710
|
-
import { dirname, join as
|
|
1720
|
+
import { dirname, join as join11 } from "path";
|
|
1711
1721
|
function stateFile() {
|
|
1712
|
-
const base = process.env.XDG_STATE_HOME ??
|
|
1713
|
-
return
|
|
1722
|
+
const base = process.env.XDG_STATE_HOME ?? join11(homedir11(), ".local", "state");
|
|
1723
|
+
return join11(base, "tangle-traces", "uploaded.json");
|
|
1714
1724
|
}
|
|
1715
1725
|
function uploadKey(harness, sessionId) {
|
|
1716
1726
|
return `${harness}:${sessionId}`;
|
|
@@ -1733,7 +1743,7 @@ async function loadState() {
|
|
|
1733
1743
|
async function saveState(state) {
|
|
1734
1744
|
const f = stateFile();
|
|
1735
1745
|
await mkdir(dirname(f), { recursive: true });
|
|
1736
|
-
await
|
|
1746
|
+
await writeFile(f, `${JSON.stringify(state, null, 2)}
|
|
1737
1747
|
`, "utf8");
|
|
1738
1748
|
}
|
|
1739
1749
|
function alreadyUploaded(state, harness, sessionId, hash) {
|
|
@@ -1744,7 +1754,7 @@ function alreadyUploaded(state, harness, sessionId, hash) {
|
|
|
1744
1754
|
import { readFile as readFile12 } from "fs/promises";
|
|
1745
1755
|
import { createRequire } from "module";
|
|
1746
1756
|
import { hostname } from "os";
|
|
1747
|
-
import { basename as basename8, join as
|
|
1757
|
+
import { basename as basename8, join as join12 } from "path";
|
|
1748
1758
|
import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
|
|
1749
1759
|
import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
|
|
1750
1760
|
var require2 = createRequire(import.meta.url);
|
|
@@ -1762,14 +1772,16 @@ async function planUpload(opts) {
|
|
|
1762
1772
|
let refs;
|
|
1763
1773
|
try {
|
|
1764
1774
|
refs = await adapter.locate({ cwd: opts.cwd, sinceMs: opts.sinceMs });
|
|
1765
|
-
} catch {
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
opts.onError?.(err);
|
|
1766
1777
|
continue;
|
|
1767
1778
|
}
|
|
1768
1779
|
for (const ref of refs) {
|
|
1769
1780
|
let raw;
|
|
1770
1781
|
try {
|
|
1771
1782
|
raw = await adapter.parse(ref);
|
|
1772
|
-
} catch {
|
|
1783
|
+
} catch (err) {
|
|
1784
|
+
opts.onError?.(err, ref);
|
|
1773
1785
|
continue;
|
|
1774
1786
|
}
|
|
1775
1787
|
if (raw.length === 0) continue;
|
|
@@ -1789,7 +1801,7 @@ async function planUpload(opts) {
|
|
|
1789
1801
|
async function gitBranch(cwd) {
|
|
1790
1802
|
if (!cwd) return void 0;
|
|
1791
1803
|
try {
|
|
1792
|
-
const head = await readFile12(
|
|
1804
|
+
const head = await readFile12(join12(cwd, ".git", "HEAD"), "utf8");
|
|
1793
1805
|
const m = head.match(/^ref:\s*refs\/heads\/(.+)$/m);
|
|
1794
1806
|
return m?.[1]?.trim();
|
|
1795
1807
|
} catch {
|
|
@@ -1825,6 +1837,8 @@ function toTraceSpanEvents(spans, rootMeta) {
|
|
|
1825
1837
|
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") attributes[k] = v;
|
|
1826
1838
|
else if (v != null) attributes[k] = JSON.stringify(v);
|
|
1827
1839
|
}
|
|
1840
|
+
attributes["tangle.sessionId"] = s.trace_id;
|
|
1841
|
+
attributes["tangle.ingest_source"] = "cli";
|
|
1828
1842
|
if (s.parent_span_id === null) Object.assign(attributes, rootMeta);
|
|
1829
1843
|
const startNano = msToNano(s.start_time);
|
|
1830
1844
|
return {
|
|
@@ -1892,7 +1906,6 @@ export {
|
|
|
1892
1906
|
span,
|
|
1893
1907
|
serializeSpans,
|
|
1894
1908
|
writeOtlpFile,
|
|
1895
|
-
writeOtlp,
|
|
1896
1909
|
analyzeSpans,
|
|
1897
1910
|
toRuntimeStore,
|
|
1898
1911
|
runPipelines,
|
package/dist/cli.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
resolveAdapter,
|
|
9
9
|
runPipelines,
|
|
10
10
|
watchSessions,
|
|
11
|
-
|
|
12
|
-
} from "./chunk-
|
|
11
|
+
writeOtlpFile
|
|
12
|
+
} from "./chunk-4PC7MQXT.js";
|
|
13
13
|
|
|
14
14
|
// src/cli.ts
|
|
15
15
|
import { stat, writeFile } from "fs/promises";
|
|
@@ -254,7 +254,7 @@ async function collectSpans(args) {
|
|
|
254
254
|
async function cmdConvert(args) {
|
|
255
255
|
const { spans } = await collectSpans(args);
|
|
256
256
|
if (spans.length === 0) throw new Error("no spans found for the given selection");
|
|
257
|
-
const path = await
|
|
257
|
+
const path = await writeOtlpFile(spans, args.otlp);
|
|
258
258
|
console.log(`wrote ${spans.length} spans \u2192 ${path}`);
|
|
259
259
|
}
|
|
260
260
|
async function cmdAnalyze(args) {
|
package/dist/index.d.ts
CHANGED
|
@@ -442,8 +442,6 @@ interface AnalyzeResult {
|
|
|
442
442
|
otlpPath: string;
|
|
443
443
|
result: Awaited<ReturnType<ReturnType<typeof buildDefaultAnalystRegistry>['run']>>;
|
|
444
444
|
}
|
|
445
|
-
/** Write spans to an OTLP-JSONL file and return its path. */
|
|
446
|
-
declare function writeOtlp(spans: readonly OtlpSpan[], outPath?: string): Promise<string>;
|
|
447
445
|
declare function analyzeSpans(spans: readonly OtlpSpan[], opts?: AnalyzeOptions): Promise<AnalyzeResult>;
|
|
448
446
|
|
|
449
447
|
/**
|
|
@@ -553,6 +551,8 @@ interface CollectOptions {
|
|
|
553
551
|
last?: number;
|
|
554
552
|
/** Redact PII/secrets (default true). Set false to get raw spans. */
|
|
555
553
|
redact?: boolean;
|
|
554
|
+
/** Called on a per-adapter locate/parse failure (collection continues). */
|
|
555
|
+
onError?: (error: unknown, ref?: SessionRef) => void;
|
|
556
556
|
}
|
|
557
557
|
interface SessionBatch {
|
|
558
558
|
ref: SessionRef;
|
|
@@ -622,6 +622,8 @@ interface PlanOptions {
|
|
|
622
622
|
all?: boolean;
|
|
623
623
|
cwd?: string;
|
|
624
624
|
sinceMs?: number;
|
|
625
|
+
/** Called on a per-adapter locate/parse failure (planning continues). */
|
|
626
|
+
onError?: (error: unknown, ref?: SessionRef) => void;
|
|
625
627
|
}
|
|
626
628
|
/** Read-only: select sessions in the window, redact, and mark which are new. */
|
|
627
629
|
declare function planUpload(opts: PlanOptions): Promise<UploadPlan>;
|
|
@@ -654,4 +656,4 @@ interface ExecuteOptions {
|
|
|
654
656
|
/** Send the NEW items (or, on dryRun, write the redacted OTLP that would send). */
|
|
655
657
|
declare function executeUpload(plan: UploadPlan, opts?: ExecuteOptions): Promise<UploadResult>;
|
|
656
658
|
|
|
657
|
-
export { AmpAdapter, type AnalyzeOptions, type AnalyzeResult, CODING_REDACTION_RULES, ClaudeAdapter, CodexAdapter, type CollectOptions, CopilotAdapter, type ExecuteOptions, FactoryAdapter, ForgeAdapter, GeminiAdapter, type HarnessTraceAdapter, type LocateOptions, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpSpan, type OtlpSpanKind, type OtlpStatusCode, PiAdapter, type PipelineOptions, type PipelineReport, type PlanOptions, QwenAdapter, type RuntimeTrace, 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, collectSessions, executeUpload, knownHarnesses, listAdapters, loadState, planUpload, redactSpans, resolveAdapter, runPipelines, saveState, serializeSpans, sessionHash, span, toRuntimeStore, toTraceSpanEvents, uploadKey, watchSessions,
|
|
659
|
+
export { AmpAdapter, type AnalyzeOptions, type AnalyzeResult, CODING_REDACTION_RULES, ClaudeAdapter, CodexAdapter, type CollectOptions, CopilotAdapter, type ExecuteOptions, FactoryAdapter, ForgeAdapter, GeminiAdapter, type HarnessTraceAdapter, type LocateOptions, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpSpan, type OtlpSpanKind, type OtlpStatusCode, PiAdapter, type PipelineOptions, type PipelineReport, type PlanOptions, QwenAdapter, type RuntimeTrace, 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, collectSessions, executeUpload, knownHarnesses, listAdapters, loadState, planUpload, redactSpans, resolveAdapter, runPipelines, saveState, serializeSpans, sessionHash, span, toRuntimeStore, toTraceSpanEvents, uploadKey, watchSessions, writeOtlpFile };
|
package/dist/index.js
CHANGED
|
@@ -29,9 +29,8 @@ import {
|
|
|
29
29
|
toTraceSpanEvents,
|
|
30
30
|
uploadKey,
|
|
31
31
|
watchSessions,
|
|
32
|
-
writeOtlp,
|
|
33
32
|
writeOtlpFile
|
|
34
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-4PC7MQXT.js";
|
|
35
34
|
|
|
36
35
|
// src/collect.ts
|
|
37
36
|
function adaptersFor(opts) {
|
|
@@ -50,7 +49,8 @@ async function collectSessions(opts = {}) {
|
|
|
50
49
|
let refs;
|
|
51
50
|
try {
|
|
52
51
|
refs = await adapter.locate({ cwd: opts.cwd, sinceMs: opts.sinceMs });
|
|
53
|
-
} catch {
|
|
52
|
+
} catch (err) {
|
|
53
|
+
opts.onError?.(err);
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
56
|
if (opts.last && opts.last > 0) refs = refs.slice(0, opts.last);
|
|
@@ -58,7 +58,8 @@ async function collectSessions(opts = {}) {
|
|
|
58
58
|
let spans;
|
|
59
59
|
try {
|
|
60
60
|
spans = await adapter.parse(ref);
|
|
61
|
-
} catch {
|
|
61
|
+
} catch (err) {
|
|
62
|
+
opts.onError?.(err, ref);
|
|
62
63
|
continue;
|
|
63
64
|
}
|
|
64
65
|
if (spans.length === 0) continue;
|
|
@@ -121,6 +122,5 @@ export {
|
|
|
121
122
|
toTraceSpanEvents,
|
|
122
123
|
uploadKey,
|
|
123
124
|
watchSessions,
|
|
124
|
-
writeOtlp,
|
|
125
125
|
writeOtlpFile
|
|
126
126
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/traces",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|