sidekick-shared 0.24.5 → 0.26.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 (157) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +205 -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 +170 -0
  7. package/dist/accountRegistry.d.ts +25 -2
  8. package/dist/accountRegistry.js +41 -13
  9. package/dist/accountStatus.d.ts +9 -1
  10. package/dist/accountStatus.js +3 -3
  11. package/dist/accounts.d.ts +3 -1
  12. package/dist/accounts.js +84 -64
  13. package/dist/aggregation/EventAggregator.d.ts +15 -2
  14. package/dist/aggregation/EventAggregator.js +54 -9
  15. package/dist/aggregation/costProvenance.d.ts +14 -0
  16. package/dist/aggregation/costProvenance.js +35 -0
  17. package/dist/aggregation/snapshot.d.ts +13 -2
  18. package/dist/aggregation/snapshot.js +57 -6
  19. package/dist/aggregation/types.d.ts +30 -1
  20. package/dist/analytics/compactionLedger.js +15 -5
  21. package/dist/analytics/qualityScore.js +2 -1
  22. package/dist/browser.d.ts +19 -4
  23. package/dist/browser.js +43 -1
  24. package/dist/codexProfiles.d.ts +3 -1
  25. package/dist/codexProfiles.js +4 -2
  26. package/dist/codexQuota.d.ts +2 -0
  27. package/dist/codexQuota.js +11 -66
  28. package/dist/codexQuotaWatcher.d.ts +6 -0
  29. package/dist/codexQuotaWatcher.js +101 -47
  30. package/dist/failingTools.d.ts +37 -0
  31. package/dist/failingTools.js +70 -0
  32. package/dist/formatters/sessionDump.js +13 -7
  33. package/dist/formatting.d.ts +13 -0
  34. package/dist/formatting.js +35 -0
  35. package/dist/historicalStore.d.ts +47 -0
  36. package/dist/historicalStore.js +312 -0
  37. package/dist/index.d.ts +67 -23
  38. package/dist/index.js +138 -26
  39. package/dist/modelAliases.d.ts +13 -0
  40. package/dist/modelAliases.js +58 -0
  41. package/dist/modelCatalog.d.ts +32 -0
  42. package/dist/modelCatalog.js +145 -0
  43. package/dist/modelContext.d.ts +36 -3
  44. package/dist/modelContext.js +109 -34
  45. package/dist/modelInfo.d.ts +30 -1
  46. package/dist/modelInfo.js +116 -26
  47. package/dist/multiProviderQuotaService.d.ts +8 -0
  48. package/dist/multiProviderQuotaService.js +64 -3
  49. package/dist/node.d.ts +23 -4
  50. package/dist/node.js +50 -1
  51. package/dist/observedContextWindows.d.ts +7 -0
  52. package/dist/observedContextWindows.js +17 -2
  53. package/dist/observedSessionCollector.d.ts +72 -6
  54. package/dist/observedSessionCollector.js +440 -47
  55. package/dist/parsers/codexHistory.d.ts +4 -2
  56. package/dist/parsers/codexHistory.js +9 -2
  57. package/dist/parsers/sessionActivityDetector.d.ts +28 -0
  58. package/dist/parsers/sessionActivityDetector.js +110 -15
  59. package/dist/parsers/sessionPathResolver.d.ts +11 -0
  60. package/dist/parsers/sessionPathResolver.js +13 -10
  61. package/dist/peakHours.d.ts +34 -3
  62. package/dist/peakHours.js +92 -7
  63. package/dist/pricingCatalog.d.ts +7 -0
  64. package/dist/pricingCatalog.js +8 -5
  65. package/dist/providerIds.d.ts +11 -0
  66. package/dist/providerIds.js +9 -0
  67. package/dist/providerQuota.d.ts +3 -1
  68. package/dist/providerQuota.js +3 -0
  69. package/dist/providers/claudeCode.d.ts +10 -1
  70. package/dist/providers/claudeCode.js +211 -131
  71. package/dist/providers/codex.d.ts +20 -1
  72. package/dist/providers/codex.js +308 -244
  73. package/dist/providers/codexDatabase.d.ts +9 -0
  74. package/dist/providers/codexDatabase.js +107 -2
  75. package/dist/providers/detect.d.ts +2 -0
  76. package/dist/providers/detect.js +29 -24
  77. package/dist/providers/diagnostics.d.ts +13 -0
  78. package/dist/providers/diagnostics.js +88 -0
  79. package/dist/providers/factory.d.ts +20 -0
  80. package/dist/providers/factory.js +57 -0
  81. package/dist/providers/openCode.d.ts +22 -1
  82. package/dist/providers/openCode.js +460 -126
  83. package/dist/providers/openCodeDatabase.d.ts +10 -0
  84. package/dist/providers/openCodeDatabase.js +69 -0
  85. package/dist/providers/rolloutWalker.d.ts +53 -0
  86. package/dist/providers/rolloutWalker.js +209 -0
  87. package/dist/providers/types.d.ts +82 -2
  88. package/dist/providers/types.js +4 -0
  89. package/dist/quota.d.ts +34 -4
  90. package/dist/quota.js +37 -9
  91. package/dist/quotaHistory.d.ts +3 -2
  92. package/dist/quotaHistory.js +36 -35
  93. package/dist/quotaPoller.d.ts +13 -1
  94. package/dist/quotaPoller.js +65 -3
  95. package/dist/quotaResolver.d.ts +81 -0
  96. package/dist/quotaResolver.js +213 -0
  97. package/dist/quotaSnapshots.d.ts +15 -2
  98. package/dist/quotaSnapshots.js +25 -3
  99. package/dist/quotaThresholds.d.ts +60 -0
  100. package/dist/quotaThresholds.js +90 -0
  101. package/dist/report/htmlReportGenerator.js +10 -4
  102. package/dist/report/sessionReportInputs.d.ts +23 -0
  103. package/dist/report/sessionReportInputs.js +34 -0
  104. package/dist/schemas/accountManager.d.ts +2 -2
  105. package/dist/schemas/accountManager.js +2 -4
  106. package/dist/schemas/accountStatus.d.ts +3 -0
  107. package/dist/schemas/accountStatus.js +1 -0
  108. package/dist/schemas/index.d.ts +3 -0
  109. package/dist/schemas/index.js +16 -2
  110. package/dist/schemas/observedSessionV1.d.ts +27 -26
  111. package/dist/schemas/observedSessionV1.js +6 -4
  112. package/dist/schemas/quota.d.ts +141 -2
  113. package/dist/schemas/quota.js +20 -8
  114. package/dist/schemas/quotaHistory.d.ts +3 -2
  115. package/dist/schemas/quotaHistory.js +1 -1
  116. package/dist/schemas/sessionEvent.d.ts +6 -0
  117. package/dist/schemas/sessionEvent.js +1 -0
  118. package/dist/schemas/stateFile.d.ts +198 -0
  119. package/dist/schemas/stateFile.js +77 -0
  120. package/dist/schemas/tokenEstimation.d.ts +1 -1
  121. package/dist/schemas/transcript.js +2 -0
  122. package/dist/sessionFingerprint.d.ts +25 -0
  123. package/dist/sessionFingerprint.js +46 -0
  124. package/dist/sessionMonitor.d.ts +15 -0
  125. package/dist/sessionMonitor.js +142 -0
  126. package/dist/sessionPreviews.d.ts +33 -2
  127. package/dist/sessionPreviews.js +333 -6
  128. package/dist/sessionStats.d.ts +51 -0
  129. package/dist/sessionStats.js +166 -0
  130. package/dist/stateFile.d.ts +113 -0
  131. package/dist/stateFile.js +160 -0
  132. package/dist/statusline/claudeStatuslinePayload.d.ts +89 -0
  133. package/dist/statusline/claudeStatuslinePayload.js +152 -0
  134. package/dist/statusline/formatter.d.ts +13 -0
  135. package/dist/statusline/formatter.js +48 -6
  136. package/dist/statusline/index.d.ts +10 -2
  137. package/dist/statusline/index.js +19 -1
  138. package/dist/tokenSummary.d.ts +62 -0
  139. package/dist/tokenSummary.js +72 -0
  140. package/dist/transcript.d.ts +5 -0
  141. package/dist/transcript.js +2 -0
  142. package/dist/types/observedSessionV1.d.ts +8 -2
  143. package/dist/types/observedSessionV1.js +88 -25
  144. package/dist/types/sessionEvent.d.ts +2 -0
  145. package/dist/usage/billingBlocks.d.ts +85 -0
  146. package/dist/usage/billingBlocks.js +153 -0
  147. package/dist/usage/importSessionHistory.d.ts +53 -0
  148. package/dist/usage/importSessionHistory.js +88 -0
  149. package/dist/usage/usageEvents.d.ts +92 -0
  150. package/dist/usage/usageEvents.js +322 -0
  151. package/dist/usage/usageReports.d.ts +76 -0
  152. package/dist/usage/usageReports.js +208 -0
  153. package/dist/watchers/factory.d.ts +6 -0
  154. package/dist/watchers/factory.js +30 -25
  155. package/dist/zaiQuotaApi.d.ts +2 -0
  156. package/dist/zaiQuotaApi.js +2 -0
  157. package/package.json +22 -1
@@ -3,14 +3,18 @@
3
3
  * Ported from sidekick-vscode/src/services/providers/CodexDatabase.ts
4
4
  */
5
5
  import type { CodexDbThread } from '../types/codex';
6
+ import type { ProviderRuntimeStatus } from './types';
6
7
  export declare class CodexDatabase {
7
8
  private readonly dbPath;
8
9
  private sqlite3Available;
10
+ private runtimeStatus;
9
11
  constructor(codexHome: string);
10
12
  isAvailable(): boolean;
11
13
  open(): boolean;
14
+ getRuntimeStatus(): ProviderRuntimeStatus;
12
15
  close(): void;
13
16
  private query;
17
+ private queryAsync;
14
18
  private queryOne;
15
19
  getThreadsByCwd(cwd: string): CodexDbThread[];
16
20
  getMostRecentThread(cwd: string): CodexDbThread | null;
@@ -20,6 +24,11 @@ export declare class CodexDatabase {
20
24
  lastUpdated: number;
21
25
  }>;
22
26
  getThread(id: string): CodexDbThread | null;
27
+ /**
28
+ * Resolve many thread ids with non-blocking sqlite3 subprocesses — one per
29
+ * chunk of ids, so the inlined query stays clear of argv length limits.
30
+ */
31
+ getThreadsByIdsAsync(ids: readonly string[]): Promise<CodexDbThread[]>;
23
32
  /** Get all threads forked from a given session ID. */
24
33
  getThreadsByForkedFromId(parentId: string): CodexDbThread[];
25
34
  /** Get the database file's mtime (ms epoch). Returns 0 if unavailable. */
@@ -41,9 +41,15 @@ exports.CodexDatabase = void 0;
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
43
  const child_process_1 = require("child_process");
44
+ /**
45
+ * Ids per inlined `IN (…)` query. Values are passed inside a single argv
46
+ * element to sqlite3, so an unbounded list would hit E2BIG on large stores.
47
+ */
48
+ const SQL_ID_CHUNK_SIZE = 400;
44
49
  class CodexDatabase {
45
50
  dbPath;
46
51
  sqlite3Available = null;
52
+ runtimeStatus = null;
47
53
  constructor(codexHome) {
48
54
  this.dbPath = findLatestStateDatabase(codexHome) ?? path.join(codexHome, 'state.sqlite');
49
55
  }
@@ -58,6 +64,11 @@ class CodexDatabase {
58
64
  open() {
59
65
  if (this.sqlite3Available !== null)
60
66
  return this.sqlite3Available;
67
+ if (!this.isAvailable()) {
68
+ this.sqlite3Available = false;
69
+ this.runtimeStatus = { available: false, kind: 'db_missing' };
70
+ return false;
71
+ }
61
72
  try {
62
73
  (0, child_process_1.execFileSync)('sqlite3', ['--version'], {
63
74
  encoding: 'utf-8',
@@ -66,13 +77,26 @@ class CodexDatabase {
66
77
  stdio: ['pipe', 'pipe', 'pipe'],
67
78
  });
68
79
  this.sqlite3Available = true;
80
+ this.runtimeStatus = { available: true, kind: 'available' };
69
81
  return true;
70
82
  }
71
- catch {
83
+ catch (error) {
72
84
  this.sqlite3Available = false;
85
+ this.runtimeStatus = classifySqliteError(error);
73
86
  return false;
74
87
  }
75
88
  }
89
+ getRuntimeStatus() {
90
+ if (this.runtimeStatus)
91
+ return this.runtimeStatus;
92
+ if (!this.isAvailable())
93
+ return { available: false, kind: 'db_missing' };
94
+ return {
95
+ available: false,
96
+ kind: 'query_failed',
97
+ message: 'Codex database has not been initialized yet.',
98
+ };
99
+ }
76
100
  close() { }
77
101
  query(sql, params = []) {
78
102
  if (!this.sqlite3Available)
@@ -103,7 +127,34 @@ class CodexDatabase {
103
127
  return [];
104
128
  return JSON.parse(trimmed);
105
129
  }
106
- catch {
130
+ catch (error) {
131
+ this.runtimeStatus = {
132
+ ...classifySqliteError(error, 'query_failed'),
133
+ available: this.sqlite3Available === true,
134
+ };
135
+ return [];
136
+ }
137
+ }
138
+ async queryAsync(sql, params = []) {
139
+ if (!this.isAvailable()) {
140
+ this.runtimeStatus = { available: false, kind: 'db_missing' };
141
+ return [];
142
+ }
143
+ const query = bindSqlParams(sql, params);
144
+ try {
145
+ const result = await execSqlite(this.dbPath, query, 10 * 1024 * 1024);
146
+ this.sqlite3Available = true;
147
+ this.runtimeStatus = { available: true, kind: 'available' };
148
+ const trimmed = result.trim();
149
+ return trimmed ? JSON.parse(trimmed) : [];
150
+ }
151
+ catch (error) {
152
+ const status = classifySqliteError(error, 'query_failed');
153
+ // Only a missing sqlite3 binary gates future attempts. A failed query
154
+ // is transient and must not latch the database off (mirrors query()).
155
+ if (status.kind === 'sqlite_missing')
156
+ this.sqlite3Available = false;
157
+ this.runtimeStatus = status;
107
158
  return [];
108
159
  }
109
160
  }
@@ -132,6 +183,19 @@ class CodexDatabase {
132
183
  getThread(id) {
133
184
  return this.queryOne('SELECT * FROM threads WHERE id = ?', [id]);
134
185
  }
186
+ /**
187
+ * Resolve many thread ids with non-blocking sqlite3 subprocesses — one per
188
+ * chunk of ids, so the inlined query stays clear of argv length limits.
189
+ */
190
+ async getThreadsByIdsAsync(ids) {
191
+ const results = [];
192
+ for (let i = 0; i < ids.length; i += SQL_ID_CHUNK_SIZE) {
193
+ const chunk = ids.slice(i, i + SQL_ID_CHUNK_SIZE);
194
+ const placeholders = chunk.map(() => '?').join(', ');
195
+ results.push(...(await this.queryAsync(`SELECT * FROM threads WHERE id IN (${placeholders})`, [...chunk])));
196
+ }
197
+ return results;
198
+ }
135
199
  /** Get all threads forked from a given session ID. */
136
200
  getThreadsByForkedFromId(parentId) {
137
201
  return this.query('SELECT * FROM threads WHERE forked_from_id = ? ORDER BY created_at ASC', [parentId]);
@@ -147,6 +211,47 @@ class CodexDatabase {
147
211
  }
148
212
  }
149
213
  exports.CodexDatabase = CodexDatabase;
214
+ function classifySqliteError(error, fallback = 'sqlite_blocked') {
215
+ const code = error?.code;
216
+ if (code === 'ENOENT') {
217
+ return {
218
+ available: false,
219
+ kind: 'sqlite_missing',
220
+ message: 'The sqlite3 executable was not found.',
221
+ };
222
+ }
223
+ return {
224
+ available: false,
225
+ kind: fallback,
226
+ message: error instanceof Error ? error.message : 'sqlite3 could not be executed.',
227
+ };
228
+ }
229
+ function bindSqlParams(sql, params) {
230
+ let paramIndex = 0;
231
+ return sql.replace(/\?/g, () => {
232
+ if (paramIndex >= params.length)
233
+ return '?';
234
+ const param = params[paramIndex++];
235
+ if (typeof param === 'number')
236
+ return Number.isFinite(param) ? String(param) : '0';
237
+ return `'${String(param).replace(/'/g, "''")}'`;
238
+ });
239
+ }
240
+ function execSqlite(dbPath, query, maxBuffer) {
241
+ return new Promise((resolve, reject) => {
242
+ (0, child_process_1.execFile)('sqlite3', ['-json', '-readonly', dbPath, query], {
243
+ encoding: 'utf8',
244
+ timeout: 4_000,
245
+ killSignal: 'SIGKILL',
246
+ maxBuffer,
247
+ }, (error, stdout) => {
248
+ if (error)
249
+ reject(error);
250
+ else
251
+ resolve(stdout);
252
+ });
253
+ });
254
+ }
150
255
  function findLatestStateDatabase(codexHome) {
151
256
  try {
152
257
  const entries = fs.readdirSync(codexHome, { withFileTypes: true });
@@ -4,6 +4,8 @@
4
4
  * without VS Code dependency (no vscode.workspace.getConfiguration).
5
5
  */
6
6
  import type { ProviderId } from './types';
7
+ /** Forget the memoised ranking. Test-only. */
8
+ export declare function _resetProviderDetectionCache(): void;
7
9
  /**
8
10
  * Returns all provider IDs whose data directories exist on the filesystem.
9
11
  * Ordered by most-recent activity first.
@@ -38,6 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  };
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports._resetProviderDetectionCache = _resetProviderDetectionCache;
41
42
  exports.getAllDetectedProviders = getAllDetectedProviders;
42
43
  exports.detectProvider = detectProvider;
43
44
  const fs = __importStar(require("fs"));
@@ -169,11 +170,25 @@ function getProviderPaths() {
169
170
  codexHomes,
170
171
  };
171
172
  }
173
+ /** Ranked detection results are reused within a process for this long. */
174
+ const DETECTION_CACHE_TTL_MS = 5_000;
175
+ let detectionCache = null;
176
+ /** Forget the memoised ranking. Test-only. */
177
+ function _resetProviderDetectionCache() {
178
+ detectionCache = null;
179
+ }
172
180
  /**
173
- * Returns all provider IDs whose data directories exist on the filesystem.
174
- * Ordered by most-recent activity first.
181
+ * Providers with data on disk, most recently active first.
182
+ *
183
+ * Detection walks every provider's session corpus (a recursive stat over
184
+ * `~/.claude/projects`, every Codex home, and the OpenCode data dir), so the
185
+ * ranking is memoised briefly: the CLI resolves the provider several times per
186
+ * command and the extension asks again on every settings-driven re-detect.
175
187
  */
176
- function getAllDetectedProviders() {
188
+ function rankDetectedProviders(now = Date.now()) {
189
+ if (detectionCache && now - detectionCache.at < DETECTION_CACHE_TTL_MS) {
190
+ return detectionCache.ranked;
191
+ }
177
192
  const { claudeBase, openCodeDbPath, openCodeStorageDir, codexHomes } = getProviderPaths();
178
193
  const hasClaude = fs.existsSync(claudeBase);
179
194
  const hasOpenCode = fs.existsSync(openCodeStorageDir) || fs.existsSync(openCodeDbPath);
@@ -186,7 +201,16 @@ function getAllDetectedProviders() {
186
201
  if (hasCodex)
187
202
  available.push({ id: 'codex', mtime: getCodexActivityMtime() });
188
203
  available.sort((a, b) => b.mtime - a.mtime);
189
- return available.map((a) => a.id);
204
+ const ranked = available.map((a) => a.id);
205
+ detectionCache = { at: now, ranked };
206
+ return ranked;
207
+ }
208
+ /**
209
+ * Returns all provider IDs whose data directories exist on the filesystem.
210
+ * Ordered by most-recent activity first.
211
+ */
212
+ function getAllDetectedProviders() {
213
+ return [...rankDetectedProviders()];
190
214
  }
191
215
  /**
192
216
  * Detects the most appropriate provider based on filesystem presence and recency.
@@ -195,24 +219,5 @@ function getAllDetectedProviders() {
195
219
  function detectProvider(override) {
196
220
  if (override && override !== 'auto')
197
221
  return override;
198
- const { claudeBase, openCodeDbPath, openCodeStorageDir, codexHomes } = getProviderPaths();
199
- const hasClaude = fs.existsSync(claudeBase);
200
- const hasOpenCode = fs.existsSync(openCodeStorageDir) || fs.existsSync(openCodeDbPath);
201
- const hasCodex = codexHomes.some((codexHome) => fs.existsSync(path.join(codexHome, 'sessions')) || hasCodexStateDb(codexHome));
202
- const available = [];
203
- if (hasClaude) {
204
- available.push({ id: 'claude-code', mtime: getClaudeActivityMtime(claudeBase) });
205
- }
206
- if (hasOpenCode) {
207
- available.push({ id: 'opencode', mtime: getOpenCodeActivityMtime() });
208
- }
209
- if (hasCodex) {
210
- available.push({ id: 'codex', mtime: getCodexActivityMtime() });
211
- }
212
- if (available.length === 0)
213
- return 'claude-code';
214
- if (available.length === 1)
215
- return available[0].id;
216
- available.sort((a, b) => b.mtime - a.mtime);
217
- return available[0].id;
222
+ return rankDetectedProviders()[0] ?? 'claude-code';
218
223
  }
@@ -0,0 +1,13 @@
1
+ import type { ProviderId, ProviderOperationStatus, ProviderRuntimeStatus, SessionProviderDiagnostic, SessionProviderOptions } from './types';
2
+ /** Small per-provider diagnostic state; it never stores transcript or environment contents. */
3
+ export declare class ProviderDiagnosticTracker {
4
+ private readonly providerId;
5
+ private readonly options;
6
+ private lastEmittedSignature;
7
+ private lastStatus;
8
+ constructor(providerId: ProviderId, options?: SessionProviderOptions);
9
+ available(operation: string): void;
10
+ degraded(operation: string, runtimeStatus: ProviderRuntimeStatus, diagnostic: Omit<SessionProviderDiagnostic, 'providerId'>, usable?: boolean): void;
11
+ getLastOperationStatus(): ProviderOperationStatus;
12
+ }
13
+ export declare function diagnosticFromRuntimeStatus(providerId: ProviderId, phase: SessionProviderDiagnostic['phase'], status: ProviderRuntimeStatus): SessionProviderDiagnostic | null;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProviderDiagnosticTracker = void 0;
4
+ exports.diagnosticFromRuntimeStatus = diagnosticFromRuntimeStatus;
5
+ const AVAILABLE_STATUS = { available: true, kind: 'available' };
6
+ /** Small per-provider diagnostic state; it never stores transcript or environment contents. */
7
+ class ProviderDiagnosticTracker {
8
+ providerId;
9
+ options;
10
+ lastEmittedSignature = null;
11
+ lastStatus = {
12
+ operation: 'construct',
13
+ usable: true,
14
+ degraded: false,
15
+ runtimeStatus: AVAILABLE_STATUS,
16
+ diagnostics: [],
17
+ };
18
+ constructor(providerId, options = {}) {
19
+ this.providerId = providerId;
20
+ this.options = options;
21
+ }
22
+ available(operation) {
23
+ this.lastEmittedSignature = null;
24
+ this.lastStatus = {
25
+ operation,
26
+ usable: true,
27
+ degraded: false,
28
+ runtimeStatus: AVAILABLE_STATUS,
29
+ diagnostics: [],
30
+ };
31
+ }
32
+ degraded(operation, runtimeStatus, diagnostic, usable = true) {
33
+ const value = { providerId: this.providerId, ...diagnostic };
34
+ this.lastStatus = {
35
+ operation,
36
+ usable,
37
+ degraded: true,
38
+ runtimeStatus,
39
+ diagnostics: [value],
40
+ };
41
+ const signature = `${value.kind}\0${value.severity}\0${value.phase}\0${value.message}`;
42
+ if (signature === this.lastEmittedSignature)
43
+ return;
44
+ this.lastEmittedSignature = signature;
45
+ try {
46
+ this.options.onDiagnostic?.(value);
47
+ }
48
+ catch {
49
+ // Diagnostics are observational and cannot break provider operations.
50
+ }
51
+ }
52
+ getLastOperationStatus() {
53
+ return {
54
+ ...this.lastStatus,
55
+ runtimeStatus: { ...this.lastStatus.runtimeStatus },
56
+ diagnostics: [...this.lastStatus.diagnostics],
57
+ };
58
+ }
59
+ }
60
+ exports.ProviderDiagnosticTracker = ProviderDiagnosticTracker;
61
+ function diagnosticFromRuntimeStatus(providerId, phase, status) {
62
+ if (status.kind === 'available')
63
+ return null;
64
+ const kind = status.kind;
65
+ return {
66
+ providerId,
67
+ kind,
68
+ severity: status.kind === 'query_failed' ? 'error' : 'warning',
69
+ phase,
70
+ message: status.message ?? defaultRuntimeMessage(providerId, status.kind),
71
+ };
72
+ }
73
+ function defaultRuntimeMessage(providerId, kind) {
74
+ switch (kind) {
75
+ case 'home_unavailable':
76
+ return `${providerId} data home is unavailable.`;
77
+ case 'config_unreadable':
78
+ return `${providerId} configuration is unreadable.`;
79
+ case 'db_missing':
80
+ return `${providerId} database is unavailable; file fallback is active.`;
81
+ case 'sqlite_missing':
82
+ return 'The sqlite3 executable is unavailable; file fallback is active.';
83
+ case 'sqlite_blocked':
84
+ return 'The sqlite3 executable could not be started; file fallback is active.';
85
+ case 'query_failed':
86
+ return `${providerId} database query failed; fallback data may be incomplete.`;
87
+ }
88
+ }
@@ -0,0 +1,20 @@
1
+ import type { ProviderId, SessionProviderBase, SessionProviderDiagnostic } from './types';
2
+ export interface CreateSessionProvidersOptions {
3
+ providerIds?: readonly ProviderId[];
4
+ onDiagnostic?: (diagnostic: SessionProviderDiagnostic) => void;
5
+ }
6
+ export interface CreateSessionProvidersResult {
7
+ providers: SessionProviderBase[];
8
+ /**
9
+ * Live, not a snapshot: the providers keep the emit closure and update
10
+ * entries in place (one per provider/kind/phase) as they run, so this array
11
+ * reflects the latest diagnostics whenever it is read. Copy it to freeze a
12
+ * point in time; use `onDiagnostic` to observe each emission.
13
+ */
14
+ diagnostics: SessionProviderDiagnostic[];
15
+ }
16
+ /**
17
+ * Constructs built-in providers without probing the filesystem or binaries.
18
+ * A broken provider is isolated and reported instead of aborting host startup.
19
+ */
20
+ export declare function createSessionProviders(options?: CreateSessionProvidersOptions): CreateSessionProvidersResult;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSessionProviders = createSessionProviders;
4
+ const claudeCode_1 = require("./claudeCode");
5
+ const codex_1 = require("./codex");
6
+ const openCode_1 = require("./openCode");
7
+ const types_1 = require("./types");
8
+ /**
9
+ * Constructs built-in providers without probing the filesystem or binaries.
10
+ * A broken provider is isolated and reported instead of aborting host startup.
11
+ */
12
+ function createSessionProviders(options = {}) {
13
+ const diagnostics = [];
14
+ const diagnosticIndexes = new Map();
15
+ const providers = [];
16
+ const selected = new Set(options.providerIds ?? types_1.SESSION_PROVIDER_IDS);
17
+ const emit = (diagnostic) => {
18
+ const key = `${diagnostic.providerId}\0${diagnostic.kind}\0${diagnostic.phase}`;
19
+ const existingIndex = diagnosticIndexes.get(key);
20
+ if (existingIndex === undefined) {
21
+ diagnosticIndexes.set(key, diagnostics.length);
22
+ diagnostics.push(diagnostic);
23
+ }
24
+ else {
25
+ diagnostics[existingIndex] = diagnostic;
26
+ }
27
+ try {
28
+ options.onDiagnostic?.(diagnostic);
29
+ }
30
+ catch {
31
+ // A host diagnostic callback cannot break provider setup.
32
+ }
33
+ };
34
+ for (const providerId of types_1.SESSION_PROVIDER_IDS) {
35
+ if (!selected.has(providerId))
36
+ continue;
37
+ try {
38
+ const providerOptions = { onDiagnostic: emit };
39
+ providers.push(providerId === 'claude-code'
40
+ ? new claudeCode_1.ClaudeCodeProvider(providerOptions)
41
+ : providerId === 'opencode'
42
+ ? new openCode_1.OpenCodeProvider(providerOptions)
43
+ : new codex_1.CodexProvider(providerOptions));
44
+ }
45
+ catch {
46
+ const diagnostic = {
47
+ providerId,
48
+ kind: 'construction_failed',
49
+ severity: 'error',
50
+ phase: 'construct',
51
+ message: `${providerId} could not be constructed and was omitted.`,
52
+ };
53
+ emit(diagnostic);
54
+ }
55
+ }
56
+ return { providers, diagnostics };
57
+ }
@@ -18,7 +18,7 @@
18
18
  import type { ReadSessionContextSnapshotOptions, SessionContextSnapshot } from '../context/sessionContext';
19
19
  import type { ZaiTier } from '../zaiQuota';
20
20
  import type { QuotaState } from '../quota';
21
- import type { SessionProviderBase, SessionReader, ProjectFolderInfo, SearchHit, SessionFileStats, ProviderId, ProviderRuntimeStatus } from './types';
21
+ import type { SessionProviderBase, SessionReader, ProjectFolderInfo, SearchHit, SessionFileStats, ProviderId, ProviderRuntimeStatus, ProviderOperationStatus, SessionFileInfo, SessionProviderOptions } from './types';
22
22
  import type { TokenUsage, SubagentStats, ContextAttribution } from '../types/sessionEvent';
23
23
  /**
24
24
  * Gets the OpenCode data directory.
@@ -42,13 +42,23 @@ export declare class OpenCodeProvider implements SessionProviderBase {
42
42
  private db;
43
43
  private dbInitialized;
44
44
  private dbStatus;
45
+ private readonly diagnostics;
45
46
  /** Cache of session metadata populated during listing */
46
47
  private sessionMetaCache;
47
48
  /** Runtime-reported context window limit (overrides static map when set). */
48
49
  private dynamicContextWindowLimit;
50
+ constructor(options?: SessionProviderOptions);
49
51
  /** Lazy-initialize the database connection. */
50
52
  private ensureDb;
53
+ private ensureDbAsync;
54
+ /**
55
+ * Cache an async-created database only after an operation proved it usable.
56
+ * Sync paths treat a cached `db` as "open() succeeded", so adopting an
57
+ * unproven instance would cut them off from the file-storage fallback.
58
+ */
59
+ private adoptDbIfAvailable;
51
60
  getRuntimeStatus(): ProviderRuntimeStatus;
61
+ getLastOperationStatus(): ProviderOperationStatus;
52
62
  canMonitorDirectory(dir: string): boolean;
53
63
  private mapDbSessions;
54
64
  getSessionDirectory(workspacePath: string): string;
@@ -58,14 +68,20 @@ export declare class OpenCodeProvider implements SessionProviderBase {
58
68
  /** Backward-compatible alias for findAllSessions. */
59
69
  findSessionFiles(workspacePath: string): string[];
60
70
  findAllSessions(workspacePath: string): string[];
71
+ findSessionById(workspacePath: string, sessionId: string): string | null;
61
72
  findSessionsInDirectory(dir: string): string[];
62
73
  private findSessionsInDirectoryFromFiles;
74
+ listAllSessionFiles(): SessionFileInfo[];
75
+ listSessionFilesAsync(workspacePath?: string): Promise<SessionFileInfo[]>;
63
76
  getAllProjectFolders(workspacePath?: string): ProjectFolderInfo[];
64
77
  private getAllProjectFoldersFromFiles;
65
78
  isSessionFile(filename: string): boolean;
66
79
  getSessionId(sessionPath: string): string;
67
80
  encodeWorkspacePath(workspacePath: string): string;
81
+ /** Session title from the metadata cache or the database; null without either. */
82
+ private extractSessionLabelFromMetadata;
68
83
  extractSessionLabel(sessionPath: string): string | null;
84
+ extractSessionLabelsAsync(sessionPaths: readonly string[]): Promise<Map<string, string | null>>;
69
85
  private extractSessionLabelFromFiles;
70
86
  createReader(sessionPath: string): SessionReader;
71
87
  scanSubagents(_sessionDir: string, sessionId: string): SubagentStats[];
@@ -73,11 +89,15 @@ export declare class OpenCodeProvider implements SessionProviderBase {
73
89
  private searchInSessionFromDb;
74
90
  private searchInSessionFromFiles;
75
91
  getProjectsBaseDir(): string;
92
+ getWatchRoots(): string[];
76
93
  readSessionStats(sessionPath: string): SessionFileStats;
77
94
  readSessionContextSnapshot(sessionPath: string, options?: ReadSessionContextSnapshotOptions): SessionContextSnapshot;
78
95
  getSessionMetadata(sessionPath: string): {
79
96
  mtime: Date;
80
97
  } | null;
98
+ getSessionMetadataAsync(sessionPath: string): Promise<{
99
+ mtime: Date;
100
+ } | null>;
81
101
  /**
82
102
  * Returns the latest assistant message's token snapshot for context window sizing.
83
103
  * Cumulative session totals are derived from readNew() events processed by SessionMonitor.
@@ -89,6 +109,7 @@ export declare class OpenCodeProvider implements SessionProviderBase {
89
109
  /** Set a runtime-reported context window limit (overrides static map). */
90
110
  setDynamicContextWindowLimit(limit: number): void;
91
111
  dispose(): void;
112
+ private recordDatabaseStatus;
92
113
  /**
93
114
  * Derives a z.ai coding-plan `QuotaState` from OpenCode assistant turns
94
115
  * tagged with `providerID ∈ {zai, zai-coding-plan}`.