@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.
- package/dist/{chunk-YYVPAOOD.js → chunk-6NILXLUI.js} +146 -110
- package/dist/cli.js +11 -22
- package/dist/index.d.ts +82 -24
- package/dist/index.js +21 -35
- package/package.json +1 -1
|
@@ -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:
|
|
101
|
-
endedAt:
|
|
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(
|
|
1145
|
-
return new Date(
|
|
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 = (
|
|
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
|
-
},
|
|
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
|
|
1624
|
-
|
|
1625
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1633
|
-
|
|
1634
|
-
let report;
|
|
1678
|
+
sessionCount += 1;
|
|
1679
|
+
if (opts.onReport) {
|
|
1635
1680
|
try {
|
|
1636
|
-
|
|
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
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
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
|
|
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
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
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[
|
|
1854
|
+
if (item.ref.cwd) meta[ATTR.CWD] = item.ref.cwd;
|
|
1822
1855
|
const branch = await gitBranch(item.ref.cwd);
|
|
1823
|
-
if (branch) meta[
|
|
1856
|
+
if (branch) meta[ATTR.GIT_BRANCH] = branch;
|
|
1824
1857
|
return meta;
|
|
1825
1858
|
}
|
|
1826
|
-
|
|
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
|
-
|
|
6
|
+
parseSince,
|
|
7
7
|
planUpload,
|
|
8
8
|
resolveAdapter,
|
|
9
9
|
runPipelines,
|
|
10
|
+
selectAdapters,
|
|
10
11
|
watchSessions,
|
|
11
12
|
writeOtlpFile
|
|
12
|
-
} from "./chunk-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
|
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-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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.
|
|
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",
|