@tangle-network/traces 0.8.27 → 0.9.1

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,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- analyzeAdoption,
4
- analyzeReactions,
5
- analyzeSpans,
3
+ ACTOR_ATTR,
4
+ ATTR,
5
+ CORRUPTION_RECEIPT_DISPLAY_LIMIT,
6
+ appendAll,
6
7
  buildPolicyEvidenceRecord,
7
8
  buildSessionIndexFromRows,
8
9
  commandAnalyzer,
@@ -10,6 +11,7 @@ import {
10
11
  executeUpload,
11
12
  exportTraceEvidenceFile,
12
13
  haloAnalyzer,
14
+ indexSessionIdsByTrace,
13
15
  inspectSessionIndex,
14
16
  knownHarnesses,
15
17
  loadTracesConfig,
@@ -19,15 +21,9 @@ import {
19
21
  parseSince,
20
22
  planUpload,
21
23
  readSessionIndexFile,
22
- renderAdoption,
23
24
  renderInspectionReport,
24
- renderPipelines,
25
- renderReactions,
26
- renderReport,
27
25
  resolveAdapter,
28
- runExternalAnalyzers,
29
- runPipelines,
30
- runTraceImprovementLoop,
26
+ runTraceImprovement,
31
27
  runTraceInvestigation,
32
28
  saveReport,
33
29
  selectAdapters,
@@ -35,8 +31,8 @@ import {
35
31
  serializeSessionIndex,
36
32
  serializeSpans,
37
33
  serializeTraceStreamEvent,
34
+ sessionIdFromAttributes,
38
35
  streamSessions,
39
- summarizeDeterministicSignals,
40
36
  traceStreamEventsFromSpans,
41
37
  watchSessions,
42
38
  writeInspectionReportFile,
@@ -44,11 +40,11 @@ import {
44
40
  writePolicyEvidenceFile,
45
41
  writeSessionIndexFile,
46
42
  writeTraceEvidenceExportFile
47
- } from "./chunk-EZDMNNW4.js";
43
+ } from "./chunk-RUG26PIS.js";
48
44
 
49
45
  // src/cli.ts
50
46
  import { readFileSync } from "fs";
51
- import { readFile, stat, writeFile } from "fs/promises";
47
+ import { readFile, stat } from "fs/promises";
52
48
  import { basename, resolve } from "path";
53
49
  function packageVersion() {
54
50
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
@@ -221,7 +217,7 @@ async function buildAxService() {
221
217
  }
222
218
  const apiURL = process.env.OPENAI_BASE_URL || void 0;
223
219
  const { AxAI } = await import("@ax-llm/ax");
224
- return new AxAI({ name: "openai", apiKey, ...apiURL ? { apiURL } : {} });
220
+ return AxAI.create({ name: "openai", apiKey, ...apiURL ? { apiURL } : {} });
225
221
  }
226
222
  async function cmdList(args) {
227
223
  const groups = await discover({ ...args, last: args.last || 20 });
@@ -233,7 +229,41 @@ ${adapter.harness}: ${refs.length} session(s)`);
233
229
  }
234
230
  }
235
231
  }
232
+ function selectedSessionSource(ref, spans, sessionIdOverride) {
233
+ const root = spans.find((item) => item.parent_span_id === null) ?? spans[0];
234
+ const prompt = spans.find(
235
+ (item) => item.name === "user.prompt" && item.attributes[ACTOR_ATTR] === "human"
236
+ ) ?? spans.find((item) => item.name === "user.prompt") ?? spans.find(
237
+ (item) => item.attributes["span.type"] === "interaction" && typeof item.attributes.content === "string"
238
+ );
239
+ const content = typeof prompt?.attributes.content === "string" ? prompt.attributes.content : "";
240
+ const firstLine = content.split(/\r?\n/, 1)[0].trim();
241
+ const subject = firstLine.length > 240 ? `${firstLine.slice(0, 240)}\u2026 [+${firstLine.length - 240} chars]` : firstLine;
242
+ const role = root?.attributes["traces.session.role"];
243
+ const parentSessionId = root?.attributes["traces.parent_session_id"];
244
+ const corruptionDigest = root?.attributes[ATTR.CORRUPTION_DIGEST];
245
+ const sessionId = [
246
+ sessionIdOverride,
247
+ root ? sessionIdFromAttributes(root.attributes) : void 0,
248
+ root?.trace_id,
249
+ ref.sessionId
250
+ ].find((value) => typeof value === "string" && value.length > 0);
251
+ return {
252
+ sessionId,
253
+ path: ref.path,
254
+ subject,
255
+ role: role === "operator" || role === "child" ? role : "unknown",
256
+ ...typeof parentSessionId === "string" ? { parentSessionId } : {},
257
+ integrity: ref.integrity?.status ?? "complete",
258
+ ...ref.integrity ? {
259
+ corruptionCount: ref.integrity.corruptions.length,
260
+ ...typeof corruptionDigest === "string" ? { corruptionDigest } : {},
261
+ corruptions: ref.integrity.corruptions.slice(0, CORRUPTION_RECEIPT_DISPLAY_LIMIT)
262
+ } : {}
263
+ };
264
+ }
236
265
  async function collectSpans(args) {
266
+ if (args.input) return collectImportedSpans(args);
237
267
  if (args.session) {
238
268
  const adapter = resolveAdapter(args.harness);
239
269
  if (!adapter) throw new Error(`unknown harness "${args.harness}"`);
@@ -248,22 +278,28 @@ async function collectSpans(args) {
248
278
  mtimeMs: st.mtimeMs
249
279
  };
250
280
  const spans2 = await parseSession(adapter, ref);
251
- return { spans: spans2, harness: adapter.harness, sessionCount: 1, cwds: ref.cwd ? [ref.cwd] : [] };
281
+ return {
282
+ spans: spans2,
283
+ harness: adapter.harness,
284
+ cwds: ref.cwd ? [ref.cwd] : [],
285
+ sources: [selectedSessionSource(ref, spans2)]
286
+ };
252
287
  }
253
288
  const groups = await discover({ ...args, last: args.last || 1 });
254
289
  const spans = [];
255
- let sessionCount = 0;
256
290
  const harnesses = [];
257
291
  const cwds = [];
292
+ const sources = [];
258
293
  for (const { adapter, refs } of groups) {
259
294
  if (refs.length > 0) harnesses.push(adapter.harness);
260
295
  for (const ref of refs) {
261
- spans.push(...await parseSession(adapter, ref));
296
+ const parsed = await parseSession(adapter, ref);
297
+ appendAll(spans, parsed);
298
+ sources.push(selectedSessionSource(ref, parsed));
262
299
  if (ref.cwd) cwds.push(ref.cwd);
263
- sessionCount += 1;
264
300
  }
265
301
  }
266
- return { spans, harness: harnesses.join("+") || args.harness, sessionCount, cwds };
302
+ return { spans, harness: harnesses.join("+") || args.harness, cwds, sources };
267
303
  }
268
304
  async function cmdConvert(args) {
269
305
  const { spans } = await collectSpans(args);
@@ -393,56 +429,43 @@ async function loadExportAttributes(args) {
393
429
  return attributes;
394
430
  }
395
431
  async function cmdAnalyze(args) {
396
- const { spans, harness, sessionCount, cwds } = args.input ? await collectImportedSpans(args) : await collectSpans(args);
397
- if (spans.length === 0) throw new Error("no spans found for the given selection");
398
- const ai = args.llm ? await buildAxService() : void 0;
399
- const { otlpPath, result } = await analyzeSpans(spans, {
400
- ai,
401
- model: args.model,
402
- budgetUsd: args.budget,
403
- otlpOutPath: args.otlp,
404
- log: (msg) => process.stderr.write(`${msg}
405
- `)
406
- });
407
- const pipelines = await runPipelines(spans, { minLoopOccurrences: args.minLoop });
408
- const reactions = analyzeReactions(spans);
409
- const adoption = await analyzeAdoption(spans, { cwds });
410
- const deterministic = summarizeDeterministicSignals(pipelines, reactions);
411
- let report = `${renderReport(result, { harness, sessionCount, spanCount: spans.length, otlpPath, deterministic })}
412
- ${renderPipelines(pipelines)}
413
- ${renderReactions(reactions)}
414
- ${renderAdoption(adoption)}`;
415
- if (args.analyzers.length > 0 && otlpPath) {
416
- const engines = externalAnalyzersFromArgs(args);
417
- const results = await runExternalAnalyzers(otlpPath, engines, { prompt: args.analyzerPrompt });
418
- for (const r of results) {
419
- report += `
420
-
421
- ## ${r.analyzer} (external analyzer)
422
-
423
- ${r.ok ? r.output || "(no output)" : `failed: ${r.error}`}`;
424
- }
425
- }
432
+ const result = await investigate(args, { loadDefaultConfig: false });
426
433
  if (args.out) {
427
- await writeFile(args.out, report, "utf8");
428
- console.log(`report \u2192 ${args.out} (${result.findings.length} findings, ${pipelines.stuckLoops.findings.length} loops, OTLP: ${otlpPath})`);
434
+ await saveReport(args.out, result.report);
435
+ console.log(
436
+ `report \u2192 ${args.out} (${result.findings.length} findings, ${result.pipelines.stuckLoops.findings.length} loops, OTLP: ${result.otlpPath})`
437
+ );
429
438
  } else {
430
- console.log(report);
439
+ console.log(result.report);
431
440
  }
432
441
  }
433
442
  async function collectImportedSpans(args) {
434
- if (!args.input) throw new Error("analyze input missing");
443
+ if (!args.input) throw new Error("input file missing");
435
444
  const attributes = await loadExportAttributes(args);
436
445
  const result = await exportTraceEvidenceFile(args.input, {
437
446
  format: traceEvidenceFormat(args.format),
438
447
  attributes
439
448
  });
440
- const traceIds = new Set(result.spans.map((s) => s.trace_id).filter(Boolean));
449
+ const { sessionByTrace } = indexSessionIdsByTrace(result.spans);
450
+ const sessions = /* @__PURE__ */ new Map();
451
+ for (const span of result.spans) {
452
+ const sessionId = sessionByTrace.get(span.trace_id);
453
+ if (!sessionId) continue;
454
+ const grouped = sessions.get(sessionId) ?? [];
455
+ grouped.push(span);
456
+ sessions.set(sessionId, grouped);
457
+ }
441
458
  return {
442
459
  spans: result.spans,
443
460
  harness: result.format,
444
- sessionCount: traceIds.size || 1,
445
- cwds: []
461
+ cwds: [],
462
+ sources: [...sessions].map(([sessionId, spans]) => selectedSessionSource({
463
+ harness: result.format,
464
+ sessionId,
465
+ path: args.input,
466
+ cwd: null,
467
+ mtimeMs: 0
468
+ }, spans, sessionId))
446
469
  };
447
470
  }
448
471
  function externalAnalyzersFromArgs(args) {
@@ -451,14 +474,23 @@ function externalAnalyzersFromArgs(args) {
451
474
  );
452
475
  }
453
476
  async function cmdInvestigate(args) {
454
- const { spans, harness, sessionCount, cwds } = await collectSpans(args);
477
+ const result = await investigate(args);
478
+ if (args.out) {
479
+ await saveReport(args.out, result.report);
480
+ console.log(`investigation report \u2192 ${args.out} (${result.findings.length} findings with actions, OTLP: ${result.otlpPath})`);
481
+ } else {
482
+ console.log(result.report);
483
+ }
484
+ }
485
+ async function investigate(args, options = {}) {
486
+ const { spans, harness, cwds, sources } = await collectSpans(args);
455
487
  if (spans.length === 0) throw new Error("no spans found for the given selection");
456
- const config = await loadTracesConfig(args.config);
488
+ const config = args.config !== void 0 || options.loadDefaultConfig !== false ? await loadTracesConfig(args.config) : void 0;
457
489
  const ai = args.llm ? await buildAxService() : void 0;
458
- const result = await runTraceInvestigation(mergeTracesConfig({
490
+ return runTraceInvestigation(mergeTracesConfig({
459
491
  spans,
460
492
  harness,
461
- sessionCount,
493
+ sources,
462
494
  cwds,
463
495
  minLoopOccurrences: args.minLoop,
464
496
  ai,
@@ -470,23 +502,17 @@ async function cmdInvestigate(args) {
470
502
  log: (msg) => process.stderr.write(`${msg}
471
503
  `)
472
504
  }, config));
473
- if (args.out) {
474
- await saveReport(args.out, result.report);
475
- console.log(`investigation report \u2192 ${args.out} (${result.findings.length} findings, ${result.recommendations.length} recommendations, OTLP: ${result.otlpPath})`);
476
- } else {
477
- console.log(result.report);
478
- }
479
505
  }
480
506
  async function cmdImprove(args) {
481
- const { spans, harness, sessionCount, cwds } = await collectSpans(args);
507
+ const { spans, harness, cwds, sources } = await collectSpans(args);
482
508
  if (spans.length === 0) throw new Error("no spans found for the given selection");
483
509
  const config = await loadTracesConfig(args.config);
484
510
  const ai = args.llm ? await buildAxService() : void 0;
485
- const result = await runTraceImprovementLoop({
511
+ const result = await runTraceImprovement({
486
512
  ...mergeTracesConfig({
487
513
  spans,
488
514
  harness,
489
- sessionCount,
515
+ sources,
490
516
  cwds,
491
517
  minLoopOccurrences: args.minLoop,
492
518
  ai,
@@ -498,13 +524,12 @@ async function cmdImprove(args) {
498
524
  log: (msg) => process.stderr.write(`${msg}
499
525
  `)
500
526
  }, config),
501
- adapter: config?.improvementAdapter,
502
527
  outDir: args.dir ?? args.out
503
528
  });
504
529
  const dir = result.artifacts?.directory;
505
530
  if (!dir) throw new Error("improve did not produce an artifact directory");
506
531
  console.log(
507
- `improvement artifacts \u2192 ${dir} (${result.findings.length} findings, ${result.recommendations.length} recommendations, ${result.proposals.length} proposal(s), OTLP: ${result.otlpPath})`
532
+ `improvement artifacts \u2192 ${dir} (${result.findings.length} findings with actions and checks, OTLP: ${result.otlpPath})`
508
533
  );
509
534
  }
510
535
  function summarizeFindingEvidence(finding) {
@@ -660,10 +685,10 @@ async function cmdUpload(args) {
660
685
  });
661
686
  const redactorParts = args.redactorCmd?.split(/\s+/).filter(Boolean) ?? [];
662
687
  const redactor = redactorParts[0] ? commandRedactor({ command: redactorParts[0], args: redactorParts.slice(1) }) : void 0;
663
- const newItems = plan.items.filter((i) => i.isNew);
688
+ const candidates = plan.items;
664
689
  const byRule = {};
665
690
  let totalRedactions = 0;
666
- for (const i of newItems) {
691
+ for (const i of candidates) {
667
692
  totalRedactions += i.redaction.redactionCount;
668
693
  for (const [r, n] of Object.entries(i.redaction.byRule)) byRule[r] = (byRule[r] ?? 0) + n;
669
694
  }
@@ -671,28 +696,28 @@ async function cmdUpload(args) {
671
696
  w(`
672
697
  Window: since ${new Date(sinceMs).toISOString()}
673
698
  `);
674
- w(`Sessions found: ${plan.items.length} \xB7 new: ${newItems.length} \xB7 already uploaded: ${plan.items.length - newItems.length}
699
+ w(`Sessions found: ${plan.items.length} \xB7 final privacy-mode dedup runs before upload
675
700
  `);
676
701
  w(`PII/secrets redacted: ${totalRedactions}${Object.keys(byRule).length ? ` (${Object.entries(byRule).map(([r, n]) => `${r}:${n}`).join(", ")})` : ""}
677
702
  `);
678
- for (const i of newItems.slice(0, 25)) {
703
+ for (const i of candidates.slice(0, 25)) {
679
704
  w(` + [${i.ref.harness}] ${i.ref.sessionId.slice(0, 8)} ${i.spans.length} spans ${i.redaction.redactionCount} redacted ${i.ref.cwd ?? ""}
680
705
  `);
681
706
  }
682
- if (newItems.length > 25) w(` \u2026 and ${newItems.length - 25} more
707
+ if (candidates.length > 25) w(` \u2026 and ${candidates.length - 25} more
683
708
  `);
684
- if (newItems.length === 0) {
685
- console.log("Nothing new to upload.");
709
+ if (candidates.length === 0) {
710
+ console.log("No sessions found to upload.");
686
711
  return;
687
712
  }
688
713
  if (args.dryRun) {
689
714
  const res2 = await executeUpload(plan, { dryRun: true, otlpOut: args.otlp, stripContent: args.noContent, redactor });
690
- console.log(`dry run: ${newItems.length} session(s), ${totalRedactions} redaction(s). Redacted OTLP -> ${res2.otlpPath}`);
715
+ console.log(`dry run: ${candidates.length - res2.skippedSessions} session(s), ${totalRedactions} redaction(s). Redacted OTLP -> ${res2.otlpPath}`);
691
716
  console.log("No upload performed. Set TANGLE_INGEST_URL / TANGLE_INGEST_API_KEY / TANGLE_TENANT_ID and drop --dry-run to send.");
692
717
  return;
693
718
  }
694
719
  if (!args.yes) {
695
- const ok = await confirm(`Upload ${newItems.length} redacted session(s) to the Tangle Intelligence Platform?`);
720
+ const ok = await confirm(`Process ${candidates.length} redacted session(s) for upload to the Tangle Intelligence Platform?`);
696
721
  if (!ok) {
697
722
  console.log("Aborted (use --yes to skip the prompt, or --dry-run to preview).");
698
723
  return;
@@ -738,8 +763,8 @@ function usage() {
738
763
  Commands:
739
764
  list List discovered sessions
740
765
  analyze Run analyst suite + loop/waste pipelines over sessions or an input file
741
- investigate Run typed investigation flow, including BYO config + recommendations
742
- improve Write a full improvement artifact directory for review/apply/rerun
766
+ investigate Run typed investigation flow, including BYO config + evidence-backed actions
767
+ improve Write findings, evidence, report, and canonical trace artifacts
743
768
  convert Emit OTLP-JSONL only (HALO: use analyze --analyzer halo)
744
769
  index Emit a reusable session index JSON for later investigation
745
770
  inspect Read a session index and print ranked improvement findings
@@ -768,7 +793,7 @@ Options:
768
793
  --no-findings stream: omit semantic live-finding events
769
794
  --llm Enable agentic RLM analysts (needs OPENAI_API_KEY / OPENAI_BASE_URL)
770
795
  --model <id> --llm model id (e.g. a router model like glm-5.2); default is agent-eval's
771
- --config <path> investigate/improve/stream: JS config with analysts, liveAnalysts, external analyzers, or proposal adapter
796
+ --config <path> investigate/improve/stream: JS config with analysts, liveAnalysts, or external analyzers
772
797
  --budget <usd> USD cap for agentic analysts
773
798
  --analyzer <cmd> analyze: also run an external engine over the OTLP (repeatable; "halo" or any command)
774
799
  --analyzer-prompt <p> analyze: prompt passed to external analyzers (default: diagnose)