@tangle-network/traces 0.16.4 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,13 +9,13 @@ function sourceFileId(path) {
9
9
  function pointerPart(value) {
10
10
  return value.replaceAll("~", "~0").replaceAll("/", "~1");
11
11
  }
12
- function locateSourceObjects(value, path, bytes, offset = 0) {
12
+ function locateSourceObjects(value, path, bytes2, offset = 0) {
13
13
  if (!value || typeof value !== "object") return;
14
14
  const record2 = {
15
15
  sourceId: sourceFileId(path),
16
16
  recordOffset: offset,
17
- recordBytes: bytes.length,
18
- recordSha256: createHash("sha256").update(bytes).digest("hex")
17
+ recordBytes: bytes2.length,
18
+ recordSha256: createHash("sha256").update(bytes2).digest("hex")
19
19
  };
20
20
  const pending = [{ value, pointer: "#" }];
21
21
  while (pending.length > 0) {
@@ -317,35 +317,35 @@ function serialize(value) {
317
317
  return { value: canonicalJson(value), mimeType: "application/json" };
318
318
  }
319
319
  function utf8Prefix(value, maxBytes) {
320
- let bytes = 0;
320
+ let bytes2 = 0;
321
321
  let end = 0;
322
322
  for (const char of value) {
323
323
  const charBytes = Buffer.byteLength(char);
324
- if (bytes + charBytes > maxBytes) break;
325
- bytes += charBytes;
324
+ if (bytes2 + charBytes > maxBytes) break;
325
+ bytes2 += charBytes;
326
326
  end += char.length;
327
327
  }
328
328
  return Buffer.from(value.slice(0, end), "utf8").toString("utf8");
329
329
  }
330
- function truncatedValue(value, bytes) {
330
+ function truncatedValue(value, bytes2) {
331
331
  const longestMarker = `
332
- [truncated ${bytes} bytes]`;
332
+ [truncated ${bytes2} bytes]`;
333
333
  const prefix = utf8Prefix(value, TOOL_IO_VALUE_MAX_BYTES - Buffer.byteLength(longestMarker));
334
- const omittedBytes = bytes - Buffer.byteLength(prefix);
334
+ const omittedBytes = bytes2 - Buffer.byteLength(prefix);
335
335
  return `${prefix}
336
336
  [truncated ${omittedBytes} bytes]`;
337
337
  }
338
338
  function valueAttributes(side, raw) {
339
339
  const serialized = serialize(raw);
340
340
  if (!serialized) return {};
341
- const bytes = Buffer.byteLength(serialized.value);
341
+ const bytes2 = Buffer.byteLength(serialized.value);
342
342
  const sha2562 = createHash2("sha256").update(serialized.value).digest("hex");
343
- const truncated = bytes > TOOL_IO_VALUE_MAX_BYTES;
344
- const value = truncated ? truncatedValue(serialized.value, bytes) : serialized.value;
343
+ const truncated = bytes2 > TOOL_IO_VALUE_MAX_BYTES;
344
+ const value = truncated ? truncatedValue(serialized.value, bytes2) : serialized.value;
345
345
  return {
346
346
  [`${side}.value`]: value,
347
347
  [`${side}.mime_type`]: serialized.mimeType,
348
- [`traces.${side}.bytes`]: bytes,
348
+ [`traces.${side}.bytes`]: bytes2,
349
349
  [`traces.${side}.sha256`]: sha2562,
350
350
  [`traces.${side}.truncated`]: truncated
351
351
  };
@@ -1011,16 +1011,36 @@ function toOpenInferenceSpan(s) {
1011
1011
  }
1012
1012
  function serializeSpans(spans) {
1013
1013
  if (spans.length === 0) return "";
1014
- const validated = validateOtlpSpans(spans, "serialized spans");
1015
- return `${validated.map((s) => JSON.stringify(toOpenInferenceSpan(s))).join("\n")}
1014
+ let out = "";
1015
+ for (const line of iterSerializedSpans(spans)) out += line;
1016
+ return out;
1017
+ }
1018
+ function* iterSerializedSpans(spans) {
1019
+ for (const s of validateOtlpSpans(spans, "serialized spans")) {
1020
+ yield `${JSON.stringify(toOpenInferenceSpan(s))}
1016
1021
  `;
1022
+ }
1017
1023
  }
1024
+ var OTLP_WRITE_CHUNK_CHARS = 4 * 1024 * 1024;
1018
1025
  async function writeOtlpFile(spans, outPath) {
1019
- const { mkdtemp: mkdtemp8, writeFile: writeFile13 } = await import("fs/promises");
1026
+ const { mkdtemp: mkdtemp8, open: open2 } = await import("fs/promises");
1020
1027
  const { tmpdir: tmpdir8 } = await import("os");
1021
1028
  const { join: join35 } = await import("path");
1022
1029
  const path = outPath ?? join35(await mkdtemp8(join35(tmpdir8(), "traces-")), "spans.otlp.jsonl");
1023
- await writeFile13(path, serializeSpans(spans), "utf8");
1030
+ const handle = await open2(path, "w");
1031
+ try {
1032
+ let chunk = "";
1033
+ for (const line of iterSerializedSpans(spans)) {
1034
+ chunk += line;
1035
+ if (chunk.length >= OTLP_WRITE_CHUNK_CHARS) {
1036
+ await handle.write(chunk);
1037
+ chunk = "";
1038
+ }
1039
+ }
1040
+ if (chunk.length > 0) await handle.write(chunk);
1041
+ } finally {
1042
+ await handle.close();
1043
+ }
1024
1044
  return path;
1025
1045
  }
1026
1046
 
@@ -1367,18 +1387,18 @@ function isMissingJsonSource(error) {
1367
1387
  }
1368
1388
  async function readJsonFile(path, options = {}) {
1369
1389
  let raw;
1370
- let bytes;
1390
+ let bytes2;
1371
1391
  try {
1372
1392
  if (options.captureSources) {
1373
- bytes = await readFile3(path);
1374
- raw = bytes.toString("utf8");
1393
+ bytes2 = await readFile3(path);
1394
+ raw = bytes2.toString("utf8");
1375
1395
  } else raw = await readFile3(path, "utf8");
1376
1396
  } catch (error) {
1377
1397
  throw new JsonSourceError(path, "read", error);
1378
1398
  }
1379
1399
  try {
1380
1400
  const value = JSON.parse(raw);
1381
- if (bytes) locateSourceObjects(value, path, bytes);
1401
+ if (bytes2) locateSourceObjects(value, path, bytes2);
1382
1402
  return value;
1383
1403
  } catch (error) {
1384
1404
  throw new JsonSourceError(path, "parse", pathSafeParseCause(error));
@@ -1585,9 +1605,9 @@ function parseLine(rawLine, path, lineNumber, byteOffset, options) {
1585
1605
  const jsonBytes = rawLine.at(-1) === 13 ? rawLine.subarray(0, -1) : rawLine;
1586
1606
  if (jsonBytes.length === 0) return void 0;
1587
1607
  if (!isUtf8(jsonBytes)) return handleCorruption(rawLine, path, lineNumber, byteOffset, options);
1588
- const json2 = jsonBytes.toString("utf8");
1589
- if (json2.trim().length === 0) return void 0;
1590
1608
  try {
1609
+ const json2 = jsonBytes.toString("utf8");
1610
+ if (json2.trim().length === 0) return void 0;
1591
1611
  const value = JSON.parse(json2);
1592
1612
  if (options.captureSources) locateSourceObjects(value, path, rawLine, byteOffset);
1593
1613
  return value;
@@ -1615,12 +1635,12 @@ async function* readJsonl(path, options = { mode: "strict" }) {
1615
1635
  try {
1616
1636
  for await (const chunk of input) {
1617
1637
  options.signal?.throwIfAborted();
1618
- const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1638
+ const bytes2 = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1619
1639
  let start = 0;
1620
- let newline = bytes.indexOf(10);
1640
+ let newline = bytes2.indexOf(10);
1621
1641
  while (newline !== -1) {
1622
1642
  options.signal?.throwIfAborted();
1623
- fragments.push(bytes.subarray(start, newline));
1643
+ fragments.push(bytes2.subarray(start, newline));
1624
1644
  const rawLine = fragments.length === 1 ? fragments[0] : Buffer.concat(fragments);
1625
1645
  fragments = [];
1626
1646
  lineNumber += 1;
@@ -1628,9 +1648,9 @@ async function* readJsonl(path, options = { mode: "strict" }) {
1628
1648
  if (parsed !== void 0) yield parsed;
1629
1649
  byteOffset += rawLine.length + 1;
1630
1650
  start = newline + 1;
1631
- newline = bytes.indexOf(10, start);
1651
+ newline = bytes2.indexOf(10, start);
1632
1652
  }
1633
- if (start < bytes.length) fragments.push(bytes.subarray(start));
1653
+ if (start < bytes2.length) fragments.push(bytes2.subarray(start));
1634
1654
  }
1635
1655
  if (fragments.length > 0) {
1636
1656
  options.signal?.throwIfAborted();
@@ -2229,6 +2249,7 @@ function createClaudeStream(startStep) {
2229
2249
  step: startStep,
2230
2250
  sawUserTurn: false,
2231
2251
  promptSpans: [],
2252
+ promptSpanByUid: /* @__PURE__ */ new Map(),
2232
2253
  sawRecordedOrigin: false,
2233
2254
  firstRecordAt: null,
2234
2255
  lastRecordAt: null
@@ -2261,6 +2282,9 @@ function projectToolResult(toolUseId, isError, output, structuredWorkflowRun, ou
2261
2282
  }
2262
2283
  function projectClaudeEvent(event) {
2263
2284
  const timestamp = event.timestamp ?? EPOCH;
2285
+ if (event.type === "system" && event.subtype === "compact_boundary") {
2286
+ return { kind: "compaction", timestamp: event.timestamp ?? "", metadata: event.compactMetadata ?? {}, parentToolUseId: event.parent_tool_use_id };
2287
+ }
2264
2288
  if (event.type === "assistant" && event.message) {
2265
2289
  const tools = [];
2266
2290
  for (const block of asBlocks(event.message.content)) {
@@ -2402,7 +2426,7 @@ function indexWorkflowProjection(projection, index) {
2402
2426
  function fingerprintClaudeEvent(event) {
2403
2427
  return createHash6("sha256").update(JSON.stringify(
2404
2428
  event,
2405
- (key, value) => key === "contentSource" || key.startsWith(SOURCE_ATTRIBUTE_PREFIX) ? void 0 : value
2429
+ (key, value) => key === "contentSource" || key === "originKind" || key.startsWith(SOURCE_ATTRIBUTE_PREFIX) ? void 0 : value
2406
2430
  )).digest("hex");
2407
2431
  }
2408
2432
  function startsClaudeTask(projection) {
@@ -2416,21 +2440,46 @@ function startsClaudeTask(projection) {
2416
2440
  function distinctClaudeEvent(event, seen, sourcePath, fallbackUid) {
2417
2441
  const projection = projectClaudeEvent(event);
2418
2442
  if (event.uuid) {
2443
+ const uid = event.uuid;
2419
2444
  const fingerprint = fingerprintClaudeEvent(projection);
2420
- const previous = seen.get(event.uuid);
2421
- if (previous !== void 0) {
2422
- if (previous !== fingerprint) {
2423
- throw new ClaudeEventConflictError(sourcePath, event.uuid);
2445
+ const originKind = (projection.kind === "user" || projection.kind === "queued-prompt" ? projection.originKind : null) ?? null;
2446
+ const previous = seen.get(uid);
2447
+ if (previous) {
2448
+ if (previous.fingerprint !== fingerprint) {
2449
+ throw new ClaudeEventConflictError(sourcePath, uid);
2450
+ }
2451
+ if (previous.originKind === null && originKind !== null) {
2452
+ seen.set(uid, { fingerprint, originKind });
2453
+ return { originEnrichment: originKind, uid };
2454
+ }
2455
+ if (previous.originKind !== null && originKind !== null && previous.originKind !== originKind) {
2456
+ throw new ClaudeEventConflictError(sourcePath, uid);
2424
2457
  }
2425
2458
  return void 0;
2426
2459
  }
2427
- seen.set(event.uuid, fingerprint);
2460
+ seen.set(uid, { fingerprint, originKind });
2428
2461
  }
2429
2462
  return { projection, uid: event.uuid ?? fallbackUid };
2430
2463
  }
2464
+ function applyOriginEnrichment(uid, originKind, state) {
2465
+ const prompt = state.promptSpanByUid.get(uid);
2466
+ if (!prompt) return;
2467
+ if (prompt.attributes[ORIGIN_RECORDED_ATTR] === true) return;
2468
+ state.sawRecordedOrigin = true;
2469
+ prompt.attributes[ORIGIN_RECORDED_ATTR] = true;
2470
+ prompt.attributes[ORIGIN_KIND_ATTR] = originKind;
2471
+ prompt.attributes[ACTOR_ATTR] = claudeActorFromOrigin(
2472
+ originKind,
2473
+ () => prompt.attributes[ACTOR_ATTR]
2474
+ );
2475
+ }
2431
2476
  function consumeDistinctClaudeEvent(event, ctx, state, seen, sourcePath) {
2432
2477
  const accepted = distinctClaudeEvent(event, seen, sourcePath, `step${state.step}`);
2433
2478
  if (!accepted) return false;
2479
+ if ("originEnrichment" in accepted) {
2480
+ applyOriginEnrichment(accepted.uid, accepted.originEnrichment, state);
2481
+ return false;
2482
+ }
2434
2483
  consumeClaudeEvent(accepted.projection, accepted.uid, ctx, state);
2435
2484
  return true;
2436
2485
  }
@@ -2440,6 +2489,34 @@ function inStreamParent(parentToolUseId, state) {
2440
2489
  return parentTool ? { span: parentTool, confidence: "explicit" } : void 0;
2441
2490
  }
2442
2491
  function consumeClaudeEvent(event, uid, ctx, state) {
2492
+ if (event.kind === "compaction") {
2493
+ const extra = {};
2494
+ const numericFields = {
2495
+ preTokens: "traces.compaction.tokens_before",
2496
+ postTokens: "traces.compaction.tokens_after",
2497
+ durationMs: "traces.compaction.latency_ms",
2498
+ cumulativeDroppedTokens: "traces.compaction.cumulative_dropped_tokens"
2499
+ };
2500
+ for (const [field, key] of Object.entries(numericFields)) {
2501
+ const value = event.metadata[field];
2502
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) extra[key] = value;
2503
+ }
2504
+ if (typeof event.metadata.trigger === "string") extra["traces.compaction.trigger"] = event.metadata.trigger;
2505
+ const parent = inStreamParent(event.parentToolUseId, state);
2506
+ state.spans.push(span({
2507
+ traceId: ctx.traceId,
2508
+ spanId: ctx.idPrefix + uid + ":compacted",
2509
+ parentSpanId: parent ? parent.span.span_id : ctx.rootParent,
2510
+ name: "session.compacted",
2511
+ kind: "CHAIN",
2512
+ startTime: event.timestamp,
2513
+ service: SERVICE2,
2514
+ agent: ctx.agent,
2515
+ step: state.step++,
2516
+ extra
2517
+ }));
2518
+ return;
2519
+ }
2443
2520
  if (event.kind === "assistant") {
2444
2521
  const messageId = event.messageId ?? uid;
2445
2522
  let llmSpan = state.llmSpanByMessageId.get(messageId);
@@ -2553,6 +2630,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
2553
2630
  if (userParent) prompt.attributes[ATTR3.parentConfidence] = userParent.confidence;
2554
2631
  state.spans.push(prompt);
2555
2632
  state.promptSpans.push(prompt);
2633
+ state.promptSpanByUid.set(uid, prompt);
2556
2634
  state.step += 1;
2557
2635
  }
2558
2636
  for (const result of event.results) {
@@ -2582,6 +2660,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
2582
2660
  }
2583
2661
  state.spans.push(prompt);
2584
2662
  state.promptSpans.push(prompt);
2663
+ state.promptSpanByUid.set(uid, prompt);
2585
2664
  state.step += 1;
2586
2665
  } else if (event.kind === "file-change") {
2587
2666
  state.spans.push(span({
@@ -2743,7 +2822,7 @@ async function readClaudeWorkflowBindings(ref, options) {
2743
2822
  options.signal?.throwIfAborted();
2744
2823
  const accepted = distinctClaudeEvent(event, seen, ref.path, `source${row}`);
2745
2824
  row += 1;
2746
- if (accepted) indexWorkflowProjection(accepted.projection, index);
2825
+ if (accepted && "projection" in accepted) indexWorkflowProjection(accepted.projection, index);
2747
2826
  }
2748
2827
  return bindWorkflowRuns(index);
2749
2828
  }
@@ -2895,7 +2974,10 @@ var ClaudeAdapter = class {
2895
2974
  runResult = { failed: event.is_error, subtype: event.subtype ?? "unknown" };
2896
2975
  }
2897
2976
  const accepted = distinctClaudeEvent(event, seen, ref.path, `step${state.step}`);
2898
- if (!accepted) continue;
2977
+ if (!accepted || "originEnrichment" in accepted) {
2978
+ if (accepted) applyOriginEnrichment(accepted.uid, accepted.originEnrichment, state);
2979
+ continue;
2980
+ }
2899
2981
  indexWorkflowProjection(accepted.projection, workflowIndex);
2900
2982
  const startsTask = startsClaudeTask(accepted.projection);
2901
2983
  if (startsTask && taskScope === "latest") {
@@ -4301,12 +4383,12 @@ var CodexAdapter = class {
4301
4383
  spans.push(turnSpan);
4302
4384
  step += 1;
4303
4385
  };
4304
- const recordInheritedContext = (l, ts) => {
4386
+ const recordInheritedContext = (l, ts, inScope = false) => {
4305
4387
  if (l.type === "compacted") {
4306
4388
  const payload = l.payload;
4307
4389
  if (!payload) return;
4308
4390
  const summary = capText(typeof payload.message === "string" ? payload.message : "");
4309
- if (summary && claimInheritedSpan()) {
4391
+ if (claimInheritedSpan()) {
4310
4392
  spans.push(span({
4311
4393
  traceId,
4312
4394
  spanId: `inherited:${step}:compacted`,
@@ -4321,7 +4403,7 @@ var CodexAdapter = class {
4321
4403
  contentSource: textSources(payload, "message"),
4322
4404
  extra: {
4323
4405
  [INHERITED_SPAN_ATTR]: true,
4324
- [INHERITED_SOURCE_ATTR]: "compacted",
4406
+ [INHERITED_SOURCE_ATTR]: inScope ? "compacted" : "pre-task-prefix",
4325
4407
  ...typeof payload.window_number === "number" ? { "traces.codex.compaction_window_number": payload.window_number } : {},
4326
4408
  ...payload.window_id ? { "traces.codex.compaction_window_id": payload.window_id } : {},
4327
4409
  ...payload.previous_window_id ? { "traces.codex.compaction_previous_window_id": payload.previous_window_id } : {}
@@ -4495,7 +4577,7 @@ var CodexAdapter = class {
4495
4577
  step += 1;
4496
4578
  }
4497
4579
  } else if (l.type === "compacted") {
4498
- recordInheritedContext(l, ts);
4580
+ recordInheritedContext(l, ts, true);
4499
4581
  } else if (l.type === "response_item" && (l.payload?.type === "function_call" || l.payload?.type === "custom_tool_call")) {
4500
4582
  const outerName = l.payload.name ?? "tool";
4501
4583
  const callId = l.payload.call_id ?? `${step}`;
@@ -6158,6 +6240,27 @@ var PiAdapter = class {
6158
6240
  if (!sessionLine && l.type === "session") {
6159
6241
  sessionLine = { id: l.id, timestamp: l.timestamp };
6160
6242
  }
6243
+ if (l.type === "compaction") {
6244
+ spans.push(span({
6245
+ traceId: sourceTraceId,
6246
+ spanId: "compacted:" + (l.id ?? step),
6247
+ parentSpanId: sourceRootId,
6248
+ name: "session.compacted",
6249
+ kind: "CHAIN",
6250
+ service: SERVICE9,
6251
+ agent: SERVICE9,
6252
+ startTime: l.timestamp ?? "",
6253
+ step: step++,
6254
+ content: typeof l.summary === "string" ? capText(l.summary) : void 0,
6255
+ contentSource: sourceOf(l, "summary"),
6256
+ extra: {
6257
+ ...typeof l.tokensBefore === "number" && Number.isFinite(l.tokensBefore) && l.tokensBefore >= 0 ? { "traces.compaction.tokens_before": l.tokensBefore } : {},
6258
+ ...typeof l.firstKeptEntryId === "string" ? { "traces.pi.first_kept_entry_id": l.firstKeptEntryId } : {},
6259
+ ...l.systemMessage !== void 0 ? { "gen_ai.system_instructions": JSON.stringify(l.systemMessage) } : {}
6260
+ }
6261
+ }));
6262
+ continue;
6263
+ }
6161
6264
  if (l.type !== "message" || !l.message) continue;
6162
6265
  const ts = l.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
6163
6266
  const mid = l.id ?? `m${step}`;
@@ -7395,7 +7498,7 @@ async function readLoopRuns(cwds, sessionIds) {
7395
7498
  }
7396
7499
  return { linkedCounts, unlinkedCounts, filesRead };
7397
7500
  }
7398
- async function analyzeAdoption(spans, opts = {}) {
7501
+ function computeAdoption(spans) {
7399
7502
  const { sessionByTrace, conflicts } = indexSessionIdsByTrace(spans);
7400
7503
  const identifiedSessions = new Set(sessionByTrace.values());
7401
7504
  const unassignedTraceIds = new Set(
@@ -7452,11 +7555,9 @@ async function analyzeAdoption(spans, opts = {}) {
7452
7555
  subagentSpawns[type] = (subagentSpawns[type] ?? 0) + 1;
7453
7556
  }
7454
7557
  }
7455
- const {
7456
- linkedCounts: loopDispatchedRuns,
7457
- unlinkedCounts: unlinkedLoopDispatchedRuns,
7458
- filesRead
7459
- } = await readLoopRuns(opts.cwds ?? [], identifiedSessions);
7558
+ const loopDispatchedRuns = {};
7559
+ const unlinkedLoopDispatchedRuns = {};
7560
+ const filesRead = 0;
7460
7561
  const totalSkillInvocations = Object.values(skillInvocations).reduce((a, b) => a + b, 0);
7461
7562
  const totalSkillDocumentReads = Object.values(skillDocumentReads).reduce((a, b) => a + b, 0);
7462
7563
  const totalSubagentSpawns = Object.values(subagentSpawns).reduce((a, b) => a + b, 0);
@@ -7500,6 +7601,19 @@ async function analyzeAdoption(spans, opts = {}) {
7500
7601
  totalUnlinkedLoopDispatchedRuns
7501
7602
  };
7502
7603
  }
7604
+ async function analyzeAdoption(spans, opts = {}) {
7605
+ const report = computeAdoption(spans);
7606
+ const { sessionByTrace } = indexSessionIdsByTrace(spans);
7607
+ const history = await readLoopRuns(opts.cwds ?? [], new Set(sessionByTrace.values()));
7608
+ return {
7609
+ ...report,
7610
+ loopDispatchedRuns: history.linkedCounts,
7611
+ totalLoopDispatchedRuns: Object.values(history.linkedCounts).reduce((sum, count2) => sum + count2, 0),
7612
+ unlinkedLoopDispatchedRuns: history.unlinkedCounts,
7613
+ totalUnlinkedLoopDispatchedRuns: Object.values(history.unlinkedCounts).reduce((sum, count2) => sum + count2, 0),
7614
+ skillRunFilesRead: history.filesRead
7615
+ };
7616
+ }
7503
7617
 
7504
7618
  // src/agentic-routing.ts
7505
7619
  import {
@@ -8458,13 +8572,13 @@ function mergeEvent(record2, recordIndex, words) {
8458
8572
  const target = positional[0] ?? null;
8459
8573
  const numberInCommand = target === null ? null : /^#?(\d+)$/.exec(target)?.[1] ?? numbersInLine(target)[0] ?? null;
8460
8574
  const numberInOutput = numberInCommand === null ? MERGED_NUMBER.exec(record2.output)?.[1] ?? null : null;
8461
- const number = numberInCommand ?? numberInOutput;
8462
- if (!reachedGh(record2, "merge", number !== null)) return null;
8575
+ const number2 = numberInCommand ?? numberInOutput;
8576
+ if (!reachedGh(record2, "merge", number2 !== null)) return null;
8463
8577
  const evidence3 = numberInCommand !== null ? "the pull request the command names" : numberInOutput !== null ? `the merge line this command printed, in span ${record2.spanId}` : target !== null ? "the branch the command names; no output that joins to it stated a number" : "none: the command named neither a pull request nor a branch";
8464
8578
  return {
8465
8579
  action: "merge",
8466
- number,
8467
- headBranch: number === null ? target : null,
8580
+ number: number2,
8581
+ headBranch: number2 === null ? target : null,
8468
8582
  command: words.join(" "),
8469
8583
  evidence: evidence3,
8470
8584
  spanIds: [record2.spanId],
@@ -8503,7 +8617,7 @@ function numberFromLaterOutput(records, after, headBranch) {
8503
8617
  ${record2.output}`.split("\n")) {
8504
8618
  const line = raw.replace(ANSI, "");
8505
8619
  if (!line.includes(headBranch)) continue;
8506
- for (const number of numbersInLine(line)) found.add(number);
8620
+ for (const number2 of numbersInLine(line)) found.add(number2);
8507
8621
  }
8508
8622
  if (found.size === 1) return { number: [...found][0], spanId: record2.spanId };
8509
8623
  }
@@ -8592,9 +8706,144 @@ function readPullRequests(spans) {
8592
8706
  };
8593
8707
  }
8594
8708
 
8709
+ // src/session-facts.ts
8710
+ import { createHash as createHash7 } from "crypto";
8711
+
8712
+ // src/session-measurements.ts
8713
+ var number = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
8714
+ function measure(spans, read, reason) {
8715
+ let total = 0;
8716
+ let measured2 = 0;
8717
+ for (const span2 of spans) {
8718
+ const value = read(span2);
8719
+ if (value !== null) {
8720
+ total += value;
8721
+ measured2 += 1;
8722
+ }
8723
+ }
8724
+ return {
8725
+ total: measured2 > 0 || spans.length === 0 ? total : null,
8726
+ measured: measured2,
8727
+ eligible: spans.length,
8728
+ unavailable: measured2 === spans.length ? null : reason
8729
+ };
8730
+ }
8731
+ function bytes(span2, side) {
8732
+ const recorded = number(span2.attributes[`traces.${side}.bytes`]);
8733
+ if (recorded !== null) return recorded;
8734
+ const text2 = span2.attributes[`${side}.value`];
8735
+ if (typeof text2 !== "string" || span2.attributes[`traces.${side}.truncated`] === true) return null;
8736
+ return Buffer.byteLength(text2);
8737
+ }
8738
+ function toolMeasurements(tools) {
8739
+ const groups = /* @__PURE__ */ new Map();
8740
+ for (const tool of tools) {
8741
+ const name = typeof tool.attributes["tool.name"] === "string" ? tool.attributes["tool.name"] : tool.name;
8742
+ const group = groups.get(name);
8743
+ if (group) group.push(tool);
8744
+ else groups.set(name, [tool]);
8745
+ }
8746
+ return [...groups].sort(([a], [b]) => a.localeCompare(b)).map(([name, spans]) => ({
8747
+ name,
8748
+ calls: spans.length,
8749
+ outcomes: {
8750
+ ok: spans.filter((span2) => span2.status.code === "OK").length,
8751
+ error: spans.filter((span2) => span2.status.code === "ERROR").length,
8752
+ unset: spans.filter((span2) => span2.status.code === "UNSET").length
8753
+ },
8754
+ inputBytes: measure(spans, (span2) => bytes(span2, "input"), "Input byte count missing or truncated without a size receipt."),
8755
+ outputBytes: measure(spans, (span2) => bytes(span2, "output"), "Output byte count missing or truncated without a size receipt."),
8756
+ callToResultMs: measure(spans, (span2) => {
8757
+ if (span2.status.code === "UNSET") return null;
8758
+ const elapsed2 = Date.parse(span2.end_time) - Date.parse(span2.start_time);
8759
+ return Number.isFinite(elapsed2) && elapsed2 > 0 ? elapsed2 : null;
8760
+ }, "A completed span with distinct recorded endpoints is required; equal endpoints may be an adapter default."),
8761
+ spanIds: spans.map((span2) => span2.span_id)
8762
+ }));
8763
+ }
8764
+ function compactions(spans) {
8765
+ const events = spans.filter((span2) => span2.name === "session.compacted" && span2.attributes[INHERITED_SOURCE_ATTR] !== "pre-task-prefix").sort((a, b) => Date.parse(a.start_time) - Date.parse(b.start_time));
8766
+ if (events.length === 0) return {
8767
+ value: null,
8768
+ spanIds: [],
8769
+ unavailable: "No compaction boundary was captured. This does not establish that no compaction occurred."
8770
+ };
8771
+ return {
8772
+ value: events.map((span2, i) => {
8773
+ const previous = i > 0 ? events[i - 1] : void 0;
8774
+ const interval = previous ? Date.parse(span2.start_time) - Date.parse(previous.start_time) : NaN;
8775
+ return {
8776
+ spanId: span2.span_id,
8777
+ at: span2.start_time,
8778
+ sincePreviousMs: Number.isFinite(interval) && interval >= 0 ? interval : null,
8779
+ latencyMs: number(span2.attributes["traces.compaction.latency_ms"]),
8780
+ tokensBefore: number(span2.attributes["traces.compaction.tokens_before"]),
8781
+ tokensAfter: number(span2.attributes["traces.compaction.tokens_after"])
8782
+ };
8783
+ }),
8784
+ spanIds: events.map((span2) => span2.span_id),
8785
+ unavailable: null,
8786
+ partial: "Observed boundaries only. Latency and context sizes are null unless the source explicitly records them."
8787
+ };
8788
+ }
8789
+ function contextFootprints(spans) {
8790
+ const out = [];
8791
+ for (const span2 of spans) {
8792
+ const fields = ["gen_ai.tool.definitions", "gen_ai.system_instructions"];
8793
+ if (span2.name === "message.system" || span2.name === "message.developer" || span2.name === "user.prompt" && span2.attributes["tangle.actor"] === "injected") fields.push("content");
8794
+ for (const field of fields) {
8795
+ const value = span2.attributes[field];
8796
+ if (value === void 0 || value === null) continue;
8797
+ const text2 = typeof value === "string" ? value : canonicalJson(value);
8798
+ let entries = Array.isArray(value) ? value.length : null;
8799
+ if (typeof value === "string") {
8800
+ try {
8801
+ const parsed = JSON.parse(value);
8802
+ if (Array.isArray(parsed)) entries = parsed.length;
8803
+ } catch {
8804
+ }
8805
+ }
8806
+ out.push({ spanId: span2.span_id, field, bytes: Buffer.byteLength(text2), lines: text2.split("\n").length, entries, scope: "captured-attribute" });
8807
+ }
8808
+ }
8809
+ return out;
8810
+ }
8811
+ function computeSessionMeasurements(spans, tools, sessionId, harness) {
8812
+ const spawns = tools.filter((span2) => span2.attributes["traces.agent.spawn"] === true || span2.attributes["traces.codex.agent_operation"] === "spawn_agent").sort((a, b) => Date.parse(a.start_time) - Date.parse(b.start_time));
8813
+ return {
8814
+ observedSpawns: spawns.length,
8815
+ spawnIntervalsMs: spawns.map((span2, i) => {
8816
+ const previous = spawns[i - 1];
8817
+ const interval = previous ? Date.parse(span2.start_time) - Date.parse(previous.start_time) : NaN;
8818
+ return { spanId: span2.span_id, intervalMs: Number.isFinite(interval) && interval >= 0 ? interval : null };
8819
+ }),
8820
+ execution: spans.length > 0 ? summarizeSpanExecution(spans) : null,
8821
+ adoption: computeAdoption(spans),
8822
+ relationship: describeSessionRelationship({
8823
+ sessionId: sessionId ?? spans[0]?.trace_id ?? "",
8824
+ harness: harness ?? "",
8825
+ path: "",
8826
+ cwd: null,
8827
+ mtimeMs: 0
8828
+ }, spans),
8829
+ tools: toolMeasurements(tools),
8830
+ compactions: compactions(spans),
8831
+ contextFootprints: contextFootprints(spans),
8832
+ coverage: {
8833
+ profile: "Native spans do not establish the authored or effective AgentProfile revision; join the Runtime profile receipt by recorded identity.",
8834
+ materializedResources: "Captured attributes measure only the visible representation. Missing tools, skills, MCP definitions, and resources are unknown.",
8835
+ contextTokens: "No tokenizer estimates are substituted for the actual request. Per-component context token counts require producer telemetry.",
8836
+ toolTokens: "Tool byte counts are not model input tokens; token attribution to tool content is not captured.",
8837
+ compaction: "Only recorded boundaries are counted. Zero-duration adapter defaults are not measured compaction latency.",
8838
+ semanticAssessments: "Success at the task, useful skill application, waste, reward hacking, safety, and dishonesty require separately attributed evaluation."
8839
+ }
8840
+ };
8841
+ }
8842
+
8595
8843
  // src/session-facts.ts
8596
8844
  import { OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND4, TOOL_NAME as TOOL_NAME5 } from "@tangle-network/agent-eval/trace-attributes";
8597
8845
  import { ingestSpans, rankFirstFailure } from "@tangle-network/agent-eval/diagnosis";
8846
+ var SESSION_FACTS_MEASUREMENT_VERSION = "3";
8598
8847
  var SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
8599
8848
  var SPAN_SYNTHESIZED_ATTR = "traces.codex.span_synthesized";
8600
8849
  var CORRUPTION_COUNT_ATTR = "traces.session.corruption_count";
@@ -8869,15 +9118,17 @@ function humanTurnsOf(spans) {
8869
9118
  excluded: [...excluded].sort(([left], [right]) => left.localeCompare(right)).map(([reason, spanIds]) => ({ reason, turns: spanIds.length, spanIds }))
8870
9119
  };
8871
9120
  }
8872
- function computeSessionFacts(spans) {
9121
+ function computeSessionFacts(spans, options = {}) {
8873
9122
  const { sessionByTrace } = indexSessionIdsByTrace(spans);
8874
9123
  const byTrace = /* @__PURE__ */ new Map();
8875
9124
  for (const span2 of spans) {
8876
- byTrace.set(span2.trace_id, [...byTrace.get(span2.trace_id) ?? [], span2]);
9125
+ const group = byTrace.get(span2.trace_id);
9126
+ if (group) group.push(span2);
9127
+ else byTrace.set(span2.trace_id, [span2]);
8877
9128
  }
8878
- return [...byTrace].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([traceId, traceSpans]) => sessionFactsForTrace(traceId, sessionByTrace.get(traceId) ?? null, traceSpans));
9129
+ return [...byTrace].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([traceId, traceSpans]) => sessionFactsForTrace(traceId, sessionByTrace.get(traceId) ?? null, traceSpans, options));
8879
9130
  }
8880
- function sessionFactsForTrace(traceId, sessionId, unordered) {
9131
+ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
8881
9132
  const spans = [...unordered].sort(byTraceOrder);
8882
9133
  const harness = spans.map((span2) => stringAttr2(span2, "service.name")).find((name) => name !== null) ?? null;
8883
9134
  const ownSpans = spans.filter((span2) => !isSubagentSpan(span2.attributes));
@@ -8916,7 +9167,7 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
8916
9167
  (span2) => span2.parent_span_id !== null && span2.attributes[CORRUPTION_RECEIPT_VERSION_ATTR] === void 0
8917
9168
  ).length;
8918
9169
  const corruptionSpan = spans.find((span2) => typeof span2.attributes[CORRUPTION_COUNT_ATTR] === "number");
8919
- return {
9170
+ const facts = {
8920
9171
  schemaVersion: 1,
8921
9172
  kind: "traces.session_facts",
8922
9173
  traceId,
@@ -8933,6 +9184,7 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
8933
9184
  spanIds: [],
8934
9185
  unavailable: `no span carries ${CORRUPTION_COUNT_ATTR}; this trace was not read through the session integrity path, so whether any record was skipped is unknown`
8935
9186
  },
9187
+ measurements: computeSessionMeasurements(ownSpans, invoked, sessionId, harness),
8936
9188
  toolCalls: {
8937
9189
  value: invoked.length,
8938
9190
  spanIds: invoked.map((span2) => span2.span_id),
@@ -8985,15 +9237,50 @@ function sessionFactsForTrace(traceId, sessionId, unordered) {
8985
9237
  unavailable: `no span carries ${SESSION_TOKEN_TOTAL_ATTR}; the per-turn llm.turn deltas in this trace are a different quantity and summing them would not be the harness total`
8986
9238
  }
8987
9239
  };
9240
+ if (options.includeContent !== false) return facts;
9241
+ const withheld = () => ({
9242
+ value: null,
9243
+ spanIds: [],
9244
+ unavailable: "Content was not requested."
9245
+ });
9246
+ return {
9247
+ ...facts,
9248
+ humanTurns: withheld(),
9249
+ finalMessages: withheld(),
9250
+ changedFiles: withheld(),
9251
+ pullRequests: withheld(),
9252
+ subagents: {
9253
+ ...facts.subagents,
9254
+ value: facts.subagents.value?.map((entry) => ({
9255
+ ...entry,
9256
+ taskName: null,
9257
+ taskNameUnavailable: "Assignment text was not requested."
9258
+ })) ?? null
9259
+ },
9260
+ firstFailure: facts.firstFailure.status === "found" ? { ...facts.firstFailure, message: null, classification: facts.firstFailure.classification ? { ...facts.firstFailure.classification, reason: "Content was not requested." } : null } : facts.firstFailure
9261
+ };
8988
9262
  }
8989
9263
  function buildSessionFactsReport(spans, options = {}) {
9264
+ const digest = createHash7("sha256");
9265
+ const ordered2 = [...spans].sort((a, b) => a.trace_id.localeCompare(b.trace_id) || a.span_id.localeCompare(b.span_id));
9266
+ let evidenceThrough = null;
9267
+ for (const span2 of ordered2) {
9268
+ digest.update(canonicalJson(span2)).update("\n");
9269
+ for (const time of [span2.start_time, span2.end_time]) {
9270
+ if (Number.isFinite(Date.parse(time)) && (evidenceThrough === null || Date.parse(time) > Date.parse(evidenceThrough))) evidenceThrough = time;
9271
+ }
9272
+ }
8990
9273
  return {
8991
9274
  schemaVersion: 1,
8992
9275
  kind: "traces.session_facts_report",
9276
+ measurementVersion: SESSION_FACTS_MEASUREMENT_VERSION,
9277
+ sourceDigest: "sha256:" + digest.digest("hex"),
9278
+ evidenceThrough,
9279
+ contentIncluded: options.includeContent !== false,
8993
9280
  generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
8994
9281
  harness: options.harness ?? null,
8995
9282
  spanCount: spans.length,
8996
- sessions: computeSessionFacts(spans)
9283
+ sessions: computeSessionFacts(spans, options)
8997
9284
  };
8998
9285
  }
8999
9286
  function factLine(label2, fact, rendered) {
@@ -9042,6 +9329,8 @@ function renderSessionFacts(report) {
9042
9329
  lines.push(factLine("changed files", facts.changedFiles, String(facts.changedFiles.value?.length ?? 0)));
9043
9330
  lines.push(factLine("first record", facts.firstRecordAt, String(facts.firstRecordAt.value)));
9044
9331
  lines.push(factLine("last record", facts.lastRecordAt, String(facts.lastRecordAt.value)));
9332
+ lines.push(factLine("compactions", facts.measurements.compactions, String(facts.measurements.compactions.value?.length ?? 0)));
9333
+ lines.push(" skills: " + facts.measurements.adoption.totalSkillInvocations + " recorded invocation(s), " + facts.measurements.adoption.totalSkillDocumentReads + " document read(s); telemetry " + facts.measurements.adoption.skillTelemetryStatus);
9045
9334
  lines.push(factLine("token total", facts.tokenTotal, String(facts.tokenTotal.value)));
9046
9335
  lines.push(` first failure: ${renderFirstFailure(facts.firstFailure)}`);
9047
9336
  if ((facts.unreadRecords.value ?? 0) > 0) {
@@ -9087,6 +9376,9 @@ ${JSON.stringify({ sessions, ...dropped2.length ? { omitted_fields: dropped2 } :
9087
9376
  ["tool_call_span_ids", (facts) => {
9088
9377
  facts.tool_calls.span_ids = [];
9089
9378
  }],
9379
+ ["measurements", (facts) => {
9380
+ delete facts.measurements;
9381
+ }],
9090
9382
  ["tool_calls_by_name", (facts) => {
9091
9383
  delete facts.tool_calls_by_name;
9092
9384
  }],
@@ -9211,7 +9503,8 @@ function compactFacts(facts) {
9211
9503
  first_record_at: factJson(facts.firstRecordAt),
9212
9504
  last_record_at: factJson(facts.lastRecordAt),
9213
9505
  token_total: factJson(facts.tokenTotal),
9214
- first_failure: compactFirstFailure(facts.firstFailure)
9506
+ first_failure: compactFirstFailure(facts.firstFailure),
9507
+ measurements: facts.measurements
9215
9508
  };
9216
9509
  }
9217
9510
  function compactFirstFailure(failure2) {
@@ -9240,7 +9533,7 @@ function sessionFactsContext(spans, options = {}) {
9240
9533
  if (report.sessions.length === 0) return void 0;
9241
9534
  return renderSessionFactsContext(report, options);
9242
9535
  }
9243
- var SESSION_FACTS_VERSION_SUFFIX = "session-facts.2";
9536
+ var SESSION_FACTS_VERSION_SUFFIX = "session-facts." + SESSION_FACTS_MEASUREMENT_VERSION;
9244
9537
  function withSessionFactsContext(definitions, spans, options = {}) {
9245
9538
  const sheet = sessionFactsContext(spans, options);
9246
9539
  if (sheet === void 0) return [...definitions];
@@ -9267,13 +9560,13 @@ import { OtlpFileTraceStore } from "@tangle-network/agent-eval/traces";
9267
9560
 
9268
9561
  // src/bundle-source.ts
9269
9562
  import { isUtf8 as isUtf82 } from "buffer";
9270
- import { createHash as createHash9 } from "crypto";
9563
+ import { createHash as createHash10 } from "crypto";
9271
9564
  import { constants } from "fs";
9272
9565
  import { lstat as lstat2, open, realpath as realpath3 } from "fs/promises";
9273
9566
  import { dirname as dirname8, isAbsolute as isAbsolute4, join as join20, relative as relative5, resolve as resolve7, sep as sep2 } from "path";
9274
9567
 
9275
9568
  // src/bundle.ts
9276
- import { createHash as createHash8 } from "crypto";
9569
+ import { createHash as createHash9 } from "crypto";
9277
9570
  import { copyFile, cp, lstat, mkdir as mkdir2, readdir as readdir16, readFile as readFile7, stat as stat14, writeFile as writeFile5 } from "fs/promises";
9278
9571
  import { dirname as dirname7, join as join19, relative as relative4, resolve as resolve6, sep } from "path";
9279
9572
 
@@ -10502,7 +10795,7 @@ function measured(value, render) {
10502
10795
  }
10503
10796
  function distributionRow(label2, distribution, unit = "") {
10504
10797
  if (distribution.n === 0) return `| ${label2} | 0 | not captured | not captured | not captured | not captured | not captured |`;
10505
- const value = (number) => measured(number, (captured) => `${decimal(captured)}${unit}`);
10798
+ const value = (number2) => measured(number2, (captured) => `${decimal(captured)}${unit}`);
10506
10799
  return `| ${label2} | ${count(distribution.n)} | ${value(distribution.min)} | ${value(distribution.mean)} | ${value(distribution.p50)} | ${value(distribution.p95)} | ${value(distribution.max)} |`;
10507
10800
  }
10508
10801
  function tokenRows(usage) {
@@ -11853,7 +12146,7 @@ async function writeSessionIndexFile(index, outPath) {
11853
12146
  }
11854
12147
 
11855
12148
  // src/session-selection.ts
11856
- import { createHash as createHash7 } from "crypto";
12149
+ import { createHash as createHash8 } from "crypto";
11857
12150
  import { createReadStream as createReadStream2 } from "fs";
11858
12151
 
11859
12152
  // src/session-workflow.ts
@@ -12179,7 +12472,7 @@ async function collectSessionWorkflow(adapter, seeds, options = {}) {
12179
12472
  // src/session-selection.ts
12180
12473
  async function fileSha256(path, signal) {
12181
12474
  signal?.throwIfAborted();
12182
- const hash2 = createHash7("sha256");
12475
+ const hash2 = createHash8("sha256");
12183
12476
  try {
12184
12477
  for await (const chunk of createReadStream2(path, { signal })) {
12185
12478
  signal?.throwIfAborted();
@@ -12207,7 +12500,7 @@ async function sourceSnapshot(adapter, ref, signal) {
12207
12500
  signal?.throwIfAborted();
12208
12501
  files.push({ path, sha256: await fileSha256(path, signal) });
12209
12502
  }
12210
- const sha2562 = files.length === 1 ? files[0].sha256 : createHash7("sha256").update(JSON.stringify(files)).digest("hex");
12503
+ const sha2562 = files.length === 1 ? files[0].sha256 : createHash8("sha256").update(JSON.stringify(files)).digest("hex");
12211
12504
  return { sha256: sha2562, files };
12212
12505
  }
12213
12506
  function summarizeWorkflows(workflows) {
@@ -12333,8 +12626,8 @@ var KNOWN_LIMITS = [
12333
12626
  "progress.md is copied whole; no session-dated section extraction is performed",
12334
12627
  "open pull-request state is not captured: the bundler makes no network calls"
12335
12628
  ];
12336
- function sha256Hex(bytes) {
12337
- return createHash8("sha256").update(bytes).digest("hex");
12629
+ function sha256Hex(bytes2) {
12630
+ return createHash9("sha256").update(bytes2).digest("hex");
12338
12631
  }
12339
12632
  function bundlePath(...parts) {
12340
12633
  return parts.join("/");
@@ -12741,13 +13034,13 @@ async function verifySessionBundle(bundleDir) {
12741
13034
  let source;
12742
13035
  if (manifest.projection) {
12743
13036
  const sourceManifest = join19(manifest.projection.sourceDirectory, "manifest.json");
12744
- const bytes = await readFile7(sourceManifest).catch((error) => {
13037
+ const bytes2 = await readFile7(sourceManifest).catch((error) => {
12745
13038
  if (isMissingPathError(error)) return null;
12746
13039
  throw error;
12747
13040
  });
12748
13041
  source = {
12749
13042
  directory: manifest.projection.sourceDirectory,
12750
- status: bytes === null ? "unavailable" : sha256Hex(bytes) === manifest.projection.sourceManifestSha256 ? "match" : "mismatch"
13043
+ status: bytes2 === null ? "unavailable" : sha256Hex(bytes2) === manifest.projection.sourceManifestSha256 ? "match" : "mismatch"
12751
13044
  };
12752
13045
  }
12753
13046
  return {
@@ -12791,8 +13084,8 @@ async function openBundleFile(root, file) {
12791
13084
  return handle;
12792
13085
  }
12793
13086
  async function fileDigest(handle, signal) {
12794
- const hash2 = createHash9("sha256");
12795
- for await (const bytes of handle.createReadStream({ start: 0, autoClose: false, signal })) hash2.update(bytes);
13087
+ const hash2 = createHash10("sha256");
13088
+ for await (const bytes2 of handle.createReadStream({ start: 0, autoClose: false, signal })) hash2.update(bytes2);
12796
13089
  return hash2.digest("hex");
12797
13090
  }
12798
13091
  async function validateFile(root, file, signal) {
@@ -12892,7 +13185,7 @@ async function createBundleSourceReader(directory, spans, options = {}) {
12892
13185
  handle = await openBundleFile(root, file);
12893
13186
  const before = await handle.stat();
12894
13187
  if (await fileDigest(handle, signal) !== file.sha256) return unavailable("retained source digest mismatch");
12895
- const hash2 = createHash9("sha256");
13188
+ const hash2 = createHash10("sha256");
12896
13189
  const record2 = Buffer.alloc(ref.recordBytes);
12897
13190
  let position = 0;
12898
13191
  for await (const raw of handle.createReadStream({
@@ -12901,10 +13194,10 @@ async function createBundleSourceReader(directory, spans, options = {}) {
12901
13194
  autoClose: false,
12902
13195
  signal
12903
13196
  })) {
12904
- const bytes2 = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
12905
- hash2.update(bytes2);
12906
- bytes2.copy(record2, position);
12907
- position += bytes2.length;
13197
+ const bytes3 = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
13198
+ hash2.update(bytes3);
13199
+ bytes3.copy(record2, position);
13200
+ position += bytes3.length;
12908
13201
  }
12909
13202
  const after = await handle.stat();
12910
13203
  if (position !== ref.recordBytes || hash2.digest("hex") !== ref.recordSha256 || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {
@@ -12927,22 +13220,22 @@ async function createBundleSourceReader(directory, spans, options = {}) {
12927
13220
  if (input.offset > field.length) return unavailable("source offset is outside the selected field");
12928
13221
  const window = field.subarray(input.offset, Math.min(field.length, input.offset + input.limit));
12929
13222
  if (window.length > 0 && (window[0] & 192) === 128) return unavailable("offset splits a UTF-8 character");
12930
- let bytes = window;
13223
+ let bytes2 = window;
12931
13224
  for (let omitted = 0; omitted <= 3; omitted += 1) {
12932
- bytes = window.subarray(0, Math.max(0, window.length - omitted));
12933
- if (isUtf82(bytes)) break;
13225
+ bytes2 = window.subarray(0, Math.max(0, window.length - omitted));
13226
+ if (isUtf82(bytes2)) break;
12934
13227
  }
12935
- if (!isUtf82(bytes) || bytes.length === 0 && input.offset < field.length) {
13228
+ if (!isUtf82(bytes2) || bytes2.length === 0 && input.offset < field.length) {
12936
13229
  return unavailable("window cannot contain a complete UTF-8 character");
12937
13230
  }
12938
- const next = input.offset + bytes.length;
13231
+ const next = input.offset + bytes2.length;
12939
13232
  return {
12940
13233
  status: "available",
12941
13234
  trace_id: input.trace_id,
12942
13235
  span_id: input.span_id,
12943
13236
  attribute: input.attribute,
12944
13237
  source_index: sourceIndex,
12945
- text: bytes.toString("utf8"),
13238
+ text: bytes2.toString("utf8"),
12946
13239
  offset: input.offset,
12947
13240
  total_bytes: field.length,
12948
13241
  next_offset: next < field.length ? next : null,
@@ -13075,7 +13368,7 @@ async function analyzeSpans(spans, opts = {}) {
13075
13368
  }
13076
13369
 
13077
13370
  // src/chat-trajectory.ts
13078
- import { createHash as createHash10 } from "crypto";
13371
+ import { createHash as createHash11 } from "crypto";
13079
13372
  function isChatTrajectoryMessage(value) {
13080
13373
  return isObject2(value) && typeof value.role === "string" && typeof value.content === "string";
13081
13374
  }
@@ -13160,7 +13453,7 @@ function trajectoryId(trajectory) {
13160
13453
  const value = trajectory.trajectory_id ?? trajectory.traj_id ?? trajectory.instance_id;
13161
13454
  if (typeof value === "string" && value.trim()) return value;
13162
13455
  if (typeof value === "number" && Number.isSafeInteger(value)) return String(value);
13163
- return `trajectory-${createHash10("sha256").update(JSON.stringify(trajectory.messages)).digest("hex").slice(0, 24)}`;
13456
+ return `trajectory-${createHash11("sha256").update(JSON.stringify(trajectory.messages)).digest("hex").slice(0, 24)}`;
13164
13457
  }
13165
13458
  function messageKind(role) {
13166
13459
  const normalized = role.trim().toLowerCase();
@@ -13217,8 +13510,8 @@ function parseTime2(value) {
13217
13510
  }
13218
13511
  function finiteNumber(value, key) {
13219
13512
  if (!isObject2(value)) return void 0;
13220
- const number = value[key];
13221
- return typeof number === "number" && Number.isFinite(number) ? number : void 0;
13513
+ const number2 = value[key];
13514
+ return typeof number2 === "number" && Number.isFinite(number2) ? number2 : void 0;
13222
13515
  }
13223
13516
  function stringValue2(value) {
13224
13517
  return typeof value === "string" && value.trim() ? value : void 0;
@@ -13240,7 +13533,7 @@ import {
13240
13533
  import { basename as basename13, dirname as dirname10, join as join23, resolve as resolve9 } from "path";
13241
13534
 
13242
13535
  // src/codetracebench-files.ts
13243
- import { createHash as createHash11, randomUUID } from "crypto";
13536
+ import { createHash as createHash12, randomUUID } from "crypto";
13244
13537
  import {
13245
13538
  link,
13246
13539
  lstat as lstat3,
@@ -13438,7 +13731,7 @@ async function atomicWriteFile(path, content, signal) {
13438
13731
  }
13439
13732
  }
13440
13733
  function sha256(value) {
13441
- return createHash11("sha256").update(value).digest("hex");
13734
+ return createHash12("sha256").update(value).digest("hex");
13442
13735
  }
13443
13736
  function isObject3(value) {
13444
13737
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -13675,10 +13968,10 @@ async function readOptionalTask(trajectoryDirectory, caseDirectory, row, signal)
13675
13968
  content: `Task: ${row.taskName}`
13676
13969
  };
13677
13970
  }
13678
- const bytes = await readFile9(path, { signal });
13679
- const content = bytes.toString("utf8").trim();
13971
+ const bytes2 = await readFile9(path, { signal });
13972
+ const content = bytes2.toString("utf8").trim();
13680
13973
  if (!content) throw new Error(`${row.trajId}/task.md is empty`);
13681
- return { source: "task.md", content, path, bytes };
13974
+ return { source: "task.md", content, path, bytes: bytes2 };
13682
13975
  }
13683
13976
  function assertNoLabelLeak(spans, row) {
13684
13977
  for (const item of spans) {
@@ -14328,7 +14621,7 @@ async function cmdVerifyFindings(argv) {
14328
14621
  }
14329
14622
 
14330
14623
  // src/hodoscope.ts
14331
- import { createHash as createHash12 } from "crypto";
14624
+ import { createHash as createHash13 } from "crypto";
14332
14625
  import { mkdir as mkdir5, mkdtemp as mkdtemp5, readFile as readFile11, readdir as readdir17, writeFile as writeFile7 } from "fs/promises";
14333
14626
  import { endianness, tmpdir as tmpdir5 } from "os";
14334
14627
  import { join as join24, resolve as resolve10 } from "path";
@@ -14367,7 +14660,7 @@ async function writeHodoscopeInput(otlpPath, options = {}) {
14367
14660
  throw new Error(`Hodoscope trace '${traceId}' contains no assistant actions`);
14368
14661
  }
14369
14662
  actionCount += actions;
14370
- const file = `${createHash12("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
14663
+ const file = `${createHash13("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
14371
14664
  await writeFile7(join24(samples, file), `${JSON.stringify(trajectory, null, 2)}
14372
14665
  `, "utf8");
14373
14666
  }
@@ -14879,7 +15172,7 @@ function validateEmbedding(encoded, dimensions, path) {
14879
15172
  `Hodoscope ${path} encodes ${encoded.length} characters; expected ${expectedLength} for ${dimensions} float32 values`
14880
15173
  );
14881
15174
  }
14882
- const bytes = new Uint8Array(dimensions * 4);
15175
+ const bytes2 = new Uint8Array(dimensions * 4);
14883
15176
  for (let group = 0; group < dimensions; group += 1) {
14884
15177
  let value = 0;
14885
15178
  const encodedOffset = group * 5;
@@ -14895,12 +15188,12 @@ function validateEmbedding(encoded, dimensions, path) {
14895
15188
  throw new Error(`Hodoscope ${path} contains an overflowing base85 block at index ${group}`);
14896
15189
  }
14897
15190
  const byteOffset = group * 4;
14898
- bytes[byteOffset] = Math.floor(value / 16777216) & 255;
14899
- bytes[byteOffset + 1] = Math.floor(value / 65536) & 255;
14900
- bytes[byteOffset + 2] = Math.floor(value / 256) & 255;
14901
- bytes[byteOffset + 3] = value & 255;
15191
+ bytes2[byteOffset] = Math.floor(value / 16777216) & 255;
15192
+ bytes2[byteOffset + 1] = Math.floor(value / 65536) & 255;
15193
+ bytes2[byteOffset + 2] = Math.floor(value / 256) & 255;
15194
+ bytes2[byteOffset + 3] = value & 255;
14902
15195
  }
14903
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
15196
+ const view = new DataView(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength);
14904
15197
  const littleEndian = endianness() === "LE";
14905
15198
  for (let index = 0; index < dimensions; index += 1) {
14906
15199
  if (!Number.isFinite(view.getFloat32(index * 4, littleEndian))) {
@@ -15015,7 +15308,7 @@ function assertPositiveInteger(name, value) {
15015
15308
  }
15016
15309
 
15017
15310
  // src/analyst-engine-prime.ts
15018
- import { createHash as createHash13 } from "crypto";
15311
+ import { createHash as createHash14 } from "crypto";
15019
15312
  import { request as httpRequest } from "http";
15020
15313
  import { request as httpsRequest } from "https";
15021
15314
  var DEFAULT_BRIDGE_URL = "http://localhost:4181";
@@ -15028,6 +15321,17 @@ var MAX_FINDINGS = 10;
15028
15321
  var MAX_SPAN_IDS_PER_FINDING = 8;
15029
15322
  var SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low", "info"]);
15030
15323
  var ANALYST_ID = "prime";
15324
+ var TRAJECTORY_FILE_NAME = "trajectory.otlp.jsonl";
15325
+ async function fileDelivery(otlpPath, perAttributeCharCap, signal) {
15326
+ signal?.throwIfAborted();
15327
+ const { copyFile: copyFile2, mkdtemp: mkdtemp8, stat: stat19 } = await import("fs/promises");
15328
+ const { tmpdir: tmpdir8 } = await import("os");
15329
+ const { join: join35 } = await import("path");
15330
+ const cwd = await mkdtemp8(join35(tmpdir8(), "traces-prime-"));
15331
+ await copyFile2(otlpPath, join35(cwd, TRAJECTORY_FILE_NAME));
15332
+ const artifactBytes = (await stat19(otlpPath)).size;
15333
+ return { mode: "file-cwd", perAttributeCharCap, renderedChars: artifactBytes, cwd, fileName: TRAJECTORY_FILE_NAME };
15334
+ }
15031
15335
  function positiveIntegerEnv(name) {
15032
15336
  const raw = process.env[name];
15033
15337
  if (raw === void 0 || raw === "") return void 0;
@@ -15078,7 +15382,7 @@ function capRowAttributes(row, cap) {
15078
15382
  }
15079
15383
  return { ...row, attributes: capped };
15080
15384
  }
15081
- async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, signal) {
15385
+ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, deliveryMode, signal) {
15082
15386
  const rows = [];
15083
15387
  const spanTraces = /* @__PURE__ */ new Map();
15084
15388
  let index = 0;
@@ -15102,9 +15406,18 @@ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, si
15102
15406
  }
15103
15407
  if (rows.length === 0) throw new Error(`${otlpPath} contains no spans`);
15104
15408
  const traceCount = new Set(rows.map((row) => row.trace_id)).size;
15409
+ if (deliveryMode === "file") {
15410
+ return {
15411
+ rows,
15412
+ rendered: null,
15413
+ delivery: await fileDelivery(otlpPath, perAttributeCharCap, signal),
15414
+ traceCount,
15415
+ spanTraces
15416
+ };
15417
+ }
15105
15418
  let projected = rows;
15106
15419
  let rendered = JSON.stringify(projected);
15107
- const delivery = {
15420
+ let delivery = {
15108
15421
  mode: "inline-json",
15109
15422
  perAttributeCharCap: null,
15110
15423
  renderedChars: rendered.length
@@ -15112,18 +15425,30 @@ async function projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, si
15112
15425
  if (rendered.length > maxInlineChars) {
15113
15426
  projected = rows.map((row) => capRowAttributes(row, perAttributeCharCap));
15114
15427
  rendered = JSON.stringify(projected);
15115
- delivery.perAttributeCharCap = perAttributeCharCap;
15116
- delivery.renderedChars = rendered.length;
15428
+ delivery = {
15429
+ mode: "inline-json",
15430
+ perAttributeCharCap,
15431
+ renderedChars: rendered.length
15432
+ };
15117
15433
  }
15118
15434
  if (rendered.length > maxInlineChars) {
15119
- throw new Error(
15120
- `trajectory renders to ${rendered.length} chars even at per-attribute cap ${perAttributeCharCap} (budget ${maxInlineChars}); inline delivery impossible`
15121
- );
15435
+ if (deliveryMode === "inline") {
15436
+ throw new Error(
15437
+ `trajectory renders to ${rendered.length} chars even at per-attribute cap ${perAttributeCharCap} (budget ${maxInlineChars}); inline delivery impossible`
15438
+ );
15439
+ }
15440
+ return {
15441
+ rows,
15442
+ rendered: null,
15443
+ delivery: await fileDelivery(otlpPath, perAttributeCharCap, signal),
15444
+ traceCount,
15445
+ spanTraces
15446
+ };
15122
15447
  }
15123
15448
  return { rows: projected, rendered, delivery, traceCount, spanTraces };
15124
15449
  }
15125
15450
  var OUTPUT_CONTRACT_LINES = [
15126
- "OUTPUT CONTRACT (you have no trace tools and no REPL):",
15451
+ "OUTPUT CONTRACT (inline delivery \u2014 the whole trajectory is in this prompt):",
15127
15452
  "You are a one-shot analyst. Every fact you need is in the TRAJECTORY JSON below.",
15128
15453
  "Do not run shell commands, do not read or write files, do not use any tools.",
15129
15454
  "Reply with EXACTLY one fenced ```json code block and no other fenced block. The JSON object has exactly two fields:",
@@ -15139,7 +15464,38 @@ var OUTPUT_CONTRACT_LINES = [
15139
15464
  `Report at most ${MAX_FINDINGS} findings; a finding cites 1..${MAX_SPAN_IDS_PER_FINDING} span_ids, each copied VERBATIM from a span in the trajectory below.`,
15140
15465
  '"findings" is [] only for a clean trajectory.'
15141
15466
  ];
15467
+ var FILE_DELIVERY_CONTRACT_LINES = [
15468
+ "OUTPUT CONTRACT (file delivery \u2014 the trajectory is a FILE in your working directory):",
15469
+ `You are a recursive language model with a REPL. The trajectory is ${TRAJECTORY_FILE_NAME} in your`,
15470
+ "working directory: OpenInference JSONL, one span object per line. Load it yourself and inspect it with code",
15471
+ '(e.g. rows = [json.loads(line) for line in open("trajectory.otlp.jsonl")]), paging rather than printing it whole;',
15472
+ "fan out with rlm() for sub-questions when that pays for itself. Stay inside your working directory.",
15473
+ "Reply with EXACTLY one fenced ```json code block and no other fenced block. The JSON object has exactly two fields:",
15474
+ ' "answer": string \u2014 ONE short sentence (max 300 chars) summarizing your verdict.',
15475
+ ' "findings": array (possibly empty) of finding rows, each exactly:',
15476
+ ' {"span_ids": [string, ...],',
15477
+ ' "severity": "critical"|"high"|"medium"|"low"|"info",',
15478
+ ' "area": string (short kebab-case topic, max 40 chars),',
15479
+ ' "claim": string (ONE short sentence, max 200 chars),',
15480
+ ' "action": string (optional, ONE short imperative sentence, max 200 chars),',
15481
+ ' "confidence": number 0..1}',
15482
+ "Do NOT include a rationale field. Keep every string SHORT \u2014 long strings get corrupted in transport and void your work.",
15483
+ `Report at most ${MAX_FINDINGS} findings; a finding cites 1..${MAX_SPAN_IDS_PER_FINDING} span_ids, each copied VERBATIM from a span in ${TRAJECTORY_FILE_NAME}.`,
15484
+ '"findings" is [] only for a clean trajectory.',
15485
+ "FINAL MESSAGE RULE: after your REPL work is done, send ONE last message that contains NOTHING BUT the",
15486
+ "fenced ```json block above \u2014 no preamble, no summary, no code identifiers around it. A reply whose last",
15487
+ "message is prose or a partial block is discarded unread."
15488
+ ];
15142
15489
  function buildPrompt(question, projection) {
15490
+ if (projection.delivery.mode === "file-cwd") {
15491
+ return [
15492
+ `QUESTION: ${question}`,
15493
+ "",
15494
+ ...FILE_DELIVERY_CONTRACT_LINES,
15495
+ "",
15496
+ `TRAJECTORY (${projection.traceCount} trace(s); ${projection.rows.length} spans): the file ${projection.delivery.fileName} in your working directory (${projection.delivery.renderedChars} bytes). It is NOT inlined in this prompt.`
15497
+ ].join("\n");
15498
+ }
15143
15499
  return [
15144
15500
  `QUESTION: ${question}`,
15145
15501
  "",
@@ -15149,16 +15505,23 @@ function buildPrompt(question, projection) {
15149
15505
  projection.rendered
15150
15506
  ].join("\n");
15151
15507
  }
15152
- function buildRepairPrompt(parseDefect, previousReply) {
15508
+ function replyWasTruncated(previousReply, finishReason) {
15509
+ if (finishReason === "length") return true;
15510
+ if (finishReason !== null) return false;
15511
+ return extractJsonObject(previousReply) === null;
15512
+ }
15513
+ function buildRepairPrompt(parseDefect, previousReply, finishReason) {
15514
+ const truncated = replyWasTruncated(previousReply, finishReason);
15153
15515
  return [
15154
15516
  "Your previous reply to a trace-analysis task was structurally malformed and could not be parsed",
15155
- `(${parseDefect}). Below is your previous reply verbatim. Re-emit ONLY the corrected JSON \u2014 one`,
15156
- "fenced ```json block, no other text, no tools. The JSON object has exactly two fields:",
15517
+ `(${parseDefect}${truncated ? "; it was cut off by the output limit" : ""}). Below is your previous reply verbatim.`,
15518
+ "Re-emit ONLY the corrected JSON \u2014 one fenced ```json block, no other text, no tools. The JSON object has exactly two fields:",
15157
15519
  ' "answer": string (ONE short sentence, max 300 chars)',
15158
15520
  ' "findings": array (possibly empty) of {"span_ids": [string, ...],',
15159
15521
  ' "severity": "critical"|"high"|"medium"|"low"|"info", "area": string (max 40 chars),',
15160
15522
  ' "claim": string (max 200 chars), "action": string (optional, max 200 chars), "confidence": number 0..1}',
15161
- "No rationale field. Keep every string SHORT. Preserve the span ids and verdicts of your previous reply exactly; shorten prose freely.",
15523
+ "No rationale field. Keep every string SHORT.",
15524
+ truncated ? "Your previous reply was cut off by the output limit, so re-emit at most 3 findings \u2014 your strongest \u2014 with terse strings (claim and action under 120 chars each); do not reproduce the whole cut-off list." : "Preserve the span ids and verdicts of your previous reply exactly; shorten prose freely.",
15162
15525
  "",
15163
15526
  "PREVIOUS REPLY:",
15164
15527
  previousReply
@@ -15239,7 +15602,7 @@ function toAnalystFinding(row, index, spanTraces, model, producedAt) {
15239
15602
  const traces = spanTraces.get(spanId);
15240
15603
  return spanEvidenceUri([...traces][0], spanId);
15241
15604
  });
15242
- const findingId = `prime-${createHash13("sha256").update(`${index}
15605
+ const findingId = `prime-${createHash14("sha256").update(`${index}
15243
15606
  ${row.claim}
15244
15607
  ${uris.join("\n")}`).digest("hex").slice(0, 12)}`;
15245
15608
  return {
@@ -15286,7 +15649,7 @@ var httpJsonTransport = ({ url, body, signal }) => {
15286
15649
  };
15287
15650
  var PrimeBridgeError = class extends Error {
15288
15651
  };
15289
- async function callBridge(transport, url, model, content, timeoutMs, callerSignal, turn) {
15652
+ async function callBridge(transport, url, model, content, timeoutMs, callerSignal, turn, cwd) {
15290
15653
  const controller = new AbortController();
15291
15654
  const timer = setTimeout(
15292
15655
  () => controller.abort(new Error(`${turn}bridge call exceeded ${timeoutMs}ms`)),
@@ -15299,7 +15662,7 @@ async function callBridge(transport, url, model, content, timeoutMs, callerSigna
15299
15662
  try {
15300
15663
  response = await transport({
15301
15664
  url,
15302
- body: { model, messages: [{ role: "user", content }] },
15665
+ body: { model, messages: [{ role: "user", content }], ...cwd ? { cwd } : {} },
15303
15666
  signal: controller.signal
15304
15667
  });
15305
15668
  } catch (error) {
@@ -15325,7 +15688,12 @@ async function callBridge(transport, url, model, content, timeoutMs, callerSigna
15325
15688
  if (typeof replyContent !== "string" || replyContent.length === 0) {
15326
15689
  throw new PrimeBridgeError(`${turn}bridge reply carries no message content`);
15327
15690
  }
15328
- return { content: replyContent, usage: normalizeUsage(record2.usage) };
15691
+ const finishReason = record2.choices?.[0]?.finish_reason;
15692
+ return {
15693
+ content: replyContent,
15694
+ usage: normalizeUsage(record2.usage),
15695
+ finishReason: typeof finishReason === "string" && finishReason.length > 0 ? finishReason : null
15696
+ };
15329
15697
  }
15330
15698
  function failure(output, error) {
15331
15699
  return { analyzer: ANALYST_ID, kind: "report", ok: false, output, error };
@@ -15346,6 +15714,10 @@ function primeAnalyzer(opts = {}) {
15346
15714
  throw new RangeError("perAttributeCharCap must be a positive safe integer");
15347
15715
  }
15348
15716
  const repairEnabled = opts.repair ?? true;
15717
+ const deliveryMode = opts.delivery ?? "auto";
15718
+ if (deliveryMode !== "auto" && deliveryMode !== "file" && deliveryMode !== "inline") {
15719
+ throw new RangeError(`delivery must be 'auto', 'file', or 'inline', got '${String(deliveryMode)}'`);
15720
+ }
15349
15721
  const transport = opts.transport ?? httpJsonTransport;
15350
15722
  const url = `${bridgeUrl}/v1/chat/completions`;
15351
15723
  return {
@@ -15353,12 +15725,19 @@ function primeAnalyzer(opts = {}) {
15353
15725
  async analyze(otlpPath, analyzeOpts = {}) {
15354
15726
  let projection;
15355
15727
  try {
15356
- projection = await projectArtifact(otlpPath, maxInlineChars, perAttributeCharCap, analyzeOpts.signal);
15728
+ projection = await projectArtifact(
15729
+ otlpPath,
15730
+ maxInlineChars,
15731
+ perAttributeCharCap,
15732
+ deliveryMode,
15733
+ analyzeOpts.signal
15734
+ );
15357
15735
  } catch (error) {
15358
15736
  return failure("", error instanceof Error ? error.message : String(error));
15359
15737
  }
15360
15738
  const question = analyzeOpts.prompt ?? opts.defaultPrompt ?? DEFAULT_QUESTION;
15361
- const deliveryLine = `delivery: ${projection.delivery.mode} (${projection.delivery.renderedChars} chars, per-attribute cap ${projection.delivery.perAttributeCharCap ?? "none"})`;
15739
+ const deliveryLine = projection.delivery.mode === "file-cwd" ? `delivery: file-cwd (${projection.rows.length} spans; ${projection.delivery.fileName} in cwd ${projection.delivery.cwd}, ${projection.delivery.renderedChars} bytes; per-attribute cap not applied)` : `delivery: ${projection.delivery.mode} (${projection.delivery.renderedChars} chars, per-attribute cap ${projection.delivery.perAttributeCharCap ?? "none"})`;
15740
+ const deliveryCwd = projection.delivery.mode === "file-cwd" ? projection.delivery.cwd : void 0;
15362
15741
  let reply;
15363
15742
  try {
15364
15743
  reply = await callBridge(
@@ -15368,7 +15747,8 @@ function primeAnalyzer(opts = {}) {
15368
15747
  buildPrompt(question, projection),
15369
15748
  timeoutMs,
15370
15749
  analyzeOpts.signal,
15371
- ""
15750
+ "",
15751
+ deliveryCwd
15372
15752
  );
15373
15753
  } catch (error) {
15374
15754
  if (!(error instanceof PrimeBridgeError)) throw error;
@@ -15386,7 +15766,7 @@ function primeAnalyzer(opts = {}) {
15386
15766
  transport,
15387
15767
  url,
15388
15768
  model,
15389
- buildRepairPrompt(parseDefect, reply.content),
15769
+ buildRepairPrompt(parseDefect, reply.content, reply.finishReason),
15390
15770
  timeoutMs,
15391
15771
  analyzeOpts.signal,
15392
15772
  "repair-turn "
@@ -15525,7 +15905,7 @@ async function applyRedactor(spans, redactor) {
15525
15905
  }
15526
15906
 
15527
15907
  // src/file-export.ts
15528
- import { createHash as createHash14 } from "crypto";
15908
+ import { createHash as createHash15 } from "crypto";
15529
15909
  import { mkdtemp as mkdtemp6, readFile as readFile12, writeFile as writeFile8 } from "fs/promises";
15530
15910
  import { tmpdir as tmpdir6 } from "os";
15531
15911
  import { join as join25 } from "path";
@@ -15563,7 +15943,7 @@ function stableJson(value) {
15563
15943
  });
15564
15944
  }
15565
15945
  function hashId(value, chars = 16) {
15566
- return createHash14("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
15946
+ return createHash15("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
15567
15947
  }
15568
15948
  function isoTime2(value) {
15569
15949
  if (typeof value === "string") {
@@ -16502,7 +16882,7 @@ async function writeInspectionReportFile(report, outPath) {
16502
16882
  }
16503
16883
 
16504
16884
  // src/live.ts
16505
- import { createHash as createHash15 } from "crypto";
16885
+ import { createHash as createHash16 } from "crypto";
16506
16886
  var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
16507
16887
  var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
16508
16888
  var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
@@ -16522,7 +16902,7 @@ var sleep = (ms2, signal) => new Promise((resolve13) => {
16522
16902
  signal?.addEventListener("abort", onAbort, { once: true });
16523
16903
  });
16524
16904
  function hash(parts) {
16525
- return createHash15("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
16905
+ return createHash16("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
16526
16906
  }
16527
16907
  function asString(value) {
16528
16908
  if (typeof value === "string" && value.trim()) return value;
@@ -18748,7 +19128,7 @@ async function watchSessions(opts) {
18748
19128
  }
18749
19129
 
18750
19130
  // src/bundle-view.ts
18751
- import { createHash as createHash16 } from "crypto";
19131
+ import { createHash as createHash17 } from "crypto";
18752
19132
  import { mkdir as mkdir7, readdir as readdir20, readFile as readFile16, writeFile as writeFile11 } from "fs/promises";
18753
19133
  import { dirname as dirname11, join as join31, resolve as resolve12 } from "path";
18754
19134
  var EVIDENCE_ONLY_ALLOW_LIST = [
@@ -18941,8 +19321,8 @@ async function projectSessionBundle(opts) {
18941
19321
  const candidates = [];
18942
19322
  for (const path of await listSessionBundleFiles(sourceDir)) {
18943
19323
  if (path === "manifest.json") continue;
18944
- const bytes = await readFile16(join31(sourceDir, path));
18945
- const sha2562 = createHash16("sha256").update(bytes).digest("hex");
19324
+ const bytes2 = await readFile16(join31(sourceDir, path));
19325
+ const sha2562 = createHash17("sha256").update(bytes2).digest("hex");
18946
19326
  const recorded = sourceHashes.get(path);
18947
19327
  if (!recorded) {
18948
19328
  throw new Error(
@@ -18956,10 +19336,10 @@ async function projectSessionBundle(opts) {
18956
19336
  }
18957
19337
  const verdict = classifyEvidenceOnlyPath(path);
18958
19338
  if (!verdict.carried) {
18959
- excluded.push({ path, bytes: bytes.length, sha256: sha2562, rule: verdict.rule, reason: verdict.reason });
19339
+ excluded.push({ path, bytes: bytes2.length, sha256: sha2562, rule: verdict.rule, reason: verdict.reason });
18960
19340
  continue;
18961
19341
  }
18962
- candidates.push({ path, bytes, sha256: sha2562, carries: verdict.carries, owned: verdict.owned });
19342
+ candidates.push({ path, bytes: bytes2, sha256: sha2562, carries: verdict.carries, owned: verdict.owned });
18963
19343
  }
18964
19344
  const report = await findSessionTextLeaks({
18965
19345
  viewDir: sourceDir,
@@ -19018,7 +19398,7 @@ async function projectSessionBundle(opts) {
19018
19398
  knownLimits: [...source.knownLimits, ...EVIDENCE_ONLY_LIMITS],
19019
19399
  projection: {
19020
19400
  sourceDirectory: sourceDir,
19021
- sourceManifestSha256: createHash16("sha256").update(sourceManifestBytes).digest("hex"),
19401
+ sourceManifestSha256: createHash17("sha256").update(sourceManifestBytes).digest("hex"),
19022
19402
  leakCheck
19023
19403
  }
19024
19404
  };
@@ -19442,14 +19822,14 @@ async function verifySafe(target, options = {}) {
19442
19822
  const files = await listFiles(target, unreadable);
19443
19823
  for (const file of files) {
19444
19824
  const label2 = relative7(target, file) || basename17(file);
19445
- let bytes;
19825
+ let bytes2;
19446
19826
  try {
19447
- bytes = await readFile19(file);
19827
+ bytes2 = await readFile19(file);
19448
19828
  } catch (error) {
19449
19829
  unreadable.push(`${label2}: ${error instanceof Error ? error.message : String(error)}`);
19450
19830
  continue;
19451
19831
  }
19452
- const text2 = decodeUtf8(bytes);
19832
+ const text2 = decodeUtf8(bytes2);
19453
19833
  if (text2 === void 0) {
19454
19834
  unreadable.push(`${label2}: not UTF-8 text`);
19455
19835
  continue;
@@ -19498,10 +19878,10 @@ async function listFiles(target, unreadable) {
19498
19878
  await walk(target);
19499
19879
  return out;
19500
19880
  }
19501
- function decodeUtf8(bytes) {
19502
- if (bytes.includes(0)) return void 0;
19881
+ function decodeUtf8(bytes2) {
19882
+ if (bytes2.includes(0)) return void 0;
19503
19883
  try {
19504
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
19884
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
19505
19885
  } catch {
19506
19886
  return void 0;
19507
19887
  }
@@ -19572,6 +19952,7 @@ export {
19572
19952
  EXPORT_RULE,
19573
19953
  toOpenInferenceSpan,
19574
19954
  serializeSpans,
19955
+ iterSerializedSpans,
19575
19956
  writeOtlpFile,
19576
19957
  summarizeOtlpExecution,
19577
19958
  summarizeSpanExecution,
@@ -19633,6 +20014,7 @@ export {
19633
20014
  serializePolicyEvidence,
19634
20015
  writePolicyEvidenceFile,
19635
20016
  countSkillRunsJsonl,
20017
+ computeAdoption,
19636
20018
  analyzeAdoption,
19637
20019
  planTraceAgenticRoute,
19638
20020
  traceAgenticKinds,
@@ -19644,6 +20026,7 @@ export {
19644
20026
  shellCommands,
19645
20027
  commandTextFromInput,
19646
20028
  readPullRequests,
20029
+ SESSION_FACTS_MEASUREMENT_VERSION,
19647
20030
  SESSION_TOKEN_TOTAL_ATTR,
19648
20031
  SPAN_SYNTHESIZED_ATTR,
19649
20032
  FACT_TEXT_CAP,