@tangle-network/traces 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -295,6 +295,7 @@ traces investigate --all --last 10 --out report.md # explicit investigation ali
295
295
  traces improve --all --last 10 --dir .traces/improvement
296
296
  traces ask --harness codex --session <id> --question "Which commands failed?"
297
297
  traces facts --harness codex --session <id> # the deterministic facts sheet, $0
298
+ traces facts --all --since 2026-10-14 --until 2026-10-15 # every session with a record that day, that day's turns
298
299
  traces skills --since 30d # skill use across every local session, incremental, $0
299
300
  traces diff run-a.otlp.jsonl run-b.otlp.jsonl # where two runs of one task first diverge
300
301
  traces mcp --otlp spans.otlp.jsonl # read-only trace tools for an MCP client
@@ -347,7 +348,8 @@ See [Replay verification](./docs/replay-verify.md) for setup, semantics, and hon
347
348
  | `--workflow` | Expand selected files through stable parent and child session IDs |
348
349
  | `--max-workflow-sessions <n>` | Stop workflow expansion before parsing more than `n` files; default `100` |
349
350
  | `--cwd <dir>` | Filter by working directory |
350
- | `--since <t>` | `upload`: window, `30m`/`2h`/`7d` or ISO (default 24h); `analyze`: ISO cutoff |
351
+ | `--since <t>` | `30m`/`2h`/`7d` before now, or an ISO date. Commands that select sessions read those modified at or after it; `upload`: its window (default 24h); `facts`: opens a time window — see [Session facts](#session-facts) |
352
+ | `--until <t>` | `facts`: closes the time window (exclusive), same forms as `--since` |
351
353
  | `--out <path>` | Write the report to a file |
352
354
  | `--dir <path>` | `improve`: write the full artifact pack to this directory; `ask`: write `answers.json` + `report.md` there |
353
355
  | `--otlp <file\|dir>` | **READ** OTLP-JSONL from any system, skipping the adapters; a directory reads the OTLP files under it (only `otlp/` when the producer made one) and names the JSONL that is not OTLP. `validate`, `check`, `analyze`, `investigate`, `improve`, `ask`, `stream` |
@@ -590,11 +592,14 @@ The same facts sheet includes observed tool outcomes, captured input/output byte
590
592
  volumes, call-to-result intervals, compaction boundaries and available context
591
593
  sizes, spawn intervals, and existing skill invocation/document-read evidence.
592
594
  Each measurement carries coverage; absent telemetry remains unknown.
593
- `facts --no-content` withholds conversation, command, changed-file and assignment
594
- text while retaining measurement metadata. This is a local metadata view, not a
595
+ `facts --no-content` withholds conversation (including each turn's `answered` text),
596
+ command, changed-file, working-directory and assignment text while retaining
597
+ measurement metadata. This is a local metadata view, not a
595
598
  share-safety certificate.
596
599
 
597
600
  SDK consumers use `buildSessionFactsReport(spans, { includeContent: false })`.
601
+ Pass `window: { since, until }` (ISO bounds) for the windowed report `facts --since/--until` prints, and use
602
+ `createSessionFactsReportBuilder(options)` (`add(spans)`, `addUnread(...)`, `build()`) to build it one session at a time.
598
603
  Cache by `measurementVersion` and `sourceDigest`; `evidenceThrough` is the latest
599
604
  recorded endpoint, not current liveness. Computing the sheet parses the supplied
600
605
  spans; returning a stored sheet needs no model call or trace scan. Semantic
@@ -608,8 +613,17 @@ No model call, no engine, no budget — it costs $0 and always returns the same
608
613
  traces facts --harness codex --session <id> # JSON on stdout
609
614
  traces facts --harness codex --last 5 --format text # the short readable form
610
615
  traces facts --otlp spans.otlp.jsonl --out facts.json
616
+ traces facts --harness claude-code --since 7d # every session with a record in the last week
617
+ traces facts --all --since 2026-10-14 --until 2026-10-15 # one day, across every harness
611
618
  ```
612
619
 
620
+ `--since <t>` and `--until <t>` open a half-open time window `[since, until)`; each takes `30m`/`2h`/`7d` before now or an ISO date, and either may be left open.
621
+ With a window, `facts` selects **every** session with a record that starts or ends inside it, not only the newest (`--last <n>` still caps it to the newest `n` per harness, and `--session` or `--otlp` still name the input).
622
+ Sessions are read one at a time, and a session file last modified before `since` is not read, because it cannot hold a later record.
623
+ Within a selected session, `humanTurns`, `excludedTurns` and `turnsByActor` keep only turns that started inside the window, so a one-day run counts only that day's turns; every other fact covers the whole session.
624
+ The report's `selection` states the window, both rules, the sessions read whose records all fell outside it (`outsideWindow`), and every session that could not be read (`unreadSessions`, each with its reason).
625
+ A report built without a window has `selection: null`.
626
+
613
627
  | Fact | What it is |
614
628
  |---|---|
615
629
  | `toolCalls` | TOOL spans **this session's agent** actually invoked. Synthesized lifecycle spans are excluded and counted in `synthesizedToolSpans`, so the total is not high by the number of subagents; calls a spawned subagent made are excluded and counted in `subagentToolSpans` |
@@ -617,7 +631,8 @@ traces facts --otlp spans.otlp.jsonl --out facts.json
617
631
  | `subagentToolSpans` | TOOL spans excluded because a subagent, not this agent, made the call. Zero on a trace that carries no subagent records |
618
632
  | `subagents` | every subagent-spawning call with the task name the adapter recorded |
619
633
  | `pullRequests` | the pull requests the commands created and merged, each named by number or head branch, with the command span and how the identity was joined. Scanned the way a shell reads the script, so a `gh pr create` inside a heredoc body is not a command that ran |
620
- | `humanTurns` | `user.prompt` turns a person typed into this session, in order, with the timestamp. Inherited fork or compaction history, harness-injected blocks, and a second record of the same turn are excluded — each listed in `excludedTurns` with its reason and span ids, never silently dropped. `turnsByActor` shows every turn by actor so the filter is checkable |
634
+ | `cwd` | the working directory the harness recorded for the session (`traces.session.cwd` on the root span): Claude Code's first record `cwd`, Codex's `session_meta` cwd, Pi's session header, OpenCode's session directory (the older JSON store: its messages' `path.cwd`), Factory's `session_start`. Never decoded from a directory name or inferred from paths; `null` with the reason when the source names none or its adapter does not read one |
635
+ | `humanTurns` | `user.prompt` turns a person typed into this session, in order, with the timestamp. Inherited fork or compaction history, harness-injected blocks, and a second record of the same turn are excluded — each listed in `excludedTurns` with its reason and span ids, never silently dropped. `turnsByActor` shows every turn by actor so the filter is checkable. Each turn's `answered` is the last message this session's own agent wrote before it (not a subagent's, not inherited history), clipped to 1,500 characters with a `… [+N chars]` marker, with its span in `answeredSpanId`; `null` when the agent had written no text yet in the spans read (a `--latest-turn` read starts at the latest task, so its first turn has none) |
621
636
  | `finalMessages` | the last message of the session's own agent, and of each subagent task, kept apart |
622
637
  | `changedFiles` | paths the harness recorded for the changes it applied, plus paths named by patch headers and file-editing tool arguments, with the operation |
623
638
  | `firstRecordAt` / `lastRecordAt` | the earliest span start and latest span end among this session's own records — a subagent that outlives the session does not stretch its window |
@@ -631,7 +646,8 @@ Three rules hold for every field:
631
646
  - **Every fact names its span ids.** `spanIds` lists the spans the value was computed from, so any number here can be opened and checked. The sheet itself is not a span and cannot be cited.
632
647
  - **A fact the spans cannot support is `null` with its reason.** It is never guessed, and never a silent zero. `partial` marks a measured value that is known to be incomplete — a truncated patch, or a list above the entry cap.
633
648
 
634
- `facts` exits non-zero when a selected session cannot be read at all: a session that produced no record spans would otherwise print a sheet of zeros stating, in the sheet's own voice, that the session did nothing.
649
+ Without a window, `facts` exits non-zero when a selected session cannot be read at all: a session that produced no record spans would otherwise print a sheet of zeros stating, in the sheet's own voice, that the session did nothing.
650
+ With a window, a session that cannot be read — the parser failed, or no record could be read — is named in `selection.unreadSessions` with its reason and a warning on stderr, and the run still exits 0 with the sessions it could read; it is never counted as a session with no turns.
635
651
 
636
652
  The same sheet reaches the model-backed analysts as prepared context, before their first model call — see [Trace analysts](docs/trace-analysts.md#session-facts-as-prepared-context).
637
653
 
@@ -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;
@@ -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));
@@ -8811,12 +8804,13 @@ function computeSessionMeasurements(spans, tools, sessionId, harness) {
8811
8804
  // src/session-facts.ts
8812
8805
  import { OPENINFERENCE_SPAN_KIND as OPENINFERENCE_SPAN_KIND4, TOOL_NAME as TOOL_NAME5 } from "@tangle-network/agent-eval/trace-attributes";
8813
8806
  import { ingestSpans, rankFirstFailure } from "@tangle-network/agent-eval/diagnosis";
8814
- var SESSION_FACTS_MEASUREMENT_VERSION = "3";
8807
+ var SESSION_FACTS_MEASUREMENT_VERSION = "4";
8815
8808
  var SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
8816
8809
  var SPAN_SYNTHESIZED_ATTR = "traces.codex.span_synthesized";
8817
8810
  var CORRUPTION_COUNT_ATTR = "traces.session.corruption_count";
8818
8811
  var CORRUPTION_RECEIPT_VERSION_ATTR = "traces.session.corruption.receipt_version";
8819
8812
  var FACT_TEXT_CAP = 2e3;
8813
+ var ANSWERED_TEXT_CAP = 1500;
8820
8814
  var FACT_LIST_CAP = 200;
8821
8815
  var CHANGED_FILE_LIST_CAP = 5e3;
8822
8816
  function attr(span2, key) {
@@ -9046,47 +9040,105 @@ function turnText(span2) {
9046
9040
  function normalizedTurnText(span2) {
9047
9041
  return turnText(span2).replace(/\s+/g, " ").trim();
9048
9042
  }
9043
+ var CAPTURE_CUT_MARKER = /… \[\+(\d+) chars\]$/;
9044
+ function clipAnswered(raw) {
9045
+ const text2 = raw.trim();
9046
+ const marker = CAPTURE_CUT_MARKER.exec(text2);
9047
+ const body = marker ? text2.slice(0, marker.index) : text2;
9048
+ if (body.length <= ANSWERED_TEXT_CAP) return text2;
9049
+ const lastKept = body.charCodeAt(ANSWERED_TEXT_CAP - 1);
9050
+ const keep = lastKept >= 55296 && lastKept <= 56319 ? ANSWERED_TEXT_CAP - 1 : ANSWERED_TEXT_CAP;
9051
+ const hidden = body.length - keep + (marker ? Number(marker[1]) : 0);
9052
+ return `${body.slice(0, keep)}\u2026 [+${hidden} chars]`;
9053
+ }
9054
+ function isOwnAgentMessage(span2) {
9055
+ return span2.name === "message.assistant" && span2.attributes[INHERITED_SPAN_ATTR] !== true && typeof span2.attributes.content === "string" && span2.attributes.content.trim().length > 0;
9056
+ }
9049
9057
  function humanTurnsOf(spans) {
9050
9058
  const excluded = /* @__PURE__ */ new Map();
9051
- const drop = (reason, spanId) => {
9052
- excluded.set(reason, [...excluded.get(reason) ?? [], spanId]);
9059
+ const drop = (reason, span2) => {
9060
+ excluded.set(reason, [...excluded.get(reason) ?? [], { spanId: span2.span_id, at: span2.start_time }]);
9053
9061
  };
9054
9062
  const kept = [];
9063
+ const turns = [];
9055
9064
  let sinceKept = [];
9065
+ let lastAgentMessage = null;
9056
9066
  for (const span2 of spans) {
9057
9067
  if (isSubagentSpan(span2.attributes)) {
9058
- if (span2.name === "user.prompt") drop(SUBAGENT_TURN_REASON, span2.span_id);
9068
+ if (span2.name === "user.prompt") drop(SUBAGENT_TURN_REASON, span2);
9059
9069
  continue;
9060
9070
  }
9061
9071
  if (span2.name !== "user.prompt") {
9072
+ if (isOwnAgentMessage(span2)) lastAgentMessage = span2;
9062
9073
  sinceKept.push(span2);
9063
9074
  continue;
9064
9075
  }
9065
9076
  if (span2.attributes[INHERITED_SPAN_ATTR] === true) {
9066
9077
  const source = stringAttr2(span2, INHERITED_SOURCE_ATTR);
9067
- drop(source === null ? INHERITED_TURN_REASON : `${INHERITED_TURN_REASON} (${source})`, span2.span_id);
9078
+ drop(source === null ? INHERITED_TURN_REASON : `${INHERITED_TURN_REASON} (${source})`, span2);
9068
9079
  continue;
9069
9080
  }
9070
9081
  const actor = stringAttr2(span2, ACTOR_ATTR) ?? "unclassified";
9071
9082
  if (actor !== "human") {
9072
- drop(TURN_EXCLUSION_REASONS[actor] ?? `the adapter classified this turn as "${actor}", not as a person typing`, span2.span_id);
9083
+ drop(TURN_EXCLUSION_REASONS[actor] ?? `the adapter classified this turn as "${actor}", not as a person typing`, span2);
9073
9084
  continue;
9074
9085
  }
9075
9086
  const previous = kept[kept.length - 1];
9076
9087
  const duplicate = previous !== void 0 && previous.start_time === span2.start_time && normalizedTurnText(previous) === normalizedTurnText(span2) && normalizedTurnText(span2).length > 0 && !sinceKept.some(isAgentActivity);
9077
9088
  if (duplicate) {
9078
- drop(DUPLICATE_TURN_REASON, span2.span_id);
9089
+ drop(DUPLICATE_TURN_REASON, span2);
9079
9090
  continue;
9080
9091
  }
9081
9092
  kept.push(span2);
9093
+ turns.push({
9094
+ actor: "human",
9095
+ at: span2.start_time,
9096
+ text: turnText(span2),
9097
+ spanId: span2.span_id,
9098
+ answered: lastAgentMessage ? clipAnswered(lastAgentMessage.attributes.content) : null,
9099
+ answeredSpanId: lastAgentMessage?.span_id ?? null
9100
+ });
9082
9101
  sinceKept = [];
9083
9102
  }
9103
+ return { turns, excluded };
9104
+ }
9105
+ function excludedTurnFacts(excluded, keep) {
9106
+ 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 }));
9107
+ }
9108
+ function windowBounds(window) {
9109
+ if (!window || window.since === void 0 && window.until === void 0) return null;
9110
+ const bound = (value, name, open4) => {
9111
+ if (value === void 0) return open4;
9112
+ const ms2 = Date.parse(value);
9113
+ if (!Number.isFinite(ms2)) throw new RangeError(`window ${name} is not a readable time: ${value}`);
9114
+ return ms2;
9115
+ };
9116
+ const sinceMs = bound(window.since, "since", Number.NEGATIVE_INFINITY);
9117
+ const untilMs = bound(window.until, "until", Number.POSITIVE_INFINITY);
9118
+ if (!(sinceMs < untilMs)) throw new RangeError(`window since (${window.since}) must be before until (${window.until})`);
9084
9119
  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 }))
9120
+ since: window.since === void 0 ? null : new Date(sinceMs).toISOString(),
9121
+ until: window.until === void 0 ? null : new Date(untilMs).toISOString(),
9122
+ sinceMs,
9123
+ untilMs
9087
9124
  };
9088
9125
  }
9126
+ function insideWindow(bounds, time) {
9127
+ if (bounds === null) return true;
9128
+ const ms2 = Date.parse(time);
9129
+ return Number.isFinite(ms2) && ms2 >= bounds.sinceMs && ms2 < bounds.untilMs;
9130
+ }
9131
+ function isRecordSpan(span2) {
9132
+ return span2.parent_span_id !== null && span2.attributes[CORRUPTION_RECEIPT_VERSION_ATTR] === void 0;
9133
+ }
9089
9134
  function computeSessionFacts(spans, options = {}) {
9135
+ const bounds = windowBounds(options.window);
9136
+ return groupByTrace(spans).sort((left, right) => compareIds(left.traceId, right.traceId)).map((group) => sessionFactsForTrace(group.traceId, group.sessionId, group.spans, options, bounds));
9137
+ }
9138
+ function compareIds(left, right) {
9139
+ return left < right ? -1 : left > right ? 1 : 0;
9140
+ }
9141
+ function groupByTrace(spans) {
9090
9142
  const { sessionByTrace } = indexSessionIdsByTrace(spans);
9091
9143
  const byTrace = /* @__PURE__ */ new Map();
9092
9144
  for (const span2 of spans) {
@@ -9094,9 +9146,13 @@ function computeSessionFacts(spans, options = {}) {
9094
9146
  if (group) group.push(span2);
9095
9147
  else byTrace.set(span2.trace_id, [span2]);
9096
9148
  }
9097
- return [...byTrace].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([traceId, traceSpans]) => sessionFactsForTrace(traceId, sessionByTrace.get(traceId) ?? null, traceSpans, options));
9149
+ return [...byTrace].map(([traceId, traceSpans]) => ({
9150
+ traceId,
9151
+ sessionId: sessionByTrace.get(traceId) ?? null,
9152
+ spans: traceSpans
9153
+ }));
9098
9154
  }
9099
- function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9155
+ function sessionFactsForTrace(traceId, sessionId, unordered, options, bounds) {
9100
9156
  const spans = [...unordered].sort(byTraceOrder);
9101
9157
  const harness = spans.map((span2) => stringAttr2(span2, "service.name")).find((name) => name !== null) ?? null;
9102
9158
  const ownSpans = spans.filter((span2) => !isSubagentSpan(span2.attributes));
@@ -9110,8 +9166,11 @@ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9110
9166
  for (const span2 of invoked) byName[toolName3(span2)] = (byName[toolName3(span2)] ?? 0) + 1;
9111
9167
  const subagents = subagentsOf(ownSpans, spans);
9112
9168
  const pullRequests = readPullRequests(ownSpans);
9113
- const promptSpans = spans.filter((span2) => span2.name === "user.prompt");
9114
- const { turns: humanTurns, excluded: excludedTurns } = humanTurnsOf(spans);
9169
+ const inWindow = (at) => insideWindow(bounds, at);
9170
+ const promptSpans = spans.filter((span2) => span2.name === "user.prompt" && inWindow(span2.start_time));
9171
+ const turnFacts = humanTurnsOf(spans);
9172
+ const humanTurns = turnFacts.turns.filter((turn) => inWindow(turn.at));
9173
+ const excludedTurns = excludedTurnFacts(turnFacts.excluded, inWindow);
9115
9174
  const actorCounts = /* @__PURE__ */ new Map();
9116
9175
  for (const span2 of promptSpans) {
9117
9176
  const actor = stringAttr2(span2, ACTOR_ATTR) ?? "unclassified";
@@ -9131,16 +9190,20 @@ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9131
9190
  );
9132
9191
  const tokenSpan = spans.find((span2) => typeof span2.attributes[SESSION_TOKEN_TOTAL_ATTR] === "number");
9133
9192
  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;
9193
+ const recordSpans = spans.filter(isRecordSpan).length;
9137
9194
  const corruptionSpan = spans.find((span2) => typeof span2.attributes[CORRUPTION_COUNT_ATTR] === "number");
9195
+ const cwdSpan = spans.find((span2) => stringAttr2(span2, SESSION_CWD_ATTR) !== null);
9138
9196
  const facts = {
9139
9197
  schemaVersion: 1,
9140
9198
  kind: "traces.session_facts",
9141
9199
  traceId,
9142
9200
  sessionId,
9143
9201
  harness,
9202
+ cwd: cwdSpan ? { value: stringAttr2(cwdSpan, SESSION_CWD_ATTR), spanIds: [cwdSpan.span_id], unavailable: null } : {
9203
+ value: null,
9204
+ spanIds: [],
9205
+ unavailable: `no span carries ${SESSION_CWD_ATTR}: the session source records no working directory, or the adapter that read it does not read one`
9206
+ },
9144
9207
  spanCount: spans.length,
9145
9208
  recordSpans,
9146
9209
  unreadRecords: corruptionSpan ? {
@@ -9213,6 +9276,7 @@ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9213
9276
  });
9214
9277
  return {
9215
9278
  ...facts,
9279
+ cwd: withheld(),
9216
9280
  humanTurns: withheld(),
9217
9281
  finalMessages: withheld(),
9218
9282
  changedFiles: withheld(),
@@ -9229,26 +9293,88 @@ function sessionFactsForTrace(traceId, sessionId, unordered, options) {
9229
9293
  };
9230
9294
  }
9231
9295
  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));
9296
+ const builder = createSessionFactsReportBuilder(options);
9297
+ builder.add(spans);
9298
+ return builder.build();
9299
+ }
9300
+ var WINDOW_SESSION_RULE = "a session is selected when at least one of its records starts or ends inside [since, until)";
9301
+ 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";
9302
+ function createSessionFactsReportBuilder(options = {}) {
9303
+ const bounds = windowBounds(options.window);
9304
+ const sessions = [];
9305
+ const digests = [];
9306
+ const outsideWindow = [];
9307
+ const unreadSessions = [];
9308
+ let spanCount = 0;
9234
9309
  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;
9310
+ const select = (group) => {
9311
+ const digest = createHash7("sha256");
9312
+ for (const span2 of [...group.spans].sort((a, b) => a.span_id.localeCompare(b.span_id))) {
9313
+ digest.update(canonicalJson(span2)).update("\n");
9314
+ for (const time of [span2.start_time, span2.end_time]) {
9315
+ if (Number.isFinite(Date.parse(time)) && (evidenceThrough === null || Date.parse(time) > Date.parse(evidenceThrough))) evidenceThrough = time;
9316
+ }
9239
9317
  }
9240
- }
9318
+ digests.push({ traceId: group.traceId, digest: digest.digest("hex") });
9319
+ spanCount += group.spans.length;
9320
+ sessions.push(sessionFactsForTrace(group.traceId, group.sessionId, group.spans, options, bounds));
9321
+ };
9241
9322
  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)
9323
+ add(spans) {
9324
+ for (const group of groupByTrace(spans)) {
9325
+ if (bounds === null) {
9326
+ select(group);
9327
+ continue;
9328
+ }
9329
+ const records = group.spans.filter(isRecordSpan);
9330
+ if (records.length === 0) {
9331
+ const unread = group.spans.find((span2) => typeof span2.attributes[CORRUPTION_COUNT_ATTR] === "number");
9332
+ unreadSessions.push({
9333
+ sessionId: group.sessionId,
9334
+ traceId: group.traceId,
9335
+ source: null,
9336
+ 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`
9337
+ });
9338
+ continue;
9339
+ }
9340
+ if (records.some((span2) => insideWindow(bounds, span2.start_time) || insideWindow(bounds, span2.end_time))) select(group);
9341
+ else outsideWindow.push(group.sessionId ?? group.traceId);
9342
+ }
9343
+ },
9344
+ addUnread(session) {
9345
+ if (bounds === null) {
9346
+ throw new Error("an unread session is reported only by a windowed report; without a window, refuse the selection");
9347
+ }
9348
+ unreadSessions.push(session);
9349
+ },
9350
+ build() {
9351
+ const digest = createHash7("sha256");
9352
+ for (const entry of [...digests].sort((a, b) => a.traceId.localeCompare(b.traceId) || a.digest.localeCompare(b.digest))) {
9353
+ digest.update(`${entry.traceId}
9354
+ ${entry.digest}
9355
+ `);
9356
+ }
9357
+ return {
9358
+ schemaVersion: 1,
9359
+ kind: "traces.session_facts_report",
9360
+ measurementVersion: SESSION_FACTS_MEASUREMENT_VERSION,
9361
+ sourceDigest: "sha256:" + digest.digest("hex"),
9362
+ evidenceThrough,
9363
+ contentIncluded: options.includeContent !== false,
9364
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
9365
+ harness: options.harness ?? null,
9366
+ spanCount,
9367
+ selection: bounds === null ? null : {
9368
+ since: bounds.since,
9369
+ until: bounds.until,
9370
+ sessionRule: WINDOW_SESSION_RULE,
9371
+ turnRule: WINDOW_TURN_RULE,
9372
+ outsideWindow: [...outsideWindow].sort(compareIds),
9373
+ unreadSessions: [...unreadSessions]
9374
+ },
9375
+ sessions: [...sessions].sort((left, right) => compareIds(left.traceId, right.traceId))
9376
+ };
9377
+ }
9252
9378
  };
9253
9379
  }
9254
9380
  function factLine(label2, fact, rendered) {
@@ -9260,8 +9386,18 @@ function renderSessionFacts(report) {
9260
9386
  const lines = [
9261
9387
  `session facts \u2014 ${report.sessions.length} session(s), ${report.spanCount} span(s), deterministic, $0`
9262
9388
  ];
9389
+ const selection = report.selection;
9390
+ if (selection) {
9391
+ lines.push(
9392
+ `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`
9393
+ );
9394
+ for (const unread of selection.unreadSessions) {
9395
+ lines.push(` unread session ${unread.sessionId ?? unread.traceId ?? unread.source ?? "unknown"}: ${unread.reason}`);
9396
+ }
9397
+ }
9263
9398
  for (const facts of report.sessions) {
9264
9399
  lines.push("", `${facts.sessionId ?? facts.traceId} [${facts.harness ?? "unknown harness"}] ${facts.spanCount} spans`);
9400
+ lines.push(factLine("cwd", facts.cwd, String(facts.cwd.value)));
9265
9401
  lines.push(
9266
9402
  factLine(
9267
9403
  "tool calls",
@@ -9329,6 +9465,9 @@ function renderSessionFactsContext(report, options = {}) {
9329
9465
  ${JSON.stringify({ sessions, ...dropped2.length ? { omitted_fields: dropped2 } : {} })}`;
9330
9466
  const dropped = [];
9331
9467
  const sheds = [
9468
+ ["human_turn_answered", (facts) => {
9469
+ for (const turn of facts.human_turns) delete turn.answered;
9470
+ }],
9332
9471
  ["human_turn_text", (facts) => {
9333
9472
  for (const turn of facts.human_turns) delete turn.text;
9334
9473
  }],
@@ -9419,6 +9558,7 @@ function compactFacts(facts) {
9419
9558
  return {
9420
9559
  session_id: facts.sessionId,
9421
9560
  trace_id: facts.traceId,
9561
+ cwd: factJson(facts.cwd),
9422
9562
  spans: facts.spanCount,
9423
9563
  tool_calls: {
9424
9564
  count: facts.toolCalls.value,
@@ -9441,7 +9581,8 @@ function compactFacts(facts) {
9441
9581
  human_turns: (facts.humanTurns.value ?? []).map((turn) => ({
9442
9582
  at: turn.at,
9443
9583
  span_id: turn.spanId,
9444
- text: turn.text
9584
+ text: turn.text,
9585
+ answered: turn.answered
9445
9586
  })),
9446
9587
  excluded_turns: facts.excludedTurns.value?.map((entry) => ({
9447
9588
  reason: entry.reason,
@@ -20550,6 +20691,7 @@ export {
20550
20691
  isSynthesizedSpan,
20551
20692
  isInheritedSpan,
20552
20693
  ATTR,
20694
+ SESSION_CWD_ATTR,
20553
20695
  INGEST_SOURCE_CLI,
20554
20696
  environmentAttributes,
20555
20697
  stampEnvironmentAttrs,
@@ -20561,6 +20703,7 @@ export {
20561
20703
  appendAll,
20562
20704
  parseIsoToEpochMs,
20563
20705
  parseSince,
20706
+ parseTimeBound,
20564
20707
  resolveOtlpInputFiles,
20565
20708
  inferSpanKind,
20566
20709
  otlpRowToSpan,
@@ -20649,10 +20792,12 @@ export {
20649
20792
  SESSION_TOKEN_TOTAL_ATTR,
20650
20793
  SPAN_SYNTHESIZED_ATTR,
20651
20794
  FACT_TEXT_CAP,
20795
+ ANSWERED_TEXT_CAP,
20652
20796
  FACT_LIST_CAP,
20653
20797
  CHANGED_FILE_LIST_CAP,
20654
20798
  computeSessionFacts,
20655
20799
  buildSessionFactsReport,
20800
+ createSessionFactsReportBuilder,
20656
20801
  renderSessionFacts,
20657
20802
  PREPARED_CONTEXT_BYTE_CEILING,
20658
20803
  renderSessionFactsContext,
package/dist/cli.js CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  assertOutsideSourceBundle,
14
14
  assessSpans,
15
15
  buildPolicyEvidenceRecord,
16
- buildSessionFactsReport,
17
16
  buildSessionIndexFromRows,
18
17
  buildSkillUsageReport,
19
18
  cmdReplayVerify,
@@ -22,6 +21,7 @@ import {
22
21
  collectSessionSelection,
23
22
  commandAnalyzer,
24
23
  commandRedactor,
24
+ createSessionFactsReportBuilder,
25
25
  defaultSkillUsageStorePath,
26
26
  diffRuns,
27
27
  executeUpload,
@@ -47,6 +47,7 @@ import {
47
47
  parseIsoToEpochMs,
48
48
  parseSession,
49
49
  parseSince,
50
+ parseTimeBound,
50
51
  planUpload,
51
52
  primeAnalyzer,
52
53
  projectSessionBundle,
@@ -66,6 +67,7 @@ import {
66
67
  runTraceInvestigation,
67
68
  runTraceQuestions,
68
69
  saveReport,
70
+ scanSessions,
69
71
  selectAdapters,
70
72
  serializePolicyEvidence,
71
73
  serializeSessionIndex,
@@ -90,7 +92,7 @@ import {
90
92
  writeSessionIndexFile,
91
93
  writeTraceEvidenceExportFile,
92
94
  writeTraceQuestionsArtifacts
93
- } from "./chunk-DON6NQ7H.js";
95
+ } from "./chunk-COSWFESO.js";
94
96
 
95
97
  // src/cli.ts
96
98
  import { readFileSync } from "fs";
@@ -489,6 +491,9 @@ function parseArgs(argv) {
489
491
  case "--since":
490
492
  a.since = next();
491
493
  break;
494
+ case "--until":
495
+ a.until = next();
496
+ break;
492
497
  case "--out":
493
498
  a.out = next();
494
499
  break;
@@ -703,6 +708,10 @@ function validateViewSelection(args) {
703
708
  `--view is not supported by ${args.command}. A view is a projection of an assembled bundle: run \`traces bundle ... --out <dir>\` first, then \`traces bundle-view <dir> --view evidence-only --out <view-dir>\`.`
704
709
  );
705
710
  }
711
+ function validateUntilSelection(args) {
712
+ if (args.until === void 0 || args.command === "facts") return args;
713
+ throw new Error(`--until is not supported by ${args.command}; it bounds the facts window (traces facts --since <t> --until <t>)`);
714
+ }
706
715
  function validateWorkflowSelection(args) {
707
716
  if (!args.workflow && !args.latestTurn) return args;
708
717
  if (!WORKFLOW_COMMANDS.has(args.command)) {
@@ -736,7 +745,7 @@ function adaptersFor(args) {
736
745
  return selectAdapters({ all: args.all, harnesses: [args.harness] });
737
746
  }
738
747
  async function discover(args) {
739
- const sinceMs = args.since ? Date.parse(args.since) : void 0;
748
+ const sinceMs = args.since ? parseSince(args.since) : void 0;
740
749
  const out = [];
741
750
  for (const adapter of adaptersFor(args)) {
742
751
  let refs = await locateSessions(adapter, { cwd: args.cwd, sinceMs });
@@ -1201,10 +1210,50 @@ async function cmdFacts(args) {
1201
1210
  if (format !== "json" && format !== "text") {
1202
1211
  throw new Error(`unknown facts format "${format}" (expected json or text)`);
1203
1212
  }
1204
- const collected = await collectSpans(args);
1205
- if (collected.spans.length === 0) throw new Error("no spans found for the given selection");
1206
- warnIncompleteWorkflow(collected.workflow);
1207
- const report = buildSessionFactsReport(collected.spans, { harness: collected.harness, includeContent: !args.noContent });
1213
+ const window = args.since !== void 0 || args.until !== void 0 ? {
1214
+ ...args.since !== void 0 ? { since: new Date(parseSince(args.since)).toISOString() } : {},
1215
+ ...args.until !== void 0 ? { until: new Date(parseTimeBound(args.until, "--until")).toISOString() } : {}
1216
+ } : void 0;
1217
+ const builder = createSessionFactsReportBuilder({ includeContent: !args.noContent, window });
1218
+ let report;
1219
+ const discoversWindow = window !== void 0 && !args.session && !args.otlp && !args.input && !args.sourceBundle && !args.workflow;
1220
+ if (discoversWindow) {
1221
+ const harnesses = /* @__PURE__ */ new Set();
1222
+ for await (const scanned of scanSessions({
1223
+ adapters: adaptersFor(args),
1224
+ cwd: args.cwd,
1225
+ sinceMs: window.since === void 0 ? void 0 : Date.parse(window.since),
1226
+ last: args.last > 0 ? args.last : void 0,
1227
+ taskScope: args.latestTurn ? "latest" : "all",
1228
+ onError: (error, ref) => {
1229
+ if (!ref) throw error;
1230
+ builder.addUnread({
1231
+ sessionId: ref.sessionId,
1232
+ traceId: null,
1233
+ source: ref.path,
1234
+ reason: error instanceof Error ? error.message : String(error)
1235
+ });
1236
+ }
1237
+ })) {
1238
+ harnesses.add(scanned.adapter.harness);
1239
+ builder.add(scanned.spans);
1240
+ }
1241
+ report = { ...builder.build(), harness: [...harnesses].join("+") || args.harness };
1242
+ } else {
1243
+ const collected = await collectSpans(args);
1244
+ if (collected.spans.length === 0) throw new Error("no spans found for the given selection");
1245
+ warnIncompleteWorkflow(collected.workflow);
1246
+ builder.add(collected.spans);
1247
+ report = { ...builder.build(), harness: collected.harness };
1248
+ }
1249
+ const unreadInWindow = report.selection?.unreadSessions ?? [];
1250
+ if (unreadInWindow.length > 0) {
1251
+ const named = unreadInWindow.slice(0, 3).map((entry) => entry.source ?? entry.sessionId ?? entry.traceId);
1252
+ process.stderr.write(
1253
+ `warning: ${unreadInWindow.length} session(s) could not be read; each is named with its reason in selection.unreadSessions (${named.join(", ")}${unreadInWindow.length > named.length ? ", \u2026" : ""})
1254
+ `
1255
+ );
1256
+ }
1208
1257
  const unread = report.sessions.filter((facts) => facts.recordSpans === 0);
1209
1258
  if (unread.length > 0) {
1210
1259
  throw new Error(
@@ -2050,10 +2099,15 @@ Commands:
2050
2099
  sessions: tool calls excluding synthesized spans, subagent spawns
2051
2100
  with task names, human turns in order, the final message per task,
2052
2101
  changed paths, first/last record times, the harness token total, and
2053
- the first failure by agent-eval's fixed precedence (or none).
2102
+ the first failure by agent-eval's fixed precedence (or none), the
2103
+ session's recorded working directory, and for each human turn the
2104
+ agent message it answered.
2054
2105
  No model call, no budget, $0. Every fact names the span ids it came
2055
2106
  from; a fact the spans cannot support is null with its reason.
2056
- --format json (default) or text (exit 1 when a session cannot be read)
2107
+ --since/--until select every session with a record in the window and
2108
+ count only that window's turns; a session that cannot be read is named
2109
+ in selection.unreadSessions. Without a window: exit 1 when a selected
2110
+ session cannot be read. --format json (default) or text
2057
2111
  skills Count skill use across Claude Code, Codex, and Kimi Code sessions on this
2058
2112
  machine, and list installed skills with no use in the window.
2059
2113
  Incremental: a run reads only transcript bytes appended since the
@@ -2129,7 +2183,11 @@ Options:
2129
2183
  accepted vs rejected. Rolls up when the dir holds many runs.
2130
2184
  analyze reads Runtime journals and recorded startup failures.
2131
2185
  watch reads a live Runtime journal and OTLP span files.
2132
- --since <t> upload: window, 30m / 2h / 7d or an ISO date (default 24h); analyze: ISO cutoff
2186
+ --since <t> 30m / 2h / 7d (before now) or an ISO date. Selection commands read
2187
+ sessions modified at or after it; upload: its window (default 24h).
2188
+ facts: opens a time window and selects EVERY session with a record
2189
+ in it, not only the newest; its turn facts keep only turns inside it
2190
+ --until <t> facts: closes the window (exclusive), same forms as --since
2133
2191
  --out <path> Write report to a file
2134
2192
  --dir <path> improve/ask: write artifacts to this directory
2135
2193
  --otlp <file|dir> READ OTLP-JSONL emitted by any system, skipping the adapters.
@@ -2261,7 +2319,7 @@ async function main() {
2261
2319
  return;
2262
2320
  }
2263
2321
  const args = validateOtlpSelection(
2264
- validateViewSelection(validateWorkflowSelection(applyCurrentSessionSelection(parsedArgs)))
2322
+ validateViewSelection(validateWorkflowSelection(validateUntilSelection(applyCurrentSessionSelection(parsedArgs))))
2265
2323
  );
2266
2324
  switch (args.command) {
2267
2325
  case "help":
package/dist/index.d.ts CHANGED
@@ -900,6 +900,14 @@ declare const ATTR: {
900
900
  * in-container path even when no such path exists on this host. */
901
901
  readonly ENVIRONMENT_CWD: "tangle.environment.cwd";
902
902
  };
903
+ /**
904
+ * The working directory the harness recorded for a session, verbatim, on the
905
+ * adapter's root `session` span. Unlike `tangle.cwd` it is never repaired,
906
+ * decoded from a directory name, or inferred from paths the spans mention: an
907
+ * adapter sets it only from a field the session source itself carries, and
908
+ * leaves it absent when the source names none.
909
+ */
910
+ declare const SESSION_CWD_ATTR = "traces.session.cwd";
903
911
  /** `tangle.ingest_source` value for CLI-uploaded traces. Wire contract. */
904
912
  declare const INGEST_SOURCE_CLI = "cli";
905
913
  /**
@@ -957,6 +965,9 @@ declare function parseIsoToEpochMs(ts: string): number;
957
965
  /** Parse a `--since` window: `30m` / `2h` / `7d` (relative to now) or an ISO
958
966
  * date, → epoch ms. Throws on a malformed value (fail-loud, not silent). */
959
967
  declare function parseSince(value: string): number;
968
+ /** Parse one time bound the way `--since` reads it: `30m` / `2h` / `7d` before
969
+ * now, or an ISO date. `flag` names the option in the error. */
970
+ declare function parseTimeBound(value: string, flag: string): number;
960
971
 
961
972
  type JsonSourceErrorKind = 'read' | 'parse';
962
973
  declare class JsonSourceError extends Error {
@@ -3844,10 +3855,28 @@ declare function readPullRequests(spans: readonly OtlpSpan[]): PullRequestReadin
3844
3855
  * deltas, which is a different (and smaller) number.
3845
3856
  */
3846
3857
  /** Cache invalidation key for deterministic measurements and prepared analyst context. */
3847
- declare const SESSION_FACTS_MEASUREMENT_VERSION = "3";
3858
+ declare const SESSION_FACTS_MEASUREMENT_VERSION = "4";
3859
+ /**
3860
+ * A half-open time window: a time `t` is inside when `since <= t < until`.
3861
+ * Either bound may be absent, which leaves that side open.
3862
+ */
3863
+ interface SessionFactsWindow {
3864
+ /** ISO-8601 lower bound, inclusive. */
3865
+ readonly since?: string;
3866
+ /** ISO-8601 upper bound, exclusive. */
3867
+ readonly until?: string;
3868
+ }
3848
3869
  interface SessionFactsOptions {
3849
3870
  /** Defaults to true for compatibility. False withholds conversation, commands, paths and assignment text. */
3850
3871
  readonly includeContent?: boolean;
3872
+ /**
3873
+ * Limit the turn facts to a time window. `humanTurns`, `excludedTurns` and
3874
+ * `turnsByActor` keep only `user.prompt` turns that started inside it; every
3875
+ * other fact still covers the whole session. A report built with a window
3876
+ * also leaves out every session with no record inside it, and names them in
3877
+ * {@link SessionFactsReport.selection}.
3878
+ */
3879
+ readonly window?: SessionFactsWindow;
3851
3880
  }
3852
3881
  declare const SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
3853
3882
  /**
@@ -3859,6 +3888,8 @@ declare const SESSION_TOKEN_TOTAL_ATTR = "traces.session.total_tokens";
3859
3888
  declare const SPAN_SYNTHESIZED_ATTR = "traces.codex.span_synthesized";
3860
3889
  /** Max characters of message or prompt text kept per fact entry. */
3861
3890
  declare const FACT_TEXT_CAP = 2000;
3891
+ /** Max characters of the agent message kept in {@link SessionTurnFact.answered}. */
3892
+ declare const ANSWERED_TEXT_CAP = 1500;
3862
3893
  /** Entries kept per list field before the sheet reports the rest as omitted. */
3863
3894
  declare const FACT_LIST_CAP = 200;
3864
3895
  /**
@@ -3904,6 +3935,15 @@ interface SessionTurnFact {
3904
3935
  readonly at: string;
3905
3936
  readonly text: string;
3906
3937
  readonly spanId: string;
3938
+ /**
3939
+ * The text of the last message this session's own agent wrote before the
3940
+ * turn: what the person was answering. Clipped to {@link ANSWERED_TEXT_CAP}
3941
+ * characters with a visible `… [+N chars]` marker. Null when the agent had
3942
+ * written no text before the turn.
3943
+ */
3944
+ readonly answered: string | null;
3945
+ /** The `message.assistant` span `answered` was read from. */
3946
+ readonly answeredSpanId: string | null;
3907
3947
  }
3908
3948
  /** How many `user.prompt` turns each actor produced. */
3909
3949
  interface TurnActorCount {
@@ -3952,6 +3992,13 @@ interface SessionFacts {
3952
3992
  readonly traceId: string;
3953
3993
  readonly sessionId: string | null;
3954
3994
  readonly harness: string | null;
3995
+ /**
3996
+ * The working directory the harness recorded for the session, read from
3997
+ * `traces.session.cwd` on the adapter's root span. Null with the reason when
3998
+ * no span carries it; never decoded from a directory name or inferred from
3999
+ * the paths the session touched.
4000
+ */
4001
+ readonly cwd: SessionFact<string>;
3955
4002
  /** Spans in this trace, including the synthesized ones. */
3956
4003
  readonly spanCount: number;
3957
4004
  /**
@@ -4002,18 +4049,45 @@ interface SessionFacts {
4002
4049
  readonly firstFailure: FirstFailure;
4003
4050
  readonly measurements: SessionMeasurements;
4004
4051
  }
4052
+ /** A session the report could not read, so whether it belongs in the window is unknown. */
4053
+ interface UnreadSessionFact {
4054
+ readonly sessionId: string | null;
4055
+ readonly traceId: string | null;
4056
+ /** Where the session was read from, when the reader knows. */
4057
+ readonly source: string | null;
4058
+ readonly reason: string;
4059
+ }
4060
+ /** How a windowed report chose its sessions and turns. Null on a report built without a window. */
4061
+ interface SessionFactsSelection {
4062
+ /** The window's inclusive lower bound, or null when open. */
4063
+ readonly since: string | null;
4064
+ /** The window's exclusive upper bound, or null when open. */
4065
+ readonly until: string | null;
4066
+ /** Which sessions the report holds. */
4067
+ readonly sessionRule: string;
4068
+ /** Which turns `humanTurns`, `excludedTurns` and `turnsByActor` hold. */
4069
+ readonly turnRule: string;
4070
+ /** Sessions read in full whose records all fall outside the window, by session id. */
4071
+ readonly outsideWindow: readonly string[];
4072
+ /** Sessions that could not be read; each is named with the reason, never counted as empty. */
4073
+ readonly unreadSessions: readonly UnreadSessionFact[];
4074
+ }
4005
4075
  interface SessionFactsReport {
4006
4076
  readonly schemaVersion: 1;
4007
4077
  readonly kind: 'traces.session_facts_report';
4008
4078
  readonly generatedAt: string;
4009
4079
  readonly measurementVersion: typeof SESSION_FACTS_MEASUREMENT_VERSION;
4010
- /** SHA-256 of the canonical selected spans, including content, attributes, links and timing. */
4080
+ /**
4081
+ * SHA-256 over the selected sessions in trace-id order, each contributing the
4082
+ * SHA-256 of its canonical spans (content, attributes, links and timing).
4083
+ */
4011
4084
  readonly sourceDigest: string;
4012
4085
  /** Latest recorded endpoint, not the last time the filesystem was checked. */
4013
4086
  readonly evidenceThrough: string | null;
4014
4087
  readonly contentIncluded: boolean;
4015
4088
  readonly harness: string | null;
4016
4089
  readonly spanCount: number;
4090
+ readonly selection: SessionFactsSelection | null;
4017
4091
  readonly sessions: readonly SessionFacts[];
4018
4092
  }
4019
4093
  /**
@@ -4022,11 +4096,25 @@ interface SessionFactsReport {
4022
4096
  * model, network call, or budget is involved.
4023
4097
  */
4024
4098
  declare function computeSessionFacts(spans: readonly OtlpSpan[], options?: SessionFactsOptions): SessionFacts[];
4025
- /** The facts sheet for a set of spans, as one report. */
4026
- declare function buildSessionFactsReport(spans: readonly OtlpSpan[], options?: SessionFactsOptions & {
4099
+ type SessionFactsReportOptions = SessionFactsOptions & {
4027
4100
  harness?: string | null;
4028
4101
  generatedAt?: string;
4029
- }): SessionFactsReport;
4102
+ };
4103
+ /** The facts sheet for a set of spans, as one report. */
4104
+ declare function buildSessionFactsReport(spans: readonly OtlpSpan[], options?: SessionFactsReportOptions): SessionFactsReport;
4105
+ /**
4106
+ * Builds one report from sessions supplied a batch at a time, so a caller
4107
+ * reading many sessions holds one session's spans at once rather than all of
4108
+ * them. `add` the whole spans of one or more sessions per call; a trace split
4109
+ * across two calls is reported as two sessions.
4110
+ */
4111
+ interface SessionFactsReportBuilder {
4112
+ add(spans: readonly OtlpSpan[]): void;
4113
+ /** Name a session that could not be read at all. Only a windowed report holds these. */
4114
+ addUnread(session: UnreadSessionFact): void;
4115
+ build(): SessionFactsReport;
4116
+ }
4117
+ declare function createSessionFactsReportBuilder(options?: SessionFactsReportOptions): SessionFactsReportBuilder;
4030
4118
  /** The short readable form: the same facts, one screen, no span-id lists. */
4031
4119
  declare function renderSessionFacts(report: SessionFactsReport): string;
4032
4120
  /**
@@ -5129,4 +5217,4 @@ declare function parseRetainedSession(input: {
5129
5217
  readonly signal?: AbortSignal;
5130
5218
  }): Promise<RetainedSessionSelection>;
5131
5219
 
5132
- export { ACTOR_ATTR, ATTR, type Actor, type AdoptionOptions, type AdoptionReport, AmpAdapter, type AnalysisEngineFromEnvOptions, type AnalyzeOptions, type AnalyzeResult, type AnswerSchema, type AssembleSessionBundleOptions, BLOBS_DIR, type BuildPolicyEvidenceOptions, type BuildSessionIndexOptions, type BuildTraceFindingPacketOptions, CHANGED_FILE_LIST_CAP, CITATION_NORMALIZATION_VERSION, CONTENT_CAP, COORDINATION_LOG_FILE, CORRECTIVE_REACTIONS, type ChangedFileFact, type ChatTrajectory, type ChatTrajectoryMessage, type ChatTrajectoryOptions, ClaudeAdapter, type CodeTraceBenchFileRef, type CodeTraceBenchImportOptions, type CodeTraceBenchImportReceipt, type CodeTraceBenchImportResult, type CodeTraceBenchImportedTrace, type CodeTraceBenchStep, CodexAdapter, CodexExecAdapter, CodexExecStreamError, CodexTaskScopeError, type CollectOptions, type CollectPolicyEvidenceOptions, type CollectSessionIndexOptions, type CollectSessionSelectionOptions, type CollectSessionWorkflowOptions, type CompactionMeasurement, type ConformanceRenderOptions, type ContentFootprint, type CoordinationLog, type CoordinationQuestion, type CoordinationSteer, CopilotAdapter, type CorruptionMode, type CoveredMeasurement, DEFAULT_ANALYST_MODEL, DEFAULT_ASK_CONCURRENCY, DEFAULT_HARNESS, DEFAULT_SANDBOX_BASE_URL, DEFAULT_SWEEP_THRESHOLD, EMPTY_SPAN_RUN_SNAPSHOT, EXPORT_RULE, EmptySessionError, type EvidenceOnlyVerdict, type ExcludedTurnFact, type ExecuteOptions, type ExportRule, type ExternalAnalysisKind, type ExternalAnalysisPayload, type ExternalAnalysisResult, type ExternalAnalyzer, type ExternalAnalyzerOptions, type ExternalDiscoveryCandidate, FACT_LIST_CAP, FACT_TEXT_CAP, FactoryAdapter, type FailureFollowUp, type FailureFollowUpReport, type FileRunContextReaderOptions, type FinalMessageFact, type FindSessionTextLeaksOptions, type FindingRejectionCounts, type FindingRejectionReasons, type FindingRejectionTally, type FindingsFileOutcome, type FollowUpKind, GeminiAdapter, type HaloAnalyzerOptions, type HaloReasoningEffort, HarnessSessionsAdapter, type HarnessTraceAdapter, type HodoscopeAnalyzerOptions, type HodoscopeInput, type HodoscopeMessage, type HodoscopeSourceMapEntry, type HodoscopeTrajectory, INGEST_SOURCE_CLI, INHERITED_SOURCE_ATTR, INHERITED_SPANS_OMITTED_ATTR, INHERITED_SPAN_ATTR, INHERITED_SPAN_COUNT_ATTR, type InheritedSpanSource, type InspectSessionIndexOptions, type InstalledSkill, JsonSourceError, type JsonlCorruptionReceipt, JsonlParseError, type JsonlReadOptions, type LiveBatchOptions, type LocateOptions, type LoopConvergence, type LoopConvergenceReport, type LoopIteration, type LoopTrend, type LoopTrendBasis, MAX_SHELL_DEPTH, MAX_TRACE_QUESTION_CHARS, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpIngestIssue, type OtlpIngestIssueKind, type OtlpInput, type OtlpInputFile, type OtlpRowConversion, type OtlpSpan, type OtlpSpanKind, type OtlpSpanLink, type OtlpStatusCode, POLICY_EVIDENCE_NOTE, PREPARED_CONTEXT_BYTE_CEILING, type ParentTaskResolution, type ParseOptions, PartialUploadError, PiAdapter, type PiAdapterOptions, type PipelineOptions, type PipelineReport, type PlanOptions, type PolicyEvidenceLoopSummary, type PolicyEvidenceRecord, type PolicyEvidenceToolSummary, type PrimeAnalyzerOptions, type PrimeTransport, type PrimeTransportRequest, type PrimeTransportResponse, type ProjectSessionBundleOptions, type ProjectSessionBundleResult, type PullRequestFact, type PullRequestFacts, type PullRequestReading, QwenAdapter, REACTIONS, RESULT_FILE, RETAINED_SESSION_READER_VERSION, RULE, type Reaction, type ReactionReport, type ReactionSessionReport, type RedactSpansOptions, type Redactor, type RefusedSession, type RepoAttrs, type RepoResolution, type RepoResolutionSource, type ResolvedOtlpInput, type RetainedSessionFile, type RetainedSessionSelection, type RunContextJournal, type RunContextNode, type RunContextResult, type RunContextSnapshot, type RunDiffOptions, type RunDiffReport, type RunDiffSide, type RunResult, type RunWatchSnapshot, type RunWatchTarget, type RuntimeTrace, SESSION_FACTS_MEASUREMENT_VERSION, SESSION_FACTS_VERSION_SUFFIX, SESSION_TOKEN_TOTAL_ATTR, SOURCE_ATTRIBUTE_PREFIX, SPAN_SYNTHESIZED_ATTR, SPAWN_JOURNAL_FILE, STATUS_MARK, SYNTHESIZED_SOURCE_ATTR, SYNTHESIZED_SPAN_ATTR, type SandboxReplayBackendOptions, type ScanOptions, type ScannedSession, type SessionBatch, type SessionBundleAbsence, type SessionBundleCarriedSignatures, type SessionBundleExclusion, type SessionBundleExclusionRule, type SessionBundleFile, type SessionBundleIssue, type SessionBundleIssueKind, type SessionBundleLeakCheck, type SessionBundleLedgerSlice, type SessionBundleManifest, type SessionBundleProjection, type SessionBundleResult, type SessionBundleSliceRule, type SessionBundleVerification, type SessionBundleView, type SessionCorruptionReceipt, type SessionEnvironment, type SessionFact, type SessionFacts, type SessionFactsOptions, type SessionFactsReport, type SessionIdentityConflict, type SessionIdentityIndex, type SessionIntegrity, type SessionMeasurements, type SessionRef, type SessionRelationship, type SessionRole, type SessionSeedGroup, type SessionSelection, type SessionSelectionRow, type SessionSourceFile, type SessionTextLeak, type SessionTextLeakReport, type SessionTurnFact, type SessionWorkflow, SessionWorkflowError, type SessionWorkflowErrorCode, type SessionWorkflowIssue, type SessionWorkflowSession, type SessionWorkflowSummary, type SkillCatalogRoots, type SkillUsageEvent, type SkillUsageHarness, type SkillUsageKind, type SkillUsageRefresh, type SkillUsageReport, type SkillUsageReportOptions, type SkillUsageRow, type SkillUsageSources, SkillUsageStore, type SkippedAnalysis, type SkippedInputFile, type SourceRecordReference, type SpanInput, type SpanNodeStatus, type SpanRedaction, type SpanRunNode, type SpanRunSnapshot, type SpanStructure, type SpawnedChildResolution, type SpendRoll, type SteeringChainReport, type SteeringEdge, type SubagentSpawnFact, TRACE_QUESTION_PREVIEW_HEAD_CHARS, type ToolMeasurement, type TraceAgenticRoute, type TraceAgenticRouteReason, type TraceAgenticRouteReasonCode, type TraceContextIndex, type TraceContextRoot, type TraceEvidenceExportOptions, type TraceEvidenceExportResult, type TraceEvidenceFormatOption, type TraceEvidenceInputFormat, type TraceEvidenceRow, type TraceExecutionOptions, type TraceFindingPacket, type TraceImprovementArtifacts, type TraceImprovementOptions, type TraceImprovementResult, type TraceIndexedFile, type TraceInspectionArea, type TraceInspectionFinding, type TraceInspectionReport, type TraceInspectionSeverity, type TraceInvestigationOptions, type TraceInvestigationResult, type TraceLiveAction, type TraceLiveActionKind, type TraceLiveAnalysisContext, type TraceLiveAnalyst, type TraceLiveBatch, type TraceLiveEvidence, type TraceLiveFinding, type TraceLiveLoop, type TraceLiveSeverity, type TraceQuestion, type TraceQuestionAnswer, type TraceQuestionCitation, type TraceQuestionFailureKind, type TraceQuestionTrace, type TraceQuestionsArtifacts, type TraceQuestionsOptions, type TraceQuestionsResult, type TraceQuestionsTotals, type TraceSessionIndex, type TraceSessionIndexRow, type TraceStoreInvestigationOptions, type TraceStoreInvestigationResult, type TraceStreamEvent, type TraceStreamOptions, type TraceStreamReplayOptions, type TraceStreamSession, type TracesConfig, type TriggerPair, type TurnActorCount, type UnavailableCapabilities, type UploadBackend, type UploadIdentity, type UploadItem, type UploadPlan, type UploadRecord, type UploadResult, type UploadState, type UserPromptInput, type VerifySafeOptions, type VerifySafeResult, type WatchRunTargetOptions, ZERO_ROLL, type ZaiChatCallerOptions, addSpend, alreadyUploaded, analysisEngineFromEnv, analyzeAdoption, analyzeLiveBatch, analyzeLoopConvergence, analyzeReactions, analyzeSpans, analyzeSteeringChain, answerSchemaErrors, applyRedactor, assembleSessionBundle, assertAnswerSchema, assertSandboxReachable, assessSpans, buildPolicyEvidenceRecord, buildSessionFactsReport, buildSessionIndex, buildSessionIndexFromRows, buildSkillUsageReport, buildSpanRunTree, buildTraceFindingPacket, capText, chatTrajectoryToSpans, classifyEvidenceOnlyPath, classifyFailureFollowUps, classifyLiveActions, classifyReaction, claudeActor, codexActor, collectPolicyEvidence, collectSessionIndex, collectSessionSelection, collectSessionWorkflow, collectSessions, commandAnalyzer, commandRedactor, commandTextFromInput, computeAdoption, computeSessionFacts, conformanceOfSpans, connectors, countSkillRunsJsonl, createBundleSourceReader, createFindingRejectionTally, cwdMatchesSelection, cwdMatchesSelectionResolved, defaultSkillCatalogRoots, defaultSkillUsageSources, defaultSkillUsageStorePath, defaultTraceLiveAnalysts, describeSessionRelationship, diffRuns, engineCallReservationFloorUsd, environmentAttributes, environmentFromSpanAttributes, equivalentGitCwds, executeUpload, exportTraceEvidenceFile, exportTraceEvidenceRows, exportTraceEvidenceText, externalFailureMessage, fileRunContextSupervisorRunReader, fileSha256, findContextRoot, findSessionTextLeaks, findSpanSourceFiles, findingRejection, findingRejectionDetail, forEachTreeNode, formatFindingRejections, formatVerdict, haloAnalyzer, hashSessionBundleFiles, hodoscopeAnalyzer, httpJsonTransport, importCodeTraceBench, incompleteInputsNote, indexSessionIdsByTrace, inferRole, inferSpanKind, inspectSessionIndex, int, isBridgeMismatchError, isChatTrajectory, isChatTrajectoryMessage, isFileRunContextDir, isInheritedSpan, isSynthesizedSpan, iterSerializedSpans, knownHarnesses, listAdapters, listSessionBundleFiles, loadState, loadTraceQuestionsFile, loadTracesConfig, locateSessions, looksLikeAgentPrompt, mergeRolls, mergeTracesConfig, ms, normalizeAnalystCitations, normalizeRemote, normalizeTraceQuestions, otlpRowToSpan, outboundHash, parseIsoToEpochMs, parseJsonAnswer, parseRetainedSession, parseSession, parseSince, planTraceAgenticRoute, planUpload, primeAnalyzer, projectSessionBundle, readCoordinationLog, readFileRunContext, readInstalledSkills, readJsonFile, readJsonl, readOtlpInput, readPullRequests, readRunContextJournal, readRunContextSnapshot, readRunSide, readRunWatchSnapshot, readSessionBundleManifest, readSessionIndexFile, readSpanRunSnapshot, recordSessionCorruption, redactSpans, renderConformance, renderInspectionReport, renderRunContextSnapshot, renderRunDiff, renderRunWatchSnapshot, renderSessionFacts, renderSessionFactsContext, renderSkillUsage, renderSpanRunSnapshot, renderTraceQuestionInstructions, renderTraceQuestionPrompt, renderTraceQuestionsReport, renderValidation, resolveAdapter, resolveOtlpInputFiles, resolveRepoAttrs, resolveRunContextDir, resolveRunWatchTarget, resolveSessionRepoAttrs, runCommand, runExternalAnalyzers, runPipelines, runTotals, runTraceImprovement, runTraceInvestigation, runTraceQuestions, runTraceStoreInvestigation, sandboxReplayBackend, saveReport, saveState, scanSessions, selectAdapters, serializeInspectionReport, serializePolicyEvidence, serializeSessionIndex, serializeSpans, serializeTraceStreamEvent, sessionFactsContext, sessionHash, sessionIdFromAttributes, sessionIntegrityAttributes, sessionTextSignatures, shellCommands, skippedAnalyses, span, spanRunTotals, stampEnvironmentAttrs, stampRepoAttrs, stampSessionIdentity, stampSessionIntegrity, stampSpanWorkdirRepoAttrs, streamSessions, summarizeOtlpExecution, summarizeSpanExecution, summarizeSpanStructure, takeJsonl, textIsSynthetic, toOpenInferenceSpan, toRuntimeStore, toTraceSpanEvents, tokens, totalAgenticFailureMessage, totalFindingRejections, traceAgenticKinds, traceCitationsInText, traceStreamEventsFromSpans, unavailableCapabilities, uploadKey, usd, userPromptSpan, validateOtlpSpans, validationExitCode, verdictExitCode, verifyFindingsInSandbox, verifySafe, verifySessionBundle, watchRunTarget, watchSessions, withSessionFactsContext, wrap, writeHodoscopeInput, writeInspectionReportFile, writeOtlpFile, writePolicyEvidenceFile, writeSessionBundleManifest, writeSessionIndexFile, writeTraceEvidenceExportFile, writeTraceImprovementArtifacts, writeTraceQuestionsArtifacts, zaiChatCaller, zeroPricedCost };
5220
+ export { ACTOR_ATTR, ANSWERED_TEXT_CAP, ATTR, type Actor, type AdoptionOptions, type AdoptionReport, AmpAdapter, type AnalysisEngineFromEnvOptions, type AnalyzeOptions, type AnalyzeResult, type AnswerSchema, type AssembleSessionBundleOptions, BLOBS_DIR, type BuildPolicyEvidenceOptions, type BuildSessionIndexOptions, type BuildTraceFindingPacketOptions, CHANGED_FILE_LIST_CAP, CITATION_NORMALIZATION_VERSION, CONTENT_CAP, COORDINATION_LOG_FILE, CORRECTIVE_REACTIONS, type ChangedFileFact, type ChatTrajectory, type ChatTrajectoryMessage, type ChatTrajectoryOptions, ClaudeAdapter, type CodeTraceBenchFileRef, type CodeTraceBenchImportOptions, type CodeTraceBenchImportReceipt, type CodeTraceBenchImportResult, type CodeTraceBenchImportedTrace, type CodeTraceBenchStep, CodexAdapter, CodexExecAdapter, CodexExecStreamError, CodexTaskScopeError, type CollectOptions, type CollectPolicyEvidenceOptions, type CollectSessionIndexOptions, type CollectSessionSelectionOptions, type CollectSessionWorkflowOptions, type CompactionMeasurement, type ConformanceRenderOptions, type ContentFootprint, type CoordinationLog, type CoordinationQuestion, type CoordinationSteer, CopilotAdapter, type CorruptionMode, type CoveredMeasurement, DEFAULT_ANALYST_MODEL, DEFAULT_ASK_CONCURRENCY, DEFAULT_HARNESS, DEFAULT_SANDBOX_BASE_URL, DEFAULT_SWEEP_THRESHOLD, EMPTY_SPAN_RUN_SNAPSHOT, EXPORT_RULE, EmptySessionError, type EvidenceOnlyVerdict, type ExcludedTurnFact, type ExecuteOptions, type ExportRule, type ExternalAnalysisKind, type ExternalAnalysisPayload, type ExternalAnalysisResult, type ExternalAnalyzer, type ExternalAnalyzerOptions, type ExternalDiscoveryCandidate, FACT_LIST_CAP, FACT_TEXT_CAP, FactoryAdapter, type FailureFollowUp, type FailureFollowUpReport, type FileRunContextReaderOptions, type FinalMessageFact, type FindSessionTextLeaksOptions, type FindingRejectionCounts, type FindingRejectionReasons, type FindingRejectionTally, type FindingsFileOutcome, type FollowUpKind, GeminiAdapter, type HaloAnalyzerOptions, type HaloReasoningEffort, HarnessSessionsAdapter, type HarnessTraceAdapter, type HodoscopeAnalyzerOptions, type HodoscopeInput, type HodoscopeMessage, type HodoscopeSourceMapEntry, type HodoscopeTrajectory, INGEST_SOURCE_CLI, INHERITED_SOURCE_ATTR, INHERITED_SPANS_OMITTED_ATTR, INHERITED_SPAN_ATTR, INHERITED_SPAN_COUNT_ATTR, type InheritedSpanSource, type InspectSessionIndexOptions, type InstalledSkill, JsonSourceError, type JsonlCorruptionReceipt, JsonlParseError, type JsonlReadOptions, type LiveBatchOptions, type LocateOptions, type LoopConvergence, type LoopConvergenceReport, type LoopIteration, type LoopTrend, type LoopTrendBasis, MAX_SHELL_DEPTH, MAX_TRACE_QUESTION_CHARS, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpIngestIssue, type OtlpIngestIssueKind, type OtlpInput, type OtlpInputFile, type OtlpRowConversion, type OtlpSpan, type OtlpSpanKind, type OtlpSpanLink, type OtlpStatusCode, POLICY_EVIDENCE_NOTE, PREPARED_CONTEXT_BYTE_CEILING, type ParentTaskResolution, type ParseOptions, PartialUploadError, PiAdapter, type PiAdapterOptions, type PipelineOptions, type PipelineReport, type PlanOptions, type PolicyEvidenceLoopSummary, type PolicyEvidenceRecord, type PolicyEvidenceToolSummary, type PrimeAnalyzerOptions, type PrimeTransport, type PrimeTransportRequest, type PrimeTransportResponse, type ProjectSessionBundleOptions, type ProjectSessionBundleResult, type PullRequestFact, type PullRequestFacts, type PullRequestReading, QwenAdapter, REACTIONS, RESULT_FILE, RETAINED_SESSION_READER_VERSION, RULE, type Reaction, type ReactionReport, type ReactionSessionReport, type RedactSpansOptions, type Redactor, type RefusedSession, type RepoAttrs, type RepoResolution, type RepoResolutionSource, type ResolvedOtlpInput, type RetainedSessionFile, type RetainedSessionSelection, type RunContextJournal, type RunContextNode, type RunContextResult, type RunContextSnapshot, type RunDiffOptions, type RunDiffReport, type RunDiffSide, type RunResult, type RunWatchSnapshot, type RunWatchTarget, type RuntimeTrace, SESSION_CWD_ATTR, SESSION_FACTS_MEASUREMENT_VERSION, SESSION_FACTS_VERSION_SUFFIX, SESSION_TOKEN_TOTAL_ATTR, SOURCE_ATTRIBUTE_PREFIX, SPAN_SYNTHESIZED_ATTR, SPAWN_JOURNAL_FILE, STATUS_MARK, SYNTHESIZED_SOURCE_ATTR, SYNTHESIZED_SPAN_ATTR, type SandboxReplayBackendOptions, type ScanOptions, type ScannedSession, type SessionBatch, type SessionBundleAbsence, type SessionBundleCarriedSignatures, type SessionBundleExclusion, type SessionBundleExclusionRule, type SessionBundleFile, type SessionBundleIssue, type SessionBundleIssueKind, type SessionBundleLeakCheck, type SessionBundleLedgerSlice, type SessionBundleManifest, type SessionBundleProjection, type SessionBundleResult, type SessionBundleSliceRule, type SessionBundleVerification, type SessionBundleView, type SessionCorruptionReceipt, type SessionEnvironment, type SessionFact, type SessionFacts, type SessionFactsOptions, type SessionFactsReport, type SessionFactsReportBuilder, type SessionFactsReportOptions, type SessionFactsSelection, type SessionFactsWindow, type SessionIdentityConflict, type SessionIdentityIndex, type SessionIntegrity, type SessionMeasurements, type SessionRef, type SessionRelationship, type SessionRole, type SessionSeedGroup, type SessionSelection, type SessionSelectionRow, type SessionSourceFile, type SessionTextLeak, type SessionTextLeakReport, type SessionTurnFact, type SessionWorkflow, SessionWorkflowError, type SessionWorkflowErrorCode, type SessionWorkflowIssue, type SessionWorkflowSession, type SessionWorkflowSummary, type SkillCatalogRoots, type SkillUsageEvent, type SkillUsageHarness, type SkillUsageKind, type SkillUsageRefresh, type SkillUsageReport, type SkillUsageReportOptions, type SkillUsageRow, type SkillUsageSources, SkillUsageStore, type SkippedAnalysis, type SkippedInputFile, type SourceRecordReference, type SpanInput, type SpanNodeStatus, type SpanRedaction, type SpanRunNode, type SpanRunSnapshot, type SpanStructure, type SpawnedChildResolution, type SpendRoll, type SteeringChainReport, type SteeringEdge, type SubagentSpawnFact, TRACE_QUESTION_PREVIEW_HEAD_CHARS, type ToolMeasurement, type TraceAgenticRoute, type TraceAgenticRouteReason, type TraceAgenticRouteReasonCode, type TraceContextIndex, type TraceContextRoot, type TraceEvidenceExportOptions, type TraceEvidenceExportResult, type TraceEvidenceFormatOption, type TraceEvidenceInputFormat, type TraceEvidenceRow, type TraceExecutionOptions, type TraceFindingPacket, type TraceImprovementArtifacts, type TraceImprovementOptions, type TraceImprovementResult, type TraceIndexedFile, type TraceInspectionArea, type TraceInspectionFinding, type TraceInspectionReport, type TraceInspectionSeverity, type TraceInvestigationOptions, type TraceInvestigationResult, type TraceLiveAction, type TraceLiveActionKind, type TraceLiveAnalysisContext, type TraceLiveAnalyst, type TraceLiveBatch, type TraceLiveEvidence, type TraceLiveFinding, type TraceLiveLoop, type TraceLiveSeverity, type TraceQuestion, type TraceQuestionAnswer, type TraceQuestionCitation, type TraceQuestionFailureKind, type TraceQuestionTrace, type TraceQuestionsArtifacts, type TraceQuestionsOptions, type TraceQuestionsResult, type TraceQuestionsTotals, type TraceSessionIndex, type TraceSessionIndexRow, type TraceStoreInvestigationOptions, type TraceStoreInvestigationResult, type TraceStreamEvent, type TraceStreamOptions, type TraceStreamReplayOptions, type TraceStreamSession, type TracesConfig, type TriggerPair, type TurnActorCount, type UnavailableCapabilities, type UnreadSessionFact, type UploadBackend, type UploadIdentity, type UploadItem, type UploadPlan, type UploadRecord, type UploadResult, type UploadState, type UserPromptInput, type VerifySafeOptions, type VerifySafeResult, type WatchRunTargetOptions, ZERO_ROLL, type ZaiChatCallerOptions, addSpend, alreadyUploaded, analysisEngineFromEnv, analyzeAdoption, analyzeLiveBatch, analyzeLoopConvergence, analyzeReactions, analyzeSpans, analyzeSteeringChain, answerSchemaErrors, applyRedactor, assembleSessionBundle, assertAnswerSchema, assertSandboxReachable, assessSpans, buildPolicyEvidenceRecord, buildSessionFactsReport, buildSessionIndex, buildSessionIndexFromRows, buildSkillUsageReport, buildSpanRunTree, buildTraceFindingPacket, capText, chatTrajectoryToSpans, classifyEvidenceOnlyPath, classifyFailureFollowUps, classifyLiveActions, classifyReaction, claudeActor, codexActor, collectPolicyEvidence, collectSessionIndex, collectSessionSelection, collectSessionWorkflow, collectSessions, commandAnalyzer, commandRedactor, commandTextFromInput, computeAdoption, computeSessionFacts, conformanceOfSpans, connectors, countSkillRunsJsonl, createBundleSourceReader, createFindingRejectionTally, createSessionFactsReportBuilder, cwdMatchesSelection, cwdMatchesSelectionResolved, defaultSkillCatalogRoots, defaultSkillUsageSources, defaultSkillUsageStorePath, defaultTraceLiveAnalysts, describeSessionRelationship, diffRuns, engineCallReservationFloorUsd, environmentAttributes, environmentFromSpanAttributes, equivalentGitCwds, executeUpload, exportTraceEvidenceFile, exportTraceEvidenceRows, exportTraceEvidenceText, externalFailureMessage, fileRunContextSupervisorRunReader, fileSha256, findContextRoot, findSessionTextLeaks, findSpanSourceFiles, findingRejection, findingRejectionDetail, forEachTreeNode, formatFindingRejections, formatVerdict, haloAnalyzer, hashSessionBundleFiles, hodoscopeAnalyzer, httpJsonTransport, importCodeTraceBench, incompleteInputsNote, indexSessionIdsByTrace, inferRole, inferSpanKind, inspectSessionIndex, int, isBridgeMismatchError, isChatTrajectory, isChatTrajectoryMessage, isFileRunContextDir, isInheritedSpan, isSynthesizedSpan, iterSerializedSpans, knownHarnesses, listAdapters, listSessionBundleFiles, loadState, loadTraceQuestionsFile, loadTracesConfig, locateSessions, looksLikeAgentPrompt, mergeRolls, mergeTracesConfig, ms, normalizeAnalystCitations, normalizeRemote, normalizeTraceQuestions, otlpRowToSpan, outboundHash, parseIsoToEpochMs, parseJsonAnswer, parseRetainedSession, parseSession, parseSince, parseTimeBound, planTraceAgenticRoute, planUpload, primeAnalyzer, projectSessionBundle, readCoordinationLog, readFileRunContext, readInstalledSkills, readJsonFile, readJsonl, readOtlpInput, readPullRequests, readRunContextJournal, readRunContextSnapshot, readRunSide, readRunWatchSnapshot, readSessionBundleManifest, readSessionIndexFile, readSpanRunSnapshot, recordSessionCorruption, redactSpans, renderConformance, renderInspectionReport, renderRunContextSnapshot, renderRunDiff, renderRunWatchSnapshot, renderSessionFacts, renderSessionFactsContext, renderSkillUsage, renderSpanRunSnapshot, renderTraceQuestionInstructions, renderTraceQuestionPrompt, renderTraceQuestionsReport, renderValidation, resolveAdapter, resolveOtlpInputFiles, resolveRepoAttrs, resolveRunContextDir, resolveRunWatchTarget, resolveSessionRepoAttrs, runCommand, runExternalAnalyzers, runPipelines, runTotals, runTraceImprovement, runTraceInvestigation, runTraceQuestions, runTraceStoreInvestigation, sandboxReplayBackend, saveReport, saveState, scanSessions, selectAdapters, serializeInspectionReport, serializePolicyEvidence, serializeSessionIndex, serializeSpans, serializeTraceStreamEvent, sessionFactsContext, sessionHash, sessionIdFromAttributes, sessionIntegrityAttributes, sessionTextSignatures, shellCommands, skippedAnalyses, span, spanRunTotals, stampEnvironmentAttrs, stampRepoAttrs, stampSessionIdentity, stampSessionIntegrity, stampSpanWorkdirRepoAttrs, streamSessions, summarizeOtlpExecution, summarizeSpanExecution, summarizeSpanStructure, takeJsonl, textIsSynthetic, toOpenInferenceSpan, toRuntimeStore, toTraceSpanEvents, tokens, totalAgenticFailureMessage, totalFindingRejections, traceAgenticKinds, traceCitationsInText, traceStreamEventsFromSpans, unavailableCapabilities, uploadKey, usd, userPromptSpan, validateOtlpSpans, validationExitCode, verdictExitCode, verifyFindingsInSandbox, verifySafe, verifySessionBundle, watchRunTarget, watchSessions, withSessionFactsContext, wrap, writeHodoscopeInput, writeInspectionReportFile, writeOtlpFile, writePolicyEvidenceFile, writeSessionBundleManifest, writeSessionIndexFile, writeTraceEvidenceExportFile, writeTraceImprovementArtifacts, writeTraceQuestionsArtifacts, zaiChatCaller, zeroPricedCost };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ACTOR_ATTR,
3
+ ANSWERED_TEXT_CAP,
3
4
  ATTR,
4
5
  AmpAdapter,
5
6
  BLOBS_DIR,
@@ -43,6 +44,7 @@ import {
43
44
  REACTIONS,
44
45
  RESULT_FILE,
45
46
  RULE,
47
+ SESSION_CWD_ATTR,
46
48
  SESSION_FACTS_MEASUREMENT_VERSION,
47
49
  SESSION_FACTS_VERSION_SUFFIX,
48
50
  SESSION_TOKEN_TOTAL_ATTR,
@@ -100,6 +102,7 @@ import {
100
102
  countSkillRunsJsonl,
101
103
  createBundleSourceReader,
102
104
  createFindingRejectionTally,
105
+ createSessionFactsReportBuilder,
103
106
  cwdMatchesSelection,
104
107
  cwdMatchesSelectionResolved,
105
108
  defaultSkillCatalogRoots,
@@ -167,6 +170,7 @@ import {
167
170
  parseJsonAnswer,
168
171
  parseSession,
169
172
  parseSince,
173
+ parseTimeBound,
170
174
  planTraceAgenticRoute,
171
175
  planUpload,
172
176
  primeAnalyzer,
@@ -283,7 +287,7 @@ import {
283
287
  writeTraceQuestionsArtifacts,
284
288
  zaiChatCaller,
285
289
  zeroPricedCost
286
- } from "./chunk-DON6NQ7H.js";
290
+ } from "./chunk-COSWFESO.js";
287
291
 
288
292
  // src/adapters/factory.ts
289
293
  import { readdir, stat } from "fs/promises";
@@ -936,6 +940,7 @@ async function parseRetainedSession(input) {
936
940
  }
937
941
  export {
938
942
  ACTOR_ATTR,
943
+ ANSWERED_TEXT_CAP,
939
944
  ATTR,
940
945
  AmpAdapter,
941
946
  AnalystRegistry,
@@ -983,6 +988,7 @@ export {
983
988
  RESULT_FILE,
984
989
  RETAINED_SESSION_READER_VERSION,
985
990
  RULE,
991
+ SESSION_CWD_ATTR,
986
992
  SESSION_FACTS_MEASUREMENT_VERSION,
987
993
  SESSION_FACTS_VERSION_SUFFIX,
988
994
  SESSION_TOKEN_TOTAL_ATTR,
@@ -1044,6 +1050,7 @@ export {
1044
1050
  createBundleSourceReader,
1045
1051
  createFindingRejectionTally,
1046
1052
  createHostedClient,
1053
+ createSessionFactsReportBuilder,
1047
1054
  cwdMatchesSelection,
1048
1055
  cwdMatchesSelectionResolved,
1049
1056
  defaultSkillCatalogRoots,
@@ -1112,6 +1119,7 @@ export {
1112
1119
  parseRetainedSession,
1113
1120
  parseSession,
1114
1121
  parseSince,
1122
+ parseTimeBound,
1115
1123
  planTraceAgenticRoute,
1116
1124
  planUpload,
1117
1125
  primeAnalyzer,
@@ -198,7 +198,8 @@ The sheet is that extraction, made part of the tool.
198
198
  | `subagentToolSpans` | TOOL spans excluded because a subagent, not this agent, made the call |
199
199
  | `subagents` | every subagent-spawning call, with the task name the call recorded |
200
200
  | `pullRequests` | pull requests the commands created and merged, each named by number or head branch, with the command span and the join evidence |
201
- | `humanTurns` | `user.prompt` turns a person typed into this session, in order, with timestamps |
201
+ | `cwd` | the working directory the harness recorded for the session, or `null` with the reason |
202
+ | `humanTurns` | `user.prompt` turns a person typed into this session, in order, with timestamps, each with the agent message it `answered` |
202
203
  | `excludedTurns` | every `user.prompt` turn `humanTurns` left out, grouped by the reason, with the span ids |
203
204
  | `turnsByActor` | every `user.prompt` turn by actor, so the human filter is checkable |
204
205
  | `finalMessages` | the last message of the session's own agent, and of each subagent task, separately |
@@ -242,7 +243,7 @@ It reaches the built-in kinds run by `analyze --llm`, `investigate`, and `improv
242
243
 
243
244
  - The sheet is bounded at `PREPARED_CONTEXT_BYTE_CEILING` (30,000 bytes), a fifth of the `perCallByteCeiling` of 150,000 the trace tools work to, so the rest of the budget stays available for the tool calls the model still makes.
244
245
  - When the sheet does not fit, fields are shed from the largest downward and the shed is listed in `omitted_fields`. The tool-call counts are the last facts to go.
245
- - Receiving the sheet changes an analyst's behavior, so its version carries `+session-facts.2`. `createTraceAnalyst` records `prepare_context` in the exact-run identity, and a changed prepared context must not hide behind an unchanged version.
246
+ - Receiving the sheet changes an analyst's behavior, so its version carries `SESSION_FACTS_VERSION_SUFFIX` (`+session-facts.` and the sheet's `measurementVersion`). `createTraceAnalyst` records `prepare_context` in the exact-run identity, and a changed prepared context must not hide behind an unchanged version.
246
247
  - The sheet is not evidence. Citations still resolve against the raw spans, which is why every fact names its span ids rather than asking the model to trust the sheet.
247
248
 
248
249
  Pass `sessionFactsContext: false` to `analyzeSpans` to run an analyst without it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/traces",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Point it at your coding-agent session traces (Claude Code, Codex, OpenCode, Gemini, Pi, \u2026) and get failure-mode + efficiency findings. CLI + SDK over the @tangle-network/agent-eval analyst suite \u2014 observe live sessions, run your own analysts, redact, and upload to the Tangle Intelligence Platform.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -65,7 +65,7 @@
65
65
  "@tangle-network/agent-interface": "5.2.0",
66
66
  "@tangle-network/agent-runtime": "0.318.0",
67
67
  "@tangle-network/agent-trace-contract": "2.0.0",
68
- "@tangle-network/harness-sessions": "0.1.0",
68
+ "@tangle-network/harness-sessions": "0.1.2",
69
69
  "@tangle-network/sandbox": "0.60.49"
70
70
  },
71
71
  "devDependencies": {