@juspay/neurolink 10.4.2 → 10.4.4

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 (54) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/auth/tokenStore.d.ts +7 -0
  3. package/dist/auth/tokenStore.js +54 -0
  4. package/dist/browser/neurolink.min.js +377 -380
  5. package/dist/cli/commands/proxy.d.ts +2 -1
  6. package/dist/cli/commands/proxy.js +290 -63
  7. package/dist/cli/commands/proxyAnalyze.js +10 -1
  8. package/dist/lib/auth/tokenStore.d.ts +7 -0
  9. package/dist/lib/auth/tokenStore.js +54 -0
  10. package/dist/lib/proxy/proxyAnalysis.js +136 -12
  11. package/dist/lib/proxy/proxyTranslationEngine.d.ts +1 -0
  12. package/dist/lib/proxy/proxyTranslationEngine.js +56 -12
  13. package/dist/lib/proxy/rawStreamCapture.js +47 -1
  14. package/dist/lib/proxy/requestLogger.d.ts +2 -0
  15. package/dist/lib/proxy/requestLogger.js +72 -13
  16. package/dist/lib/proxy/rollingProxyServer.js +79 -8
  17. package/dist/lib/proxy/rollingWorkerProcess.js +20 -15
  18. package/dist/lib/proxy/rollingWorkerProtocol.js +3 -0
  19. package/dist/lib/proxy/rollingWorkerSupervisor.d.ts +1 -0
  20. package/dist/lib/proxy/rollingWorkerSupervisor.js +31 -11
  21. package/dist/lib/proxy/runtimeConfig.d.ts +1 -0
  22. package/dist/lib/proxy/runtimeConfig.js +20 -5
  23. package/dist/lib/proxy/socketWorkerRuntime.js +41 -13
  24. package/dist/lib/proxy/sseInterceptor.js +47 -1
  25. package/dist/lib/proxy/tokenRefresh.d.ts +6 -0
  26. package/dist/lib/proxy/tokenRefresh.js +70 -15
  27. package/dist/lib/proxy/usageStats.d.ts +25 -3
  28. package/dist/lib/proxy/usageStats.js +546 -55
  29. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +2 -0
  30. package/dist/lib/server/routes/claudeProxyRoutes.js +261 -297
  31. package/dist/lib/types/proxy.d.ts +90 -1
  32. package/dist/proxy/proxyAnalysis.js +136 -12
  33. package/dist/proxy/proxyTranslationEngine.d.ts +1 -0
  34. package/dist/proxy/proxyTranslationEngine.js +56 -12
  35. package/dist/proxy/rawStreamCapture.js +47 -1
  36. package/dist/proxy/requestLogger.d.ts +2 -0
  37. package/dist/proxy/requestLogger.js +72 -13
  38. package/dist/proxy/rollingProxyServer.js +79 -8
  39. package/dist/proxy/rollingWorkerProcess.js +20 -15
  40. package/dist/proxy/rollingWorkerProtocol.js +3 -0
  41. package/dist/proxy/rollingWorkerSupervisor.d.ts +1 -0
  42. package/dist/proxy/rollingWorkerSupervisor.js +31 -11
  43. package/dist/proxy/runtimeConfig.d.ts +1 -0
  44. package/dist/proxy/runtimeConfig.js +20 -5
  45. package/dist/proxy/socketWorkerRuntime.js +41 -13
  46. package/dist/proxy/sseInterceptor.js +47 -1
  47. package/dist/proxy/tokenRefresh.d.ts +6 -0
  48. package/dist/proxy/tokenRefresh.js +70 -15
  49. package/dist/proxy/usageStats.d.ts +25 -3
  50. package/dist/proxy/usageStats.js +546 -55
  51. package/dist/server/routes/claudeProxyRoutes.d.ts +2 -0
  52. package/dist/server/routes/claudeProxyRoutes.js +261 -297
  53. package/dist/types/proxy.d.ts +90 -1
  54. package/package.json +1 -1
@@ -8,6 +8,7 @@ function isTransferableProxySocket(handle) {
8
8
  typeof candidate.resume === "function" &&
9
9
  typeof candidate.destroy === "function" &&
10
10
  typeof candidate.end === "function" &&
11
+ typeof candidate.off === "function" &&
11
12
  typeof candidate.once === "function");
12
13
  }
13
14
  function destroyTransferredHandle(handle) {
@@ -153,6 +154,16 @@ export function attachSocketWorkerProcess(server, input) {
153
154
  // synchronously here would truncate that in-flight request.
154
155
  setImmediate(() => runtime.drain());
155
156
  };
157
+ const takePendingSocket = (socketId) => {
158
+ const pending = pendingSockets.get(socketId);
159
+ if (!pending) {
160
+ return undefined;
161
+ }
162
+ pendingSockets.delete(socketId);
163
+ pending.socket.off("error", pending.onError);
164
+ pending.socket.off("close", pending.onClose);
165
+ return pending.socket;
166
+ };
156
167
  const onMessage = (message, handle) => {
157
168
  if (message &&
158
169
  typeof message === "object" &&
@@ -176,7 +187,27 @@ export function attachSocketWorkerProcess(server, input) {
176
187
  socket.destroy();
177
188
  return;
178
189
  }
179
- pendingSockets.set(socketId, socket);
190
+ // A client can reset while the transferred handle is paused between
191
+ // acceptance and commit. The HTTP server has not seen the socket yet,
192
+ // so it cannot install its normal transport-error handler for us.
193
+ const onError = () => {
194
+ if (pendingSockets.get(socketId)?.socket !== socket) {
195
+ return;
196
+ }
197
+ takePendingSocket(socketId);
198
+ socket.destroy();
199
+ drainWhenPendingSettled();
200
+ };
201
+ const onClose = () => {
202
+ if (pendingSockets.get(socketId)?.socket !== socket) {
203
+ return;
204
+ }
205
+ takePendingSocket(socketId);
206
+ drainWhenPendingSettled();
207
+ };
208
+ pendingSockets.set(socketId, { socket, onError, onClose });
209
+ socket.once("error", onError);
210
+ socket.once("close", onClose);
180
211
  try {
181
212
  process.send({
182
213
  type: "proxy-worker:socket-accepted",
@@ -185,14 +216,14 @@ export function attachSocketWorkerProcess(server, input) {
185
216
  socketId,
186
217
  }, (error) => {
187
218
  if (error) {
188
- pendingSockets.delete(socketId);
189
- socket.destroy();
219
+ takePendingSocket(socketId)?.destroy();
220
+ drainWhenPendingSettled();
190
221
  }
191
222
  });
192
223
  }
193
224
  catch {
194
- pendingSockets.delete(socketId);
195
- socket.destroy();
225
+ takePendingSocket(socketId)?.destroy();
226
+ drainWhenPendingSettled();
196
227
  }
197
228
  }
198
229
  else {
@@ -229,11 +260,10 @@ export function attachSocketWorkerProcess(server, input) {
229
260
  }
230
261
  else if (message.type === "proxy-worker:socket-commit" ||
231
262
  message.type === "proxy-worker:socket-cancel") {
232
- const socket = pendingSockets.get(message.socketId);
263
+ const socket = takePendingSocket(message.socketId);
233
264
  if (!socket) {
234
265
  return;
235
266
  }
236
- pendingSockets.delete(message.socketId);
237
267
  if (message.type === "proxy-worker:socket-commit") {
238
268
  runtime.acceptSocket(socket);
239
269
  }
@@ -259,10 +289,9 @@ export function attachSocketWorkerProcess(server, input) {
259
289
  // is exiting. The zero-downtime guarantee applies to the rolling handoff
260
290
  // (control-message) path, not to process termination.
261
291
  const drain = () => {
262
- for (const socket of pendingSockets.values()) {
263
- socket.destroy();
292
+ for (const socketId of [...pendingSockets.keys()]) {
293
+ takePendingSocket(socketId)?.destroy();
264
294
  }
265
- pendingSockets.clear();
266
295
  runtime.drain();
267
296
  };
268
297
  const onTerminationSignal = () => drain();
@@ -283,10 +312,9 @@ export function attachSocketWorkerProcess(server, input) {
283
312
  process.off("disconnect", drain);
284
313
  process.off("SIGTERM", onTerminationSignal);
285
314
  process.off("SIGINT", onTerminationSignal);
286
- for (const socket of pendingSockets.values()) {
287
- socket.destroy();
315
+ for (const socketId of [...pendingSockets.keys()]) {
316
+ takePendingSocket(socketId)?.destroy();
288
317
  }
289
- pendingSockets.clear();
290
318
  runtime.close();
291
319
  },
292
320
  };
@@ -429,7 +429,11 @@ export function createSSEInterceptor(options = {}) {
429
429
  // Wrap the writable side so we can intercept abort() — which does NOT
430
430
  // trigger the TransformStream's flush() or cancel() callbacks.
431
431
  const innerWriter = transform.writable.getWriter();
432
+ let writableController;
432
433
  const writable = new WritableStream({
434
+ start(controller) {
435
+ writableController = controller;
436
+ },
433
437
  write(chunk) {
434
438
  return innerWriter.write(chunk);
435
439
  },
@@ -441,8 +445,50 @@ export function createSSEInterceptor(options = {}) {
441
445
  return innerWriter.abort(reason);
442
446
  },
443
447
  });
448
+ // Preserve downstream cancellation across the wrapped writable. Without
449
+ // this bridge, client disconnects leave the upstream source and telemetry
450
+ // promise open indefinitely after the readable side is cancelled.
451
+ void innerWriter.closed.catch((reason) => {
452
+ settle();
453
+ try {
454
+ writableController.error(reason);
455
+ }
456
+ catch {
457
+ // The wrapper may already be closing or aborted.
458
+ }
459
+ });
460
+ const innerReader = transform.readable.getReader();
461
+ const readable = new ReadableStream({
462
+ async pull(controller) {
463
+ try {
464
+ const { done, value } = await innerReader.read();
465
+ if (done) {
466
+ controller.close();
467
+ }
468
+ else {
469
+ controller.enqueue(value);
470
+ }
471
+ }
472
+ catch (error) {
473
+ controller.error(error);
474
+ }
475
+ },
476
+ async cancel(reason) {
477
+ settle();
478
+ try {
479
+ writableController.error(reason);
480
+ }
481
+ catch {
482
+ // The wrapper may already be closing or aborted.
483
+ }
484
+ await Promise.allSettled([
485
+ innerReader.cancel(reason),
486
+ innerWriter.abort(reason),
487
+ ]);
488
+ },
489
+ });
444
490
  const stream = {
445
- readable: transform.readable,
491
+ readable,
446
492
  writable,
447
493
  };
448
494
  return { stream, telemetry: telemetryPromise };
@@ -8,5 +8,11 @@ export declare function isPermanentRefreshFailure(result: RefreshResult): boolea
8
8
  * loaded either the old token or the newly rotated token around persistence.
9
9
  */
10
10
  export declare function refreshToken(account: RefreshableAccount): Promise<RefreshResult>;
11
+ /**
12
+ * Refreshes against the latest persisted credential generation. Queued
13
+ * requests can otherwise reject a rotated refresh token and disable a newer
14
+ * login that was saved while they were waiting.
15
+ */
16
+ export declare function refreshTokenFromLatest(account: RefreshableAccount, target?: TokenPersistTarget): Promise<RefreshResult>;
11
17
  export declare function clearRefreshStateForTests(): void;
12
18
  export declare function persistTokens(target: TokenPersistTarget, account: RefreshableAccount): Promise<void>;
@@ -1,12 +1,14 @@
1
1
  import { logger } from "../utils/logger.js";
2
2
  import { tokenStore } from "../auth/tokenStore.js";
3
+ import { ANTHROPIC_TOKEN_URL, ANTHROPIC_TOKEN_URL_FALLBACK, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_CLIENT_ID, } from "../auth/anthropicOAuth.js";
3
4
  import { writeJsonSnapshotAtomically } from "./snapshotPersistence.js";
4
- const REFRESH_URL = "https://api.anthropic.com/v1/oauth/token";
5
- const REFRESH_URL_FALLBACK = "https://console.anthropic.com/v1/oauth/token";
6
- const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
5
+ import { withTimeout } from "../utils/async/withTimeout.js";
6
+ import { redactUrlForError } from "../utils/logSanitize.js";
7
7
  const BUFFER_MS = 5 * 60 * 1000;
8
8
  const SUCCESS_CACHE_MS = 60_000;
9
- const USER_AGENT = "claude-cli/2.1.80 (external, cli)";
9
+ // Bound the best-effort persisted-credential reconciliation read so a hung or
10
+ // slow token-store decrypt can never stall (or abort) a live token refresh.
11
+ const PEEK_TOKENS_TIMEOUT_MS = 2_000;
10
12
  const refreshesInFlight = new Map();
11
13
  export function needsRefresh(account) {
12
14
  return !!(account.expiresAt &&
@@ -23,28 +25,33 @@ async function performTokenRefresh(account) {
23
25
  if (!account.refreshToken) {
24
26
  return { success: false, error: "No refresh token available" };
25
27
  }
26
- const formBody = new URLSearchParams({
28
+ // OAuth 2.0 token endpoints require an `application/x-www-form-urlencoded`
29
+ // request body (RFC 6749 §6); a JSON body is nonstandard for this grant and
30
+ // is rejected by RFC-compliant endpoints. Mirror the interactive auth flow
31
+ // in `anthropicOAuth.ts::_refreshAccessToken`.
32
+ const requestBody = new URLSearchParams({
27
33
  grant_type: "refresh_token",
28
34
  refresh_token: account.refreshToken,
29
- client_id: CLIENT_ID,
35
+ client_id: CLAUDE_CODE_CLIENT_ID,
30
36
  }).toString();
31
37
  const headers = {
32
38
  "Content-Type": "application/x-www-form-urlencoded",
33
- "User-Agent": USER_AGENT,
39
+ Accept: "application/json",
40
+ "User-Agent": CLAUDE_CLI_USER_AGENT,
34
41
  };
35
- const urls = [REFRESH_URL, REFRESH_URL_FALLBACK];
42
+ const urls = [ANTHROPIC_TOKEN_URL, ANTHROPIC_TOKEN_URL_FALLBACK];
36
43
  let terminalFailure;
37
44
  for (const url of urls) {
38
45
  try {
39
46
  const resp = await fetch(url, {
40
47
  method: "POST",
41
48
  headers,
42
- body: formBody,
49
+ body: requestBody,
43
50
  signal: AbortSignal.timeout(10_000),
44
51
  });
45
52
  if (!resp.ok) {
46
53
  const errorBody = await resp.text();
47
- logger.warn(`[token-refresh] failed for ${account.label} at ${url}`, {
54
+ logger.warn(`[token-refresh] failed for ${account.label} at ${redactUrlForError(url)}`, {
48
55
  status: resp.status,
49
56
  error: errorBody.slice(0, 500),
50
57
  });
@@ -57,7 +64,7 @@ async function performTokenRefresh(account) {
57
64
  terminalFailure = failure;
58
65
  }
59
66
  // If primary URL returned a non-ok status, try fallback.
60
- if (url === REFRESH_URL) {
67
+ if (url === ANTHROPIC_TOKEN_URL) {
61
68
  continue;
62
69
  }
63
70
  return terminalFailure ?? failure;
@@ -75,11 +82,11 @@ async function performTokenRefresh(account) {
75
82
  return { success: true };
76
83
  }
77
84
  catch (e) {
78
- logger.warn(`[token-refresh] exception for ${account.label} at ${url}`, {
85
+ logger.warn(`[token-refresh] exception for ${account.label} at ${redactUrlForError(url)}`, {
79
86
  error: String(e),
80
87
  });
81
88
  // If primary URL threw, try fallback
82
- if (url === REFRESH_URL) {
89
+ if (url === ANTHROPIC_TOKEN_URL) {
83
90
  continue;
84
91
  }
85
92
  return terminalFailure ?? { success: false, error: String(e) };
@@ -151,6 +158,54 @@ export async function refreshToken(account) {
151
158
  }
152
159
  return shared.result;
153
160
  }
161
+ async function reloadPersistedTokenStoreAccount(account, target) {
162
+ if (!target || typeof target === "string" || !("providerKey" in target)) {
163
+ return false;
164
+ }
165
+ // Best-effort, bounded reconciliation: this only adopts a newer persisted
166
+ // credential generation if one exists. A decryption/IO failure — or a hung
167
+ // read — must NOT abort the caller, which can still refresh with the
168
+ // already-loaded token. Swallow the failure and treat it as "no newer
169
+ // persisted generation found" so refresh proceeds instead of the account
170
+ // being spuriously disabled.
171
+ let stored;
172
+ try {
173
+ stored = await withTimeout(tokenStore.peekTokens(target.providerKey), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] peekTokens reconciliation timed out");
174
+ }
175
+ catch (err) {
176
+ logger.debug("[token-refresh] skipping persisted-state reconciliation (peek failed)", { error: err instanceof Error ? err.message : String(err) });
177
+ return false;
178
+ }
179
+ if (!stored ||
180
+ stored.expiresAt <= (account.expiresAt ?? 0) ||
181
+ (stored.accessToken === account.token &&
182
+ stored.refreshToken === account.refreshToken &&
183
+ stored.expiresAt === account.expiresAt)) {
184
+ return false;
185
+ }
186
+ account.token = stored.accessToken;
187
+ account.refreshToken = stored.refreshToken;
188
+ account.expiresAt = stored.expiresAt;
189
+ return true;
190
+ }
191
+ /**
192
+ * Refreshes against the latest persisted credential generation. Queued
193
+ * requests can otherwise reject a rotated refresh token and disable a newer
194
+ * login that was saved while they were waiting.
195
+ */
196
+ export async function refreshTokenFromLatest(account, target) {
197
+ const reloadedBeforeRefresh = await reloadPersistedTokenStoreAccount(account, target);
198
+ if (reloadedBeforeRefresh && !needsRefresh(account)) {
199
+ return { success: true };
200
+ }
201
+ const result = await refreshToken(account);
202
+ if (result.success) {
203
+ return result;
204
+ }
205
+ return (await reloadPersistedTokenStoreAccount(account, target))
206
+ ? { success: true }
207
+ : result;
208
+ }
154
209
  export function clearRefreshStateForTests() {
155
210
  refreshesInFlight.clear();
156
211
  }
@@ -183,7 +238,7 @@ async function persistLegacyCredentials(credPath, account) {
183
238
  }
184
239
  async function persistTokenStoreAccount(providerKey, account) {
185
240
  try {
186
- const existing = await tokenStore.loadTokens(providerKey);
241
+ const existing = await withTimeout(tokenStore.peekTokens(providerKey), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] peekTokens for persistence timed out");
187
242
  const merged = {
188
243
  accessToken: account.token,
189
244
  refreshToken: account.refreshToken ?? existing?.refreshToken,
@@ -191,7 +246,7 @@ async function persistTokenStoreAccount(providerKey, account) {
191
246
  tokenType: existing?.tokenType ?? "Bearer",
192
247
  ...(existing?.scope ? { scope: existing.scope } : {}),
193
248
  };
194
- await tokenStore.saveTokens(providerKey, merged);
249
+ await withTimeout(tokenStore.saveTokens(providerKey, merged), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] saveTokens timed out");
195
250
  }
196
251
  catch (err) {
197
252
  logger.warn("[token-refresh] Failed to persist TokenStore credentials", {
@@ -5,34 +5,49 @@
5
5
  * shared snapshot asynchronously, under a cross-process lock, so overlapping
6
6
  * rolling workers cannot overwrite each other's counters.
7
7
  */
8
- import type { AccountStats, ProxyStats, ProxyStatsPersistenceStatus, ProxyUsageStatsStoreOptions } from "../types/index.js";
8
+ import type { AccountStats, ProxyTerminalErrorDetails, ProxyTerminalErrorJournal, ProxyStats, ProxyStatsPersistenceStatus, ProxyUsageStatsSnapshot, ProxyUsageStatsStoreOptions } from "../types/index.js";
9
9
  export declare class ProxyUsageStatsStore {
10
10
  private readonly now;
11
11
  private readonly flushIntervalMs;
12
12
  private readonly lockTimeoutMs;
13
13
  private readonly staleLockMs;
14
14
  private filePath?;
15
+ private terminalErrorsFilePath?;
15
16
  private stats;
16
17
  private pending;
18
+ private terminalErrors;
19
+ private pendingTerminalErrors;
17
20
  private pendingMutations;
18
21
  private inFlightMutations;
22
+ private pendingTerminalErrorMutations;
23
+ private inFlightTerminalErrorMutations;
19
24
  private revision;
25
+ private terminalErrorsRevision;
20
26
  private flushTimer;
21
27
  private readonly flushMutex;
22
28
  private lastFlushedAt?;
23
29
  private lastReconciledAt?;
24
30
  private lastRecoveryAt?;
25
31
  private lastError?;
32
+ private terminalErrorsLastFlushedAt?;
33
+ private terminalErrorsLastRecoveryAt?;
34
+ private terminalErrorsLastError?;
35
+ private snapshotVersion;
26
36
  constructor(options?: ProxyUsageStatsStoreOptions);
27
37
  initialize(filePath?: string): Promise<void>;
28
38
  recordAttempt(accountLabel: string, accountType: string): void;
29
39
  recordFinalSuccess(accountLabel?: string, accountType?: string): void;
30
40
  recordAttemptError(accountLabel: string, accountType: string, status: number, rateLimitKind?: "transient" | "quota"): void;
31
- recordFinalError(_status: number, accountLabel?: string, accountType?: string): void;
41
+ recordFinalError(status: number, accountLabel?: string, accountType?: string, details?: ProxyTerminalErrorDetails): void;
32
42
  getStats(): ProxyStats;
33
43
  getAccountStats(label: string): AccountStats | undefined;
44
+ getTerminalErrors(): ProxyTerminalErrorJournal;
45
+ getUsageSnapshot(): ProxyUsageStatsSnapshot;
34
46
  getPersistenceStatus(): ProxyStatsPersistenceStatus;
35
47
  flush(): Promise<void>;
48
+ private persistStatsDelta;
49
+ private persistTerminalErrorDelta;
50
+ reconcileUsageSnapshot(): Promise<ProxyUsageStatsSnapshot>;
36
51
  reconcile(): Promise<ProxyStats>;
37
52
  resetMemory(): void;
38
53
  resetForTests(): Promise<void>;
@@ -41,13 +56,18 @@ export declare class ProxyUsageStatsStore {
41
56
  private applyFinalSuccess;
42
57
  private applyAttemptError;
43
58
  private applyFinalError;
59
+ private applyTerminalError;
44
60
  private ensureAccount;
45
61
  private scheduleFlush;
46
62
  private cancelFlushTimer;
47
63
  private readSnapshot;
64
+ private readTerminalErrorSnapshot;
48
65
  private applySnapshot;
66
+ private applyTerminalErrorSnapshot;
49
67
  private recoverCorruptSnapshot;
68
+ private recoverCorruptTerminalErrorSnapshot;
50
69
  private quarantineCorruptSnapshot;
70
+ private quarantineCorruptTerminalErrorSnapshot;
51
71
  private pruneCorruptSnapshots;
52
72
  private describeError;
53
73
  }
@@ -55,10 +75,12 @@ export declare function initUsageStats(filePath: string): Promise<void>;
55
75
  export declare function recordAttempt(accountLabel: string, accountType: string): void;
56
76
  export declare function recordFinalSuccess(accountLabel?: string, accountType?: string): void;
57
77
  export declare function recordAttemptError(accountLabel: string, accountType: string, status: number, rateLimitKind?: "transient" | "quota"): void;
58
- export declare function recordFinalError(_status: number, accountLabel?: string, accountType?: string): void;
78
+ export declare function recordFinalError(status: number, accountLabel?: string, accountType?: string, details?: ProxyTerminalErrorDetails): void;
59
79
  export declare function getStats(): ProxyStats;
60
80
  export declare function getReconciledStats(): Promise<ProxyStats>;
81
+ export declare function getReconciledUsageSnapshot(): Promise<ProxyUsageStatsSnapshot>;
61
82
  export declare function getAccountStats(label: string): AccountStats | undefined;
83
+ export declare function getTerminalErrors(): ProxyTerminalErrorJournal;
62
84
  export declare function getUsageStatsPersistenceStatus(): ProxyStatsPersistenceStatus;
63
85
  export declare function flushUsageStats(): Promise<void>;
64
86
  /** Reset process-local counters while intentionally preserving durable state. */