@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/README.md +62 -1
- package/dist/activeSplit.js +65 -16
- package/dist/activeSplit.js.map +1 -1
- package/dist/activeTimeFigures.js +7 -0
- package/dist/activeTimeFigures.js.map +1 -1
- package/dist/activeUnion.js +39 -1
- package/dist/activeUnion.js.map +1 -1
- package/dist/cli.js +207 -11
- package/dist/daySlice.js +33 -3
- package/dist/daySlice.js.map +1 -1
- package/dist/extractors/claudeCode.js +80 -5
- package/dist/extractors/claudeCode.js.map +1 -1
- package/dist/extractors/codex.js +14 -1
- package/dist/extractors/codex.js.map +1 -1
- package/dist/handoffActiveTime.js +11 -1
- package/dist/handoffActiveTime.js.map +1 -1
- package/dist/interruptedRuns.js +148 -0
- package/dist/interruptedRuns.js.map +1 -0
- package/dist/localHandoff.js +40 -14
- package/dist/localHandoff.js.map +1 -1
- package/package.json +1 -1
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, {
|
|
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:
|
|
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
|
|
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
|
|
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({
|
|
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({
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|
package/dist/daySlice.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daySlice.js","sourceRoot":"","sources":["../src/daySlice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,WAAW,EAAsB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"daySlice.js","sourceRoot":"","sources":["../src/daySlice.ts"],"names":[],"mappings":"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"}
|