@tangle-network/traces 0.8.17 → 0.8.18
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 +35 -4
- package/dist/{chunk-QM4QAG3V.js → chunk-G3OBZSWY.js} +421 -73
- package/dist/cli.js +123 -5
- package/dist/index.d.ts +114 -1
- package/dist/index.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ It reads the transcripts your harness leaves on disk, reconstructs the run as sp
|
|
|
17
17
|
- [What it finds](#what-it-finds)
|
|
18
18
|
- [Supported harnesses](#supported-harnesses)
|
|
19
19
|
- [CLI reference](#cli-reference)
|
|
20
|
+
- [Live stream](#live-stream)
|
|
20
21
|
- [Improvement engine](#improvement-engine)
|
|
21
22
|
- [Session index](#session-index)
|
|
22
23
|
- [Policy-mining evidence](#policy-mining-evidence)
|
|
@@ -44,6 +45,8 @@ Requires Node ≥ 22.
|
|
|
44
45
|
```bash
|
|
45
46
|
traces analyze --harness claude-code --last 1
|
|
46
47
|
traces improve --harness claude-code --last 5 --dir .traces/improvement
|
|
48
|
+
traces watch --all
|
|
49
|
+
traces stream --all --no-spans
|
|
47
50
|
```
|
|
48
51
|
|
|
49
52
|
That's the command in the demo above. The **deterministic pass** — stuck loops, token growth, output decay, missing self-verification, tool monoculture — needs no API key and costs nothing.
|
|
@@ -100,7 +103,9 @@ traces index --all --since 24h --out session-index.json
|
|
|
100
103
|
traces inspect session-index.json --out inspection-report.md
|
|
101
104
|
traces evidence --harness codex --last 20 --out policy-evidence.jsonl
|
|
102
105
|
traces export policy-evidence.jsonl --out spans.openinference.jsonl
|
|
103
|
-
traces watch --all # live observer;
|
|
106
|
+
traces watch --all # live observer; loops + semantic findings
|
|
107
|
+
traces stream --all --no-spans # live JSONL events for dashboards/visualizers
|
|
108
|
+
traces stream spans.openinference.jsonl --format openinference --no-spans
|
|
104
109
|
traces upload --since 1h --dry-run # redact + dedup + preview, no network
|
|
105
110
|
traces upload --since 24h # upload last day to the Intelligence Platform
|
|
106
111
|
```
|
|
@@ -116,14 +121,34 @@ traces upload --since 24h # upload last day to the Inte
|
|
|
116
121
|
| `--out <path>` | Write the report to a file |
|
|
117
122
|
| `--dir <path>` | `improve`: write the full artifact pack to this directory |
|
|
118
123
|
| `--otlp <path>` | OTLP artifact path (also evidence provenance / dry-run upload preview) |
|
|
119
|
-
| `--format <kind>` | `export`: `auto`, `policy-evidence`, `sandbox-events`, or `openinference` |
|
|
124
|
+
| `--format <kind>` | `export` / file `stream`: `auto`, `policy-evidence`, `sandbox-events`, or `openinference` |
|
|
120
125
|
| `--llm` / `--budget <usd>` | Enable agentic analysts (needs `OPENAI_API_KEY`) / cap their spend |
|
|
121
126
|
| `--config <path>` | `investigate` / `improve`: load BYO analysts, external analyzers, and proposal adapters |
|
|
122
|
-
| `--interval <s>` / `--window <m>` | `watch`: poll seconds (default 5) / active-session window minutes (default 30) |
|
|
127
|
+
| `--interval <s>` / `--window <m>` | `watch` / live `stream`: poll seconds (default 5) / active-session window minutes (default 30) |
|
|
123
128
|
| `--min-loop <n>` | Identical repeated calls before flagging a loop (default 3) |
|
|
129
|
+
| `--replay` / `--once` | `stream`: scan once, then exit |
|
|
130
|
+
| `--no-spans` / `--no-findings` | `stream`: suppress raw span rows / finding rows |
|
|
124
131
|
| `--no-content` | `upload`: send metadata only — strip all prompt/response text |
|
|
125
132
|
| `--dry-run` / `--yes` | `upload`: preview without sending / skip the confirm prompt |
|
|
126
133
|
|
|
134
|
+
## Live stream
|
|
135
|
+
|
|
136
|
+
`traces watch` is the human terminal view.
|
|
137
|
+
It prints repeated-tool loops and semantic findings while a coding agent is still running.
|
|
138
|
+
|
|
139
|
+
`traces stream` is the machine feed.
|
|
140
|
+
It emits newline-delimited JSON events that a dashboard, art visualizer, local watcher, or hosted product can consume without scraping terminal prose.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
traces stream --all
|
|
144
|
+
traces stream --all --no-spans
|
|
145
|
+
traces stream spans.openinference.jsonl --format openinference --no-spans
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The stream emits `session`, `span`, `analysis_batch`, `finding`, and `tick` events.
|
|
149
|
+
The semantic findings currently cover repeated failing commands, verification churn without code/config changes, completion claims without later verification, and high tool-error rates.
|
|
150
|
+
Use `--no-spans` when you want the low-volume meaning layer; keep spans on for real-time visualizers that need motion, timing, and tool-call texture.
|
|
151
|
+
|
|
127
152
|
## Improvement engine
|
|
128
153
|
|
|
129
154
|
`traces improve` turns observed sessions into a portable improvement packet:
|
|
@@ -289,6 +314,9 @@ The CLI is a thin consumer of these exports.
|
|
|
289
314
|
| `runTraceStoreInvestigation` | `({ traceStore }) → TraceStoreInvestigationResult` | run the same packet layer over a hosted/custom `TraceAnalysisStore` |
|
|
290
315
|
| `loadTracesConfig` | `(path?) → TracesConfig \| undefined` | load BYO analysts, external analyzers, and proposal adapters |
|
|
291
316
|
| `watchSessions` | `(ObserverOptions) → Promise<void>` | live observer; `onLoop` / `onReport` / `signal` / `adapters` |
|
|
317
|
+
| `streamSessions` | `(TraceStreamOptions) → Promise<void>` | live JSONL-ready event stream over active sessions |
|
|
318
|
+
| `traceStreamEventsFromSpans` | `(spans, opts?) → TraceStreamEvent[]` | replay an existing span list as stream events |
|
|
319
|
+
| `analyzeLiveBatch` | `(spans, opts?) → TraceLiveBatch` | compute semantic online findings for one batch |
|
|
292
320
|
| `collectSessionIndex` | `(ScanOptions) → TraceSessionIndex` | scan sessions and return a reusable JSON-ready catalog |
|
|
293
321
|
| `inspectSessionIndex` | `(TraceSessionIndex) → TraceInspectionReport` | rank improvement findings from an index without rescanning sessions |
|
|
294
322
|
| `buildPolicyEvidenceRecord` | `(ref, spans, opts?) → PolicyEvidenceRecord` | summarize one session for downstream policy mining |
|
|
@@ -303,12 +331,15 @@ The CLI is a thin consumer of these exports.
|
|
|
303
331
|
| `ExternalAnalyzer` / `Redactor` | `haloAnalyzer` / `commandAnalyzer` / `commandRedactor` | drive engines/models you install (not bundled) |
|
|
304
332
|
|
|
305
333
|
```ts
|
|
306
|
-
import { watchSessions, analyzeSpans, AnalystRegistry, makeFinding } from '@tangle-network/traces'
|
|
334
|
+
import { watchSessions, streamSessions, analyzeSpans, AnalystRegistry, makeFinding } from '@tangle-network/traces'
|
|
307
335
|
|
|
308
336
|
// Observe live sessions, feed findings anywhere (read-only, cancellable):
|
|
309
337
|
const c = new AbortController()
|
|
310
338
|
await watchSessions({ all: true, signal: c.signal, onLoop: (l) => alert(l.toolName, l.occurrences) })
|
|
311
339
|
|
|
340
|
+
// Feed a visualizer or dashboard:
|
|
341
|
+
await streamSessions({ all: true, signal: c.signal, includeSpans: false, onEvent: (event) => console.log(event) })
|
|
342
|
+
|
|
312
343
|
// Run your own analyst instead of the built-ins:
|
|
313
344
|
const registry = new AnalystRegistry()
|
|
314
345
|
registry.register({
|
|
@@ -751,10 +751,10 @@ var ClaudeAdapter = class {
|
|
|
751
751
|
let step = main.nextStep;
|
|
752
752
|
for (const f of files) {
|
|
753
753
|
if (!f.endsWith(".jsonl")) continue;
|
|
754
|
-
const
|
|
754
|
+
const hash2 = basename2(f, ".jsonl");
|
|
755
755
|
let meta = {};
|
|
756
756
|
try {
|
|
757
|
-
meta = JSON.parse(await readFile3(join3(subDir, `${
|
|
757
|
+
meta = JSON.parse(await readFile3(join3(subDir, `${hash2}.meta.json`), "utf8"));
|
|
758
758
|
} catch {
|
|
759
759
|
}
|
|
760
760
|
const parent = meta.toolUseId && main.toolSpanByUseId.get(meta.toolUseId)?.span_id || `root:${traceId}`;
|
|
@@ -768,7 +768,7 @@ var ClaudeAdapter = class {
|
|
|
768
768
|
traceId,
|
|
769
769
|
agent: meta.agentType ? `subagent:${meta.agentType}` : "subagent",
|
|
770
770
|
startStep: step,
|
|
771
|
-
idPrefix: `${
|
|
771
|
+
idPrefix: `${hash2}:`,
|
|
772
772
|
rootParent: parent
|
|
773
773
|
});
|
|
774
774
|
out.push(...parsed.spans);
|
|
@@ -2286,13 +2286,13 @@ function extractAbsolutePaths(spans) {
|
|
|
2286
2286
|
let scannedChars = 0;
|
|
2287
2287
|
for (const span2 of spans) {
|
|
2288
2288
|
for (const value of [span2.name, span2.status.message, ...Object.values(span2.attributes)]) {
|
|
2289
|
-
for (const
|
|
2290
|
-
scannedChars +=
|
|
2289
|
+
for (const evidence3 of pathsFromUnknown(value)) {
|
|
2290
|
+
scannedChars += evidence3.path.length;
|
|
2291
2291
|
if (scannedChars > MAX_SPAN_TEXT_CHARS) return paths;
|
|
2292
|
-
const key = `${
|
|
2292
|
+
const key = `${evidence3.source}:${evidence3.path}`;
|
|
2293
2293
|
if (seen.has(key)) continue;
|
|
2294
2294
|
seen.add(key);
|
|
2295
|
-
paths.push(
|
|
2295
|
+
paths.push(evidence3);
|
|
2296
2296
|
if (paths.length >= MAX_SPAN_PATH_CANDIDATES) return paths;
|
|
2297
2297
|
}
|
|
2298
2298
|
}
|
|
@@ -2346,8 +2346,8 @@ async function resolveSessionRepoAttrs(cwd, spans) {
|
|
|
2346
2346
|
add(await repairDashedPath(cwd), "repaired-cwd");
|
|
2347
2347
|
add(cwd, "ref-cwd");
|
|
2348
2348
|
}
|
|
2349
|
-
for (const
|
|
2350
|
-
add(await repoCandidateForPath(
|
|
2349
|
+
for (const evidence3 of extractAbsolutePaths(spans)) {
|
|
2350
|
+
add(await repoCandidateForPath(evidence3.path), evidence3.source, evidence3.weight);
|
|
2351
2351
|
}
|
|
2352
2352
|
const resolved = [];
|
|
2353
2353
|
for (const candidate of candidates.values()) {
|
|
@@ -2492,9 +2492,9 @@ async function buildPolicyEvidenceRecord(ref, spans, opts = {}) {
|
|
|
2492
2492
|
signals: {
|
|
2493
2493
|
stuckLoopCount: loopFindings.length,
|
|
2494
2494
|
affectedRunRatio: pipelines.stuckLoops.affectedRunRatio,
|
|
2495
|
-
stuckLoops: loopFindings.slice(0, loopLimit).map((
|
|
2496
|
-
toolName:
|
|
2497
|
-
occurrences:
|
|
2495
|
+
stuckLoops: loopFindings.slice(0, loopLimit).map((finding2) => ({
|
|
2496
|
+
toolName: finding2.toolName,
|
|
2497
|
+
occurrences: finding2.occurrences
|
|
2498
2498
|
})),
|
|
2499
2499
|
stuckLoopsOmitted: Math.max(0, loopFindings.length - loopLimit),
|
|
2500
2500
|
toolErrorRate: toolSpans.length === 0 ? 0 : erroredToolCallCount / toolSpans.length
|
|
@@ -3235,12 +3235,12 @@ function topLoopTools(row) {
|
|
|
3235
3235
|
current.maxOccurrences = Math.max(current.maxOccurrences, loop.occurrences);
|
|
3236
3236
|
byTool.set(loop.toolName, current);
|
|
3237
3237
|
}
|
|
3238
|
-
const loops = [...byTool.entries()].sort(([, a], [, b]) => b.groups - a.groups || b.maxOccurrences - a.maxOccurrences).slice(0, 3).map(([
|
|
3238
|
+
const loops = [...byTool.entries()].sort(([, a], [, b]) => b.groups - a.groups || b.maxOccurrences - a.maxOccurrences).slice(0, 3).map(([toolName3, stats]) => `${toolName3} ${stats.groups} group(s), max x${stats.maxOccurrences}`);
|
|
3239
3239
|
const suffix = row.signals.stuckLoopsOmitted > 0 ? `, +${row.signals.stuckLoopsOmitted} omitted` : "";
|
|
3240
3240
|
return loops.length > 0 ? `${loops.join(", ")}${suffix}` : `${row.signals.stuckLoopCount} loop(s)`;
|
|
3241
3241
|
}
|
|
3242
3242
|
function sortFindings(findings) {
|
|
3243
|
-
return findings.sort((a, b) => severityRank[a.severity] - severityRank[b.severity] || b.impact - a.impact || a.id.localeCompare(b.id)).map((
|
|
3243
|
+
return findings.sort((a, b) => severityRank[a.severity] - severityRank[b.severity] || b.impact - a.impact || a.id.localeCompare(b.id)).map((finding2) => ({ ...finding2 }));
|
|
3244
3244
|
}
|
|
3245
3245
|
function contextSourceCounts(index) {
|
|
3246
3246
|
return { contextFiles: allContextFiles(index).length };
|
|
@@ -3435,9 +3435,9 @@ function inspectSessionIndex(index, opts = {}) {
|
|
|
3435
3435
|
},
|
|
3436
3436
|
totals: {
|
|
3437
3437
|
findings: ranked.length,
|
|
3438
|
-
high: ranked.filter((
|
|
3439
|
-
medium: ranked.filter((
|
|
3440
|
-
low: ranked.filter((
|
|
3438
|
+
high: ranked.filter((finding2) => finding2.severity === "high").length,
|
|
3439
|
+
medium: ranked.filter((finding2) => finding2.severity === "medium").length,
|
|
3440
|
+
low: ranked.filter((finding2) => finding2.severity === "low").length
|
|
3441
3441
|
},
|
|
3442
3442
|
findings: ranked
|
|
3443
3443
|
};
|
|
@@ -3456,16 +3456,16 @@ function renderInspectionReport(report) {
|
|
|
3456
3456
|
return `${lines.join("\n")}
|
|
3457
3457
|
`;
|
|
3458
3458
|
}
|
|
3459
|
-
report.findings.forEach((
|
|
3459
|
+
report.findings.forEach((finding2, i) => {
|
|
3460
3460
|
lines.push("");
|
|
3461
|
-
lines.push(`## ${i + 1}. [${
|
|
3462
|
-
lines.push(`Area: ${
|
|
3461
|
+
lines.push(`## ${i + 1}. [${finding2.severity}] ${finding2.title}`);
|
|
3462
|
+
lines.push(`Area: ${finding2.area} Impact: ${finding2.impact}`);
|
|
3463
3463
|
lines.push("Evidence:");
|
|
3464
|
-
for (const item of
|
|
3465
|
-
lines.push(`Next: ${
|
|
3466
|
-
if (
|
|
3464
|
+
for (const item of finding2.evidence) lines.push(`- ${item}`);
|
|
3465
|
+
lines.push(`Next: ${finding2.next}`);
|
|
3466
|
+
if (finding2.refs && finding2.refs.length > 0) {
|
|
3467
3467
|
lines.push("Refs:");
|
|
3468
|
-
for (const ref of
|
|
3468
|
+
for (const ref of finding2.refs.slice(0, 5)) lines.push(`- ${ref}`);
|
|
3469
3469
|
}
|
|
3470
3470
|
});
|
|
3471
3471
|
return `${lines.join("\n")}
|
|
@@ -3926,55 +3926,55 @@ function normalizeExternalFindings(results) {
|
|
|
3926
3926
|
for (const result of results) {
|
|
3927
3927
|
if (result.findings) out.push(...result.findings);
|
|
3928
3928
|
externalRows(result.output).forEach((row, index) => {
|
|
3929
|
-
const
|
|
3930
|
-
if (
|
|
3929
|
+
const finding2 = coerceExternalFinding(result.analyzer, row, index);
|
|
3930
|
+
if (finding2) out.push(finding2);
|
|
3931
3931
|
});
|
|
3932
3932
|
}
|
|
3933
3933
|
return out;
|
|
3934
3934
|
}
|
|
3935
|
-
function sourceOf(
|
|
3936
|
-
if (
|
|
3937
|
-
if (
|
|
3935
|
+
function sourceOf(finding2) {
|
|
3936
|
+
if (finding2.analyst_id.startsWith("external:")) return "external";
|
|
3937
|
+
if (finding2.analyst_id === "traces-deterministic") return "deterministic";
|
|
3938
3938
|
return "analyst";
|
|
3939
3939
|
}
|
|
3940
|
-
function recommendationTitle(
|
|
3941
|
-
if (
|
|
3942
|
-
return `Investigate ${
|
|
3940
|
+
function recommendationTitle(finding2) {
|
|
3941
|
+
if (finding2.recommended_action) return finding2.recommended_action.replace(/\.$/, "");
|
|
3942
|
+
return `Investigate ${finding2.area}: ${finding2.claim}`.replace(/\.$/, "");
|
|
3943
3943
|
}
|
|
3944
3944
|
function buildRecommendations(findings) {
|
|
3945
|
-
return [...findings].filter((
|
|
3945
|
+
return [...findings].filter((finding2) => finding2.severity !== "info").sort((a, b) => severityRank2[a.severity] - severityRank2[b.severity] || b.confidence - a.confidence || a.finding_id.localeCompare(b.finding_id)).slice(0, 20).map((finding2, index) => ({
|
|
3946
3946
|
schemaVersion: 1,
|
|
3947
|
-
id: `rec-${index + 1}-${slug(
|
|
3948
|
-
severity:
|
|
3949
|
-
title: recommendationTitle(
|
|
3950
|
-
action:
|
|
3951
|
-
rationale:
|
|
3952
|
-
evidenceRefs:
|
|
3953
|
-
findingIds: [
|
|
3954
|
-
validationPlan:
|
|
3955
|
-
source: sourceOf(
|
|
3947
|
+
id: `rec-${index + 1}-${slug(finding2.area)}`,
|
|
3948
|
+
severity: finding2.severity,
|
|
3949
|
+
title: recommendationTitle(finding2),
|
|
3950
|
+
action: finding2.recommended_action ?? `Review and fix: ${finding2.claim}`,
|
|
3951
|
+
rationale: finding2.rationale ?? finding2.claim,
|
|
3952
|
+
evidenceRefs: finding2.evidence_refs,
|
|
3953
|
+
findingIds: [finding2.finding_id],
|
|
3954
|
+
validationPlan: finding2.validation_plan ?? "Rerun `traces improve` on a fresh comparable session window and confirm this finding disappears or drops in severity.",
|
|
3955
|
+
source: sourceOf(finding2)
|
|
3956
3956
|
}));
|
|
3957
3957
|
}
|
|
3958
3958
|
function buildClaims(findings) {
|
|
3959
|
-
return findings.map((
|
|
3959
|
+
return findings.map((finding2, index) => ({
|
|
3960
3960
|
schemaVersion: 1,
|
|
3961
|
-
id: `claim-${index + 1}-${slug(
|
|
3962
|
-
severity:
|
|
3963
|
-
text:
|
|
3964
|
-
area:
|
|
3965
|
-
sourceFindingId:
|
|
3966
|
-
evidenceRefs:
|
|
3967
|
-
validationPlan:
|
|
3961
|
+
id: `claim-${index + 1}-${slug(finding2.area)}`,
|
|
3962
|
+
severity: finding2.severity,
|
|
3963
|
+
text: finding2.claim,
|
|
3964
|
+
area: finding2.area,
|
|
3965
|
+
sourceFindingId: finding2.finding_id,
|
|
3966
|
+
evidenceRefs: finding2.evidence_refs,
|
|
3967
|
+
validationPlan: finding2.validation_plan
|
|
3968
3968
|
}));
|
|
3969
3969
|
}
|
|
3970
3970
|
function buildEvidenceRows(findings) {
|
|
3971
|
-
return findings.flatMap((
|
|
3971
|
+
return findings.flatMap((finding2) => finding2.evidence_refs.map((ref) => ({
|
|
3972
3972
|
schemaVersion: 1,
|
|
3973
3973
|
kind: "traces.improvement_evidence",
|
|
3974
|
-
findingId:
|
|
3975
|
-
severity:
|
|
3976
|
-
area:
|
|
3977
|
-
claim:
|
|
3974
|
+
findingId: finding2.finding_id,
|
|
3975
|
+
severity: finding2.severity,
|
|
3976
|
+
area: finding2.area,
|
|
3977
|
+
claim: finding2.claim,
|
|
3978
3978
|
evidence: ref
|
|
3979
3979
|
})));
|
|
3980
3980
|
}
|
|
@@ -4006,14 +4006,14 @@ function renderFindingPacket(packet, title = "Trace finding packet") {
|
|
|
4006
4006
|
if (packet.findings.length > 0) {
|
|
4007
4007
|
lines.push("## findings");
|
|
4008
4008
|
lines.push("");
|
|
4009
|
-
for (const
|
|
4010
|
-
lines.push(`### ${
|
|
4009
|
+
for (const finding2 of [...packet.findings].sort((a, b) => severityRank2[a.severity] - severityRank2[b.severity] || a.area.localeCompare(b.area) || a.claim.localeCompare(b.claim))) {
|
|
4010
|
+
lines.push(`### ${finding2.severity.toUpperCase()} \u2014 ${finding2.claim}`);
|
|
4011
4011
|
lines.push("");
|
|
4012
|
-
lines.push(`- **Area:** ${
|
|
4013
|
-
lines.push(`- **Analyst:** ${
|
|
4014
|
-
if (
|
|
4015
|
-
if (
|
|
4016
|
-
for (const ref of
|
|
4012
|
+
lines.push(`- **Area:** ${finding2.area}`);
|
|
4013
|
+
lines.push(`- **Analyst:** ${finding2.analyst_id}`);
|
|
4014
|
+
if (finding2.recommended_action) lines.push(`- **Action:** ${finding2.recommended_action}`);
|
|
4015
|
+
if (finding2.validation_plan) lines.push(`- **Check:** ${finding2.validation_plan}`);
|
|
4016
|
+
for (const ref of finding2.evidence_refs.slice(0, 3)) {
|
|
4017
4017
|
lines.push(`- **Evidence:** ${ref.kind} ${ref.uri}${ref.excerpt ? ` \u2014 \`${ref.excerpt.slice(0, 180)}\`` : ""}`);
|
|
4018
4018
|
}
|
|
4019
4019
|
lines.push("");
|
|
@@ -4242,8 +4242,330 @@ async function saveReport(path, report) {
|
|
|
4242
4242
|
await writeFile4(path, report, "utf8");
|
|
4243
4243
|
}
|
|
4244
4244
|
|
|
4245
|
-
// src/
|
|
4245
|
+
// src/live.ts
|
|
4246
|
+
import { createHash as createHash2 } from "crypto";
|
|
4247
|
+
var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
|
|
4248
|
+
var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
|
|
4249
|
+
var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
|
|
4246
4250
|
var sleep = (ms, signal) => new Promise((resolve2) => {
|
|
4251
|
+
if (signal?.aborted) {
|
|
4252
|
+
resolve2();
|
|
4253
|
+
return;
|
|
4254
|
+
}
|
|
4255
|
+
const onAbort = () => {
|
|
4256
|
+
clearTimeout(t);
|
|
4257
|
+
resolve2();
|
|
4258
|
+
};
|
|
4259
|
+
const t = setTimeout(() => {
|
|
4260
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4261
|
+
resolve2();
|
|
4262
|
+
}, ms);
|
|
4263
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4264
|
+
});
|
|
4265
|
+
function hash(parts) {
|
|
4266
|
+
return createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
4267
|
+
}
|
|
4268
|
+
function asString(value) {
|
|
4269
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
4270
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
4271
|
+
return void 0;
|
|
4272
|
+
}
|
|
4273
|
+
function spanKind2(span2) {
|
|
4274
|
+
return asString(span2.attributes["openinference.span.kind"])?.toUpperCase() ?? "SPAN";
|
|
4275
|
+
}
|
|
4276
|
+
function spanContent(span2) {
|
|
4277
|
+
return asString(span2.attributes.content) ?? asString(span2.attributes["input.value"]) ?? asString(span2.attributes["llm.input"]) ?? asString(span2.attributes["gen_ai.prompt"]) ?? "";
|
|
4278
|
+
}
|
|
4279
|
+
function toolName2(span2) {
|
|
4280
|
+
return asString(span2.attributes["tool.name"]) ?? span2.name.replace(/^tool\./, "");
|
|
4281
|
+
}
|
|
4282
|
+
function toolSignature(span2) {
|
|
4283
|
+
const content = spanContent(span2).replace(/\s+/g, " ").trim();
|
|
4284
|
+
return `${toolName2(span2)}:${content || span2.name}`;
|
|
4285
|
+
}
|
|
4286
|
+
function isTool(span2) {
|
|
4287
|
+
return spanKind2(span2) === "TOOL" || span2.attributes["tool.name"] != null || span2.name.startsWith("tool.");
|
|
4288
|
+
}
|
|
4289
|
+
function isTextSpan(span2) {
|
|
4290
|
+
return !isTool(span2) && spanContent(span2).length > 0;
|
|
4291
|
+
}
|
|
4292
|
+
function isVerification(span2) {
|
|
4293
|
+
if (!isTool(span2)) return false;
|
|
4294
|
+
return VERIFY_RE.test(`${toolName2(span2)} ${span2.name} ${spanContent(span2)}`);
|
|
4295
|
+
}
|
|
4296
|
+
function isChange(span2) {
|
|
4297
|
+
if (!isTool(span2)) return false;
|
|
4298
|
+
return CHANGE_RE.test(`${toolName2(span2)} ${span2.name} ${spanContent(span2)}`);
|
|
4299
|
+
}
|
|
4300
|
+
function timeOf(span2) {
|
|
4301
|
+
const parsed = Date.parse(span2.start_time);
|
|
4302
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
4303
|
+
}
|
|
4304
|
+
function orderSpans(spans) {
|
|
4305
|
+
return [...spans].sort((a, b) => {
|
|
4306
|
+
const stepA = typeof a.attributes.step === "number" ? a.attributes.step : void 0;
|
|
4307
|
+
const stepB = typeof b.attributes.step === "number" ? b.attributes.step : void 0;
|
|
4308
|
+
if (stepA != null && stepB != null && stepA !== stepB) return stepA - stepB;
|
|
4309
|
+
return timeOf(a) - timeOf(b);
|
|
4310
|
+
});
|
|
4311
|
+
}
|
|
4312
|
+
function sessionFrom(spans, opts) {
|
|
4313
|
+
const first = spans[0];
|
|
4314
|
+
return {
|
|
4315
|
+
harness: opts.session?.harness ?? asString(first?.attributes["service.name"]) ?? "unknown",
|
|
4316
|
+
sessionId: opts.session?.sessionId ?? first?.trace_id ?? "unknown",
|
|
4317
|
+
cwd: opts.session?.cwd ?? asString(first?.attributes["tangle.cwd"]) ?? null,
|
|
4318
|
+
...opts.session?.path ? { path: opts.session.path } : {}
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
function evidence2(kind, label, value, spanIds) {
|
|
4322
|
+
return { kind, label, value, ...spanIds && spanIds.length > 0 ? { spanIds } : {} };
|
|
4323
|
+
}
|
|
4324
|
+
function finding(input) {
|
|
4325
|
+
const fingerprint = hash([input.ruleId, ...input.signature]);
|
|
4326
|
+
return {
|
|
4327
|
+
schemaVersion: 1,
|
|
4328
|
+
kind: "traces.live_finding",
|
|
4329
|
+
id: `live.${input.ruleId}.${fingerprint}`,
|
|
4330
|
+
fingerprint,
|
|
4331
|
+
ruleId: input.ruleId,
|
|
4332
|
+
severity: input.severity,
|
|
4333
|
+
title: input.title,
|
|
4334
|
+
claim: input.claim,
|
|
4335
|
+
action: input.action,
|
|
4336
|
+
check: input.check,
|
|
4337
|
+
evidence: input.evidence,
|
|
4338
|
+
session: input.session,
|
|
4339
|
+
observedAt: input.observedAt
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
function analyzeLiveBatch(spans, opts = {}) {
|
|
4343
|
+
const ordered2 = orderSpans(spans);
|
|
4344
|
+
const generatedAt = opts.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4345
|
+
const session = sessionFrom(ordered2, opts);
|
|
4346
|
+
const tools = ordered2.filter(isTool);
|
|
4347
|
+
const erroredTools = tools.filter((span2) => span2.status.code === "ERROR");
|
|
4348
|
+
const verificationCalls = tools.filter(isVerification);
|
|
4349
|
+
const changeCalls = tools.filter(isChange);
|
|
4350
|
+
const findings = [];
|
|
4351
|
+
const failedBySignature = /* @__PURE__ */ new Map();
|
|
4352
|
+
for (const span2 of erroredTools) {
|
|
4353
|
+
const signature = toolSignature(span2);
|
|
4354
|
+
failedBySignature.set(signature, [...failedBySignature.get(signature) ?? [], span2]);
|
|
4355
|
+
}
|
|
4356
|
+
for (const [signature, rows] of failedBySignature) {
|
|
4357
|
+
if (rows.length < 2) continue;
|
|
4358
|
+
findings.push(finding({
|
|
4359
|
+
ruleId: "same-failing-command",
|
|
4360
|
+
severity: rows.length >= 3 ? "high" : "medium",
|
|
4361
|
+
title: "Same failing command is repeating",
|
|
4362
|
+
claim: `${rows.length} failed tool call(s) repeated the same command or arguments.`,
|
|
4363
|
+
action: "Stop rerunning it; inspect the first failure, change state, or choose a different diagnostic path before another retry.",
|
|
4364
|
+
check: "The next action should read the failing file, inspect the stack/output, or edit code before this command runs again.",
|
|
4365
|
+
evidence: [
|
|
4366
|
+
evidence2("pattern", "command", signature, rows.map((span2) => span2.span_id)),
|
|
4367
|
+
evidence2("metric", "failed_repeats", String(rows.length))
|
|
4368
|
+
],
|
|
4369
|
+
session,
|
|
4370
|
+
observedAt: generatedAt,
|
|
4371
|
+
signature: [signature, String(rows.length)]
|
|
4372
|
+
}));
|
|
4373
|
+
}
|
|
4374
|
+
let lastChangeIndex = -1;
|
|
4375
|
+
let verificationSinceChange = 0;
|
|
4376
|
+
const verificationSpanIds = [];
|
|
4377
|
+
for (let i = 0; i < ordered2.length; i++) {
|
|
4378
|
+
const span2 = ordered2[i];
|
|
4379
|
+
if (isChange(span2)) {
|
|
4380
|
+
lastChangeIndex = i;
|
|
4381
|
+
verificationSinceChange = 0;
|
|
4382
|
+
verificationSpanIds.length = 0;
|
|
4383
|
+
} else if (isVerification(span2)) {
|
|
4384
|
+
verificationSinceChange += 1;
|
|
4385
|
+
verificationSpanIds.push(span2.span_id);
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
if (verificationSinceChange >= 2) {
|
|
4389
|
+
findings.push(finding({
|
|
4390
|
+
ruleId: "verification-without-change",
|
|
4391
|
+
severity: verificationSinceChange >= 3 ? "high" : "medium",
|
|
4392
|
+
title: "Verification is repeating without a state change",
|
|
4393
|
+
claim: `${verificationSinceChange} verification command(s) ran after the last detected code/config change.`,
|
|
4394
|
+
action: "Do not spend another verification run until the agent either edits state or explains what new signal the rerun will reveal.",
|
|
4395
|
+
check: "The next action should be a code/config edit, targeted file read, or a different narrow diagnostic command.",
|
|
4396
|
+
evidence: [
|
|
4397
|
+
evidence2("metric", "verification_calls_since_change", String(verificationSinceChange), verificationSpanIds),
|
|
4398
|
+
evidence2("metric", "change_calls", String(changeCalls.length))
|
|
4399
|
+
],
|
|
4400
|
+
session,
|
|
4401
|
+
observedAt: generatedAt,
|
|
4402
|
+
signature: [String(verificationSinceChange), verificationSpanIds.join(",")]
|
|
4403
|
+
}));
|
|
4404
|
+
}
|
|
4405
|
+
const lastClaim = [...ordered2].reverse().find((span2) => isTextSpan(span2) && CLAIM_DONE_RE.test(spanContent(span2)));
|
|
4406
|
+
if (lastClaim) {
|
|
4407
|
+
const claimTime = timeOf(lastClaim);
|
|
4408
|
+
const verificationAfterClaim = verificationCalls.some((span2) => timeOf(span2) > claimTime);
|
|
4409
|
+
if (!verificationAfterClaim) {
|
|
4410
|
+
findings.push(finding({
|
|
4411
|
+
ruleId: "completion-claim-without-verification",
|
|
4412
|
+
severity: "medium",
|
|
4413
|
+
title: "Completion claim has no later verification",
|
|
4414
|
+
claim: "The agent claimed completion, correctness, or verification without a later verification tool call in the observed trace.",
|
|
4415
|
+
action: "Require a concrete check after the claim, or downgrade the claim to an unverified hypothesis.",
|
|
4416
|
+
check: "A later test/build/typecheck/lint/smoke command should appear after the claim.",
|
|
4417
|
+
evidence: [
|
|
4418
|
+
evidence2("span", "claim_span", spanContent(lastClaim).slice(0, 240), [lastClaim.span_id]),
|
|
4419
|
+
evidence2("metric", "verification_after_claim", "0")
|
|
4420
|
+
],
|
|
4421
|
+
session,
|
|
4422
|
+
observedAt: generatedAt,
|
|
4423
|
+
signature: [lastClaim.span_id, spanContent(lastClaim).slice(0, 120)]
|
|
4424
|
+
}));
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
if (tools.length >= 4 && erroredTools.length / tools.length >= 0.5) {
|
|
4428
|
+
findings.push(finding({
|
|
4429
|
+
ruleId: "high-tool-error-rate",
|
|
4430
|
+
severity: erroredTools.length / tools.length >= 0.75 ? "high" : "medium",
|
|
4431
|
+
title: "Tool calls are mostly failing",
|
|
4432
|
+
claim: `${erroredTools.length}/${tools.length} tool call(s) in this live batch ended with ERROR.`,
|
|
4433
|
+
action: "Switch from broad execution to diagnosis: classify the dominant error, then run one targeted command to validate the fix path.",
|
|
4434
|
+
check: "The next batch should reduce the error ratio or show a new targeted diagnostic result.",
|
|
4435
|
+
evidence: [
|
|
4436
|
+
evidence2("metric", "errored_tool_calls", `${erroredTools.length}/${tools.length}`, erroredTools.map((span2) => span2.span_id))
|
|
4437
|
+
],
|
|
4438
|
+
session,
|
|
4439
|
+
observedAt: generatedAt,
|
|
4440
|
+
signature: [String(erroredTools.length), String(tools.length)]
|
|
4441
|
+
}));
|
|
4442
|
+
}
|
|
4443
|
+
return {
|
|
4444
|
+
schemaVersion: 1,
|
|
4445
|
+
kind: "traces.live_batch",
|
|
4446
|
+
generatedAt,
|
|
4447
|
+
session,
|
|
4448
|
+
spanCount: ordered2.length,
|
|
4449
|
+
newSpanCount: opts.newSpanCount ?? ordered2.length,
|
|
4450
|
+
toolCallCount: tools.length,
|
|
4451
|
+
erroredToolCallCount: erroredTools.length,
|
|
4452
|
+
verificationCallCount: verificationCalls.length,
|
|
4453
|
+
changeCallCount: changeCalls.length,
|
|
4454
|
+
findingCount: findings.length,
|
|
4455
|
+
findings
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
function sessionEvent(session, spans, generatedAt) {
|
|
4459
|
+
const ordered2 = orderSpans(spans);
|
|
4460
|
+
return {
|
|
4461
|
+
schemaVersion: 1,
|
|
4462
|
+
kind: "traces.stream.session",
|
|
4463
|
+
event: "session",
|
|
4464
|
+
generatedAt,
|
|
4465
|
+
session,
|
|
4466
|
+
spanCount: ordered2.length,
|
|
4467
|
+
startedAt: ordered2[0]?.start_time ?? null,
|
|
4468
|
+
endedAt: ordered2.at(-1)?.end_time ?? null
|
|
4469
|
+
};
|
|
4470
|
+
}
|
|
4471
|
+
function traceStreamEventsFromSpans(spans, opts = {}) {
|
|
4472
|
+
const generatedAt = opts.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4473
|
+
const session = sessionFrom(spans, {
|
|
4474
|
+
...opts,
|
|
4475
|
+
session: opts.ref ? { harness: opts.ref.harness, sessionId: opts.ref.sessionId, cwd: opts.ref.cwd, path: opts.ref.path } : opts.session
|
|
4476
|
+
});
|
|
4477
|
+
const ordered2 = orderSpans(spans);
|
|
4478
|
+
const batch = analyzeLiveBatch(ordered2, { generatedAt, session, newSpanCount: ordered2.length });
|
|
4479
|
+
const events = [sessionEvent(session, ordered2, generatedAt)];
|
|
4480
|
+
if (opts.includeSpans !== false) {
|
|
4481
|
+
for (const span2 of ordered2) {
|
|
4482
|
+
events.push({ schemaVersion: 1, kind: "traces.stream.span", event: "span", generatedAt, session, span: span2 });
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
events.push({ schemaVersion: 1, kind: "traces.stream.batch", event: "analysis_batch", generatedAt, batch });
|
|
4486
|
+
if (opts.includeFindings !== false) {
|
|
4487
|
+
for (const liveFinding of batch.findings) {
|
|
4488
|
+
events.push({ schemaVersion: 1, kind: "traces.stream.finding", event: "finding", generatedAt, finding: liveFinding });
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
return events;
|
|
4492
|
+
}
|
|
4493
|
+
function serializeTraceStreamEvent(event) {
|
|
4494
|
+
return `${JSON.stringify(event)}
|
|
4495
|
+
`;
|
|
4496
|
+
}
|
|
4497
|
+
async function streamSessions(opts) {
|
|
4498
|
+
const intervalMs = Math.max(250, opts.intervalMs ?? 5e3);
|
|
4499
|
+
const windowMs = opts.windowMs ?? 30 * 6e4;
|
|
4500
|
+
const includeSpans = opts.includeSpans !== false;
|
|
4501
|
+
const includeFindings = opts.includeFindings !== false;
|
|
4502
|
+
const includeBatches = opts.includeBatches !== false;
|
|
4503
|
+
const seenSessions = /* @__PURE__ */ new Set();
|
|
4504
|
+
const seenSpans = /* @__PURE__ */ new Set();
|
|
4505
|
+
const seenFindings = /* @__PURE__ */ new Set();
|
|
4506
|
+
while (!opts.signal?.aborted) {
|
|
4507
|
+
let sessions = 0;
|
|
4508
|
+
let newSpans = 0;
|
|
4509
|
+
let findings = 0;
|
|
4510
|
+
for await (const { adapter, ref, spans } of scanSessions({
|
|
4511
|
+
...opts,
|
|
4512
|
+
sinceMs: opts.sinceMs ?? Date.now() - windowMs,
|
|
4513
|
+
signal: opts.signal
|
|
4514
|
+
})) {
|
|
4515
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4516
|
+
const session = { harness: adapter.harness, sessionId: ref.sessionId, cwd: ref.cwd, path: ref.path };
|
|
4517
|
+
const ordered2 = orderSpans(spans);
|
|
4518
|
+
const sessionKey = `${session.harness}:${session.sessionId}`;
|
|
4519
|
+
const unseen = ordered2.filter((span2) => {
|
|
4520
|
+
const key = `${sessionKey}:${span2.span_id}`;
|
|
4521
|
+
if (seenSpans.has(key)) return false;
|
|
4522
|
+
seenSpans.add(key);
|
|
4523
|
+
return true;
|
|
4524
|
+
});
|
|
4525
|
+
sessions += 1;
|
|
4526
|
+
newSpans += unseen.length;
|
|
4527
|
+
if (!seenSessions.has(sessionKey)) {
|
|
4528
|
+
seenSessions.add(sessionKey);
|
|
4529
|
+
await opts.onEvent(sessionEvent(session, ordered2, generatedAt));
|
|
4530
|
+
}
|
|
4531
|
+
if (includeSpans) {
|
|
4532
|
+
for (const span2 of unseen) {
|
|
4533
|
+
await opts.onEvent({ schemaVersion: 1, kind: "traces.stream.span", event: "span", generatedAt, session, span: span2 });
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
const batch = analyzeLiveBatch(ordered2, { generatedAt, session, newSpanCount: unseen.length });
|
|
4537
|
+
if (includeBatches) {
|
|
4538
|
+
await opts.onEvent({ schemaVersion: 1, kind: "traces.stream.batch", event: "analysis_batch", generatedAt, batch });
|
|
4539
|
+
}
|
|
4540
|
+
if (includeFindings) {
|
|
4541
|
+
for (const liveFinding of batch.findings) {
|
|
4542
|
+
if (seenFindings.has(liveFinding.id)) continue;
|
|
4543
|
+
seenFindings.add(liveFinding.id);
|
|
4544
|
+
findings += 1;
|
|
4545
|
+
await opts.onEvent({ schemaVersion: 1, kind: "traces.stream.finding", event: "finding", generatedAt, finding: liveFinding });
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
await opts.onEvent({
|
|
4550
|
+
schemaVersion: 1,
|
|
4551
|
+
kind: "traces.stream.tick",
|
|
4552
|
+
event: "tick",
|
|
4553
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4554
|
+
sessions,
|
|
4555
|
+
newSpans,
|
|
4556
|
+
findings
|
|
4557
|
+
});
|
|
4558
|
+
if (opts.once || opts.signal?.aborted) break;
|
|
4559
|
+
await sleep(intervalMs, opts.signal);
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
// src/observer.ts
|
|
4564
|
+
var sleep2 = (ms, signal) => new Promise((resolve2) => {
|
|
4565
|
+
if (signal?.aborted) {
|
|
4566
|
+
resolve2();
|
|
4567
|
+
return;
|
|
4568
|
+
}
|
|
4247
4569
|
const onAbort = () => {
|
|
4248
4570
|
clearTimeout(t);
|
|
4249
4571
|
resolve2();
|
|
@@ -4254,14 +4576,15 @@ var sleep = (ms, signal) => new Promise((resolve2) => {
|
|
|
4254
4576
|
}, ms);
|
|
4255
4577
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4256
4578
|
});
|
|
4257
|
-
function loopKey(sessionId,
|
|
4258
|
-
return `${sessionId}
|
|
4579
|
+
function loopKey(sessionId, toolName3, argHash) {
|
|
4580
|
+
return `${sessionId}:${toolName3}:${argHash}`;
|
|
4259
4581
|
}
|
|
4260
4582
|
async function watchSessions(opts) {
|
|
4261
4583
|
const windowMs = opts.windowMs ?? 30 * 6e4;
|
|
4262
4584
|
const intervalMs = Math.max(250, opts.intervalMs ?? 5e3);
|
|
4263
4585
|
const minLoop = opts.minLoopOccurrences ?? 3;
|
|
4264
4586
|
const seen = /* @__PURE__ */ new Map();
|
|
4587
|
+
const seenFindings = /* @__PURE__ */ new Set();
|
|
4265
4588
|
while (!opts.signal?.aborted) {
|
|
4266
4589
|
let sessionCount = 0;
|
|
4267
4590
|
for await (const { adapter, ref, spans } of scanSessions({
|
|
@@ -4288,6 +4611,27 @@ async function watchSessions(opts) {
|
|
|
4288
4611
|
opts.onError?.(err, ref);
|
|
4289
4612
|
}
|
|
4290
4613
|
}
|
|
4614
|
+
const batch = analyzeLiveBatch(spans, {
|
|
4615
|
+
session: { harness: adapter.harness, sessionId: ref.sessionId, cwd: ref.cwd, path: ref.path }
|
|
4616
|
+
});
|
|
4617
|
+
if (opts.onBatch) {
|
|
4618
|
+
try {
|
|
4619
|
+
await opts.onBatch(ref, batch);
|
|
4620
|
+
} catch (err) {
|
|
4621
|
+
opts.onError?.(err, ref);
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4624
|
+
if (opts.onFinding) {
|
|
4625
|
+
for (const finding2 of batch.findings) {
|
|
4626
|
+
if (seenFindings.has(finding2.id)) continue;
|
|
4627
|
+
seenFindings.add(finding2.id);
|
|
4628
|
+
try {
|
|
4629
|
+
await opts.onFinding(finding2);
|
|
4630
|
+
} catch (err) {
|
|
4631
|
+
opts.onError?.(err, ref);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4291
4635
|
for (const f of report.stuckLoops.findings) {
|
|
4292
4636
|
const key = loopKey(ref.sessionId, f.toolName, f.argHash);
|
|
4293
4637
|
if (f.occurrences > (seen.get(key) ?? 0)) {
|
|
@@ -4316,7 +4660,7 @@ async function watchSessions(opts) {
|
|
|
4316
4660
|
opts.onError?.(err);
|
|
4317
4661
|
}
|
|
4318
4662
|
if (opts.signal?.aborted) break;
|
|
4319
|
-
await
|
|
4663
|
+
await sleep2(intervalMs, opts.signal);
|
|
4320
4664
|
}
|
|
4321
4665
|
}
|
|
4322
4666
|
|
|
@@ -4543,7 +4887,7 @@ async function writeSessionIndexFile(index, outPath) {
|
|
|
4543
4887
|
}
|
|
4544
4888
|
|
|
4545
4889
|
// src/upload-state.ts
|
|
4546
|
-
import { createHash as
|
|
4890
|
+
import { createHash as createHash3 } from "crypto";
|
|
4547
4891
|
import { mkdir as mkdir2, readFile as readFile15, writeFile as writeFile6 } from "fs/promises";
|
|
4548
4892
|
import { homedir as homedir11 } from "os";
|
|
4549
4893
|
import { dirname as dirname4, join as join18 } from "path";
|
|
@@ -4555,7 +4899,7 @@ function uploadKey(harness, sessionId) {
|
|
|
4555
4899
|
return `${harness}:${sessionId}`;
|
|
4556
4900
|
}
|
|
4557
4901
|
function sessionHash(spans) {
|
|
4558
|
-
const h =
|
|
4902
|
+
const h = createHash3("sha256");
|
|
4559
4903
|
for (const s of spans) {
|
|
4560
4904
|
h.update(`${s.span_id}\0${s.name}\0${s.start_time}\0${s.status.code}
|
|
4561
4905
|
`);
|
|
@@ -4575,8 +4919,8 @@ async function saveState(state) {
|
|
|
4575
4919
|
await writeFile6(f, `${JSON.stringify(state, null, 2)}
|
|
4576
4920
|
`, "utf8");
|
|
4577
4921
|
}
|
|
4578
|
-
function alreadyUploaded(state, harness, sessionId,
|
|
4579
|
-
return state[uploadKey(harness, sessionId)]?.hash ===
|
|
4922
|
+
function alreadyUploaded(state, harness, sessionId, hash2) {
|
|
4923
|
+
return state[uploadKey(harness, sessionId)]?.hash === hash2;
|
|
4580
4924
|
}
|
|
4581
4925
|
|
|
4582
4926
|
// src/upload.ts
|
|
@@ -4593,13 +4937,13 @@ async function planUpload(opts = {}) {
|
|
|
4593
4937
|
const items = [];
|
|
4594
4938
|
for await (const { ref, spans: raw } of scanSessions(opts)) {
|
|
4595
4939
|
const { spans, report } = redactSpans(raw);
|
|
4596
|
-
const
|
|
4940
|
+
const hash2 = sessionHash(spans);
|
|
4597
4941
|
items.push({
|
|
4598
4942
|
ref,
|
|
4599
4943
|
spans,
|
|
4600
4944
|
redaction: report,
|
|
4601
|
-
hash,
|
|
4602
|
-
isNew: !alreadyUploaded(state, ref.harness, ref.sessionId,
|
|
4945
|
+
hash: hash2,
|
|
4946
|
+
isNew: !alreadyUploaded(state, ref.harness, ref.sessionId, hash2)
|
|
4603
4947
|
});
|
|
4604
4948
|
}
|
|
4605
4949
|
return { items, state, sinceMs: opts.sinceMs };
|
|
@@ -4796,6 +5140,10 @@ export {
|
|
|
4796
5140
|
writeTraceImprovementArtifacts,
|
|
4797
5141
|
runTraceImprovementLoop,
|
|
4798
5142
|
saveReport,
|
|
5143
|
+
analyzeLiveBatch,
|
|
5144
|
+
traceStreamEventsFromSpans,
|
|
5145
|
+
serializeTraceStreamEvent,
|
|
5146
|
+
streamSessions,
|
|
4799
5147
|
watchSessions,
|
|
4800
5148
|
buildSessionIndex,
|
|
4801
5149
|
buildSessionIndexFromRows,
|
package/dist/cli.js
CHANGED
|
@@ -33,18 +33,22 @@ import {
|
|
|
33
33
|
serializePolicyEvidence,
|
|
34
34
|
serializeSessionIndex,
|
|
35
35
|
serializeSpans,
|
|
36
|
+
serializeTraceStreamEvent,
|
|
37
|
+
streamSessions,
|
|
36
38
|
summarizeDeterministicSignals,
|
|
39
|
+
traceStreamEventsFromSpans,
|
|
37
40
|
watchSessions,
|
|
38
41
|
writeInspectionReportFile,
|
|
39
42
|
writeOtlpFile,
|
|
40
43
|
writePolicyEvidenceFile,
|
|
41
44
|
writeSessionIndexFile,
|
|
42
45
|
writeTraceEvidenceExportFile
|
|
43
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-G3OBZSWY.js";
|
|
44
47
|
|
|
45
48
|
// src/cli.ts
|
|
46
49
|
import { readFileSync } from "fs";
|
|
47
50
|
import { readFile, stat, writeFile } from "fs/promises";
|
|
51
|
+
import { basename, resolve } from "path";
|
|
48
52
|
function packageVersion() {
|
|
49
53
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
50
54
|
if (typeof pkg.version !== "string" || !pkg.version) throw new Error("package.json is missing version");
|
|
@@ -66,6 +70,9 @@ function parseArgs(argv) {
|
|
|
66
70
|
yes: false,
|
|
67
71
|
noContent: false,
|
|
68
72
|
analyzers: [],
|
|
73
|
+
replay: false,
|
|
74
|
+
noSpans: false,
|
|
75
|
+
noFindings: false,
|
|
69
76
|
attrs: []
|
|
70
77
|
};
|
|
71
78
|
for (let i = 1; i < argv.length; i++) {
|
|
@@ -135,6 +142,16 @@ function parseArgs(argv) {
|
|
|
135
142
|
case "--no-content":
|
|
136
143
|
a.noContent = true;
|
|
137
144
|
break;
|
|
145
|
+
case "--replay":
|
|
146
|
+
case "--once":
|
|
147
|
+
a.replay = true;
|
|
148
|
+
break;
|
|
149
|
+
case "--no-spans":
|
|
150
|
+
a.noSpans = true;
|
|
151
|
+
break;
|
|
152
|
+
case "--no-findings":
|
|
153
|
+
a.noFindings = true;
|
|
154
|
+
break;
|
|
138
155
|
case "--analyzer": {
|
|
139
156
|
const v = next();
|
|
140
157
|
if (v) a.analyzers.push(v);
|
|
@@ -486,12 +503,102 @@ async function cmdImprove(args) {
|
|
|
486
503
|
`improvement artifacts \u2192 ${dir} (${result.findings.length} findings, ${result.recommendations.length} recommendations, ${result.proposals.length} proposal(s), OTLP: ${result.otlpPath})`
|
|
487
504
|
);
|
|
488
505
|
}
|
|
506
|
+
function summarizeFindingEvidence(finding) {
|
|
507
|
+
const first = finding.evidence[0];
|
|
508
|
+
if (!first) return "no evidence";
|
|
509
|
+
return `${first.label}: ${first.value}`;
|
|
510
|
+
}
|
|
511
|
+
function formatLiveFinding(finding) {
|
|
512
|
+
const ts = new Date(finding.observedAt).toISOString().slice(11, 19);
|
|
513
|
+
const where = `[${finding.session.harness}] ${finding.session.sessionId.slice(0, 8)}`;
|
|
514
|
+
const cwd = finding.session.cwd ? ` \xB7 ${finding.session.cwd}` : "";
|
|
515
|
+
return [
|
|
516
|
+
`${ts} ${finding.severity.toUpperCase()} ${where} \u2014 ${finding.title}${cwd}`,
|
|
517
|
+
` evidence: ${summarizeFindingEvidence(finding)}`,
|
|
518
|
+
` action: ${finding.action}`,
|
|
519
|
+
` check: ${finding.check}`
|
|
520
|
+
].join("\n");
|
|
521
|
+
}
|
|
522
|
+
async function streamExplicitSession(args) {
|
|
523
|
+
if (!args.session) throw new Error("streamExplicitSession needs --session");
|
|
524
|
+
const adapter = resolveAdapter(args.harness);
|
|
525
|
+
if (!adapter) throw new Error(`unknown harness "${args.harness}"`);
|
|
526
|
+
const st = await stat(args.session);
|
|
527
|
+
const ref = {
|
|
528
|
+
harness: adapter.harness,
|
|
529
|
+
sessionId: basename(args.session),
|
|
530
|
+
path: resolve(args.session),
|
|
531
|
+
cwd: args.cwd ?? null,
|
|
532
|
+
mtimeMs: st.mtimeMs
|
|
533
|
+
};
|
|
534
|
+
const spans = await parseSession(adapter, ref);
|
|
535
|
+
for (const event of traceStreamEventsFromSpans(spans, {
|
|
536
|
+
ref,
|
|
537
|
+
includeSpans: !args.noSpans,
|
|
538
|
+
includeFindings: !args.noFindings
|
|
539
|
+
})) {
|
|
540
|
+
process.stdout.write(serializeTraceStreamEvent(event));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async function cmdStream(args) {
|
|
544
|
+
if (args.input) {
|
|
545
|
+
const { spans, harness } = await collectImportedSpans(args);
|
|
546
|
+
const ref = {
|
|
547
|
+
harness,
|
|
548
|
+
sessionId: basename(args.input),
|
|
549
|
+
path: resolve(args.input),
|
|
550
|
+
cwd: args.cwd ?? null,
|
|
551
|
+
mtimeMs: Date.now()
|
|
552
|
+
};
|
|
553
|
+
for (const event of traceStreamEventsFromSpans(spans, {
|
|
554
|
+
ref,
|
|
555
|
+
includeSpans: !args.noSpans,
|
|
556
|
+
includeFindings: !args.noFindings
|
|
557
|
+
})) {
|
|
558
|
+
process.stdout.write(serializeTraceStreamEvent(event));
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (args.session) {
|
|
563
|
+
await streamExplicitSession(args);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const all = args.all || !args.harnessExplicit && !args.cwd;
|
|
567
|
+
const controller = new AbortController();
|
|
568
|
+
process.once("SIGINT", () => controller.abort());
|
|
569
|
+
if (!args.replay) {
|
|
570
|
+
process.stderr.write(
|
|
571
|
+
`traces stream \u2014 JSONL live feed for ${all ? "all harnesses" : args.harness}, last ${args.window}m, every ${args.interval}s. Ctrl-C to stop.
|
|
572
|
+
`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
await streamSessions({
|
|
576
|
+
all,
|
|
577
|
+
harnesses: all ? void 0 : [args.harness],
|
|
578
|
+
cwd: args.cwd,
|
|
579
|
+
last: args.last > 0 ? args.last : args.replay ? 1 : void 0,
|
|
580
|
+
windowMs: args.window * 6e4,
|
|
581
|
+
intervalMs: args.interval * 1e3,
|
|
582
|
+
once: args.replay,
|
|
583
|
+
includeSpans: !args.noSpans,
|
|
584
|
+
includeFindings: !args.noFindings,
|
|
585
|
+
signal: controller.signal,
|
|
586
|
+
onEvent: (event) => {
|
|
587
|
+
process.stdout.write(serializeTraceStreamEvent(event));
|
|
588
|
+
},
|
|
589
|
+
onError: (err, ref) => {
|
|
590
|
+
const where = ref ? ` [${ref.harness}] ${ref.sessionId}` : "";
|
|
591
|
+
process.stderr.write(`stream error${where}: ${err instanceof Error ? err.message : String(err)}
|
|
592
|
+
`);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}
|
|
489
596
|
async function cmdWatch(args) {
|
|
490
597
|
const all = args.all || !args.harnessExplicit && !args.cwd;
|
|
491
598
|
const controller = new AbortController();
|
|
492
599
|
process.once("SIGINT", () => controller.abort());
|
|
493
600
|
process.stderr.write(
|
|
494
|
-
`traces watch \u2014 observing ${all ? "all harnesses" : args.harness}, sessions active in the last ${args.window}m, every ${args.interval}s.
|
|
601
|
+
`traces watch \u2014 observing ${all ? "all harnesses" : args.harness}, sessions active in the last ${args.window}m, every ${args.interval}s. Loop + semantic live findings; read-only; Ctrl-C to stop.
|
|
495
602
|
`
|
|
496
603
|
);
|
|
497
604
|
await watchSessions({
|
|
@@ -505,9 +612,13 @@ async function cmdWatch(args) {
|
|
|
505
612
|
onLoop: (l) => {
|
|
506
613
|
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
507
614
|
process.stdout.write(
|
|
508
|
-
|
|
615
|
+
`${ts} LOOP [${l.harness}] ${l.sessionId.slice(0, 8)} \u2014 \`${l.toolName}\` repeated \xD7${l.occurrences} with identical args (${(l.windowMs / 1e3).toFixed(0)}s)${l.cwd ? ` \xB7 ${l.cwd}` : ""}
|
|
509
616
|
`
|
|
510
617
|
);
|
|
618
|
+
},
|
|
619
|
+
onFinding: (finding) => {
|
|
620
|
+
process.stdout.write(`${formatLiveFinding(finding)}
|
|
621
|
+
`);
|
|
511
622
|
}
|
|
512
623
|
});
|
|
513
624
|
}
|
|
@@ -607,14 +718,15 @@ Commands:
|
|
|
607
718
|
inspect Read a session index and print ranked improvement findings
|
|
608
719
|
export Convert evidence/events files to OpenInference JSONL for HALO
|
|
609
720
|
evidence Emit compact session-evidence JSONL for downstream policy miners
|
|
610
|
-
|
|
721
|
+
stream Emit JSONL trace stream events for live visualizers or replay
|
|
722
|
+
watch Online observer: tail active sessions, notify on loops + semantic findings
|
|
611
723
|
upload Redact + upload sessions in a time window to the Tangle Intelligence Platform
|
|
612
724
|
|
|
613
725
|
Options:
|
|
614
726
|
--harness <id> Harness or alias (default: claude-code). Known: ${knownHarnesses().join(", ")}
|
|
615
727
|
--all Sweep every known harness
|
|
616
728
|
--last <n> Most-recent N sessions
|
|
617
|
-
--session <path> Analyze one explicit session file
|
|
729
|
+
--session <path> Analyze/stream one explicit harness session file
|
|
618
730
|
--cwd <dir> Filter sessions by working directory
|
|
619
731
|
--since <t> upload: window \u2014 30m / 2h / 7d or an ISO date (default 24h); analyze: ISO cutoff
|
|
620
732
|
--out <path> Write report to a file
|
|
@@ -623,6 +735,9 @@ Options:
|
|
|
623
735
|
--format <kind> analyze/export file: auto | policy-evidence | sandbox-events | openinference | intelligence-spans
|
|
624
736
|
--metadata <json> analyze/export file: attach JSON object fields as span attributes
|
|
625
737
|
--attr <k=v> analyze/export file: attach one span attribute (repeatable)
|
|
738
|
+
--replay, --once stream: scan once and exit (default for positional input / --session)
|
|
739
|
+
--no-spans stream: omit per-span pulse events
|
|
740
|
+
--no-findings stream: omit semantic live-finding events
|
|
626
741
|
--llm Enable agentic RLM analysts (needs OPENAI_API_KEY / OPENAI_BASE_URL)
|
|
627
742
|
--model <id> --llm model id (e.g. a router model like glm-5.2); default is agent-eval's
|
|
628
743
|
--config <path> investigate/improve: JS config with analysts, external analyzers, or proposal adapter
|
|
@@ -685,6 +800,9 @@ async function main() {
|
|
|
685
800
|
case "evidence":
|
|
686
801
|
await cmdEvidence(args);
|
|
687
802
|
break;
|
|
803
|
+
case "stream":
|
|
804
|
+
await cmdStream(args);
|
|
805
|
+
break;
|
|
688
806
|
case "watch":
|
|
689
807
|
await cmdWatch(args);
|
|
690
808
|
break;
|
package/dist/index.d.ts
CHANGED
|
@@ -1276,6 +1276,115 @@ declare function writeTraceImprovementArtifacts(result: Pick<TraceImprovementRes
|
|
|
1276
1276
|
declare function runTraceImprovementLoop(opts: TraceImprovementOptions): Promise<TraceImprovementResult>;
|
|
1277
1277
|
declare function saveReport(path: string, report: string): Promise<void>;
|
|
1278
1278
|
|
|
1279
|
+
type TraceLiveSeverity = 'info' | 'low' | 'medium' | 'high';
|
|
1280
|
+
interface TraceLiveEvidence {
|
|
1281
|
+
readonly kind: 'metric' | 'span' | 'pattern';
|
|
1282
|
+
readonly label: string;
|
|
1283
|
+
readonly value: string;
|
|
1284
|
+
readonly spanIds?: readonly string[];
|
|
1285
|
+
}
|
|
1286
|
+
interface TraceLiveFinding {
|
|
1287
|
+
readonly schemaVersion: 1;
|
|
1288
|
+
readonly kind: 'traces.live_finding';
|
|
1289
|
+
readonly id: string;
|
|
1290
|
+
readonly ruleId: string;
|
|
1291
|
+
readonly fingerprint: string;
|
|
1292
|
+
readonly severity: TraceLiveSeverity;
|
|
1293
|
+
readonly title: string;
|
|
1294
|
+
readonly claim: string;
|
|
1295
|
+
readonly action: string;
|
|
1296
|
+
readonly check: string;
|
|
1297
|
+
readonly evidence: readonly TraceLiveEvidence[];
|
|
1298
|
+
readonly session: TraceStreamSession;
|
|
1299
|
+
readonly observedAt: string;
|
|
1300
|
+
}
|
|
1301
|
+
interface TraceStreamSession {
|
|
1302
|
+
readonly harness: string;
|
|
1303
|
+
readonly sessionId: string;
|
|
1304
|
+
readonly cwd: string | null;
|
|
1305
|
+
readonly path?: string;
|
|
1306
|
+
}
|
|
1307
|
+
interface TraceLiveBatch {
|
|
1308
|
+
readonly schemaVersion: 1;
|
|
1309
|
+
readonly kind: 'traces.live_batch';
|
|
1310
|
+
readonly generatedAt: string;
|
|
1311
|
+
readonly session: TraceStreamSession;
|
|
1312
|
+
readonly spanCount: number;
|
|
1313
|
+
readonly newSpanCount: number;
|
|
1314
|
+
readonly toolCallCount: number;
|
|
1315
|
+
readonly erroredToolCallCount: number;
|
|
1316
|
+
readonly verificationCallCount: number;
|
|
1317
|
+
readonly changeCallCount: number;
|
|
1318
|
+
readonly findingCount: number;
|
|
1319
|
+
readonly findings: readonly TraceLiveFinding[];
|
|
1320
|
+
}
|
|
1321
|
+
type TraceStreamEvent = {
|
|
1322
|
+
readonly schemaVersion: 1;
|
|
1323
|
+
readonly kind: 'traces.stream.session';
|
|
1324
|
+
readonly event: 'session';
|
|
1325
|
+
readonly generatedAt: string;
|
|
1326
|
+
readonly session: TraceStreamSession;
|
|
1327
|
+
readonly spanCount: number;
|
|
1328
|
+
readonly startedAt: string | null;
|
|
1329
|
+
readonly endedAt: string | null;
|
|
1330
|
+
} | {
|
|
1331
|
+
readonly schemaVersion: 1;
|
|
1332
|
+
readonly kind: 'traces.stream.span';
|
|
1333
|
+
readonly event: 'span';
|
|
1334
|
+
readonly generatedAt: string;
|
|
1335
|
+
readonly session: TraceStreamSession;
|
|
1336
|
+
readonly span: OtlpSpan;
|
|
1337
|
+
} | {
|
|
1338
|
+
readonly schemaVersion: 1;
|
|
1339
|
+
readonly kind: 'traces.stream.batch';
|
|
1340
|
+
readonly event: 'analysis_batch';
|
|
1341
|
+
readonly generatedAt: string;
|
|
1342
|
+
readonly batch: TraceLiveBatch;
|
|
1343
|
+
} | {
|
|
1344
|
+
readonly schemaVersion: 1;
|
|
1345
|
+
readonly kind: 'traces.stream.finding';
|
|
1346
|
+
readonly event: 'finding';
|
|
1347
|
+
readonly generatedAt: string;
|
|
1348
|
+
readonly finding: TraceLiveFinding;
|
|
1349
|
+
} | {
|
|
1350
|
+
readonly schemaVersion: 1;
|
|
1351
|
+
readonly kind: 'traces.stream.tick';
|
|
1352
|
+
readonly event: 'tick';
|
|
1353
|
+
readonly generatedAt: string;
|
|
1354
|
+
readonly sessions: number;
|
|
1355
|
+
readonly newSpans: number;
|
|
1356
|
+
readonly findings: number;
|
|
1357
|
+
};
|
|
1358
|
+
interface LiveBatchOptions {
|
|
1359
|
+
readonly generatedAt?: string;
|
|
1360
|
+
readonly session?: Partial<TraceStreamSession>;
|
|
1361
|
+
readonly newSpanCount?: number;
|
|
1362
|
+
}
|
|
1363
|
+
interface TraceStreamReplayOptions extends LiveBatchOptions {
|
|
1364
|
+
readonly ref?: SessionRef;
|
|
1365
|
+
readonly includeSpans?: boolean;
|
|
1366
|
+
readonly includeFindings?: boolean;
|
|
1367
|
+
}
|
|
1368
|
+
interface TraceStreamOptions extends ScanOptions {
|
|
1369
|
+
/** Observe sessions active within this window. Default 30 min. */
|
|
1370
|
+
readonly windowMs?: number;
|
|
1371
|
+
/** Poll interval. Default 5s. */
|
|
1372
|
+
readonly intervalMs?: number;
|
|
1373
|
+
/** Stop after one scan, useful for replay or tests. */
|
|
1374
|
+
readonly once?: boolean;
|
|
1375
|
+
/** Emit one JSON event per new span. Default true. */
|
|
1376
|
+
readonly includeSpans?: boolean;
|
|
1377
|
+
/** Emit live semantic findings. Default true. */
|
|
1378
|
+
readonly includeFindings?: boolean;
|
|
1379
|
+
/** Emit session and analysis-batch events. Default true. */
|
|
1380
|
+
readonly includeBatches?: boolean;
|
|
1381
|
+
readonly onEvent: (event: TraceStreamEvent) => void | Promise<void>;
|
|
1382
|
+
}
|
|
1383
|
+
declare function analyzeLiveBatch(spans: readonly OtlpSpan[], opts?: LiveBatchOptions): TraceLiveBatch;
|
|
1384
|
+
declare function traceStreamEventsFromSpans(spans: readonly OtlpSpan[], opts?: TraceStreamReplayOptions): TraceStreamEvent[];
|
|
1385
|
+
declare function serializeTraceStreamEvent(event: TraceStreamEvent): string;
|
|
1386
|
+
declare function streamSessions(opts: TraceStreamOptions): Promise<void>;
|
|
1387
|
+
|
|
1279
1388
|
/**
|
|
1280
1389
|
* Live trace observer — the reusable, event-driven core behind `traces watch`.
|
|
1281
1390
|
*
|
|
@@ -1323,6 +1432,10 @@ interface ObserverOptions {
|
|
|
1323
1432
|
/** Fired for every observed session each tick, with its full pipeline report
|
|
1324
1433
|
* (stuck loops + tool-use metrics) — react to anything, not just loops. */
|
|
1325
1434
|
onReport?: (ref: SessionRef, report: PipelineReport) => void | Promise<void>;
|
|
1435
|
+
/** Fired for every observed session each tick, after semantic live analysis. */
|
|
1436
|
+
onBatch?: (ref: SessionRef, batch: TraceLiveBatch) => void | Promise<void>;
|
|
1437
|
+
/** Fired once for each newly-observed live semantic finding. */
|
|
1438
|
+
onFinding?: (finding: TraceLiveFinding) => void | Promise<void>;
|
|
1326
1439
|
/** Per-session parse/pipeline error (the observer keeps going). */
|
|
1327
1440
|
onError?: (error: unknown, ref?: SessionRef) => void;
|
|
1328
1441
|
/** Fired after each poll cycle. */
|
|
@@ -1489,4 +1602,4 @@ interface ExecuteOptions {
|
|
|
1489
1602
|
/** Send the NEW items (or, on dryRun, write the redacted OTLP that would send). */
|
|
1490
1603
|
declare function executeUpload(plan: UploadPlan, opts?: ExecuteOptions): Promise<UploadResult>;
|
|
1491
1604
|
|
|
1492
|
-
export { ACTOR_ATTR, ATTR, type Actor, type AdoptionOptions, type AdoptionReport, AmpAdapter, type AnalyzeOptions, type AnalyzeResult, type BuildPolicyEvidenceOptions, type BuildSessionIndexOptions, type BuildTraceFindingPacketOptions, CODING_REDACTION_RULES, CONTENT_CAP, CORRECTIVE_REACTIONS, ClaudeAdapter, CodexAdapter, type CollectOptions, type CollectPolicyEvidenceOptions, type CollectSessionIndexOptions, CopilotAdapter, DEFAULT_HARNESS, type ExecuteOptions, type ExternalAnalysisResult, type ExternalAnalyzer, FactoryAdapter, type FindingStore, ForgeAdapter, GeminiAdapter, type HarnessTraceAdapter, INGEST_SOURCE_CLI, type ImprovementAdapter, type ImprovementAdapterInput, type ImprovementProposal, type InspectSessionIndexOptions, type LocateOptions, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpSpan, type OtlpSpanKind, type OtlpStatusCode, PiAdapter, type PipelineOptions, type PipelineReport, type PlanOptions, type PolicyEvidenceLoopSummary, type PolicyEvidenceRecord, type PolicyEvidenceToolSummary, QwenAdapter, REACTIONS, type Reaction, type ReactionReport, type ReactionSessionReport, type RecommendationWriter, type Redactor, type RepoAttrs, type RepoResolution, type RepoResolutionSource, type RunResult, type RuntimeTrace, type ScanOptions, type ScannedSession, type SessionBatch, type SessionRef, type SpanInput, type SpanRedaction, TRACES_REDACTION_RULES, type TraceClaim, type TraceContextIndex, type TraceContextRoot, type TraceEvidenceExportOptions, type TraceEvidenceExportResult, type TraceEvidenceFormatOption, type TraceEvidenceInputFormat, type TraceEvidenceRow, type TraceFindingPacket, type TraceImprovementArtifacts, type TraceImprovementOptions, type TraceImprovementResult, type TraceIndexedFile, type TraceInspectionArea, type TraceInspectionFinding, type TraceInspectionReport, type TraceInspectionSeverity, type TraceInvestigationOptions, type TraceInvestigationResult, type TraceRecommendation, type TraceReplayProof, type TraceSessionIndex, type TraceSessionIndexRow, type TraceStoreInvestigationOptions, type TraceStoreInvestigationResult, type TracesConfig, type TriggerPair, type UploadBackend, type UploadItem, type UploadPlan, type UploadRecord, type UploadResult, type UploadState, type UserPromptInput, alreadyUploaded, analyzeAdoption, analyzeReactions, analyzeSpans, applyRedactor, buildPolicyEvidenceRecord, buildSessionIndex, buildSessionIndexFromRows, buildTraceFindingPacket, capText, classifyReaction, claudeActor, codexActor, collectPolicyEvidence, collectSessionIndex, collectSessions, commandAnalyzer, commandRedactor, countSkillRunsJsonl, executeUpload, exportTraceEvidenceFile, exportTraceEvidenceRows, exportTraceEvidenceText, haloAnalyzer, inspectSessionIndex, knownHarnesses, listAdapters, loadState, loadTracesConfig, looksLikeAgentPrompt, mergeTracesConfig, normalizeRemote, parseIsoToEpochMs, parseSession, parseSince, planUpload, readSessionIndexFile, redactSpans, renderInspectionReport, resolveAdapter, resolveRepoAttrs, resolveSessionRepoAttrs, runCommand, runExternalAnalyzers, runPipelines, runTraceImprovementLoop, runTraceInvestigation, runTraceStoreInvestigation, saveReport, saveState, scanSessions, selectAdapters, serializeInspectionReport, serializePolicyEvidence, serializeSessionIndex, serializeSpans, sessionHash, span, stampRepoAttrs, textIsSynthetic, toOpenInferenceSpan, toRuntimeStore, toTraceSpanEvents, uploadKey, userPromptSpan, watchSessions, writeInspectionReportFile, writeOtlpFile, writePolicyEvidenceFile, writeSessionIndexFile, writeTraceEvidenceExportFile, writeTraceImprovementArtifacts };
|
|
1605
|
+
export { ACTOR_ATTR, ATTR, type Actor, type AdoptionOptions, type AdoptionReport, AmpAdapter, type AnalyzeOptions, type AnalyzeResult, type BuildPolicyEvidenceOptions, type BuildSessionIndexOptions, type BuildTraceFindingPacketOptions, CODING_REDACTION_RULES, CONTENT_CAP, CORRECTIVE_REACTIONS, ClaudeAdapter, CodexAdapter, type CollectOptions, type CollectPolicyEvidenceOptions, type CollectSessionIndexOptions, CopilotAdapter, DEFAULT_HARNESS, type ExecuteOptions, type ExternalAnalysisResult, type ExternalAnalyzer, FactoryAdapter, type FindingStore, ForgeAdapter, GeminiAdapter, type HarnessTraceAdapter, INGEST_SOURCE_CLI, type ImprovementAdapter, type ImprovementAdapterInput, type ImprovementProposal, type InspectSessionIndexOptions, type LiveBatchOptions, type LocateOptions, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpSpan, type OtlpSpanKind, type OtlpStatusCode, PiAdapter, type PipelineOptions, type PipelineReport, type PlanOptions, type PolicyEvidenceLoopSummary, type PolicyEvidenceRecord, type PolicyEvidenceToolSummary, QwenAdapter, REACTIONS, type Reaction, type ReactionReport, type ReactionSessionReport, type RecommendationWriter, type Redactor, type RepoAttrs, type RepoResolution, type RepoResolutionSource, type RunResult, type RuntimeTrace, type ScanOptions, type ScannedSession, type SessionBatch, type SessionRef, type SpanInput, type SpanRedaction, TRACES_REDACTION_RULES, type TraceClaim, type TraceContextIndex, type TraceContextRoot, type TraceEvidenceExportOptions, type TraceEvidenceExportResult, type TraceEvidenceFormatOption, type TraceEvidenceInputFormat, type TraceEvidenceRow, type TraceFindingPacket, type TraceImprovementArtifacts, type TraceImprovementOptions, type TraceImprovementResult, type TraceIndexedFile, type TraceInspectionArea, type TraceInspectionFinding, type TraceInspectionReport, type TraceInspectionSeverity, type TraceInvestigationOptions, type TraceInvestigationResult, type TraceLiveBatch, type TraceLiveEvidence, type TraceLiveFinding, type TraceLiveSeverity, type TraceRecommendation, type TraceReplayProof, type TraceSessionIndex, type TraceSessionIndexRow, type TraceStoreInvestigationOptions, type TraceStoreInvestigationResult, type TraceStreamEvent, type TraceStreamOptions, type TraceStreamReplayOptions, type TraceStreamSession, type TracesConfig, type TriggerPair, type UploadBackend, type UploadItem, type UploadPlan, type UploadRecord, type UploadResult, type UploadState, type UserPromptInput, alreadyUploaded, analyzeAdoption, analyzeLiveBatch, analyzeReactions, analyzeSpans, applyRedactor, buildPolicyEvidenceRecord, buildSessionIndex, buildSessionIndexFromRows, buildTraceFindingPacket, capText, classifyReaction, claudeActor, codexActor, collectPolicyEvidence, collectSessionIndex, collectSessions, commandAnalyzer, commandRedactor, countSkillRunsJsonl, executeUpload, exportTraceEvidenceFile, exportTraceEvidenceRows, exportTraceEvidenceText, haloAnalyzer, inspectSessionIndex, knownHarnesses, listAdapters, loadState, loadTracesConfig, looksLikeAgentPrompt, mergeTracesConfig, normalizeRemote, parseIsoToEpochMs, parseSession, parseSince, planUpload, readSessionIndexFile, redactSpans, renderInspectionReport, resolveAdapter, resolveRepoAttrs, resolveSessionRepoAttrs, runCommand, runExternalAnalyzers, runPipelines, runTraceImprovementLoop, runTraceInvestigation, runTraceStoreInvestigation, saveReport, saveState, scanSessions, selectAdapters, serializeInspectionReport, serializePolicyEvidence, serializeSessionIndex, serializeSpans, serializeTraceStreamEvent, sessionHash, span, stampRepoAttrs, streamSessions, textIsSynthetic, toOpenInferenceSpan, toRuntimeStore, toTraceSpanEvents, traceStreamEventsFromSpans, uploadKey, userPromptSpan, watchSessions, writeInspectionReportFile, writeOtlpFile, writePolicyEvidenceFile, writeSessionIndexFile, writeTraceEvidenceExportFile, writeTraceImprovementArtifacts };
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
TRACES_REDACTION_RULES,
|
|
21
21
|
alreadyUploaded,
|
|
22
22
|
analyzeAdoption,
|
|
23
|
+
analyzeLiveBatch,
|
|
23
24
|
analyzeReactions,
|
|
24
25
|
analyzeSpans,
|
|
25
26
|
applyRedactor,
|
|
@@ -73,13 +74,16 @@ import {
|
|
|
73
74
|
serializePolicyEvidence,
|
|
74
75
|
serializeSessionIndex,
|
|
75
76
|
serializeSpans,
|
|
77
|
+
serializeTraceStreamEvent,
|
|
76
78
|
sessionHash,
|
|
77
79
|
span,
|
|
78
80
|
stampRepoAttrs,
|
|
81
|
+
streamSessions,
|
|
79
82
|
textIsSynthetic,
|
|
80
83
|
toOpenInferenceSpan,
|
|
81
84
|
toRuntimeStore,
|
|
82
85
|
toTraceSpanEvents,
|
|
86
|
+
traceStreamEventsFromSpans,
|
|
83
87
|
uploadKey,
|
|
84
88
|
userPromptSpan,
|
|
85
89
|
watchSessions,
|
|
@@ -89,7 +93,7 @@ import {
|
|
|
89
93
|
writeSessionIndexFile,
|
|
90
94
|
writeTraceEvidenceExportFile,
|
|
91
95
|
writeTraceImprovementArtifacts
|
|
92
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-G3OBZSWY.js";
|
|
93
97
|
|
|
94
98
|
// src/collect.ts
|
|
95
99
|
async function collectSessions(opts = {}) {
|
|
@@ -137,6 +141,7 @@ export {
|
|
|
137
141
|
TRACES_REDACTION_RULES,
|
|
138
142
|
alreadyUploaded,
|
|
139
143
|
analyzeAdoption,
|
|
144
|
+
analyzeLiveBatch,
|
|
140
145
|
analyzeReactions,
|
|
141
146
|
analyzeSpans,
|
|
142
147
|
applyRedactor,
|
|
@@ -197,13 +202,16 @@ export {
|
|
|
197
202
|
serializePolicyEvidence,
|
|
198
203
|
serializeSessionIndex,
|
|
199
204
|
serializeSpans,
|
|
205
|
+
serializeTraceStreamEvent,
|
|
200
206
|
sessionHash,
|
|
201
207
|
span,
|
|
202
208
|
stampRepoAttrs,
|
|
209
|
+
streamSessions,
|
|
203
210
|
textIsSynthetic,
|
|
204
211
|
toOpenInferenceSpan,
|
|
205
212
|
toRuntimeStore,
|
|
206
213
|
toTraceSpanEvents,
|
|
214
|
+
traceStreamEventsFromSpans,
|
|
207
215
|
uploadKey,
|
|
208
216
|
userPromptSpan,
|
|
209
217
|
watchSessions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/traces",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.18",
|
|
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",
|