@thehammer/danx-session-telemetry 0.1.16 → 0.1.17

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.
@@ -50,6 +50,13 @@ export declare class DispatchUsage {
50
50
  */
51
51
  private startCursors;
52
52
  private startCursorsPersisted;
53
+ /**
54
+ * DX-2954 — set the moment the first flush begins. `attachCursors` after that
55
+ * point would set the starting cursors AFTER a write that carried none, and
56
+ * `startCursorsPersisted` (set by any landed write) would then keep them from
57
+ * ever being carried — the exact AC 34324 loss, silently. So it fails loud.
58
+ */
59
+ private flushBegan;
53
60
  /** Past-EOF resets to report once, then forget. */
54
61
  private pastEof;
55
62
  /**
@@ -134,7 +141,7 @@ export declare class DispatchUsage {
134
141
  *
135
142
  * So every source in the cursors this process STARTED from that the watcher
136
143
  * has not opened yet is reported VERBATIM — the exact `{acked, delivered}`
137
- * pair it resumed from — until one write carrying them has landed. That is
144
+ * pair it resumed from — until a write that carries them has landed. That is
138
145
  * what makes "every attach persists its starting cursors" true on the FIRST
139
146
  * write rather than one stats tick (15-30 s) later: a reattach of this row
140
147
  * inside its first interval reads them back (start-order rule 1) and starts
@@ -70,6 +70,13 @@ export class DispatchUsage {
70
70
  */
71
71
  startCursors = {};
72
72
  startCursorsPersisted = false;
73
+ /**
74
+ * DX-2954 — set the moment the first flush begins. `attachCursors` after that
75
+ * point would set the starting cursors AFTER a write that carried none, and
76
+ * `startCursorsPersisted` (set by any landed write) would then keep them from
77
+ * ever being carried — the exact AC 34324 loss, silently. So it fails loud.
78
+ */
79
+ flushBegan = false;
73
80
  /** Past-EOF resets to report once, then forget. */
74
81
  pastEof = [];
75
82
  /**
@@ -93,6 +100,11 @@ export class DispatchUsage {
93
100
  * each write.
94
101
  */
95
102
  attachCursors(readCursorState, startCursors) {
103
+ // DX-2954 (AC 34324): the starting cursors ride the process-start write, so
104
+ // they must be attached BEFORE any write is attempted.
105
+ if (this.flushBegan) {
106
+ throw new Error("DispatchUsage.attachCursors: called after the first stats write began — the starting cursors would never be persisted");
107
+ }
96
108
  this.readCursorState = readCursorState;
97
109
  this.startCursors = startCursors;
98
110
  for (const [source, cursor] of Object.entries(startCursors)) {
@@ -173,7 +185,7 @@ export class DispatchUsage {
173
185
  *
174
186
  * So every source in the cursors this process STARTED from that the watcher
175
187
  * has not opened yet is reported VERBATIM — the exact `{acked, delivered}`
176
- * pair it resumed from — until one write carrying them has landed. That is
188
+ * pair it resumed from — until a write that carries them has landed. That is
177
189
  * what makes "every attach persists its starting cursors" true on the FIRST
178
190
  * write rather than one stats tick (15-30 s) later: a reattach of this row
179
191
  * inside its first interval reads them back (start-order rule 1) and starts
@@ -289,6 +301,7 @@ export class DispatchUsage {
289
301
  return this.queue(async () => {
290
302
  if (this.refused)
291
303
  return "refused";
304
+ this.flushBegan = true;
292
305
  const changed = this.counter.takeChanged();
293
306
  const batches = chunkEntries(changed);
294
307
  const { reports, deliveredAtSend, reportedResets, carriesStartCursors } = this.buildCursorReports();
@@ -360,6 +373,9 @@ export class DispatchUsage {
360
373
  }
361
374
  if (payload.transcriptPath !== undefined)
362
375
  this.transcriptPathSent = true;
376
+ // Safe on ANY landed write: `attachCursors` refuses to run after the
377
+ // first flush began, so every starting cursor was either carried by
378
+ // this write or is superseded by a live one that this write reported.
363
379
  this.startCursorsPersisted = true;
364
380
  }
365
381
  }
@@ -76,7 +76,7 @@ export interface SessionLogWatcherOptions {
76
76
  * That lost every line between the last write and the restart; a cursor does
77
77
  * not.
78
78
  */
79
- startCursors?: Record<string, SourceCursorStart>;
79
+ startCursors: Record<string, SourceCursorStart>;
80
80
  /**
81
81
  * DX-2954 — rule 4 of the start order: start every ALREADY-EXISTING file at
82
82
  * its current end.
@@ -507,7 +507,7 @@ export class SessionLogWatcher {
507
507
  this.sessionId = options.sessionId;
508
508
  this.dispatchId = options.dispatchId;
509
509
  this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
510
- this.startCursors = options.startCursors ?? {};
510
+ this.startCursors = options.startCursors;
511
511
  this.startExistingFilesAtEof = options.startExistingFilesAtEof ?? false;
512
512
  this.onCursorPastEof = options.onCursorPastEof;
513
513
  this.onSourceDiscovered = options.onSourceDiscovered;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-session-telemetry",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Shared Claude Code transcript collector (DX-2962) — incremental JSONL tailing for the danxbot worker and the danx-dashboard-mcp desktop bridge, so both run identical collector code.",
5
5
  "license": "MIT",
6
6
  "type": "module",