@thehammer/danx-session-telemetry 0.1.44 → 0.1.45
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/.source-hash +1 -1
- package/dist/dispatch-usage.d.ts +48 -0
- package/dist/dispatch-usage.js +75 -2
- package/package.json +1 -1
package/dist/.source-hash
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3d2e532980e05f6c38b15f2a85eca3c84c3488c2afec7c285c5ac8a5c20ee254
|
package/dist/dispatch-usage.d.ts
CHANGED
|
@@ -68,6 +68,13 @@ export declare class DispatchUsage {
|
|
|
68
68
|
* its own resume point instead of regressing it.
|
|
69
69
|
*/
|
|
70
70
|
private readonly ackedCursor;
|
|
71
|
+
/**
|
|
72
|
+
* DX-3972 - per source, the `acked` the server holds for this dispatch: the `acked` of the last ACKNOWLEDGED
|
|
73
|
+
* report that carried the source. It is NOT {@link DispatchUsage.ackedCursor}: that is what the NEXT report
|
|
74
|
+
* claims (one write behind `delivered`), this is what the LAST one claimed. The terminal catch-up
|
|
75
|
+
* ({@link DispatchUsage.flushFinal}) is needed exactly while this lags the watcher's `delivered`.
|
|
76
|
+
*/
|
|
77
|
+
private readonly storedAcked;
|
|
71
78
|
/**
|
|
72
79
|
* DX-2954 (AC 34324) — the cursors this process STARTED from, reported
|
|
73
80
|
* verbatim for every source the watcher has not opened yet until one write
|
|
@@ -157,6 +164,18 @@ export declare class DispatchUsage {
|
|
|
157
164
|
* excess rule) and every side-effecting subscriber skips it — so the cost is
|
|
158
165
|
* one window of re-read, never a double count or a duplicate side effect.
|
|
159
166
|
*
|
|
167
|
+
* ## The terminal write closes the lag (DX-3972)
|
|
168
|
+
*
|
|
169
|
+
* A restart is a crash, and a crash's window is the cost above. A dispatch that
|
|
170
|
+
* ENDS is not one: its watcher is drained and stopped, so every line it read has been
|
|
171
|
+
* fed to this collector and nothing will raise a reply any more. The terminal
|
|
172
|
+
* {@link DispatchUsage.flushFinal} therefore reports `acked` equal to `delivered` for
|
|
173
|
+
* every source it has opened (see {@link DispatchUsage.catchUpReports}), and a
|
|
174
|
+
* `--resume` of the session starts at the end of what this dispatch read instead of
|
|
175
|
+
* replaying the whole file. Without it a dispatch with ONE line-bearing write ended at
|
|
176
|
+
* `{acked: 0, delivered: EOF}`, and every resume of the session re-read the whole
|
|
177
|
+
* transcript and re-shipped it into its own `dispatch_logs`.
|
|
178
|
+
*
|
|
160
179
|
* ## The process-start write carries the STARTING cursors (AC 34324)
|
|
161
180
|
*
|
|
162
181
|
* `attachMonitoringStack` deliberately does NOT await `watcher.start()` —
|
|
@@ -195,6 +214,20 @@ export declare class DispatchUsage {
|
|
|
195
214
|
* is why a write is not forced merely because cursors EXIST.
|
|
196
215
|
*/
|
|
197
216
|
private cursorsMoved;
|
|
217
|
+
/**
|
|
218
|
+
* DX-3972 - the TERMINAL write's reports: the tick's reports with every source this process has read claiming its
|
|
219
|
+
* whole `delivered` as `acked`, and the claim state AS OF that offset (the tracker's current state belongs to
|
|
220
|
+
* `delivered`, so it describes the new `acked` exactly, never a later position). Sources the watcher has not
|
|
221
|
+
* opened (a start cursor reported verbatim) are untouched: this process read nothing there to claim.
|
|
222
|
+
*/
|
|
223
|
+
private catchUpReports;
|
|
224
|
+
/**
|
|
225
|
+
* DX-3972 - whether the terminal write has anything to say: the server's `acked` still lags what the catch-up now
|
|
226
|
+
* claims. `ackedCursor` (the NEXT tick's claim) cannot tell, because a dispatch whose last write reported
|
|
227
|
+
* `delivered` with an older `acked` has `ackedCursor === delivered` and still lags; {@link DispatchUsage.storedAcked}
|
|
228
|
+
* is what the server holds. A source never reported at all has no entry, so it counts as lagging.
|
|
229
|
+
*/
|
|
230
|
+
private storedAckedLags;
|
|
198
231
|
/**
|
|
199
232
|
* Feed one raw transcript line of the usage feed set (assistant, user, attachment, system), in file order.
|
|
200
233
|
* DX-3964 - the usage collector is fed from the watcher's collector-only `onUsageLine` subscription, never from
|
|
@@ -221,6 +254,21 @@ export declare class DispatchUsage {
|
|
|
221
254
|
* acknowledged ahead of the entries it covers.
|
|
222
255
|
*/
|
|
223
256
|
flush(sink: StatsSink, dispatchId: string): Promise<FlushOutcome>;
|
|
257
|
+
/**
|
|
258
|
+
* DX-3972 - the TERMINAL write: {@link DispatchUsage.flush} that also catches the stored `acked` up to
|
|
259
|
+
* `delivered` for every source (see {@link DispatchUsage.catchUpReports}). Call it once, when the dispatch ends,
|
|
260
|
+
* after the watcher is drained and stopped (that is the whole precondition: every line it read has reached this
|
|
261
|
+
* collector, so `delivered` is covered by the entries this very write carries), and BEFORE the row goes terminal
|
|
262
|
+
* (the server refuses a terminal dispatch's stats, which would drop the catch-up silently). A write that does not
|
|
263
|
+
* land promotes nothing, exactly as in a tick; a dispatch already caught up sends no write.
|
|
264
|
+
*
|
|
265
|
+
* It does NOT wait for the dispatch's log shipper: the spawn path flushes it before this call, the reattach path
|
|
266
|
+
* after (`reattach.ts` `_cleanup`), so on a reattach `acked` can claim a line whose `dispatch_logs` row is still in
|
|
267
|
+
* the shipper's buffer. The shipper is best-effort today and a resume's shipper skips replays either way, so
|
|
268
|
+
* nothing is lost that was not already; acking only after the log line is acknowledged is DX-2956's change.
|
|
269
|
+
*/
|
|
270
|
+
flushFinal(sink: StatsSink, dispatchId: string): Promise<FlushOutcome>;
|
|
271
|
+
private write;
|
|
224
272
|
/** The dispatch's totals as the server last returned them. */
|
|
225
273
|
totals(): UsageTotals;
|
|
226
274
|
/** Window tokens (input + output + cache writes) of the server totals. */
|
package/dist/dispatch-usage.js
CHANGED
|
@@ -63,6 +63,13 @@ export class DispatchUsage {
|
|
|
63
63
|
* its own resume point instead of regressing it.
|
|
64
64
|
*/
|
|
65
65
|
ackedCursor = new Map();
|
|
66
|
+
/**
|
|
67
|
+
* DX-3972 - per source, the `acked` the server holds for this dispatch: the `acked` of the last ACKNOWLEDGED
|
|
68
|
+
* report that carried the source. It is NOT {@link DispatchUsage.ackedCursor}: that is what the NEXT report
|
|
69
|
+
* claims (one write behind `delivered`), this is what the LAST one claimed. The terminal catch-up
|
|
70
|
+
* ({@link DispatchUsage.flushFinal}) is needed exactly while this lags the watcher's `delivered`.
|
|
71
|
+
*/
|
|
72
|
+
storedAcked = new Map();
|
|
66
73
|
/**
|
|
67
74
|
* DX-2954 (AC 34324) — the cursors this process STARTED from, reported
|
|
68
75
|
* verbatim for every source the watcher has not opened yet until one write
|
|
@@ -186,6 +193,18 @@ export class DispatchUsage {
|
|
|
186
193
|
* excess rule) and every side-effecting subscriber skips it — so the cost is
|
|
187
194
|
* one window of re-read, never a double count or a duplicate side effect.
|
|
188
195
|
*
|
|
196
|
+
* ## The terminal write closes the lag (DX-3972)
|
|
197
|
+
*
|
|
198
|
+
* A restart is a crash, and a crash's window is the cost above. A dispatch that
|
|
199
|
+
* ENDS is not one: its watcher is drained and stopped, so every line it read has been
|
|
200
|
+
* fed to this collector and nothing will raise a reply any more. The terminal
|
|
201
|
+
* {@link DispatchUsage.flushFinal} therefore reports `acked` equal to `delivered` for
|
|
202
|
+
* every source it has opened (see {@link DispatchUsage.catchUpReports}), and a
|
|
203
|
+
* `--resume` of the session starts at the end of what this dispatch read instead of
|
|
204
|
+
* replaying the whole file. Without it a dispatch with ONE line-bearing write ended at
|
|
205
|
+
* `{acked: 0, delivered: EOF}`, and every resume of the session re-read the whole
|
|
206
|
+
* transcript and re-shipped it into its own `dispatch_logs`.
|
|
207
|
+
*
|
|
189
208
|
* ## The process-start write carries the STARTING cursors (AC 34324)
|
|
190
209
|
*
|
|
191
210
|
* `attachMonitoringStack` deliberately does NOT await `watcher.start()` —
|
|
@@ -281,6 +300,32 @@ export class DispatchUsage {
|
|
|
281
300
|
}
|
|
282
301
|
return false;
|
|
283
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* DX-3972 - the TERMINAL write's reports: the tick's reports with every source this process has read claiming its
|
|
305
|
+
* whole `delivered` as `acked`, and the claim state AS OF that offset (the tracker's current state belongs to
|
|
306
|
+
* `delivered`, so it describes the new `acked` exactly, never a later position). Sources the watcher has not
|
|
307
|
+
* opened (a start cursor reported verbatim) are untouched: this process read nothing there to claim.
|
|
308
|
+
*/
|
|
309
|
+
catchUpReports(reports, deliveredAtSend, claimsAtSend) {
|
|
310
|
+
const caughtUp = { ...reports };
|
|
311
|
+
for (const [source, delivered] of deliveredAtSend) {
|
|
312
|
+
caughtUp[source] = { ...reports[source], acked: delivered, claims: claimsAtSend.get(source) };
|
|
313
|
+
}
|
|
314
|
+
return caughtUp;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* DX-3972 - whether the terminal write has anything to say: the server's `acked` still lags what the catch-up now
|
|
318
|
+
* claims. `ackedCursor` (the NEXT tick's claim) cannot tell, because a dispatch whose last write reported
|
|
319
|
+
* `delivered` with an older `acked` has `ackedCursor === delivered` and still lags; {@link DispatchUsage.storedAcked}
|
|
320
|
+
* is what the server holds. A source never reported at all has no entry, so it counts as lagging.
|
|
321
|
+
*/
|
|
322
|
+
storedAckedLags(reports) {
|
|
323
|
+
for (const [source, report] of Object.entries(reports)) {
|
|
324
|
+
if (report.pastEof !== undefined || report.acked !== this.storedAcked.get(source))
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
284
329
|
/**
|
|
285
330
|
* Feed one raw transcript line of the usage feed set (assistant, user, attachment, system), in file order.
|
|
286
331
|
* DX-3964 - the usage collector is fed from the watcher's collector-only `onUsageLine` subscription, never from
|
|
@@ -329,13 +374,36 @@ export class DispatchUsage {
|
|
|
329
374
|
* acknowledged ahead of the entries it covers.
|
|
330
375
|
*/
|
|
331
376
|
flush(sink, dispatchId) {
|
|
377
|
+
return this.write(sink, dispatchId, false);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* DX-3972 - the TERMINAL write: {@link DispatchUsage.flush} that also catches the stored `acked` up to
|
|
381
|
+
* `delivered` for every source (see {@link DispatchUsage.catchUpReports}). Call it once, when the dispatch ends,
|
|
382
|
+
* after the watcher is drained and stopped (that is the whole precondition: every line it read has reached this
|
|
383
|
+
* collector, so `delivered` is covered by the entries this very write carries), and BEFORE the row goes terminal
|
|
384
|
+
* (the server refuses a terminal dispatch's stats, which would drop the catch-up silently). A write that does not
|
|
385
|
+
* land promotes nothing, exactly as in a tick; a dispatch already caught up sends no write.
|
|
386
|
+
*
|
|
387
|
+
* It does NOT wait for the dispatch's log shipper: the spawn path flushes it before this call, the reattach path
|
|
388
|
+
* after (`reattach.ts` `_cleanup`), so on a reattach `acked` can claim a line whose `dispatch_logs` row is still in
|
|
389
|
+
* the shipper's buffer. The shipper is best-effort today and a resume's shipper skips replays either way, so
|
|
390
|
+
* nothing is lost that was not already; acking only after the log line is acknowledged is DX-2956's change.
|
|
391
|
+
*/
|
|
392
|
+
flushFinal(sink, dispatchId) {
|
|
393
|
+
return this.write(sink, dispatchId, true);
|
|
394
|
+
}
|
|
395
|
+
write(sink, dispatchId, catchUp) {
|
|
332
396
|
return this.queue(async () => {
|
|
333
397
|
if (this.refused)
|
|
334
398
|
return "refused";
|
|
335
399
|
this.flushBegan = true;
|
|
336
400
|
const changed = this.counter.takeChanged();
|
|
337
401
|
const batches = chunkEntries(changed);
|
|
338
|
-
const
|
|
402
|
+
const built = this.buildCursorReports();
|
|
403
|
+
const { deliveredAtSend, claimsAtSend, reportedResets, carriesStartCursors } = built;
|
|
404
|
+
const reports = catchUp
|
|
405
|
+
? this.catchUpReports(built.reports, deliveredAtSend, claimsAtSend)
|
|
406
|
+
: built.reports;
|
|
339
407
|
// TAKE the queued side data, exactly as `counter.takeChanged()` does, so a
|
|
340
408
|
// hook that fires during the await lands in the NEXT write rather than
|
|
341
409
|
// being silently dropped by a post-write reset of the live array.
|
|
@@ -361,7 +429,7 @@ export class DispatchUsage {
|
|
|
361
429
|
// whose last batch failed must still deliver it on a later, otherwise idle
|
|
362
430
|
// tick.
|
|
363
431
|
const haveSideData = carriesStartCursors ||
|
|
364
|
-
this.cursorsMoved(reports) ||
|
|
432
|
+
(catchUp ? this.storedAckedLags(reports) : this.cursorsMoved(reports)) ||
|
|
365
433
|
activities.length > 0 ||
|
|
366
434
|
(this.transcriptPath !== null && !this.transcriptPathSent);
|
|
367
435
|
if (batches.length === 0 && (!this.started || haveSideData))
|
|
@@ -390,6 +458,11 @@ export class DispatchUsage {
|
|
|
390
458
|
// there. A source the server REFUSED as a regression keeps its own
|
|
391
459
|
// stored value, so we must not promote ours past it.
|
|
392
460
|
const refused = new Set(res.cursorRegressions);
|
|
461
|
+
// DX-3972 - what the server now holds as each reported source's `acked` (a refused source kept its own).
|
|
462
|
+
for (const [source, report] of Object.entries(reports)) {
|
|
463
|
+
if (!refused.has(source))
|
|
464
|
+
this.storedAcked.set(source, report.acked);
|
|
465
|
+
}
|
|
393
466
|
// A source whose file was found truncated WHILE this write was in
|
|
394
467
|
// flight must NOT have its floor promoted. `recordPastEof` dropped
|
|
395
468
|
// that floor to 0 on purpose; re-raising it here would make the next
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/danx-session-telemetry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
4
4
|
"description": "Shared Claude Code transcript collector (DX-2962) — incremental JSONL tailing for the danxbot worker and the danx-dashboard-mcp MCP server's relay, so both run identical collector code.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|