@ascenda-one/history-import 0.1.25 → 0.1.27
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 +89 -7
- package/dist/cli.js +253 -46
- package/dist/daySlice.js +15 -2
- package/dist/daySlice.js.map +1 -1
- package/dist/extractors/claudeCode.js +85 -20
- package/dist/extractors/claudeCode.js.map +1 -1
- package/dist/interruptedRuns.js +11 -0
- package/dist/interruptedRuns.js.map +1 -1
- package/dist/localHandoff.js +4 -0
- package/dist/localHandoff.js.map +1 -1
- package/dist/promptLedger.js +297 -7
- package/dist/promptLedger.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,6 +152,47 @@ line that closes the turn. The rule is in
|
|
|
152
152
|
[`src/interruptedRuns.ts`](./src/interruptedRuns.ts), and the desktop app's
|
|
153
153
|
importer counts by the same one.
|
|
154
154
|
|
|
155
|
+
A cut counts in the session you pressed Escape in, and in no other. Resuming
|
|
156
|
+
a session copies the history it inherits, markers included, so before this
|
|
157
|
+
rule every resume in a lineage reported its ancestors' interruptions as
|
|
158
|
+
well — a fifth of the counted total on the store this was measured against.
|
|
159
|
+
The same ownership rule as the minutes (`minutesBasis: owned_lines`).
|
|
160
|
+
|
|
161
|
+
### When two files both say a line is theirs
|
|
162
|
+
|
|
163
|
+
Most copies keep the session id the line was written with, which is how the
|
|
164
|
+
import tells a copy from the real thing. Some rewrite it to the file the copy
|
|
165
|
+
now sits in, keeping the line's own id and its original timestamp — so the
|
|
166
|
+
test that settles every other copy answers "written here" in both places, and
|
|
167
|
+
both sessions counted the line. Nothing on the line says which of the two
|
|
168
|
+
wrote it: the two records are identical apart from the fields the copy
|
|
169
|
+
rewrote.
|
|
170
|
+
|
|
171
|
+
Where the line sits is what settles it. A line two files claim is contested.
|
|
172
|
+
Inside a file, the run of contested lines at the top is the history it
|
|
173
|
+
inherited, and it ends at the first line that file claims and nobody else
|
|
174
|
+
does — from there on, what it holds it wrote. So a contested line belongs to
|
|
175
|
+
the file still holding it after its own history has started.
|
|
176
|
+
|
|
177
|
+
Not simply the first file the import reaches, which would be the easy rule and
|
|
178
|
+
the wrong one: on the store this was measured against it would hand two fifths
|
|
179
|
+
of these lines to the copy instead, which is the error this whole rule exists
|
|
180
|
+
to remove.
|
|
181
|
+
|
|
182
|
+
**What that leaves.** A lineage whose earliest file the 30-day cleanup has
|
|
183
|
+
already taken has no file that wrote those lines — every one still holding
|
|
184
|
+
them holds them in its inherited head, and nothing left on disk says which
|
|
185
|
+
session did the work. The line is still counted once, by the first file the
|
|
186
|
+
import reaches, the same answer any orphaned line gets. The total is right and
|
|
187
|
+
the session it lands on is a guess. That was 16.1% of contested lines here.
|
|
188
|
+
|
|
189
|
+
Sizes, on the same store: 3.7% of the lines claiming their own file were
|
|
190
|
+
claimed by two of them, and counting each once takes 3.9% off the summed
|
|
191
|
+
active minutes. **Your week doesn't move** — the union of your active time
|
|
192
|
+
holds to a minute, because a line counted twice was one minute counted twice.
|
|
193
|
+
Hands-on shifts a little more than that: a span changes sides when the lines
|
|
194
|
+
around it move, so the unioned hands-on figure moved by 19 minutes in 6,734.
|
|
195
|
+
|
|
155
196
|
### What counts as a prompt
|
|
156
197
|
|
|
157
198
|
`promptCount` on a Claude Code session counts the prompts you typed. Claude
|
|
@@ -180,13 +221,17 @@ test is `isTypedPromptLine` in
|
|
|
180
221
|
Two more rules need the whole store, so the extractor reads every transcript
|
|
181
222
|
once before it folds any session ([`src/promptLedger.ts`](./src/promptLedger.ts)):
|
|
182
223
|
|
|
183
|
-
- **Each
|
|
184
|
-
transcript that copies the history it inherited, line ids and timestamps
|
|
185
|
-
included.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
224
|
+
- **Each line belongs to one session.** Resuming or forking a session writes a
|
|
225
|
+
new transcript that copies the history it inherited, line ids and timestamps
|
|
226
|
+
included. Each copy has one owner: the transcript the line's `sessionId`
|
|
227
|
+
names, or, where the purge took that file, the first in the walk's sorted
|
|
228
|
+
order. The owner counts the prompt, and the owner's timeline carries the
|
|
229
|
+
instant — so `activeMinutes`, the hands-on split, the day slices and
|
|
230
|
+
`startedAt` describe the session you resumed rather than everything behind
|
|
231
|
+
it. Two files can both name themselves on one line, which "When two files
|
|
232
|
+
both say a line is theirs" above settles. A line with no id is counted
|
|
233
|
+
wherever it appears; on the reference store that's `queue-operation` lines
|
|
234
|
+
and nothing else.
|
|
190
235
|
- **A chip's prompt isn't typed.** A session launched from a `spawn_task` chip
|
|
191
236
|
opens on the chip's prompt. When a line's text, wrappers stripped, matches a
|
|
192
237
|
chip that some session in the store offered, it counts in
|
|
@@ -202,6 +247,43 @@ typed. A handoff without the stamp counted every `user` line that wasn't a tool
|
|
|
202
247
|
result. Prompt counts for the same week run highest under that rule and lowest
|
|
203
248
|
under `typed_once`. The schema number doesn't move.
|
|
204
249
|
|
|
250
|
+
Beside it, `minutesBasis: "owned_lines"` says the minutes were cut the same
|
|
251
|
+
way. `"every_line"` — and an absent stamp — means a transcript's whole
|
|
252
|
+
contents counted there, inherited history included. Gate on this before
|
|
253
|
+
comparing minutes across two handoffs: a store's figures drop when it changes,
|
|
254
|
+
and nothing else in the file says so.
|
|
255
|
+
|
|
256
|
+
### How much history a resume carries
|
|
257
|
+
|
|
258
|
+
Enough to matter, which is why the rule above covers the minutes and not only
|
|
259
|
+
the counts. On one real store of 984 transcripts, 151 carried inherited lines:
|
|
260
|
+
275,801 of them, 29% of every dated line in the store. They held the whole of
|
|
261
|
+
the gap between what the sessions claimed and what the clock allowed. Both
|
|
262
|
+
columns are the same 983 sessions, read from one snapshot:
|
|
263
|
+
|
|
264
|
+
| | Every line | Owned lines |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| Summed active minutes | 70,212 | 53,600 |
|
|
267
|
+
| Summed hands-on minutes | 12,238 | 8,006 |
|
|
268
|
+
| Union of every active interval | 25,078 | 25,077 |
|
|
269
|
+
| Exact-duplicate spans | 262,500 of 873,599 | 21,982 of 635,126 |
|
|
270
|
+
| Sessions apparently spanning over a day | 158 | 154 |
|
|
271
|
+
|
|
272
|
+
The union is the control. It holds to the minute, so no work anyone did was
|
|
273
|
+
dropped: what went was the copy of it. Duplicate spans fell from 30% of the
|
|
274
|
+
store to 3.5%, which matters to any count of how many agents were running at
|
|
275
|
+
once, since a duplicate reads as a second agent. 127 sessions now start when
|
|
276
|
+
they were resumed, 13 of them more than an hour later than they used to.
|
|
277
|
+
|
|
278
|
+
That 3.5% was not the floor, and it was not coincidence: four fifths of it was
|
|
279
|
+
the copy path settled above. On a later and slightly larger snapshot of the
|
|
280
|
+
same store, the duplicate spans go from 21,982 to 4,087 once contested lines
|
|
281
|
+
are settled.
|
|
282
|
+
|
|
283
|
+
An earlier reading of this exposure put it at 0.72% of sessions. It counted
|
|
284
|
+
repeated `sessionRef`s, and a resumed transcript carries a fresh one, so it saw
|
|
285
|
+
three sessions where 151 transcripts were affected.
|
|
286
|
+
|
|
205
287
|
### Autonomy bands
|
|
206
288
|
|
|
207
289
|
`permissionMode` is on the transcript's human-prompt lines and nowhere else —
|
package/dist/cli.js
CHANGED
|
@@ -540,6 +540,10 @@ var require_metricKeys = __commonJS({
|
|
|
540
540
|
orphanedBubbles: { readBy: ["diagnostic"] },
|
|
541
541
|
orphanedSubagentBubbles: { readBy: ["diagnostic"] },
|
|
542
542
|
sessionsWithoutTimeline: { readBy: ["diagnostic"] },
|
|
543
|
+
sessionsWithOnlyInheritedLines: {
|
|
544
|
+
readBy: ["diagnostic"],
|
|
545
|
+
note: "Claude Code transcripts holding nothing but a copy of an ancestor's history, so no line's instant is theirs and no session is emitted for them. Excluded by rule, not a read failure: the work is in the transcript that owns it."
|
|
546
|
+
},
|
|
543
547
|
emptyComposers: { readBy: ["diagnostic"] },
|
|
544
548
|
apiErrorCount: { readBy: ["diagnostic"] },
|
|
545
549
|
assistantTurns: { readBy: ["diagnostic"] },
|
|
@@ -735,6 +739,16 @@ var require_out = __commonJS({
|
|
|
735
739
|
// `ai_prompt_submitted` closes. Neutral so a marker sent once per turn
|
|
736
740
|
// doesn't weigh on any workload leg.
|
|
737
741
|
ai_turn_completed: "neutral",
|
|
742
|
+
// The agent stopped and is waiting on the person: Claude Code's
|
|
743
|
+
// `Notification`, Codex's `PermissionRequest`. Supervision, beside the other
|
|
744
|
+
// oversight overhead, because that is what it is — time the person spends
|
|
745
|
+
// attending to the agent rather than to the work.
|
|
746
|
+
//
|
|
747
|
+
// Deliberately NOT risk. Risk is the leg that reads as something going
|
|
748
|
+
// wrong, and being asked is not a failure; classifying it there is the first
|
|
749
|
+
// step toward an interruption count becoming a score, a streak, or something
|
|
750
|
+
// that fires on a miss. It must never become any of those.
|
|
751
|
+
supervision_interruption: "supervision",
|
|
738
752
|
// Semantic (agent-observed) — see SEMANTIC_WORK_SIGNAL_EVENT_TYPES.
|
|
739
753
|
approach_churn_detected: "risk",
|
|
740
754
|
goal_drift_detected: "risk",
|
|
@@ -3349,16 +3363,20 @@ var require_liveBus = __commonJS({
|
|
|
3349
3363
|
path13.join(home, "Library", "Containers", SAVER_HOST_BUNDLE_ID, "Data", "l.sock")
|
|
3350
3364
|
];
|
|
3351
3365
|
}
|
|
3352
|
-
function
|
|
3366
|
+
function existingSocketCandidates() {
|
|
3353
3367
|
const candidates = liveBusSocketCandidates();
|
|
3354
|
-
|
|
3368
|
+
if (process.env.ASCENDA_LIVE_BUS_SOCKET)
|
|
3369
|
+
return candidates;
|
|
3370
|
+
return candidates.filter((candidate) => {
|
|
3355
3371
|
try {
|
|
3356
|
-
|
|
3357
|
-
return candidate;
|
|
3372
|
+
return fs12.statSync(candidate).isSocket();
|
|
3358
3373
|
} catch {
|
|
3374
|
+
return false;
|
|
3359
3375
|
}
|
|
3360
|
-
}
|
|
3361
|
-
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
function liveBusSocketPath() {
|
|
3379
|
+
return existingSocketCandidates()[0] ?? liveBusSocketCandidates()[0];
|
|
3362
3380
|
}
|
|
3363
3381
|
function bucketPromptSize(text) {
|
|
3364
3382
|
const length = typeof text === "string" ? text.length : 0;
|
|
@@ -3370,44 +3388,75 @@ var require_liveBus = __commonJS({
|
|
|
3370
3388
|
return "l";
|
|
3371
3389
|
return "xl";
|
|
3372
3390
|
}
|
|
3391
|
+
var NOBODY_LISTENING = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOENT"]);
|
|
3373
3392
|
function emitLiveSignal(signal) {
|
|
3374
3393
|
return new Promise((resolve2) => {
|
|
3394
|
+
const candidates = existingSocketCandidates();
|
|
3395
|
+
if (candidates.length === 0) {
|
|
3396
|
+
resolve2();
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3375
3399
|
let settled = false;
|
|
3400
|
+
let socket;
|
|
3401
|
+
const drop = () => {
|
|
3402
|
+
try {
|
|
3403
|
+
socket?.destroy();
|
|
3404
|
+
} catch {
|
|
3405
|
+
}
|
|
3406
|
+
socket = void 0;
|
|
3407
|
+
};
|
|
3376
3408
|
const done = () => {
|
|
3377
3409
|
if (settled)
|
|
3378
3410
|
return;
|
|
3379
3411
|
settled = true;
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
} catch {
|
|
3383
|
-
}
|
|
3412
|
+
clearTimeout(timer);
|
|
3413
|
+
drop();
|
|
3384
3414
|
resolve2();
|
|
3385
3415
|
};
|
|
3386
|
-
let socket;
|
|
3387
|
-
try {
|
|
3388
|
-
socket = net.createConnection(liveBusSocketPath());
|
|
3389
|
-
} catch {
|
|
3390
|
-
resolve2();
|
|
3391
|
-
return;
|
|
3392
|
-
}
|
|
3393
3416
|
const timer = setTimeout(done, WRITE_TIMEOUT_MS);
|
|
3394
3417
|
if (typeof timer.unref === "function")
|
|
3395
3418
|
timer.unref();
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3419
|
+
const tryFrom = (index) => {
|
|
3420
|
+
if (settled)
|
|
3421
|
+
return;
|
|
3422
|
+
if (index >= candidates.length) {
|
|
3423
|
+
done();
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
let attempt;
|
|
3400
3427
|
try {
|
|
3401
|
-
|
|
3402
|
-
`, () => {
|
|
3403
|
-
clearTimeout(timer);
|
|
3404
|
-
done();
|
|
3405
|
-
});
|
|
3428
|
+
attempt = net.createConnection(candidates[index]);
|
|
3406
3429
|
} catch {
|
|
3407
|
-
|
|
3408
|
-
|
|
3430
|
+
tryFrom(index + 1);
|
|
3431
|
+
return;
|
|
3409
3432
|
}
|
|
3410
|
-
|
|
3433
|
+
socket = attempt;
|
|
3434
|
+
if (typeof attempt.unref === "function")
|
|
3435
|
+
attempt.unref();
|
|
3436
|
+
let connected = false;
|
|
3437
|
+
attempt.on("error", (error) => {
|
|
3438
|
+
if (settled || socket !== attempt)
|
|
3439
|
+
return;
|
|
3440
|
+
if (!connected && error.code && NOBODY_LISTENING.has(error.code)) {
|
|
3441
|
+
drop();
|
|
3442
|
+
tryFrom(index + 1);
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
done();
|
|
3446
|
+
});
|
|
3447
|
+
attempt.on("connect", () => {
|
|
3448
|
+
if (settled || socket !== attempt)
|
|
3449
|
+
return;
|
|
3450
|
+
connected = true;
|
|
3451
|
+
try {
|
|
3452
|
+
attempt.write(`${JSON.stringify(signal)}
|
|
3453
|
+
`, done);
|
|
3454
|
+
} catch {
|
|
3455
|
+
done();
|
|
3456
|
+
}
|
|
3457
|
+
});
|
|
3458
|
+
};
|
|
3459
|
+
tryFrom(0);
|
|
3411
3460
|
});
|
|
3412
3461
|
}
|
|
3413
3462
|
}
|
|
@@ -4385,7 +4434,6 @@ function localDayCounts(timestamps) {
|
|
|
4385
4434
|
}
|
|
4386
4435
|
function sliceSessionByLocalDay(timestamps, options = {}) {
|
|
4387
4436
|
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());
|
|
4388
|
-
if (instants.length === 0) return [];
|
|
4389
4437
|
const prompts = /* @__PURE__ */ new Map();
|
|
4390
4438
|
for (const t of instants) {
|
|
4391
4439
|
const key = localDayKey(t);
|
|
@@ -4583,14 +4631,109 @@ var PromptLedger = class {
|
|
|
4583
4631
|
this.owners.set(uuid, null);
|
|
4584
4632
|
return true;
|
|
4585
4633
|
}
|
|
4634
|
+
/**
|
|
4635
|
+
* Hands a contested line to the transcript that wrote it.
|
|
4636
|
+
*
|
|
4637
|
+
* The ledger picks an owner by `sessionId` too, so a copy that rewrote it
|
|
4638
|
+
* reads as home here as well. That never counted a prompt twice — the id is
|
|
4639
|
+
* the key either way — but it could count it in the wrong session, whichever
|
|
4640
|
+
* of the two the walk reached first. `HomeClaims` settles the same question
|
|
4641
|
+
* for the timeline, and one answer serves both: a prompt and the instant it
|
|
4642
|
+
* happened at cannot belong to different sessions.
|
|
4643
|
+
*/
|
|
4644
|
+
applyContestedOwners(owners) {
|
|
4645
|
+
for (const [uuid, transcript] of owners) {
|
|
4646
|
+
if (!this.owners.has(uuid)) continue;
|
|
4647
|
+
this.owners.set(uuid, { transcript, home: true });
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4586
4650
|
/** Distinct chip prompts seen, for tests and diagnostics. */
|
|
4587
4651
|
get chipPromptCount() {
|
|
4588
4652
|
return this.chipPrompts.size;
|
|
4589
4653
|
}
|
|
4590
4654
|
};
|
|
4655
|
+
var HomeClaims = class {
|
|
4656
|
+
/** Id to the first transcript that claimed it, in walk order. */
|
|
4657
|
+
firstClaim = /* @__PURE__ */ new Map();
|
|
4658
|
+
/** Id to every transcript claiming it, once a second one does. */
|
|
4659
|
+
contested = /* @__PURE__ */ new Map();
|
|
4660
|
+
/** Takes one line of the pre-read, in walk order. */
|
|
4661
|
+
observe(uuid, sessionId, transcript) {
|
|
4662
|
+
if (typeof uuid !== "string" || !isHomeTranscript(transcript, sessionId)) return;
|
|
4663
|
+
const first = this.firstClaim.get(uuid);
|
|
4664
|
+
if (first === void 0) {
|
|
4665
|
+
this.firstClaim.set(uuid, transcript);
|
|
4666
|
+
return;
|
|
4667
|
+
}
|
|
4668
|
+
if (first === transcript) return;
|
|
4669
|
+
const all = this.contested.get(uuid) ?? /* @__PURE__ */ new Set([first]);
|
|
4670
|
+
all.add(transcript);
|
|
4671
|
+
this.contested.set(uuid, all);
|
|
4672
|
+
}
|
|
4673
|
+
get contestedCount() {
|
|
4674
|
+
return this.contested.size;
|
|
4675
|
+
}
|
|
4676
|
+
/** Every transcript holding a contested id — the only ones worth re-reading. */
|
|
4677
|
+
transcriptsToClassify() {
|
|
4678
|
+
const out = /* @__PURE__ */ new Set();
|
|
4679
|
+
for (const files of this.contested.values()) for (const f of files) out.add(f);
|
|
4680
|
+
return [...out];
|
|
4681
|
+
}
|
|
4682
|
+
has(uuid) {
|
|
4683
|
+
return this.contested.has(uuid);
|
|
4684
|
+
}
|
|
4685
|
+
/** Every contested id, so each one can be given an owner. */
|
|
4686
|
+
contestedUuids() {
|
|
4687
|
+
return this.contested.keys();
|
|
4688
|
+
}
|
|
4689
|
+
/** The first transcript in walk order that claimed this id. */
|
|
4690
|
+
firstClaimOf(uuid) {
|
|
4691
|
+
return this.firstClaim.get(uuid);
|
|
4692
|
+
}
|
|
4693
|
+
};
|
|
4694
|
+
async function resolveContestedOwners(claims, walkOrder) {
|
|
4695
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4696
|
+
const rank = new Map(walkOrder.map((file, i) => [file, i]));
|
|
4697
|
+
const toRead = claims.transcriptsToClassify().sort((a, b) => (rank.get(a) ?? 0) - (rank.get(b) ?? 0));
|
|
4698
|
+
for (const transcript of toRead) {
|
|
4699
|
+
let headOver = false;
|
|
4700
|
+
const own = [];
|
|
4701
|
+
try {
|
|
4702
|
+
await eachLine(transcript, (line) => {
|
|
4703
|
+
if (!line.includes('"uuid"')) return;
|
|
4704
|
+
let parsed;
|
|
4705
|
+
try {
|
|
4706
|
+
parsed = JSON.parse(line);
|
|
4707
|
+
} catch {
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
4710
|
+
if (typeof parsed !== "object" || parsed === null) return;
|
|
4711
|
+
const record = parsed;
|
|
4712
|
+
if (typeof record.uuid !== "string") return;
|
|
4713
|
+
if (!isHomeTranscript(transcript, record.sessionId)) return;
|
|
4714
|
+
if (!claims.has(record.uuid)) {
|
|
4715
|
+
headOver = true;
|
|
4716
|
+
return;
|
|
4717
|
+
}
|
|
4718
|
+
if (headOver) own.push(record.uuid);
|
|
4719
|
+
});
|
|
4720
|
+
} catch {
|
|
4721
|
+
}
|
|
4722
|
+
for (const uuid of own) if (!owners.has(uuid)) owners.set(uuid, transcript);
|
|
4723
|
+
}
|
|
4724
|
+
for (const uuid of claims.contestedUuids()) {
|
|
4725
|
+
if (owners.has(uuid)) continue;
|
|
4726
|
+
const first = claims.firstClaimOf(uuid);
|
|
4727
|
+
if (first !== void 0) owners.set(uuid, first);
|
|
4728
|
+
}
|
|
4729
|
+
return owners;
|
|
4730
|
+
}
|
|
4591
4731
|
function mayMatter(line) {
|
|
4592
4732
|
return line.includes('"user"') || line.includes(SPAWN_TASK_TOOL_NAME);
|
|
4593
4733
|
}
|
|
4734
|
+
function mayClaim(line) {
|
|
4735
|
+
return line.includes('"uuid"');
|
|
4736
|
+
}
|
|
4594
4737
|
async function eachLine(transcript, onLine) {
|
|
4595
4738
|
const stream = createReadStream(transcript, { encoding: "utf8", highWaterMark: 1 << 20 });
|
|
4596
4739
|
let pending = [];
|
|
@@ -4609,10 +4752,12 @@ async function eachLine(transcript, onLine) {
|
|
|
4609
4752
|
}
|
|
4610
4753
|
async function readPromptLedger(mainTranscripts, subagentTranscripts, isToolResult) {
|
|
4611
4754
|
const ledger = new PromptLedger();
|
|
4755
|
+
const claims = new HomeClaims();
|
|
4612
4756
|
const read = async (transcript, isSidechain) => {
|
|
4613
4757
|
try {
|
|
4614
4758
|
await eachLine(transcript, (line) => {
|
|
4615
|
-
|
|
4759
|
+
const claiming = !isSidechain && mayClaim(line);
|
|
4760
|
+
if (!claiming && !mayMatter(line)) return;
|
|
4616
4761
|
let parsed;
|
|
4617
4762
|
try {
|
|
4618
4763
|
parsed = JSON.parse(line);
|
|
@@ -4620,15 +4765,59 @@ async function readPromptLedger(mainTranscripts, subagentTranscripts, isToolResu
|
|
|
4620
4765
|
return;
|
|
4621
4766
|
}
|
|
4622
4767
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
|
|
4623
|
-
|
|
4768
|
+
const record = parsed;
|
|
4769
|
+
if (claiming) claims.observe(record.uuid, record.sessionId, transcript);
|
|
4770
|
+
if (mayMatter(line)) {
|
|
4771
|
+
ledger.observe(record, transcript, { isSidechain, isToolResult });
|
|
4772
|
+
}
|
|
4624
4773
|
});
|
|
4625
4774
|
} catch {
|
|
4626
4775
|
}
|
|
4627
4776
|
};
|
|
4628
4777
|
for (const transcript of mainTranscripts) await read(transcript, false);
|
|
4629
4778
|
for (const transcript of subagentTranscripts) await read(transcript, true);
|
|
4630
|
-
return ledger;
|
|
4779
|
+
return { ledger, claims };
|
|
4631
4780
|
}
|
|
4781
|
+
var TimelineOwnership = class {
|
|
4782
|
+
/** Basenames of every main-thread transcript the walk listed. */
|
|
4783
|
+
transcriptIds;
|
|
4784
|
+
/** Ids of inherited lines whose home file is gone, claimed as they're met. */
|
|
4785
|
+
claimedOrphans = /* @__PURE__ */ new Set();
|
|
4786
|
+
/**
|
|
4787
|
+
* Ids two transcripts both claim as written in them, mapped to the one that
|
|
4788
|
+
* wrote it — see `HomeClaims`. Empty where no copy rewrote a `sessionId`,
|
|
4789
|
+
* which is most stores and every store this rule's first version was
|
|
4790
|
+
* measured on.
|
|
4791
|
+
*/
|
|
4792
|
+
contestedOwners;
|
|
4793
|
+
constructor(mainTranscripts, contestedOwners = /* @__PURE__ */ new Map()) {
|
|
4794
|
+
this.transcriptIds = new Set(mainTranscripts.map((file) => path4.basename(file, ".jsonl")));
|
|
4795
|
+
this.contestedOwners = contestedOwners;
|
|
4796
|
+
}
|
|
4797
|
+
/**
|
|
4798
|
+
* Whether this transcript's fold counts this line's instant. Called once per
|
|
4799
|
+
* known line, in the extractor's walk order — the orphan rule is first-come,
|
|
4800
|
+
* so a different order would hand a purged lineage's minutes to a different
|
|
4801
|
+
* fold.
|
|
4802
|
+
*/
|
|
4803
|
+
owns(line, transcript, opts) {
|
|
4804
|
+
if (opts.isSidechain) return true;
|
|
4805
|
+
if (line.sessionId === null || line.uuid === null) return true;
|
|
4806
|
+
if (isHomeTranscript(transcript, line.sessionId)) {
|
|
4807
|
+
const owner = this.contestedOwners.get(line.uuid);
|
|
4808
|
+
if (owner === void 0) return true;
|
|
4809
|
+
return owner === transcript;
|
|
4810
|
+
}
|
|
4811
|
+
if (this.transcriptIds.has(line.sessionId)) return false;
|
|
4812
|
+
if (this.claimedOrphans.has(line.uuid)) return false;
|
|
4813
|
+
this.claimedOrphans.add(line.uuid);
|
|
4814
|
+
return true;
|
|
4815
|
+
}
|
|
4816
|
+
/** Ids held for orphaned lines, for tests and diagnostics. */
|
|
4817
|
+
get orphanLineCount() {
|
|
4818
|
+
return this.claimedOrphans.size;
|
|
4819
|
+
}
|
|
4820
|
+
};
|
|
4632
4821
|
|
|
4633
4822
|
// src/extractors/claudeCode.ts
|
|
4634
4823
|
var KNOWN_CLAUDE_LINE_TYPES = [
|
|
@@ -4676,6 +4865,7 @@ function sniffClaudeLine(line) {
|
|
|
4676
4865
|
kind: type,
|
|
4677
4866
|
sourceVersion: typeof record.version === "string" ? record.version : null,
|
|
4678
4867
|
occurredAt: typeof record.timestamp === "string" ? record.timestamp : null,
|
|
4868
|
+
uuid: typeof record.uuid === "string" ? record.uuid : null,
|
|
4679
4869
|
sessionId: typeof record.sessionId === "string" ? record.sessionId : null,
|
|
4680
4870
|
model: message && typeof message.model === "string" ? message.model : null
|
|
4681
4871
|
};
|
|
@@ -4775,6 +4965,7 @@ function newFold(sessionId, projectSlug) {
|
|
|
4775
4965
|
toolCalls: [],
|
|
4776
4966
|
timelinePoints: [],
|
|
4777
4967
|
undatedTimelineLines: 0,
|
|
4968
|
+
inheritedTimelineLines: 0,
|
|
4778
4969
|
humanPromptTimestamps: [],
|
|
4779
4970
|
interruptedRuns: 0,
|
|
4780
4971
|
interruptedRunTimestamps: []
|
|
@@ -4834,11 +5025,12 @@ async function foldLinesInto(fold, filePath, opts) {
|
|
|
4834
5025
|
fold.unknownLines += 1;
|
|
4835
5026
|
continue;
|
|
4836
5027
|
}
|
|
4837
|
-
|
|
5028
|
+
const owned = opts.ownership.owns(sniffed, filePath, { isSidechain: opts.isSidechain });
|
|
5029
|
+
if (owned && !opts.isSidechain && sniffed.sessionId && fold.sessionId !== sniffed.sessionId) {
|
|
4838
5030
|
fold.sessionId = sniffed.sessionId;
|
|
4839
5031
|
}
|
|
4840
5032
|
if (sniffed.sourceVersion) fold.sourceVersion = sniffed.sourceVersion;
|
|
4841
|
-
if (sniffed.occurredAt) {
|
|
5033
|
+
if (owned && sniffed.occurredAt) {
|
|
4842
5034
|
if (!fold.firstTs || sniffed.occurredAt < fold.firstTs) fold.firstTs = sniffed.occurredAt;
|
|
4843
5035
|
if (!fold.lastTs || sniffed.occurredAt > fold.lastTs) fold.lastTs = sniffed.occurredAt;
|
|
4844
5036
|
}
|
|
@@ -4855,7 +5047,7 @@ async function foldLinesInto(fold, filePath, opts) {
|
|
|
4855
5047
|
if (!opts.isSidechain) {
|
|
4856
5048
|
const step = stepRun(turnRunning, record, isToolResultUserLine);
|
|
4857
5049
|
turnRunning = step.running;
|
|
4858
|
-
if (step.cut) {
|
|
5050
|
+
if (step.cut && owned) {
|
|
4859
5051
|
fold.interruptedRuns += 1;
|
|
4860
5052
|
fold.interruptedRunTimestamps.push(sniffed.occurredAt);
|
|
4861
5053
|
}
|
|
@@ -4966,7 +5158,9 @@ async function foldLinesInto(fold, filePath, opts) {
|
|
|
4966
5158
|
default:
|
|
4967
5159
|
break;
|
|
4968
5160
|
}
|
|
4969
|
-
if (sniffed.occurredAt) {
|
|
5161
|
+
if (sniffed.occurredAt && !owned) {
|
|
5162
|
+
fold.inheritedTimelineLines += 1;
|
|
5163
|
+
} else if (sniffed.occurredAt) {
|
|
4970
5164
|
const ms = Date.parse(sniffed.occurredAt);
|
|
4971
5165
|
if (Number.isFinite(ms)) {
|
|
4972
5166
|
fold.timelinePoints.push({
|
|
@@ -4984,9 +5178,9 @@ async function foldLinesInto(fold, filePath, opts) {
|
|
|
4984
5178
|
await handle.close();
|
|
4985
5179
|
}
|
|
4986
5180
|
}
|
|
4987
|
-
async function foldTranscript(filePath, projectSlug, ledger) {
|
|
5181
|
+
async function foldTranscript(filePath, projectSlug, ledger, ownership) {
|
|
4988
5182
|
const fold = newFold(path5.basename(filePath, ".jsonl"), projectSlug);
|
|
4989
|
-
await foldLinesInto(fold, filePath, { isSidechain: false, ledger });
|
|
5183
|
+
await foldLinesInto(fold, filePath, { isSidechain: false, ledger, ownership });
|
|
4990
5184
|
return fold;
|
|
4991
5185
|
}
|
|
4992
5186
|
async function walkJsonlFiles(root) {
|
|
@@ -5036,6 +5230,7 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5036
5230
|
let windowNewest = null;
|
|
5037
5231
|
let sessionCount = 0;
|
|
5038
5232
|
let projectsWithNoReadableTranscript = 0;
|
|
5233
|
+
let sessionsWithOnlyInheritedLines = 0;
|
|
5039
5234
|
const plan = [];
|
|
5040
5235
|
for (const slug of projectSlugs.sort()) {
|
|
5041
5236
|
const dir = path5.join(projectsDir, slug);
|
|
@@ -5059,17 +5254,21 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5059
5254
|
sessionDirs
|
|
5060
5255
|
});
|
|
5061
5256
|
}
|
|
5062
|
-
const
|
|
5063
|
-
|
|
5257
|
+
const mainTranscripts = plan.flatMap((p) => p.topLevelFiles.map((name) => path5.join(p.dir, name)));
|
|
5258
|
+
const { ledger, claims } = await readPromptLedger(
|
|
5259
|
+
mainTranscripts,
|
|
5064
5260
|
plan.flatMap((p) => p.sessionDirs.flatMap((d) => d.files)),
|
|
5065
5261
|
isToolResultUserLine
|
|
5066
5262
|
);
|
|
5263
|
+
const contestedOwners = await resolveContestedOwners(claims, mainTranscripts);
|
|
5264
|
+
ledger.applyContestedOwners(contestedOwners);
|
|
5265
|
+
const ownership = new TimelineOwnership(mainTranscripts, contestedOwners);
|
|
5067
5266
|
for (const { slug, dir, topLevelFiles, otherFiles, sessionDirs } of plan) {
|
|
5068
5267
|
let slugJsonlFiles = topLevelFiles.length;
|
|
5069
5268
|
let slugOtherFiles = otherFiles;
|
|
5070
5269
|
const foldsByKey = /* @__PURE__ */ new Map();
|
|
5071
5270
|
for (const name of topLevelFiles) {
|
|
5072
|
-
const fold = await foldTranscript(path5.join(dir, name), slug, ledger);
|
|
5271
|
+
const fold = await foldTranscript(path5.join(dir, name), slug, ledger, ownership);
|
|
5073
5272
|
foldsByKey.set(path5.basename(name, ".jsonl"), fold);
|
|
5074
5273
|
}
|
|
5075
5274
|
for (const { name: sessionDirName, files: nestedFiles, otherFiles: nestedOther } of sessionDirs) {
|
|
@@ -5083,12 +5282,15 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5083
5282
|
}
|
|
5084
5283
|
for (const nestedPath of nestedFiles) {
|
|
5085
5284
|
fold.subagentTranscripts += 1;
|
|
5086
|
-
await foldLinesInto(fold, nestedPath, { isSidechain: true, ledger });
|
|
5285
|
+
await foldLinesInto(fold, nestedPath, { isSidechain: true, ledger, ownership });
|
|
5087
5286
|
}
|
|
5088
5287
|
}
|
|
5089
5288
|
if (slugJsonlFiles === 0 && slugOtherFiles > 0) projectsWithNoReadableTranscript += 1;
|
|
5090
5289
|
for (const fold of foldsByKey.values()) {
|
|
5091
|
-
if (!fold.firstTs || !fold.lastTs)
|
|
5290
|
+
if (!fold.firstTs || !fold.lastTs) {
|
|
5291
|
+
if (fold.inheritedTimelineLines > 0) sessionsWithOnlyInheritedLines += 1;
|
|
5292
|
+
continue;
|
|
5293
|
+
}
|
|
5092
5294
|
sessionCount += 1;
|
|
5093
5295
|
if (!windowOldest || fold.firstTs < windowOldest) windowOldest = fold.firstTs;
|
|
5094
5296
|
if (!windowNewest || fold.lastTs > windowNewest) windowNewest = fold.lastTs;
|
|
@@ -5265,7 +5467,7 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5265
5467
|
}
|
|
5266
5468
|
}
|
|
5267
5469
|
}
|
|
5268
|
-
if (windowOldest && windowNewest || projectsWithNoReadableTranscript > 0) {
|
|
5470
|
+
if (windowOldest && windowNewest || projectsWithNoReadableTranscript > 0 || sessionsWithOnlyInheritedLines > 0) {
|
|
5269
5471
|
const window = windowOldest && windowNewest ? { windowOldest, windowNewest } : {};
|
|
5270
5472
|
yield {
|
|
5271
5473
|
// No window means nothing datable was read; the only honest timestamp
|
|
@@ -5279,7 +5481,8 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5279
5481
|
metrics: {
|
|
5280
5482
|
...window,
|
|
5281
5483
|
sessionCount,
|
|
5282
|
-
projectsWithNoReadableTranscript
|
|
5484
|
+
projectsWithNoReadableTranscript,
|
|
5485
|
+
sessionsWithOnlyInheritedLines
|
|
5283
5486
|
},
|
|
5284
5487
|
provenance: HISTORICAL_PROVENANCE.derived,
|
|
5285
5488
|
extractionId
|
|
@@ -7688,6 +7891,10 @@ function buildHandoff(events, extractionId, generatedAt) {
|
|
|
7688
7891
|
// and the prompts chips write, and counts each typed line in one session
|
|
7689
7892
|
// only, so `promptCount` below is typed prompts, once each.
|
|
7690
7893
|
promptBasis: "typed_once",
|
|
7894
|
+
// And the same lineage rule on the timeline: an inherited line's instant
|
|
7895
|
+
// belongs to the session it was written in, so the minutes below are this
|
|
7896
|
+
// session's own and `startedAt` is when it was resumed.
|
|
7897
|
+
minutesBasis: "owned_lines",
|
|
7691
7898
|
// And what each of those figures measures, keyed by the path a reader
|
|
7692
7899
|
// walks. The `projects` rollup below is why this store needs the stamp
|
|
7693
7900
|
// most: it carries the summed pair and the unioned pair under one
|
package/dist/daySlice.js
CHANGED
|
@@ -97,8 +97,21 @@ export function sliceSessionByLocalDay(timestamps, options = {}) {
|
|
|
97
97
|
.map((t) => new Date(t))
|
|
98
98
|
.filter((d) => Number.isFinite(d.getTime()))
|
|
99
99
|
.sort((a, b) => a.getTime() - b.getTime());
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// No early return for an empty prompt list. There used to be one, and it
|
|
101
|
+
// ran before `activeInstants` and `interruptedRunTimestamps` were read, so a
|
|
102
|
+
// session with active time but no prompt of its own got no slices at all —
|
|
103
|
+
// silently, since the session's own `activeMinutes` were fine and only the
|
|
104
|
+
// placement of them vanished. Zero-prompt sessions are ordinary: one opened
|
|
105
|
+
// from a chip, or a resume whose prompts all belong to an ancestor. On the
|
|
106
|
+
// reference store 103 of 987 sessions have no prompt of their own, 97 of
|
|
107
|
+
// them hold active time, and 1,523 minutes were dropped from the day
|
|
108
|
+
// breakdown against 52,267 placed. Six cut-short runs went with them, which
|
|
109
|
+
// the option above promises a slice for "even with no prompt on it".
|
|
110
|
+
//
|
|
111
|
+
// Nothing replaces the guard, because nothing needs to: with no prompts, no
|
|
112
|
+
// instants and no cuts, `days` below is empty and the map returns `[]` on
|
|
113
|
+
// its own. "Nothing to place" is still an empty array, and it is now the
|
|
114
|
+
// answer to the whole question rather than to the first part of it.
|
|
102
115
|
const prompts = new Map();
|
|
103
116
|
for (const t of instants) {
|
|
104
117
|
const key = localDayKey(t);
|
package/dist/daySlice.js.map
CHANGED
|
@@ -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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"}
|