@tangle-network/traces 0.8.27 → 0.9.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,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ ACTOR_ATTR,
4
+ ATTR,
5
+ CORRUPTION_RECEIPT_DISPLAY_LIMIT,
3
6
  analyzeAdoption,
4
7
  analyzeReactions,
5
8
  analyzeSpans,
@@ -10,6 +13,7 @@ import {
10
13
  executeUpload,
11
14
  exportTraceEvidenceFile,
12
15
  haloAnalyzer,
16
+ indexSessionIdsByTrace,
13
17
  inspectSessionIndex,
14
18
  knownHarnesses,
15
19
  loadTracesConfig,
@@ -27,7 +31,7 @@ import {
27
31
  resolveAdapter,
28
32
  runExternalAnalyzers,
29
33
  runPipelines,
30
- runTraceImprovementLoop,
34
+ runTraceImprovement,
31
35
  runTraceInvestigation,
32
36
  saveReport,
33
37
  selectAdapters,
@@ -35,6 +39,7 @@ import {
35
39
  serializeSessionIndex,
36
40
  serializeSpans,
37
41
  serializeTraceStreamEvent,
42
+ sessionIdFromAttributes,
38
43
  streamSessions,
39
44
  summarizeDeterministicSignals,
40
45
  traceStreamEventsFromSpans,
@@ -44,7 +49,7 @@ import {
44
49
  writePolicyEvidenceFile,
45
50
  writeSessionIndexFile,
46
51
  writeTraceEvidenceExportFile
47
- } from "./chunk-EZDMNNW4.js";
52
+ } from "./chunk-5SBXL62V.js";
48
53
 
49
54
  // src/cli.ts
50
55
  import { readFileSync } from "fs";
@@ -233,7 +238,41 @@ ${adapter.harness}: ${refs.length} session(s)`);
233
238
  }
234
239
  }
235
240
  }
241
+ function selectedSessionSource(ref, spans, sessionIdOverride) {
242
+ const root = spans.find((item) => item.parent_span_id === null) ?? spans[0];
243
+ const prompt = spans.find(
244
+ (item) => item.name === "user.prompt" && item.attributes[ACTOR_ATTR] === "human"
245
+ ) ?? spans.find((item) => item.name === "user.prompt") ?? spans.find(
246
+ (item) => item.attributes["span.type"] === "interaction" && typeof item.attributes.content === "string"
247
+ );
248
+ const content = typeof prompt?.attributes.content === "string" ? prompt.attributes.content : "";
249
+ const firstLine = content.split(/\r?\n/, 1)[0].trim();
250
+ const subject = firstLine.length > 240 ? `${firstLine.slice(0, 240)}\u2026 [+${firstLine.length - 240} chars]` : firstLine;
251
+ const role = root?.attributes["traces.session.role"];
252
+ const parentSessionId = root?.attributes["traces.parent_session_id"];
253
+ const corruptionDigest = root?.attributes[ATTR.CORRUPTION_DIGEST];
254
+ const sessionId = [
255
+ sessionIdOverride,
256
+ root ? sessionIdFromAttributes(root.attributes) : void 0,
257
+ root?.trace_id,
258
+ ref.sessionId
259
+ ].find((value) => typeof value === "string" && value.length > 0);
260
+ return {
261
+ sessionId,
262
+ path: ref.path,
263
+ subject,
264
+ role: role === "operator" || role === "child" ? role : "unknown",
265
+ ...typeof parentSessionId === "string" ? { parentSessionId } : {},
266
+ integrity: ref.integrity?.status ?? "complete",
267
+ ...ref.integrity ? {
268
+ corruptionCount: ref.integrity.corruptions.length,
269
+ ...typeof corruptionDigest === "string" ? { corruptionDigest } : {},
270
+ corruptions: ref.integrity.corruptions.slice(0, CORRUPTION_RECEIPT_DISPLAY_LIMIT)
271
+ } : {}
272
+ };
273
+ }
236
274
  async function collectSpans(args) {
275
+ if (args.input) return collectImportedSpans(args);
237
276
  if (args.session) {
238
277
  const adapter = resolveAdapter(args.harness);
239
278
  if (!adapter) throw new Error(`unknown harness "${args.harness}"`);
@@ -248,22 +287,28 @@ async function collectSpans(args) {
248
287
  mtimeMs: st.mtimeMs
249
288
  };
250
289
  const spans2 = await parseSession(adapter, ref);
251
- return { spans: spans2, harness: adapter.harness, sessionCount: 1, cwds: ref.cwd ? [ref.cwd] : [] };
290
+ return {
291
+ spans: spans2,
292
+ harness: adapter.harness,
293
+ cwds: ref.cwd ? [ref.cwd] : [],
294
+ sources: [selectedSessionSource(ref, spans2)]
295
+ };
252
296
  }
253
297
  const groups = await discover({ ...args, last: args.last || 1 });
254
298
  const spans = [];
255
- let sessionCount = 0;
256
299
  const harnesses = [];
257
300
  const cwds = [];
301
+ const sources = [];
258
302
  for (const { adapter, refs } of groups) {
259
303
  if (refs.length > 0) harnesses.push(adapter.harness);
260
304
  for (const ref of refs) {
261
- spans.push(...await parseSession(adapter, ref));
305
+ const parsed = await parseSession(adapter, ref);
306
+ spans.push(...parsed);
307
+ sources.push(selectedSessionSource(ref, parsed));
262
308
  if (ref.cwd) cwds.push(ref.cwd);
263
- sessionCount += 1;
264
309
  }
265
310
  }
266
- return { spans, harness: harnesses.join("+") || args.harness, sessionCount, cwds };
311
+ return { spans, harness: harnesses.join("+") || args.harness, cwds, sources };
267
312
  }
268
313
  async function cmdConvert(args) {
269
314
  const { spans } = await collectSpans(args);
@@ -393,10 +438,10 @@ async function loadExportAttributes(args) {
393
438
  return attributes;
394
439
  }
395
440
  async function cmdAnalyze(args) {
396
- const { spans, harness, sessionCount, cwds } = args.input ? await collectImportedSpans(args) : await collectSpans(args);
441
+ const { spans, harness, cwds, sources } = await collectSpans(args);
397
442
  if (spans.length === 0) throw new Error("no spans found for the given selection");
398
443
  const ai = args.llm ? await buildAxService() : void 0;
399
- const { otlpPath, result } = await analyzeSpans(spans, {
444
+ const { otlpPath, execution, result } = await analyzeSpans(spans, {
400
445
  ai,
401
446
  model: args.model,
402
447
  budgetUsd: args.budget,
@@ -408,7 +453,16 @@ async function cmdAnalyze(args) {
408
453
  const reactions = analyzeReactions(spans);
409
454
  const adoption = await analyzeAdoption(spans, { cwds });
410
455
  const deterministic = summarizeDeterministicSignals(pipelines, reactions);
411
- let report = `${renderReport(result, { harness, sessionCount, spanCount: spans.length, otlpPath, deterministic })}
456
+ let report = `${renderReport(result, {
457
+ harness,
458
+ sessionCount: adoption.identifiedSessionCount,
459
+ unassignedTraceCount: adoption.unassignedTraceCount,
460
+ spanCount: spans.length,
461
+ otlpPath,
462
+ execution,
463
+ deterministic,
464
+ sources
465
+ })}
412
466
  ${renderPipelines(pipelines)}
413
467
  ${renderReactions(reactions)}
414
468
  ${renderAdoption(adoption)}`;
@@ -425,24 +479,40 @@ ${r.ok ? r.output || "(no output)" : `failed: ${r.error}`}`;
425
479
  }
426
480
  if (args.out) {
427
481
  await writeFile(args.out, report, "utf8");
428
- console.log(`report \u2192 ${args.out} (${result.findings.length} findings, ${pipelines.stuckLoops.findings.length} loops, OTLP: ${otlpPath})`);
482
+ console.log(
483
+ `report \u2192 ${args.out} (${result.findings.length} findings, ${pipelines.stuckLoops.findings.length} loops, OTLP: ${otlpPath})`
484
+ );
429
485
  } else {
430
486
  console.log(report);
431
487
  }
432
488
  }
433
489
  async function collectImportedSpans(args) {
434
- if (!args.input) throw new Error("analyze input missing");
490
+ if (!args.input) throw new Error("input file missing");
435
491
  const attributes = await loadExportAttributes(args);
436
492
  const result = await exportTraceEvidenceFile(args.input, {
437
493
  format: traceEvidenceFormat(args.format),
438
494
  attributes
439
495
  });
440
- const traceIds = new Set(result.spans.map((s) => s.trace_id).filter(Boolean));
496
+ const { sessionByTrace } = indexSessionIdsByTrace(result.spans);
497
+ const sessions = /* @__PURE__ */ new Map();
498
+ for (const span of result.spans) {
499
+ const sessionId = sessionByTrace.get(span.trace_id);
500
+ if (!sessionId) continue;
501
+ const grouped = sessions.get(sessionId) ?? [];
502
+ grouped.push(span);
503
+ sessions.set(sessionId, grouped);
504
+ }
441
505
  return {
442
506
  spans: result.spans,
443
507
  harness: result.format,
444
- sessionCount: traceIds.size || 1,
445
- cwds: []
508
+ cwds: [],
509
+ sources: [...sessions].map(([sessionId, spans]) => selectedSessionSource({
510
+ harness: result.format,
511
+ sessionId,
512
+ path: args.input,
513
+ cwd: null,
514
+ mtimeMs: 0
515
+ }, spans, sessionId))
446
516
  };
447
517
  }
448
518
  function externalAnalyzersFromArgs(args) {
@@ -451,14 +521,14 @@ function externalAnalyzersFromArgs(args) {
451
521
  );
452
522
  }
453
523
  async function cmdInvestigate(args) {
454
- const { spans, harness, sessionCount, cwds } = await collectSpans(args);
524
+ const { spans, harness, cwds, sources } = await collectSpans(args);
455
525
  if (spans.length === 0) throw new Error("no spans found for the given selection");
456
526
  const config = await loadTracesConfig(args.config);
457
527
  const ai = args.llm ? await buildAxService() : void 0;
458
528
  const result = await runTraceInvestigation(mergeTracesConfig({
459
529
  spans,
460
530
  harness,
461
- sessionCount,
531
+ sources,
462
532
  cwds,
463
533
  minLoopOccurrences: args.minLoop,
464
534
  ai,
@@ -472,21 +542,21 @@ async function cmdInvestigate(args) {
472
542
  }, config));
473
543
  if (args.out) {
474
544
  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})`);
545
+ console.log(`investigation report \u2192 ${args.out} (${result.findings.length} findings with actions, OTLP: ${result.otlpPath})`);
476
546
  } else {
477
547
  console.log(result.report);
478
548
  }
479
549
  }
480
550
  async function cmdImprove(args) {
481
- const { spans, harness, sessionCount, cwds } = await collectSpans(args);
551
+ const { spans, harness, cwds, sources } = await collectSpans(args);
482
552
  if (spans.length === 0) throw new Error("no spans found for the given selection");
483
553
  const config = await loadTracesConfig(args.config);
484
554
  const ai = args.llm ? await buildAxService() : void 0;
485
- const result = await runTraceImprovementLoop({
555
+ const result = await runTraceImprovement({
486
556
  ...mergeTracesConfig({
487
557
  spans,
488
558
  harness,
489
- sessionCount,
559
+ sources,
490
560
  cwds,
491
561
  minLoopOccurrences: args.minLoop,
492
562
  ai,
@@ -498,13 +568,12 @@ async function cmdImprove(args) {
498
568
  log: (msg) => process.stderr.write(`${msg}
499
569
  `)
500
570
  }, config),
501
- adapter: config?.improvementAdapter,
502
571
  outDir: args.dir ?? args.out
503
572
  });
504
573
  const dir = result.artifacts?.directory;
505
574
  if (!dir) throw new Error("improve did not produce an artifact directory");
506
575
  console.log(
507
- `improvement artifacts \u2192 ${dir} (${result.findings.length} findings, ${result.recommendations.length} recommendations, ${result.proposals.length} proposal(s), OTLP: ${result.otlpPath})`
576
+ `improvement artifacts \u2192 ${dir} (${result.findings.length} findings with actions and checks, OTLP: ${result.otlpPath})`
508
577
  );
509
578
  }
510
579
  function summarizeFindingEvidence(finding) {
@@ -660,10 +729,10 @@ async function cmdUpload(args) {
660
729
  });
661
730
  const redactorParts = args.redactorCmd?.split(/\s+/).filter(Boolean) ?? [];
662
731
  const redactor = redactorParts[0] ? commandRedactor({ command: redactorParts[0], args: redactorParts.slice(1) }) : void 0;
663
- const newItems = plan.items.filter((i) => i.isNew);
732
+ const candidates = plan.items;
664
733
  const byRule = {};
665
734
  let totalRedactions = 0;
666
- for (const i of newItems) {
735
+ for (const i of candidates) {
667
736
  totalRedactions += i.redaction.redactionCount;
668
737
  for (const [r, n] of Object.entries(i.redaction.byRule)) byRule[r] = (byRule[r] ?? 0) + n;
669
738
  }
@@ -671,28 +740,28 @@ async function cmdUpload(args) {
671
740
  w(`
672
741
  Window: since ${new Date(sinceMs).toISOString()}
673
742
  `);
674
- w(`Sessions found: ${plan.items.length} \xB7 new: ${newItems.length} \xB7 already uploaded: ${plan.items.length - newItems.length}
743
+ w(`Sessions found: ${plan.items.length} \xB7 final privacy-mode dedup runs before upload
675
744
  `);
676
745
  w(`PII/secrets redacted: ${totalRedactions}${Object.keys(byRule).length ? ` (${Object.entries(byRule).map(([r, n]) => `${r}:${n}`).join(", ")})` : ""}
677
746
  `);
678
- for (const i of newItems.slice(0, 25)) {
747
+ for (const i of candidates.slice(0, 25)) {
679
748
  w(` + [${i.ref.harness}] ${i.ref.sessionId.slice(0, 8)} ${i.spans.length} spans ${i.redaction.redactionCount} redacted ${i.ref.cwd ?? ""}
680
749
  `);
681
750
  }
682
- if (newItems.length > 25) w(` \u2026 and ${newItems.length - 25} more
751
+ if (candidates.length > 25) w(` \u2026 and ${candidates.length - 25} more
683
752
  `);
684
- if (newItems.length === 0) {
685
- console.log("Nothing new to upload.");
753
+ if (candidates.length === 0) {
754
+ console.log("No sessions found to upload.");
686
755
  return;
687
756
  }
688
757
  if (args.dryRun) {
689
758
  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}`);
759
+ console.log(`dry run: ${candidates.length - res2.skippedSessions} session(s), ${totalRedactions} redaction(s). Redacted OTLP -> ${res2.otlpPath}`);
691
760
  console.log("No upload performed. Set TANGLE_INGEST_URL / TANGLE_INGEST_API_KEY / TANGLE_TENANT_ID and drop --dry-run to send.");
692
761
  return;
693
762
  }
694
763
  if (!args.yes) {
695
- const ok = await confirm(`Upload ${newItems.length} redacted session(s) to the Tangle Intelligence Platform?`);
764
+ const ok = await confirm(`Process ${candidates.length} redacted session(s) for upload to the Tangle Intelligence Platform?`);
696
765
  if (!ok) {
697
766
  console.log("Aborted (use --yes to skip the prompt, or --dry-run to preview).");
698
767
  return;
@@ -738,8 +807,8 @@ function usage() {
738
807
  Commands:
739
808
  list List discovered sessions
740
809
  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
810
+ investigate Run typed investigation flow, including BYO config + evidence-backed actions
811
+ improve Write findings, evidence, report, and canonical trace artifacts
743
812
  convert Emit OTLP-JSONL only (HALO: use analyze --analyzer halo)
744
813
  index Emit a reusable session index JSON for later investigation
745
814
  inspect Read a session index and print ranked improvement findings
@@ -768,7 +837,7 @@ Options:
768
837
  --no-findings stream: omit semantic live-finding events
769
838
  --llm Enable agentic RLM analysts (needs OPENAI_API_KEY / OPENAI_BASE_URL)
770
839
  --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
840
+ --config <path> investigate/improve/stream: JS config with analysts, liveAnalysts, or external analyzers
772
841
  --budget <usd> USD cap for agentic analysts
773
842
  --analyzer <cmd> analyze: also run an external engine over the OTLP (repeatable; "halo" or any command)
774
843
  --analyzer-prompt <p> analyze: prompt passed to external analyzers (default: diagnose)