@tangle-network/traces 0.11.4 → 0.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -2
- package/dist/{chunk-AGPOIWPQ.js → chunk-MEAEXKUV.js} +2475 -316
- package/dist/cli.js +226 -65
- package/dist/index.d.ts +738 -4
- package/dist/index.js +68 -74
- package/docs/replay-verify.md +17 -0
- package/docs/trace-analysts.md +67 -1
- package/examples/external-engines.ts +14 -1
- package/package.json +6 -5
- package/skills/build-trace-analyst/SKILL.md +13 -0
- package/skills/inspect-agent-traces/SKILL.md +30 -4
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
ATTR,
|
|
5
|
-
CORRUPTION_RECEIPT_DISPLAY_LIMIT,
|
|
3
|
+
DEFAULT_SANDBOX_BASE_URL,
|
|
6
4
|
appendAll,
|
|
5
|
+
assembleSessionBundle,
|
|
7
6
|
buildPolicyEvidenceRecord,
|
|
8
7
|
buildSessionIndexFromRows,
|
|
9
8
|
cmdReplayVerify,
|
|
10
9
|
cmdReplayVerifyBatch,
|
|
10
|
+
cmdVerifyFindings,
|
|
11
11
|
collectSessionSelection,
|
|
12
12
|
commandAnalyzer,
|
|
13
13
|
commandRedactor,
|
|
14
|
-
describeSessionRelationship,
|
|
15
14
|
executeUpload,
|
|
16
15
|
exportTraceEvidenceFile,
|
|
17
16
|
fileRunContextSupervisorRunReader,
|
|
@@ -25,13 +24,17 @@ import {
|
|
|
25
24
|
loadTracesConfig,
|
|
26
25
|
locateSessions,
|
|
27
26
|
mergeTracesConfig,
|
|
27
|
+
parseCorpusFlag,
|
|
28
28
|
parseSession,
|
|
29
29
|
parseSince,
|
|
30
30
|
planUpload,
|
|
31
|
+
primeAnalyzer,
|
|
32
|
+
projectSessionBundle,
|
|
31
33
|
readOtlpInput,
|
|
32
34
|
readSessionIndexFile,
|
|
33
35
|
renderInspectionReport,
|
|
34
36
|
renderValidation,
|
|
37
|
+
renderVerifiedFindingsSection,
|
|
35
38
|
resolveAdapter,
|
|
36
39
|
resolveRunWatchTarget,
|
|
37
40
|
runTraceImprovement,
|
|
@@ -42,11 +45,12 @@ import {
|
|
|
42
45
|
serializeSessionIndex,
|
|
43
46
|
serializeSpans,
|
|
44
47
|
serializeTraceStreamEvent,
|
|
45
|
-
|
|
48
|
+
sessionReportSource,
|
|
46
49
|
streamSessions,
|
|
47
50
|
totalAgenticFailureMessage,
|
|
48
51
|
traceStreamEventsFromSpans,
|
|
49
52
|
validationExitCode,
|
|
53
|
+
verifyFindings,
|
|
50
54
|
watchRunTarget,
|
|
51
55
|
watchSessions,
|
|
52
56
|
writeInspectionReportFile,
|
|
@@ -54,7 +58,7 @@ import {
|
|
|
54
58
|
writePolicyEvidenceFile,
|
|
55
59
|
writeSessionIndexFile,
|
|
56
60
|
writeTraceEvidenceExportFile
|
|
57
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-MEAEXKUV.js";
|
|
58
62
|
|
|
59
63
|
// src/cli.ts
|
|
60
64
|
import { readFileSync } from "fs";
|
|
@@ -69,6 +73,71 @@ import {
|
|
|
69
73
|
rollupSupervisorRuns
|
|
70
74
|
} from "@tangle-network/agent-eval/supervisor-run";
|
|
71
75
|
import { createDspyRlmTraceEngine } from "@tangle-network/agent-eval/analyst";
|
|
76
|
+
|
|
77
|
+
// src/analyst-model-call.ts
|
|
78
|
+
import {
|
|
79
|
+
callLlm,
|
|
80
|
+
costReceiptFromLlm,
|
|
81
|
+
costReceiptFromLlmError,
|
|
82
|
+
LlmCallError
|
|
83
|
+
} from "@tangle-network/agent-eval";
|
|
84
|
+
var PROVIDER_ERROR_BODY_LIMIT = 2e3;
|
|
85
|
+
function createAnalystModelCall(opts) {
|
|
86
|
+
const { apiKey, baseUrl } = opts;
|
|
87
|
+
return async ({ request, callId, signal }) => {
|
|
88
|
+
try {
|
|
89
|
+
const req = structuredClone(request);
|
|
90
|
+
const response = await callLlm(req, {
|
|
91
|
+
apiKey,
|
|
92
|
+
baseUrl,
|
|
93
|
+
signal,
|
|
94
|
+
idempotencyKey: callId
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
succeeded: true,
|
|
98
|
+
response,
|
|
99
|
+
receipt: costReceiptFromLlm(response),
|
|
100
|
+
execution: {
|
|
101
|
+
callId,
|
|
102
|
+
baseUrl,
|
|
103
|
+
requestedModel: req.model,
|
|
104
|
+
servedModel: response.servedModel ?? null,
|
|
105
|
+
finishReason: response.finishReason ?? null,
|
|
106
|
+
durationMs: response.durationMs
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
111
|
+
const aborted = signal.aborted;
|
|
112
|
+
const message = aborted ? `AbortError: ${err.message}` : err.message;
|
|
113
|
+
const detail = err instanceof LlmCallError ? { status: err.status, body: err.body.slice(0, PROVIDER_ERROR_BODY_LIMIT) } : {};
|
|
114
|
+
return {
|
|
115
|
+
succeeded: false,
|
|
116
|
+
error: message,
|
|
117
|
+
// costReceiptFromLlmError recovers the provider receipt when the
|
|
118
|
+
// response completed but violated the contract; otherwise usage and
|
|
119
|
+
// cost stay explicitly unknown.
|
|
120
|
+
receipt: costReceiptFromLlmError(err) ?? {
|
|
121
|
+
model: request.model,
|
|
122
|
+
inputTokens: 0,
|
|
123
|
+
outputTokens: 0,
|
|
124
|
+
costUnknown: true,
|
|
125
|
+
usageUnknown: true
|
|
126
|
+
},
|
|
127
|
+
execution: {
|
|
128
|
+
callId,
|
|
129
|
+
baseUrl,
|
|
130
|
+
requestedModel: request.model,
|
|
131
|
+
aborted,
|
|
132
|
+
error: message,
|
|
133
|
+
...detail
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/cli.ts
|
|
72
141
|
var DEFAULT_ANALYST_MODEL = "gpt-5-mini";
|
|
73
142
|
var TANGLE_ROUTER_BASE_URL = "https://router.tangle.tools/v1";
|
|
74
143
|
function packageVersion() {
|
|
@@ -102,7 +171,9 @@ function parseArgs(argv) {
|
|
|
102
171
|
noSpans: false,
|
|
103
172
|
noFindings: false,
|
|
104
173
|
attrs: [],
|
|
105
|
-
concurrency: 4
|
|
174
|
+
concurrency: 4,
|
|
175
|
+
verifyFindings: false,
|
|
176
|
+
replayCorpora: []
|
|
106
177
|
};
|
|
107
178
|
for (let i = 1; i < argv.length; i++) {
|
|
108
179
|
const arg = argv[i];
|
|
@@ -178,6 +249,9 @@ function parseArgs(argv) {
|
|
|
178
249
|
case "--mode":
|
|
179
250
|
a.mode = next();
|
|
180
251
|
break;
|
|
252
|
+
case "--view":
|
|
253
|
+
a.view = next();
|
|
254
|
+
break;
|
|
181
255
|
case "--metadata":
|
|
182
256
|
a.metadata = next();
|
|
183
257
|
break;
|
|
@@ -222,6 +296,17 @@ function parseArgs(argv) {
|
|
|
222
296
|
if (v) a.analyzers.push(v);
|
|
223
297
|
break;
|
|
224
298
|
}
|
|
299
|
+
case "--verify-findings":
|
|
300
|
+
a.verifyFindings = true;
|
|
301
|
+
break;
|
|
302
|
+
case "--replay-corpus": {
|
|
303
|
+
const v = next();
|
|
304
|
+
if (v) a.replayCorpora.push(v);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case "--verify-out":
|
|
308
|
+
a.verifyOut = next();
|
|
309
|
+
break;
|
|
225
310
|
case "--analyzer-prompt":
|
|
226
311
|
a.analyzerPrompt = next();
|
|
227
312
|
break;
|
|
@@ -278,6 +363,7 @@ var CURRENT_SESSION_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
278
363
|
"convert",
|
|
279
364
|
"index",
|
|
280
365
|
"evidence",
|
|
366
|
+
"bundle",
|
|
281
367
|
"stream"
|
|
282
368
|
]);
|
|
283
369
|
var WORKFLOW_COMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -306,6 +392,12 @@ function applyCurrentSessionSelection(args) {
|
|
|
306
392
|
if (!sessionId) throw new Error("--current requires CODEX_THREAD_ID from the active Codex session");
|
|
307
393
|
return { ...args, session: sessionId };
|
|
308
394
|
}
|
|
395
|
+
function validateViewSelection(args) {
|
|
396
|
+
if (args.view === void 0 || args.command === "bundle-view") return args;
|
|
397
|
+
throw new Error(
|
|
398
|
+
`--view is not supported by ${args.command}. A view is a projection of an assembled bundle: run \`traces bundle ... --out <dir>\` first, then \`traces bundle-view <dir> --view evidence-only --out <view-dir>\`.`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
309
401
|
function validateWorkflowSelection(args) {
|
|
310
402
|
if (!args.workflow && !args.latestTurn) return args;
|
|
311
403
|
if (!WORKFLOW_COMMANDS.has(args.command)) {
|
|
@@ -404,15 +496,19 @@ function buildAnalysisEngine(model, budgetUsd) {
|
|
|
404
496
|
const baseUrl = process.env.OPENAI_BASE_URL || (tangleKey ? TANGLE_ROUTER_BASE_URL : "https://api.openai.com/v1");
|
|
405
497
|
const python = process.env.TRACES_PYTHON;
|
|
406
498
|
return createDspyRlmTraceEngine({
|
|
407
|
-
apiKey,
|
|
408
|
-
baseUrl
|
|
499
|
+
call: createAnalystModelCall({ apiKey, baseUrl }),
|
|
500
|
+
callRef: `traces-cli:${baseUrl}#${model}`,
|
|
501
|
+
recordExecution: (observation) => {
|
|
502
|
+
analystLog(
|
|
503
|
+
`[analyst] model call ${observation.sequence} ${observation.succeeded ? "ok" : "FAIL"} ${observation.model}`,
|
|
504
|
+
observation.succeeded ? void 0 : { error: observation.error }
|
|
505
|
+
);
|
|
506
|
+
},
|
|
409
507
|
model,
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
// reservation, and the fail-closed ledger then refuses every later call
|
|
415
|
-
// in the run.
|
|
508
|
+
// Pinned, not defaulted: one findings array from a current coding model
|
|
509
|
+
// needs this cap, and glm-5.2 counts reasoning tokens in
|
|
510
|
+
// completion_tokens — a smaller cap breaches its cost reservation and the
|
|
511
|
+
// fail-closed ledger then refuses every later call in the run.
|
|
416
512
|
maxOutputTokens: 16384,
|
|
417
513
|
// maxCostUsd defaults to $1 per analyst — a proxy-side ceiling separate
|
|
418
514
|
// from --budget. With the larger token cap the per-call reservation grows
|
|
@@ -448,47 +544,6 @@ ${adapter.harness}: ${refs.length} session(s)`);
|
|
|
448
544
|
}
|
|
449
545
|
}
|
|
450
546
|
}
|
|
451
|
-
function selectedSessionSource(ref, spans, sessionIdOverride) {
|
|
452
|
-
const root = spans.find((item) => item.parent_span_id === null) ?? spans[0];
|
|
453
|
-
const prompt = spans.find(
|
|
454
|
-
(item) => item.name === "user.prompt" && item.attributes[ACTOR_ATTR] === "human"
|
|
455
|
-
) ?? spans.find((item) => item.name === "user.prompt") ?? spans.find(
|
|
456
|
-
(item) => item.attributes["span.type"] === "interaction" && typeof item.attributes.content === "string"
|
|
457
|
-
);
|
|
458
|
-
const content = typeof prompt?.attributes.content === "string" ? prompt.attributes.content : "";
|
|
459
|
-
const firstLine = content.split(/\r?\n/, 1)[0].trim();
|
|
460
|
-
const subject = firstLine.length > 240 ? `${firstLine.slice(0, 240)}\u2026 [+${firstLine.length - 240} chars]` : firstLine;
|
|
461
|
-
const role = root?.attributes["traces.session.role"];
|
|
462
|
-
const parentSessionId = root?.attributes["traces.parent_session_id"];
|
|
463
|
-
const relationship = describeSessionRelationship(ref, spans);
|
|
464
|
-
const corruptionDigest = root?.attributes[ATTR.CORRUPTION_DIGEST];
|
|
465
|
-
const sessionId = [
|
|
466
|
-
sessionIdOverride,
|
|
467
|
-
root ? sessionIdFromAttributes(root.attributes) : void 0,
|
|
468
|
-
root?.trace_id,
|
|
469
|
-
ref.sessionId
|
|
470
|
-
].find((value) => typeof value === "string" && value.length > 0);
|
|
471
|
-
return {
|
|
472
|
-
sessionId,
|
|
473
|
-
path: ref.path,
|
|
474
|
-
subject,
|
|
475
|
-
role: role === "operator" || role === "child" ? role : "unknown",
|
|
476
|
-
...typeof parentSessionId === "string" ? { parentSessionId } : {},
|
|
477
|
-
childSessionIds: relationship.childSessionIds,
|
|
478
|
-
...relationship.depth !== void 0 ? { depth: relationship.depth } : {},
|
|
479
|
-
...relationship.agentNickname ? { agentNickname: relationship.agentNickname } : {},
|
|
480
|
-
...relationship.agentRole ? { agentRole: relationship.agentRole } : {},
|
|
481
|
-
...relationship.agentPath ? { agentPath: relationship.agentPath } : {},
|
|
482
|
-
...relationship.taskScope ? { taskScope: relationship.taskScope } : {},
|
|
483
|
-
...relationship.turnId ? { turnId: relationship.turnId } : {},
|
|
484
|
-
integrity: ref.integrity?.status ?? "complete",
|
|
485
|
-
...ref.integrity ? {
|
|
486
|
-
corruptionCount: ref.integrity.corruptions.length,
|
|
487
|
-
...typeof corruptionDigest === "string" ? { corruptionDigest } : {},
|
|
488
|
-
corruptions: ref.integrity.corruptions.slice(0, CORRUPTION_RECEIPT_DISPLAY_LIMIT)
|
|
489
|
-
} : {}
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
547
|
async function collectOtlpSpans(path) {
|
|
493
548
|
const input = await readOtlpInput(path);
|
|
494
549
|
if (input.spans.length === 0) {
|
|
@@ -510,7 +565,7 @@ async function collectOtlpSpans(path) {
|
|
|
510
565
|
spans: [...input.spans],
|
|
511
566
|
harness: "otlp",
|
|
512
567
|
cwds: [],
|
|
513
|
-
sources: [...byTrace].map(([traceId, spans]) =>
|
|
568
|
+
sources: [...byTrace].map(([traceId, spans]) => sessionReportSource({
|
|
514
569
|
harness: "otlp",
|
|
515
570
|
sessionId: traceId,
|
|
516
571
|
path: fileByTrace.get(traceId) ?? path,
|
|
@@ -540,7 +595,7 @@ async function collectSpans(args) {
|
|
|
540
595
|
for (const row of selection.rows) {
|
|
541
596
|
harnesses.add(row.adapter.harness);
|
|
542
597
|
appendAll(spans, row.spans);
|
|
543
|
-
sources.push(
|
|
598
|
+
sources.push(sessionReportSource(row.ref, row.spans));
|
|
544
599
|
if (row.ref.cwd) cwds.push(row.ref.cwd);
|
|
545
600
|
}
|
|
546
601
|
return {
|
|
@@ -659,6 +714,46 @@ async function cmdIndex(args) {
|
|
|
659
714
|
process.stdout.write(serializeSessionIndex(index));
|
|
660
715
|
}
|
|
661
716
|
}
|
|
717
|
+
async function cmdBundle(args) {
|
|
718
|
+
if (!args.session) {
|
|
719
|
+
throw new Error("bundle needs --session <id|path>; run `traces list` to pick a session ID");
|
|
720
|
+
}
|
|
721
|
+
if (!args.out) throw new Error("bundle needs --out <dir> \u2014 a new or empty directory for the bundle");
|
|
722
|
+
const { adapter, ref } = await resolveSelectedSession(args);
|
|
723
|
+
const result = await assembleSessionBundle({
|
|
724
|
+
adapter,
|
|
725
|
+
ref,
|
|
726
|
+
outDir: args.out,
|
|
727
|
+
minLoopOccurrences: args.minLoop,
|
|
728
|
+
log: analystLog
|
|
729
|
+
});
|
|
730
|
+
const { manifest } = result;
|
|
731
|
+
console.log(
|
|
732
|
+
`session bundle (${manifest.view} view) \u2192 ${result.directory} (${manifest.files.length} file(s), ${manifest.ledgerSlices.length} ledger slice(s), ${manifest.absent.length} recorded absent)`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
var PROJECTABLE_VIEWS = ["evidence-only"];
|
|
736
|
+
async function cmdBundleView(args) {
|
|
737
|
+
if (!args.input) {
|
|
738
|
+
throw new Error("bundle-view needs the bundle directory to project; run `traces bundle --out <dir>` first");
|
|
739
|
+
}
|
|
740
|
+
if (!args.view) {
|
|
741
|
+
throw new Error(`bundle-view needs --view <${PROJECTABLE_VIEWS.join("|")}>`);
|
|
742
|
+
}
|
|
743
|
+
if (!PROJECTABLE_VIEWS.includes(args.view)) {
|
|
744
|
+
throw new Error(`unknown view "${args.view}"; projectable views: ${PROJECTABLE_VIEWS.join(", ")}`);
|
|
745
|
+
}
|
|
746
|
+
if (!args.out) throw new Error("bundle-view needs --out <dir> \u2014 a new or empty directory for the view");
|
|
747
|
+
const result = await projectSessionBundle({
|
|
748
|
+
bundleDir: args.input,
|
|
749
|
+
outDir: args.out,
|
|
750
|
+
view: args.view
|
|
751
|
+
});
|
|
752
|
+
const { manifest, leakCheck } = result;
|
|
753
|
+
console.log(
|
|
754
|
+
`${manifest.view} view \u2192 ${result.directory} (${manifest.files.length} file(s) carried, ${manifest.excluded.length} excluded, ${leakCheck.matches} session-text match(es) against ${leakCheck.sourceSignatures} signature(s) from ${leakCheck.comparedSources.length} excluded source(s))`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
662
757
|
async function cmdInspect(args) {
|
|
663
758
|
if (!args.input) throw new Error("inspect needs an index file; run `traces index --out session-index.json` first");
|
|
664
759
|
const index = await readSessionIndexFile(args.input);
|
|
@@ -765,16 +860,43 @@ async function loadExportAttributes(args) {
|
|
|
765
860
|
async function cmdAnalyze(args) {
|
|
766
861
|
if (args.supervisorRunDir) return cmdAnalyzeSupervisorRun(args.supervisorRunDir, args);
|
|
767
862
|
const result = await investigate(args, { loadDefaultConfig: false });
|
|
863
|
+
let report = result.report;
|
|
864
|
+
let verifySummary = "";
|
|
865
|
+
if (args.verifyFindings) {
|
|
866
|
+
const run = await verifyAnalyzeFindings(args, result);
|
|
867
|
+
report = `${report}
|
|
868
|
+
${renderVerifiedFindingsSection(run)}`;
|
|
869
|
+
verifySummary = `, verified: ${run.counts.reproduced} reproduced / ${run.counts["fix-flipped"]} fix-flipped / ${run.counts.divergent} divergent / ${run.counts["not-replayable"]} not-replayable \u2192 ${run.out}`;
|
|
870
|
+
}
|
|
768
871
|
if (args.out) {
|
|
769
|
-
await saveReport(args.out,
|
|
872
|
+
await saveReport(args.out, report);
|
|
770
873
|
console.log(
|
|
771
|
-
`report \u2192 ${args.out} (${result.findings.length} findings, ${result.pipelines.stuckLoops.findings.length} loops, OTLP: ${result.otlpPath})`
|
|
874
|
+
`report \u2192 ${args.out} (${result.findings.length} findings, ${result.pipelines.stuckLoops.findings.length} loops, OTLP: ${result.otlpPath}${verifySummary})`
|
|
772
875
|
);
|
|
773
876
|
} else {
|
|
774
|
-
console.log(
|
|
877
|
+
console.log(report);
|
|
775
878
|
}
|
|
776
879
|
assertAgenticAnalystsRan(args, result.agenticPerAnalyst);
|
|
777
880
|
}
|
|
881
|
+
async function verifyAnalyzeFindings(args, result) {
|
|
882
|
+
if (args.replayCorpora.length === 0) {
|
|
883
|
+
throw new Error(
|
|
884
|
+
"analyze --verify-findings needs the executable trajectory source: pass --replay-corpus name=<labels.json>::<preparedDir> (repeatable). Harness sessions carry no docker_config, so findings cannot be replayed from the session store alone."
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (result.findings.length === 0) {
|
|
888
|
+
throw new Error("analyze --verify-findings: the analysis produced no findings to verify");
|
|
889
|
+
}
|
|
890
|
+
const out = args.verifyOut ?? (args.out ? `${args.out}.verify` : "traces-verify-findings");
|
|
891
|
+
return verifyFindings(result.findings, {
|
|
892
|
+
source: { kind: "corpus", corpora: args.replayCorpora.map(parseCorpusFlag) },
|
|
893
|
+
out,
|
|
894
|
+
apiKey: process.env.SANDBOX_API_KEY,
|
|
895
|
+
baseUrl: process.env.SANDBOX_API_URL ?? DEFAULT_SANDBOX_BASE_URL,
|
|
896
|
+
onProgress: (message) => process.stderr.write(`${message}
|
|
897
|
+
`)
|
|
898
|
+
});
|
|
899
|
+
}
|
|
778
900
|
async function cmdAnalyzeSupervisorRun(runDir, args) {
|
|
779
901
|
const runContextDirs = await findFileRunContextDirs(runDir);
|
|
780
902
|
const nested = runContextDirs.length > 0 ? [] : await findSupervisorRunDirs(runDir);
|
|
@@ -845,7 +967,7 @@ async function collectImportedSpans(args) {
|
|
|
845
967
|
spans: result.spans,
|
|
846
968
|
harness: result.format,
|
|
847
969
|
cwds: [],
|
|
848
|
-
sources: [...sessions].map(([sessionId, spans]) =>
|
|
970
|
+
sources: [...sessions].map(([sessionId, spans]) => sessionReportSource({
|
|
849
971
|
harness: result.format,
|
|
850
972
|
sessionId,
|
|
851
973
|
path: args.input,
|
|
@@ -863,7 +985,7 @@ function analystLog(msg, fields) {
|
|
|
863
985
|
}
|
|
864
986
|
function externalAnalyzersFromArgs(args) {
|
|
865
987
|
return args.analyzers.map(
|
|
866
|
-
(spec) => spec === "halo" ? haloAnalyzer({ defaultPrompt: args.analyzerPrompt, model: args.model }) : spec === "hodoscope" ? hodoscopeAnalyzer({ summarizeModel: args.model }) : commandAnalyzer({ name: spec, command: spec, args: (p, prompt) => prompt ? [p, prompt] : [p] })
|
|
988
|
+
(spec) => spec === "halo" ? haloAnalyzer({ defaultPrompt: args.analyzerPrompt, model: args.model }) : spec === "hodoscope" ? hodoscopeAnalyzer({ summarizeModel: args.model }) : spec === "prime" ? primeAnalyzer({ defaultPrompt: args.analyzerPrompt, ...args.model ? { model: args.model } : {} }) : commandAnalyzer({ name: spec, command: spec, args: (p, prompt) => prompt ? [p, prompt] : [p] })
|
|
867
989
|
);
|
|
868
990
|
}
|
|
869
991
|
async function cmdInvestigate(args) {
|
|
@@ -1267,6 +1389,17 @@ Commands:
|
|
|
1267
1389
|
improve Write findings, evidence, report, and canonical trace artifacts
|
|
1268
1390
|
convert Emit OTLP-JSONL only, to --otlp-out (HALO: use analyze --analyzer halo)
|
|
1269
1391
|
index Emit a reusable session index JSON for later investigation
|
|
1392
|
+
bundle Assemble one session's durable evidence directory: transcript +
|
|
1393
|
+
subagents, derived index/report/evidence/OTLP, the repo's .evolve
|
|
1394
|
+
ledger sliced to the session window, git log, and a sha256 manifest
|
|
1395
|
+
(needs --session <id|path> and --out <new-dir>). This is the FULL
|
|
1396
|
+
view: it holds the session's own words, for a reader who cites them
|
|
1397
|
+
bundle-view <bundle-dir>
|
|
1398
|
+
Project an assembled bundle into a narrower view for a consumer that
|
|
1399
|
+
must NOT read the session's own words. --view evidence-only carries
|
|
1400
|
+
the counted artifacts and structured ledger rows, excludes the
|
|
1401
|
+
transcript, the report, the spans and every prose file by name, and
|
|
1402
|
+
refuses to write a view whose text repeats an excluded source
|
|
1270
1403
|
inspect Read a session index and print ranked improvement findings
|
|
1271
1404
|
export Convert evidence/events files to OpenInference JSONL for HALO
|
|
1272
1405
|
import-codetracebench
|
|
@@ -1277,6 +1410,10 @@ Commands:
|
|
|
1277
1410
|
replay-verify-batch
|
|
1278
1411
|
Measure replayability + fix-flip rates across gold-labeled corpora:
|
|
1279
1412
|
arm A per replayable case, LLM-generated arm-B fixes (--help for flags)
|
|
1413
|
+
verify-findings
|
|
1414
|
+
Execute recorded analyst findings as sandbox replay proofs: each
|
|
1415
|
+
finding \u2192 reproduced | fix-flipped | divergent | not-replayable,
|
|
1416
|
+
with a receipt directory per finding (--help for flags)
|
|
1280
1417
|
evidence Emit compact session-evidence JSONL for downstream policy miners
|
|
1281
1418
|
stream Emit JSONL trace stream events for live visualizers or replay
|
|
1282
1419
|
watch Online observer: tail active sessions, notify on loops + semantic findings
|
|
@@ -1316,6 +1453,9 @@ Options:
|
|
|
1316
1453
|
--metadata <json> analyze/export file: attach JSON object fields as span attributes
|
|
1317
1454
|
--attr <k=v> analyze/export file: attach one span attribute (repeatable)
|
|
1318
1455
|
--mode <kind> stream: visualizer | findings | agent (default visualizer)
|
|
1456
|
+
--view <name> bundle-view: which projection to write. evidence-only is the
|
|
1457
|
+
writer's view; the full view is the bundle itself, so there is
|
|
1458
|
+
nothing to project into it
|
|
1319
1459
|
--replay, --once stream: scan once and exit (default for positional input / --session)
|
|
1320
1460
|
--once watch <target>: print ONE snapshot and exit, for scripts and agents
|
|
1321
1461
|
--no-spans stream: omit per-span pulse events
|
|
@@ -1329,8 +1469,17 @@ Options:
|
|
|
1329
1469
|
--model <id> Model for --llm, HALO, and Hodoscope (default for --llm: ${DEFAULT_ANALYST_MODEL})
|
|
1330
1470
|
--config <path> investigate/improve/stream: JS config with analysts, liveAnalysts, or external analyzers
|
|
1331
1471
|
--budget <usd> USD cap for agentic analysts
|
|
1332
|
-
--analyzer <id> analyze: also run halo, hodoscope, or an installed command (repeatable)
|
|
1472
|
+
--analyzer <id> analyze: also run halo, hodoscope, prime, or an installed command (repeatable)
|
|
1473
|
+
prime posts the full span projection to an OpenAI-compatible bridge
|
|
1474
|
+
(TRACES_PRIME_BRIDGE_URL, default http://localhost:4181;
|
|
1475
|
+
TRACES_PRIME_MODEL, default prime/zai/glm-5.2; TRACES_PRIME_TIMEOUT_MS)
|
|
1333
1476
|
--analyzer-prompt <p> analyze: prompt passed to external analyzers (default: diagnose)
|
|
1477
|
+
--verify-findings analyze: execute the findings as sandbox replay proofs and mark
|
|
1478
|
+
each VERIFIED (receipt path) or UNVERIFIABLE (reason). Needs
|
|
1479
|
+
--replay-corpus + a running sandbox (SANDBOX_API_KEY/_URL)
|
|
1480
|
+
--replay-corpus name=<labels.json>::<preparedDir>
|
|
1481
|
+
trajectory source for --verify-findings (repeatable)
|
|
1482
|
+
--verify-out <dir> receipt root for --verify-findings (default: <--out>.verify)
|
|
1334
1483
|
--interval <s> watch/stream: poll interval seconds (sessions 5, run tree 2)
|
|
1335
1484
|
--window <m> watch/stream: only sessions active in the last N minutes (default 30)
|
|
1336
1485
|
--min-loop <n> Min identical repeated calls to flag a loop (default 3)
|
|
@@ -1365,6 +1514,10 @@ async function main() {
|
|
|
1365
1514
|
await cmdReplayVerifyBatch(rawArgs.slice(1));
|
|
1366
1515
|
return;
|
|
1367
1516
|
}
|
|
1517
|
+
if (rawArgs[0] === "verify-findings") {
|
|
1518
|
+
await cmdVerifyFindings(rawArgs.slice(1));
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1368
1521
|
const parsedArgs = parseArgs(rawArgs);
|
|
1369
1522
|
if (parsedArgs.help) {
|
|
1370
1523
|
if (parsedArgs.command === "import-codetracebench" || parsedArgs.command === "help" && parsedArgs.input === "import-codetracebench") usageImportCodeTraceBench();
|
|
@@ -1373,7 +1526,9 @@ async function main() {
|
|
|
1373
1526
|
else usage();
|
|
1374
1527
|
return;
|
|
1375
1528
|
}
|
|
1376
|
-
const args = validateOtlpSelection(
|
|
1529
|
+
const args = validateOtlpSelection(
|
|
1530
|
+
validateViewSelection(validateWorkflowSelection(applyCurrentSessionSelection(parsedArgs)))
|
|
1531
|
+
);
|
|
1377
1532
|
switch (args.command) {
|
|
1378
1533
|
case "help":
|
|
1379
1534
|
if (args.input === "import-codetracebench") usageImportCodeTraceBench();
|
|
@@ -1402,6 +1557,12 @@ async function main() {
|
|
|
1402
1557
|
case "index":
|
|
1403
1558
|
await cmdIndex(args);
|
|
1404
1559
|
break;
|
|
1560
|
+
case "bundle":
|
|
1561
|
+
await cmdBundle(args);
|
|
1562
|
+
break;
|
|
1563
|
+
case "bundle-view":
|
|
1564
|
+
await cmdBundleView(args);
|
|
1565
|
+
break;
|
|
1405
1566
|
case "inspect":
|
|
1406
1567
|
await cmdInspect(args);
|
|
1407
1568
|
break;
|