@tangle-network/traces 0.11.5 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ACTOR_ATTR,
4
- ATTR,
5
- CORRUPTION_RECEIPT_DISPLAY_LIMIT,
6
3
  DEFAULT_SANDBOX_BASE_URL,
7
4
  appendAll,
5
+ assembleSessionBundle,
8
6
  buildPolicyEvidenceRecord,
9
7
  buildSessionIndexFromRows,
10
8
  cmdReplayVerify,
@@ -13,7 +11,6 @@ import {
13
11
  collectSessionSelection,
14
12
  commandAnalyzer,
15
13
  commandRedactor,
16
- describeSessionRelationship,
17
14
  executeUpload,
18
15
  exportTraceEvidenceFile,
19
16
  fileRunContextSupervisorRunReader,
@@ -31,6 +28,8 @@ import {
31
28
  parseSession,
32
29
  parseSince,
33
30
  planUpload,
31
+ primeAnalyzer,
32
+ projectSessionBundle,
34
33
  readOtlpInput,
35
34
  readSessionIndexFile,
36
35
  renderInspectionReport,
@@ -46,7 +45,7 @@ import {
46
45
  serializeSessionIndex,
47
46
  serializeSpans,
48
47
  serializeTraceStreamEvent,
49
- sessionIdFromAttributes,
48
+ sessionReportSource,
50
49
  streamSessions,
51
50
  totalAgenticFailureMessage,
52
51
  traceStreamEventsFromSpans,
@@ -59,7 +58,7 @@ import {
59
58
  writePolicyEvidenceFile,
60
59
  writeSessionIndexFile,
61
60
  writeTraceEvidenceExportFile
62
- } from "./chunk-VTYOCFGF.js";
61
+ } from "./chunk-CV22VGCK.js";
63
62
 
64
63
  // src/cli.ts
65
64
  import { readFileSync } from "fs";
@@ -74,6 +73,71 @@ import {
74
73
  rollupSupervisorRuns
75
74
  } from "@tangle-network/agent-eval/supervisor-run";
76
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
77
141
  var DEFAULT_ANALYST_MODEL = "gpt-5-mini";
78
142
  var TANGLE_ROUTER_BASE_URL = "https://router.tangle.tools/v1";
79
143
  function packageVersion() {
@@ -185,6 +249,9 @@ function parseArgs(argv) {
185
249
  case "--mode":
186
250
  a.mode = next();
187
251
  break;
252
+ case "--view":
253
+ a.view = next();
254
+ break;
188
255
  case "--metadata":
189
256
  a.metadata = next();
190
257
  break;
@@ -296,6 +363,7 @@ var CURRENT_SESSION_COMMANDS = /* @__PURE__ */ new Set([
296
363
  "convert",
297
364
  "index",
298
365
  "evidence",
366
+ "bundle",
299
367
  "stream"
300
368
  ]);
301
369
  var WORKFLOW_COMMANDS = /* @__PURE__ */ new Set([
@@ -324,6 +392,12 @@ function applyCurrentSessionSelection(args) {
324
392
  if (!sessionId) throw new Error("--current requires CODEX_THREAD_ID from the active Codex session");
325
393
  return { ...args, session: sessionId };
326
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
+ }
327
401
  function validateWorkflowSelection(args) {
328
402
  if (!args.workflow && !args.latestTurn) return args;
329
403
  if (!WORKFLOW_COMMANDS.has(args.command)) {
@@ -422,15 +496,19 @@ function buildAnalysisEngine(model, budgetUsd) {
422
496
  const baseUrl = process.env.OPENAI_BASE_URL || (tangleKey ? TANGLE_ROUTER_BASE_URL : "https://api.openai.com/v1");
423
497
  const python = process.env.TRACES_PYTHON;
424
498
  return createDspyRlmTraceEngine({
425
- apiKey,
426
- 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
+ },
427
507
  model,
428
- // agent-eval 0.139.3's engine defaults are tuned below what real runs
429
- // need. maxOutputTokens 4096 is under what current coding models emit for
430
- // one findings array: glm-5.2 counts reasoning tokens in
431
- // completion_tokens, the first oversized completion breaches its cost
432
- // reservation, and the fail-closed ledger then refuses every later call
433
- // 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.
434
512
  maxOutputTokens: 16384,
435
513
  // maxCostUsd defaults to $1 per analyst — a proxy-side ceiling separate
436
514
  // from --budget. With the larger token cap the per-call reservation grows
@@ -466,47 +544,6 @@ ${adapter.harness}: ${refs.length} session(s)`);
466
544
  }
467
545
  }
468
546
  }
469
- function selectedSessionSource(ref, spans, sessionIdOverride) {
470
- const root = spans.find((item) => item.parent_span_id === null) ?? spans[0];
471
- const prompt = spans.find(
472
- (item) => item.name === "user.prompt" && item.attributes[ACTOR_ATTR] === "human"
473
- ) ?? spans.find((item) => item.name === "user.prompt") ?? spans.find(
474
- (item) => item.attributes["span.type"] === "interaction" && typeof item.attributes.content === "string"
475
- );
476
- const content = typeof prompt?.attributes.content === "string" ? prompt.attributes.content : "";
477
- const firstLine = content.split(/\r?\n/, 1)[0].trim();
478
- const subject = firstLine.length > 240 ? `${firstLine.slice(0, 240)}\u2026 [+${firstLine.length - 240} chars]` : firstLine;
479
- const role = root?.attributes["traces.session.role"];
480
- const parentSessionId = root?.attributes["traces.parent_session_id"];
481
- const relationship = describeSessionRelationship(ref, spans);
482
- const corruptionDigest = root?.attributes[ATTR.CORRUPTION_DIGEST];
483
- const sessionId = [
484
- sessionIdOverride,
485
- root ? sessionIdFromAttributes(root.attributes) : void 0,
486
- root?.trace_id,
487
- ref.sessionId
488
- ].find((value) => typeof value === "string" && value.length > 0);
489
- return {
490
- sessionId,
491
- path: ref.path,
492
- subject,
493
- role: role === "operator" || role === "child" ? role : "unknown",
494
- ...typeof parentSessionId === "string" ? { parentSessionId } : {},
495
- childSessionIds: relationship.childSessionIds,
496
- ...relationship.depth !== void 0 ? { depth: relationship.depth } : {},
497
- ...relationship.agentNickname ? { agentNickname: relationship.agentNickname } : {},
498
- ...relationship.agentRole ? { agentRole: relationship.agentRole } : {},
499
- ...relationship.agentPath ? { agentPath: relationship.agentPath } : {},
500
- ...relationship.taskScope ? { taskScope: relationship.taskScope } : {},
501
- ...relationship.turnId ? { turnId: relationship.turnId } : {},
502
- integrity: ref.integrity?.status ?? "complete",
503
- ...ref.integrity ? {
504
- corruptionCount: ref.integrity.corruptions.length,
505
- ...typeof corruptionDigest === "string" ? { corruptionDigest } : {},
506
- corruptions: ref.integrity.corruptions.slice(0, CORRUPTION_RECEIPT_DISPLAY_LIMIT)
507
- } : {}
508
- };
509
- }
510
547
  async function collectOtlpSpans(path) {
511
548
  const input = await readOtlpInput(path);
512
549
  if (input.spans.length === 0) {
@@ -528,7 +565,7 @@ async function collectOtlpSpans(path) {
528
565
  spans: [...input.spans],
529
566
  harness: "otlp",
530
567
  cwds: [],
531
- sources: [...byTrace].map(([traceId, spans]) => selectedSessionSource({
568
+ sources: [...byTrace].map(([traceId, spans]) => sessionReportSource({
532
569
  harness: "otlp",
533
570
  sessionId: traceId,
534
571
  path: fileByTrace.get(traceId) ?? path,
@@ -558,7 +595,7 @@ async function collectSpans(args) {
558
595
  for (const row of selection.rows) {
559
596
  harnesses.add(row.adapter.harness);
560
597
  appendAll(spans, row.spans);
561
- sources.push(selectedSessionSource(row.ref, row.spans));
598
+ sources.push(sessionReportSource(row.ref, row.spans));
562
599
  if (row.ref.cwd) cwds.push(row.ref.cwd);
563
600
  }
564
601
  return {
@@ -677,6 +714,46 @@ async function cmdIndex(args) {
677
714
  process.stdout.write(serializeSessionIndex(index));
678
715
  }
679
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
+ }
680
757
  async function cmdInspect(args) {
681
758
  if (!args.input) throw new Error("inspect needs an index file; run `traces index --out session-index.json` first");
682
759
  const index = await readSessionIndexFile(args.input);
@@ -890,7 +967,7 @@ async function collectImportedSpans(args) {
890
967
  spans: result.spans,
891
968
  harness: result.format,
892
969
  cwds: [],
893
- sources: [...sessions].map(([sessionId, spans]) => selectedSessionSource({
970
+ sources: [...sessions].map(([sessionId, spans]) => sessionReportSource({
894
971
  harness: result.format,
895
972
  sessionId,
896
973
  path: args.input,
@@ -908,7 +985,7 @@ function analystLog(msg, fields) {
908
985
  }
909
986
  function externalAnalyzersFromArgs(args) {
910
987
  return args.analyzers.map(
911
- (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] })
912
989
  );
913
990
  }
914
991
  async function cmdInvestigate(args) {
@@ -1312,6 +1389,17 @@ Commands:
1312
1389
  improve Write findings, evidence, report, and canonical trace artifacts
1313
1390
  convert Emit OTLP-JSONL only, to --otlp-out (HALO: use analyze --analyzer halo)
1314
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
1315
1403
  inspect Read a session index and print ranked improvement findings
1316
1404
  export Convert evidence/events files to OpenInference JSONL for HALO
1317
1405
  import-codetracebench
@@ -1365,6 +1453,9 @@ Options:
1365
1453
  --metadata <json> analyze/export file: attach JSON object fields as span attributes
1366
1454
  --attr <k=v> analyze/export file: attach one span attribute (repeatable)
1367
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
1368
1459
  --replay, --once stream: scan once and exit (default for positional input / --session)
1369
1460
  --once watch <target>: print ONE snapshot and exit, for scripts and agents
1370
1461
  --no-spans stream: omit per-span pulse events
@@ -1378,7 +1469,10 @@ Options:
1378
1469
  --model <id> Model for --llm, HALO, and Hodoscope (default for --llm: ${DEFAULT_ANALYST_MODEL})
1379
1470
  --config <path> investigate/improve/stream: JS config with analysts, liveAnalysts, or external analyzers
1380
1471
  --budget <usd> USD cap for agentic analysts
1381
- --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)
1382
1476
  --analyzer-prompt <p> analyze: prompt passed to external analyzers (default: diagnose)
1383
1477
  --verify-findings analyze: execute the findings as sandbox replay proofs and mark
1384
1478
  each VERIFIED (receipt path) or UNVERIFIABLE (reason). Needs
@@ -1432,7 +1526,9 @@ async function main() {
1432
1526
  else usage();
1433
1527
  return;
1434
1528
  }
1435
- const args = validateOtlpSelection(validateWorkflowSelection(applyCurrentSessionSelection(parsedArgs)));
1529
+ const args = validateOtlpSelection(
1530
+ validateViewSelection(validateWorkflowSelection(applyCurrentSessionSelection(parsedArgs)))
1531
+ );
1436
1532
  switch (args.command) {
1437
1533
  case "help":
1438
1534
  if (args.input === "import-codetracebench") usageImportCodeTraceBench();
@@ -1461,6 +1557,12 @@ async function main() {
1461
1557
  case "index":
1462
1558
  await cmdIndex(args);
1463
1559
  break;
1560
+ case "bundle":
1561
+ await cmdBundle(args);
1562
+ break;
1563
+ case "bundle-view":
1564
+ await cmdBundleView(args);
1565
+ break;
1464
1566
  case "inspect":
1465
1567
  await cmdInspect(args);
1466
1568
  break;