@ascenda-one/history-import 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -429,6 +429,16 @@ var require_metricKeys = __commonJS({
429
429
  linesChangedBucket: { readBy: ["backend"], backendAliases: ["linesChangedBucket", "lines_changed_bucket"] },
430
430
  // ── Read by the local handoff only ──────────────────────────────────────
431
431
  activeMinutes: { readBy: ["handoff"], unit: "minutes" },
432
+ syntheticPromptLines: {
433
+ readBy: ["handoff"],
434
+ unit: "lines",
435
+ note: "Main-thread user lines the runtime wrote on the person's behalf (notifications, its own bookkeeping, slash-command wrappers, interrupt markers) and promptCount left out. Claude Code only; the receipt for the handoff's promptBasis: typed."
436
+ },
437
+ interruptedRuns: {
438
+ readBy: ["handoff"],
439
+ unit: "runs",
440
+ note: "Agent turns the person cut short with an interrupt while the turn was still going. A count per session, never a rate. Claude Code only; a handoff that counted them says so with interruptedRunsCounted, and absent means not counted, never zero."
441
+ },
432
442
  /**
433
443
  * The two halves of `activeMinutes`, and deliberately two keys.
434
444
  *
@@ -715,6 +725,11 @@ var require_out = __commonJS({
715
725
  context_compression_manual: "neutral",
716
726
  context_compression_auto: "neutral",
717
727
  editor_activity: "neutral",
728
+ // The agent finished a turn and handed back to the person. A boundary, not
729
+ // work: it closes the agent's stretch and opens the person's, which the next
730
+ // `ai_prompt_submitted` closes. Neutral so a marker sent once per turn
731
+ // doesn't weigh on any workload leg.
732
+ ai_turn_completed: "neutral",
718
733
  // Semantic (agent-observed) — see SEMANTIC_WORK_SIGNAL_EVENT_TYPES.
719
734
  approach_churn_detected: "risk",
720
735
  goal_drift_detected: "risk",
@@ -4263,9 +4278,14 @@ function activeSpans(points, options) {
4263
4278
  const existing = collapsed.get(point.at);
4264
4279
  if (existing) {
4265
4280
  existing.human = existing.human || point.human;
4281
+ existing.agentOutput = existing.agentOutput || point.agentOutput;
4266
4282
  if (point.autonomyMode) existing.autonomyMode = point.autonomyMode;
4267
4283
  } else {
4268
- collapsed.set(point.at, { human: point.human, autonomyMode: point.autonomyMode });
4284
+ collapsed.set(point.at, {
4285
+ human: point.human,
4286
+ agentOutput: point.agentOutput,
4287
+ autonomyMode: point.autonomyMode
4288
+ });
4269
4289
  }
4270
4290
  }
4271
4291
  const instants = [...collapsed.keys()].sort((a, b) => a - b);
@@ -4278,14 +4298,22 @@ function activeSpans(points, options) {
4278
4298
  if (instants.length === 0) return report;
4279
4299
  let posture = collapsed.get(instants[0])?.autonomyMode ?? null;
4280
4300
  if (posture === null) report.unposturedInstants += 1;
4301
+ let runStart = 0;
4281
4302
  for (let i = 1; i < instants.length; i += 1) {
4282
4303
  const from = instants[i - 1];
4283
4304
  const to = instants[i];
4284
4305
  const here = collapsed.get(to);
4285
4306
  const gap = to - from;
4286
4307
  if (gap > 0 && gap <= options.activeGapMs) {
4287
- report.spans.push({ from, to, handsOn: here.human, band: (0, import_tool_kit2.autonomyBand)(posture) });
4308
+ report.spans.push({ from, to, handsOn: false, band: (0, import_tool_kit2.autonomyBand)(posture) });
4309
+ } else {
4310
+ runStart = report.spans.length;
4311
+ }
4312
+ if (here.human) {
4313
+ for (let s = runStart; s < report.spans.length; s += 1) report.spans[s].handsOn = true;
4314
+ runStart = report.spans.length;
4288
4315
  }
4316
+ if (here.agentOutput) runStart = report.spans.length;
4289
4317
  if (here.autonomyMode) posture = here.autonomyMode;
4290
4318
  else if (posture === null) report.unposturedInstants += 1;
4291
4319
  }
@@ -4337,6 +4365,17 @@ function addSpanByLocalDay(into, from, to) {
4337
4365
  cursor = segmentEnd;
4338
4366
  }
4339
4367
  }
4368
+ function localDayCounts(timestamps) {
4369
+ const counts = /* @__PURE__ */ new Map();
4370
+ for (const t of timestamps) {
4371
+ if (typeof t !== "string" || t.length === 0) continue;
4372
+ const d = new Date(t);
4373
+ if (!Number.isFinite(d.getTime())) continue;
4374
+ const key = localDayKey(d);
4375
+ counts.set(key, (counts.get(key) ?? 0) + 1);
4376
+ }
4377
+ return counts;
4378
+ }
4340
4379
  function sliceSessionByLocalDay(timestamps, options = {}) {
4341
4380
  const instants = timestamps.filter((t) => typeof t === "string" && t.length > 0).map((t) => new Date(t)).filter((d) => Number.isFinite(d.getTime())).sort((a, b) => a.getTime() - b.getTime());
4342
4381
  if (instants.length === 0) return [];
@@ -4351,7 +4390,9 @@ function sliceSessionByLocalDay(timestamps, options = {}) {
4351
4390
  const gapMs = options.activeGapMs;
4352
4391
  const classified = options.activeInstants;
4353
4392
  if (gapMs !== void 0) {
4354
- const points = classified ?? instants.map((d) => ({ at: d.getTime(), human: true, autonomyMode: null }));
4393
+ const points = classified ?? // Prompt-only instants are never split, so the classification is moot;
4394
+ // `agentOutput: false` is stated so the type says what this path knows.
4395
+ instants.map((d) => ({ at: d.getTime(), human: true, agentOutput: false, autonomyMode: null }));
4355
4396
  for (const span of activeSpans(points, { activeGapMs: gapMs }).spans) {
4356
4397
  const from = new Date(span.from);
4357
4398
  const to = new Date(span.to);
@@ -4359,7 +4400,8 @@ function sliceSessionByLocalDay(timestamps, options = {}) {
4359
4400
  if (classified) addSpanByLocalDay(span.handsOn ? handsOnMs : supervisingMs, from, to);
4360
4401
  }
4361
4402
  }
4362
- const days = [.../* @__PURE__ */ new Set([...prompts.keys(), ...activeMs.keys()])].sort();
4403
+ const cuts = options.interruptedRunTimestamps ? localDayCounts(options.interruptedRunTimestamps) : null;
4404
+ const days = [.../* @__PURE__ */ new Set([...prompts.keys(), ...activeMs.keys(), ...cuts?.keys() ?? []])].sort();
4363
4405
  return days.map((day) => {
4364
4406
  const slice = { day, prompts: prompts.get(day) ?? 0 };
4365
4407
  if (gapMs !== void 0) {
@@ -4369,10 +4411,87 @@ function sliceSessionByLocalDay(timestamps, options = {}) {
4369
4411
  slice.agentSupervisingMinutes = Math.round((supervisingMs.get(day) ?? 0) / 6e4);
4370
4412
  }
4371
4413
  }
4414
+ if (cuts) slice.interruptedRuns = cuts.get(day) ?? 0;
4372
4415
  return slice;
4373
4416
  });
4374
4417
  }
4375
4418
 
4419
+ // src/interruptedRuns.ts
4420
+ var RUNTIME_WRAPPER_ELEMENTS = [
4421
+ "system-reminder",
4422
+ "command-name",
4423
+ "command-message",
4424
+ "command-args",
4425
+ "local-command-stdout",
4426
+ "local-command-stderr",
4427
+ "local-command-caveat"
4428
+ ];
4429
+ var INTERRUPTION_MARKER = /^\[Request interrupted by[^\]]*\]$/;
4430
+ function typedRemainderOf(record) {
4431
+ const message = record.message;
4432
+ if (typeof message !== "object" || message === null) return null;
4433
+ const content = message.content;
4434
+ let text = "";
4435
+ if (typeof content === "string") {
4436
+ text = content;
4437
+ } else if (Array.isArray(content)) {
4438
+ for (const item of content) {
4439
+ if (typeof item !== "object" || item === null) return null;
4440
+ const block = item;
4441
+ if (block.type !== "text") return null;
4442
+ if (typeof block.text === "string") text = text === "" ? block.text : `${text}
4443
+ ${block.text}`;
4444
+ }
4445
+ } else {
4446
+ return null;
4447
+ }
4448
+ for (const element of RUNTIME_WRAPPER_ELEMENTS) {
4449
+ text = text.replace(new RegExp(`<${element}>[\\s\\S]*?</${element}>`, "g"), "");
4450
+ }
4451
+ return text.trim();
4452
+ }
4453
+ function isInterruptionMarkerLine(record) {
4454
+ const remainder = typedRemainderOf(record);
4455
+ return remainder !== null && INTERRUPTION_MARKER.test(remainder);
4456
+ }
4457
+ function isRuntimeOrigin(record) {
4458
+ const origin = record.origin;
4459
+ if (typeof origin !== "object" || origin === null) return false;
4460
+ const kind = origin.kind;
4461
+ return kind === "task-notification" || kind === "peer";
4462
+ }
4463
+ function isTypedPromptLine(record) {
4464
+ if (isRuntimeOrigin(record)) return false;
4465
+ if (record.isMeta === true || record.isCompactSummary === true) return false;
4466
+ if (isInterruptionMarkerLine(record)) return false;
4467
+ return typedRemainderOf(record) !== "";
4468
+ }
4469
+ function userLineStartsTurn(record) {
4470
+ return isRuntimeOrigin(record) || isTypedPromptLine(record);
4471
+ }
4472
+ function stepRun(running, record, isToolResult) {
4473
+ switch (record.type) {
4474
+ case "assistant": {
4475
+ const message = record.message;
4476
+ const stop = message?.stop_reason;
4477
+ return { running: stop === "tool_use" || stop === null || stop === void 0, cut: false };
4478
+ }
4479
+ case "system":
4480
+ if (record.subtype === "stop_hook_summary" || record.subtype === "turn_duration") {
4481
+ return { running: false, cut: false };
4482
+ }
4483
+ return { running, cut: false };
4484
+ case "user":
4485
+ if (isToolResult(record)) return { running: true, cut: false };
4486
+ if (isInterruptionMarkerLine(record)) {
4487
+ return { running: false, cut: running && record.interruptedByShutdown !== true };
4488
+ }
4489
+ return { running: running || userLineStartsTurn(record), cut: false };
4490
+ default:
4491
+ return { running, cut: false };
4492
+ }
4493
+ }
4494
+
4376
4495
  // src/extractors/claudeCode.ts
4377
4496
  var KNOWN_CLAUDE_LINE_TYPES = [
4378
4497
  "user",
@@ -4485,6 +4604,7 @@ function newFold(sessionId, projectSlug) {
4485
4604
  firstTs: null,
4486
4605
  lastTs: null,
4487
4606
  humanPrompts: 0,
4607
+ syntheticPromptLines: 0,
4488
4608
  afterHoursPrompts: 0,
4489
4609
  assistantTurns: 0,
4490
4610
  toolResults: 0,
@@ -4516,7 +4636,9 @@ function newFold(sessionId, projectSlug) {
4516
4636
  toolCalls: [],
4517
4637
  timelinePoints: [],
4518
4638
  undatedTimelineLines: 0,
4519
- humanPromptTimestamps: []
4639
+ humanPromptTimestamps: [],
4640
+ interruptedRuns: 0,
4641
+ interruptedRunTimestamps: []
4520
4642
  };
4521
4643
  }
4522
4644
  function asNumber(value) {
@@ -4557,6 +4679,7 @@ function contextWindowPeakPctOf(fold) {
4557
4679
  }
4558
4680
  async function foldLinesInto(fold, filePath, opts) {
4559
4681
  const handle = await fs4.open(filePath);
4682
+ let turnRunning = false;
4560
4683
  try {
4561
4684
  for await (const line of handle.readLines({ encoding: "utf8" })) {
4562
4685
  const sniffed = sniffClaudeLine(line);
@@ -4582,6 +4705,7 @@ async function foldLinesInto(fold, filePath, opts) {
4582
4705
  }
4583
4706
  let humanHere = false;
4584
4707
  let postureHere = null;
4708
+ let agentOutputHere = false;
4585
4709
  switch (sniffed.kind) {
4586
4710
  case "user": {
4587
4711
  const record = JSON.parse(line);
@@ -4589,8 +4713,17 @@ async function foldLinesInto(fold, filePath, opts) {
4589
4713
  if (typeof record.gitBranch === "string" && !fold.gitBranch) {
4590
4714
  fold.gitBranch = record.gitBranch;
4591
4715
  }
4716
+ if (!opts.isSidechain) {
4717
+ const step = stepRun(turnRunning, record, isToolResultUserLine);
4718
+ turnRunning = step.running;
4719
+ if (step.cut) {
4720
+ fold.interruptedRuns += 1;
4721
+ fold.interruptedRunTimestamps.push(sniffed.occurredAt);
4722
+ }
4723
+ }
4592
4724
  if (isToolFailureLine(record)) fold.toolResultErrorCount += 1;
4593
4725
  if (isToolResultUserLine(record)) {
4726
+ agentOutputHere = true;
4594
4727
  fold.toolResults += 1;
4595
4728
  const tur = record.toolUseResult;
4596
4729
  if (tur && typeof tur === "object" && !Array.isArray(tur)) {
@@ -4601,7 +4734,13 @@ async function foldLinesInto(fold, filePath, opts) {
4601
4734
  }
4602
4735
  }
4603
4736
  } else if (opts.isSidechain) {
4737
+ agentOutputHere = true;
4604
4738
  fold.subagentPrompts += 1;
4739
+ } else if (!isTypedPromptLine(record)) {
4740
+ fold.syntheticPromptLines += 1;
4741
+ if (typeof record.permissionMode === "string" && record.permissionMode !== "") {
4742
+ postureHere = snakeCasePermissionMode(record.permissionMode);
4743
+ }
4605
4744
  } else {
4606
4745
  fold.humanPrompts += 1;
4607
4746
  if (sniffed.occurredAt && (0, import_tool_kit3.isOutsideBusinessHours)(new Date(sniffed.occurredAt))) {
@@ -4616,7 +4755,9 @@ async function foldLinesInto(fold, filePath, opts) {
4616
4755
  break;
4617
4756
  }
4618
4757
  case "assistant": {
4758
+ agentOutputHere = true;
4619
4759
  const record = JSON.parse(line);
4760
+ if (!opts.isSidechain) turnRunning = stepRun(turnRunning, record, isToolResultUserLine).running;
4620
4761
  const usage = record.message?.usage;
4621
4762
  const toolNames = toolUseNamesOf(record);
4622
4763
  if (toolNames.length > 0) {
@@ -4654,7 +4795,9 @@ async function foldLinesInto(fold, filePath, opts) {
4654
4795
  break;
4655
4796
  }
4656
4797
  case "system": {
4798
+ agentOutputHere = true;
4657
4799
  const record = JSON.parse(line);
4800
+ if (!opts.isSidechain) turnRunning = stepRun(turnRunning, record, isToolResultUserLine).running;
4658
4801
  if (record.subtype === "compact_boundary") {
4659
4802
  fold.compactionCount += 1;
4660
4803
  const trigger = record.compactMetadata?.trigger;
@@ -4680,7 +4823,12 @@ async function foldLinesInto(fold, filePath, opts) {
4680
4823
  if (sniffed.occurredAt) {
4681
4824
  const ms = Date.parse(sniffed.occurredAt);
4682
4825
  if (Number.isFinite(ms)) {
4683
- fold.timelinePoints.push({ at: ms, human: humanHere, autonomyMode: postureHere });
4826
+ fold.timelinePoints.push({
4827
+ at: ms,
4828
+ human: humanHere,
4829
+ agentOutput: agentOutputHere,
4830
+ autonomyMode: postureHere
4831
+ });
4684
4832
  } else {
4685
4833
  fold.undatedTimelineLines += 1;
4686
4834
  }
@@ -4813,6 +4961,7 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
4813
4961
  const split = activeSplitOf(fold);
4814
4962
  const sessionMetrics = {
4815
4963
  promptCount: fold.humanPrompts,
4964
+ syntheticPromptLines: fold.syntheticPromptLines,
4816
4965
  assistantTurns: fold.assistantTurns,
4817
4966
  toolCallCount: fold.toolCallCount,
4818
4967
  toolResultCount: fold.toolResults,
@@ -4840,6 +4989,7 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
4840
4989
  linesChangedBucket: (0, import_tool_kit3.bucketLinesChanged)(fold.linesChangedTotal),
4841
4990
  rapidRepromptCount: rapidRepromptCountOf(fold),
4842
4991
  abandonedPromptCount: fold.abandonedPromptCount,
4992
+ interruptedRuns: fold.interruptedRuns,
4843
4993
  activeMinutes: minutesOf(split.handsOnMs + split.agentSupervisingMs),
4844
4994
  // The two halves of that figure, and the reason it is not the whole
4845
4995
  // answer: a session that spent 5 minutes being typed at and 17 minutes
@@ -4881,7 +5031,8 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
4881
5031
  // figure reported the timeline. Both materials now cross this call.
4882
5032
  dayBreakdown: sliceSessionByLocalDay(fold.humanPromptTimestamps, {
4883
5033
  activeGapMs: ACTIVE_GAP_MS,
4884
- activeInstants: fold.timelinePoints
5034
+ activeInstants: fold.timelinePoints,
5035
+ interruptedRunTimestamps: fold.interruptedRunTimestamps
4885
5036
  }),
4886
5037
  // Local-only, consumed by `buildHandoff` and never written anywhere;
4887
5038
  // see `NormalizedHistoricalEvent.activeSpans`.
@@ -5285,7 +5436,12 @@ async function foldRollout(filePath) {
5285
5436
  if (sniffed.occurredAt) {
5286
5437
  const ms = Date.parse(sniffed.occurredAt);
5287
5438
  if (Number.isFinite(ms)) {
5288
- fold.timelinePoints.push({ at: ms, human: humanHere, autonomyMode: null });
5439
+ fold.timelinePoints.push({
5440
+ at: ms,
5441
+ human: humanHere,
5442
+ agentOutput: !humanHere,
5443
+ autonomyMode: null
5444
+ });
5289
5445
  } else {
5290
5446
  fold.undatedTimelineLines += 1;
5291
5447
  }
@@ -6998,7 +7154,17 @@ var PROJECT_ELAPSED_QUANTITIES = {
6998
7154
  // than borrow a corpus-wide ratio. The `summed` prefix is the only thing
6999
7155
  // separating them in the file; this says what the prefix means.
7000
7156
  "projects[].elapsed.days[].summedHandsOnMinutes": "hands_on_agent_hours",
7001
- "projects[].elapsed.days[].summedAgentSupervisingMinutes": "supervising_agent_hours"
7157
+ "projects[].elapsed.days[].summedAgentSupervisingMinutes": "supervising_agent_hours",
7158
+ // Not coverage. Every key above answers "how much clock was touched" and is
7159
+ // a total; this one answers "how long was the best single stretch" and is a
7160
+ // maximum — the distinction `block_coverage` was declared for, reached for
7161
+ // rather than a name invented for the occasion. It is cut by this file's gap
7162
+ // rule like the rest, and the gap stamp beside it already says which rule.
7163
+ //
7164
+ // The consequence a reader needs from the quantity: a maximum does not add.
7165
+ // It cannot be summed with another project's, and it does not decompose into
7166
+ // `elapsed.days[]` — those are cut at local midnight and this is not.
7167
+ "projects[].elapsed.longestActiveRunMinutes": "block_coverage"
7002
7168
  };
7003
7169
  var PROJECT_DIGEST_QUANTITIES = {
7004
7170
  ...PROJECT_SUMMED_QUANTITIES,
@@ -7065,6 +7231,14 @@ function peakOverlap(intervals) {
7065
7231
  }
7066
7232
  return peak;
7067
7233
  }
7234
+ function longestRunOf(intervals) {
7235
+ let longest = null;
7236
+ for (const run of mergeIntervals(intervals)) {
7237
+ const length = run.to - run.from;
7238
+ if (longest === null || length > longest.length) longest = { length, from: run.from };
7239
+ }
7240
+ return longest;
7241
+ }
7068
7242
  function unionActiveTime(spans) {
7069
7243
  const handsOn = [];
7070
7244
  const supervising = [];
@@ -7079,13 +7253,16 @@ function unionActiveTime(spans) {
7079
7253
  const summedHandsOnMs = totalMs(handsOn);
7080
7254
  const summedAgentSupervisingMs = totalMs(supervising);
7081
7255
  const unionedMs = handsOnMs + agentSupervisingMs;
7256
+ const longest = longestRunOf([...handsOn, ...supervising]);
7082
7257
  return {
7083
7258
  handsOnMs,
7084
7259
  agentSupervisingMs,
7085
7260
  summedHandsOnMs,
7086
7261
  summedAgentSupervisingMs,
7087
7262
  meanConcurrency: unionedMs > 0 ? (summedHandsOnMs + summedAgentSupervisingMs) / unionedMs : null,
7088
- peakConcurrency: peakOverlap([...handsOn, ...supervising])
7263
+ peakConcurrency: peakOverlap([...handsOn, ...supervising]),
7264
+ longestRunMs: longest?.length ?? null,
7265
+ longestRunFrom: longest?.from ?? null
7089
7266
  };
7090
7267
  }
7091
7268
  function unionActiveSpans(spans) {
@@ -7165,7 +7342,7 @@ import * as fs9 from "node:fs/promises";
7165
7342
  import * as os2 from "node:os";
7166
7343
  import * as path9 from "node:path";
7167
7344
  var APP_BUNDLE_ID = "one.ascenda.ascendaMissionControl";
7168
- var HANDOFF_SCHEMA = 5;
7345
+ var HANDOFF_SCHEMA = 6;
7169
7346
  function handoffDir(home = os2.homedir()) {
7170
7347
  return path9.join((0, import_tool_kit9.ascendaHome)(home), "history-import");
7171
7348
  }
@@ -7214,6 +7391,11 @@ function elapsedActiveOf(spans) {
7214
7391
  agentSupervisingMinutes: minutesOf(union.agentSupervisingMs),
7215
7392
  meanConcurrency: union.meanConcurrency === null ? null : Math.round(union.meanConcurrency * 100) / 100,
7216
7393
  peakConcurrency: union.peakConcurrency,
7394
+ // Absent, not zero, where there were no spans: see the field note.
7395
+ ...union.longestRunMs === null || union.longestRunFrom === null ? {} : {
7396
+ longestActiveRunMinutes: minutesOf(union.longestRunMs),
7397
+ longestActiveRunStartedOn: localDayKey(new Date(union.longestRunFrom))
7398
+ },
7217
7399
  days: [...byDay.entries()].map(([day, ms]) => ({
7218
7400
  day,
7219
7401
  handsOnMinutes: minutesOf(ms.handsOnMs),
@@ -7293,6 +7475,7 @@ function buildHandoff(events, extractionId, generatedAt) {
7293
7475
  projectLabel: projectLabelOf(event.repoRef),
7294
7476
  projectHash: projectHashOf(event.repoRef),
7295
7477
  promptCount: Number(event.metrics.promptCount ?? 0),
7478
+ syntheticPromptLines: Number(event.metrics.syntheticPromptLines ?? 0),
7296
7479
  durationBucket: String(event.metrics.durationBucket ?? "unknown"),
7297
7480
  afterHoursPrompts: Number(event.metrics.afterHoursPrompts ?? 0),
7298
7481
  primaryModel: typeof event.metrics.primaryModel === "string" ? event.metrics.primaryModel : null,
@@ -7304,6 +7487,7 @@ function buildHandoff(events, extractionId, generatedAt) {
7304
7487
  toolFailureCount: Number(event.metrics.toolFailureCount ?? 0),
7305
7488
  contextWindowPeakPct: Number(event.metrics.contextWindowPeakPct ?? 0),
7306
7489
  contextWindowPeakTokens: Number(event.metrics.contextWindowPeakTokens ?? 0),
7490
+ interruptedRuns: Number(event.metrics.interruptedRuns ?? 0),
7307
7491
  // Null, never 0. `toolUseResult.userModified` occurs 20,133 times
7308
7492
  // across a real 363-session store and is `false` on every single one
7309
7493
  // — Claude Code does not appear to set it true. Shipping 0 says "no
@@ -7329,6 +7513,15 @@ function buildHandoff(events, extractionId, generatedAt) {
7329
7513
  // repeated here — a provenance stamp that can disagree with the rule it
7330
7514
  // describes is worse than none.
7331
7515
  activeGapMinutes: DEFAULT_ACTIVE_GAP_MS / 6e4,
7516
+ // The Claude Code extractor classifies its lines, so hands-on here runs
7517
+ // from the agent's last output to the prompt.
7518
+ handsOnBoundary: "human_turn",
7519
+ // This extractor counts the runs a person cut short, so a `0` on a session
7520
+ // or a day slice below means none, not "not looked for".
7521
+ interruptedRunsCounted: true,
7522
+ // The extractor declines the lines the runtime writes on a person's behalf,
7523
+ // so `promptCount` below is typed prompts only.
7524
+ promptBasis: "typed",
7332
7525
  // And what each of those figures measures, keyed by the path a reader
7333
7526
  // walks. The `projects` rollup below is why this store needs the stamp
7334
7527
  // most: it carries the summed pair and the unioned pair under one
@@ -7391,6 +7584,9 @@ function buildCodexHandoff(events, extractionId, generatedAt) {
7391
7584
  // repeated here — a provenance stamp that can disagree with the rule it
7392
7585
  // describes is worse than none.
7393
7586
  activeGapMinutes: DEFAULT_ACTIVE_GAP_MS / 6e4,
7587
+ // The Codex extractor has not classified its bookkeeping lines, so its
7588
+ // hands-on is still the span ending at the prompt — see the extractor.
7589
+ handsOnBoundary: "nearest_line",
7394
7590
  // And what each of those figures measures, keyed by the path a reader
7395
7591
  // walks. The `projects` rollup below is why this store needs the stamp
7396
7592
  // most: it carries the summed pair and the unioned pair under one
package/dist/daySlice.js CHANGED
@@ -27,7 +27,16 @@
27
27
  import { activeSpans } from "./activeSplit.js";
28
28
  /** The IANA zone the slices were cut in, or null where the host cannot say. */
29
29
  export const LOCAL_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone ?? null;
30
- function localDayKey(t) {
30
+ /**
31
+ * The `YYYY-MM-DD` a moment falls on in the host's local time.
32
+ *
33
+ * Exported so that a figure dated outside the per-day slices — the longest
34
+ * unbroken stretch, which is taken before the midnight cut and so has no slice
35
+ * of its own — is dated by the same rule the slices are keyed by. Two spellings
36
+ * of one day key is how `2026-08-03` comes to mean two different days in one
37
+ * file.
38
+ */
39
+ export function localDayKey(t) {
31
40
  const y = t.getFullYear();
32
41
  const m = String(t.getMonth() + 1).padStart(2, "0");
33
42
  const d = String(t.getDate()).padStart(2, "0");
@@ -59,6 +68,20 @@ export function addSpanByLocalDay(into, from, to) {
59
68
  cursor = segmentEnd;
60
69
  }
61
70
  }
71
+ /** Local-day keys of the parseable timestamps in `timestamps`, unsorted. */
72
+ function localDayCounts(timestamps) {
73
+ const counts = new Map();
74
+ for (const t of timestamps) {
75
+ if (typeof t !== "string" || t.length === 0)
76
+ continue;
77
+ const d = new Date(t);
78
+ if (!Number.isFinite(d.getTime()))
79
+ continue;
80
+ const key = localDayKey(d);
81
+ counts.set(key, (counts.get(key) ?? 0) + 1);
82
+ }
83
+ return counts;
84
+ }
62
85
  /**
63
86
  * Slices a session's prompt timestamps into per-local-day counts, oldest
64
87
  * first. Unparseable and null timestamps are dropped rather than guessed at:
@@ -93,7 +116,9 @@ export function sliceSessionByLocalDay(timestamps, options = {}) {
93
116
  const classified = options.activeInstants;
94
117
  if (gapMs !== undefined) {
95
118
  const points = classified ??
96
- instants.map((d) => ({ at: d.getTime(), human: true, autonomyMode: null }));
119
+ // Prompt-only instants are never split, so the classification is moot;
120
+ // `agentOutput: false` is stated so the type says what this path knows.
121
+ instants.map((d) => ({ at: d.getTime(), human: true, agentOutput: false, autonomyMode: null }));
97
122
  // One classification, shared with the session totals — see activeSplit.ts.
98
123
  for (const span of activeSpans(points, { activeGapMs: gapMs }).spans) {
99
124
  const from = new Date(span.from);
@@ -103,7 +128,10 @@ export function sliceSessionByLocalDay(timestamps, options = {}) {
103
128
  addSpanByLocalDay(span.handsOn ? handsOnMs : supervisingMs, from, to);
104
129
  }
105
130
  }
106
- const days = [...new Set([...prompts.keys(), ...activeMs.keys()])].sort();
131
+ const cuts = options.interruptedRunTimestamps
132
+ ? localDayCounts(options.interruptedRunTimestamps)
133
+ : null;
134
+ const days = [...new Set([...prompts.keys(), ...activeMs.keys(), ...(cuts?.keys() ?? [])])].sort();
107
135
  return days.map((day) => {
108
136
  const slice = { day, prompts: prompts.get(day) ?? 0 };
109
137
  if (gapMs !== undefined) {
@@ -113,6 +141,8 @@ export function sliceSessionByLocalDay(timestamps, options = {}) {
113
141
  slice.agentSupervisingMinutes = Math.round((supervisingMs.get(day) ?? 0) / 60_000);
114
142
  }
115
143
  }
144
+ if (cuts)
145
+ slice.interruptedRuns = cuts.get(day) ?? 0;
116
146
  return slice;
117
147
  });
118
148
  }
@@ -1 +1 @@
1
- {"version":3,"file":"daySlice.js","sourceRoot":"","sources":["../src/daySlice.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"daySlice.js","sourceRoot":"","sources":["../src/daySlice.ts"],"names":[],"mappings":"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"}