@tangle-network/traces 0.2.1 → 0.3.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.
@@ -70,6 +70,25 @@ async function analyzeSpans(spans, opts = {}) {
70
70
  return { otlpPath, result };
71
71
  }
72
72
 
73
+ // src/time.ts
74
+ function parseIsoToEpochMs(ts) {
75
+ if (!ts) return 0;
76
+ if (/^\d+$/.test(ts)) return Number(ts);
77
+ const n = Date.parse(ts);
78
+ return Number.isNaN(n) ? 0 : n;
79
+ }
80
+ function parseSince(value) {
81
+ const m = value.match(/^(\d+)\s*([mhd])$/i);
82
+ if (m) {
83
+ const unit = m[2].toLowerCase();
84
+ const ms = unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
85
+ return Date.now() - Number(m[1]) * ms;
86
+ }
87
+ const t = Date.parse(value);
88
+ if (Number.isNaN(t)) throw new Error(`--since: expected 30m / 2h / 7d or an ISO date, got "${value}"`);
89
+ return t;
90
+ }
91
+
73
92
  // src/runtime-store.ts
74
93
  import { InMemoryTraceStore } from "@tangle-network/agent-eval";
75
94
  var KIND = {
@@ -79,10 +98,6 @@ var KIND = {
79
98
  CHAIN: "custom",
80
99
  SPAN: "custom"
81
100
  };
82
- function ms(iso2) {
83
- const n = Date.parse(iso2);
84
- return Number.isNaN(n) ? 0 : n;
85
- }
86
101
  async function toRuntimeStore(spans) {
87
102
  const store = new InMemoryTraceStore();
88
103
  const runIds = /* @__PURE__ */ new Set();
@@ -97,8 +112,8 @@ async function toRuntimeStore(spans) {
97
112
  parentSpanId: s.parent_span_id ?? void 0,
98
113
  runId: s.trace_id,
99
114
  name: s.name,
100
- startedAt: ms(s.start_time),
101
- endedAt: ms(s.end_time),
115
+ startedAt: parseIsoToEpochMs(s.start_time),
116
+ endedAt: parseIsoToEpochMs(s.end_time),
102
117
  status: s.status.code === "ERROR" ? "error" : "ok",
103
118
  error: s.status.message,
104
119
  attributes: s.attributes
@@ -1141,8 +1156,8 @@ import { readdir as readdir8, readFile as readFile8, stat as stat8 } from "fs/pr
1141
1156
  import { homedir as homedir8 } from "os";
1142
1157
  import { join as join8 } from "path";
1143
1158
  var SERVICE7 = "opencode";
1144
- function isoOf(ms2) {
1145
- return new Date(ms2 ?? 0).toISOString();
1159
+ function isoOf(ms) {
1160
+ return new Date(ms ?? 0).toISOString();
1146
1161
  }
1147
1162
  var OpencodeAdapter = class {
1148
1163
  harness = "opencode";
@@ -1585,9 +1600,45 @@ function resolveAdapter(harness) {
1585
1600
  function knownHarnesses() {
1586
1601
  return [...BY_NAME.keys()].sort();
1587
1602
  }
1603
+ function selectAdapters(opts) {
1604
+ if (opts.adapters && opts.adapters.length > 0) return [...opts.adapters];
1605
+ if (opts.all || !opts.harnesses || opts.harnesses.length === 0) return [...ADAPTERS];
1606
+ return opts.harnesses.map((h) => {
1607
+ const a = resolveAdapter(h);
1608
+ if (!a) throw new Error(`unknown harness "${h}". Known: ${knownHarnesses().join(", ")}`);
1609
+ return a;
1610
+ });
1611
+ }
1612
+
1613
+ // src/session-source.ts
1614
+ async function* scanSessions(opts) {
1615
+ for (const adapter of selectAdapters(opts)) {
1616
+ if (opts.signal?.aborted) return;
1617
+ let refs;
1618
+ try {
1619
+ refs = await adapter.locate({ cwd: opts.cwd, sinceMs: opts.sinceMs });
1620
+ } catch (err) {
1621
+ opts.onError?.(err);
1622
+ continue;
1623
+ }
1624
+ if (opts.last && opts.last > 0) refs = refs.slice(0, opts.last);
1625
+ for (const ref of refs) {
1626
+ if (opts.signal?.aborted) return;
1627
+ let spans;
1628
+ try {
1629
+ spans = await adapter.parse(ref);
1630
+ } catch (err) {
1631
+ opts.onError?.(err, ref);
1632
+ continue;
1633
+ }
1634
+ if (spans.length === 0) continue;
1635
+ yield { adapter, ref, spans };
1636
+ }
1637
+ }
1638
+ }
1588
1639
 
1589
1640
  // src/observer.ts
1590
- var sleep = (ms2, signal) => new Promise((resolve) => {
1641
+ var sleep = (ms, signal) => new Promise((resolve) => {
1591
1642
  const onAbort = () => {
1592
1643
  clearTimeout(t);
1593
1644
  resolve();
@@ -1595,19 +1646,9 @@ var sleep = (ms2, signal) => new Promise((resolve) => {
1595
1646
  const t = setTimeout(() => {
1596
1647
  signal?.removeEventListener("abort", onAbort);
1597
1648
  resolve();
1598
- }, ms2);
1649
+ }, ms);
1599
1650
  signal?.addEventListener("abort", onAbort, { once: true });
1600
1651
  });
1601
- function adaptersFor(opts) {
1602
- if (opts.adapters && opts.adapters.length > 0) return opts.adapters;
1603
- if (opts.all || !opts.harnesses || opts.harnesses.length === 0) return [...listAdapters()];
1604
- const out = [];
1605
- for (const h of opts.harnesses) {
1606
- const a = resolveAdapter(h);
1607
- if (a && !out.includes(a)) out.push(a);
1608
- }
1609
- return out;
1610
- }
1611
1652
  function loopKey(sessionId, toolName, argHash) {
1612
1653
  return `${sessionId}\0${toolName}\0${argHash}`;
1613
1654
  }
@@ -1615,57 +1656,50 @@ async function watchSessions(opts) {
1615
1656
  const windowMs = opts.windowMs ?? 30 * 6e4;
1616
1657
  const intervalMs = Math.max(250, opts.intervalMs ?? 5e3);
1617
1658
  const minLoop = opts.minLoopOccurrences ?? 3;
1618
- const adapters = adaptersFor(opts);
1619
1659
  const seen = /* @__PURE__ */ new Map();
1620
1660
  while (!opts.signal?.aborted) {
1621
- const sinceMs = Date.now() - windowMs;
1622
1661
  let sessionCount = 0;
1623
- for (const adapter of adapters) {
1624
- if (opts.signal?.aborted) break;
1625
- let refs;
1662
+ for await (const { adapter, ref, spans } of scanSessions({
1663
+ adapters: opts.adapters,
1664
+ all: opts.all,
1665
+ harnesses: opts.harnesses,
1666
+ cwd: opts.cwd,
1667
+ sinceMs: Date.now() - windowMs,
1668
+ signal: opts.signal,
1669
+ onError: opts.onError
1670
+ })) {
1671
+ let report;
1626
1672
  try {
1627
- refs = await adapter.locate({ cwd: opts.cwd, sinceMs });
1673
+ report = await runPipelines(spans, { minLoopOccurrences: minLoop });
1628
1674
  } catch (err) {
1629
- opts.onError?.(err);
1675
+ opts.onError?.(err, ref);
1630
1676
  continue;
1631
1677
  }
1632
- for (const ref of refs) {
1633
- if (opts.signal?.aborted) break;
1634
- let report;
1678
+ sessionCount += 1;
1679
+ if (opts.onReport) {
1635
1680
  try {
1636
- const spans = await adapter.parse(ref);
1637
- if (spans.length === 0) continue;
1638
- report = await runPipelines(spans, { minLoopOccurrences: minLoop });
1681
+ await opts.onReport(ref, report);
1639
1682
  } catch (err) {
1640
1683
  opts.onError?.(err, ref);
1641
- continue;
1642
1684
  }
1643
- sessionCount += 1;
1644
- if (opts.onReport) {
1645
- try {
1646
- await opts.onReport(ref, report);
1647
- } catch (err) {
1648
- opts.onError?.(err, ref);
1649
- }
1650
- }
1651
- for (const f of report.stuckLoops.findings) {
1652
- const key = loopKey(ref.sessionId, f.toolName, f.argHash);
1653
- if (f.occurrences > (seen.get(key) ?? 0)) {
1654
- seen.set(key, f.occurrences);
1655
- if (opts.onLoop) {
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
- }
1685
+ }
1686
+ for (const f of report.stuckLoops.findings) {
1687
+ const key = loopKey(ref.sessionId, f.toolName, f.argHash);
1688
+ if (f.occurrences > (seen.get(key) ?? 0)) {
1689
+ seen.set(key, f.occurrences);
1690
+ if (opts.onLoop) {
1691
+ try {
1692
+ await opts.onLoop({
1693
+ harness: adapter.harness,
1694
+ sessionId: ref.sessionId,
1695
+ cwd: ref.cwd,
1696
+ toolName: f.toolName,
1697
+ argHash: f.argHash,
1698
+ occurrences: f.occurrences,
1699
+ windowMs: f.windowMs
1700
+ });
1701
+ } catch (err) {
1702
+ opts.onError?.(err, ref);
1669
1703
  }
1670
1704
  }
1671
1705
  }
@@ -1681,6 +1715,28 @@ async function watchSessions(opts) {
1681
1715
  }
1682
1716
  }
1683
1717
 
1718
+ // src/attributes.ts
1719
+ var ATTR = {
1720
+ /** = the trace/session id. Wire contract (dedup linkage). */
1721
+ SESSION_ID: "tangle.sessionId",
1722
+ /** "cli" marks a CLI upload so the platform can drop a re-upload of a session
1723
+ * that also streamed live. Wire contract. */
1724
+ INGEST_SOURCE: "tangle.ingest_source",
1725
+ HARNESS: "tangle.harness",
1726
+ CWD: "tangle.cwd",
1727
+ GIT_BRANCH: "tangle.git_branch",
1728
+ HOST: "tangle.host",
1729
+ /** Basename of the session file. (Renamed from the ambiguous `tangle.source`,
1730
+ * which collided with `tangle.ingest_source`.) */
1731
+ SESSION_FILE: "tangle.session_file",
1732
+ UPLOADED_AT: "tangle.uploaded_at",
1733
+ UPLOADER: "tangle.uploader",
1734
+ REDACTION_VERSION: "redaction.version",
1735
+ REDACTION_COUNT: "redaction.count"
1736
+ };
1737
+ var INGEST_SOURCE_CLI = "cli";
1738
+ var DEFAULT_HARNESS = "claude-code";
1739
+
1684
1740
  // src/redact.ts
1685
1741
  import { DEFAULT_REDACTION_RULES, redactValue } from "@tangle-network/agent-eval/traces";
1686
1742
  var CODING_REDACTION_RULES = [
@@ -1759,42 +1815,19 @@ import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
1759
1815
  import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
1760
1816
  var require2 = createRequire(import.meta.url);
1761
1817
  var TRACES_VERSION = require2("../package.json").version;
1762
- function adaptersFor2(opts) {
1763
- if (opts.all) return [...listAdapters()];
1764
- const a = resolveAdapter(opts.harness ?? "claude-code");
1765
- if (!a) throw new Error(`unknown harness "${opts.harness}"`);
1766
- return [a];
1767
- }
1768
- async function planUpload(opts) {
1818
+ async function planUpload(opts = {}) {
1769
1819
  const state = await loadState();
1770
1820
  const items = [];
1771
- for (const adapter of adaptersFor2(opts)) {
1772
- let refs;
1773
- try {
1774
- refs = await adapter.locate({ cwd: opts.cwd, sinceMs: opts.sinceMs });
1775
- } catch (err) {
1776
- opts.onError?.(err);
1777
- continue;
1778
- }
1779
- for (const ref of refs) {
1780
- let raw;
1781
- try {
1782
- raw = await adapter.parse(ref);
1783
- } catch (err) {
1784
- opts.onError?.(err, ref);
1785
- continue;
1786
- }
1787
- if (raw.length === 0) continue;
1788
- const { spans, report } = redactSpans(raw);
1789
- const hash = sessionHash(spans);
1790
- items.push({
1791
- ref,
1792
- spans,
1793
- redaction: report,
1794
- hash,
1795
- isNew: !alreadyUploaded(state, ref.harness, ref.sessionId, hash)
1796
- });
1797
- }
1821
+ for await (const { ref, spans: raw } of scanSessions(opts)) {
1822
+ const { spans, report } = redactSpans(raw);
1823
+ const hash = sessionHash(spans);
1824
+ items.push({
1825
+ ref,
1826
+ spans,
1827
+ redaction: report,
1828
+ hash,
1829
+ isNew: !alreadyUploaded(state, ref.harness, ref.sessionId, hash)
1830
+ });
1798
1831
  }
1799
1832
  return { items, state, sinceMs: opts.sinceMs };
1800
1833
  }
@@ -1810,26 +1843,20 @@ async function gitBranch(cwd) {
1810
1843
  }
1811
1844
  async function sessionMeta(item, uploadedAt) {
1812
1845
  const meta = {
1813
- "tangle.harness": item.ref.harness,
1814
- "tangle.source": basename8(item.ref.path),
1815
- "tangle.host": hostname(),
1816
- "tangle.uploaded_at": uploadedAt,
1817
- "tangle.uploader": `tangle-traces@${TRACES_VERSION}`,
1818
- "redaction.version": REDACTION_VERSION,
1819
- "redaction.count": item.redaction.redactionCount
1846
+ [ATTR.HARNESS]: item.ref.harness,
1847
+ [ATTR.SESSION_FILE]: basename8(item.ref.path),
1848
+ [ATTR.HOST]: hostname(),
1849
+ [ATTR.UPLOADED_AT]: uploadedAt,
1850
+ [ATTR.UPLOADER]: `tangle-traces@${TRACES_VERSION}`,
1851
+ [ATTR.REDACTION_VERSION]: REDACTION_VERSION,
1852
+ [ATTR.REDACTION_COUNT]: item.redaction.redactionCount
1820
1853
  };
1821
- if (item.ref.cwd) meta["tangle.cwd"] = item.ref.cwd;
1854
+ if (item.ref.cwd) meta[ATTR.CWD] = item.ref.cwd;
1822
1855
  const branch = await gitBranch(item.ref.cwd);
1823
- if (branch) meta["tangle.git_branch"] = branch;
1856
+ if (branch) meta[ATTR.GIT_BRANCH] = branch;
1824
1857
  return meta;
1825
1858
  }
1826
- function epochMs(ts) {
1827
- if (!ts) return 0;
1828
- if (/^\d+$/.test(ts)) return Number(ts);
1829
- const n = Date.parse(ts);
1830
- return Number.isNaN(n) ? 0 : n;
1831
- }
1832
- var msToNano = (iso2) => epochMs(iso2) * 1e6;
1859
+ var msToNano = (iso2) => parseIsoToEpochMs(iso2) * 1e6;
1833
1860
  function toTraceSpanEvents(spans, rootMeta) {
1834
1861
  return spans.map((s) => {
1835
1862
  const attributes = {};
@@ -1837,6 +1864,8 @@ function toTraceSpanEvents(spans, rootMeta) {
1837
1864
  if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") attributes[k] = v;
1838
1865
  else if (v != null) attributes[k] = JSON.stringify(v);
1839
1866
  }
1867
+ attributes[ATTR.SESSION_ID] = s.trace_id;
1868
+ attributes[ATTR.INGEST_SOURCE] = INGEST_SOURCE_CLI;
1840
1869
  if (s.parent_span_id === null) Object.assign(attributes, rootMeta);
1841
1870
  const startNano = msToNano(s.start_time);
1842
1871
  return {
@@ -1905,6 +1934,8 @@ export {
1905
1934
  serializeSpans,
1906
1935
  writeOtlpFile,
1907
1936
  analyzeSpans,
1937
+ parseIsoToEpochMs,
1938
+ parseSince,
1908
1939
  toRuntimeStore,
1909
1940
  runPipelines,
1910
1941
  AmpAdapter,
@@ -1920,7 +1951,12 @@ export {
1920
1951
  listAdapters,
1921
1952
  resolveAdapter,
1922
1953
  knownHarnesses,
1954
+ selectAdapters,
1955
+ scanSessions,
1923
1956
  watchSessions,
1957
+ ATTR,
1958
+ INGEST_SOURCE_CLI,
1959
+ DEFAULT_HARNESS,
1924
1960
  CODING_REDACTION_RULES,
1925
1961
  TRACES_REDACTION_RULES,
1926
1962
  redactSpans,
package/dist/cli.js CHANGED
@@ -3,13 +3,14 @@ import {
3
3
  analyzeSpans,
4
4
  executeUpload,
5
5
  knownHarnesses,
6
- listAdapters,
6
+ parseSince,
7
7
  planUpload,
8
8
  resolveAdapter,
9
9
  runPipelines,
10
+ selectAdapters,
10
11
  watchSessions,
11
12
  writeOtlpFile
12
- } from "./chunk-YYVPAOOD.js";
13
+ } from "./chunk-6NILXLUI.js";
13
14
 
14
15
  // src/cli.ts
15
16
  import { stat, writeFile } from "fs/promises";
@@ -163,18 +164,6 @@ function parseArgs(argv) {
163
164
  }
164
165
  return a;
165
166
  }
166
- function parseSince(s) {
167
- if (!s) return Date.now() - 24 * 60 * 60 * 1e3;
168
- const m = s.match(/^(\d+)\s*([mhd])$/i);
169
- if (m) {
170
- const unit = m[2].toLowerCase();
171
- const ms = unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
172
- return Date.now() - Number(m[1]) * ms;
173
- }
174
- const t = Date.parse(s);
175
- if (Number.isNaN(t)) throw new Error(`--since: expected 30m / 2h / 7d or an ISO date, got "${s}"`);
176
- return t;
177
- }
178
167
  async function confirm(question) {
179
168
  if (!process.stdin.isTTY) return false;
180
169
  const rl = (await import("readline/promises")).createInterface({
@@ -189,12 +178,7 @@ async function confirm(question) {
189
178
  }
190
179
  }
191
180
  function adaptersFor(args) {
192
- if (args.all) return [...listAdapters()];
193
- const adapter = resolveAdapter(args.harness);
194
- if (!adapter) {
195
- throw new Error(`unknown harness "${args.harness}". Known: ${knownHarnesses().join(", ")}`);
196
- }
197
- return [adapter];
181
+ return selectAdapters({ all: args.all, harnesses: [args.harness] });
198
182
  }
199
183
  async function discover(args) {
200
184
  const sinceMs = args.since ? Date.parse(args.since) : void 0;
@@ -304,9 +288,14 @@ async function cmdWatch(args) {
304
288
  });
305
289
  }
306
290
  async function cmdUpload(args) {
307
- const sinceMs = parseSince(args.since);
291
+ const sinceMs = args.since ? parseSince(args.since) : Date.now() - 24 * 60 * 60 * 1e3;
308
292
  const all = args.all || !args.harnessExplicit;
309
- const plan = await planUpload({ all, harness: args.harness, cwd: args.cwd, sinceMs });
293
+ const plan = await planUpload({
294
+ all,
295
+ harnesses: args.harnessExplicit ? [args.harness] : void 0,
296
+ cwd: args.cwd,
297
+ sinceMs
298
+ });
310
299
  const newItems = plan.items.filter((i) => i.isNew);
311
300
  const byRule = {};
312
301
  let totalRedactions = 0;
package/dist/index.d.ts CHANGED
@@ -118,6 +118,49 @@ interface HarnessTraceAdapter {
118
118
  parse(ref: SessionRef): Promise<OtlpSpan[]>;
119
119
  }
120
120
 
121
+ /**
122
+ * Span-attribute keys the upload path stamps. The CLI is the producer; the
123
+ * Tangle Intelligence Platform is the consumer.
124
+ *
125
+ * `SESSION_ID` and `INGEST_SOURCE` are a cross-repo WIRE CONTRACT — the platform
126
+ * keys its cross-source dedup on them (`session.id`/`tangle.sessionId` linkage +
127
+ * `tangle.ingest_source === "cli"`). Do NOT change their string VALUES without a
128
+ * coordinated platform release. The rest are stored-but-not-parsed metadata,
129
+ * free to evolve.
130
+ *
131
+ * `SESSION_ID` stays camelCase because it must match agent-eval's
132
+ * `parseConversationLinkage` key list; the metadata keys are snake_case.
133
+ */
134
+ declare const ATTR: {
135
+ /** = the trace/session id. Wire contract (dedup linkage). */
136
+ readonly SESSION_ID: "tangle.sessionId";
137
+ /** "cli" marks a CLI upload so the platform can drop a re-upload of a session
138
+ * that also streamed live. Wire contract. */
139
+ readonly INGEST_SOURCE: "tangle.ingest_source";
140
+ readonly HARNESS: "tangle.harness";
141
+ readonly CWD: "tangle.cwd";
142
+ readonly GIT_BRANCH: "tangle.git_branch";
143
+ readonly HOST: "tangle.host";
144
+ /** Basename of the session file. (Renamed from the ambiguous `tangle.source`,
145
+ * which collided with `tangle.ingest_source`.) */
146
+ readonly SESSION_FILE: "tangle.session_file";
147
+ readonly UPLOADED_AT: "tangle.uploaded_at";
148
+ readonly UPLOADER: "tangle.uploader";
149
+ readonly REDACTION_VERSION: "redaction.version";
150
+ readonly REDACTION_COUNT: "redaction.count";
151
+ };
152
+ /** `tangle.ingest_source` value for CLI-uploaded traces. Wire contract. */
153
+ declare const INGEST_SOURCE_CLI = "cli";
154
+ /** Harness used when none is specified on a single-harness command. */
155
+ declare const DEFAULT_HARNESS = "claude-code";
156
+
157
+ /** Parse an ISO-8601 timestamp or an epoch-millis string to epoch ms.
158
+ * Returns 0 for empty/unparseable input (callers treat 0 as "unknown"). */
159
+ declare function parseIsoToEpochMs(ts: string): number;
160
+ /** Parse a `--since` window: `30m` / `2h` / `7d` (relative to now) or an ISO
161
+ * date, → epoch ms. Throws on a malformed value (fail-loud, not silent). */
162
+ declare function parseSince(value: string): number;
163
+
121
164
  /**
122
165
  * Harness adapter registry — resolves a harness id (or alias) to the
123
166
  * adapter that knows its on-disk session format.
@@ -134,6 +177,41 @@ interface HarnessTraceAdapter {
134
177
  declare function listAdapters(): readonly HarnessTraceAdapter[];
135
178
  declare function resolveAdapter(harness: string): HarnessTraceAdapter | undefined;
136
179
  declare function knownHarnesses(): string[];
180
+ /** Options for selecting which adapters a command/SDK call operates on. */
181
+ interface AdapterSelection {
182
+ /** Use these adapters verbatim (e.g. a caller's own harness) — wins over all. */
183
+ adapters?: readonly HarnessTraceAdapter[];
184
+ /** Every known harness. Implied when no `adapters`/`harnesses` are given. */
185
+ all?: boolean;
186
+ /** Specific harness ids/aliases. Unknown ids throw (fail-loud). */
187
+ harnesses?: readonly string[];
188
+ }
189
+ /** Resolve a selection to the adapters to operate on. Precedence:
190
+ * explicit `adapters` → named `harnesses` → everything (`all` or unspecified).
191
+ * A named harness that doesn't resolve throws rather than silently dropping. */
192
+ declare function selectAdapters(opts: AdapterSelection): HarnessTraceAdapter[];
193
+
194
+ interface ScanOptions extends AdapterSelection {
195
+ /** Filter by working directory (exact/prefix). */
196
+ cwd?: string;
197
+ /** Only sessions modified at/after this epoch ms. */
198
+ sinceMs?: number;
199
+ /** Cap to the most-recent N sessions per harness. */
200
+ last?: number;
201
+ /** Cancel the scan; iteration stops between sessions. */
202
+ signal?: AbortSignal;
203
+ /** Per-adapter locate/parse failure (the scan continues). */
204
+ onError?: (error: unknown, ref?: SessionRef) => void;
205
+ }
206
+ interface ScannedSession {
207
+ adapter: HarnessTraceAdapter;
208
+ ref: SessionRef;
209
+ /** Non-empty parsed spans for the session. */
210
+ spans: OtlpSpan[];
211
+ }
212
+ /** Yield every non-empty session across the selected adapters. Locate/parse
213
+ * errors route to `onError` and skip that adapter/session, never aborting. */
214
+ declare function scanSessions(opts: ScanOptions): AsyncGenerator<ScannedSession>;
137
215
 
138
216
  /**
139
217
  * Claude Code adapter — `~/.claude/projects/<encoded-cwd>/<session>.jsonl`.
@@ -537,22 +615,9 @@ declare function redactSpans(spans: readonly OtlpSpan[], rules?: RedactionRule[]
537
615
  * Redaction is ON by default — the safe posture when traces leave the box.
538
616
  */
539
617
 
540
- interface CollectOptions {
541
- /** Harness ids/aliases. Omit (or `all: true`) → every harness. */
542
- harnesses?: string[];
543
- all?: boolean;
544
- /** Collect from these adapters instead of the built-in registry. */
545
- adapters?: HarnessTraceAdapter[];
546
- /** Filter by working directory (exact/prefix). */
547
- cwd?: string;
548
- /** Only sessions modified at/after this epoch ms. */
549
- sinceMs?: number;
550
- /** Cap to the most-recent N sessions per harness. */
551
- last?: number;
618
+ interface CollectOptions extends ScanOptions {
552
619
  /** Redact PII/secrets (default true). Set false to get raw spans. */
553
620
  redact?: boolean;
554
- /** Called on a per-adapter locate/parse failure (collection continues). */
555
- onError?: (error: unknown, ref?: SessionRef) => void;
556
621
  }
557
622
  interface SessionBatch {
558
623
  ref: SessionRef;
@@ -617,16 +682,9 @@ interface UploadPlan {
617
682
  state: UploadState;
618
683
  sinceMs?: number;
619
684
  }
620
- interface PlanOptions {
621
- harness?: string;
622
- all?: boolean;
623
- cwd?: string;
624
- sinceMs?: number;
625
- /** Called on a per-adapter locate/parse failure (planning continues). */
626
- onError?: (error: unknown, ref?: SessionRef) => void;
627
- }
685
+ type PlanOptions = ScanOptions;
628
686
  /** Read-only: select sessions in the window, redact, and mark which are new. */
629
- declare function planUpload(opts: PlanOptions): Promise<UploadPlan>;
687
+ declare function planUpload(opts?: PlanOptions): Promise<UploadPlan>;
630
688
  /** Map redacted OtlpSpan[] → the hosted TraceSpanEvent[] wire shape, attaching
631
689
  * session metadata to the root span(s). */
632
690
  declare function toTraceSpanEvents(spans: readonly OtlpSpan[], rootMeta: Record<string, string | number | boolean>): TraceSpanEvent[];
@@ -656,4 +714,4 @@ interface ExecuteOptions {
656
714
  /** Send the NEW items (or, on dryRun, write the redacted OTLP that would send). */
657
715
  declare function executeUpload(plan: UploadPlan, opts?: ExecuteOptions): Promise<UploadResult>;
658
716
 
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 };
717
+ export { ATTR, AmpAdapter, type AnalyzeOptions, type AnalyzeResult, CODING_REDACTION_RULES, ClaudeAdapter, CodexAdapter, type CollectOptions, CopilotAdapter, DEFAULT_HARNESS, type ExecuteOptions, 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 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, collectSessions, executeUpload, knownHarnesses, listAdapters, loadState, parseIsoToEpochMs, parseSince, planUpload, redactSpans, resolveAdapter, runPipelines, saveState, scanSessions, selectAdapters, serializeSpans, sessionHash, span, toRuntimeStore, toTraceSpanEvents, uploadKey, watchSessions, writeOtlpFile };
package/dist/index.js CHANGED
@@ -1,12 +1,15 @@
1
1
  import {
2
+ ATTR,
2
3
  AmpAdapter,
3
4
  CODING_REDACTION_RULES,
4
5
  ClaudeAdapter,
5
6
  CodexAdapter,
6
7
  CopilotAdapter,
8
+ DEFAULT_HARNESS,
7
9
  FactoryAdapter,
8
10
  ForgeAdapter,
9
11
  GeminiAdapter,
12
+ INGEST_SOURCE_CLI,
10
13
  OpencodeAdapter,
11
14
  PiAdapter,
12
15
  QwenAdapter,
@@ -17,11 +20,15 @@ import {
17
20
  knownHarnesses,
18
21
  listAdapters,
19
22
  loadState,
23
+ parseIsoToEpochMs,
24
+ parseSince,
20
25
  planUpload,
21
26
  redactSpans,
22
27
  resolveAdapter,
23
28
  runPipelines,
24
29
  saveState,
30
+ scanSessions,
31
+ selectAdapters,
25
32
  serializeSpans,
26
33
  sessionHash,
27
34
  span,
@@ -30,45 +37,17 @@ import {
30
37
  uploadKey,
31
38
  watchSessions,
32
39
  writeOtlpFile
33
- } from "./chunk-YYVPAOOD.js";
40
+ } from "./chunk-6NILXLUI.js";
34
41
 
35
42
  // src/collect.ts
36
- function adaptersFor(opts) {
37
- if (opts.adapters && opts.adapters.length > 0) return opts.adapters;
38
- if (opts.all || !opts.harnesses || opts.harnesses.length === 0) return [...listAdapters()];
39
- const out = [];
40
- for (const h of opts.harnesses) {
41
- const a = resolveAdapter(h);
42
- if (a && !out.includes(a)) out.push(a);
43
- }
44
- return out;
45
- }
46
43
  async function collectSessions(opts = {}) {
47
44
  const out = [];
48
- for (const adapter of adaptersFor(opts)) {
49
- let refs;
50
- try {
51
- refs = await adapter.locate({ cwd: opts.cwd, sinceMs: opts.sinceMs });
52
- } catch (err) {
53
- opts.onError?.(err);
54
- continue;
55
- }
56
- if (opts.last && opts.last > 0) refs = refs.slice(0, opts.last);
57
- for (const ref of refs) {
58
- let spans;
59
- try {
60
- spans = await adapter.parse(ref);
61
- } catch (err) {
62
- opts.onError?.(err, ref);
63
- continue;
64
- }
65
- if (spans.length === 0) continue;
66
- if (opts.redact === false) {
67
- out.push({ ref, spans });
68
- } else {
69
- const r = redactSpans(spans);
70
- out.push({ ref, spans: r.spans, redaction: r.report });
71
- }
45
+ for await (const { ref, spans } of scanSessions(opts)) {
46
+ if (opts.redact === false) {
47
+ out.push({ ref, spans });
48
+ } else {
49
+ const r = redactSpans(spans);
50
+ out.push({ ref, spans: r.spans, redaction: r.report });
72
51
  }
73
52
  }
74
53
  return out;
@@ -83,16 +62,19 @@ import {
83
62
  import { createHostedClient, hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
84
63
  import { DEFAULT_REDACTION_RULES, redactString, redactValue } from "@tangle-network/agent-eval/traces";
85
64
  export {
65
+ ATTR,
86
66
  AmpAdapter,
87
67
  AnalystRegistry,
88
68
  CODING_REDACTION_RULES,
89
69
  ClaudeAdapter,
90
70
  CodexAdapter,
91
71
  CopilotAdapter,
72
+ DEFAULT_HARNESS,
92
73
  DEFAULT_REDACTION_RULES,
93
74
  FactoryAdapter,
94
75
  ForgeAdapter,
95
76
  GeminiAdapter,
77
+ INGEST_SOURCE_CLI,
96
78
  OpencodeAdapter,
97
79
  PiAdapter,
98
80
  QwenAdapter,
@@ -108,6 +90,8 @@ export {
108
90
  listAdapters,
109
91
  loadState,
110
92
  makeFinding,
93
+ parseIsoToEpochMs,
94
+ parseSince,
111
95
  planUpload,
112
96
  redactSpans,
113
97
  redactString,
@@ -115,6 +99,8 @@ export {
115
99
  resolveAdapter,
116
100
  runPipelines,
117
101
  saveState,
102
+ scanSessions,
103
+ selectAdapters,
118
104
  serializeSpans,
119
105
  sessionHash,
120
106
  span,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/traces",
3
- "version": "0.2.1",
3
+ "version": "0.3.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",