@tangle-network/traces 0.15.4 → 0.15.5
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 +80 -12
- package/dist/{chunk-SKOW5V5U.js → chunk-RERVIGLH.js} +577 -283
- package/dist/cli.js +252 -31
- package/dist/index.d.ts +140 -32
- package/dist/index.js +27 -9
- package/docs/trace-analysts.md +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
appendAll,
|
|
9
9
|
assembleSessionBundle,
|
|
10
10
|
assertOutsideSourceBundle,
|
|
11
|
+
assessSpans,
|
|
11
12
|
buildPolicyEvidenceRecord,
|
|
12
13
|
buildSessionFactsReport,
|
|
13
14
|
buildSessionIndexFromRows,
|
|
@@ -17,11 +18,13 @@ import {
|
|
|
17
18
|
collectSessionSelection,
|
|
18
19
|
commandAnalyzer,
|
|
19
20
|
commandRedactor,
|
|
21
|
+
diffRuns,
|
|
20
22
|
executeUpload,
|
|
21
23
|
exportTraceEvidenceFile,
|
|
22
24
|
externalFailureMessage,
|
|
23
25
|
fileRunContextSupervisorRunReader,
|
|
24
26
|
findingRejectionDetail,
|
|
27
|
+
formatVerdict,
|
|
25
28
|
haloAnalyzer,
|
|
26
29
|
hodoscopeAnalyzer,
|
|
27
30
|
importCodeTraceBench,
|
|
@@ -34,6 +37,7 @@ import {
|
|
|
34
37
|
locateSessions,
|
|
35
38
|
mergeTracesConfig,
|
|
36
39
|
normalizeTraceQuestions,
|
|
40
|
+
openAgenticTraceStore,
|
|
37
41
|
parseSession,
|
|
38
42
|
parseSince,
|
|
39
43
|
planUpload,
|
|
@@ -41,7 +45,9 @@ import {
|
|
|
41
45
|
projectSessionBundle,
|
|
42
46
|
readOtlpInput,
|
|
43
47
|
readSessionIndexFile,
|
|
48
|
+
redactSpans,
|
|
44
49
|
renderInspectionReport,
|
|
50
|
+
renderRunDiff,
|
|
45
51
|
renderSessionFacts,
|
|
46
52
|
renderValidation,
|
|
47
53
|
resolveAdapter,
|
|
@@ -59,18 +65,22 @@ import {
|
|
|
59
65
|
streamSessions,
|
|
60
66
|
totalAgenticFailureMessage,
|
|
61
67
|
traceStreamEventsFromSpans,
|
|
68
|
+
tracesVersion,
|
|
62
69
|
validationExitCode,
|
|
70
|
+
verdictExitCode,
|
|
63
71
|
verifyFindingsInSandbox,
|
|
72
|
+
verifySafe,
|
|
64
73
|
verifySessionBundle,
|
|
65
74
|
watchRunTarget,
|
|
66
75
|
watchSessions,
|
|
76
|
+
writeAnalysisTraceFile,
|
|
67
77
|
writeInspectionReportFile,
|
|
68
78
|
writeOtlpFile,
|
|
69
79
|
writePolicyEvidenceFile,
|
|
70
80
|
writeSessionIndexFile,
|
|
71
81
|
writeTraceEvidenceExportFile,
|
|
72
82
|
writeTraceQuestionsArtifacts
|
|
73
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-RERVIGLH.js";
|
|
74
84
|
|
|
75
85
|
// src/cli.ts
|
|
76
86
|
import { readFileSync } from "fs";
|
|
@@ -89,11 +99,60 @@ import {
|
|
|
89
99
|
renderSupervisorRunMarkdown,
|
|
90
100
|
rollupSupervisorRuns
|
|
91
101
|
} from "@tangle-network/agent-eval/supervisor-run";
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
|
|
103
|
+
// src/mcp.ts
|
|
104
|
+
import { rm } from "fs/promises";
|
|
105
|
+
import { dirname } from "path";
|
|
106
|
+
import { buildTraceAnalysisToolDescriptors, redact, shareAllowed, UNTRUSTED_TRACE_TEXT } from "@tangle-network/agent-eval/traces";
|
|
107
|
+
import { createStdioToolServer } from "@tangle-network/agent-runtime/mcp";
|
|
108
|
+
var MCP_RESULT_BYTE_CAP = 512 * 1024;
|
|
109
|
+
async function traceMcpTools(options) {
|
|
110
|
+
const { spans } = redactSpans(options.spans);
|
|
111
|
+
const verdict = assessSpans(spans);
|
|
112
|
+
if (!shareAllowed(verdict)) {
|
|
113
|
+
const findings = verdict.findings.map((f) => `${f.category}/${f.detector} (${f.count})`).join(", ");
|
|
114
|
+
throw new Error(
|
|
115
|
+
`mcp: refusing to serve \u2014 redacted spans are still ${verdict.status}` + (findings ? `: ${findings}` : "") + (verdict.unreadable.length > 0 ? `; unreadable: ${verdict.unreadable.join("; ")}` : "")
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const file = await writeAnalysisTraceFile(spans);
|
|
119
|
+
const store = await openAgenticTraceStore(file);
|
|
120
|
+
const descriptors = buildTraceAnalysisToolDescriptors({ store }).filter((d) => d.name !== "readSpanSource");
|
|
121
|
+
const tools = descriptors.map((descriptor) => ({
|
|
122
|
+
name: descriptor.name,
|
|
123
|
+
description: descriptor.description,
|
|
124
|
+
inputSchema: descriptor.parameters,
|
|
125
|
+
annotations: {
|
|
126
|
+
readOnlyHint: descriptor.readOnly,
|
|
127
|
+
idempotentHint: descriptor.idempotent,
|
|
128
|
+
destructiveHint: false,
|
|
129
|
+
openWorldHint: false
|
|
130
|
+
},
|
|
131
|
+
handler: async (args) => {
|
|
132
|
+
const { value } = redact(await descriptor.handler(args));
|
|
133
|
+
const result = { untrusted: UNTRUSTED_TRACE_TEXT, result: value };
|
|
134
|
+
const bytes = Buffer.byteLength(JSON.stringify(result));
|
|
135
|
+
if (bytes > MCP_RESULT_BYTE_CAP) {
|
|
136
|
+
throw new RangeError(
|
|
137
|
+
`${descriptor.name}: result is ${bytes} bytes, above the ${MCP_RESULT_BYTE_CAP}-byte cap; narrow the request`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
}));
|
|
143
|
+
return { tools, otlpPath: file.otlpPath };
|
|
96
144
|
}
|
|
145
|
+
async function serveTraceMcp(options) {
|
|
146
|
+
const { tools, otlpPath } = await traceMcpTools(options);
|
|
147
|
+
const server = createStdioToolServer({ serverName: "traces", serverVersion: tracesVersion(), tools });
|
|
148
|
+
try {
|
|
149
|
+
await server.serve();
|
|
150
|
+
} finally {
|
|
151
|
+
await rm(dirname(otlpPath), { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/cli.ts
|
|
97
156
|
function parseArgs(argv) {
|
|
98
157
|
const a = {
|
|
99
158
|
command: argv[0] === "--help" || argv[0] === "-h" ? "help" : argv[0] ?? "help",
|
|
@@ -296,22 +355,14 @@ function parseArgs(argv) {
|
|
|
296
355
|
}
|
|
297
356
|
return a;
|
|
298
357
|
}
|
|
299
|
-
var OTLP_INPUT_COMMANDS = /* @__PURE__ */ new Set(["analyze", "facts", "investigate", "improve", "ask", "stream", "validate"]);
|
|
300
|
-
function
|
|
301
|
-
if (args.otlp === void 0
|
|
302
|
-
if (args.
|
|
358
|
+
var OTLP_INPUT_COMMANDS = /* @__PURE__ */ new Set(["analyze", "facts", "investigate", "improve", "ask", "stream", "validate", "mcp"]);
|
|
359
|
+
function validateOtlpSelection(args) {
|
|
360
|
+
if (args.otlp === void 0) return args;
|
|
361
|
+
if (!OTLP_INPUT_COMMANDS.has(args.command)) {
|
|
303
362
|
throw new Error(
|
|
304
|
-
`${args.command}
|
|
363
|
+
`${args.command} does not read --otlp. --otlp reads OTLP-JSONL on ${[...OTLP_INPUT_COMMANDS].join(", ")}; use --otlp-out <path> to write the artifact.`
|
|
305
364
|
);
|
|
306
365
|
}
|
|
307
|
-
console.error(
|
|
308
|
-
`warning: --otlp is deprecated on \`${args.command}\` and will be removed in 0.12. --otlp now READS OTLP-JSONL; use --otlp-out <path> to write the artifact. Treating it as --otlp-out for this run.`
|
|
309
|
-
);
|
|
310
|
-
return { ...args, otlpOut: args.otlp, otlp: void 0 };
|
|
311
|
-
}
|
|
312
|
-
function validateOtlpSelection(raw) {
|
|
313
|
-
const args = migrateWritingOtlpFlag(raw);
|
|
314
|
-
if (args.otlp === void 0) return args;
|
|
315
366
|
if (args.input) throw new Error("--otlp cannot be combined with an input file");
|
|
316
367
|
if (args.session) throw new Error("--otlp reads a file and cannot be combined with --session");
|
|
317
368
|
if (args.supervisorRunDir) throw new Error("--otlp cannot be combined with --supervisor-run-dir");
|
|
@@ -686,6 +737,73 @@ async function cmdBundle(args) {
|
|
|
686
737
|
`session bundle (${manifest.view} view) \u2192 ${result.directory} (${manifest.files.length} file(s), ${manifest.ledgerSlices.length} ledger slice(s), ${manifest.absent.length} recorded absent)`
|
|
687
738
|
);
|
|
688
739
|
}
|
|
740
|
+
async function cmdMcp(args) {
|
|
741
|
+
if (args.help) {
|
|
742
|
+
process.stdout.write(`traces mcp \u2014 read-only MCP server over stdio for the selected traces
|
|
743
|
+
|
|
744
|
+
Usage:
|
|
745
|
+
traces mcp --otlp <file|dir> any OTLP-JSONL
|
|
746
|
+
traces mcp --harness claude-code --last 5 recent harness sessions (any selection flag)
|
|
747
|
+
|
|
748
|
+
Register with a client, for example:
|
|
749
|
+
claude mcp add traces -- traces mcp --otlp ./spans.otlp.jsonl
|
|
750
|
+
|
|
751
|
+
Every tool is read-only and idempotent and says so in its MCP annotations.
|
|
752
|
+
Spans (including causal links) and results are redacted, the server refuses
|
|
753
|
+
to start if the redacted spans are still UNSAFE or UNKNOWN, each result
|
|
754
|
+
carries an untrusted-text notice, and a result above 512 KiB is refused
|
|
755
|
+
rather than truncated. readSpanSource is not served: windowed reads of
|
|
756
|
+
original source bytes cannot be redacted.
|
|
757
|
+
`);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const selected = validateOtlpSelection(validateWorkflowSelection(applyCurrentSessionSelection(args)));
|
|
761
|
+
const collected = await collectSpans(selected);
|
|
762
|
+
if (collected.spans.length === 0) throw new Error("mcp: no spans found for the given selection");
|
|
763
|
+
warnIncompleteWorkflow(collected.workflow);
|
|
764
|
+
process.stderr.write(`traces mcp: serving ${collected.spans.length} span(s) over stdio
|
|
765
|
+
`);
|
|
766
|
+
await serveTraceMcp({ spans: collected.spans });
|
|
767
|
+
}
|
|
768
|
+
async function cmdDiff(argv) {
|
|
769
|
+
const inputs = [];
|
|
770
|
+
const kinds = [];
|
|
771
|
+
let format = "text";
|
|
772
|
+
for (let i = 0; i < argv.length; i++) {
|
|
773
|
+
const arg = argv[i];
|
|
774
|
+
if (arg === "--help" || arg === "-h") {
|
|
775
|
+
process.stdout.write(`traces diff \u2014 where two runs of one task first diverge
|
|
776
|
+
|
|
777
|
+
Usage:
|
|
778
|
+
traces diff <a> <b> [--kind TOOL]... [--format text|json]
|
|
779
|
+
|
|
780
|
+
Each side is an OTLP file or directory, any file convert reads, file#<trace id>,
|
|
781
|
+
or file#branch=<id> for one arm (agent.branch.id or agent.branch.arm, and every
|
|
782
|
+
span below it \u2014 refused if the label names more than one subtree). Steps pair
|
|
783
|
+
by span id, then position with the same name and kind, then name and kind
|
|
784
|
+
anywhere. Exit 0 when the runs agree on outcome, 1 when they diverge, 2 when a
|
|
785
|
+
side cannot be read, 3 when the steps line up with no divergence but neither
|
|
786
|
+
side carries a definitive OK/ERROR status or a TOOL step \u2014 there is no
|
|
787
|
+
recorded outcome to call agreement on.
|
|
788
|
+
`);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (arg === "--format") format = argv[++i] ?? "";
|
|
792
|
+
else if (arg === "--kind") kinds.push(argv[++i] ?? "");
|
|
793
|
+
else if (arg.startsWith("--")) throw new Error(`diff: unknown flag ${arg}`);
|
|
794
|
+
else inputs.push(arg);
|
|
795
|
+
}
|
|
796
|
+
if (inputs.length !== 2) {
|
|
797
|
+
throw new Error("diff needs two runs: traces diff <a> <b> (a file, a directory, file#<trace id> or file#branch=<id>)");
|
|
798
|
+
}
|
|
799
|
+
if (format !== "json" && format !== "text") throw new Error(`diff: --format must be json or text, got "${format}"`);
|
|
800
|
+
if (kinds.some((kind) => !kind)) throw new Error("diff: --kind needs a span kind, e.g. --kind TOOL");
|
|
801
|
+
const report = await diffRuns(inputs[0], inputs[1], { kinds });
|
|
802
|
+
process.stdout.write(format === "json" ? `${JSON.stringify(report, null, 2)}
|
|
803
|
+
` : renderRunDiff(report));
|
|
804
|
+
if (report.diff.firstDivergence) process.exitCode = 1;
|
|
805
|
+
else if (report.caveat) process.exitCode = 3;
|
|
806
|
+
}
|
|
689
807
|
async function cmdBundleVerify(argv) {
|
|
690
808
|
let dir;
|
|
691
809
|
let format = "text";
|
|
@@ -1296,11 +1414,11 @@ async function cmdUpload(args) {
|
|
|
1296
1414
|
const redactorParts = args.redactorCmd?.split(/\s+/).filter(Boolean) ?? [];
|
|
1297
1415
|
const redactor = redactorParts[0] ? commandRedactor({ command: redactorParts[0], args: redactorParts.slice(1) }) : void 0;
|
|
1298
1416
|
const candidates = plan.items;
|
|
1299
|
-
const
|
|
1417
|
+
const byDetector = {};
|
|
1300
1418
|
let totalRedactions = 0;
|
|
1301
1419
|
for (const i of candidates) {
|
|
1302
1420
|
totalRedactions += i.redaction.redactionCount;
|
|
1303
|
-
for (const [r, n] of Object.entries(i.redaction.
|
|
1421
|
+
for (const [r, n] of Object.entries(i.redaction.byDetector)) byDetector[r] = (byDetector[r] ?? 0) + n;
|
|
1304
1422
|
}
|
|
1305
1423
|
const w = (s) => process.stderr.write(s);
|
|
1306
1424
|
w(`
|
|
@@ -1308,7 +1426,7 @@ Window: since ${new Date(sinceMs).toISOString()}
|
|
|
1308
1426
|
`);
|
|
1309
1427
|
w(`Sessions found: ${plan.items.length} \xB7 final privacy-mode dedup runs before upload
|
|
1310
1428
|
`);
|
|
1311
|
-
w(`PII/secrets redacted: ${totalRedactions}${Object.keys(
|
|
1429
|
+
w(`PII/secrets redacted: ${totalRedactions}${Object.keys(byDetector).length ? ` (${Object.entries(byDetector).map(([r, n]) => `${r}:${n}`).join(", ")})` : ""}
|
|
1312
1430
|
`);
|
|
1313
1431
|
for (const i of candidates.slice(0, 25)) {
|
|
1314
1432
|
w(` + [${i.ref.harness}] ${i.ref.sessionId.slice(0, 8)} ${i.spans.length} spans ${i.redaction.redactionCount} redacted ${i.ref.cwd ?? ""}
|
|
@@ -1322,7 +1440,8 @@ Window: since ${new Date(sinceMs).toISOString()}
|
|
|
1322
1440
|
}
|
|
1323
1441
|
if (args.dryRun) {
|
|
1324
1442
|
const res2 = await executeUpload(plan, { dryRun: true, otlpOut: args.otlpOut, stripContent: args.noContent, redactor });
|
|
1325
|
-
|
|
1443
|
+
reportRefused(res2.refused);
|
|
1444
|
+
console.log(`dry run: ${candidates.length - res2.skippedSessions - res2.refused.length} session(s), ${totalRedactions} redaction(s). Redacted OTLP -> ${res2.otlpPath}`);
|
|
1326
1445
|
console.log("No upload performed. Set TANGLE_INGEST_URL / TANGLE_INGEST_API_KEY / TANGLE_TENANT_ID and drop --dry-run to send.");
|
|
1327
1446
|
return;
|
|
1328
1447
|
}
|
|
@@ -1335,10 +1454,79 @@ Window: since ${new Date(sinceMs).toISOString()}
|
|
|
1335
1454
|
}
|
|
1336
1455
|
const res = await executeUpload(plan, { log: (m) => w(`${m}
|
|
1337
1456
|
`), stripContent: args.noContent, redactor });
|
|
1457
|
+
reportRefused(res.refused);
|
|
1338
1458
|
console.log(
|
|
1339
|
-
`Uploaded ${res.uploadedSessions} session(s), ${res.acceptedSpans} spans accepted, ${res.redactionCount} redaction(s); ${res.skippedSessions} already-uploaded skipped.`
|
|
1459
|
+
`Uploaded ${res.uploadedSessions} session(s), ${res.acceptedSpans} spans accepted, ${res.redactionCount} redaction(s); ${res.skippedSessions} already-uploaded skipped; ${res.refused.length} refused.`
|
|
1340
1460
|
);
|
|
1341
1461
|
}
|
|
1462
|
+
function reportRefused(refused) {
|
|
1463
|
+
for (const { harness, sessionId, verdict } of refused) {
|
|
1464
|
+
process.stderr.write(` REFUSED [${harness}] ${sessionId.slice(0, 8)}: ${verdict.status}
|
|
1465
|
+
`);
|
|
1466
|
+
for (const finding of verdict.findings.filter((f) => f.severity === "error")) {
|
|
1467
|
+
process.stderr.write(` ${finding.category}/${finding.detector} x${finding.count} at ${finding.paths.join(", ")}
|
|
1468
|
+
`);
|
|
1469
|
+
}
|
|
1470
|
+
for (const entry of verdict.unreadable) process.stderr.write(` unread ${entry}
|
|
1471
|
+
`);
|
|
1472
|
+
}
|
|
1473
|
+
if (refused.length > 0) {
|
|
1474
|
+
process.stderr.write(`${refused.length} session(s) refused by the share-safety check and kept on this machine.
|
|
1475
|
+
`);
|
|
1476
|
+
process.exitCode = 1;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
async function cmdVerifySafe(argv) {
|
|
1480
|
+
let target;
|
|
1481
|
+
let format = "text";
|
|
1482
|
+
let profile = "share";
|
|
1483
|
+
const knownSecrets = [];
|
|
1484
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1485
|
+
const arg = argv[i];
|
|
1486
|
+
if (arg === "--format") format = argv[++i] ?? "";
|
|
1487
|
+
else if (arg === "--profile") profile = argv[++i] ?? "";
|
|
1488
|
+
else if (arg === "--known-secret-env") {
|
|
1489
|
+
const name = argv[++i] ?? "";
|
|
1490
|
+
const value = process.env[name];
|
|
1491
|
+
if (!value) throw new Error(`verify-safe: environment variable ${name || "(missing name)"} is not set`);
|
|
1492
|
+
knownSecrets.push(value);
|
|
1493
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
1494
|
+
usageVerifySafe();
|
|
1495
|
+
return;
|
|
1496
|
+
} else if (arg.startsWith("--")) throw new Error(`verify-safe: unknown flag ${arg}`);
|
|
1497
|
+
else if (target === void 0) target = arg;
|
|
1498
|
+
else throw new Error(`verify-safe: unexpected argument ${arg}`);
|
|
1499
|
+
}
|
|
1500
|
+
if (!target) throw new Error("verify-safe needs a file or directory: traces verify-safe <file|dir>");
|
|
1501
|
+
if (format !== "json" && format !== "text") throw new Error(`verify-safe: --format must be json or text, got "${format}"`);
|
|
1502
|
+
if (profile !== "default" && profile !== "share" && profile !== "strict") {
|
|
1503
|
+
throw new Error(`verify-safe: --profile must be default, share or strict, got "${profile}"`);
|
|
1504
|
+
}
|
|
1505
|
+
const result = await verifySafe(target, { profile, knownSecrets });
|
|
1506
|
+
process.stdout.write(format === "json" ? `${JSON.stringify(result, null, 2)}
|
|
1507
|
+
` : `${formatVerdict(result)}
|
|
1508
|
+
`);
|
|
1509
|
+
process.exitCode = verdictExitCode(result);
|
|
1510
|
+
}
|
|
1511
|
+
function usageVerifySafe() {
|
|
1512
|
+
console.log(`traces verify-safe <file|dir> [--profile default|share|strict] [--known-secret-env NAME]... [--format text|json]
|
|
1513
|
+
|
|
1514
|
+
Check whether files are safe to share, without changing them. A directory is read
|
|
1515
|
+
recursively. .json files are parsed whole, .jsonl and .ndjson line by line, and
|
|
1516
|
+
any other file is scanned as text.
|
|
1517
|
+
|
|
1518
|
+
SAFE nothing found exit 0
|
|
1519
|
+
SAFE_WITH_WARNINGS only warnings (raw content, identifiers, media) exit 0
|
|
1520
|
+
UNSAFE a credential, or personal data under share/strict exit 1
|
|
1521
|
+
UNKNOWN a file, line or byte range could not be read exit 2
|
|
1522
|
+
|
|
1523
|
+
--profile default | share (default) | strict; see agent-eval docs/redaction.md
|
|
1524
|
+
--known-secret-env name of an environment variable holding a secret to look for in
|
|
1525
|
+
any encoding (repeatable); the value is never printed
|
|
1526
|
+
--format text (default) | json
|
|
1527
|
+
|
|
1528
|
+
Findings name the file, line and JSON Pointer, never the matched value.`);
|
|
1529
|
+
}
|
|
1342
1530
|
function usageExport() {
|
|
1343
1531
|
console.log(`traces export: convert trace evidence files to OpenInference JSONL
|
|
1344
1532
|
|
|
@@ -1365,7 +1553,7 @@ Examples:
|
|
|
1365
1553
|
halo spans.openinference.jsonl --prompt "Analyze this trace slice" --max-turns 1
|
|
1366
1554
|
|
|
1367
1555
|
Safety:
|
|
1368
|
-
export runs the same local
|
|
1556
|
+
export runs the same local redaction (agent-eval's redaction core) used by upload before writing spans.
|
|
1369
1557
|
--metadata must be a JSON object; --attr key=value is repeatable and overrides matching metadata keys.`);
|
|
1370
1558
|
}
|
|
1371
1559
|
function usageImportCodeTraceBench() {
|
|
@@ -1447,7 +1635,8 @@ Commands:
|
|
|
1447
1635
|
facts Print the deterministic session-facts sheet for the selected
|
|
1448
1636
|
sessions: tool calls excluding synthesized spans, subagent spawns
|
|
1449
1637
|
with task names, human turns in order, the final message per task,
|
|
1450
|
-
changed paths, first/last record times,
|
|
1638
|
+
changed paths, first/last record times, the harness token total, and
|
|
1639
|
+
the first failure by agent-eval's fixed precedence (or none).
|
|
1451
1640
|
No model call, no budget, $0. Every fact names the span ids it came
|
|
1452
1641
|
from; a fact the spans cannot support is null with its reason.
|
|
1453
1642
|
--format json (default) or text (exit 1 when a session cannot be read)
|
|
@@ -1458,6 +1647,17 @@ Commands:
|
|
|
1458
1647
|
ledger sliced to the session window, git log, and a sha256 manifest
|
|
1459
1648
|
(needs --session <id|path> and --out <new-dir>). This is the FULL
|
|
1460
1649
|
view: it holds the session's own words, for a reader who cites them
|
|
1650
|
+
mcp Serve the selected traces to an MCP client over stdio: 7 read-only,
|
|
1651
|
+
idempotent trace tools; spans and results redacted, results capped
|
|
1652
|
+
and marked untrusted (traces mcp --help)
|
|
1653
|
+
diff <a> <b>
|
|
1654
|
+
Pair two runs' steps (by span id, then position with the same name
|
|
1655
|
+
and kind, then name and kind anywhere) and mark the first divergence:
|
|
1656
|
+
changed, replaced, only-in-a, only-in-b or reordered. Each side is an
|
|
1657
|
+
OTLP file or directory, any file convert reads, file#<trace id>, or
|
|
1658
|
+
file#branch=<id> for one arm (agent.branch.id or agent.branch.arm).
|
|
1659
|
+
--kind TOOL (repeatable) keeps only steps of that span kind.
|
|
1660
|
+
Exit 0 agree, 1 diverge, 2 unreadable (--format text|json)
|
|
1461
1661
|
bundle verify <bundle-dir>
|
|
1462
1662
|
Check a bundle against its manifest: every listed file present with
|
|
1463
1663
|
its recorded size and SHA-256, no unlisted or non-regular file beside
|
|
@@ -1487,7 +1687,11 @@ Commands:
|
|
|
1487
1687
|
stream Emit JSONL trace stream events for live visualizers or replay
|
|
1488
1688
|
watch Online observer: tail active sessions, notify on loops + semantic findings
|
|
1489
1689
|
(watch <target> tails ONE run tree instead: a run directory or a span file)
|
|
1490
|
-
upload Redact + upload sessions in a time window to the Tangle Intelligence Platform
|
|
1690
|
+
upload Redact + upload sessions in a time window to the Tangle Intelligence Platform;
|
|
1691
|
+
a session whose final events are UNSAFE or UNKNOWN is refused (exit 1)
|
|
1692
|
+
verify-safe <file|dir>
|
|
1693
|
+
Say whether files are safe to share: SAFE, SAFE_WITH_WARNINGS, UNSAFE
|
|
1694
|
+
(exit 1) or UNKNOWN (exit 2). Read-only (--help for flags)
|
|
1491
1695
|
|
|
1492
1696
|
Options:
|
|
1493
1697
|
--harness <id> Harness or alias (default: claude-code). Known: ${knownHarnesses().join(", ")}
|
|
@@ -1513,9 +1717,8 @@ Options:
|
|
|
1513
1717
|
A directory reads the OTLP files under it \u2014 only the otlp/
|
|
1514
1718
|
subdirectory when the producer made one \u2014 and names the JSONL
|
|
1515
1719
|
that is not OTLP instead of reading it as broken spans.
|
|
1516
|
-
Supported by: validate, analyze, investigate, improve, ask,
|
|
1517
|
-
|
|
1518
|
-
--otlp-out; it still works, with a warning, until 0.12.
|
|
1720
|
+
Supported by: validate, analyze, facts, investigate, improve, ask,
|
|
1721
|
+
stream, mcp. Writing commands take --otlp-out.
|
|
1519
1722
|
--source-bundle <dir> Analyze a retained full bundle; explicitly grant source-field reads.
|
|
1520
1723
|
Available for analyze, investigate, improve, and ask.
|
|
1521
1724
|
--otlp-out <path> WRITE the OTLP-JSONL artifact here (also evidence
|
|
@@ -1567,7 +1770,7 @@ Options:
|
|
|
1567
1770
|
--min-loop <n> Min identical repeated calls to flag a loop (default 3)
|
|
1568
1771
|
--dry-run upload: redact + dedup + preview, write OTLP, but do NOT send
|
|
1569
1772
|
--no-content upload: strip prompt/response text; send metadata only
|
|
1570
|
-
--redactor <cmd> upload: external PII scrubber (JSON array stdin\u2192stdout) after the
|
|
1773
|
+
--redactor <cmd> upload: external PII scrubber (JSON array stdin\u2192stdout) after the redaction core
|
|
1571
1774
|
--yes, -y upload: skip the confirmation prompt
|
|
1572
1775
|
--version, -v Print the installed traces version
|
|
1573
1776
|
--help, -h Show help (use \`traces validate --help\`, \`traces export --help\`,
|
|
@@ -1585,7 +1788,7 @@ Upload env: TANGLE_INGEST_URL (or TANGLE_ORCHESTRATOR_URL), TANGLE_INGEST_API_KE
|
|
|
1585
1788
|
async function main() {
|
|
1586
1789
|
const rawArgs = process.argv.slice(2);
|
|
1587
1790
|
if (rawArgs[0] === "--version" || rawArgs[0] === "-v" || rawArgs[0] === "version") {
|
|
1588
|
-
console.log(`traces ${
|
|
1791
|
+
console.log(`traces ${tracesVersion()}`);
|
|
1589
1792
|
return;
|
|
1590
1793
|
}
|
|
1591
1794
|
if (rawArgs[0] === "replay-verify") {
|
|
@@ -1600,10 +1803,28 @@ async function main() {
|
|
|
1600
1803
|
await cmdVerifyFindings(rawArgs.slice(1));
|
|
1601
1804
|
return;
|
|
1602
1805
|
}
|
|
1806
|
+
if (rawArgs[0] === "mcp") {
|
|
1807
|
+
await cmdMcp(parseArgs(rawArgs));
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
if (rawArgs[0] === "diff") {
|
|
1811
|
+
try {
|
|
1812
|
+
await cmdDiff(rawArgs.slice(1));
|
|
1813
|
+
} catch (err) {
|
|
1814
|
+
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
|
|
1815
|
+
`);
|
|
1816
|
+
process.exitCode = 2;
|
|
1817
|
+
}
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1603
1820
|
if (rawArgs[0] === "bundle" && rawArgs[1] === "verify") {
|
|
1604
1821
|
await cmdBundleVerify(rawArgs.slice(2));
|
|
1605
1822
|
return;
|
|
1606
1823
|
}
|
|
1824
|
+
if (rawArgs[0] === "verify-safe") {
|
|
1825
|
+
await cmdVerifySafe(rawArgs.slice(1));
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1607
1828
|
const parsedArgs = parseArgs(rawArgs);
|
|
1608
1829
|
if (parsedArgs.help) {
|
|
1609
1830
|
if (parsedArgs.command === "import-codetracebench" || parsedArgs.command === "help" && parsedArgs.input === "import-codetracebench") usageImportCodeTraceBench();
|