@tangle-network/traces 0.15.3 → 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.
@@ -881,8 +881,8 @@ function traceContractBuildId(fromFile = import.meta.url) {
881
881
  }
882
882
  var buildIdCache = /* @__PURE__ */ new Map();
883
883
  function traceContractBuildIdOrNull(fromFile = import.meta.url) {
884
- const cached = buildIdCache.get(fromFile);
885
- if (cached !== void 0) return cached;
884
+ const cached2 = buildIdCache.get(fromFile);
885
+ if (cached2 !== void 0) return cached2;
886
886
  let resolved;
887
887
  try {
888
888
  resolved = traceContractBuildId(fromFile);
@@ -1018,8 +1018,8 @@ function serializeSpans(spans) {
1018
1018
  async function writeOtlpFile(spans, outPath) {
1019
1019
  const { mkdtemp: mkdtemp8, writeFile: writeFile13 } = await import("fs/promises");
1020
1020
  const { tmpdir: tmpdir8 } = await import("os");
1021
- const { join: join34 } = await import("path");
1022
- const path = outPath ?? join34(await mkdtemp8(join34(tmpdir8(), "traces-")), "spans.otlp.jsonl");
1021
+ const { join: join35 } = await import("path");
1022
+ const path = outPath ?? join35(await mkdtemp8(join35(tmpdir8(), "traces-")), "spans.otlp.jsonl");
1023
1023
  await writeFile13(path, serializeSpans(spans), "utf8");
1024
1024
  return path;
1025
1025
  }
@@ -2225,6 +2225,7 @@ function projectClaudeEvent(event) {
2225
2225
  cacheWriteInputTokens: event.message.usage?.cache_creation_input_tokens ?? null,
2226
2226
  content: textOf2(event.message.content) || null,
2227
2227
  contentSource: textSources(event.message, "content"),
2228
+ parentToolUseId: event.parent_tool_use_id ?? null,
2228
2229
  tools
2229
2230
  };
2230
2231
  }
@@ -2255,6 +2256,7 @@ function projectClaudeEvent(event) {
2255
2256
  timestamp,
2256
2257
  prompt: prompt || null,
2257
2258
  contentSource: textSources(event.message, "content"),
2259
+ parentToolUseId: event.parent_tool_use_id ?? null,
2258
2260
  ...prompt ? {
2259
2261
  isSidechain: event.isSidechain === true,
2260
2262
  isMeta: event.isMeta === true,
@@ -2374,15 +2376,21 @@ function consumeDistinctClaudeEvent(event, ctx, state, seen, sourcePath) {
2374
2376
  consumeClaudeEvent(accepted.projection, accepted.uid, ctx, state);
2375
2377
  return true;
2376
2378
  }
2379
+ function inStreamParent(parentToolUseId, state) {
2380
+ if (!parentToolUseId) return void 0;
2381
+ const parentTool = state.toolSpanByUseId.get(parentToolUseId);
2382
+ return parentTool ? { span: parentTool, confidence: "explicit" } : void 0;
2383
+ }
2377
2384
  function consumeClaudeEvent(event, uid, ctx, state) {
2378
2385
  if (event.kind === "assistant") {
2379
2386
  const messageId = event.messageId ?? uid;
2380
2387
  let llmSpan = state.llmSpanByMessageId.get(messageId);
2381
2388
  if (!llmSpan) {
2389
+ const parent = inStreamParent(event.parentToolUseId, state);
2382
2390
  llmSpan = span({
2383
2391
  traceId: ctx.traceId,
2384
2392
  spanId: `${ctx.idPrefix}${uid}`,
2385
- parentSpanId: ctx.rootParent,
2393
+ parentSpanId: parent ? parent.span.span_id : ctx.rootParent,
2386
2394
  name: "llm.turn",
2387
2395
  kind: "LLM",
2388
2396
  startTime: event.timestamp,
@@ -2395,6 +2403,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
2395
2403
  cacheWriteInputTokens: event.cacheWriteInputTokens,
2396
2404
  step: state.step
2397
2405
  });
2406
+ if (parent) llmSpan.attributes[ATTR3.parentConfidence] = parent.confidence;
2398
2407
  state.spans.push(llmSpan);
2399
2408
  state.llmSpanByMessageId.set(messageId, llmSpan);
2400
2409
  state.step += 1;
@@ -2466,10 +2475,11 @@ function consumeClaudeEvent(event, uid, ctx, state) {
2466
2475
  }));
2467
2476
  state.sawUserTurn = true;
2468
2477
  if (event.originKind) state.sawRecordedOrigin = true;
2478
+ const userParent = inStreamParent(event.parentToolUseId, state);
2469
2479
  const prompt = userPromptSpan({
2470
2480
  traceId: ctx.traceId,
2471
2481
  spanId: `${ctx.idPrefix}${uid}:user`,
2472
- parentSpanId: ctx.rootParent,
2482
+ parentSpanId: userParent ? userParent.span.span_id : ctx.rootParent,
2473
2483
  startTime: event.timestamp,
2474
2484
  service: SERVICE2,
2475
2485
  agent: ctx.agent,
@@ -2482,6 +2492,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
2482
2492
  prompt.attributes[ORIGIN_RECORDED_ATTR] = true;
2483
2493
  prompt.attributes[ORIGIN_KIND_ATTR] = event.originKind;
2484
2494
  }
2495
+ if (userParent) prompt.attributes[ATTR3.parentConfidence] = userParent.confidence;
2485
2496
  state.spans.push(prompt);
2486
2497
  state.promptSpans.push(prompt);
2487
2498
  state.step += 1;
@@ -3833,9 +3844,9 @@ function itemCountsJson(counts) {
3833
3844
  }
3834
3845
  function placeInnerSpans(pending, windows) {
3835
3846
  return pending.map((entry) => {
3836
- const { parent, join: join34 } = joinItem(windows, entry.startMs, entry.endMs);
3847
+ const { parent, join: join35 } = joinItem(windows, entry.startMs, entry.endMs);
3837
3848
  if (parent) entry.span.parent_span_id = parent.span_id;
3838
- entry.span.attributes["traces.codex.item_join"] = join34;
3849
+ entry.span.attributes["traces.codex.item_join"] = join35;
3839
3850
  return entry.span;
3840
3851
  });
3841
3852
  }
@@ -4159,8 +4170,8 @@ var CodexAdapter = class {
4159
4170
  const completedItemKeys = /* @__PURE__ */ new Set();
4160
4171
  const unmodeledItemCounts = /* @__PURE__ */ new Map();
4161
4172
  const droppedItemCounts = /* @__PURE__ */ new Map();
4162
- const countItem = (counts, label) => {
4163
- counts.set(label, (counts.get(label) ?? 0) + 1);
4173
+ const countItem = (counts, label2) => {
4174
+ counts.set(label2, (counts.get(label2) ?? 0) + 1);
4164
4175
  };
4165
4176
  let taskIndex = 0;
4166
4177
  const unpairedUserItems = [];
@@ -4670,11 +4681,11 @@ var CodexAdapter = class {
4670
4681
  const input = reportedCount(sessionTotalUsage.input_tokens);
4671
4682
  const output = reportedCount(sessionTotalUsage.output_tokens);
4672
4683
  const reasoning = reportedCount(sessionTotalUsage.reasoning_output_tokens);
4673
- const cached = reportedCount(sessionTotalUsage.cached_input_tokens);
4684
+ const cached2 = reportedCount(sessionTotalUsage.cached_input_tokens);
4674
4685
  if (input !== void 0) root.attributes[SESSION_TOTAL_INPUT_TOKENS] = input;
4675
4686
  if (output !== void 0) root.attributes[SESSION_TOTAL_OUTPUT_TOKENS] = output;
4676
4687
  if (reasoning !== void 0) root.attributes[SESSION_TOTAL_REASONING_TOKENS] = reasoning;
4677
- if (cached !== void 0) root.attributes[SESSION_TOTAL_CACHED_INPUT_TOKENS] = cached;
4688
+ if (cached2 !== void 0) root.attributes[SESSION_TOTAL_CACHED_INPUT_TOKENS] = cached2;
4678
4689
  }
4679
4690
  }
4680
4691
  if (inheritedSpansEmitted > 0) root.attributes[INHERITED_SPAN_COUNT_ATTR] = inheritedSpansEmitted;
@@ -4748,14 +4759,14 @@ function earlier(left, right) {
4748
4759
  function later(left, right) {
4749
4760
  return Date.parse(left) >= Date.parse(right) ? left : right;
4750
4761
  }
4751
- function requireObject2(value, sourcePath, label) {
4762
+ function requireObject2(value, sourcePath, label2) {
4752
4763
  const object = objectValue(value);
4753
- if (!object) throw new CodexExecStreamError(sourcePath, `${label} must be an object`);
4764
+ if (!object) throw new CodexExecStreamError(sourcePath, `${label2} must be an object`);
4754
4765
  return object;
4755
4766
  }
4756
- function requireString(value, sourcePath, label) {
4767
+ function requireString(value, sourcePath, label2) {
4757
4768
  const string = stringValue(value);
4758
- if (!string) throw new CodexExecStreamError(sourcePath, `${label} must be a non-empty string`);
4769
+ if (!string) throw new CodexExecStreamError(sourcePath, `${label2} must be a non-empty string`);
4759
4770
  return string;
4760
4771
  }
4761
4772
  function optionalTokenCount(usage, key, sourcePath) {
@@ -6708,7 +6719,7 @@ function extractAbsolutePaths(spans) {
6708
6719
  async function resolveRepoAttrs(cwd) {
6709
6720
  if (!cwd) return {};
6710
6721
  const attrs = { [ATTR.CWD]: cwd };
6711
- const basename18 = (() => {
6722
+ const basename19 = (() => {
6712
6723
  const trimmed = cwd.replace(/\/+$/g, "");
6713
6724
  const idx = trimmed.lastIndexOf("/");
6714
6725
  const name = idx === -1 ? trimmed : trimmed.slice(idx + 1);
@@ -6730,7 +6741,7 @@ async function resolveRepoAttrs(cwd) {
6730
6741
  if (commit) attrs[ATTR.GIT_COMMIT] = commit;
6731
6742
  } catch {
6732
6743
  }
6733
- attrs[ATTR.SUBJECT_KEY] = basename18;
6744
+ attrs[ATTR.SUBJECT_KEY] = basename19;
6734
6745
  return attrs;
6735
6746
  }
6736
6747
  async function repoCandidateForPath(path) {
@@ -6786,8 +6797,8 @@ function replaceRepoAttrs(spans, attrs) {
6786
6797
  }
6787
6798
  }
6788
6799
  async function resolutionForWorkdirEvidence(evidence3, cache) {
6789
- const cached = cache.get(evidence3.path);
6790
- if (cached !== void 0) return cached;
6800
+ const cached2 = cache.get(evidence3.path);
6801
+ if (cached2 !== void 0) return cached2;
6791
6802
  const cwd = await repoCandidateForPath(evidence3.path);
6792
6803
  if (!cwd) {
6793
6804
  cache.set(evidence3.path, null);
@@ -6891,9 +6902,9 @@ async function locateSessions(adapter, opts = {}) {
6891
6902
  for (const ref of refs) {
6892
6903
  let matched = cwdMatchesSelection(ref.cwd, cwdSelections);
6893
6904
  if (!matched && ref.cwd !== null) {
6894
- const cached = resolvedMatch.get(ref.cwd);
6895
- matched = cached ?? await cwdMatchesSelectionResolved(ref.cwd, cwdSelections);
6896
- if (cached === void 0) resolvedMatch.set(ref.cwd, matched);
6905
+ const cached2 = resolvedMatch.get(ref.cwd);
6906
+ matched = cached2 ?? await cwdMatchesSelectionResolved(ref.cwd, cwdSelections);
6907
+ if (cached2 === void 0) resolvedMatch.set(ref.cwd, matched);
6897
6908
  }
6898
6909
  if (!matched && !(ref.cwd === null && ref.integrity)) continue;
6899
6910
  byKey.set(refKey(ref), ref);
@@ -7523,106 +7534,106 @@ var RESULT_FAILURE_KEYS = /* @__PURE__ */ new Set(["analyzer", "kind", "ok", "ou
7523
7534
  var RESULT_REPORT_KEYS = /* @__PURE__ */ new Set(["analyzer", "kind", "ok", "output"]);
7524
7535
  var RESULT_FINDING_KEYS = /* @__PURE__ */ new Set(["analyzer", "kind", "ok", "output", "findings"]);
7525
7536
  var RESULT_DISCOVERY_KEYS = /* @__PURE__ */ new Set(["analyzer", "kind", "ok", "output", "candidates"]);
7526
- function record(value, label) {
7537
+ function record(value, label2) {
7527
7538
  if (!value || typeof value !== "object" || Array.isArray(value)) {
7528
- throw new TypeError(`${label} must be an object`);
7539
+ throw new TypeError(`${label2} must be an object`);
7529
7540
  }
7530
7541
  return value;
7531
7542
  }
7532
- function assertOnlyKeys(value, allowed, label) {
7543
+ function assertOnlyKeys(value, allowed, label2) {
7533
7544
  for (const key of Object.keys(value)) {
7534
- if (!allowed.has(key)) throw new TypeError(`${label} contains unexpected field '${key}'`);
7545
+ if (!allowed.has(key)) throw new TypeError(`${label2} contains unexpected field '${key}'`);
7535
7546
  }
7536
7547
  }
7537
- function text(value, label) {
7538
- if (typeof value !== "string") throw new TypeError(`${label} must be a string`);
7548
+ function text(value, label2) {
7549
+ if (typeof value !== "string") throw new TypeError(`${label2} must be a string`);
7539
7550
  return value;
7540
7551
  }
7541
- function nonEmptyString3(value, label) {
7542
- const decoded = text(value, label);
7543
- if (decoded.length === 0) throw new TypeError(`${label} must be a non-empty string`);
7552
+ function nonEmptyString3(value, label2) {
7553
+ const decoded = text(value, label2);
7554
+ if (decoded.length === 0) throw new TypeError(`${label2} must be a non-empty string`);
7544
7555
  return decoded;
7545
7556
  }
7546
- function optionalString(value, label) {
7547
- return value === void 0 ? void 0 : text(value, label);
7557
+ function optionalString(value, label2) {
7558
+ return value === void 0 ? void 0 : text(value, label2);
7548
7559
  }
7549
- function boolean(value, label) {
7550
- if (typeof value !== "boolean") throw new TypeError(`${label} must be a boolean`);
7560
+ function boolean(value, label2) {
7561
+ if (typeof value !== "boolean") throw new TypeError(`${label2} must be a boolean`);
7551
7562
  return value;
7552
7563
  }
7553
- function integer(value, label, minimum) {
7564
+ function integer(value, label2, minimum) {
7554
7565
  if (!Number.isSafeInteger(value) || value < minimum) {
7555
- throw new TypeError(`${label} must be a safe integer >= ${minimum}`);
7566
+ throw new TypeError(`${label2} must be a safe integer >= ${minimum}`);
7556
7567
  }
7557
7568
  return value;
7558
7569
  }
7559
- function optionalRecord(value, label) {
7560
- return value === void 0 ? void 0 : record(value, label);
7570
+ function optionalRecord(value, label2) {
7571
+ return value === void 0 ? void 0 : record(value, label2);
7561
7572
  }
7562
7573
  function spanEvidenceUri(traceId, spanId) {
7563
7574
  return `trace://${encodeURIComponent(traceId)}/span/${encodeURIComponent(spanId)}`;
7564
7575
  }
7565
- function validateSpanEvidence(uri, knownSpanUris, label) {
7576
+ function validateSpanEvidence(uri, knownSpanUris, label2) {
7566
7577
  if (knownSpanUris && !knownSpanUris.has(uri)) {
7567
- throw new TypeError(`${label} references unknown span evidence URI '${uri}'`);
7578
+ throw new TypeError(`${label2} references unknown span evidence URI '${uri}'`);
7568
7579
  }
7569
7580
  }
7570
- function decodeEvidenceRef(value, label, knownSpanUris) {
7571
- const ref = record(value, label);
7572
- assertOnlyKeys(ref, EVIDENCE_KEYS, label);
7573
- const kind = nonEmptyString3(ref.kind, `${label}.kind`);
7581
+ function decodeEvidenceRef(value, label2, knownSpanUris) {
7582
+ const ref = record(value, label2);
7583
+ assertOnlyKeys(ref, EVIDENCE_KEYS, label2);
7584
+ const kind = nonEmptyString3(ref.kind, `${label2}.kind`);
7574
7585
  if (!["span", "event", "artifact", "finding", "metric"].includes(kind)) {
7575
- throw new TypeError(`${label}.kind is unsupported`);
7586
+ throw new TypeError(`${label2}.kind is unsupported`);
7576
7587
  }
7577
- const uri = nonEmptyString3(ref.uri, `${label}.uri`);
7578
- if (kind === "span") validateSpanEvidence(uri, knownSpanUris, label);
7579
- const excerpt = optionalString(ref.excerpt, `${label}.excerpt`);
7588
+ const uri = nonEmptyString3(ref.uri, `${label2}.uri`);
7589
+ if (kind === "span") validateSpanEvidence(uri, knownSpanUris, label2);
7590
+ const excerpt = optionalString(ref.excerpt, `${label2}.excerpt`);
7580
7591
  return {
7581
7592
  kind,
7582
7593
  uri,
7583
7594
  ...excerpt === void 0 ? {} : { excerpt }
7584
7595
  };
7585
7596
  }
7586
- function decodeFinding(value, label, knownSpanUris) {
7587
- const finding2 = record(value, label);
7588
- assertOnlyKeys(finding2, FINDING_KEYS, label);
7597
+ function decodeFinding(value, label2, knownSpanUris) {
7598
+ const finding2 = record(value, label2);
7599
+ assertOnlyKeys(finding2, FINDING_KEYS, label2);
7589
7600
  if (finding2.schema_version !== "1.0.0") {
7590
- throw new TypeError(`${label}.schema_version must be '1.0.0'`);
7601
+ throw new TypeError(`${label2}.schema_version must be '1.0.0'`);
7591
7602
  }
7592
- const findingId = nonEmptyString3(finding2.finding_id, `${label}.finding_id`);
7593
- const analystId = nonEmptyString3(finding2.analyst_id, `${label}.analyst_id`);
7594
- const producedAt = nonEmptyString3(finding2.produced_at, `${label}.produced_at`);
7603
+ const findingId = nonEmptyString3(finding2.finding_id, `${label2}.finding_id`);
7604
+ const analystId = nonEmptyString3(finding2.analyst_id, `${label2}.analyst_id`);
7605
+ const producedAt = nonEmptyString3(finding2.produced_at, `${label2}.produced_at`);
7595
7606
  if (!Number.isFinite(Date.parse(producedAt))) {
7596
- throw new TypeError(`${label}.produced_at must be a valid timestamp`);
7607
+ throw new TypeError(`${label2}.produced_at must be a valid timestamp`);
7597
7608
  }
7598
- const severity = nonEmptyString3(finding2.severity, `${label}.severity`);
7609
+ const severity = nonEmptyString3(finding2.severity, `${label2}.severity`);
7599
7610
  if (!["critical", "high", "medium", "low", "info"].includes(severity)) {
7600
- throw new TypeError(`${label}.severity is unsupported`);
7611
+ throw new TypeError(`${label2}.severity is unsupported`);
7601
7612
  }
7602
7613
  if (!Array.isArray(finding2.evidence_refs)) {
7603
- throw new TypeError(`${label}.evidence_refs must be an array`);
7614
+ throw new TypeError(`${label2}.evidence_refs must be an array`);
7604
7615
  }
7605
7616
  if (typeof finding2.confidence !== "number" || !Number.isFinite(finding2.confidence) || finding2.confidence < 0 || finding2.confidence > 1) {
7606
- throw new TypeError(`${label}.confidence must be a finite number from 0 to 1`);
7617
+ throw new TypeError(`${label2}.confidence must be a finite number from 0 to 1`);
7607
7618
  }
7608
- const rationale = optionalString(finding2.rationale, `${label}.rationale`);
7619
+ const rationale = optionalString(finding2.rationale, `${label2}.rationale`);
7609
7620
  const recommendedAction = optionalString(
7610
7621
  finding2.recommended_action,
7611
- `${label}.recommended_action`
7622
+ `${label2}.recommended_action`
7612
7623
  );
7613
- const validationPlan = optionalString(finding2.validation_plan, `${label}.validation_plan`);
7614
- const subject = optionalString(finding2.subject, `${label}.subject`);
7615
- const derivedFromJudge = finding2.derived_from_judge === void 0 ? void 0 : boolean(finding2.derived_from_judge, `${label}.derived_from_judge`);
7616
- const metadata = optionalRecord(finding2.metadata, `${label}.metadata`);
7624
+ const validationPlan = optionalString(finding2.validation_plan, `${label2}.validation_plan`);
7625
+ const subject = optionalString(finding2.subject, `${label2}.subject`);
7626
+ const derivedFromJudge = finding2.derived_from_judge === void 0 ? void 0 : boolean(finding2.derived_from_judge, `${label2}.derived_from_judge`);
7627
+ const metadata = optionalRecord(finding2.metadata, `${label2}.metadata`);
7617
7628
  return {
7618
7629
  schema_version: "1.0.0",
7619
7630
  finding_id: findingId,
7620
7631
  analyst_id: analystId,
7621
7632
  produced_at: producedAt,
7622
7633
  severity,
7623
- area: nonEmptyString3(finding2.area, `${label}.area`),
7624
- claim: nonEmptyString3(finding2.claim, `${label}.claim`),
7625
- evidence_refs: finding2.evidence_refs.map((ref, index) => decodeEvidenceRef(ref, `${label}.evidence_refs[${index}]`, knownSpanUris)),
7634
+ area: nonEmptyString3(finding2.area, `${label2}.area`),
7635
+ claim: nonEmptyString3(finding2.claim, `${label2}.claim`),
7636
+ evidence_refs: finding2.evidence_refs.map((ref, index) => decodeEvidenceRef(ref, `${label2}.evidence_refs[${index}]`, knownSpanUris)),
7626
7637
  confidence: finding2.confidence,
7627
7638
  ...rationale === void 0 ? {} : { rationale },
7628
7639
  ...recommendedAction === void 0 ? {} : { recommended_action: recommendedAction },
@@ -7632,32 +7643,32 @@ function decodeFinding(value, label, knownSpanUris) {
7632
7643
  ...metadata === void 0 ? {} : { metadata }
7633
7644
  };
7634
7645
  }
7635
- function decodeCandidate(value, label, knownSpanUris) {
7636
- const candidate = record(value, label);
7637
- assertOnlyKeys(candidate, CANDIDATE_KEYS, label);
7646
+ function decodeCandidate(value, label2, knownSpanUris) {
7647
+ const candidate = record(value, label2);
7648
+ assertOnlyKeys(candidate, CANDIDATE_KEYS, label2);
7638
7649
  if (candidate.status !== "needs_review") {
7639
- throw new TypeError(`${label}.status must be 'needs_review'`);
7650
+ throw new TypeError(`${label2}.status must be 'needs_review'`);
7640
7651
  }
7641
- const traceId = nonEmptyString3(candidate.traceId, `${label}.traceId`);
7642
- const spanId = nonEmptyString3(candidate.spanId, `${label}.spanId`);
7643
- const evidenceUri = nonEmptyString3(candidate.evidenceUri, `${label}.evidenceUri`);
7652
+ const traceId = nonEmptyString3(candidate.traceId, `${label2}.traceId`);
7653
+ const spanId = nonEmptyString3(candidate.spanId, `${label2}.spanId`);
7654
+ const evidenceUri = nonEmptyString3(candidate.evidenceUri, `${label2}.evidenceUri`);
7644
7655
  if (evidenceUri !== spanEvidenceUri(traceId, spanId)) {
7645
- throw new TypeError(`${label}.evidenceUri does not match its traceId and spanId`);
7656
+ throw new TypeError(`${label2}.evidenceUri does not match its traceId and spanId`);
7646
7657
  }
7647
- validateSpanEvidence(evidenceUri, knownSpanUris, label);
7648
- const metadata = optionalRecord(candidate.metadata, `${label}.metadata`);
7658
+ validateSpanEvidence(evidenceUri, knownSpanUris, label2);
7659
+ const metadata = optionalRecord(candidate.metadata, `${label2}.metadata`);
7649
7660
  return {
7650
- engine: nonEmptyString3(candidate.engine, `${label}.engine`),
7651
- engineVersion: nonEmptyString3(candidate.engineVersion, `${label}.engineVersion`),
7661
+ engine: nonEmptyString3(candidate.engine, `${label2}.engine`),
7662
+ engineVersion: nonEmptyString3(candidate.engineVersion, `${label2}.engineVersion`),
7652
7663
  status: "needs_review",
7653
- group: nonEmptyString3(candidate.group, `${label}.group`),
7654
- rank: integer(candidate.rank, `${label}.rank`, 0),
7655
- groupSize: integer(candidate.groupSize, `${label}.groupSize`, 1),
7664
+ group: nonEmptyString3(candidate.group, `${label2}.group`),
7665
+ rank: integer(candidate.rank, `${label2}.rank`, 0),
7666
+ groupSize: integer(candidate.groupSize, `${label2}.groupSize`, 1),
7656
7667
  traceId,
7657
7668
  spanId,
7658
7669
  evidenceUri,
7659
- summary: nonEmptyString3(candidate.summary, `${label}.summary`),
7660
- actionText: nonEmptyString3(candidate.actionText, `${label}.actionText`),
7670
+ summary: nonEmptyString3(candidate.summary, `${label2}.summary`),
7671
+ actionText: nonEmptyString3(candidate.actionText, `${label2}.actionText`),
7661
7672
  ...metadata === void 0 ? {} : { metadata }
7662
7673
  };
7663
7674
  }
@@ -7691,57 +7702,57 @@ function decodeExternalAnalysisPayload(value) {
7691
7702
  throw new TypeError(`external analyzer payload.kind '${kind}' is unsupported`);
7692
7703
  }
7693
7704
  function decodeExternalAnalysisResult(value, analyzerName, knownSpanUris) {
7694
- const label = `external analyzer '${analyzerName}' result`;
7695
- const result = record(value, label);
7696
- const reportedAnalyzer = nonEmptyString3(result.analyzer, `${label}.analyzer`);
7705
+ const label2 = `external analyzer '${analyzerName}' result`;
7706
+ const result = record(value, label2);
7707
+ const reportedAnalyzer = nonEmptyString3(result.analyzer, `${label2}.analyzer`);
7697
7708
  if (reportedAnalyzer !== analyzerName) {
7698
7709
  throw new TypeError(
7699
7710
  `external analyzer '${analyzerName}' reported analyzer '${reportedAnalyzer}'`
7700
7711
  );
7701
7712
  }
7702
- const kind = nonEmptyString3(result.kind, `${label}.kind`);
7713
+ const kind = nonEmptyString3(result.kind, `${label2}.kind`);
7703
7714
  if (!["report", "findings", "discovery"].includes(kind)) {
7704
- throw new TypeError(`${label}.kind '${kind}' is unsupported`);
7715
+ throw new TypeError(`${label2}.kind '${kind}' is unsupported`);
7705
7716
  }
7706
- const ok = boolean(result.ok, `${label}.ok`);
7707
- const output = text(result.output, `${label}.output`);
7717
+ const ok = boolean(result.ok, `${label2}.ok`);
7718
+ const output = text(result.output, `${label2}.output`);
7708
7719
  if (!ok) {
7709
- assertOnlyKeys(result, RESULT_FAILURE_KEYS, label);
7720
+ assertOnlyKeys(result, RESULT_FAILURE_KEYS, label2);
7710
7721
  return {
7711
7722
  analyzer: analyzerName,
7712
7723
  kind,
7713
7724
  ok: false,
7714
7725
  output,
7715
- error: nonEmptyString3(result.error, `${label}.error`)
7726
+ error: nonEmptyString3(result.error, `${label2}.error`)
7716
7727
  };
7717
7728
  }
7718
7729
  if (kind === "report") {
7719
- assertOnlyKeys(result, RESULT_REPORT_KEYS, label);
7730
+ assertOnlyKeys(result, RESULT_REPORT_KEYS, label2);
7720
7731
  return { analyzer: analyzerName, kind, ok: true, output };
7721
7732
  }
7722
7733
  if (kind === "findings") {
7723
- assertOnlyKeys(result, RESULT_FINDING_KEYS, label);
7734
+ assertOnlyKeys(result, RESULT_FINDING_KEYS, label2);
7724
7735
  if (!Array.isArray(result.findings)) {
7725
- throw new TypeError(`${label}.findings must be an array`);
7736
+ throw new TypeError(`${label2}.findings must be an array`);
7726
7737
  }
7727
7738
  return {
7728
7739
  analyzer: analyzerName,
7729
7740
  kind,
7730
7741
  ok: true,
7731
7742
  output,
7732
- findings: result.findings.map((finding2, index) => decodeFinding(finding2, `${label}.findings[${index}]`, knownSpanUris))
7743
+ findings: result.findings.map((finding2, index) => decodeFinding(finding2, `${label2}.findings[${index}]`, knownSpanUris))
7733
7744
  };
7734
7745
  }
7735
- assertOnlyKeys(result, RESULT_DISCOVERY_KEYS, label);
7746
+ assertOnlyKeys(result, RESULT_DISCOVERY_KEYS, label2);
7736
7747
  if (!Array.isArray(result.candidates)) {
7737
- throw new TypeError(`${label}.candidates must be an array`);
7748
+ throw new TypeError(`${label2}.candidates must be an array`);
7738
7749
  }
7739
7750
  return {
7740
7751
  analyzer: analyzerName,
7741
7752
  kind: "discovery",
7742
7753
  ok: true,
7743
7754
  output,
7744
- candidates: result.candidates.map((candidate, index) => decodeCandidate(candidate, `${label}.candidates[${index}]`, knownSpanUris))
7755
+ candidates: result.candidates.map((candidate, index) => decodeCandidate(candidate, `${label2}.candidates[${index}]`, knownSpanUris))
7745
7756
  };
7746
7757
  }
7747
7758
 
@@ -8524,6 +8535,7 @@ function readPullRequests(spans) {
8524
8535
 
8525
8536
  // src/session-facts.ts
8526
8537
  import { OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND4, TOOL_NAME as TOOL_NAME5 } from "@tangle-network/agent-eval/trace-attributes";
8538
+ import { ingestSpans, rankFirstFailure } from "@tangle-network/agent-eval/diagnosis";
8527
8539
  var SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
8528
8540
  var SPAN_SYNTHESIZED_ATTR = "traces.codex.span_synthesized";
8529
8541
  var CORRUPTION_COUNT_ATTR = "traces.session.corruption_count";
@@ -8899,6 +8911,11 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
8899
8911
  changedFiles: changedFilesFact(files, truncatedInputs, unresolvedPaths),
8900
8912
  firstRecordAt: firstSpan ? { value: firstSpan.start_time, spanIds: [firstSpan.span_id], unavailable: null } : { value: null, spanIds: [], unavailable: "no span of this session's own records carries a parseable start time" },
8901
8913
  lastRecordAt: lastSpan ? { value: lastSpan.end_time, spanIds: [lastSpan.span_id], unavailable: null } : { value: null, spanIds: [], unavailable: "no span of this session's own records carries a parseable end time" },
8914
+ // rankFirstFailure throws on an empty list (distinct from "every span is
8915
+ // UNSET", which it reports as status 'unknown'); a trace whose spans are
8916
+ // all traces.span.subagent — every record folded into subagents' own
8917
+ // reports — legitimately has no records of its own to rank.
8918
+ firstFailure: ownSpans.length === 0 ? { status: "unknown", traceId, unsetCount: 0, reason: "no span of this session's own records" } : rankFirstFailure(ingestSpans(ownSpans, { contentIncluded: true }).spans),
8902
8919
  tokenTotal: tokenSpan ? {
8903
8920
  value: tokenSpan.attributes[SESSION_TOKEN_TOTAL_ATTR],
8904
8921
  spanIds: [tokenSpan.span_id],
@@ -8920,10 +8937,10 @@ function buildSessionFactsReport(spans, options = {}) {
8920
8937
  sessions: computeSessionFacts(spans)
8921
8938
  };
8922
8939
  }
8923
- function factLine(label, fact, rendered) {
8924
- if (fact.value === null) return ` ${label}: unavailable \u2014 ${fact.unavailable}`;
8940
+ function factLine(label2, fact, rendered) {
8941
+ if (fact.value === null) return ` ${label2}: unavailable \u2014 ${fact.unavailable}`;
8925
8942
  const partial = fact.partial ? ` (partial: ${fact.partial})` : "";
8926
- return ` ${label}: ${rendered}${partial}`;
8943
+ return ` ${label2}: ${rendered}${partial}`;
8927
8944
  }
8928
8945
  function renderSessionFacts(report) {
8929
8946
  const lines = [
@@ -8967,6 +8984,7 @@ function renderSessionFacts(report) {
8967
8984
  lines.push(factLine("first record", facts.firstRecordAt, String(facts.firstRecordAt.value)));
8968
8985
  lines.push(factLine("last record", facts.lastRecordAt, String(facts.lastRecordAt.value)));
8969
8986
  lines.push(factLine("token total", facts.tokenTotal, String(facts.tokenTotal.value)));
8987
+ lines.push(` first failure: ${renderFirstFailure(facts.firstFailure)}`);
8970
8988
  if ((facts.unreadRecords.value ?? 0) > 0) {
8971
8989
  lines.push(` unread records: ${facts.unreadRecords.value} (the facts above are computed from the rest)`);
8972
8990
  }
@@ -8977,6 +8995,15 @@ function renderSessionFacts(report) {
8977
8995
  return `${lines.join("\n")}
8978
8996
  `;
8979
8997
  }
8998
+ function renderFirstFailure(failure2) {
8999
+ if (failure2.status === "unknown") return `unknown \u2014 not recorded whether this run failed (${failure2.reason})`;
9000
+ if (failure2.status === "none") return `none recorded (${failure2.reason})`;
9001
+ if (failure2.status === "ambiguous") {
9002
+ return `ambiguous ${failure2.stage}: ${failure2.reason} (${failure2.candidates.join(", ")})`;
9003
+ }
9004
+ const message = failure2.message ? ` \u2014 ${failure2.message.split("\n")[0]?.slice(0, 160)}` : "";
9005
+ return `${failure2.stage} ${failure2.kind} "${failure2.name}" [${failure2.spanId}], blame ${failure2.blame}${message}`;
9006
+ }
8980
9007
  var PREPARED_CONTEXT_BYTE_CEILING = 3e4;
8981
9008
  function renderSessionFactsContext(report, options = {}) {
8982
9009
  const ceiling = options.byteCeiling ?? PREPARED_CONTEXT_BYTE_CEILING;
@@ -9124,7 +9151,26 @@ function compactFacts(facts) {
9124
9151
  },
9125
9152
  first_record_at: factJson(facts.firstRecordAt),
9126
9153
  last_record_at: factJson(facts.lastRecordAt),
9127
- token_total: factJson(facts.tokenTotal)
9154
+ token_total: factJson(facts.tokenTotal),
9155
+ first_failure: compactFirstFailure(facts.firstFailure)
9156
+ };
9157
+ }
9158
+ function compactFirstFailure(failure2) {
9159
+ if (failure2.status === "unknown") {
9160
+ return { status: "unknown", reason: failure2.reason, unset_count: failure2.unsetCount };
9161
+ }
9162
+ if (failure2.status === "none") return { status: "none", reason: failure2.reason };
9163
+ if (failure2.status === "ambiguous") {
9164
+ return { status: "ambiguous", stage: failure2.stage, reason: failure2.reason, span_ids: failure2.candidates };
9165
+ }
9166
+ return {
9167
+ status: "found",
9168
+ stage: failure2.stage,
9169
+ span_id: failure2.spanId,
9170
+ name: failure2.name,
9171
+ kind: failure2.kind,
9172
+ blame: failure2.blame,
9173
+ message: failure2.message
9128
9174
  };
9129
9175
  }
9130
9176
  function sessionFactsContext(spans, options = {}) {
@@ -9135,7 +9181,7 @@ function sessionFactsContext(spans, options = {}) {
9135
9181
  if (report.sessions.length === 0) return void 0;
9136
9182
  return renderSessionFactsContext(report, options);
9137
9183
  }
9138
- var SESSION_FACTS_VERSION_SUFFIX = "session-facts.1";
9184
+ var SESSION_FACTS_VERSION_SUFFIX = "session-facts.2";
9139
9185
  function withSessionFactsContext(definitions, spans, options = {}) {
9140
9186
  const sheet = sessionFactsContext(spans, options);
9141
9187
  if (sheet === void 0) return [...definitions];
@@ -9169,7 +9215,6 @@ import { dirname as dirname8, isAbsolute as isAbsolute4, join as join20, relativ
9169
9215
 
9170
9216
  // src/bundle.ts
9171
9217
  import { createHash as createHash8 } from "crypto";
9172
- import { readFileSync as readFileSync2 } from "fs";
9173
9218
  import { copyFile, cp, lstat, mkdir as mkdir2, readdir as readdir16, readFile as readFile7, stat as stat14, writeFile as writeFile5 } from "fs/promises";
9174
9219
  import { dirname as dirname7, join as join19, relative as relative4, resolve as resolve6, sep } from "path";
9175
9220
 
@@ -9196,34 +9241,13 @@ var SEVERITY_BADGE = {
9196
9241
  info: "\u2139\uFE0F INFO"
9197
9242
  };
9198
9243
  var CAPABILITY_ANALYSES = {
9199
- "token-accounting": {
9200
- analysis: "execution facts \u2192 Direct model usage (input/output/reasoning/cache token totals)",
9201
- built: true
9202
- },
9203
- "cost-attribution": {
9204
- analysis: "execution facts \u2192 Cost coverage (observed vs estimated USD)",
9205
- built: true
9206
- },
9207
- "tool-usage": {
9208
- analysis: "tool-usage table + per-tool duplicate/retry/error rates + stuck-loop detection",
9209
- built: true
9210
- },
9211
- "loop-convergence": {
9212
- analysis: "round-over-round convergence (did round N+1 improve on N)",
9213
- built: true
9214
- },
9215
- "tree-comparison": {
9216
- analysis: "arm-vs-arm comparison across branches of one run \u2014 NOT YET IMPLEMENTED in this package",
9217
- built: false
9218
- },
9219
- "steering-chain": {
9220
- analysis: "steering chain (which verdict caused which retry)",
9221
- built: true
9222
- },
9223
- "latency-analysis": {
9224
- analysis: "latency + critical-path timing distributions",
9225
- built: true
9226
- }
9244
+ "token-accounting": "execution facts \u2192 Direct model usage (input/output/reasoning/cache token totals)",
9245
+ "cost-attribution": "execution facts \u2192 Cost coverage (observed vs estimated USD)",
9246
+ "tool-usage": "tool-usage table + per-tool duplicate/retry/error rates + stuck-loop detection",
9247
+ "loop-convergence": "round-over-round convergence (did round N+1 improve on N)",
9248
+ "tree-comparison": "arm-vs-arm comparison: `traces diff <file>#branch=<a> <file>#branch=<b>` marks where two arms first diverge",
9249
+ "steering-chain": "steering chain (which verdict caused which retry)",
9250
+ "latency-analysis": "latency + critical-path timing distributions"
9227
9251
  };
9228
9252
  function unavailableCapabilities(validation) {
9229
9253
  return new Map(
@@ -9300,13 +9324,10 @@ function conformanceOfSpans(spans) {
9300
9324
  function skippedAnalyses(validation) {
9301
9325
  return validation.capabilities.filter((capability) => !capability.available).map((capability) => ({
9302
9326
  capability: capability.name,
9303
- analysis: CAPABILITY_ANALYSES[capability.name]?.analysis ?? "analyses keyed off this capability",
9327
+ analysis: CAPABILITY_ANALYSES[capability.name] ?? "analyses keyed off this capability",
9304
9328
  reason: capability.reason ?? "no reason recorded"
9305
9329
  }));
9306
9330
  }
9307
- function unbuiltAnalyses(validation) {
9308
- return validation.capabilities.filter((capability) => capability.available && CAPABILITY_ANALYSES[capability.name]?.built === false).map((capability) => capability.name);
9309
- }
9310
9331
  function tableCell(value) {
9311
9332
  return value.replace(/\s+/g, " ").replaceAll("|", "\\|").trim();
9312
9333
  }
@@ -9317,12 +9338,8 @@ function spanIdSample(finding2) {
9317
9338
  return ids.length > SPAN_ID_SAMPLE ? `${shown} +${ids.length - SPAN_ID_SAMPLE} more` : shown;
9318
9339
  }
9319
9340
  function capabilityRow(capability) {
9320
- const entry = CAPABILITY_ANALYSES[capability.name];
9321
- const analysis = entry?.analysis ?? "\u2014";
9322
- if (!capability.available) {
9323
- return `| \`${capability.name}\` | \u274C unavailable | ${tableCell(analysis)} | ${tableCell(capability.reason ?? "no reason recorded")} |`;
9324
- }
9325
- return entry?.built === false ? `| \`${capability.name}\` | \u26A0\uFE0F available, unused | ${tableCell(analysis)} | the trace supports it; this package has no analysis for it yet |` : `| \`${capability.name}\` | \u2705 available | ${tableCell(analysis)} | \u2014 |`;
9341
+ const analysis = CAPABILITY_ANALYSES[capability.name] ?? "\u2014";
9342
+ return capability.available ? `| \`${capability.name}\` | \u2705 available | ${tableCell(analysis)} | \u2014 |` : `| \`${capability.name}\` | \u274C unavailable | ${tableCell(analysis)} | ${tableCell(capability.reason ?? "no reason recorded")} |`;
9326
9343
  }
9327
9344
  function renderInheritedLoss(lines, unreadable) {
9328
9345
  if (unreadable === void 0 || unreadable.inherited === 0) return;
@@ -9445,26 +9462,17 @@ function conformanceBody(validation, opts) {
9445
9462
  for (const capability of validation.capabilities) lines.push(capabilityRow(capability));
9446
9463
  lines.push("");
9447
9464
  const skipped = skippedAnalyses(validation);
9448
- const unbuilt = unbuiltAnalyses(validation);
9449
- if (skipped.length > 0 || unbuilt.length > 0) {
9465
+ if (skipped.length > 0) {
9450
9466
  lines.push(`${heading}# analyses skipped, and why`);
9451
9467
  lines.push("");
9452
- if (skipped.length > 0) {
9453
- lines.push(
9454
- "These reported nothing because the trace never carried what they read \u2014 not because the run was uneventful. Every section they feed is marked at the table, not only here."
9455
- );
9456
- lines.push("");
9457
- for (const entry of skipped) {
9458
- lines.push(`- **${entry.analysis}** \u2014 skipped: ${entry.reason} (\`${entry.capability}\`).`);
9459
- }
9460
- lines.push("");
9461
- }
9462
- if (unbuilt.length > 0) {
9463
- lines.push(
9464
- `These are supported by the trace and **not yet implemented here**: ${unbuilt.map((name) => `\`${name}\``).join(", ")}. The spans carry what they need; the analysis does not exist, so nothing about them appears below.`
9465
- );
9466
- lines.push("");
9468
+ lines.push(
9469
+ "These reported nothing because the trace never carried what they read \u2014 not because the run was uneventful. Every section they feed is marked at the table, not only here."
9470
+ );
9471
+ lines.push("");
9472
+ for (const entry of skipped) {
9473
+ lines.push(`- **${entry.analysis}** \u2014 skipped: ${entry.reason} (\`${entry.capability}\`).`);
9467
9474
  }
9475
+ lines.push("");
9468
9476
  }
9469
9477
  return lines.join("\n");
9470
9478
  }
@@ -9829,10 +9837,10 @@ async function readLinuxProcProcessTable() {
9829
9837
  const rows = await Promise.all(
9830
9838
  entries.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name)).map(async (entry) => {
9831
9839
  try {
9832
- const stat18 = await readFile5(`/proc/${entry.name}/stat`, "utf8");
9833
- const commandEnd = stat18.lastIndexOf(")");
9840
+ const stat19 = await readFile5(`/proc/${entry.name}/stat`, "utf8");
9841
+ const commandEnd = stat19.lastIndexOf(")");
9834
9842
  if (commandEnd < 0) return void 0;
9835
- const fields = stat18.slice(commandEnd + 1).trim().split(/\s+/);
9843
+ const fields = stat19.slice(commandEnd + 1).trim().split(/\s+/);
9836
9844
  const pid = Number(entry.name);
9837
9845
  const parentPid = Number(fields[1]);
9838
9846
  if (!Number.isSafeInteger(pid) || pid < 1 || !Number.isSafeInteger(parentPid) || parentPid < 0) {
@@ -10051,16 +10059,16 @@ async function spanUrisFromArtifact(otlpPath, signal) {
10051
10059
  const uris = /* @__PURE__ */ new Set();
10052
10060
  let index = 0;
10053
10061
  for await (const value of readJsonl(otlpPath, { signal })) {
10054
- const label = `${otlpPath}:${index + 1}`;
10062
+ const label2 = `${otlpPath}:${index + 1}`;
10055
10063
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
10056
- throw new TypeError(`${label} must contain an object`);
10064
+ throw new TypeError(`${label2} must contain an object`);
10057
10065
  }
10058
10066
  const row = value;
10059
10067
  if (typeof row.trace_id !== "string" || row.trace_id.trim().length === 0) {
10060
- throw new TypeError(`${label}.trace_id must be a non-empty string`);
10068
+ throw new TypeError(`${label2}.trace_id must be a non-empty string`);
10061
10069
  }
10062
10070
  if (typeof row.span_id !== "string" || row.span_id.trim().length === 0) {
10063
- throw new TypeError(`${label}.span_id must be a non-empty string`);
10071
+ throw new TypeError(`${label2}.span_id must be a non-empty string`);
10064
10072
  }
10065
10073
  uris.add(spanEvidenceUri(row.trace_id, row.span_id));
10066
10074
  index += 1;
@@ -10433,10 +10441,10 @@ function decimal(value, digits = 1) {
10433
10441
  function measured(value, render) {
10434
10442
  return value === null ? "not captured" : render(value);
10435
10443
  }
10436
- function distributionRow(label, distribution, unit = "") {
10437
- if (distribution.n === 0) return `| ${label} | 0 | not captured | not captured | not captured | not captured | not captured |`;
10444
+ function distributionRow(label2, distribution, unit = "") {
10445
+ if (distribution.n === 0) return `| ${label2} | 0 | not captured | not captured | not captured | not captured | not captured |`;
10438
10446
  const value = (number) => measured(number, (captured) => `${decimal(captured)}${unit}`);
10439
- return `| ${label} | ${count(distribution.n)} | ${value(distribution.min)} | ${value(distribution.mean)} | ${value(distribution.p50)} | ${value(distribution.p95)} | ${value(distribution.max)} |`;
10447
+ return `| ${label2} | ${count(distribution.n)} | ${value(distribution.min)} | ${value(distribution.mean)} | ${value(distribution.p50)} | ${value(distribution.p95)} | ${value(distribution.max)} |`;
10440
10448
  }
10441
10449
  function tokenRows(usage) {
10442
10450
  const rows = [
@@ -10447,7 +10455,7 @@ function tokenRows(usage) {
10447
10455
  ["Cache write", usage.totals.cacheWrite, usage.cacheWrite]
10448
10456
  ];
10449
10457
  return rows.map(
10450
- ([label, total, values]) => `| ${label} | ${count(total)} | ${count(values.n)} | ${measured(values.mean, decimal)} | ${measured(values.p50, decimal)} | ${measured(values.p95, decimal)} | ${measured(values.max, count)} |`
10458
+ ([label2, total, values]) => `| ${label2} | ${count(total)} | ${count(values.n)} | ${measured(values.mean, decimal)} | ${measured(values.p50, decimal)} | ${measured(values.p95, decimal)} | ${measured(values.max, count)} |`
10451
10459
  );
10452
10460
  }
10453
10461
  function analysisCost(result) {
@@ -10940,9 +10948,9 @@ function renderAdoption(ar) {
10940
10948
  "- **Prompt, tools, MCP, hooks, and full agent profile:** not assessed; no baseline profile was supplied."
10941
10949
  );
10942
10950
  if (ar.skillPenetration === null) {
10943
- const label = ar.skillTelemetryStatus === "unsupported" ? "uncaptured/unsupported" : "uncaptured/unknown";
10951
+ const label2 = ar.skillTelemetryStatus === "unsupported" ? "uncaptured/unsupported" : "uncaptured/unknown";
10944
10952
  lines.push(
10945
- `- **Explicit skill invocation rate:** ${label} (${ar.skillTelemetrySessions}/${ar.executionGroupCount} observed group(s) expose a dedicated Skill event)`
10953
+ `- **Explicit skill invocation rate:** ${label2} (${ar.skillTelemetrySessions}/${ar.executionGroupCount} observed group(s) expose a dedicated Skill event)`
10946
10954
  );
10947
10955
  } else {
10948
10956
  const excluded = ar.executionGroupCount - ar.skillTelemetrySessions;
@@ -12246,6 +12254,17 @@ async function collectSessionSelection(groups, options = {}) {
12246
12254
  return { rows, ...workflow ? { workflow } : {} };
12247
12255
  }
12248
12256
 
12257
+ // src/version.ts
12258
+ import { readFileSync as readFileSync2 } from "fs";
12259
+ var cached;
12260
+ function tracesVersion() {
12261
+ if (cached) return cached;
12262
+ const pkg = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
12263
+ if (typeof pkg.version !== "string" || !pkg.version) throw new Error("package.json is missing version");
12264
+ cached = pkg.version;
12265
+ return cached;
12266
+ }
12267
+
12249
12268
  // src/bundle.ts
12250
12269
  var SESSION_WINDOW_PAD_MS = 15 * 60 * 1e3;
12251
12270
  var KNOWN_LIMITS = [
@@ -12538,7 +12557,7 @@ async function assembleSessionBundle(opts) {
12538
12557
  transcriptPath: resolve6(ref.path),
12539
12558
  transcriptSha256,
12540
12559
  contextRoot,
12541
- tracesVersion: tracesPackageVersion(),
12560
+ tracesVersion: tracesVersion(),
12542
12561
  sessionWindow: { firstSpanAt, lastSpanAt, padMs: SESSION_WINDOW_PAD_MS }
12543
12562
  },
12544
12563
  files,
@@ -12682,16 +12701,6 @@ async function verifySessionBundle(bundleDir) {
12682
12701
  ...source ? { source } : {}
12683
12702
  };
12684
12703
  }
12685
- var cachedVersion;
12686
- function tracesPackageVersion() {
12687
- if (cachedVersion) return cachedVersion;
12688
- const pkg = JSON.parse(
12689
- readFileSync2(new URL("../package.json", import.meta.url), "utf8")
12690
- );
12691
- if (typeof pkg.version !== "string" || !pkg.version) throw new Error("package.json is missing version");
12692
- cachedVersion = pkg.version;
12693
- return cachedVersion;
12694
- }
12695
12704
 
12696
12705
  // src/bundle-source.ts
12697
12706
  var SHA256 = /^[a-f0-9]{64}$/;
@@ -13319,9 +13328,9 @@ async function containedDirectory(path) {
13319
13328
  }
13320
13329
  return canonical;
13321
13330
  }
13322
- async function containedFile(root, candidate, label) {
13331
+ async function containedFile(root, candidate, label2) {
13323
13332
  const path = await optionalContainedFile(root, candidate);
13324
- if (!path) throw new Error(`${label} is missing`);
13333
+ if (!path) throw new Error(`${label2} is missing`);
13325
13334
  return path;
13326
13335
  }
13327
13336
  async function optionalContainedFile(root, candidate) {
@@ -13553,49 +13562,49 @@ function parseSteps(text2, row) {
13553
13562
  );
13554
13563
  }
13555
13564
  return parsed.map((value, index) => {
13556
- const label = `${row.trajId}/steps.json[${index}]`;
13557
- if (!isObject3(value)) throw new TypeError(`${label} must be an object`);
13558
- const stepId = positiveInteger2(value.step_id, `${label}.step_id`);
13565
+ const label2 = `${row.trajId}/steps.json[${index}]`;
13566
+ if (!isObject3(value)) throw new TypeError(`${label2} must be an object`);
13567
+ const stepId = positiveInteger2(value.step_id, `${label2}.step_id`);
13559
13568
  if (stepId !== index + 1) {
13560
- throw new Error(`${label}.step_id is ${stepId}; expected ${index + 1}`);
13569
+ throw new Error(`${label2}.step_id is ${stepId}; expected ${index + 1}`);
13561
13570
  }
13562
13571
  if (!Object.hasOwn(value, "observation")) {
13563
- throw new TypeError(`${label}.observation is required and may be null`);
13572
+ throw new TypeError(`${label2}.observation is required and may be null`);
13564
13573
  }
13565
13574
  const observation = value.observation;
13566
13575
  if (observation !== null && typeof observation !== "string") {
13567
- throw new TypeError(`${label}.observation must be a string or null`);
13576
+ throw new TypeError(`${label2}.observation must be a string or null`);
13568
13577
  }
13569
13578
  return {
13570
13579
  step_id: stepId,
13571
- action: requiredString(value.action, `${label}.action`),
13580
+ action: requiredString(value.action, `${label2}.action`),
13572
13581
  observation,
13573
- ...value.thinking === void 0 ? {} : { thinking: requiredString(value.thinking, `${label}.thinking`) },
13582
+ ...value.thinking === void 0 ? {} : { thinking: requiredString(value.thinking, `${label2}.thinking`) },
13574
13583
  ...value.parallel_group === void 0 ? {} : {
13575
13584
  parallel_group: nonNegativeInteger(
13576
13585
  value.parallel_group,
13577
- `${label}.parallel_group`
13586
+ `${label2}.parallel_group`
13578
13587
  )
13579
13588
  },
13580
- ...value.tool_type === void 0 ? {} : { tool_type: requiredString(value.tool_type, `${label}.tool_type`) },
13581
- action_ref: fileRef(value.action_ref, `${label}.action_ref`),
13582
- observation_ref: fileRef(value.observation_ref, `${label}.observation_ref`)
13589
+ ...value.tool_type === void 0 ? {} : { tool_type: requiredString(value.tool_type, `${label2}.tool_type`) },
13590
+ action_ref: fileRef(value.action_ref, `${label2}.action_ref`),
13591
+ observation_ref: fileRef(value.observation_ref, `${label2}.observation_ref`)
13583
13592
  };
13584
13593
  });
13585
13594
  }
13586
- function fileRef(value, label) {
13595
+ function fileRef(value, label2) {
13587
13596
  if (value === null) return null;
13588
- if (!isObject3(value)) throw new TypeError(`${label} must be an object or null`);
13589
- const lineStart = positiveInteger2(value.line_start, `${label}.line_start`);
13590
- const lineEnd = positiveInteger2(value.line_end, `${label}.line_end`);
13597
+ if (!isObject3(value)) throw new TypeError(`${label2} must be an object or null`);
13598
+ const lineStart = positiveInteger2(value.line_start, `${label2}.line_start`);
13599
+ const lineEnd = positiveInteger2(value.line_end, `${label2}.line_end`);
13591
13600
  if (lineEnd < lineStart) {
13592
- throw new RangeError(`${label}.line_end must not precede line_start`);
13601
+ throw new RangeError(`${label2}.line_end must not precede line_start`);
13593
13602
  }
13594
13603
  return {
13595
- path: requiredString(value.path, `${label}.path`),
13604
+ path: requiredString(value.path, `${label2}.path`),
13596
13605
  line_start: lineStart,
13597
13606
  line_end: lineEnd,
13598
- content: stringField(value.content, `${label}.content`)
13607
+ content: stringField(value.content, `${label2}.content`)
13599
13608
  };
13600
13609
  }
13601
13610
  async function readOptionalTask(trajectoryDirectory, caseDirectory, row, signal) {
@@ -13652,26 +13661,26 @@ function normalizedLabels(value) {
13652
13661
  function compact(value) {
13653
13662
  return value.replace(/\s+/g, "");
13654
13663
  }
13655
- function positiveInteger2(value, label) {
13664
+ function positiveInteger2(value, label2) {
13656
13665
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
13657
- throw new TypeError(`${label} must be a positive integer`);
13666
+ throw new TypeError(`${label2} must be a positive integer`);
13658
13667
  }
13659
13668
  return value;
13660
13669
  }
13661
- function nonNegativeInteger(value, label) {
13670
+ function nonNegativeInteger(value, label2) {
13662
13671
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
13663
- throw new TypeError(`${label} must be a non-negative integer`);
13672
+ throw new TypeError(`${label2} must be a non-negative integer`);
13664
13673
  }
13665
13674
  return value;
13666
13675
  }
13667
- function requiredString(value, label) {
13676
+ function requiredString(value, label2) {
13668
13677
  if (typeof value !== "string" || !value.trim()) {
13669
- throw new TypeError(`${label} must be a non-empty string`);
13678
+ throw new TypeError(`${label2} must be a non-empty string`);
13670
13679
  }
13671
13680
  return value.trim();
13672
13681
  }
13673
- function stringField(value, label) {
13674
- if (typeof value !== "string") throw new TypeError(`${label} must be a string`);
13682
+ function stringField(value, label2) {
13683
+ if (typeof value !== "string") throw new TypeError(`${label2} must be a string`);
13675
13684
  return value;
13676
13685
  }
13677
13686
  function errorMessage(error) {
@@ -15015,16 +15024,16 @@ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, si
15015
15024
  const spanTraces = /* @__PURE__ */ new Map();
15016
15025
  let index = 0;
15017
15026
  for await (const value of readJsonl(otlpPath, { signal })) {
15018
- const label = `${otlpPath}:${index + 1}`;
15027
+ const label2 = `${otlpPath}:${index + 1}`;
15019
15028
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
15020
- throw new TypeError(`${label} must contain an object`);
15029
+ throw new TypeError(`${label2} must contain an object`);
15021
15030
  }
15022
15031
  const row = value;
15023
15032
  if (typeof row.trace_id !== "string" || row.trace_id.length === 0) {
15024
- throw new TypeError(`${label}.trace_id must be a non-empty string`);
15033
+ throw new TypeError(`${label2}.trace_id must be a non-empty string`);
15025
15034
  }
15026
15035
  if (typeof row.span_id !== "string" || row.span_id.length === 0) {
15027
- throw new TypeError(`${label}.span_id must be a non-empty string`);
15036
+ throw new TypeError(`${label2}.span_id must be a non-empty string`);
15028
15037
  }
15029
15038
  rows.push(row);
15030
15039
  const traces = spanTraces.get(row.span_id) ?? /* @__PURE__ */ new Set();
@@ -15366,53 +15375,55 @@ function primeAnalyzer(opts = {}) {
15366
15375
  }
15367
15376
 
15368
15377
  // src/redact.ts
15369
- import { DEFAULT_REDACTION_RULES, redactValue } from "@tangle-network/agent-eval/traces";
15370
- var CODING_REDACTION_RULES = [
15371
- { id: "github-token", pattern: /\b(?:gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{22,})\b/g },
15372
- { id: "aws-akid", pattern: /\bAKIA[0-9A-Z]{16}\b/g },
15373
- { id: "slack-token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
15374
- { id: "jwt", pattern: /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g },
15375
- { id: "bearer", pattern: /\bBearer\s+[A-Za-z0-9._-]{16,}/gi },
15376
- {
15377
- id: "private-key",
15378
- pattern: /-----BEGIN(?:[A-Z ]+)?PRIVATE KEY-----[\s\S]*?-----END(?:[A-Z ]+)?PRIVATE KEY-----/g
15379
- },
15380
- // Match the whole identifier because underscores prevent a word boundary before a secret suffix.
15381
- {
15382
- id: "assigned-secret",
15383
- pattern: /(?<![A-Za-z0-9_-])(?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)(?![A-Za-z0-9_-])\+?=(?:\$\((?:\\.|[^\\\r\n])*\)|\$\{(?:\\.|[^}\\\r\n])*\}|`(?:\\.|[^`\\\r\n])*`|\$?'(?:\\.|[^'\\\r\n])*'|\$?"(?:\\.|[^"\\\r\n])*"|\\[^\r\n]|[^\s;&|<>()"'`\\\r\n])+/gi
15384
- },
15385
- // Config-file secrets (YAML/INI line keys, JSON quoted keys) — the coverage the shell
15386
- // rule above deliberately gave up to spare prose ("Narrative token: x") and type
15387
- // annotations ("(token: string)"). The line anchor is the discriminator: config keys
15388
- // start their line, prose colons never do; JSON keys are safe anywhere because both
15389
- // sides are quoted. Identifiers must END in a secret word, same as the shell rule.
15390
- {
15391
- id: "config-secret",
15392
- pattern: /(?:^[ \t]*["']?(?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)(?![A-Za-z0-9_-])["']?[ \t]*[:=][ \t]*|["'](?:[A-Za-z_][A-Za-z0-9_-]*)?(?:api[_-]?key|secret|token|password|passwd|access[_-]?token|client[_-]?secret)["']\s*:\s*)["']?[A-Za-z0-9._\-]{12,}["']?/gim
15393
- },
15394
- // Credentials embedded in URLs — common when a prompt pastes a curl/clone line.
15395
- { id: "url-userinfo", pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@/gi },
15396
- {
15397
- id: "url-secret-param",
15398
- pattern: /[?&](?:access[_-]?token|api[_-]?key|apikey|auth|token|secret|password|sig|signature)=[^&\s"'#)]+/gi
15399
- }
15400
- ];
15401
- var TRACES_REDACTION_RULES = [...DEFAULT_REDACTION_RULES, ...CODING_REDACTION_RULES];
15402
- function redactSpans(spans, rules = TRACES_REDACTION_RULES) {
15403
- const report = { redactionCount: 0, byRule: {} };
15404
- const out = spans.map((s) => {
15405
- const attributes2 = redactValue(stripSourceAttributes(s.attributes), rules, report).value;
15406
- normalizeToolIoAttributes(attributes2);
15407
- let status = s.status;
15408
- if (status.message) {
15409
- const m = redactValue(status.message, rules, report).value;
15410
- if (typeof m === "string" && m !== status.message) status = { ...status, message: m };
15411
- }
15412
- return { ...s, attributes: attributes2, status };
15378
+ import {
15379
+ assessShareSafety,
15380
+ combineVerdicts,
15381
+ redact
15382
+ } from "@tangle-network/agent-eval/traces";
15383
+ function redactSpans(spans, options = {}) {
15384
+ const parts = spans.map((span2) => ({
15385
+ name: span2.name,
15386
+ attributes: stripSourceAttributes(span2.attributes),
15387
+ message: span2.status.message,
15388
+ links: span2.links?.map((link2) => ({
15389
+ ...link2,
15390
+ ...link2.attributes ? { attributes: stripSourceAttributes(link2.attributes) } : {}
15391
+ }))
15392
+ }));
15393
+ const { value, report } = redact(parts, options);
15394
+ const out = spans.map((span2, index) => {
15395
+ const part = value[index];
15396
+ normalizeToolIoAttributes(part.attributes);
15397
+ const status = part.message !== void 0 && part.message !== span2.status.message ? { ...span2.status, message: part.message } : span2.status;
15398
+ return {
15399
+ ...span2,
15400
+ name: part.name,
15401
+ attributes: part.attributes,
15402
+ status,
15403
+ ...part.links ? { links: part.links } : {}
15404
+ };
15413
15405
  });
15414
15406
  return { spans: out, report };
15415
15407
  }
15408
+ function assessSpans(spans, profile = "default") {
15409
+ return combineVerdicts(
15410
+ profile,
15411
+ spans.map((span2) => {
15412
+ const verdict = assessShareSafety(
15413
+ { name: span2.name, attributes: span2.attributes, status: span2.status, links: span2.links },
15414
+ { profile }
15415
+ );
15416
+ return {
15417
+ ...verdict,
15418
+ findings: verdict.findings.map((finding2) => ({
15419
+ ...finding2,
15420
+ paths: finding2.paths.map((path) => `span:${span2.span_id}${path}`)
15421
+ })),
15422
+ unreadable: verdict.unreadable.map((entry) => `span:${span2.span_id}${entry}`)
15423
+ };
15424
+ })
15425
+ );
15426
+ }
15416
15427
  async function applyRedactor(spans, redactor) {
15417
15428
  const capturedKeys = ["content", ...TOOL_IO_VALUE_KEYS];
15418
15429
  const fields = [];
@@ -16078,7 +16089,7 @@ function exportTraceEvidenceRows(rows, opts = {}, wrapper) {
16078
16089
  format,
16079
16090
  spans: validated,
16080
16091
  redactionCount: redacted.report.redactionCount,
16081
- redactionsByRule: redacted.report.byRule
16092
+ redactionsByDetector: redacted.report.byDetector
16082
16093
  };
16083
16094
  }
16084
16095
  function exportTraceEvidenceText(text2, opts = {}) {
@@ -16091,7 +16102,7 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
16091
16102
  const spans = [];
16092
16103
  const sandboxRows = [];
16093
16104
  let redactionCount = 0;
16094
- const redactionsByRule = {};
16105
+ const redactionsByDetector = {};
16095
16106
  for await (const row of readJsonl(inputPath)) {
16096
16107
  format ??= detectFormat([row], opts.format ?? "auto");
16097
16108
  if (format === "sandbox-events") {
@@ -16105,8 +16116,8 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
16105
16116
  });
16106
16117
  appendAll(spans, converted.spans);
16107
16118
  redactionCount += converted.redactionCount;
16108
- for (const [rule, count2] of Object.entries(converted.redactionsByRule)) {
16109
- redactionsByRule[rule] = (redactionsByRule[rule] ?? 0) + count2;
16119
+ for (const [rule, count2] of Object.entries(converted.redactionsByDetector)) {
16120
+ redactionsByDetector[rule] = (redactionsByDetector[rule] ?? 0) + count2;
16110
16121
  }
16111
16122
  }
16112
16123
  if (!format) throw new Error("input file is empty");
@@ -16121,7 +16132,7 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
16121
16132
  format,
16122
16133
  spans: validateOtlpSpans(spans, `${format} file export`),
16123
16134
  redactionCount,
16124
- redactionsByRule
16135
+ redactionsByDetector
16125
16136
  };
16126
16137
  }
16127
16138
  const text2 = await readFile12(inputPath, "utf8");
@@ -16539,8 +16550,8 @@ function sessionFrom(spans, opts) {
16539
16550
  ...opts.session?.path ? { path: opts.session.path } : {}
16540
16551
  };
16541
16552
  }
16542
- function evidence2(kind, label, value, spanIds) {
16543
- return { kind, label, value, ...spanIds && spanIds.length > 0 ? { spanIds } : {} };
16553
+ function evidence2(kind, label2, value, spanIds) {
16554
+ return { kind, label: label2, value, ...spanIds && spanIds.length > 0 ? { spanIds } : {} };
16544
16555
  }
16545
16556
  function finding(input) {
16546
16557
  const fingerprint = hash([input.ruleId, ...input.signature]);
@@ -17417,8 +17428,8 @@ function buildSpanRunTree(spans, source) {
17417
17428
  for (const root of childrenOf.get(null) ?? []) walk(root, null, null, 0);
17418
17429
  const subtotal = /* @__PURE__ */ new Map();
17419
17430
  const subtotalOf = (key) => {
17420
- const cached = subtotal.get(key);
17421
- if (cached !== void 0) return cached;
17431
+ const cached2 = subtotal.get(key);
17432
+ if (cached2 !== void 0) return cached2;
17422
17433
  const node = building.get(key);
17423
17434
  if (node === void 0) return ZERO_ROLL;
17424
17435
  subtotal.set(key, ZERO_ROLL);
@@ -18956,6 +18967,169 @@ async function projectSessionBundle(opts) {
18956
18967
  return { directory: outDir, manifestPath, manifest, leakCheck };
18957
18968
  }
18958
18969
 
18970
+ // src/diff.ts
18971
+ import { stat as stat18 } from "fs/promises";
18972
+ import { ATTR as ATTR5 } from "@tangle-network/agent-trace-contract";
18973
+ import { ingestSpans as ingestSpans2 } from "@tangle-network/agent-eval/diagnosis";
18974
+ import { diffSteps, diffStepsFromSpans } from "@tangle-network/agent-eval/pipelines";
18975
+ async function readSpans(path) {
18976
+ const otlp = await readOtlpInput(path);
18977
+ if (otlp.spans.length > 0) return [...otlp.spans];
18978
+ if (!(await stat18(path)).isFile()) {
18979
+ throw new Error(`${path}: no OTLP spans found (${otlp.issues.length} unusable row(s))`);
18980
+ }
18981
+ return (await exportTraceEvidenceFile(path)).spans;
18982
+ }
18983
+ function branchSpans(spans, branch) {
18984
+ const byId = /* @__PURE__ */ new Map();
18985
+ for (const span2 of spans) byId.set(span2.span_id, span2);
18986
+ const children = /* @__PURE__ */ new Map();
18987
+ for (const span2 of spans) {
18988
+ if (!span2.parent_span_id) continue;
18989
+ const list = children.get(span2.parent_span_id);
18990
+ if (list) list.push(span2);
18991
+ else children.set(span2.parent_span_id, [span2]);
18992
+ }
18993
+ const matched = spans.filter(
18994
+ (span2) => span2.attributes[ATTR5.branchId] === branch || span2.attributes[ATTR5.branchArm] === branch
18995
+ );
18996
+ const matchedIds = new Set(matched.map((span2) => span2.span_id));
18997
+ const hasMatchedAncestor = (span2) => {
18998
+ let current = span2;
18999
+ while (current.parent_span_id) {
19000
+ const parent = byId.get(current.parent_span_id);
19001
+ if (!parent) return false;
19002
+ if (matchedIds.has(parent.span_id)) return true;
19003
+ current = parent;
19004
+ }
19005
+ return false;
19006
+ };
19007
+ const roots = matched.filter((span2) => !hasMatchedAncestor(span2));
19008
+ if (roots.length > 1) {
19009
+ const named = roots.map((span2) => `${span2.span_id} (${ATTR5.branchId}=${JSON.stringify(span2.attributes[ATTR5.branchId] ?? null)})`).join(", ");
19010
+ throw new Error(
19011
+ `"${branch}" matches ${roots.length} separate subtrees, not one arm: ${named} \u2014 sibling arms can share a branch id; select by span id, or narrow the input to one arm first`
19012
+ );
19013
+ }
19014
+ const selected = /* @__PURE__ */ new Set();
19015
+ const stack = [...matched];
19016
+ while (stack.length > 0) {
19017
+ const span2 = stack.pop();
19018
+ if (selected.has(span2)) continue;
19019
+ selected.add(span2);
19020
+ stack.push(...children.get(span2.span_id) ?? []);
19021
+ }
19022
+ return spans.filter((span2) => selected.has(span2));
19023
+ }
19024
+ async function readRunSide(ref, options = {}) {
19025
+ const hash2 = ref.lastIndexOf("#");
19026
+ const path = hash2 > 0 ? ref.slice(0, hash2) : ref;
19027
+ const selector = hash2 > 0 ? ref.slice(hash2 + 1) : void 0;
19028
+ const branch = selector?.startsWith("branch=") ? selector.slice("branch=".length) : void 0;
19029
+ const all = await readSpans(path);
19030
+ let spans;
19031
+ let traceId;
19032
+ if (branch !== void 0) {
19033
+ spans = branchSpans(all, branch);
19034
+ if (spans.length === 0) throw new Error(`${path} holds no span with ${ATTR5.branchId} or ${ATTR5.branchArm} "${branch}"`);
19035
+ const traces = new Set(spans.map((span2) => span2.trace_id));
19036
+ if (traces.size !== 1) throw new Error(`${path}: branch "${branch}" appears in ${traces.size} runs; export one run per file first`);
19037
+ traceId = [...traces][0];
19038
+ } else {
19039
+ const byTrace = /* @__PURE__ */ new Map();
19040
+ for (const span2 of all) {
19041
+ const list = byTrace.get(span2.trace_id);
19042
+ if (list) list.push(span2);
19043
+ else byTrace.set(span2.trace_id, [span2]);
19044
+ }
19045
+ if (selector === void 0 && byTrace.size !== 1) {
19046
+ throw new Error(
19047
+ `${path} holds ${byTrace.size} runs; pick one with ${path}#<trace id>: ${[...byTrace.keys()].slice(0, 10).join(", ")}`
19048
+ );
19049
+ }
19050
+ traceId = selector ?? [...byTrace.keys()][0];
19051
+ const picked = byTrace.get(traceId);
19052
+ if (!picked) throw new Error(`${path} holds no run with trace id ${traceId}`);
19053
+ spans = picked;
19054
+ }
19055
+ const { spans: ingested } = ingestSpans2(spans, { contentIncluded: false });
19056
+ const kinds = options.kinds?.length ? new Set(options.kinds.map((kind) => kind.toUpperCase())) : null;
19057
+ const steps = diffStepsFromSpans(ingested).filter((step) => kinds === null || kinds.has(step.kind));
19058
+ return { path, traceId, ...branch === void 0 ? {} : { branch }, spans: spans.length, steps };
19059
+ }
19060
+ function hasDefiniteStatus(steps) {
19061
+ return steps.some((step) => step.fields.status === "OK" || step.fields.status === "ERROR");
19062
+ }
19063
+ function hasToolStep(steps) {
19064
+ return steps.some((step) => step.kind === "TOOL");
19065
+ }
19066
+ function agreementCaveat(a, b) {
19067
+ const reasons = [];
19068
+ if (!hasDefiniteStatus(a) && !hasDefiniteStatus(b)) {
19069
+ reasons.push("no step on either side carries a definitive OK or ERROR status (all UNSET)");
19070
+ }
19071
+ if (!hasToolStep(a) && !hasToolStep(b)) {
19072
+ reasons.push("no TOOL step on either side");
19073
+ }
19074
+ return reasons.length > 0 ? reasons.join("; ") : void 0;
19075
+ }
19076
+ async function diffRuns(a, b, options = {}) {
19077
+ const [left, right] = await Promise.all([readRunSide(a, options), readRunSide(b, options)]);
19078
+ const diff = diffSteps(left.steps, right.steps);
19079
+ const caveat = diff.firstDivergence ? void 0 : agreementCaveat(left.steps, right.steps);
19080
+ return {
19081
+ kind: "traces.run_diff",
19082
+ ...options.kinds?.length ? { kinds: options.kinds.map((kind) => kind.toUpperCase()) } : {},
19083
+ a: left,
19084
+ b: right,
19085
+ diff,
19086
+ ...caveat ? { caveat } : {}
19087
+ };
19088
+ }
19089
+ var TEXT_STEP_LIMIT = 40;
19090
+ function sideLabel(side) {
19091
+ const branch = side.branch === void 0 ? "" : `, branch ${side.branch}`;
19092
+ return `${side.path} (trace ${side.traceId}${branch}, ${side.steps.length} steps)`;
19093
+ }
19094
+ function label(step) {
19095
+ const status = step.fields.status;
19096
+ return `${step.kind} ${JSON.stringify(step.name)}${status === "ERROR" ? " ERROR" : ""}`;
19097
+ }
19098
+ function renderRunDiff(report) {
19099
+ const { a, b, diff } = report;
19100
+ const byPairing = { id: 0, position: 0, name: 0 };
19101
+ for (const pair of diff.pairs) byPairing[pair.pairedBy] += 1;
19102
+ const lines = [
19103
+ ...report.kinds ? [`steps limited to kind ${report.kinds.join(", ")}`] : [],
19104
+ `A: ${sideLabel(a)}`,
19105
+ `B: ${sideLabel(b)}`,
19106
+ `paired ${diff.pairs.length} (by id ${byPairing.id}, position ${byPairing.position}, name ${byPairing.name}), only in A ${diff.onlyInA.length}, only in B ${diff.onlyInB.length}, common prefix ${diff.commonPrefixLen}`
19107
+ ];
19108
+ const first = diff.firstDivergence;
19109
+ if (!first) {
19110
+ lines.push(
19111
+ report.caveat ? `same steps, same order \u2014 but this is not agreement on outcome: ${report.caveat}` : "no divergence: the runs agree step for step"
19112
+ );
19113
+ return `${lines.join("\n")}
19114
+ `;
19115
+ }
19116
+ lines.push(`first divergence [${first.kind}] at step ${first.index}: ${first.reason}`);
19117
+ const details = [];
19118
+ for (const pair of diff.pairs) {
19119
+ if (pair.differences.length === 0) continue;
19120
+ const changes = pair.differences.map((d) => `${d.field} ${String(d.a)} -> ${String(d.b)}`).join("; ");
19121
+ details.push(` changed A[${pair.a}]~B[${pair.b}] ${label(a.steps[pair.a])}: ${changes}`);
19122
+ }
19123
+ for (const index of diff.onlyInA) details.push(` only in A A[${index}] ${label(a.steps[index])}`);
19124
+ for (const index of diff.onlyInB) details.push(` only in B B[${index}] ${label(b.steps[index])}`);
19125
+ lines.push(...details.slice(0, TEXT_STEP_LIMIT));
19126
+ if (details.length > TEXT_STEP_LIMIT) {
19127
+ lines.push(` \u2026 ${details.length - TEXT_STEP_LIMIT} more (use --format json for all)`);
19128
+ }
19129
+ return `${lines.join("\n")}
19130
+ `;
19131
+ }
19132
+
18959
19133
  // src/upload-state.ts
18960
19134
  import { mkdir as mkdir8, readFile as readFile17, writeFile as writeFile12 } from "fs/promises";
18961
19135
  import { homedir as homedir12 } from "os";
@@ -18997,7 +19171,7 @@ import { createRequire as createRequire2 } from "module";
18997
19171
  import { hostname as hostname2 } from "os";
18998
19172
  import { basename as basename17, join as join33 } from "path";
18999
19173
  import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
19000
- import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
19174
+ import { assessShareSafety as assessShareSafety2, REDACTION_VERSION, redact as redact2, shareAllowed } from "@tangle-network/agent-eval/traces";
19001
19175
  var require2 = createRequire2(import.meta.url);
19002
19176
  var TRACES_VERSION = require2("../package.json").version;
19003
19177
  async function planUpload(opts = {}) {
@@ -19038,7 +19212,7 @@ async function sessionMeta(item) {
19038
19212
  if (item.ref.cwd) meta[ATTR.CWD] = item.ref.cwd;
19039
19213
  const branch = await gitBranch(item.ref.cwd);
19040
19214
  if (branch) meta[ATTR.GIT_BRANCH] = branch;
19041
- return meta;
19215
+ return redact2(meta).value;
19042
19216
  }
19043
19217
  var msToNano = (epochMs2) => (BigInt(epochMs2) * 1000000n).toString();
19044
19218
  function toTraceSpanEvents(spans, rootMeta) {
@@ -19104,6 +19278,7 @@ async function prepareOutbound(plan, opts, uploadedAt) {
19104
19278
  redactionVersion: REDACTION_VERSION
19105
19279
  };
19106
19280
  const items = [];
19281
+ const refused = [];
19107
19282
  for (const item of candidates) {
19108
19283
  const key = uploadKey(item.ref.harness, item.ref.sessionId);
19109
19284
  const previous = plan.state[key];
@@ -19118,13 +19293,18 @@ async function prepareOutbound(plan, opts, uploadedAt) {
19118
19293
  events = eventsAt(eventTimestamp2);
19119
19294
  hash2 = outboundHash(events, identity2);
19120
19295
  }
19296
+ const verdict = assessShareSafety2(events);
19297
+ if (!shareAllowed(verdict)) {
19298
+ refused.push({ harness: item.ref.harness, sessionId: item.ref.sessionId, verdict });
19299
+ continue;
19300
+ }
19121
19301
  items.push({ item, events, hash: hash2 });
19122
19302
  }
19123
- return { items, skipped: plan.items.length - items.length };
19303
+ return { items, skipped: plan.items.length - items.length - refused.length, refused };
19124
19304
  }
19125
19305
  async function executeUpload(plan, opts = {}) {
19126
19306
  const uploadedAt = (/* @__PURE__ */ new Date()).toISOString();
19127
- const { items, skipped } = await prepareOutbound(plan, opts, uploadedAt);
19307
+ const { items, skipped, refused } = await prepareOutbound(plan, opts, uploadedAt);
19128
19308
  const redactionCount = items.reduce((n, { item }) => n + item.redaction.redactionCount, 0);
19129
19309
  if (opts.dryRun) {
19130
19310
  const allSpans = items.flatMap(({ item }) => item.spans);
@@ -19135,7 +19315,8 @@ async function executeUpload(plan, opts = {}) {
19135
19315
  acceptedSpans: 0,
19136
19316
  redactionCount,
19137
19317
  dryRun: true,
19138
- otlpPath: path
19318
+ otlpPath: path,
19319
+ refused
19139
19320
  };
19140
19321
  }
19141
19322
  if (items.length === 0) {
@@ -19144,7 +19325,8 @@ async function executeUpload(plan, opts = {}) {
19144
19325
  skippedSessions: skipped,
19145
19326
  acceptedSpans: 0,
19146
19327
  redactionCount,
19147
- dryRun: false
19328
+ dryRun: false,
19329
+ refused
19148
19330
  };
19149
19331
  }
19150
19332
  const client = opts.backend ?? hostedClientFromEnv();
@@ -19180,9 +19362,124 @@ async function executeUpload(plan, opts = {}) {
19180
19362
  skippedSessions: skipped,
19181
19363
  acceptedSpans,
19182
19364
  redactionCount,
19183
- dryRun: false
19365
+ dryRun: false,
19366
+ refused
19367
+ };
19368
+ }
19369
+
19370
+ // src/verify-safe.ts
19371
+ import { lstat as lstat4, readdir as readdir21, readFile as readFile19 } from "fs/promises";
19372
+ import { basename as basename18, extname, join as join34, relative as relative7 } from "path";
19373
+ import {
19374
+ assessShareSafety as assessShareSafety3,
19375
+ combineVerdicts as combineVerdicts2
19376
+ } from "@tangle-network/agent-eval/traces";
19377
+ var JSON_LINES = /* @__PURE__ */ new Set([".jsonl", ".ndjson"]);
19378
+ async function verifySafe(target, options = {}) {
19379
+ const profile = options.profile ?? "share";
19380
+ const assess = { profile, knownSecrets: options.knownSecrets };
19381
+ const verdicts = [];
19382
+ const unreadable = [];
19383
+ const files = await listFiles(target, unreadable);
19384
+ for (const file of files) {
19385
+ const label2 = relative7(target, file) || basename18(file);
19386
+ let bytes;
19387
+ try {
19388
+ bytes = await readFile19(file);
19389
+ } catch (error) {
19390
+ unreadable.push(`${label2}: ${error instanceof Error ? error.message : String(error)}`);
19391
+ continue;
19392
+ }
19393
+ const text2 = decodeUtf8(bytes);
19394
+ if (text2 === void 0) {
19395
+ unreadable.push(`${label2}: not UTF-8 text`);
19396
+ continue;
19397
+ }
19398
+ const extension = extname(file).toLowerCase();
19399
+ if (JSON_LINES.has(extension)) {
19400
+ text2.split("\n").forEach((line, index) => {
19401
+ if (!line.trim()) return;
19402
+ const parsed = parseJson(line);
19403
+ if (parsed === void 0) unreadable.push(`${label2}:${index + 1}: not JSON`);
19404
+ else verdicts.push(located(assessShareSafety3(parsed.value, assess), `${label2}:${index + 1}`));
19405
+ });
19406
+ } else if (extension === ".json") {
19407
+ const parsed = parseJson(text2);
19408
+ if (parsed === void 0) unreadable.push(`${label2}: not JSON`);
19409
+ else verdicts.push(located(assessShareSafety3(parsed.value, assess), label2));
19410
+ } else {
19411
+ verdicts.push(located(assessShareSafety3(text2, assess), label2));
19412
+ }
19413
+ }
19414
+ if (files.length === 0 && unreadable.length === 0) unreadable.push(`${target}: no files to check`);
19415
+ return { ...combineVerdicts2(profile, verdicts, unreadable), target, files: files.length };
19416
+ }
19417
+ async function listFiles(target, unreadable) {
19418
+ let stat19;
19419
+ try {
19420
+ stat19 = await lstat4(target);
19421
+ } catch (error) {
19422
+ unreadable.push(`${target}: ${error instanceof Error ? error.message : String(error)}`);
19423
+ return [];
19424
+ }
19425
+ if (stat19.isFile()) return [target];
19426
+ if (!stat19.isDirectory()) {
19427
+ unreadable.push(`${target}: not a regular file or directory`);
19428
+ return [];
19429
+ }
19430
+ const out = [];
19431
+ const walk = async (dir) => {
19432
+ for (const entry of (await readdir21(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
19433
+ const path = join34(dir, entry.name);
19434
+ if (entry.isDirectory()) await walk(path);
19435
+ else if (entry.isFile()) out.push(path);
19436
+ else unreadable.push(`${relative7(target, path)}: not a regular file`);
19437
+ }
19438
+ };
19439
+ await walk(target);
19440
+ return out;
19441
+ }
19442
+ function decodeUtf8(bytes) {
19443
+ if (bytes.includes(0)) return void 0;
19444
+ try {
19445
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
19446
+ } catch {
19447
+ return void 0;
19448
+ }
19449
+ }
19450
+ function parseJson(text2) {
19451
+ try {
19452
+ return { value: JSON.parse(text2) };
19453
+ } catch {
19454
+ return void 0;
19455
+ }
19456
+ }
19457
+ function located(verdict, where) {
19458
+ return {
19459
+ ...verdict,
19460
+ findings: verdict.findings.map((finding2) => ({
19461
+ ...finding2,
19462
+ paths: finding2.paths.map((path) => `${where}#${path}`)
19463
+ })),
19464
+ unreadable: verdict.unreadable.map((entry) => `${where}#${entry}`)
19184
19465
  };
19185
19466
  }
19467
+ function verdictExitCode(verdict) {
19468
+ if (verdict.status === "UNKNOWN") return 2;
19469
+ return verdict.status === "UNSAFE" ? 1 : 0;
19470
+ }
19471
+ function formatVerdict(result) {
19472
+ const lines = [`${result.status} ${result.target} (${result.files} file(s), profile ${result.profile})`];
19473
+ for (const finding2 of result.findings) {
19474
+ lines.push(
19475
+ ` ${finding2.severity.padEnd(7)} ${finding2.category}/${finding2.detector} x${finding2.count} ${finding2.paths.join(", ")}`
19476
+ );
19477
+ }
19478
+ for (const entry of result.unreadable) lines.push(` unread ${entry}`);
19479
+ if (result.status === "UNSAFE") lines.push("Refuse to share. Redact with the same profile, then run verify-safe again.");
19480
+ if (result.status === "UNKNOWN") lines.push("Refuse to share: part of the input could not be read, so its safety is unknown.");
19481
+ return lines.join("\n");
19482
+ }
19186
19483
 
19187
19484
  export {
19188
19485
  SOURCE_ATTRIBUTE_PREFIX,
@@ -19279,6 +19576,8 @@ export {
19279
19576
  analyzeAdoption,
19280
19577
  planTraceAgenticRoute,
19281
19578
  traceAgenticKinds,
19579
+ writeAnalysisTraceFile,
19580
+ openAgenticTraceStore,
19282
19581
  CITATION_NORMALIZATION_VERSION,
19283
19582
  normalizeAnalystCitations,
19284
19583
  MAX_SHELL_DEPTH,
@@ -19342,6 +19641,7 @@ export {
19342
19641
  collectSessionWorkflow,
19343
19642
  fileSha256,
19344
19643
  collectSessionSelection,
19644
+ tracesVersion,
19345
19645
  listSessionBundleFiles,
19346
19646
  assembleSessionBundle,
19347
19647
  hashSessionBundleFiles,
@@ -19366,9 +19666,8 @@ export {
19366
19666
  hodoscopeAnalyzer,
19367
19667
  httpJsonTransport,
19368
19668
  primeAnalyzer,
19369
- CODING_REDACTION_RULES,
19370
- TRACES_REDACTION_RULES,
19371
19669
  redactSpans,
19670
+ assessSpans,
19372
19671
  applyRedactor,
19373
19672
  exportTraceEvidenceRows,
19374
19673
  exportTraceEvidenceText,
@@ -19445,6 +19744,9 @@ export {
19445
19744
  findSessionTextLeaks,
19446
19745
  classifyEvidenceOnlyPath,
19447
19746
  projectSessionBundle,
19747
+ readRunSide,
19748
+ diffRuns,
19749
+ renderRunDiff,
19448
19750
  uploadKey,
19449
19751
  sessionHash,
19450
19752
  outboundHash,
@@ -19454,5 +19756,8 @@ export {
19454
19756
  planUpload,
19455
19757
  toTraceSpanEvents,
19456
19758
  PartialUploadError,
19457
- executeUpload
19759
+ executeUpload,
19760
+ verifySafe,
19761
+ verdictExitCode,
19762
+ formatVerdict
19458
19763
  };