sidekick-shared 0.24.4 → 0.25.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 (98) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +200 -52
  3. package/dist/accountChangeSignal.d.ts +3 -0
  4. package/dist/accountChangeSignal.js +19 -0
  5. package/dist/accountChanges.d.ts +21 -0
  6. package/dist/accountChanges.js +151 -0
  7. package/dist/accountManager.d.ts +13 -0
  8. package/dist/accountManager.js +60 -32
  9. package/dist/accountRegistry.d.ts +3 -1
  10. package/dist/accountRegistry.js +64 -41
  11. package/dist/accounts.js +40 -19
  12. package/dist/autoSwitch.d.ts +2 -1
  13. package/dist/autoSwitch.js +32 -7
  14. package/dist/browser.d.ts +10 -3
  15. package/dist/browser.js +17 -1
  16. package/dist/codexProfiles.d.ts +14 -0
  17. package/dist/codexProfiles.js +268 -116
  18. package/dist/codexQuotaWatcher.d.ts +6 -0
  19. package/dist/codexQuotaWatcher.js +101 -47
  20. package/dist/context/sessionContext.js +2 -4
  21. package/dist/ensureDefaultAccounts.js +4 -4
  22. package/dist/index.d.ts +32 -15
  23. package/dist/index.js +54 -9
  24. package/dist/modelAliases.d.ts +13 -0
  25. package/dist/modelAliases.js +58 -0
  26. package/dist/modelCatalog.d.ts +32 -0
  27. package/dist/modelCatalog.js +145 -0
  28. package/dist/modelContext.d.ts +36 -3
  29. package/dist/modelContext.js +109 -34
  30. package/dist/modelInfo.d.ts +30 -1
  31. package/dist/modelInfo.js +116 -26
  32. package/dist/multiProviderQuotaService.d.ts +8 -0
  33. package/dist/multiProviderQuotaService.js +64 -3
  34. package/dist/node.d.ts +8 -2
  35. package/dist/node.js +11 -1
  36. package/dist/observedContextWindows.d.ts +13 -0
  37. package/dist/observedContextWindows.js +13 -0
  38. package/dist/observedSessionCollector.d.ts +75 -4
  39. package/dist/observedSessionCollector.js +452 -42
  40. package/dist/parsers/codexHistory.d.ts +32 -0
  41. package/dist/parsers/codexHistory.js +125 -0
  42. package/dist/parsers/mcpToolName.d.ts +26 -0
  43. package/dist/parsers/mcpToolName.js +31 -0
  44. package/dist/parsers/sessionActivityDetector.d.ts +6 -0
  45. package/dist/parsers/sessionActivityDetector.js +17 -0
  46. package/dist/parsers/sessionPathResolver.d.ts +11 -4
  47. package/dist/parsers/sessionPathResolver.js +11 -4
  48. package/dist/paths.d.ts +7 -3
  49. package/dist/paths.js +7 -3
  50. package/dist/providerIds.d.ts +11 -0
  51. package/dist/providerIds.js +9 -0
  52. package/dist/providerQuota.d.ts +3 -1
  53. package/dist/providerQuota.js +3 -0
  54. package/dist/providers/claudeCode.d.ts +11 -1
  55. package/dist/providers/claudeCode.js +222 -39
  56. package/dist/providers/codex.d.ts +39 -1
  57. package/dist/providers/codex.js +326 -39
  58. package/dist/providers/codexDatabase.d.ts +9 -0
  59. package/dist/providers/codexDatabase.js +107 -2
  60. package/dist/providers/diagnostics.d.ts +13 -0
  61. package/dist/providers/diagnostics.js +88 -0
  62. package/dist/providers/factory.d.ts +14 -0
  63. package/dist/providers/factory.js +57 -0
  64. package/dist/providers/openCode.d.ts +20 -1
  65. package/dist/providers/openCode.js +361 -11
  66. package/dist/providers/openCodeDatabase.d.ts +10 -0
  67. package/dist/providers/openCodeDatabase.js +69 -0
  68. package/dist/providers/types.d.ts +57 -2
  69. package/dist/providers/types.js +4 -0
  70. package/dist/quota.d.ts +4 -1
  71. package/dist/quota.js +3 -7
  72. package/dist/quotaHistory.d.ts +3 -2
  73. package/dist/quotaHistory.js +2 -2
  74. package/dist/quotaPoller.d.ts +13 -1
  75. package/dist/quotaPoller.js +65 -3
  76. package/dist/quotaSnapshots.js +8 -77
  77. package/dist/schemas/accountManager.d.ts +2 -2
  78. package/dist/schemas/accountManager.js +2 -4
  79. package/dist/schemas/accountStatus.d.ts +3 -0
  80. package/dist/schemas/accountStatus.js +1 -0
  81. package/dist/schemas/index.d.ts +1 -0
  82. package/dist/schemas/index.js +7 -2
  83. package/dist/schemas/observedSessionV1.d.ts +27 -26
  84. package/dist/schemas/observedSessionV1.js +6 -4
  85. package/dist/schemas/quota.d.ts +2 -2
  86. package/dist/schemas/quota.js +3 -6
  87. package/dist/schemas/quotaHistory.d.ts +2 -2
  88. package/dist/schemas/tokenEstimation.d.ts +1 -1
  89. package/dist/sessionMonitor.d.ts +15 -0
  90. package/dist/sessionMonitor.js +142 -0
  91. package/dist/sessionPreviews.d.ts +85 -0
  92. package/dist/sessionPreviews.js +549 -0
  93. package/dist/sessionTranscripts.js +10 -4
  94. package/dist/types/observedSessionV1.d.ts +6 -2
  95. package/dist/types/observedSessionV1.js +14 -8
  96. package/dist/writers/atomic.d.ts +20 -0
  97. package/dist/writers/atomic.js +190 -19
  98. package/package.json +22 -1
@@ -10,7 +10,31 @@
10
10
  */
11
11
  import type { ReadSessionContextSnapshotOptions, SessionContextSnapshot } from '../context/sessionContext';
12
12
  import type { SessionEvent, SubagentStats, TokenUsage, ContextAttribution } from '../types/sessionEvent';
13
- export type ProviderId = 'claude-code' | 'opencode' | 'codex';
13
+ import { SESSION_PROVIDER_IDS } from '../providerIds';
14
+ /** Stable runtime list for consumers that must enumerate session providers. */
15
+ export { SESSION_PROVIDER_IDS } from '../providerIds';
16
+ export type ProviderId = (typeof SESSION_PROVIDER_IDS)[number];
17
+ export type SessionProviderDiagnosticSeverity = 'info' | 'warning' | 'error';
18
+ export type SessionProviderDiagnosticPhase = 'construct' | 'initialize' | 'enumerate' | 'query' | 'read' | 'watch';
19
+ export type SessionProviderDiagnosticKind = 'construction_failed' | 'home_unavailable' | 'config_unreadable' | 'db_missing' | 'sqlite_missing' | 'sqlite_blocked' | 'query_failed' | 'read_failed' | 'watch_failed';
20
+ /** Content-safe provider degradation emitted at construction or first use. */
21
+ export interface SessionProviderDiagnostic {
22
+ providerId: ProviderId;
23
+ kind: SessionProviderDiagnosticKind;
24
+ severity: SessionProviderDiagnosticSeverity;
25
+ phase: SessionProviderDiagnosticPhase;
26
+ message: string;
27
+ }
28
+ export interface ProviderOperationStatus {
29
+ operation: string;
30
+ usable: boolean;
31
+ degraded: boolean;
32
+ runtimeStatus: ProviderRuntimeStatus;
33
+ diagnostics: readonly SessionProviderDiagnostic[];
34
+ }
35
+ export interface SessionProviderOptions {
36
+ onDiagnostic?: (diagnostic: SessionProviderDiagnostic) => void;
37
+ }
14
38
  export interface SearchHit {
15
39
  sessionPath: string;
16
40
  line: string;
@@ -25,6 +49,14 @@ export interface SessionFileInfo {
25
49
  mtime: Date;
26
50
  /** Optional human-readable label (e.g., first user prompt) */
27
51
  label?: string;
52
+ /** Optional size supplied by synthetic/DB-backed enumerators. */
53
+ sizeBytes?: number;
54
+ /** Optional provider-native session id. */
55
+ sessionId?: string;
56
+ /** Optional provider-recorded workspace path. */
57
+ workspacePath?: string;
58
+ /** Optional provider-recorded creation timestamp. */
59
+ createdAt?: Date;
28
60
  }
29
61
  export interface SessionFileStats {
30
62
  providerId: ProviderId;
@@ -58,7 +90,7 @@ export interface ProjectFolderInfo {
58
90
  }
59
91
  export interface ProviderRuntimeStatus {
60
92
  available: boolean;
61
- kind: 'available' | 'db_missing' | 'sqlite_missing' | 'sqlite_blocked' | 'query_failed';
93
+ kind: 'available' | 'home_unavailable' | 'config_unreadable' | 'db_missing' | 'sqlite_missing' | 'sqlite_blocked' | 'query_failed';
62
94
  message?: string;
63
95
  }
64
96
  /**
@@ -105,6 +137,12 @@ export interface SessionProviderBase {
105
137
  findActiveSession(workspacePath: string): string | null;
106
138
  /** Finds all session files for a workspace, sorted by mtime (most recent first). */
107
139
  findAllSessions(workspacePath: string): string[];
140
+ /**
141
+ * Finds one session without reading every transcript. Wrong ids return null.
142
+ * Optional so pre-0.25 external implementations keep compiling; when absent,
143
+ * callers fall back to scanning findAllSessions().
144
+ */
145
+ findSessionById?(workspacePath: string, sessionId: string): string | null;
108
146
  /** Finds all session files in a specific directory. */
109
147
  findSessionsInDirectory(dir: string): string[];
110
148
  /** Gets all project folders with session data. */
@@ -131,6 +169,12 @@ export interface SessionProviderBase {
131
169
  getSessionMetadata?(sessionPath: string): {
132
170
  mtime: Date;
133
171
  } | null;
172
+ /** Event-loop-safe metadata refresh for synthetic/DB-backed session paths. */
173
+ getSessionMetadataAsync?(sessionPath: string): Promise<{
174
+ mtime: Date;
175
+ } | null>;
176
+ /** Roots whose changes can invalidate this provider's session fingerprints. */
177
+ getWatchRoots?(): string[];
134
178
  /** Gets the context window token limit for a model. Returns 200K by default. */
135
179
  getContextWindowLimit?(modelId?: string): number;
136
180
  /** Computes context window size from token usage. Provider-specific formula. */
@@ -143,6 +187,17 @@ export interface SessionProviderBase {
143
187
  readSessionContextSnapshot?(sessionPath: string, options?: ReadSessionContextSnapshotOptions): SessionContextSnapshot;
144
188
  /** Reports provider runtime readiness for DB-backed providers. */
145
189
  getRuntimeStatus?(): ProviderRuntimeStatus;
190
+ /** Status for the most recent provider operation, including fallback diagnostics. */
191
+ getLastOperationStatus?(): ProviderOperationStatus;
192
+ /**
193
+ * Lists every session file this provider knows about, across all workspaces.
194
+ * Stat-only — implementations must not read file contents.
195
+ */
196
+ listAllSessionFiles?(): SessionFileInfo[];
197
+ /** Event-loop-safe enumeration used by async preview and subscription APIs. */
198
+ listSessionFilesAsync?(workspacePath?: string): Promise<SessionFileInfo[]>;
199
+ /** Batch label extraction. DB-backed implementations use at most one query. */
200
+ extractSessionLabelsAsync?(sessionPaths: readonly string[]): Promise<Map<string, string | null>>;
146
201
  /** Tests whether a provider can monitor a directory path, including synthetic DB-backed paths. */
147
202
  canMonitorDirectory?(dir: string): boolean;
148
203
  /** Release resources. */
@@ -10,3 +10,7 @@
10
10
  * compatibility with existing CLI code.
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.SESSION_PROVIDER_IDS = void 0;
14
+ /** Stable runtime list for consumers that must enumerate session providers. */
15
+ var providerIds_1 = require("../providerIds");
16
+ Object.defineProperty(exports, "SESSION_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.SESSION_PROVIDER_IDS; } });
package/dist/quota.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import type { QuotaProviderId } from './providerIds';
2
+ export { QUOTA_PROVIDER_IDS } from './providerIds';
3
+ export type { QuotaProviderId } from './providerIds';
1
4
  /**
2
5
  * Stateless quota fetcher for Claude Max subscription usage.
3
6
  *
@@ -42,7 +45,7 @@ export interface QuotaState {
42
45
  /** Projected 7-day utilization at reset (percentage, capped at 200) */
43
46
  projectedSevenDay?: number;
44
47
  /** Provider that produced this quota sample */
45
- providerId?: 'claude-code' | 'codex' | 'zai';
48
+ providerId?: QuotaProviderId;
46
49
  /** Source of the sample */
47
50
  source?: 'api' | 'session' | 'cache';
48
51
  /** ISO timestamp when the sample was captured */
package/dist/quota.js CHANGED
@@ -1,15 +1,11 @@
1
1
  "use strict";
2
- /**
3
- * Stateless quota fetcher for Claude Max subscription usage.
4
- *
5
- * Calls the Anthropic OAuth usage endpoint and returns utilization data.
6
- * Polling / eventing is the caller's responsibility.
7
- */
8
2
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.SEVEN_DAY_WINDOW_MS = exports.FIVE_HOUR_WINDOW_MS = void 0;
3
+ exports.SEVEN_DAY_WINDOW_MS = exports.FIVE_HOUR_WINDOW_MS = exports.QUOTA_PROVIDER_IDS = void 0;
10
4
  exports.projectQuotaWindow = projectQuotaWindow;
11
5
  exports.withQuotaProjections = withQuotaProjections;
12
6
  exports.fetchQuota = fetchQuota;
7
+ var providerIds_1 = require("./providerIds");
8
+ Object.defineProperty(exports, "QUOTA_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.QUOTA_PROVIDER_IDS; } });
13
9
  const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
14
10
  const BETA_HEADER = 'oauth-2025-04-20';
15
11
  const FETCH_TIMEOUT_MS = 10_000;
@@ -3,10 +3,11 @@
3
3
  *
4
4
  * Sibling to `quotaSnapshots.ts`. Where snapshots persist a single most-recent sample per
5
5
  * (provider, account), this module accumulates time-series samples so consumers (the VS Code
6
- * dashboard, the `sidekick quota history` CLI, contextful_desktop) can render heatmaps and
6
+ * dashboard, the `sidekick quota history` CLI, external consumers) can render heatmaps and
7
7
  * trend visualisations over a 13-week window.
8
8
  */
9
- export type QuotaHistoryRuntimeProvider = 'claude' | 'codex' | 'zai';
9
+ import type { RuntimeQuotaProviderId } from './providerIds';
10
+ export type QuotaHistoryRuntimeProvider = RuntimeQuotaProviderId;
10
11
  export interface QuotaHistorySample {
11
12
  timestamp: string;
12
13
  runtimeProvider: QuotaHistoryRuntimeProvider;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Sibling to `quotaSnapshots.ts`. Where snapshots persist a single most-recent sample per
6
6
  * (provider, account), this module accumulates time-series samples so consumers (the VS Code
7
- * dashboard, the `sidekick quota history` CLI, contextful_desktop) can render heatmaps and
7
+ * dashboard, the `sidekick quota history` CLI, external consumers) can render heatmaps and
8
8
  * trend visualisations over a 13-week window.
9
9
  */
10
10
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -258,7 +258,7 @@ async function runAppend(sample, filePath, options) {
258
258
  // ignore
259
259
  }
260
260
  // Backwards-compat: keep the latest-snapshot store hot so existing callers (DashboardViewProvider,
261
- // codex session provider, contextful_desktop) don't have to query history for "latest".
261
+ // codex session provider, external consumers) don't have to query history for "latest".
262
262
  try {
263
263
  const providerId = runtimeProviderToSnapshotProvider(sample.runtimeProvider);
264
264
  (0, quotaSnapshots_1.writeQuotaSnapshot)(providerId, sample.providerId, sampleToQuotaState(sample));
@@ -7,6 +7,7 @@
7
7
  * @module quotaPoller
8
8
  */
9
9
  import type { QuotaState } from './quota';
10
+ import { type AccountsChangedEvent } from './accountChanges';
10
11
  /** Disposable subscription handle. */
11
12
  export interface Disposable {
12
13
  dispose(): void;
@@ -20,7 +21,11 @@ export interface QuotaPollerOptions {
20
21
  /** Maximum backoff delay for retries (ms). Default: 120_000 (2 minutes). */
21
22
  maxBackoffMs?: number;
22
23
  /** Returns the current access token. Called before each fetch. */
23
- getAccessToken: () => Promise<string>;
24
+ getAccessToken: () => Promise<string | null | undefined>;
25
+ /** Optional cheap account-presence check performed before credential access. */
26
+ hasAccount?: () => boolean | Promise<boolean>;
27
+ /** Override the process-wide login/logout signal (primarily for embedded hosts/tests). */
28
+ subscribeAccountsChanged?: (listener: (event: AccountsChangedEvent) => void) => Disposable;
24
29
  }
25
30
  /**
26
31
  * Polls Claude Max quota usage with exponential backoff on transient errors,
@@ -47,12 +52,18 @@ export declare class QuotaPoller {
47
52
  private readonly idleIntervalMs;
48
53
  private readonly maxBackoffMs;
49
54
  private readonly getAccessToken;
55
+ private readonly hasAccount?;
56
+ private readonly subscribeAccountsChanged;
50
57
  private timer;
51
58
  private listeners;
52
59
  private latest;
53
60
  private isActive;
54
61
  private consecutiveFailures;
55
62
  private stopped;
63
+ private polling;
64
+ private pendingWake;
65
+ private dormant;
66
+ private accountSubscription;
56
67
  constructor(options: QuotaPollerOptions);
57
68
  /**
58
69
  * Starts polling. First poll is immediate.
@@ -82,6 +93,7 @@ export declare class QuotaPoller {
82
93
  */
83
94
  getLatest(): QuotaState | null;
84
95
  private poll;
96
+ private wake;
85
97
  private scheduleNext;
86
98
  private notify;
87
99
  }
@@ -10,6 +10,7 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.QuotaPoller = void 0;
12
12
  const quota_1 = require("./quota");
13
+ const accountChanges_1 = require("./accountChanges");
13
14
  /**
14
15
  * Polls Claude Max quota usage with exponential backoff on transient errors,
15
16
  * cached fallback, and configurable active/idle intervals.
@@ -35,17 +36,25 @@ class QuotaPoller {
35
36
  idleIntervalMs;
36
37
  maxBackoffMs;
37
38
  getAccessToken;
39
+ hasAccount;
40
+ subscribeAccountsChanged;
38
41
  timer = null;
39
42
  listeners = [];
40
43
  latest = null;
41
44
  isActive = false;
42
45
  consecutiveFailures = 0;
43
46
  stopped = false;
47
+ polling = false;
48
+ pendingWake = false;
49
+ dormant = false;
50
+ accountSubscription = null;
44
51
  constructor(options) {
45
52
  this.activeIntervalMs = options.activeIntervalMs ?? 60_000;
46
53
  this.idleIntervalMs = options.idleIntervalMs ?? 300_000;
47
54
  this.maxBackoffMs = options.maxBackoffMs ?? 120_000;
48
55
  this.getAccessToken = options.getAccessToken;
56
+ this.hasAccount = options.hasAccount;
57
+ this.subscribeAccountsChanged = options.subscribeAccountsChanged ?? accountChanges_1.onAccountsChanged;
49
58
  }
50
59
  /**
51
60
  * Starts polling. First poll is immediate.
@@ -53,17 +62,21 @@ class QuotaPoller {
53
62
  start() {
54
63
  this.stopped = false;
55
64
  this.isActive = true;
56
- void this.poll();
65
+ this.accountSubscription ??= this.subscribeAccountsChanged(() => this.wake());
66
+ this.wake();
57
67
  }
58
68
  /**
59
69
  * Stops polling and clears timers.
60
70
  */
61
71
  stop() {
62
72
  this.stopped = true;
73
+ this.dormant = false;
63
74
  if (this.timer) {
64
75
  clearTimeout(this.timer);
65
76
  this.timer = null;
66
77
  }
78
+ this.accountSubscription?.dispose();
79
+ this.accountSubscription = null;
67
80
  }
68
81
  /**
69
82
  * Switches to active polling interval.
@@ -102,8 +115,35 @@ class QuotaPoller {
102
115
  async poll() {
103
116
  if (this.stopped)
104
117
  return;
118
+ if (this.polling) {
119
+ this.pendingWake = true;
120
+ return;
121
+ }
122
+ this.polling = true;
123
+ this.pendingWake = false;
124
+ this.dormant = false;
125
+ let nextDelayOverride;
105
126
  try {
127
+ if (this.hasAccount && !(await this.hasAccount())) {
128
+ this.dormant = true;
129
+ return;
130
+ }
106
131
  const token = await this.getAccessToken();
132
+ if (!token) {
133
+ if (this.hasAccount) {
134
+ // The account exists but its token was unreadable (locked keychain,
135
+ // transient credential I/O). That never changes the account
136
+ // fingerprint, so no wake would arrive — retry at the idle cadence
137
+ // instead of parking forever.
138
+ nextDelayOverride = this.idleIntervalMs;
139
+ }
140
+ else {
141
+ // Without a hasAccount probe, a missing token is the no-account
142
+ // signal; the account-change subscription wakes the poller on login.
143
+ this.dormant = true;
144
+ }
145
+ return;
146
+ }
107
147
  const state = await (0, quota_1.fetchQuota)(token);
108
148
  if (state.available) {
109
149
  this.latest = state;
@@ -116,7 +156,7 @@ class QuotaPoller {
116
156
  this.latest = state;
117
157
  this.notify(state);
118
158
  this.consecutiveFailures = 0;
119
- this.scheduleNext(this.idleIntervalMs);
159
+ nextDelayOverride = this.idleIntervalMs;
120
160
  return;
121
161
  }
122
162
  else {
@@ -148,7 +188,29 @@ class QuotaPoller {
148
188
  this.latest = state;
149
189
  this.notify(state);
150
190
  }
151
- this.scheduleNext();
191
+ finally {
192
+ this.polling = false;
193
+ if (this.pendingWake) {
194
+ this.pendingWake = false;
195
+ void this.poll();
196
+ }
197
+ else if (!this.dormant) {
198
+ this.scheduleNext(nextDelayOverride);
199
+ }
200
+ }
201
+ }
202
+ wake() {
203
+ if (this.stopped)
204
+ return;
205
+ if (this.timer) {
206
+ clearTimeout(this.timer);
207
+ this.timer = null;
208
+ }
209
+ if (this.polling) {
210
+ this.pendingWake = true;
211
+ return;
212
+ }
213
+ void this.poll();
152
214
  }
153
215
  scheduleNext(overrideMs) {
154
216
  if (this.stopped)
@@ -35,40 +35,17 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.writeQuotaSnapshot = writeQuotaSnapshot;
37
37
  exports.readQuotaSnapshot = readQuotaSnapshot;
38
- const crypto = __importStar(require("crypto"));
39
38
  const fs = __importStar(require("fs"));
40
39
  const path = __importStar(require("path"));
41
40
  const paths_1 = require("./paths");
41
+ const atomic_1 = require("./writers/atomic");
42
42
  const codex_1 = require("./types/codex");
43
- const LOCK_WAIT_MS = 15;
44
- const LOCK_STALE_MS = 10_000;
45
- const LOCK_TIMEOUT_MS = 15_000;
46
- const lockSleepView = new Int32Array(new SharedArrayBuffer(4));
47
43
  function getQuotaSnapshotPath() {
48
44
  return path.join((0, paths_1.getConfigDir)(), 'quota-snapshots.json');
49
45
  }
50
46
  function ensureConfigDir() {
51
47
  fs.mkdirSync((0, paths_1.getConfigDir)(), { recursive: true, mode: 0o700 });
52
48
  }
53
- function atomicWriteJson(filePath, data, mode = 0o600) {
54
- const tmp = `${filePath}.${process.pid}.${Date.now()}.${crypto.randomBytes(8).toString('hex')}.tmp`;
55
- const json = JSON.stringify(data, null, 2);
56
- // Throws if `data` was undefined or a top-level non-serializable value — prevents writing the literal string "undefined" to disk.
57
- JSON.parse(json);
58
- try {
59
- fs.writeFileSync(tmp, json, { encoding: 'utf8', mode });
60
- fs.renameSync(tmp, filePath);
61
- }
62
- catch (error) {
63
- try {
64
- fs.rmSync(tmp, { force: true });
65
- }
66
- catch {
67
- // Best effort cleanup only.
68
- }
69
- throw error;
70
- }
71
- }
72
49
  function readStore() {
73
50
  try {
74
51
  const parsed = JSON.parse(fs.readFileSync(getQuotaSnapshotPath(), 'utf8'));
@@ -86,62 +63,16 @@ function readStore() {
86
63
  }
87
64
  function writeStore(store) {
88
65
  ensureConfigDir();
89
- atomicWriteJson(getQuotaSnapshotPath(), store);
66
+ (0, atomic_1.atomicWriteJsonSync)(getQuotaSnapshotPath(), store);
90
67
  }
68
+ /**
69
+ * Serializes snapshot read-modify-write cycles across processes on the shared
70
+ * store lock. The lock file path is unchanged from the previous local
71
+ * implementation, so processes running older versions still exclude correctly.
72
+ */
91
73
  function withSnapshotStoreLock(operation) {
92
74
  ensureConfigDir();
93
- const lockPath = `${getQuotaSnapshotPath()}.lock`;
94
- const token = `${process.pid}:${crypto.randomBytes(16).toString('hex')}`;
95
- const deadline = Date.now() + LOCK_TIMEOUT_MS;
96
- while (true) {
97
- try {
98
- const descriptor = fs.openSync(lockPath, 'wx', 0o600);
99
- fs.writeFileSync(descriptor, token, 'utf8');
100
- fs.fsyncSync(descriptor);
101
- fs.closeSync(descriptor);
102
- break;
103
- }
104
- catch (error) {
105
- if (error.code !== 'EEXIST')
106
- throw error;
107
- try {
108
- const stat = fs.statSync(lockPath);
109
- if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
110
- const owner = fs.readFileSync(lockPath, 'utf8');
111
- const ownerPid = Number.parseInt(owner.split(':', 1)[0], 10);
112
- let ownerAlive = Number.isInteger(ownerPid) && ownerPid > 0;
113
- if (ownerAlive) {
114
- try {
115
- process.kill(ownerPid, 0);
116
- }
117
- catch (ownerError) {
118
- ownerAlive = ownerError.code === 'EPERM';
119
- }
120
- }
121
- if (!ownerAlive)
122
- fs.rmSync(lockPath, { force: true });
123
- }
124
- }
125
- catch {
126
- // The owner may have released the lock between checks.
127
- }
128
- if (Date.now() >= deadline)
129
- throw new Error(`Timed out waiting for quota lock: ${lockPath}`);
130
- Atomics.wait(lockSleepView, 0, 0, LOCK_WAIT_MS);
131
- }
132
- }
133
- try {
134
- return operation();
135
- }
136
- finally {
137
- try {
138
- if (fs.readFileSync(lockPath, 'utf8') === token)
139
- fs.rmSync(lockPath, { force: true });
140
- }
141
- catch {
142
- // Never remove a lock that has already been released or replaced.
143
- }
144
- }
75
+ return (0, atomic_1.withFileLockSync)(`${getQuotaSnapshotPath()}.lock`, operation);
145
76
  }
146
77
  function snapshotTimeMs(quota) {
147
78
  const capturedAt = quota.capturedAt ? Date.parse(quota.capturedAt) : NaN;
@@ -63,9 +63,9 @@ export declare const savedAccountProfileSchema: z.ZodObject<{
63
63
  workspaceId: z.ZodOptional<z.ZodString>;
64
64
  planType: z.ZodOptional<z.ZodString>;
65
65
  authMode: z.ZodOptional<z.ZodEnum<{
66
+ unknown: "unknown";
66
67
  chatgpt: "chatgpt";
67
68
  "api-key": "api-key";
68
- unknown: "unknown";
69
69
  }>>;
70
70
  }, z.core.$strip>>;
71
71
  }, z.core.$strip>;
@@ -91,9 +91,9 @@ export declare const listAllAccountsResultSchema: z.ZodObject<{
91
91
  workspaceId: z.ZodOptional<z.ZodString>;
92
92
  planType: z.ZodOptional<z.ZodString>;
93
93
  authMode: z.ZodOptional<z.ZodEnum<{
94
+ unknown: "unknown";
94
95
  chatgpt: "chatgpt";
95
96
  "api-key": "api-key";
96
- unknown: "unknown";
97
97
  }>>;
98
98
  }, z.core.$strip>>;
99
99
  }, z.core.$strip>>;
@@ -12,11 +12,9 @@
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = exports.accountEntrySchema = exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = void 0;
14
14
  const zod_1 = require("zod");
15
+ const providerIds_1 = require("../providerIds");
15
16
  // ── AccountProviderId ──
16
- exports.accountProviderIdSchema = zod_1.z.enum([
17
- 'claude-code',
18
- 'codex',
19
- ]);
17
+ exports.accountProviderIdSchema = zod_1.z.enum(providerIds_1.ACCOUNT_PROVIDER_IDS);
20
18
  // ── AccountManagerResult ──
21
19
  exports.accountManagerResultSchema = zod_1.z.object({
22
20
  success: zod_1.z.boolean(),
@@ -11,6 +11,7 @@
11
11
  import { z } from 'zod';
12
12
  export declare const activeProviderAccountStatusSchema: z.ZodObject<{
13
13
  present: z.ZodBoolean;
14
+ accountId: z.ZodOptional<z.ZodString>;
14
15
  email: z.ZodOptional<z.ZodString>;
15
16
  label: z.ZodOptional<z.ZodString>;
16
17
  }, z.core.$strip>;
@@ -18,11 +19,13 @@ export declare const activeAccountStatusSchema: z.ZodObject<{
18
19
  ok: z.ZodBoolean;
19
20
  claude: z.ZodObject<{
20
21
  present: z.ZodBoolean;
22
+ accountId: z.ZodOptional<z.ZodString>;
21
23
  email: z.ZodOptional<z.ZodString>;
22
24
  label: z.ZodOptional<z.ZodString>;
23
25
  }, z.core.$strip>;
24
26
  codex: z.ZodObject<{
25
27
  present: z.ZodBoolean;
28
+ accountId: z.ZodOptional<z.ZodString>;
26
29
  email: z.ZodOptional<z.ZodString>;
27
30
  label: z.ZodOptional<z.ZodString>;
28
31
  }, z.core.$strip>;
@@ -15,6 +15,7 @@ const zod_1 = require("zod");
15
15
  // ── ActiveProviderAccountStatus ──
16
16
  exports.activeProviderAccountStatusSchema = zod_1.z.object({
17
17
  present: zod_1.z.boolean(),
18
+ accountId: zod_1.z.string().optional(),
18
19
  email: zod_1.z.string().optional(),
19
20
  label: zod_1.z.string().optional(),
20
21
  });
@@ -10,6 +10,7 @@
10
10
  * The mirrored TypeScript interfaces are re-exported as types so this
11
11
  * subpath is self-sufficient for boundary-validation modules.
12
12
  */
13
+ export { ACCOUNT_PROVIDER_IDS, QUOTA_PROVIDER_IDS, RUNTIME_QUOTA_PROVIDER_IDS, SESSION_PROVIDER_IDS, } from '../providerIds';
13
14
  export { messageUsageSchema, sessionMessageSchema, sessionEventSchema, permissionModeSchema, extractSessionEvents, userSessionEventSchema, assistantSessionEventSchema, summarySessionEventSchema, systemSessionEventSchema, toolUseSessionEventSchema, toolResultSessionEventSchema, } from './sessionEvent';
14
15
  export type { MessageUsage, SessionMessage, SessionEvent, PermissionMode, } from '../types/sessionEvent';
15
16
  export { normalizedUsageSchema, normalizedUsageCostSchema } from './usageNormalization';
@@ -12,8 +12,13 @@
12
12
  * subpath is self-sufficient for boundary-validation modules.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.zaiProviderQuotaStateSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.codexResetCreditsSnapshotSchema = exports.codexResetCreditSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.sessionEvidenceRefV1Schema = exports.providerSessionAdapterV1Schema = exports.providerCapabilitiesV1Schema = exports.pendingUserRequestV1Schema = exports.observedValueV1Schema = exports.observedAgentSessionV1Schema = exports.observationProvenanceV1Schema = exports.canonicalSessionTranscriptSchema = exports.canonicalTranscriptMessageSchema = exports.canonicalToolCallSchema = exports.canonicalTranscriptBlockSchema = exports.transcriptSourceProvenanceSchema = exports.tokenEstimateSchema = exports.normalizedUsageCostSchema = exports.normalizedUsageSchema = exports.toolResultSessionEventSchema = exports.toolUseSessionEventSchema = exports.systemSessionEventSchema = exports.summarySessionEventSchema = exports.assistantSessionEventSchema = exports.userSessionEventSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = void 0;
16
- exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = exports.accountEntrySchema = void 0;
15
+ exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.zaiProviderQuotaStateSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.codexResetCreditsSnapshotSchema = exports.codexResetCreditSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.sessionEvidenceRefV1Schema = exports.providerSessionAdapterV1Schema = exports.providerCapabilitiesV1Schema = exports.pendingUserRequestV1Schema = exports.observedValueV1Schema = exports.observedAgentSessionV1Schema = exports.observationProvenanceV1Schema = exports.canonicalSessionTranscriptSchema = exports.canonicalTranscriptMessageSchema = exports.canonicalToolCallSchema = exports.canonicalTranscriptBlockSchema = exports.transcriptSourceProvenanceSchema = exports.tokenEstimateSchema = exports.normalizedUsageCostSchema = exports.normalizedUsageSchema = exports.toolResultSessionEventSchema = exports.toolUseSessionEventSchema = exports.systemSessionEventSchema = exports.summarySessionEventSchema = exports.assistantSessionEventSchema = exports.userSessionEventSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.SESSION_PROVIDER_IDS = exports.RUNTIME_QUOTA_PROVIDER_IDS = exports.QUOTA_PROVIDER_IDS = exports.ACCOUNT_PROVIDER_IDS = void 0;
16
+ exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = exports.accountEntrySchema = exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = void 0;
17
+ var providerIds_1 = require("../providerIds");
18
+ Object.defineProperty(exports, "ACCOUNT_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.ACCOUNT_PROVIDER_IDS; } });
19
+ Object.defineProperty(exports, "QUOTA_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.QUOTA_PROVIDER_IDS; } });
20
+ Object.defineProperty(exports, "RUNTIME_QUOTA_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.RUNTIME_QUOTA_PROVIDER_IDS; } });
21
+ Object.defineProperty(exports, "SESSION_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.SESSION_PROVIDER_IDS; } });
17
22
  var sessionEvent_1 = require("./sessionEvent");
18
23
  Object.defineProperty(exports, "messageUsageSchema", { enumerable: true, get: function () { return sessionEvent_1.messageUsageSchema; } });
19
24
  Object.defineProperty(exports, "sessionMessageSchema", { enumerable: true, get: function () { return sessionEvent_1.sessionMessageSchema; } });