@tangle-network/traces 0.22.0 → 0.23.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.
@@ -151,6 +151,7 @@ var ATTR = {
151
151
  * in-container path even when no such path exists on this host. */
152
152
  ENVIRONMENT_CWD: "tangle.environment.cwd"
153
153
  };
154
+ var SESSION_CWD_ATTR = "traces.session.cwd";
154
155
  var INGEST_SOURCE_CLI = "cli";
155
156
  function environmentAttributes(environment) {
156
157
  if (!environment) return {};
@@ -254,6 +255,9 @@ function parseIsoToEpochMs(ts) {
254
255
  return n;
255
256
  }
256
257
  function parseSince(value) {
258
+ return parseTimeBound(value, "--since");
259
+ }
260
+ function parseTimeBound(value, flag) {
257
261
  const m = value.match(/^(\d+)\s*([mhd])$/i);
258
262
  if (m) {
259
263
  const unit = m[2].toLowerCase();
@@ -261,7 +265,7 @@ function parseSince(value) {
261
265
  return Date.now() - Number(m[1]) * ms2;
262
266
  }
263
267
  const t = Date.parse(value);
264
- if (Number.isNaN(t)) throw new Error(`--since: expected 30m / 2h / 7d or an ISO date, got "${value}"`);
268
+ if (Number.isNaN(t)) throw new Error(`${flag}: expected 30m / 2h / 7d or an ISO date, got "${value}"`);
265
269
  return t;
266
270
  }
267
271
 
@@ -2585,6 +2589,7 @@ var GeminiAdapter = class extends GeminiFamilyAdapter {
2585
2589
  };
2586
2590
 
2587
2591
  // src/adapters/actor.ts
2592
+ import { claudeRecordIsMachine } from "@tangle-network/harness-sessions";
2588
2593
  var CMD_MARKERS = [
2589
2594
  "<command-name>",
2590
2595
  "<local-command-caveat>",
@@ -2633,22 +2638,6 @@ var SYNTHETIC_MARKERS = [
2633
2638
  "Staff-engineer code review"
2634
2639
  ];
2635
2640
  var AGENT_PROMPT = /^(you are\b|you're\b|read[- ]only\b|stop now\b|your task\b|your job\b|review the\b|audit\b|analyze the\b|return (only|a |the )|here is the\b|context:|task:|do not (revert|edit|touch)|work only in\b|you have been\b|act as\b|operate the\b|goal:|objective:)/i;
2636
- var CLAUDE_MACHINE_PREFIXES = [
2637
- "<local-command-caveat>",
2638
- "<local-command-stdout>",
2639
- "<local-command-stderr>",
2640
- "Caveat: The messages below",
2641
- "<task-notification>",
2642
- "<bash-stdout>",
2643
- "<bash-stderr>",
2644
- "<user-prompt-submit-hook>",
2645
- "[Request interrupted by user"
2646
- ];
2647
- function claudeRecordIsMachine(args) {
2648
- if (args.isMeta === true || args.isCompactSummary === true || args.entrypoint === "sdk-cli") return true;
2649
- const text2 = args.text.trimStart();
2650
- return CLAUDE_MACHINE_PREFIXES.some((prefix) => text2.startsWith(prefix));
2651
- }
2652
2641
  function textIsCmdOrInject(s) {
2653
2642
  return CMD_MARKERS.some((m) => s.includes(m)) || INJECT_MARKERS.some((m) => s.includes(m));
2654
2643
  }
@@ -2686,7 +2675,7 @@ function claudePromptStartsTask(args) {
2686
2675
  }
2687
2676
  function claudeActor(args) {
2688
2677
  if (args.isSidechain === true) return "subagent-spawn";
2689
- if (claudeRecordIsMachine(args)) return "injected";
2678
+ if (claudeRecordIsMachine({ ...args, entrypoint: args.entrypoint ?? void 0 })) return "injected";
2690
2679
  if (args.userType != null && args.userType !== "external") return "injected";
2691
2680
  if (textIsCmdOrInject(args.text)) return "injected";
2692
2681
  if (textIsSynthetic(args.text)) return "injected";
@@ -2770,6 +2759,7 @@ import {
2770
2759
  LLM_OUTPUT_TOKENS as LLM_OUTPUT_TOKENS2
2771
2760
  } from "@tangle-network/agent-eval/trace-attributes";
2772
2761
  import { ATTR as ATTR3, deriveHexId as deriveHexId2 } from "@tangle-network/agent-trace-contract";
2762
+ import { claudeRecordIsMachine as claudeRecordIsMachine2, claudeTypedPromptText } from "@tangle-network/harness-sessions";
2773
2763
 
2774
2764
  // src/adapters/claude-subagent-sources.ts
2775
2765
  import { readdir as readdir6, realpath } from "fs/promises";
@@ -3125,8 +3115,6 @@ var ORIGIN_EVIDENCE_ATTR = "traces.claude.actor_evidence";
3125
3115
  var MACHINE_RECORD_ATTR = "traces.claude.machine_record";
3126
3116
  var ORIGIN_RECORDED_ATTR = "traces.claude.origin_recorded";
3127
3117
  var ORIGIN_KIND_ATTR = "traces.claude.origin_kind";
3128
- var COMMAND_NAME = /<command-name>([\s\S]*?)<\/command-name>/;
3129
- var COMMAND_ARGS = /<command-args>([\s\S]*?)<\/command-args>/;
3130
3118
  var SPAWN_TYPE_KEY = "subagent_type";
3131
3119
  var SPAWN_DESCRIPTION_KEY = "description";
3132
3120
  var SPAWN_TYPE_ATTR = "traces.claude.subagent_type";
@@ -3322,12 +3310,6 @@ function claudeActorFromOrigin(recorded, fallback) {
3322
3310
  }
3323
3311
  return fallback();
3324
3312
  }
3325
- function typedPromptText(text2) {
3326
- const name = COMMAND_NAME.exec(text2)?.[1]?.trim();
3327
- if (!name) return text2;
3328
- const args = COMMAND_ARGS.exec(text2)?.[1]?.trim();
3329
- return args ? `${name} ${args}` : name;
3330
- }
3331
3313
  function recordedOrigin(origin) {
3332
3314
  const kind = origin?.kind;
3333
3315
  return typeof kind === "string" && kind.length > 0 ? kind : null;
@@ -3533,11 +3515,11 @@ function consumeClaudeEvent(event, uid, ctx, state) {
3533
3515
  }
3534
3516
  } else if (event.kind === "user") {
3535
3517
  if (event.prompt) {
3536
- const machine = claudeRecordIsMachine({
3518
+ const machine = claudeRecordIsMachine2({
3537
3519
  text: event.prompt,
3538
3520
  isMeta: event.isMeta,
3539
3521
  isCompactSummary: event.isCompactSummary,
3540
- entrypoint: event.entrypoint
3522
+ entrypoint: event.entrypoint ?? void 0
3541
3523
  });
3542
3524
  const actor = machine ? "injected" : claudeActorFromOrigin(event.originKind, () => claudeActor({
3543
3525
  text: event.prompt ?? "",
@@ -3556,7 +3538,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
3556
3538
  service: SERVICE4,
3557
3539
  agent: ctx.agent,
3558
3540
  step: state.step,
3559
- content: actor === "human" ? typedPromptText(event.prompt) : event.prompt,
3541
+ content: actor === "human" ? claudeTypedPromptText(event.prompt) : event.prompt,
3560
3542
  contentSource: event.contentSource,
3561
3543
  actor
3562
3544
  });
@@ -3577,7 +3559,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
3577
3559
  } else if (event.kind === "attachment") {
3578
3560
  backfillResult(state.toolSpanByUseId.get(event.result.toolUseId), event.timestamp, event.result);
3579
3561
  } else if (event.kind === "queued-prompt") {
3580
- const machine = claudeRecordIsMachine({ text: event.prompt });
3562
+ const machine = claudeRecordIsMachine2({ text: event.prompt });
3581
3563
  const actor = machine ? "injected" : claudeActorFromOrigin(event.originKind, () => "injected");
3582
3564
  if (event.originKind) state.sawRecordedOrigin = true;
3583
3565
  const prompt = userPromptSpan({
@@ -3588,7 +3570,7 @@ function consumeClaudeEvent(event, uid, ctx, state) {
3588
3570
  service: SERVICE4,
3589
3571
  agent: ctx.agent,
3590
3572
  step: state.step,
3591
- content: actor === "human" ? typedPromptText(event.prompt) : event.prompt,
3573
+ content: actor === "human" ? claudeTypedPromptText(event.prompt) : event.prompt,
3592
3574
  contentSource: event.contentSource,
3593
3575
  actor
3594
3576
  });
@@ -3900,10 +3882,12 @@ var ClaudeAdapter = class {
3900
3882
  let active = taskScope === "all";
3901
3883
  let offeredTools;
3902
3884
  let runResult;
3885
+ let recordedCwd;
3903
3886
  for await (const event of readJsonl(ref.path, sessionJsonlOptions(ref, options))) {
3904
3887
  options.signal?.throwIfAborted();
3905
3888
  const eventSessionId = event.sessionId ?? event.session_id;
3906
3889
  if (!discoveredTraceId && eventSessionId) discoveredTraceId = eventSessionId;
3890
+ if (!recordedCwd && typeof event.cwd === "string" && event.cwd.length > 0) recordedCwd = event.cwd;
3907
3891
  if (event.type === "system" && event.subtype === "init" && Array.isArray(event.tools)) {
3908
3892
  offeredTools = modelFacingToolNames(
3909
3893
  event.tools.filter((t) => typeof t === "string"),
@@ -3973,6 +3957,7 @@ var ClaudeAdapter = class {
3973
3957
  if (offeredTools) {
3974
3958
  root.attributes[TOOL_DEFINITIONS_ATTR] = JSON.stringify(offeredTools.map((name) => ({ type: "function", name })));
3975
3959
  }
3960
+ if (recordedCwd) root.attributes[SESSION_CWD_ATTR] = recordedCwd;
3976
3961
  if (runResult) {
3977
3962
  root.attributes[RUN_STATUS_ATTR] = runResult.failed ? "failed" : "completed";
3978
3963
  root.status = runResult.failed ? { code: "ERROR", message: `the run ended with ${runResult.subtype}` } : { code: "OK" };
@@ -4626,6 +4611,9 @@ var CODEX_TOTAL_TOKENS_SOURCE = "codex.token_count.info.total_token_usage";
4626
4611
  function reportedCount(value) {
4627
4612
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
4628
4613
  }
4614
+ function nonEmpty(value) {
4615
+ return typeof value === "string" && value.length > 0 ? value : void 0;
4616
+ }
4629
4617
  function normalizeCodexIds(spans) {
4630
4618
  for (const item of spans) {
4631
4619
  const sourceTraceId = item.trace_id;
@@ -5203,6 +5191,7 @@ var CodexAdapter = class {
5203
5191
  }
5204
5192
  }
5205
5193
  const traceId = meta?.payload?.id ?? ref.sessionId;
5194
+ const recordedCwd = nonEmpty(meta?.payload?.cwd);
5206
5195
  const scriptLaunched = meta?.payload?.originator === "codex_exec" || meta?.payload?.source === "exec";
5207
5196
  const sessionRole = parentSessionId || meta?.payload?.thread_source === "subagent" ? "child" : "operator";
5208
5197
  const rootId = `root:${traceId}`;
@@ -5221,6 +5210,7 @@ var CodexAdapter = class {
5221
5210
  // Keep the source session identity searchable after the wire id is normalized.
5222
5211
  "tangle.sessionId": traceId,
5223
5212
  "traces.session.role": sessionRole,
5213
+ ...recordedCwd ? { [SESSION_CWD_ATTR]: recordedCwd } : {},
5224
5214
  "traces.codex.task_scope": options.taskScope === "latest" ? "latest" : options.taskScope === "turn" ? "turn" : forkBoundary ? "fork-current" : "all",
5225
5215
  ...selectedBoundary?.turnId ? { "traces.codex.turn_id": selectedBoundary.turnId } : {},
5226
5216
  ...parentSessionId ? { "traces.parent_session_id": parentSessionId } : {},
@@ -5865,6 +5855,7 @@ var OpencodeAdapter = class {
5865
5855
  messages.push(await readJsonFile(join9(ref.path, f), options));
5866
5856
  }
5867
5857
  messages.sort((a, b) => (a.time?.created ?? 0) - (b.time?.created ?? 0));
5858
+ const recordedCwd = messages.map((message) => message.path?.cwd).find((cwd) => typeof cwd === "string" && cwd.length > 0);
5868
5859
  const spans = [
5869
5860
  span({
5870
5861
  traceId,
@@ -5875,7 +5866,8 @@ var OpencodeAdapter = class {
5875
5866
  startTime: isoOf(messages[0]?.time?.created),
5876
5867
  endTime: isoOf(messages.at(-1)?.time?.completed ?? messages.at(-1)?.time?.created),
5877
5868
  service: SERVICE6,
5878
- agent: SERVICE6
5869
+ agent: SERVICE6,
5870
+ ...recordedCwd ? { extra: { [SESSION_CWD_ATTR]: recordedCwd } } : {}
5879
5871
  })
5880
5872
  ];
5881
5873
  let step = 0;
@@ -5979,7 +5971,7 @@ var OpencodeAdapter = class {
5979
5971
  // src/adapters/harness-sessions.ts
5980
5972
  import { homedir as homedir8 } from "os";
5981
5973
  import { open, stat as stat8 } from "fs/promises";
5982
- import { join as join10 } from "path";
5974
+ import { basename as basename6, dirname as dirname4, join as join10 } from "path";
5983
5975
  import {
5984
5976
  claudeCodeRefForFile,
5985
5977
  codexRefForFile,
@@ -6317,6 +6309,7 @@ var HarnessSessionsAdapter = class {
6317
6309
  const root = spans[0];
6318
6310
  root.attributes["service.name"] = this.harness;
6319
6311
  root.attributes["traces.session.role"] = session.parentNativeSessionId ? "child" : "operator";
6312
+ if (session.cwd) root.attributes[SESSION_CWD_ATTR] = session.cwd;
6320
6313
  if (session.parentNativeSessionId) root.attributes["traces.parent_session_id"] = session.parentNativeSessionId;
6321
6314
  if (session.children.length > 0) {
6322
6315
  root.attributes["traces.child_session_ids"] = JSON.stringify(session.children.map((child) => child.nativeSessionId));
@@ -6344,12 +6337,16 @@ var HarnessSessionsAdapter = class {
6344
6337
  const toolByCallId = new Map(spans.filter((item) => item.attributes["openinference.span.kind"] === "TOOL").map((item) => [item.attributes["gen_ai.tool.call.id"], item]));
6345
6338
  let childSpanCount = 0;
6346
6339
  const childIds = new Set(session.children.map((child) => child.nativeSessionId));
6340
+ const readIds = /* @__PURE__ */ new Set();
6347
6341
  for (const path of childPaths) {
6348
6342
  const childRef = claudeCodeRefForFile(path, session.nativeSessionId);
6349
- const child = await this.reader.read(childRef, { signal: options.signal, corruption: options.corruptionMode });
6350
- childIds.add(child.nativeSessionId);
6343
+ const read = await this.reader.read(childRef, { signal: options.signal, corruption: options.corruptionMode });
6344
+ const repeated = readIds.has(read.nativeSessionId);
6345
+ readIds.add(read.nativeSessionId);
6346
+ const child = repeated ? { ...read, nativeSessionId: `${read.nativeSessionId}@${basename6(dirname4(path))}` } : read;
6347
+ childIds.add(read.nativeSessionId);
6351
6348
  if (!child.startedAt) continue;
6352
- const binding = session.children.find((item) => item.nativeSessionId === child.nativeSessionId);
6349
+ const binding = repeated ? void 0 : session.children.find((item) => item.nativeSessionId === child.nativeSessionId);
6353
6350
  const parent = binding?.toolCallId ? toolByCallId.get(binding.toolCallId) : void 0;
6354
6351
  const projected = toOtlpSpans(child).map(asTracesSpan);
6355
6352
  stampNativeProvenance(child, projected);
@@ -6397,7 +6394,7 @@ var SHARED_SESSION_ADAPTERS = [
6397
6394
  // src/adapters/qwen.ts
6398
6395
  import { readdir as readdir10, stat as stat9 } from "fs/promises";
6399
6396
  import { homedir as homedir9 } from "os";
6400
- import { basename as basename6, join as join11 } from "path";
6397
+ import { basename as basename7, join as join11 } from "path";
6401
6398
  var SERVICE7 = "qwen";
6402
6399
  function textOf6(parts) {
6403
6400
  if (!parts) return "";
@@ -6440,7 +6437,7 @@ var QwenAdapter = class {
6440
6437
  throw error;
6441
6438
  }
6442
6439
  if (opts.sinceMs && st.mtimeMs < opts.sinceMs) continue;
6443
- refs.push({ harness: this.harness, sessionId: basename6(f, ".jsonl"), path, cwd: null, mtimeMs: st.mtimeMs });
6440
+ refs.push({ harness: this.harness, sessionId: basename7(f, ".jsonl"), path, cwd: null, mtimeMs: st.mtimeMs });
6444
6441
  }
6445
6442
  }
6446
6443
  return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
@@ -6595,7 +6592,7 @@ function selectAdapters(opts) {
6595
6592
 
6596
6593
  // src/repo.ts
6597
6594
  import { realpath as realpath2, stat as stat10 } from "fs/promises";
6598
- import { dirname as dirname4, isAbsolute as isAbsolute3, join as join12, normalize, parse as parsePath } from "path";
6595
+ import { dirname as dirname5, isAbsolute as isAbsolute3, join as join12, normalize, parse as parsePath } from "path";
6599
6596
  function normalizeRemote(url) {
6600
6597
  const raw = url.trim();
6601
6598
  if (!raw) return null;
@@ -6727,8 +6724,8 @@ async function nearestExistingDir(path) {
6727
6724
  for (; ; ) {
6728
6725
  const s = await pathStat(cur);
6729
6726
  if (s?.isDirectory()) return cur;
6730
- if (s?.isFile()) return dirname4(cur);
6731
- const parent = dirname4(cur);
6727
+ if (s?.isFile()) return dirname5(cur);
6728
+ const parent = dirname5(cur);
6732
6729
  if (parent === cur) return null;
6733
6730
  cur = parent;
6734
6731
  }
@@ -6737,7 +6734,7 @@ async function findGitRoot(path) {
6737
6734
  let cur = await nearestExistingDir(path);
6738
6735
  while (cur) {
6739
6736
  if (await pathStat(join12(cur, ".git"))) return cur;
6740
- const parent = dirname4(cur);
6737
+ const parent = dirname5(cur);
6741
6738
  if (parent === cur) return null;
6742
6739
  cur = parent;
6743
6740
  }
@@ -6848,7 +6845,7 @@ function extractAbsolutePaths(spans) {
6848
6845
  async function resolveRepoAttrs(cwd) {
6849
6846
  if (!cwd) return {};
6850
6847
  const attrs = { [ATTR.CWD]: cwd };
6851
- const basename15 = (() => {
6848
+ const basename16 = (() => {
6852
6849
  const trimmed = cwd.replace(/\/+$/g, "");
6853
6850
  const idx = trimmed.lastIndexOf("/");
6854
6851
  const name = idx === -1 ? trimmed : trimmed.slice(idx + 1);
@@ -6870,7 +6867,7 @@ async function resolveRepoAttrs(cwd) {
6870
6867
  if (commit) attrs[ATTR.GIT_COMMIT] = commit;
6871
6868
  } catch {
6872
6869
  }
6873
- attrs[ATTR.SUBJECT_KEY] = basename15;
6870
+ attrs[ATTR.SUBJECT_KEY] = basename16;
6874
6871
  return attrs;
6875
6872
  }
6876
6873
  async function repoCandidateForPath(path) {
@@ -8074,7 +8071,7 @@ var COMMAND_PREFIXES = /* @__PURE__ */ new Set([
8074
8071
  var ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
8075
8072
  var MAX_SHELL_DEPTH = 4;
8076
8073
  var MAX_WORDS = 2e4;
8077
- function basename7(word) {
8074
+ function basename8(word) {
8078
8075
  const cut = word.lastIndexOf("/");
8079
8076
  return cut === -1 ? word : word.slice(cut + 1);
8080
8077
  }
@@ -8173,7 +8170,7 @@ var ShellScanner = class {
8173
8170
  let start = 0;
8174
8171
  while (start < words.length) {
8175
8172
  const word = words[start];
8176
- if (ASSIGNMENT.test(word) || COMMAND_PREFIXES.has(basename7(word))) start += 1;
8173
+ if (ASSIGNMENT.test(word) || COMMAND_PREFIXES.has(basename8(word))) start += 1;
8177
8174
  else break;
8178
8175
  }
8179
8176
  return start === 0 || start >= words.length ? words : words.slice(start);
@@ -8314,7 +8311,7 @@ function shellCommands(script, depth = 0) {
8314
8311
  const out = [];
8315
8312
  for (const words of commands) {
8316
8313
  out.push(words);
8317
- const name = basename7(words[0] ?? "");
8314
+ const name = basename8(words[0] ?? "");
8318
8315
  if (!SHELLS.has(name) || depth >= MAX_SHELL_DEPTH) continue;
8319
8316
  for (let index = 1; index < words.length; index += 1) {
8320
8317
  const word = words[index];
@@ -8342,7 +8339,7 @@ function commandTextFromInput(input) {
8342
8339
  if (!Array.isArray(value) || value.length === 0) return void 0;
8343
8340
  const words = value.filter((entry) => typeof entry === "string");
8344
8341
  if (words.length !== value.length) return void 0;
8345
- const head = basename7(words[0] ?? "");
8342
+ const head = basename8(words[0] ?? "");
8346
8343
  if (SHELLS.has(head)) {
8347
8344
  const flag = words.findIndex((word, position) => position > 0 && /^-[a-z]*c$/.test(word));
8348
8345
  if (flag !== -1 && words[flag + 1] !== void 0) return words[flag + 1];
@@ -8404,7 +8401,7 @@ function stringAttribute2(span2, key) {
8404
8401
  const value = span2.attributes[key];
8405
8402
  return typeof value === "string" ? value : null;
8406
8403
  }
8407
- function basename8(word) {
8404
+ function basename9(word) {
8408
8405
  const cut = word.lastIndexOf("/");
8409
8406
  return cut === -1 ? word : word.slice(cut + 1);
8410
8407
  }
@@ -8487,7 +8484,7 @@ function numbersInLine(line) {
8487
8484
  function inferHeadBranch(commands, upTo, output) {
8488
8485
  for (let index = upTo - 1; index >= 0; index -= 1) {
8489
8486
  const words = commands[index];
8490
- if (basename8(words[0] ?? "") !== "git" || words.length < 2) continue;
8487
+ if (basename9(words[0] ?? "") !== "git" || words.length < 2) continue;
8491
8488
  const sub = words[1];
8492
8489
  const rest = words.slice(2);
8493
8490
  if (sub === "push") {
@@ -8562,7 +8559,7 @@ function eventsFromRecord(record2, recordIndex) {
8562
8559
  let createIndex = 0;
8563
8560
  for (let index = 0; index < commands.length; index += 1) {
8564
8561
  const words = commands[index];
8565
- if (words.length < 3 || !GH_NAMES.has(basename8(words[0])) || words[1] !== "pr") continue;
8562
+ if (words.length < 3 || !GH_NAMES.has(basename9(words[0])) || words[1] !== "pr") continue;
8566
8563
  const action = words[2];
8567
8564
  if (action === "create") {
8568
8565
  const printedNumber = printed[createIndex] ?? null;
@@ -8811,12 +8808,13 @@ function computeSessionMeasurements(spans, tools, sessionId, harness) {
8811
8808
  // src/session-facts.ts
8812
8809
  import { OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND4, TOOL_NAME as TOOL_NAME5 } from "@tangle-network/agent-eval/trace-attributes";
8813
8810
  import { ingestSpans, rankFirstFailure } from "@tangle-network/agent-eval/diagnosis";
8814
- var SESSION_FACTS_MEASUREMENT_VERSION = "3";
8811
+ var SESSION_FACTS_MEASUREMENT_VERSION = "4";
8815
8812
  var SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
8816
8813
  var SPAN_SYNTHESIZED_ATTR = "traces.codex.span_synthesized";
8817
8814
  var CORRUPTION_COUNT_ATTR = "traces.session.corruption_count";
8818
8815
  var CORRUPTION_RECEIPT_VERSION_ATTR = "traces.session.corruption.receipt_version";
8819
8816
  var FACT_TEXT_CAP = 2e3;
8817
+ var ANSWERED_TEXT_CAP = 1500;
8820
8818
  var FACT_LIST_CAP = 200;
8821
8819
  var CHANGED_FILE_LIST_CAP = 5e3;
8822
8820
  function attr(span2, key) {
@@ -9046,47 +9044,105 @@ function turnText(span2) {
9046
9044
  function normalizedTurnText(span2) {
9047
9045
  return turnText(span2).replace(/\s+/g, " ").trim();
9048
9046
  }
9047
+ var CAPTURE_CUT_MARKER = /… \[\+(\d+) chars\]$/;
9048
+ function clipAnswered(raw) {
9049
+ const text2 = raw.trim();
9050
+ const marker = CAPTURE_CUT_MARKER.exec(text2);
9051
+ const body = marker ? text2.slice(0, marker.index) : text2;
9052
+ if (body.length <= ANSWERED_TEXT_CAP) return text2;
9053
+ const lastKept = body.charCodeAt(ANSWERED_TEXT_CAP - 1);
9054
+ const keep = lastKept >= 55296 && lastKept <= 56319 ? ANSWERED_TEXT_CAP - 1 : ANSWERED_TEXT_CAP;
9055
+ const hidden = body.length - keep + (marker ? Number(marker[1]) : 0);
9056
+ return `${body.slice(0, keep)}\u2026 [+${hidden} chars]`;
9057
+ }
9058
+ function isOwnAgentMessage(span2) {
9059
+ return span2.name === "message.assistant" && span2.attributes[INHERITED_SPAN_ATTR] !== true && typeof span2.attributes.content === "string" && span2.attributes.content.trim().length > 0;
9060
+ }
9049
9061
  function humanTurnsOf(spans) {
9050
9062
  const excluded = /* @__PURE__ */ new Map();
9051
- const drop = (reason, spanId) => {
9052
- excluded.set(reason, [...excluded.get(reason) ?? [], spanId]);
9063
+ const drop = (reason, span2) => {
9064
+ excluded.set(reason, [...excluded.get(reason) ?? [], { spanId: span2.span_id, at: span2.start_time }]);
9053
9065
  };
9054
9066
  const kept = [];
9067
+ const turns = [];
9055
9068
  let sinceKept = [];
9069
+ let lastAgentMessage = null;
9056
9070
  for (const span2 of spans) {
9057
9071
  if (isSubagentSpan(span2.attributes)) {
9058
- if (span2.name === "user.prompt") drop(SUBAGENT_TURN_REASON, span2.span_id);
9072
+ if (span2.name === "user.prompt") drop(SUBAGENT_TURN_REASON, span2);
9059
9073
  continue;
9060
9074
  }
9061
9075
  if (span2.name !== "user.prompt") {
9076
+ if (isOwnAgentMessage(span2)) lastAgentMessage = span2;
9062
9077
  sinceKept.push(span2);
9063
9078
  continue;
9064
9079
  }
9065
9080
  if (span2.attributes[INHERITED_SPAN_ATTR] === true) {
9066
9081
  const source = stringAttr2(span2, INHERITED_SOURCE_ATTR);
9067
- drop(source === null ? INHERITED_TURN_REASON : `${INHERITED_TURN_REASON} (${source})`, span2.span_id);
9082
+ drop(source === null ? INHERITED_TURN_REASON : `${INHERITED_TURN_REASON} (${source})`, span2);
9068
9083
  continue;
9069
9084
  }
9070
9085
  const actor = stringAttr2(span2, ACTOR_ATTR) ?? "unclassified";
9071
9086
  if (actor !== "human") {
9072
- drop(TURN_EXCLUSION_REASONS[actor] ?? `the adapter classified this turn as "${actor}", not as a person typing`, span2.span_id);
9087
+ drop(TURN_EXCLUSION_REASONS[actor] ?? `the adapter classified this turn as "${actor}", not as a person typing`, span2);
9073
9088
  continue;
9074
9089
  }
9075
9090
  const previous = kept[kept.length - 1];
9076
9091
  const duplicate = previous !== void 0 && previous.start_time === span2.start_time && normalizedTurnText(previous) === normalizedTurnText(span2) && normalizedTurnText(span2).length > 0 && !sinceKept.some(isAgentActivity);
9077
9092
  if (duplicate) {
9078
- drop(DUPLICATE_TURN_REASON, span2.span_id);
9093
+ drop(DUPLICATE_TURN_REASON, span2);
9079
9094
  continue;
9080
9095
  }
9081
9096
  kept.push(span2);
9097
+ turns.push({
9098
+ actor: "human",
9099
+ at: span2.start_time,
9100
+ text: turnText(span2),
9101
+ spanId: span2.span_id,
9102
+ answered: lastAgentMessage ? clipAnswered(lastAgentMessage.attributes.content) : null,
9103
+ answeredSpanId: lastAgentMessage?.span_id ?? null
9104
+ });
9082
9105
  sinceKept = [];
9083
9106
  }
9107
+ return { turns, excluded };
9108
+ }
9109
+ function excludedTurnFacts(excluded, keep) {
9110
+ return [...excluded].map(([reason, entries]) => [reason, entries.filter((entry) => keep(entry.at)).map((entry) => entry.spanId)]).filter(([, spanIds]) => spanIds.length > 0).sort(([left], [right]) => left.localeCompare(right)).map(([reason, spanIds]) => ({ reason, turns: spanIds.length, spanIds }));
9111
+ }
9112
+ function windowBounds(window) {
9113
+ if (!window || window.since === void 0 && window.until === void 0) return null;
9114
+ const bound = (value, name, open4) => {
9115
+ if (value === void 0) return open4;
9116
+ const ms2 = Date.parse(value);
9117
+ if (!Number.isFinite(ms2)) throw new RangeError(`window ${name} is not a readable time: ${value}`);
9118
+ return ms2;
9119
+ };
9120
+ const sinceMs = bound(window.since, "since", Number.NEGATIVE_INFINITY);
9121
+ const untilMs = bound(window.until, "until", Number.POSITIVE_INFINITY);
9122
+ if (!(sinceMs < untilMs)) throw new RangeError(`window since (${window.since}) must be before until (${window.until})`);
9084
9123
  return {
9085
- turns: kept.map((span2) => ({ actor: "human", at: span2.start_time, text: turnText(span2), spanId: span2.span_id })),
9086
- excluded: [...excluded].sort(([left], [right]) => left.localeCompare(right)).map(([reason, spanIds]) => ({ reason, turns: spanIds.length, spanIds }))
9124
+ since: window.since === void 0 ? null : new Date(sinceMs).toISOString(),
9125
+ until: window.until === void 0 ? null : new Date(untilMs).toISOString(),
9126
+ sinceMs,
9127
+ untilMs
9087
9128
  };
9088
9129
  }
9130
+ function insideWindow(bounds, time) {
9131
+ if (bounds === null) return true;
9132
+ const ms2 = Date.parse(time);
9133
+ return Number.isFinite(ms2) && ms2 >= bounds.sinceMs && ms2 < bounds.untilMs;
9134
+ }
9135
+ function isRecordSpan(span2) {
9136
+ return span2.parent_span_id !== null && span2.attributes[CORRUPTION_RECEIPT_VERSION_ATTR] === void 0;
9137
+ }
9089
9138
  function computeSessionFacts(spans, options = {}) {
9139
+ const bounds = windowBounds(options.window);
9140
+ return groupByTrace(spans).sort((left, right) => compareIds(left.traceId, right.traceId)).map((group) => sessionFactsForTrace(group.traceId, group.sessionId, group.spans, options, bounds));
9141
+ }
9142
+ function compareIds(left, right) {
9143
+ return left < right ? -1 : left > right ? 1 : 0;
9144
+ }
9145
+ function groupByTrace(spans) {
9090
9146
  const { sessionByTrace } = indexSessionIdsByTrace(spans);
9091
9147
  const byTrace = /* @__PURE__ */ new Map();
9092
9148
  for (const span2 of spans) {
@@ -9094,9 +9150,13 @@ function computeSessionFacts(spans, options = {}) {
9094
9150
  if (group) group.push(span2);
9095
9151
  else byTrace.set(span2.trace_id, [span2]);
9096
9152
  }
9097
- return [...byTrace].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([traceId, traceSpans]) => sessionFactsForTrace(traceId, sessionByTrace.get(traceId) ?? null, traceSpans, options));
9153
+ return [...byTrace].map(([traceId, traceSpans]) => ({
9154
+ traceId,
9155
+ sessionId: sessionByTrace.get(traceId) ?? null,
9156
+ spans: traceSpans
9157
+ }));
9098
9158
  }
9099
- function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9159
+ function sessionFactsForTrace(traceId, sessionId, unordered, options, bounds) {
9100
9160
  const spans = [...unordered].sort(byTraceOrder);
9101
9161
  const harness = spans.map((span2) => stringAttr2(span2, "service.name")).find((name) => name !== null) ?? null;
9102
9162
  const ownSpans = spans.filter((span2) => !isSubagentSpan(span2.attributes));
@@ -9110,8 +9170,11 @@ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9110
9170
  for (const span2 of invoked) byName[toolName3(span2)] = (byName[toolName3(span2)] ?? 0) + 1;
9111
9171
  const subagents = subagentsOf(ownSpans, spans);
9112
9172
  const pullRequests = readPullRequests(ownSpans);
9113
- const promptSpans = spans.filter((span2) => span2.name === "user.prompt");
9114
- const { turns: humanTurns, excluded: excludedTurns } = humanTurnsOf(spans);
9173
+ const inWindow = (at) => insideWindow(bounds, at);
9174
+ const promptSpans = spans.filter((span2) => span2.name === "user.prompt" && inWindow(span2.start_time));
9175
+ const turnFacts = humanTurnsOf(spans);
9176
+ const humanTurns = turnFacts.turns.filter((turn) => inWindow(turn.at));
9177
+ const excludedTurns = excludedTurnFacts(turnFacts.excluded, inWindow);
9115
9178
  const actorCounts = /* @__PURE__ */ new Map();
9116
9179
  for (const span2 of promptSpans) {
9117
9180
  const actor = stringAttr2(span2, ACTOR_ATTR) ?? "unclassified";
@@ -9131,16 +9194,20 @@ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9131
9194
  );
9132
9195
  const tokenSpan = spans.find((span2) => typeof span2.attributes[SESSION_TOKEN_TOTAL_ATTR] === "number");
9133
9196
  const receipts = spans.filter((span2) => span2.attributes[CORRUPTION_RECEIPT_VERSION_ATTR] !== void 0);
9134
- const recordSpans = spans.filter(
9135
- (span2) => span2.parent_span_id !== null && span2.attributes[CORRUPTION_RECEIPT_VERSION_ATTR] === void 0
9136
- ).length;
9197
+ const recordSpans = spans.filter(isRecordSpan).length;
9137
9198
  const corruptionSpan = spans.find((span2) => typeof span2.attributes[CORRUPTION_COUNT_ATTR] === "number");
9199
+ const cwdSpan = spans.find((span2) => stringAttr2(span2, SESSION_CWD_ATTR) !== null);
9138
9200
  const facts = {
9139
9201
  schemaVersion: 1,
9140
9202
  kind: "traces.session_facts",
9141
9203
  traceId,
9142
9204
  sessionId,
9143
9205
  harness,
9206
+ cwd: cwdSpan ? { value: stringAttr2(cwdSpan, SESSION_CWD_ATTR), spanIds: [cwdSpan.span_id], unavailable: null } : {
9207
+ value: null,
9208
+ spanIds: [],
9209
+ unavailable: `no span carries ${SESSION_CWD_ATTR}: the session source records no working directory, or the adapter that read it does not read one`
9210
+ },
9144
9211
  spanCount: spans.length,
9145
9212
  recordSpans,
9146
9213
  unreadRecords: corruptionSpan ? {
@@ -9213,6 +9280,7 @@ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9213
9280
  });
9214
9281
  return {
9215
9282
  ...facts,
9283
+ cwd: withheld(),
9216
9284
  humanTurns: withheld(),
9217
9285
  finalMessages: withheld(),
9218
9286
  changedFiles: withheld(),
@@ -9229,26 +9297,88 @@ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9229
9297
  };
9230
9298
  }
9231
9299
  function buildSessionFactsReport(spans, options = {}) {
9232
- const digest = createHash7("sha256");
9233
- const ordered2 = [...spans].sort((a, b) => a.trace_id.localeCompare(b.trace_id) || a.span_id.localeCompare(b.span_id));
9300
+ const builder = createSessionFactsReportBuilder(options);
9301
+ builder.add(spans);
9302
+ return builder.build();
9303
+ }
9304
+ var WINDOW_SESSION_RULE = "a session is selected when at least one of its records starts or ends inside [since, until)";
9305
+ var WINDOW_TURN_RULE = "humanTurns, excludedTurns and turnsByActor keep only user.prompt turns that started inside [since, until); every other fact, and each turn's answered text, covers the whole session";
9306
+ function createSessionFactsReportBuilder(options = {}) {
9307
+ const bounds = windowBounds(options.window);
9308
+ const sessions = [];
9309
+ const digests = [];
9310
+ const outsideWindow = [];
9311
+ const unreadSessions = [];
9312
+ let spanCount = 0;
9234
9313
  let evidenceThrough = null;
9235
- for (const span2 of ordered2) {
9236
- digest.update(canonicalJson(span2)).update("\n");
9237
- for (const time of [span2.start_time, span2.end_time]) {
9238
- if (Number.isFinite(Date.parse(time)) && (evidenceThrough === null || Date.parse(time) > Date.parse(evidenceThrough))) evidenceThrough = time;
9314
+ const select = (group) => {
9315
+ const digest = createHash7("sha256");
9316
+ for (const span2 of [...group.spans].sort((a, b) => a.span_id.localeCompare(b.span_id))) {
9317
+ digest.update(canonicalJson(span2)).update("\n");
9318
+ for (const time of [span2.start_time, span2.end_time]) {
9319
+ if (Number.isFinite(Date.parse(time)) && (evidenceThrough === null || Date.parse(time) > Date.parse(evidenceThrough))) evidenceThrough = time;
9320
+ }
9239
9321
  }
9240
- }
9322
+ digests.push({ traceId: group.traceId, digest: digest.digest("hex") });
9323
+ spanCount += group.spans.length;
9324
+ sessions.push(sessionFactsForTrace(group.traceId, group.sessionId, group.spans, options, bounds));
9325
+ };
9241
9326
  return {
9242
- schemaVersion: 1,
9243
- kind: "traces.session_facts_report",
9244
- measurementVersion: SESSION_FACTS_MEASUREMENT_VERSION,
9245
- sourceDigest: "sha256:" + digest.digest("hex"),
9246
- evidenceThrough,
9247
- contentIncluded: options.includeContent !== false,
9248
- generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
9249
- harness: options.harness ?? null,
9250
- spanCount: spans.length,
9251
- sessions: computeSessionFacts(spans, options)
9327
+ add(spans) {
9328
+ for (const group of groupByTrace(spans)) {
9329
+ if (bounds === null) {
9330
+ select(group);
9331
+ continue;
9332
+ }
9333
+ const records = group.spans.filter(isRecordSpan);
9334
+ if (records.length === 0) {
9335
+ const unread = group.spans.find((span2) => typeof span2.attributes[CORRUPTION_COUNT_ATTR] === "number");
9336
+ unreadSessions.push({
9337
+ sessionId: group.sessionId,
9338
+ traceId: group.traceId,
9339
+ source: null,
9340
+ reason: `no record could be read (${unread ? `${unread.attributes[CORRUPTION_COUNT_ATTR]} unread record(s)` : "no record spans"}), so whether the session holds a record inside the window is unknown`
9341
+ });
9342
+ continue;
9343
+ }
9344
+ if (records.some((span2) => insideWindow(bounds, span2.start_time) || insideWindow(bounds, span2.end_time))) select(group);
9345
+ else outsideWindow.push(group.sessionId ?? group.traceId);
9346
+ }
9347
+ },
9348
+ addUnread(session) {
9349
+ if (bounds === null) {
9350
+ throw new Error("an unread session is reported only by a windowed report; without a window, refuse the selection");
9351
+ }
9352
+ unreadSessions.push(session);
9353
+ },
9354
+ build() {
9355
+ const digest = createHash7("sha256");
9356
+ for (const entry of [...digests].sort((a, b) => a.traceId.localeCompare(b.traceId) || a.digest.localeCompare(b.digest))) {
9357
+ digest.update(`${entry.traceId}
9358
+ ${entry.digest}
9359
+ `);
9360
+ }
9361
+ return {
9362
+ schemaVersion: 1,
9363
+ kind: "traces.session_facts_report",
9364
+ measurementVersion: SESSION_FACTS_MEASUREMENT_VERSION,
9365
+ sourceDigest: "sha256:" + digest.digest("hex"),
9366
+ evidenceThrough,
9367
+ contentIncluded: options.includeContent !== false,
9368
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
9369
+ harness: options.harness ?? null,
9370
+ spanCount,
9371
+ selection: bounds === null ? null : {
9372
+ since: bounds.since,
9373
+ until: bounds.until,
9374
+ sessionRule: WINDOW_SESSION_RULE,
9375
+ turnRule: WINDOW_TURN_RULE,
9376
+ outsideWindow: [...outsideWindow].sort(compareIds),
9377
+ unreadSessions: [...unreadSessions]
9378
+ },
9379
+ sessions: [...sessions].sort((left, right) => compareIds(left.traceId, right.traceId))
9380
+ };
9381
+ }
9252
9382
  };
9253
9383
  }
9254
9384
  function factLine(label2, fact, rendered) {
@@ -9260,8 +9390,18 @@ function renderSessionFacts(report) {
9260
9390
  const lines = [
9261
9391
  `session facts \u2014 ${report.sessions.length} session(s), ${report.spanCount} span(s), deterministic, $0`
9262
9392
  ];
9393
+ const selection = report.selection;
9394
+ if (selection) {
9395
+ lines.push(
9396
+ `window [${selection.since ?? "open"}, ${selection.until ?? "open"}): turn facts count only turns inside it; ${selection.outsideWindow.length} session(s) read with no record inside it`
9397
+ );
9398
+ for (const unread of selection.unreadSessions) {
9399
+ lines.push(` unread session ${unread.sessionId ?? unread.traceId ?? unread.source ?? "unknown"}: ${unread.reason}`);
9400
+ }
9401
+ }
9263
9402
  for (const facts of report.sessions) {
9264
9403
  lines.push("", `${facts.sessionId ?? facts.traceId} [${facts.harness ?? "unknown harness"}] ${facts.spanCount} spans`);
9404
+ lines.push(factLine("cwd", facts.cwd, String(facts.cwd.value)));
9265
9405
  lines.push(
9266
9406
  factLine(
9267
9407
  "tool calls",
@@ -9329,6 +9469,9 @@ function renderSessionFactsContext(report, options = {}) {
9329
9469
  ${JSON.stringify({ sessions, ...dropped2.length ? { omitted_fields: dropped2 } : {} })}`;
9330
9470
  const dropped = [];
9331
9471
  const sheds = [
9472
+ ["human_turn_answered", (facts) => {
9473
+ for (const turn of facts.human_turns) delete turn.answered;
9474
+ }],
9332
9475
  ["human_turn_text", (facts) => {
9333
9476
  for (const turn of facts.human_turns) delete turn.text;
9334
9477
  }],
@@ -9419,6 +9562,7 @@ function compactFacts(facts) {
9419
9562
  return {
9420
9563
  session_id: facts.sessionId,
9421
9564
  trace_id: facts.traceId,
9565
+ cwd: factJson(facts.cwd),
9422
9566
  spans: facts.spanCount,
9423
9567
  tool_calls: {
9424
9568
  count: facts.toolCalls.value,
@@ -9441,7 +9585,8 @@ function compactFacts(facts) {
9441
9585
  human_turns: (facts.humanTurns.value ?? []).map((turn) => ({
9442
9586
  at: turn.at,
9443
9587
  span_id: turn.spanId,
9444
- text: turn.text
9588
+ text: turn.text,
9589
+ answered: turn.answered
9445
9590
  })),
9446
9591
  excluded_turns: facts.excludedTurns.value?.map((entry) => ({
9447
9592
  reason: entry.reason,
@@ -9531,17 +9676,17 @@ import { isUtf8 as isUtf82 } from "buffer";
9531
9676
  import { createHash as createHash10 } from "crypto";
9532
9677
  import { constants } from "fs";
9533
9678
  import { lstat as lstat2, open as open2, realpath as realpath3 } from "fs/promises";
9534
- import { dirname as dirname8, isAbsolute as isAbsolute4, join as join18, relative as relative5, resolve as resolve6, sep as sep2 } from "path";
9679
+ import { dirname as dirname9, isAbsolute as isAbsolute4, join as join18, relative as relative5, resolve as resolve6, sep as sep2 } from "path";
9535
9680
 
9536
9681
  // src/bundle.ts
9537
9682
  import { createHash as createHash9 } from "crypto";
9538
9683
  import { copyFile, cp, lstat, mkdir as mkdir2, readdir as readdir13, readFile as readFile7, stat as stat12, writeFile as writeFile5 } from "fs/promises";
9539
- import { dirname as dirname7, join as join17, relative as relative4, resolve as resolve5, sep } from "path";
9684
+ import { dirname as dirname8, join as join17, relative as relative4, resolve as resolve5, sep } from "path";
9540
9685
 
9541
9686
  // src/improvement.ts
9542
9687
  import { access, mkdir, mkdtemp as mkdtemp2, writeFile as writeFile3 } from "fs/promises";
9543
9688
  import { tmpdir as tmpdir3 } from "os";
9544
- import { dirname as dirname5, join as join15, resolve as resolve4 } from "path";
9689
+ import { dirname as dirname6, join as join15, resolve as resolve4 } from "path";
9545
9690
  import { pathToFileURL } from "url";
9546
9691
  import {
9547
9692
  AnalystRegistry,
@@ -11870,14 +12015,14 @@ async function runTraceImprovement(opts) {
11870
12015
  };
11871
12016
  }
11872
12017
  async function saveReport(path, report) {
11873
- await mkdir(dirname5(resolve4(path)), { recursive: true });
12018
+ await mkdir(dirname6(resolve4(path)), { recursive: true });
11874
12019
  await writeFile3(path, report, "utf8");
11875
12020
  }
11876
12021
 
11877
12022
  // src/session-index.ts
11878
12023
  import { mkdtemp as mkdtemp3, readdir as readdir12, readFile as readFile6, stat as stat11, writeFile as writeFile4 } from "fs/promises";
11879
12024
  import { tmpdir as tmpdir4 } from "os";
11880
- import { dirname as dirname6, join as join16 } from "path";
12025
+ import { dirname as dirname7, join as join16 } from "path";
11881
12026
  function indexRow(record2) {
11882
12027
  return {
11883
12028
  session: record2.session,
@@ -11948,13 +12093,13 @@ async function findContextRoot(cwd) {
11948
12093
  let current = cwd;
11949
12094
  const s = await pathStat2(current);
11950
12095
  if (!s) return null;
11951
- if (!s.isDirectory()) current = dirname6(current);
12096
+ if (!s.isDirectory()) current = dirname7(current);
11952
12097
  for (; ; ) {
11953
12098
  if (await pathStat2(join16(current, ".git")) || await dirExists(join16(current, ".evolve")) || await fileExists(join16(current, "AGENTS.md")) || await fileExists(join16(current, "CLAUDE.md"))) {
11954
12099
  return current;
11955
12100
  }
11956
- const parent = dirname6(current);
11957
- if (parent === current) return s.isDirectory() ? cwd : dirname6(cwd);
12101
+ const parent = dirname7(current);
12102
+ if (parent === current) return s.isDirectory() ? cwd : dirname7(cwd);
11958
12103
  current = parent;
11959
12104
  }
11960
12105
  }
@@ -12788,7 +12933,7 @@ async function assembleSessionBundle(opts) {
12788
12933
  const subagentRelative = relative4(subagentsSource, file.path);
12789
12934
  const path = resolve5(file.path) === resolve5(ref.path) ? bundlePath("session", "transcript.jsonl") : !subagentRelative.startsWith("..") && !subagentRelative.startsWith(sep) ? bundlePath("session", "subagents", subagentRelative) : bundlePath("session", "sources", `${sourceId}.jsonl`);
12790
12935
  const target = join17(outDir, path);
12791
- await mkdir2(dirname7(target), { recursive: true });
12936
+ await mkdir2(dirname8(target), { recursive: true });
12792
12937
  await copyFile(file.path, target);
12793
12938
  if (await fileSha256(target, opts.signal) !== file.sha256) {
12794
12939
  throw new Error(`session source changed while retaining bundle evidence: ${file.path}`);
@@ -13232,7 +13377,7 @@ async function assertOutsideSourceBundle(directory, outputPath) {
13232
13377
  break;
13233
13378
  } catch (error) {
13234
13379
  if (error.code !== "ENOENT") throw error;
13235
- const parent = dirname8(ancestor);
13380
+ const parent = dirname9(ancestor);
13236
13381
  if (parent === ancestor) throw error;
13237
13382
  ancestor = parent;
13238
13383
  }
@@ -13498,7 +13643,7 @@ import {
13498
13643
  readFile as readFile10,
13499
13644
  rm as rm2
13500
13645
  } from "fs/promises";
13501
- import { basename as basename10, dirname as dirname10, join as join21, resolve as resolve8 } from "path";
13646
+ import { basename as basename11, dirname as dirname11, join as join21, resolve as resolve8 } from "path";
13502
13647
 
13503
13648
  // src/codetracebench-files.ts
13504
13649
  import { createHash as createHash12, randomUUID } from "crypto";
@@ -13516,13 +13661,13 @@ import {
13516
13661
  writeFile as writeFile6
13517
13662
  } from "fs/promises";
13518
13663
  import { hostname } from "os";
13519
- import { basename as basename9, dirname as dirname9, isAbsolute as isAbsolute5, join as join19, relative as relative6, resolve as resolve7, sep as sep3 } from "path";
13664
+ import { basename as basename10, dirname as dirname10, isAbsolute as isAbsolute5, join as join19, relative as relative6, resolve as resolve7, sep as sep3 } from "path";
13520
13665
  async function claimImportLock(input) {
13521
13666
  const claimId = randomUUID();
13522
13667
  const path = `${input.outputDirectory}.lock`;
13523
13668
  const temporary = join19(
13524
- dirname9(path),
13525
- `.${basename9(path)}.${claimId}.tmp`
13669
+ dirname10(path),
13670
+ `.${basename10(path)}.${claimId}.tmp`
13526
13671
  );
13527
13672
  const metadata = {
13528
13673
  kind: "traces.codetracebench-import-lock",
@@ -13683,8 +13828,8 @@ async function assertPathMissing(path) {
13683
13828
  async function atomicWriteFile(path, content, signal) {
13684
13829
  signal.throwIfAborted();
13685
13830
  const temporary = join19(
13686
- dirname9(path),
13687
- `.${basename9(path)}.${randomUUID()}.tmp`
13831
+ dirname10(path),
13832
+ `.${basename10(path)}.${randomUUID()}.tmp`
13688
13833
  );
13689
13834
  try {
13690
13835
  await writeFile6(temporary, content, {
@@ -14017,12 +14162,12 @@ async function importCodeTraceBench(options) {
14017
14162
  const rowsPath = resolve8(options.rowsPath);
14018
14163
  const trajectoryPath = resolve8(options.trajectoryDir);
14019
14164
  const outputDirectory = resolve8(options.outDir);
14020
- const outputBase = basename10(outputDirectory);
14021
- if (!outputBase || outputDirectory === dirname10(outputDirectory)) {
14165
+ const outputBase = basename11(outputDirectory);
14166
+ if (!outputBase || outputDirectory === dirname11(outputDirectory)) {
14022
14167
  throw new Error("CodeTraceBench output directory must not be a filesystem root");
14023
14168
  }
14024
14169
  options.signal?.throwIfAborted();
14025
- await mkdir4(dirname10(outputDirectory), { recursive: true });
14170
+ await mkdir4(dirname11(outputDirectory), { recursive: true });
14026
14171
  const lock = await claimImportLock({
14027
14172
  rowsPath,
14028
14173
  trajectoryDirectory: trajectoryPath,
@@ -14061,7 +14206,7 @@ async function runCodeTraceBenchImport(input) {
14061
14206
  const rows = parseRows(rowsBytes.toString("utf8"), rowsPath);
14062
14207
  const sourceRows = validateRows(rows, rowsPath);
14063
14208
  const stagingDirectory = await mkdtemp4(
14064
- join21(dirname10(outputDirectory), `.${outputBase}.tmp-`)
14209
+ join21(dirname11(outputDirectory), `.${outputBase}.tmp-`)
14065
14210
  );
14066
14211
  const stop = new AbortController();
14067
14212
  const signal = outerSignal ? AbortSignal.any([outerSignal, stop.signal]) : stop.signal;
@@ -14121,10 +14266,10 @@ async function runCodeTraceBenchImport(input) {
14121
14266
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14122
14267
  input: {
14123
14268
  revision,
14124
- rowsFile: basename10(rowsPath),
14269
+ rowsFile: basename11(rowsPath),
14125
14270
  rowsSha256,
14126
14271
  rowsBytes: rowsBytes.byteLength,
14127
- trajectoryDirectory: basename10(trajectoryDirectory),
14272
+ trajectoryDirectory: basename11(trajectoryDirectory),
14128
14273
  sha256: inputSha256
14129
14274
  },
14130
14275
  settings: {
@@ -16630,7 +16775,7 @@ async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
16630
16775
  // src/inspect.ts
16631
16776
  import { mkdtemp as mkdtemp7, readFile as readFile13, writeFile as writeFile9 } from "fs/promises";
16632
16777
  import { tmpdir as tmpdir7 } from "os";
16633
- import { basename as basename11, join as join25 } from "path";
16778
+ import { basename as basename12, join as join25 } from "path";
16634
16779
  var severityRank2 = {
16635
16780
  high: 0,
16636
16781
  medium: 1,
@@ -16813,7 +16958,7 @@ function addContextFindings(index, findings) {
16813
16958
  refs: invalidJsonlFiles.slice(0, 5).map((file) => file.path)
16814
16959
  });
16815
16960
  }
16816
- const skillRunFiles = files.filter((file) => basename11(file.path) === "skill-runs.jsonl" && (file.jsonl?.rows ?? 0) > 0).map((file) => {
16961
+ const skillRunFiles = files.filter((file) => basename12(file.path) === "skill-runs.jsonl" && (file.jsonl?.rows ?? 0) > 0).map((file) => {
16817
16962
  const rows = file.jsonl?.rows ?? 0;
16818
16963
  const keys = file.jsonl?.keys ?? {};
16819
16964
  const linkKeyCounts = {
@@ -17353,7 +17498,7 @@ async function streamSessions(opts) {
17353
17498
 
17354
17499
  // src/supervisor-run-context.ts
17355
17500
  import { readFile as readFile14, stat as stat14 } from "fs/promises";
17356
- import { basename as basename12, join as join26 } from "path";
17501
+ import { basename as basename13, join as join26 } from "path";
17357
17502
  import { materializeTreeView } from "@tangle-network/agent-runtime/kernel";
17358
17503
  import {
17359
17504
  readRuntimeSupervisorRun
@@ -17526,7 +17671,7 @@ async function readFileRunContext(runDir, opts = {}) {
17526
17671
  return {
17527
17672
  ...sources,
17528
17673
  instanceId: opts.instanceId !== void 0 ? opts.instanceId : sources.instanceId,
17529
- arm: opts.arm !== void 0 ? opts.arm : basename12(runDir),
17674
+ arm: opts.arm !== void 0 ? opts.arm : basename13(runDir),
17530
17675
  workers: sources.workers !== null && coordination.present ? workerSources(sources.workers, coordination) : sources.workers,
17531
17676
  ...sources.journal !== null ? {
17532
17677
  rootTranscriptRef: join26(runDir, SPAWN_JOURNAL_FILE),
@@ -19188,7 +19333,7 @@ async function watchSessions(opts) {
19188
19333
  // src/bundle-view.ts
19189
19334
  import { createHash as createHash17 } from "crypto";
19190
19335
  import { mkdir as mkdir7, readdir as readdir17, readFile as readFile16, writeFile as writeFile11 } from "fs/promises";
19191
- import { dirname as dirname11, join as join30, resolve as resolve11 } from "path";
19336
+ import { dirname as dirname12, join as join30, resolve as resolve11 } from "path";
19192
19337
  var EVIDENCE_ONLY_ALLOW_LIST = [
19193
19338
  {
19194
19339
  path: "derived/session-index.json",
@@ -19426,7 +19571,7 @@ async function projectSessionBundle(opts) {
19426
19571
  continue;
19427
19572
  }
19428
19573
  const target = join30(outDir, candidate.path);
19429
- await mkdir7(dirname11(target), { recursive: true });
19574
+ await mkdir7(dirname12(target), { recursive: true });
19430
19575
  await writeFile11(target, candidate.bytes);
19431
19576
  carriedPaths.push(candidate.path);
19432
19577
  carries.set(candidate.path, candidate.carries);
@@ -19630,7 +19775,7 @@ function renderRunDiff(report) {
19630
19775
  // src/upload-state.ts
19631
19776
  import { mkdir as mkdir8, readFile as readFile17, writeFile as writeFile12 } from "fs/promises";
19632
19777
  import { homedir as homedir10 } from "os";
19633
- import { dirname as dirname12, join as join31 } from "path";
19778
+ import { dirname as dirname13, join as join31 } from "path";
19634
19779
  import { contentHash } from "@tangle-network/agent-eval";
19635
19780
  function stateFile() {
19636
19781
  const base = process.env.XDG_STATE_HOME ?? join31(homedir10(), ".local", "state");
@@ -19654,7 +19799,7 @@ async function loadState() {
19654
19799
  }
19655
19800
  async function saveState(state) {
19656
19801
  const f = stateFile();
19657
- await mkdir8(dirname12(f), { recursive: true });
19802
+ await mkdir8(dirname13(f), { recursive: true });
19658
19803
  await writeFile12(f, `${JSON.stringify(state, null, 2)}
19659
19804
  `, "utf8");
19660
19805
  }
@@ -19666,7 +19811,7 @@ function alreadyUploaded(state, harness, sessionId, hash2) {
19666
19811
  import { readFile as readFile18 } from "fs/promises";
19667
19812
  import { createRequire as createRequire2 } from "module";
19668
19813
  import { hostname as hostname2 } from "os";
19669
- import { basename as basename13, join as join32 } from "path";
19814
+ import { basename as basename14, join as join32 } from "path";
19670
19815
  import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
19671
19816
  import { assessShareSafety as assessShareSafety2, REDACTION_VERSION, redact as redact2, shareAllowed } from "@tangle-network/agent-eval/traces";
19672
19817
  var require2 = createRequire2(import.meta.url);
@@ -19700,7 +19845,7 @@ async function gitBranch(cwd) {
19700
19845
  async function sessionMeta(item) {
19701
19846
  const meta = {
19702
19847
  [ATTR.HARNESS]: item.ref.harness,
19703
- [ATTR.SESSION_FILE]: basename13(item.ref.path),
19848
+ [ATTR.SESSION_FILE]: basename14(item.ref.path),
19704
19849
  [ATTR.HOST]: hostname2(),
19705
19850
  [ATTR.UPLOADER]: `tangle-traces@${TRACES_VERSION}`,
19706
19851
  [ATTR.REDACTION_VERSION]: REDACTION_VERSION,
@@ -19866,7 +20011,7 @@ async function executeUpload(plan, opts = {}) {
19866
20011
 
19867
20012
  // src/verify-safe.ts
19868
20013
  import { lstat as lstat4, readdir as readdir18, readFile as readFile19 } from "fs/promises";
19869
- import { basename as basename14, extname, join as join33, relative as relative7 } from "path";
20014
+ import { basename as basename15, extname, join as join33, relative as relative7 } from "path";
19870
20015
  import {
19871
20016
  assessShareSafety as assessShareSafety3,
19872
20017
  combineVerdicts as combineVerdicts2
@@ -19879,7 +20024,7 @@ async function verifySafe(target, options = {}) {
19879
20024
  const unreadable = [];
19880
20025
  const files = await listFiles(target, unreadable);
19881
20026
  for (const file of files) {
19882
- const label2 = relative7(target, file) || basename14(file);
20027
+ const label2 = relative7(target, file) || basename15(file);
19883
20028
  let bytes2;
19884
20029
  try {
19885
20030
  bytes2 = await readFile19(file);
@@ -19981,7 +20126,7 @@ function formatVerdict(result) {
19981
20126
  // src/skill-usage.ts
19982
20127
  import { mkdir as mkdir9, open as open3, readdir as readdir19, readFile as readFile20, rename as rename3, stat as stat17, writeFile as writeFile13 } from "fs/promises";
19983
20128
  import { homedir as homedir11 } from "os";
19984
- import { dirname as dirname13, join as join34 } from "path";
20129
+ import { dirname as dirname14, join as join34 } from "path";
19985
20130
  import { STORES } from "@tangle-network/harness-sessions/catalog";
19986
20131
  var CHUNK_BYTES = 8 * 1024 * 1024;
19987
20132
  var NEWLINE = 10;
@@ -20066,7 +20211,7 @@ var SkillUsageStore = class _SkillUsageStore {
20066
20211
  files: this.files,
20067
20212
  events: this.list().map((e) => [e.harness, e.kind, e.skill, e.root, e.ts, e.session, e.id])
20068
20213
  };
20069
- await mkdir9(dirname13(this.path), { recursive: true });
20214
+ await mkdir9(dirname14(this.path), { recursive: true });
20070
20215
  const temporary = `${this.path}.${process.pid}.tmp`;
20071
20216
  await writeFile13(temporary, JSON.stringify(body));
20072
20217
  await rename3(temporary, this.path);
@@ -20345,7 +20490,7 @@ function skillName(value) {
20345
20490
  }
20346
20491
  function rootOf(baseDirectoryText) {
20347
20492
  const directory = baseDirectoryText.slice(BASE_DIRECTORY.length).split("\n", 1)[0].trim();
20348
- return dirname13(directory);
20493
+ return dirname14(directory);
20349
20494
  }
20350
20495
  function sessionFromPath(path) {
20351
20496
  return SESSION_UUID.exec(path)?.[1] ?? path;
@@ -20550,6 +20695,7 @@ export {
20550
20695
  isSynthesizedSpan,
20551
20696
  isInheritedSpan,
20552
20697
  ATTR,
20698
+ SESSION_CWD_ATTR,
20553
20699
  INGEST_SOURCE_CLI,
20554
20700
  environmentAttributes,
20555
20701
  stampEnvironmentAttrs,
@@ -20561,6 +20707,7 @@ export {
20561
20707
  appendAll,
20562
20708
  parseIsoToEpochMs,
20563
20709
  parseSince,
20710
+ parseTimeBound,
20564
20711
  resolveOtlpInputFiles,
20565
20712
  inferSpanKind,
20566
20713
  otlpRowToSpan,
@@ -20649,10 +20796,12 @@ export {
20649
20796
  SESSION_TOKEN_TOTAL_ATTR,
20650
20797
  SPAN_SYNTHESIZED_ATTR,
20651
20798
  FACT_TEXT_CAP,
20799
+ ANSWERED_TEXT_CAP,
20652
20800
  FACT_LIST_CAP,
20653
20801
  CHANGED_FILE_LIST_CAP,
20654
20802
  computeSessionFacts,
20655
20803
  buildSessionFactsReport,
20804
+ createSessionFactsReportBuilder,
20656
20805
  renderSessionFacts,
20657
20806
  PREPARED_CONTEXT_BYTE_CEILING,
20658
20807
  renderSessionFactsContext,