@newrelic/preflight 1.70.0 → 1.72.0

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.
Files changed (181) hide show
  1. package/README.md +2 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +7 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
  6. package/dist/dashboard/routes/api-handler.js +11 -16
  7. package/dist/dashboard/routes/api-handler.js.map +1 -1
  8. package/dist/dashboard/subagent-timeline-store.d.ts +12 -1
  9. package/dist/dashboard/subagent-timeline-store.d.ts.map +1 -1
  10. package/dist/dashboard/subagent-timeline-store.js +32 -4
  11. package/dist/dashboard/subagent-timeline-store.js.map +1 -1
  12. package/dist/dashboard/workflow-store.d.ts.map +1 -1
  13. package/dist/dashboard/workflow-store.js +2 -3
  14. package/dist/dashboard/workflow-store.js.map +1 -1
  15. package/dist/hooks/collector-script.d.ts.map +1 -1
  16. package/dist/hooks/collector-script.js +14 -8
  17. package/dist/hooks/collector-script.js.map +1 -1
  18. package/dist/hooks/copilot-app-usage-watcher.d.ts.map +1 -1
  19. package/dist/hooks/copilot-app-usage-watcher.js +3 -4
  20. package/dist/hooks/copilot-app-usage-watcher.js.map +1 -1
  21. package/dist/hooks/copilot-usage-watcher.d.ts +25 -5
  22. package/dist/hooks/copilot-usage-watcher.d.ts.map +1 -1
  23. package/dist/hooks/copilot-usage-watcher.js +254 -92
  24. package/dist/hooks/copilot-usage-watcher.js.map +1 -1
  25. package/dist/hooks/event-processor.d.ts +24 -11
  26. package/dist/hooks/event-processor.d.ts.map +1 -1
  27. package/dist/hooks/event-processor.js +139 -45
  28. package/dist/hooks/event-processor.js.map +1 -1
  29. package/dist/hooks/index.d.ts +1 -1
  30. package/dist/hooks/index.d.ts.map +1 -1
  31. package/dist/hooks/index.js +1 -1
  32. package/dist/hooks/index.js.map +1 -1
  33. package/dist/hooks/parent-transcript-watcher.d.ts +1 -1
  34. package/dist/hooks/parent-transcript-watcher.d.ts.map +1 -1
  35. package/dist/hooks/parent-transcript-watcher.js +11 -6
  36. package/dist/hooks/parent-transcript-watcher.js.map +1 -1
  37. package/dist/hooks/session-resolver.d.ts.map +1 -1
  38. package/dist/hooks/session-resolver.js +2 -1
  39. package/dist/hooks/session-resolver.js.map +1 -1
  40. package/dist/hooks/subagent-watcher.d.ts +21 -15
  41. package/dist/hooks/subagent-watcher.d.ts.map +1 -1
  42. package/dist/hooks/subagent-watcher.js +89 -73
  43. package/dist/hooks/subagent-watcher.js.map +1 -1
  44. package/dist/hooks/throttled-warning.d.ts +5 -5
  45. package/dist/hooks/throttled-warning.d.ts.map +1 -1
  46. package/dist/hooks/throttled-warning.js +14 -12
  47. package/dist/hooks/throttled-warning.js.map +1 -1
  48. package/dist/hooks/workflow-watcher.d.ts.map +1 -1
  49. package/dist/hooks/workflow-watcher.js +2 -2
  50. package/dist/hooks/workflow-watcher.js.map +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +57 -62
  53. package/dist/index.js.map +1 -1
  54. package/dist/install/cli.d.ts.map +1 -1
  55. package/dist/install/cli.js +131 -17
  56. package/dist/install/cli.js.map +1 -1
  57. package/dist/install/copilot-install-helper.d.ts +1 -0
  58. package/dist/install/copilot-install-helper.d.ts.map +1 -1
  59. package/dist/install/copilot-install-helper.js +82 -26
  60. package/dist/install/copilot-install-helper.js.map +1 -1
  61. package/dist/install/credentials.d.ts +23 -0
  62. package/dist/install/credentials.d.ts.map +1 -0
  63. package/dist/install/credentials.js +57 -0
  64. package/dist/install/credentials.js.map +1 -0
  65. package/dist/install/diagnostics.d.ts.map +1 -1
  66. package/dist/install/diagnostics.js +6 -3
  67. package/dist/install/diagnostics.js.map +1 -1
  68. package/dist/install/install-helper.d.ts +9 -0
  69. package/dist/install/install-helper.d.ts.map +1 -1
  70. package/dist/install/install-helper.js +42 -17
  71. package/dist/install/install-helper.js.map +1 -1
  72. package/dist/install/schedule.d.ts.map +1 -1
  73. package/dist/install/schedule.js +4 -1
  74. package/dist/install/schedule.js.map +1 -1
  75. package/dist/install/setup-wizard.d.ts.map +1 -1
  76. package/dist/install/setup-wizard.js +3 -3
  77. package/dist/install/setup-wizard.js.map +1 -1
  78. package/dist/install/target-user.d.ts +33 -0
  79. package/dist/install/target-user.d.ts.map +1 -0
  80. package/dist/install/target-user.js +121 -0
  81. package/dist/install/target-user.js.map +1 -0
  82. package/dist/lib/claude-config-dir.d.ts +3 -0
  83. package/dist/lib/claude-config-dir.d.ts.map +1 -0
  84. package/dist/lib/claude-config-dir.js +10 -0
  85. package/dist/lib/claude-config-dir.js.map +1 -0
  86. package/dist/lib/claude-settings-env.d.ts +18 -0
  87. package/dist/lib/claude-settings-env.d.ts.map +1 -0
  88. package/dist/lib/claude-settings-env.js +78 -0
  89. package/dist/lib/claude-settings-env.js.map +1 -0
  90. package/dist/lib/date.d.ts +7 -0
  91. package/dist/lib/date.d.ts.map +1 -1
  92. package/dist/lib/date.js +11 -1
  93. package/dist/lib/date.js.map +1 -1
  94. package/dist/lib/subagent-transcript-parser.d.ts +2 -0
  95. package/dist/lib/subagent-transcript-parser.d.ts.map +1 -1
  96. package/dist/lib/subagent-transcript-parser.js +5 -0
  97. package/dist/lib/subagent-transcript-parser.js.map +1 -1
  98. package/dist/lib/transcript-types.d.ts +4 -0
  99. package/dist/lib/transcript-types.d.ts.map +1 -1
  100. package/dist/metrics/cost-forecast.d.ts +2 -0
  101. package/dist/metrics/cost-forecast.d.ts.map +1 -1
  102. package/dist/metrics/cost-forecast.js +46 -24
  103. package/dist/metrics/cost-forecast.js.map +1 -1
  104. package/dist/metrics/cost-tracker.d.ts +3 -3
  105. package/dist/metrics/cost-tracker.js +3 -3
  106. package/dist/metrics/efficiency-score.d.ts +2 -2
  107. package/dist/metrics/efficiency-score.js +2 -2
  108. package/dist/metrics/first-save-baselines.d.ts +60 -0
  109. package/dist/metrics/first-save-baselines.d.ts.map +1 -0
  110. package/dist/metrics/first-save-baselines.js +77 -0
  111. package/dist/metrics/first-save-baselines.js.map +1 -0
  112. package/dist/metrics/local-session-aggregator.d.ts +6 -5
  113. package/dist/metrics/local-session-aggregator.d.ts.map +1 -1
  114. package/dist/metrics/local-session-aggregator.js +34 -6
  115. package/dist/metrics/local-session-aggregator.js.map +1 -1
  116. package/dist/metrics/model-pricing.d.ts +1 -1
  117. package/dist/metrics/model-pricing.d.ts.map +1 -1
  118. package/dist/metrics/model-pricing.js +2 -2
  119. package/dist/metrics/model-pricing.js.map +1 -1
  120. package/dist/metrics/model-usage-tracker.d.ts +2 -2
  121. package/dist/metrics/model-usage-tracker.js +2 -2
  122. package/dist/metrics/pricing-init.d.ts.map +1 -1
  123. package/dist/metrics/pricing-init.js +5 -3
  124. package/dist/metrics/pricing-init.js.map +1 -1
  125. package/dist/metrics/pricing-overlay.d.ts.map +1 -1
  126. package/dist/metrics/pricing-overlay.js +1 -4
  127. package/dist/metrics/pricing-overlay.js.map +1 -1
  128. package/dist/metrics/pricing-refresh.d.ts +16 -4
  129. package/dist/metrics/pricing-refresh.d.ts.map +1 -1
  130. package/dist/metrics/pricing-refresh.js +47 -1
  131. package/dist/metrics/pricing-refresh.js.map +1 -1
  132. package/dist/metrics/quality-proxy-tracker.d.ts +2 -2
  133. package/dist/metrics/quality-proxy-tracker.js +2 -2
  134. package/dist/metrics/session-tracker.d.ts +5 -1
  135. package/dist/metrics/session-tracker.d.ts.map +1 -1
  136. package/dist/metrics/session-tracker.js +58 -17
  137. package/dist/metrics/session-tracker.js.map +1 -1
  138. package/dist/metrics/task-detector.d.ts +2 -2
  139. package/dist/metrics/task-detector.js +2 -2
  140. package/dist/metrics/token-usage.d.ts +6 -0
  141. package/dist/metrics/token-usage.d.ts.map +1 -0
  142. package/dist/metrics/token-usage.js +34 -0
  143. package/dist/metrics/token-usage.js.map +1 -0
  144. package/dist/metrics/turn-cost-attributor.d.ts.map +1 -1
  145. package/dist/metrics/turn-cost-attributor.js +3 -0
  146. package/dist/metrics/turn-cost-attributor.js.map +1 -1
  147. package/dist/platforms/copilot-adapter.d.ts.map +1 -1
  148. package/dist/platforms/copilot-adapter.js +3 -0
  149. package/dist/platforms/copilot-adapter.js.map +1 -1
  150. package/dist/platforms/copilot-sdk-adapter.d.ts.map +1 -1
  151. package/dist/platforms/copilot-sdk-adapter.js +3 -0
  152. package/dist/platforms/copilot-sdk-adapter.js.map +1 -1
  153. package/dist/storage/drain-claims.d.ts +82 -0
  154. package/dist/storage/drain-claims.d.ts.map +1 -0
  155. package/dist/storage/drain-claims.js +239 -0
  156. package/dist/storage/drain-claims.js.map +1 -0
  157. package/dist/storage/hook-buffer.d.ts +7 -0
  158. package/dist/storage/hook-buffer.d.ts.map +1 -0
  159. package/dist/storage/hook-buffer.js +132 -0
  160. package/dist/storage/hook-buffer.js.map +1 -0
  161. package/dist/storage/local-store.d.ts +38 -16
  162. package/dist/storage/local-store.d.ts.map +1 -1
  163. package/dist/storage/local-store.js +228 -72
  164. package/dist/storage/local-store.js.map +1 -1
  165. package/dist/storage/retention.d.ts.map +1 -1
  166. package/dist/storage/retention.js +7 -6
  167. package/dist/storage/retention.js.map +1 -1
  168. package/dist/storage/session-store.d.ts +37 -8
  169. package/dist/storage/session-store.d.ts.map +1 -1
  170. package/dist/storage/session-store.js +255 -48
  171. package/dist/storage/session-store.js.map +1 -1
  172. package/dist/storage/types.d.ts +7 -0
  173. package/dist/storage/types.d.ts.map +1 -1
  174. package/dist/storage/types.js +4 -0
  175. package/dist/storage/types.js.map +1 -1
  176. package/dist/tools/cost-tools.d.ts.map +1 -1
  177. package/dist/tools/cost-tools.js +1 -0
  178. package/dist/tools/cost-tools.js.map +1 -1
  179. package/dist/web/assets/{index-U9DPZ-Tb.js → index-CZvLy2Qy.js} +2 -2
  180. package/dist/web/index.html +1 -1
  181. package/package.json +4 -1
@@ -1 +1 @@
1
- {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/storage/retention.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/storage/retention.ts"],"names":[],"mappings":"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"}
@@ -212,15 +212,20 @@ export interface AttributableActivity {
212
212
  readonly subagentCostUsd?: number;
213
213
  readonly timeline?: ReadonlyArray<unknown>;
214
214
  }
215
+ export interface RecordedUsage extends AttributableActivity {
216
+ readonly estimatedCostUsd?: number | null;
217
+ readonly tokensInput?: number;
218
+ readonly tokensOutput?: number;
219
+ readonly tokensThinking?: number;
220
+ }
215
221
  /**
216
222
  * Does this summary represent work we can attribute to a real session?
217
223
  *
218
- * THE single definition of "not empty" for session persistence. Three call
219
- * sites derive from it and must never re-implement it:
220
- * 1. `LocalSessionAggregator.toSummaries()` — which rollups get written.
221
- * 2. `SessionStore.saveSession()` — which incoming writes may overwrite.
222
- * 3. the `/api/sessions/today/aggregate` route — which persisted rows may
223
- * contribute a pro-rated cost when they carry no per-day buckets.
224
+ * The narrower of the two definitions of "not empty" for session
225
+ * persistence; `hasRecordedUsage` is the wider and gates the writes. This one
226
+ * is for the `/api/sessions/today/aggregate` route, which uses it to decide
227
+ * which persisted rows may contribute a pro-rated cost when they carry no
228
+ * per-day buckets. Callers must never re-implement either definition.
224
229
  *
225
230
  * Subagent spend counts as activity on purpose. A session whose parent process
226
231
  * died while its subagent transcripts were still being tailed has an empty
@@ -232,6 +237,16 @@ export interface AttributableActivity {
232
237
  * — is still false, which is what keeps the empty-clobber guard intact.
233
238
  */
234
239
  export declare function hasAttributableActivity(s: AttributableActivity): boolean;
240
+ /**
241
+ * Did this session record anything worth a file on disk? Wider than
242
+ * `hasAttributableActivity`: a chat-only session has no tool calls but real
243
+ * cost and tokens, and must still be kept. A session with none of these is an
244
+ * idle MCP process (a window that was opened and never used). It gates the
245
+ * aggregator's summaries, every write `SessionStore.saveSession()` accepts,
246
+ * the existing file an empty write must not clobber, and the History list in
247
+ * `/api/sessions`.
248
+ */
249
+ export declare function hasRecordedUsage(s: RecordedUsage): boolean;
235
250
  export interface SessionFileInfo {
236
251
  readonly filename: string;
237
252
  readonly sessionId: string;
@@ -270,9 +285,23 @@ export declare class SessionStore {
270
285
  storagePath: string;
271
286
  });
272
287
  getSessionsDir(): string;
273
- saveSession(summary: FullSessionSummary): void;
288
+ /**
289
+ * Returns null when nothing was written, else the file the write merged
290
+ * against (`previous`, null for a new file).
291
+ */
292
+ saveSession(summary: FullSessionSummary): {
293
+ readonly previous: FullSessionSummary | null;
294
+ } | null;
274
295
  loadSession(sessionId: string): FullSessionSummary | null;
275
- /** Like loadSession(), but also returns the file path it was loaded from — used by saveSession() to detect and clean up a stale file after a merge relocates a session to a different filename. */
296
+ /**
297
+ * Like loadSession(), but also returns the file path it was loaded from — used by saveSession() to detect and clean up a stale file after a merge relocates a session to a different filename.
298
+ *
299
+ * An unreadable file is skipped rather than ending the search: returning null
300
+ * there made saveSession() treat the session as new and start a fresh file
301
+ * on every later save. Sibling files for one id are not merged here,
302
+ * because files split that way hold disjoint activity that mergeSummaries'
303
+ * per-counter max would drop.
304
+ */
276
305
  private loadSessionWithPath;
277
306
  listSessions(options?: ListSessionsOptions): SessionFileInfo[];
278
307
  loadAllSessions(options?: LoadSessionsOptions): FullSessionSummary[];
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/storage/session-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/storage/session-store.ts"],"names":[],"mappings":"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"}
@@ -11,8 +11,9 @@
11
11
  * `buildSessionSummary()` pulls getMetrics() from each tracker and aggregates
12
12
  * task-level data into a single FullSessionSummary.
13
13
  */
14
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
15
- import { join, resolve, sep } from 'node:path';
14
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
15
+ import { randomBytes } from 'node:crypto';
16
+ import { basename, dirname, join, resolve, sep } from 'node:path';
16
17
  import { createLogger } from '../shared/index.js';
17
18
  import { redactIdentifier, redactSensitive } from '../config.js';
18
19
  import { stripRepoNameCredentials } from '../lib/git-remote.js';
@@ -41,12 +42,11 @@ export function toPersistedAntiPatterns(patterns) {
41
42
  /**
42
43
  * Does this summary represent work we can attribute to a real session?
43
44
  *
44
- * THE single definition of "not empty" for session persistence. Three call
45
- * sites derive from it and must never re-implement it:
46
- * 1. `LocalSessionAggregator.toSummaries()` — which rollups get written.
47
- * 2. `SessionStore.saveSession()` — which incoming writes may overwrite.
48
- * 3. the `/api/sessions/today/aggregate` route — which persisted rows may
49
- * contribute a pro-rated cost when they carry no per-day buckets.
45
+ * The narrower of the two definitions of "not empty" for session
46
+ * persistence; `hasRecordedUsage` is the wider and gates the writes. This one
47
+ * is for the `/api/sessions/today/aggregate` route, which uses it to decide
48
+ * which persisted rows may contribute a pro-rated cost when they carry no
49
+ * per-day buckets. Callers must never re-implement either definition.
50
50
  *
51
51
  * Subagent spend counts as activity on purpose. A session whose parent process
52
52
  * died while its subagent transcripts were still being tailed has an empty
@@ -66,9 +66,12 @@ export function hasAttributableActivity(s) {
66
66
  * Did this session record anything worth a file on disk? Wider than
67
67
  * `hasAttributableActivity`: a chat-only session has no tool calls but real
68
68
  * cost and tokens, and must still be kept. A session with none of these is an
69
- * idle MCP process (a window that was opened and never used).
69
+ * idle MCP process (a window that was opened and never used). It gates the
70
+ * aggregator's summaries, every write `SessionStore.saveSession()` accepts,
71
+ * the existing file an empty write must not clobber, and the History list in
72
+ * `/api/sessions`.
70
73
  */
71
- function hasRecordedUsage(s) {
74
+ export function hasRecordedUsage(s) {
72
75
  return (hasAttributableActivity(s) ||
73
76
  (s.estimatedCostUsd ?? 0) > 0 ||
74
77
  (s.tokensInput ?? 0) + (s.tokensOutput ?? 0) + (s.tokensThinking ?? 0) > 0);
@@ -387,10 +390,14 @@ export class SessionStore {
387
390
  getSessionsDir() {
388
391
  return this.sessionsDir;
389
392
  }
393
+ /**
394
+ * Returns null when nothing was written, else the file the write merged
395
+ * against (`previous`, null for a new file).
396
+ */
390
397
  saveSession(summary) {
391
398
  if (!/^[A-Za-z0-9_-]{1,128}$/.test(summary.sessionId) || summary.sessionId === 'unknown') {
392
399
  logger.warn('Rejecting invalid sessionId for file path', { sessionId: summary.sessionId });
393
- return;
400
+ return null;
394
401
  }
395
402
  if (!existsSync(this.sessionsDir)) {
396
403
  mkdirSync(this.sessionsDir, { recursive: true, mode: 0o700 });
@@ -406,7 +413,7 @@ export class SessionStore {
406
413
  // merged (inflated, since counters take the max) result to a NEW file
407
414
  // while leaving the OLD file on disk untouched — the session then
408
415
  // appeared twice in history with overlapping, double-counted totals.
409
- const existingWithPath = this.loadSessionWithPath(summary.sessionId);
416
+ const { existing: existingWithPath, filesRead } = this.loadSessionWithPath(summary.sessionId);
410
417
  // Every MCP server process checkpoints every 30s, including windows that
411
418
  // never saw a tool call or a token. Writing those first-time files filled
412
419
  // history with model-less zero-activity rows that dragged down every
@@ -415,7 +422,7 @@ export class SessionStore {
415
422
  logger.debug('Skipping first write of a session with no recorded usage', {
416
423
  sessionId: summary.sessionId,
417
424
  });
418
- return;
425
+ return null;
419
426
  }
420
427
  // A second process saving under the same sessionId (e.g. two MCP
421
428
  // servers both resumed/forked against one real session ID) would
@@ -426,16 +433,16 @@ export class SessionStore {
426
433
  // never observed any hook activity would replace a fully recorded session
427
434
  // with an all-zero summary, erasing tool calls, tokens and cost from the
428
435
  // dashboard on the next refresh. Never let an empty summary clobber a
429
- // non-empty one; other collisions keep the previous last-write-wins
430
- // behaviour with a warning.
436
+ // non-empty one. A summary with cost or tokens but no tool calls, such as
437
+ // a restarted dashboard's parent-only rollup, is not empty: it merges.
431
438
  let toWrite = summary;
432
439
  if (existingWithPath) {
433
440
  const { summary: existing } = existingWithPath;
434
- if (hasAttributableActivity(existing) && !hasAttributableActivity(summary)) {
441
+ if (hasRecordedUsage(existing) && !hasRecordedUsage(summary)) {
435
442
  logger.warn('Refusing to overwrite recorded session with an empty summary', {
436
443
  sessionId: summary.sessionId,
437
444
  });
438
- return;
445
+ return null;
439
446
  }
440
447
  // Two processes legitimately share one session id: the CLI/editor
441
448
  // spawns an MCP engine that owns the session natively, while a
@@ -463,8 +470,28 @@ export class SessionStore {
463
470
  if (!filepath.startsWith(this.sessionsDir + sep)) {
464
471
  throw new Error(`Session path escaped storage directory: ${filepath}`);
465
472
  }
473
+ const unparsedFilesReplaced = filesRead.filter((file) => file.parsed.kind !== 'clean' &&
474
+ (file.filepath === filepath || file.filepath === existingWithPath?.filepath));
475
+ // Write-then-rename so two processes saving the same session id can never
476
+ // interleave bytes in the target file (the temp name does not end in .json,
477
+ // so loaders never see it).
478
+ const content = JSON.stringify(toWrite, null, 2) + '\n';
479
+ const tmpPath = `${filepath}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
466
480
  try {
467
- writeFileSync(filepath, JSON.stringify(toWrite, null, 2) + '\n', { mode: 0o600 });
481
+ try {
482
+ writeFileSync(tmpPath, content, { mode: 0o600 });
483
+ if (!unparsedFilesReplaced.every(copiedAsideUnchanged))
484
+ return null;
485
+ renameIntoPlace(tmpPath, filepath, content);
486
+ }
487
+ finally {
488
+ try {
489
+ unlinkSync(tmpPath);
490
+ }
491
+ catch {
492
+ // Renamed into place, or never written.
493
+ }
494
+ }
468
495
  // The merge landed on a different filename than where the existing
469
496
  // record lived (a date-rollover case) — remove the now-superseded
470
497
  // file so the session isn't left duplicated across two files.
@@ -481,6 +508,7 @@ export class SessionStore {
481
508
  }
482
509
  }
483
510
  logger.debug('Session saved', { sessionId: toWrite.sessionId, filename });
511
+ return { previous: existingWithPath?.summary ?? null };
484
512
  }
485
513
  catch (err) {
486
514
  const message = err instanceof Error ? err.message : String(err);
@@ -489,42 +517,57 @@ export class SessionStore {
489
517
  filename,
490
518
  error: message,
491
519
  });
520
+ return null;
492
521
  }
493
522
  }
494
523
  loadSession(sessionId) {
495
- return this.loadSessionWithPath(sessionId)?.summary ?? null;
524
+ return this.loadSessionWithPath(sessionId).existing?.summary ?? null;
496
525
  }
497
- /** Like loadSession(), but also returns the file path it was loaded from — used by saveSession() to detect and clean up a stale file after a merge relocates a session to a different filename. */
526
+ /**
527
+ * Like loadSession(), but also returns the file path it was loaded from — used by saveSession() to detect and clean up a stale file after a merge relocates a session to a different filename.
528
+ *
529
+ * An unreadable file is skipped rather than ending the search: returning null
530
+ * there made saveSession() treat the session as new and start a fresh file
531
+ * on every later save. Sibling files for one id are not merged here,
532
+ * because files split that way hold disjoint activity that mergeSummaries'
533
+ * per-counter max would drop.
534
+ */
498
535
  loadSessionWithPath(sessionId) {
499
536
  if (!existsSync(this.sessionsDir))
500
- return null;
501
- for (const file of readdirSync(this.sessionsDir)) {
502
- if (!file.endsWith('.json'))
503
- continue;
504
- if (parseSessionFilename(file)?.sessionId !== sessionId)
505
- continue;
506
- const filepath = join(this.sessionsDir, file);
537
+ return { existing: null, filesRead: [] };
538
+ const names = readdirSync(this.sessionsDir)
539
+ .filter((file) => parseSessionFilename(file)?.sessionId === sessionId)
540
+ .sort();
541
+ const filesRead = [];
542
+ for (const file of names) {
543
+ const filepath = resolve(this.sessionsDir, file);
507
544
  try {
508
- const raw = readFileSync(filepath, 'utf-8');
509
- const session = deserializeSession(raw);
510
- if (session === null) {
511
- logger.warn('Failed to deserialize session file', { file });
512
- return null;
513
- }
514
- return { summary: session, filepath };
545
+ const bytes = readFileSync(filepath);
546
+ filesRead.push({
547
+ filepath,
548
+ bytes,
549
+ parsed: parseSessionFile(bytes.toString('utf-8'), filepath),
550
+ });
515
551
  }
516
552
  catch {
517
553
  logger.warn('Failed to read session file', { file });
518
554
  }
519
555
  }
520
- return null;
556
+ for (const { filepath, parsed } of filesRead) {
557
+ const summary = usableSummary(parsed, () => names.length - filesRead.filter((f) => f.parsed.kind === 'unreadable').length);
558
+ if (summary)
559
+ return { existing: { summary, filepath }, filesRead };
560
+ }
561
+ return { existing: null, filesRead };
521
562
  }
522
563
  listSessions(options) {
523
564
  if (!existsSync(this.sessionsDir))
524
565
  return [];
525
566
  const sinceDate = options?.since ? formatDate(options.since) : null;
526
567
  const results = [];
527
- for (const file of readdirSync(this.sessionsDir)) {
568
+ const names = readdirSync(this.sessionsDir);
569
+ const parsingFilesForId = parsingFileCounter(this.sessionsDir, names.flatMap((name) => parseSessionFilename(name) ?? []));
570
+ for (const file of names) {
528
571
  if (!file.endsWith('.json'))
529
572
  continue;
530
573
  const parsed = parseSessionFilename(file);
@@ -534,9 +577,9 @@ export class SessionStore {
534
577
  continue;
535
578
  if (options?.developer) {
536
579
  try {
537
- const raw = readFileSync(join(this.sessionsDir, file), 'utf-8');
538
- const session = deserializeSession(raw);
539
- if (session?.developer !== options.developer)
580
+ const filepath = join(this.sessionsDir, file);
581
+ const summary = usableSummary(parseSessionFile(readFileSync(filepath, 'utf-8'), filepath), () => parsingFilesForId(parsed.sessionId));
582
+ if (!summary || summary.developer !== options.developer)
540
583
  continue;
541
584
  }
542
585
  catch {
@@ -553,12 +596,10 @@ export class SessionStore {
553
596
  const sinceDate = options?.since ? formatDate(options.since) : null;
554
597
  const modifiedSinceMs = options?.includeModifiedSince?.getTime() ?? null;
555
598
  const results = [];
556
- for (const file of readdirSync(this.sessionsDir)) {
557
- if (!file.endsWith('.json'))
558
- continue;
559
- const parsed = parseSessionFilename(file);
560
- if (!parsed)
561
- continue;
599
+ const sessionFiles = readdirSync(this.sessionsDir).flatMap((file) => parseSessionFilename(file) ?? []);
600
+ const parsingFilesForId = parsingFileCounter(this.sessionsDir, sessionFiles);
601
+ for (const parsed of sessionFiles) {
602
+ const file = parsed.filename;
562
603
  if (sinceDate && parsed.date < sinceDate) {
563
604
  if (modifiedSinceMs === null)
564
605
  continue;
@@ -571,8 +612,8 @@ export class SessionStore {
571
612
  }
572
613
  }
573
614
  try {
574
- const raw = readFileSync(join(this.sessionsDir, file), 'utf-8');
575
- const session = deserializeSession(raw);
615
+ const filepath = join(this.sessionsDir, file);
616
+ const session = usableSummary(parseSessionFile(readFileSync(filepath, 'utf-8'), filepath), () => parsingFilesForId(parsed.sessionId));
576
617
  if (!session)
577
618
  continue;
578
619
  // Synthetic session IDs (`local-*`, `proxy-*`, `pending-*`) are
@@ -1313,11 +1354,77 @@ export function deserializeFullSessionSummary(obj) {
1313
1354
  attribution: parseAttribution(obj.attribution),
1314
1355
  };
1315
1356
  }
1357
+ const warnedKeys = new Set();
1358
+ function warnOnce(key, message, data) {
1359
+ if (warnedKeys.has(key))
1360
+ return;
1361
+ warnedKeys.add(key);
1362
+ logger.warn(message, data);
1363
+ }
1364
+ function parseSessionFile(raw, filepath) {
1365
+ const summary = parseSummary(raw);
1366
+ if (summary)
1367
+ return { kind: 'clean', summary };
1368
+ const first = firstTopLevelJsonDocument(raw);
1369
+ const recovered = first === null ? null : parseSummary(first);
1370
+ const file = basename(filepath);
1371
+ if (recovered) {
1372
+ warnOnce(`recovered:${filepath}`, 'Recovered first JSON document from corrupt session file', {
1373
+ file,
1374
+ });
1375
+ return { kind: 'recovered', summary: recovered };
1376
+ }
1377
+ warnOnce(`unreadable:${filepath}`, 'Failed to deserialize session file', { file });
1378
+ return { kind: 'unreadable' };
1379
+ }
1380
+ /**
1381
+ * A recovered document is used only when its file is the only one of its id
1382
+ * that parses. Beside a parsing sibling it may repeat activity the sibling
1383
+ * already holds, so adding the two could double count. It stays unread there,
1384
+ * as it was before recovery existed, until a session's split files are
1385
+ * combined. A sibling that parses as unreadable holds nothing to double count,
1386
+ * so it does not hide the recovered file. A sibling that cannot be read at all
1387
+ * might hold anything, so it counts as parsing. The count includes the file
1388
+ * itself and is only asked for when the file is recovered.
1389
+ */
1390
+ function usableSummary(parsed, parsingFilesForId) {
1391
+ if (parsed.kind === 'unreadable')
1392
+ return null;
1393
+ return parsed.kind === 'clean' || parsingFilesForId() === 1 ? parsed.summary : null;
1394
+ }
1395
+ /**
1396
+ * Counts, per session id, the files that parse as clean or recovered. A file
1397
+ * that cannot be read at all might hold anything, so it counts as parsing.
1398
+ * Reads an id's files once, on first ask.
1399
+ */
1400
+ function parsingFileCounter(sessionsDir, files) {
1401
+ const filenamesById = new Map();
1402
+ for (const { sessionId, filename } of files) {
1403
+ filenamesById.set(sessionId, [...(filenamesById.get(sessionId) ?? []), filename]);
1404
+ }
1405
+ const counts = new Map();
1406
+ return (sessionId) => {
1407
+ const known = counts.get(sessionId);
1408
+ if (known !== undefined)
1409
+ return known;
1410
+ const count = (filenamesById.get(sessionId) ?? []).filter((filename) => {
1411
+ const filepath = join(sessionsDir, filename);
1412
+ try {
1413
+ return parseSessionFile(readFileSync(filepath, 'utf-8'), filepath).kind !== 'unreadable';
1414
+ }
1415
+ catch {
1416
+ return true;
1417
+ }
1418
+ }).length;
1419
+ counts.set(sessionId, count);
1420
+ return count;
1421
+ };
1422
+ }
1316
1423
  /**
1317
1424
  * Parse a raw session JSON string and extract known fields. Delegates field
1318
1425
  * extraction to `deserializeFullSessionSummary` to avoid duplication.
1319
1426
  */
1320
- function deserializeSession(raw) {
1427
+ function parseSummary(raw) {
1321
1428
  let parsed;
1322
1429
  try {
1323
1430
  parsed = JSON.parse(raw);
@@ -1329,6 +1436,106 @@ function deserializeSession(raw) {
1329
1436
  return null;
1330
1437
  return deserializeFullSessionSummary(parsed);
1331
1438
  }
1439
+ function copiedAsideUnchanged(file) {
1440
+ const name = basename(file.filepath);
1441
+ try {
1442
+ if (!readFileSync(file.filepath).equals(file.bytes)) {
1443
+ logger.warn('Session file changed since it was read; skipping this save', { file: name });
1444
+ return false;
1445
+ }
1446
+ if (hasCorruptCopy(file))
1447
+ return true;
1448
+ const suffix = `${Date.now()}.${process.pid}.${randomBytes(4).toString('hex')}`;
1449
+ writeFileSync(`${file.filepath}.${suffix}.corrupt`, file.bytes, {
1450
+ mode: 0o600,
1451
+ flag: 'wx',
1452
+ });
1453
+ return true;
1454
+ }
1455
+ catch (err) {
1456
+ logger.warn('Could not copy an unparsed session file aside; skipping this save', {
1457
+ file: name,
1458
+ error: err instanceof Error ? err.message : String(err),
1459
+ });
1460
+ return false;
1461
+ }
1462
+ }
1463
+ /** True when a `.corrupt` copy of these exact bytes already sits beside the file. */
1464
+ function hasCorruptCopy(file) {
1465
+ const dir = dirname(file.filepath);
1466
+ const prefix = `${basename(file.filepath)}.`;
1467
+ return readdirSync(dir).some((entry) => {
1468
+ if (!entry.startsWith(prefix) || !entry.endsWith('.corrupt'))
1469
+ return false;
1470
+ try {
1471
+ return readFileSync(join(dir, entry)).equals(file.bytes);
1472
+ }
1473
+ catch {
1474
+ return false;
1475
+ }
1476
+ });
1477
+ }
1478
+ const RENAME_RETRY_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);
1479
+ const RENAME_BACKOFF_MS = [10, 20, 40, 80];
1480
+ const SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
1481
+ /**
1482
+ * Windows refuses to rename over a file another process has open, which
1483
+ * antivirus scanners do briefly after each write. Retry with backoff, then
1484
+ * write the target directly so saves never silently stop.
1485
+ */
1486
+ function renameIntoPlace(tmpPath, filepath, content) {
1487
+ for (let attempt = 0;; attempt++) {
1488
+ try {
1489
+ renameSync(tmpPath, filepath);
1490
+ return;
1491
+ }
1492
+ catch (err) {
1493
+ const code = err.code ?? '';
1494
+ if (!RENAME_RETRY_CODES.has(code))
1495
+ throw err;
1496
+ if (process.platform !== 'win32' || attempt === RENAME_BACKOFF_MS.length)
1497
+ break;
1498
+ Atomics.wait(SLEEP_CELL, 0, 0, RENAME_BACKOFF_MS[attempt]);
1499
+ }
1500
+ }
1501
+ try {
1502
+ writeFileSync(filepath, content, { mode: 0o600 });
1503
+ }
1504
+ catch (err) {
1505
+ warnOnce(`rename-failed:${filepath}`, 'Could not rename session file into place and the direct write failed too', { file: basename(filepath), error: err instanceof Error ? err.message : String(err) });
1506
+ throw err;
1507
+ }
1508
+ warnOnce(`rename:${filepath}`, 'Could not rename session file into place; wrote it directly', {
1509
+ file: basename(filepath),
1510
+ });
1511
+ }
1512
+ /** Returns the leading `{...}` of `raw` (string- and escape-aware), or null if it never closes. */
1513
+ function firstTopLevelJsonDocument(raw) {
1514
+ const start = raw.search(/\S/);
1515
+ if (start === -1 || raw[start] !== '{')
1516
+ return null;
1517
+ let depth = 0;
1518
+ let inString = false;
1519
+ for (let i = start; i < raw.length; i++) {
1520
+ const ch = raw[i];
1521
+ if (inString) {
1522
+ if (ch === '\\')
1523
+ i++;
1524
+ else if (ch === '"')
1525
+ inString = false;
1526
+ }
1527
+ else if (ch === '"') {
1528
+ inString = true;
1529
+ }
1530
+ else if (ch === '{') {
1531
+ depth++;
1532
+ }
1533
+ else if (ch === '}' && --depth === 0) {
1534
+ return raw.slice(start, i + 1);
1535
+ }
1536
+ }
1537
+ return null;
1538
+ }
1332
1539
  function parseSessionFilename(filename) {
1333
1540
  // Expected format: YYYY-MM-DD_sessionId.json
1334
1541
  const match = filename.match(/^(\d{4}-\d{2}-\d{2})_(.+)\.json$/);