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.
- package/CHANGELOG.md +92 -0
- package/README.md +205 -52
- package/dist/accountChangeSignal.d.ts +3 -0
- package/dist/accountChangeSignal.js +19 -0
- package/dist/accountChanges.d.ts +21 -0
- package/dist/accountChanges.js +170 -0
- package/dist/accountRegistry.d.ts +25 -2
- package/dist/accountRegistry.js +41 -13
- package/dist/accountStatus.d.ts +9 -1
- package/dist/accountStatus.js +3 -3
- package/dist/accounts.d.ts +3 -1
- package/dist/accounts.js +84 -64
- package/dist/aggregation/EventAggregator.d.ts +15 -2
- package/dist/aggregation/EventAggregator.js +54 -9
- package/dist/aggregation/costProvenance.d.ts +14 -0
- package/dist/aggregation/costProvenance.js +35 -0
- package/dist/aggregation/snapshot.d.ts +13 -2
- package/dist/aggregation/snapshot.js +57 -6
- package/dist/aggregation/types.d.ts +30 -1
- package/dist/analytics/compactionLedger.js +15 -5
- package/dist/analytics/qualityScore.js +2 -1
- package/dist/browser.d.ts +19 -4
- package/dist/browser.js +43 -1
- package/dist/codexProfiles.d.ts +3 -1
- package/dist/codexProfiles.js +4 -2
- package/dist/codexQuota.d.ts +2 -0
- package/dist/codexQuota.js +11 -66
- package/dist/codexQuotaWatcher.d.ts +6 -0
- package/dist/codexQuotaWatcher.js +101 -47
- package/dist/failingTools.d.ts +37 -0
- package/dist/failingTools.js +70 -0
- package/dist/formatters/sessionDump.js +13 -7
- package/dist/formatting.d.ts +13 -0
- package/dist/formatting.js +35 -0
- package/dist/historicalStore.d.ts +47 -0
- package/dist/historicalStore.js +312 -0
- package/dist/index.d.ts +67 -23
- package/dist/index.js +138 -26
- package/dist/modelAliases.d.ts +13 -0
- package/dist/modelAliases.js +58 -0
- package/dist/modelCatalog.d.ts +32 -0
- package/dist/modelCatalog.js +145 -0
- package/dist/modelContext.d.ts +36 -3
- package/dist/modelContext.js +109 -34
- package/dist/modelInfo.d.ts +30 -1
- package/dist/modelInfo.js +116 -26
- package/dist/multiProviderQuotaService.d.ts +8 -0
- package/dist/multiProviderQuotaService.js +64 -3
- package/dist/node.d.ts +23 -4
- package/dist/node.js +50 -1
- package/dist/observedContextWindows.d.ts +7 -0
- package/dist/observedContextWindows.js +17 -2
- package/dist/observedSessionCollector.d.ts +72 -6
- package/dist/observedSessionCollector.js +440 -47
- package/dist/parsers/codexHistory.d.ts +4 -2
- package/dist/parsers/codexHistory.js +9 -2
- package/dist/parsers/sessionActivityDetector.d.ts +28 -0
- package/dist/parsers/sessionActivityDetector.js +110 -15
- package/dist/parsers/sessionPathResolver.d.ts +11 -0
- package/dist/parsers/sessionPathResolver.js +13 -10
- package/dist/peakHours.d.ts +34 -3
- package/dist/peakHours.js +92 -7
- package/dist/pricingCatalog.d.ts +7 -0
- package/dist/pricingCatalog.js +8 -5
- package/dist/providerIds.d.ts +11 -0
- package/dist/providerIds.js +9 -0
- package/dist/providerQuota.d.ts +3 -1
- package/dist/providerQuota.js +3 -0
- package/dist/providers/claudeCode.d.ts +10 -1
- package/dist/providers/claudeCode.js +211 -131
- package/dist/providers/codex.d.ts +20 -1
- package/dist/providers/codex.js +308 -244
- package/dist/providers/codexDatabase.d.ts +9 -0
- package/dist/providers/codexDatabase.js +107 -2
- package/dist/providers/detect.d.ts +2 -0
- package/dist/providers/detect.js +29 -24
- package/dist/providers/diagnostics.d.ts +13 -0
- package/dist/providers/diagnostics.js +88 -0
- package/dist/providers/factory.d.ts +20 -0
- package/dist/providers/factory.js +57 -0
- package/dist/providers/openCode.d.ts +22 -1
- package/dist/providers/openCode.js +460 -126
- package/dist/providers/openCodeDatabase.d.ts +10 -0
- package/dist/providers/openCodeDatabase.js +69 -0
- package/dist/providers/rolloutWalker.d.ts +53 -0
- package/dist/providers/rolloutWalker.js +209 -0
- package/dist/providers/types.d.ts +82 -2
- package/dist/providers/types.js +4 -0
- package/dist/quota.d.ts +34 -4
- package/dist/quota.js +37 -9
- package/dist/quotaHistory.d.ts +3 -2
- package/dist/quotaHistory.js +36 -35
- package/dist/quotaPoller.d.ts +13 -1
- package/dist/quotaPoller.js +65 -3
- package/dist/quotaResolver.d.ts +81 -0
- package/dist/quotaResolver.js +213 -0
- package/dist/quotaSnapshots.d.ts +15 -2
- package/dist/quotaSnapshots.js +25 -3
- package/dist/quotaThresholds.d.ts +60 -0
- package/dist/quotaThresholds.js +90 -0
- package/dist/report/htmlReportGenerator.js +10 -4
- package/dist/report/sessionReportInputs.d.ts +23 -0
- package/dist/report/sessionReportInputs.js +34 -0
- package/dist/schemas/accountManager.d.ts +2 -2
- package/dist/schemas/accountManager.js +2 -4
- package/dist/schemas/accountStatus.d.ts +3 -0
- package/dist/schemas/accountStatus.js +1 -0
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.js +16 -2
- package/dist/schemas/observedSessionV1.d.ts +27 -26
- package/dist/schemas/observedSessionV1.js +6 -4
- package/dist/schemas/quota.d.ts +141 -2
- package/dist/schemas/quota.js +20 -8
- package/dist/schemas/quotaHistory.d.ts +3 -2
- package/dist/schemas/quotaHistory.js +1 -1
- package/dist/schemas/sessionEvent.d.ts +6 -0
- package/dist/schemas/sessionEvent.js +1 -0
- package/dist/schemas/stateFile.d.ts +198 -0
- package/dist/schemas/stateFile.js +77 -0
- package/dist/schemas/tokenEstimation.d.ts +1 -1
- package/dist/schemas/transcript.js +2 -0
- package/dist/sessionFingerprint.d.ts +25 -0
- package/dist/sessionFingerprint.js +46 -0
- package/dist/sessionMonitor.d.ts +15 -0
- package/dist/sessionMonitor.js +142 -0
- package/dist/sessionPreviews.d.ts +33 -2
- package/dist/sessionPreviews.js +333 -6
- package/dist/sessionStats.d.ts +51 -0
- package/dist/sessionStats.js +166 -0
- package/dist/stateFile.d.ts +113 -0
- package/dist/stateFile.js +160 -0
- package/dist/statusline/claudeStatuslinePayload.d.ts +89 -0
- package/dist/statusline/claudeStatuslinePayload.js +152 -0
- package/dist/statusline/formatter.d.ts +13 -0
- package/dist/statusline/formatter.js +48 -6
- package/dist/statusline/index.d.ts +10 -2
- package/dist/statusline/index.js +19 -1
- package/dist/tokenSummary.d.ts +62 -0
- package/dist/tokenSummary.js +72 -0
- package/dist/transcript.d.ts +5 -0
- package/dist/transcript.js +2 -0
- package/dist/types/observedSessionV1.d.ts +8 -2
- package/dist/types/observedSessionV1.js +88 -25
- package/dist/types/sessionEvent.d.ts +2 -0
- package/dist/usage/billingBlocks.d.ts +85 -0
- package/dist/usage/billingBlocks.js +153 -0
- package/dist/usage/importSessionHistory.d.ts +53 -0
- package/dist/usage/importSessionHistory.js +88 -0
- package/dist/usage/usageEvents.d.ts +92 -0
- package/dist/usage/usageEvents.js +322 -0
- package/dist/usage/usageReports.d.ts +76 -0
- package/dist/usage/usageReports.js +208 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +30 -25
- package/dist/zaiQuotaApi.d.ts +2 -0
- package/dist/zaiQuotaApi.js +2 -0
- 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.
|
package/dist/providers/detect.js
CHANGED
|
@@ -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
|
-
*
|
|
174
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}`.
|