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.
- package/CHANGELOG.md +59 -0
- package/README.md +200 -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 +151 -0
- package/dist/accountManager.d.ts +13 -0
- package/dist/accountManager.js +60 -32
- package/dist/accountRegistry.d.ts +3 -1
- package/dist/accountRegistry.js +64 -41
- package/dist/accounts.js +40 -19
- package/dist/autoSwitch.d.ts +2 -1
- package/dist/autoSwitch.js +32 -7
- package/dist/browser.d.ts +10 -3
- package/dist/browser.js +17 -1
- package/dist/codexProfiles.d.ts +14 -0
- package/dist/codexProfiles.js +268 -116
- package/dist/codexQuotaWatcher.d.ts +6 -0
- package/dist/codexQuotaWatcher.js +101 -47
- package/dist/context/sessionContext.js +2 -4
- package/dist/ensureDefaultAccounts.js +4 -4
- package/dist/index.d.ts +32 -15
- package/dist/index.js +54 -9
- 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 +8 -2
- package/dist/node.js +11 -1
- package/dist/observedContextWindows.d.ts +13 -0
- package/dist/observedContextWindows.js +13 -0
- package/dist/observedSessionCollector.d.ts +75 -4
- package/dist/observedSessionCollector.js +452 -42
- package/dist/parsers/codexHistory.d.ts +32 -0
- package/dist/parsers/codexHistory.js +125 -0
- package/dist/parsers/mcpToolName.d.ts +26 -0
- package/dist/parsers/mcpToolName.js +31 -0
- package/dist/parsers/sessionActivityDetector.d.ts +6 -0
- package/dist/parsers/sessionActivityDetector.js +17 -0
- package/dist/parsers/sessionPathResolver.d.ts +11 -4
- package/dist/parsers/sessionPathResolver.js +11 -4
- package/dist/paths.d.ts +7 -3
- package/dist/paths.js +7 -3
- 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 +11 -1
- package/dist/providers/claudeCode.js +222 -39
- package/dist/providers/codex.d.ts +39 -1
- package/dist/providers/codex.js +326 -39
- package/dist/providers/codexDatabase.d.ts +9 -0
- package/dist/providers/codexDatabase.js +107 -2
- package/dist/providers/diagnostics.d.ts +13 -0
- package/dist/providers/diagnostics.js +88 -0
- package/dist/providers/factory.d.ts +14 -0
- package/dist/providers/factory.js +57 -0
- package/dist/providers/openCode.d.ts +20 -1
- package/dist/providers/openCode.js +361 -11
- package/dist/providers/openCodeDatabase.d.ts +10 -0
- package/dist/providers/openCodeDatabase.js +69 -0
- package/dist/providers/types.d.ts +57 -2
- package/dist/providers/types.js +4 -0
- package/dist/quota.d.ts +4 -1
- package/dist/quota.js +3 -7
- package/dist/quotaHistory.d.ts +3 -2
- package/dist/quotaHistory.js +2 -2
- package/dist/quotaPoller.d.ts +13 -1
- package/dist/quotaPoller.js +65 -3
- package/dist/quotaSnapshots.js +8 -77
- 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 +1 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/observedSessionV1.d.ts +27 -26
- package/dist/schemas/observedSessionV1.js +6 -4
- package/dist/schemas/quota.d.ts +2 -2
- package/dist/schemas/quota.js +3 -6
- package/dist/schemas/quotaHistory.d.ts +2 -2
- package/dist/schemas/tokenEstimation.d.ts +1 -1
- package/dist/sessionMonitor.d.ts +15 -0
- package/dist/sessionMonitor.js +142 -0
- package/dist/sessionPreviews.d.ts +85 -0
- package/dist/sessionPreviews.js +549 -0
- package/dist/sessionTranscripts.js +10 -4
- package/dist/types/observedSessionV1.d.ts +6 -2
- package/dist/types/observedSessionV1.js +14 -8
- package/dist/writers/atomic.d.ts +20 -0
- package/dist/writers/atomic.js +190 -19
- package/package.json +22 -1
package/dist/providers/codex.js
CHANGED
|
@@ -44,6 +44,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.CodexProvider = void 0;
|
|
47
|
+
exports.findCodexRolloutFile = findCodexRolloutFile;
|
|
47
48
|
const fs = __importStar(require("fs"));
|
|
48
49
|
const path = __importStar(require("path"));
|
|
49
50
|
const string_decoder_1 = require("string_decoder");
|
|
@@ -52,6 +53,7 @@ const sessionContext_1 = require("../context/sessionContext");
|
|
|
52
53
|
const codexParser_1 = require("../parsers/codexParser");
|
|
53
54
|
const codexDatabase_1 = require("./codexDatabase");
|
|
54
55
|
const codexProfiles_1 = require("../codexProfiles");
|
|
56
|
+
const diagnostics_1 = require("./diagnostics");
|
|
55
57
|
const modelContext_1 = require("../modelContext");
|
|
56
58
|
const observedContextWindows_1 = require("../observedContextWindows");
|
|
57
59
|
const usageNormalization_1 = require("../usageNormalization");
|
|
@@ -164,6 +166,94 @@ function findRolloutFilesInConfiguredHomes() {
|
|
|
164
166
|
}
|
|
165
167
|
return results;
|
|
166
168
|
}
|
|
169
|
+
async function findRolloutFilesAsync(dir) {
|
|
170
|
+
const results = [];
|
|
171
|
+
let entries;
|
|
172
|
+
try {
|
|
173
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
for (const entry of entries) {
|
|
179
|
+
const fullPath = path.join(dir, entry.name);
|
|
180
|
+
if (entry.isDirectory()) {
|
|
181
|
+
results.push(...(await findRolloutFilesAsync(fullPath)));
|
|
182
|
+
}
|
|
183
|
+
else if (entry.isFile() && isRolloutFile(entry.name)) {
|
|
184
|
+
try {
|
|
185
|
+
const stats = await fs.promises.stat(fullPath);
|
|
186
|
+
if (stats.size > 0) {
|
|
187
|
+
results.push({
|
|
188
|
+
path: fullPath,
|
|
189
|
+
mtime: stats.mtime,
|
|
190
|
+
sizeBytes: stats.size,
|
|
191
|
+
sessionId: extractSessionId(entry.name),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Skip inaccessible files.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
200
|
+
}
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Locates the rollout file for a Codex session id by walking
|
|
205
|
+
* `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`.
|
|
206
|
+
*
|
|
207
|
+
* Filename-only match, case-insensitive on the id — no file contents are read
|
|
208
|
+
* and files are only stat'ed once their name matches, so the walk stays cheap
|
|
209
|
+
* even over a large history. When the same id exists in more than one home,
|
|
210
|
+
* the most recently modified copy wins. Returns `null` for an unknown, empty,
|
|
211
|
+
* or whitespace id. Consumers who want the database's answer instead can query
|
|
212
|
+
* the already-public `CodexDatabase` (requires the external `sqlite3` binary).
|
|
213
|
+
*/
|
|
214
|
+
function findCodexRolloutFile(sessionId, options = {}) {
|
|
215
|
+
const normalizedId = sessionId.trim().toLowerCase();
|
|
216
|
+
if (!normalizedId)
|
|
217
|
+
return null;
|
|
218
|
+
const sessionsDirs = options.codexHome
|
|
219
|
+
? [path.join(options.codexHome, 'sessions')]
|
|
220
|
+
: getSessionsDirs();
|
|
221
|
+
let best = null;
|
|
222
|
+
for (const dir of sessionsDirs) {
|
|
223
|
+
for (const candidate of findRolloutFilesById(dir, normalizedId)) {
|
|
224
|
+
if (!best || candidate.mtimeMs > best.mtimeMs)
|
|
225
|
+
best = candidate;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return best?.path ?? null;
|
|
229
|
+
}
|
|
230
|
+
function findRolloutFilesById(dir, normalizedId) {
|
|
231
|
+
const results = [];
|
|
232
|
+
try {
|
|
233
|
+
if (!fs.existsSync(dir))
|
|
234
|
+
return results;
|
|
235
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
236
|
+
const fullPath = path.join(dir, entry.name);
|
|
237
|
+
if (entry.isDirectory()) {
|
|
238
|
+
results.push(...findRolloutFilesById(fullPath, normalizedId));
|
|
239
|
+
}
|
|
240
|
+
else if (entry.isFile() &&
|
|
241
|
+
isRolloutFile(entry.name) &&
|
|
242
|
+
extractSessionId(entry.name).toLowerCase() === normalizedId) {
|
|
243
|
+
try {
|
|
244
|
+
results.push({ path: fullPath, mtimeMs: fs.statSync(fullPath).mtimeMs });
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Skip files that vanish between readdir and stat.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Skip inaccessible directories.
|
|
254
|
+
}
|
|
255
|
+
return results;
|
|
256
|
+
}
|
|
167
257
|
/** Pick the best sessions directory for UI/search entrypoints. */
|
|
168
258
|
function getSessionsDir() {
|
|
169
259
|
const dirs = getSessionsDirs();
|
|
@@ -243,6 +333,41 @@ function readSessionMeta(rolloutPath) {
|
|
|
243
333
|
}
|
|
244
334
|
return null;
|
|
245
335
|
}
|
|
336
|
+
async function readSessionMetaAsync(rolloutPath) {
|
|
337
|
+
// Mirrors readSessionMeta()/readFirstLines(): chunk-read until the first
|
|
338
|
+
// line is complete under the same 256 KiB cap — session_meta carries
|
|
339
|
+
// `instructions` and can far exceed a single chunk.
|
|
340
|
+
const maxBytes = 256 * 1024;
|
|
341
|
+
const chunkSize = 64 * 1024;
|
|
342
|
+
let handle = null;
|
|
343
|
+
try {
|
|
344
|
+
handle = await fs.promises.open(rolloutPath, 'r');
|
|
345
|
+
const chunks = [];
|
|
346
|
+
let totalRead = 0;
|
|
347
|
+
let newlineFound = false;
|
|
348
|
+
while (totalRead < maxBytes && !newlineFound) {
|
|
349
|
+
const buffer = Buffer.alloc(Math.min(chunkSize, maxBytes - totalRead));
|
|
350
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, totalRead);
|
|
351
|
+
if (bytesRead === 0)
|
|
352
|
+
break;
|
|
353
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
354
|
+
chunks.push(chunk);
|
|
355
|
+
totalRead += bytesRead;
|
|
356
|
+
newlineFound = chunk.includes(0x0a);
|
|
357
|
+
}
|
|
358
|
+
const firstLine = Buffer.concat(chunks).toString('utf8').split('\n', 1)[0]?.trim();
|
|
359
|
+
if (!firstLine)
|
|
360
|
+
return null;
|
|
361
|
+
const parsed = JSON.parse(firstLine);
|
|
362
|
+
return parsed.type === 'session_meta' ? parsed.payload : null;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
await handle?.close().catch(() => undefined);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
246
371
|
/** Read the CWD from a rollout file's session_meta. Cached. */
|
|
247
372
|
const cwdCache = new Map();
|
|
248
373
|
function readSessionCwd(rolloutPath) {
|
|
@@ -254,6 +379,16 @@ function readSessionCwd(rolloutPath) {
|
|
|
254
379
|
cwdCache.set(rolloutPath, cwd);
|
|
255
380
|
return cwd;
|
|
256
381
|
}
|
|
382
|
+
/** Async twin of readSessionCwd, sharing its cache so repeat listings do no content reads. */
|
|
383
|
+
async function readSessionCwdAsync(rolloutPath) {
|
|
384
|
+
const cached = cwdCache.get(rolloutPath);
|
|
385
|
+
if (cached !== undefined)
|
|
386
|
+
return cached;
|
|
387
|
+
const meta = await readSessionMetaAsync(rolloutPath);
|
|
388
|
+
const cwd = meta?.cwd || null;
|
|
389
|
+
cwdCache.set(rolloutPath, cwd);
|
|
390
|
+
return cwd;
|
|
391
|
+
}
|
|
257
392
|
/** Check if a session's CWD matches a workspace path. */
|
|
258
393
|
function cwdMatches(sessionCwd, workspacePath) {
|
|
259
394
|
const normalizedSession = normalizePath(sessionCwd);
|
|
@@ -278,48 +413,65 @@ function normalizePath(input) {
|
|
|
278
413
|
*/
|
|
279
414
|
function extractFirstUserMessage(rolloutPath) {
|
|
280
415
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
416
|
+
return extractFirstUserMessageFromLines(readFirstLines(rolloutPath, 20));
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function extractFirstUserMessageFromLines(lines) {
|
|
423
|
+
for (const line of lines) {
|
|
424
|
+
const trimmed = line.trim();
|
|
425
|
+
if (!trimmed)
|
|
426
|
+
continue;
|
|
427
|
+
try {
|
|
428
|
+
const parsed = JSON.parse(trimmed);
|
|
429
|
+
// Check response_item with user message
|
|
430
|
+
if (parsed.type === 'response_item') {
|
|
431
|
+
const payload = parsed.payload;
|
|
432
|
+
if (payload.role === 'user') {
|
|
433
|
+
const content = payload.content;
|
|
434
|
+
if (typeof content === 'string' && content.trim() && !isSystemInjection(content)) {
|
|
435
|
+
return truncate(content, 60);
|
|
436
|
+
}
|
|
437
|
+
if (Array.isArray(content)) {
|
|
438
|
+
for (const part of content) {
|
|
439
|
+
const p = part;
|
|
440
|
+
if (p.text?.trim() && !isSystemInjection(p.text)) {
|
|
441
|
+
return truncate(p.text, 60);
|
|
302
442
|
}
|
|
303
443
|
}
|
|
304
444
|
}
|
|
305
445
|
}
|
|
306
|
-
// Check event_msg with user_message
|
|
307
|
-
if (parsed.type === 'event_msg') {
|
|
308
|
-
const payload = parsed.payload;
|
|
309
|
-
if (payload.type === 'user_message' && payload.message?.trim()) {
|
|
310
|
-
return truncate(payload.message, 60);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
446
|
}
|
|
314
|
-
|
|
315
|
-
|
|
447
|
+
// Check event_msg with user_message
|
|
448
|
+
if (parsed.type === 'event_msg') {
|
|
449
|
+
const payload = parsed.payload;
|
|
450
|
+
if (payload.type === 'user_message' && payload.message?.trim()) {
|
|
451
|
+
return truncate(payload.message, 60);
|
|
452
|
+
}
|
|
316
453
|
}
|
|
317
454
|
}
|
|
318
|
-
|
|
455
|
+
catch {
|
|
456
|
+
// Skip malformed lines
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
async function extractFirstUserMessageAsync(rolloutPath) {
|
|
462
|
+
let handle = null;
|
|
463
|
+
try {
|
|
464
|
+
handle = await fs.promises.open(rolloutPath, 'r');
|
|
465
|
+
const buffer = Buffer.alloc(256 * 1024);
|
|
466
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
467
|
+
return extractFirstUserMessageFromLines(buffer.toString('utf8', 0, bytesRead).split('\n').slice(0, 20));
|
|
319
468
|
}
|
|
320
469
|
catch {
|
|
321
470
|
return null;
|
|
322
471
|
}
|
|
472
|
+
finally {
|
|
473
|
+
await handle?.close().catch(() => undefined);
|
|
474
|
+
}
|
|
323
475
|
}
|
|
324
476
|
/** Extract searchable text from a rollout line. */
|
|
325
477
|
function extractSearchableText(line) {
|
|
@@ -606,9 +758,14 @@ class CodexProvider {
|
|
|
606
758
|
displayName = 'Codex CLI';
|
|
607
759
|
db = null;
|
|
608
760
|
dbInitialized = false;
|
|
761
|
+
dbStatus = { available: false, kind: 'db_missing' };
|
|
762
|
+
diagnostics;
|
|
609
763
|
dynamicContextWindowLimit = null;
|
|
610
764
|
lastRateLimitsData = null;
|
|
611
765
|
lastTokenUsageData = null;
|
|
766
|
+
constructor(options = {}) {
|
|
767
|
+
this.diagnostics = new diagnostics_1.ProviderDiagnosticTracker(this.id, options);
|
|
768
|
+
}
|
|
612
769
|
// --- Database ---
|
|
613
770
|
/** Lazy-initialize database connection. */
|
|
614
771
|
ensureDb() {
|
|
@@ -620,8 +777,34 @@ class CodexProvider {
|
|
|
620
777
|
if (db.isAvailable() && db.open()) {
|
|
621
778
|
this.db = db;
|
|
622
779
|
}
|
|
780
|
+
if (typeof db.getRuntimeStatus === 'function') {
|
|
781
|
+
this.dbStatus = db.getRuntimeStatus();
|
|
782
|
+
}
|
|
623
783
|
return this.db;
|
|
624
784
|
}
|
|
785
|
+
async ensureDbAsync() {
|
|
786
|
+
if (this.db)
|
|
787
|
+
return this.db;
|
|
788
|
+
return new codexDatabase_1.CodexDatabase(getCodexHome());
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Cache an async-created database only after an operation proved it usable.
|
|
792
|
+
* Sync paths treat a cached `db` as "open() succeeded", so adopting an
|
|
793
|
+
* unproven instance would silently disable their file fallbacks.
|
|
794
|
+
*/
|
|
795
|
+
adoptDbIfAvailable(db) {
|
|
796
|
+
if (this.db || !db.getRuntimeStatus().available)
|
|
797
|
+
return;
|
|
798
|
+
this.db = db;
|
|
799
|
+
this.dbInitialized = true;
|
|
800
|
+
}
|
|
801
|
+
getRuntimeStatus() {
|
|
802
|
+
const db = this.ensureDb();
|
|
803
|
+
return db && typeof db.getRuntimeStatus === 'function' ? db.getRuntimeStatus() : this.dbStatus;
|
|
804
|
+
}
|
|
805
|
+
getLastOperationStatus() {
|
|
806
|
+
return this.diagnostics.getLastOperationStatus();
|
|
807
|
+
}
|
|
625
808
|
// --- Path resolution ---
|
|
626
809
|
getSessionDirectory(workspacePath) {
|
|
627
810
|
// Codex doesn't use workspace-encoded directories like Claude Code.
|
|
@@ -631,10 +814,13 @@ class CodexProvider {
|
|
|
631
814
|
if (db) {
|
|
632
815
|
const thread = db.getMostRecentThread(workspacePath);
|
|
633
816
|
if (thread?.rollout_path) {
|
|
817
|
+
this.recordDatabaseFallback('getSessionDirectory', true);
|
|
634
818
|
return path.dirname(thread.rollout_path);
|
|
635
819
|
}
|
|
636
820
|
}
|
|
637
|
-
|
|
821
|
+
const directory = getSessionsDir();
|
|
822
|
+
this.recordDatabaseFallback('getSessionDirectory', true);
|
|
823
|
+
return directory;
|
|
638
824
|
}
|
|
639
825
|
discoverSessionDirectory(workspacePath) {
|
|
640
826
|
// DB: find latest rollout directory for this workspace
|
|
@@ -642,23 +828,29 @@ class CodexProvider {
|
|
|
642
828
|
if (db) {
|
|
643
829
|
const thread = db.getMostRecentThread(workspacePath);
|
|
644
830
|
if (thread?.rollout_path && fs.existsSync(thread.rollout_path)) {
|
|
831
|
+
this.recordDatabaseFallback('discoverSessionDirectory', true);
|
|
645
832
|
return path.dirname(thread.rollout_path);
|
|
646
833
|
}
|
|
647
834
|
}
|
|
648
835
|
// File scan: look for any rollout file whose session_meta.cwd matches
|
|
649
836
|
const files = findRolloutFilesInConfiguredHomes();
|
|
650
|
-
if (files.length === 0)
|
|
837
|
+
if (files.length === 0) {
|
|
838
|
+
this.recordDatabaseFallback('discoverSessionDirectory', true);
|
|
651
839
|
return null;
|
|
840
|
+
}
|
|
652
841
|
// Sort by mtime descending
|
|
653
842
|
files.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
654
843
|
for (const file of files) {
|
|
655
844
|
const meta = readSessionMeta(file.path);
|
|
656
845
|
if (meta && cwdMatches(meta.cwd, workspacePath)) {
|
|
846
|
+
this.recordDatabaseFallback('discoverSessionDirectory', true);
|
|
657
847
|
return path.dirname(file.path);
|
|
658
848
|
}
|
|
659
849
|
}
|
|
660
850
|
// Fallback: return the most relevant base sessions dir if one exists
|
|
661
|
-
|
|
851
|
+
const directory = getSessionsDir();
|
|
852
|
+
this.recordDatabaseFallback('discoverSessionDirectory', true);
|
|
853
|
+
return directory;
|
|
662
854
|
}
|
|
663
855
|
// --- Session discovery ---
|
|
664
856
|
findActiveSession(workspacePath) {
|
|
@@ -667,6 +859,7 @@ class CodexProvider {
|
|
|
667
859
|
if (db) {
|
|
668
860
|
const thread = db.getMostRecentThread(workspacePath);
|
|
669
861
|
if (thread?.rollout_path && fs.existsSync(thread.rollout_path)) {
|
|
862
|
+
this.recordDatabaseFallback('findActiveSession', true);
|
|
670
863
|
return thread.rollout_path;
|
|
671
864
|
}
|
|
672
865
|
}
|
|
@@ -676,9 +869,11 @@ class CodexProvider {
|
|
|
676
869
|
for (const file of files) {
|
|
677
870
|
const meta = readSessionMeta(file.path);
|
|
678
871
|
if (meta && cwdMatches(meta.cwd, workspacePath)) {
|
|
872
|
+
this.recordDatabaseFallback('findActiveSession', true);
|
|
679
873
|
return file.path;
|
|
680
874
|
}
|
|
681
875
|
}
|
|
876
|
+
this.recordDatabaseFallback('findActiveSession', true);
|
|
682
877
|
return null;
|
|
683
878
|
}
|
|
684
879
|
findAllSessions(workspacePath) {
|
|
@@ -689,18 +884,33 @@ class CodexProvider {
|
|
|
689
884
|
const dbPaths = threads
|
|
690
885
|
.filter((t) => t.rollout_path && fs.existsSync(t.rollout_path))
|
|
691
886
|
.map((t) => t.rollout_path);
|
|
692
|
-
if (dbPaths.length > 0)
|
|
887
|
+
if (dbPaths.length > 0) {
|
|
888
|
+
this.recordDatabaseFallback('findAllSessions', true);
|
|
693
889
|
return dbPaths;
|
|
890
|
+
}
|
|
694
891
|
}
|
|
695
892
|
// File scan
|
|
696
893
|
const files = findRolloutFilesInConfiguredHomes();
|
|
697
894
|
files.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
698
|
-
|
|
895
|
+
const sessions = files
|
|
699
896
|
.filter((f) => {
|
|
700
897
|
const meta = readSessionMeta(f.path);
|
|
701
898
|
return meta && cwdMatches(meta.cwd, workspacePath);
|
|
702
899
|
})
|
|
703
900
|
.map((f) => f.path);
|
|
901
|
+
this.recordDatabaseFallback('findAllSessions', true);
|
|
902
|
+
return sessions;
|
|
903
|
+
}
|
|
904
|
+
findSessionById(_workspacePath, sessionId) {
|
|
905
|
+
try {
|
|
906
|
+
const found = findCodexRolloutFile(sessionId);
|
|
907
|
+
this.recordDatabaseFallback('findSessionById', true);
|
|
908
|
+
return found;
|
|
909
|
+
}
|
|
910
|
+
catch {
|
|
911
|
+
this.recordDatabaseFallback('findSessionById', false);
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
704
914
|
}
|
|
705
915
|
/** Backward-compatible alias for findAllSessions. */
|
|
706
916
|
findSessionFiles(workspacePath) {
|
|
@@ -711,6 +921,30 @@ class CodexProvider {
|
|
|
711
921
|
files.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
712
922
|
return files.map((f) => f.path);
|
|
713
923
|
}
|
|
924
|
+
listAllSessionFiles() {
|
|
925
|
+
const files = findRolloutFilesInConfiguredHomes();
|
|
926
|
+
this.recordDatabaseFallback('listAllSessionFiles', true);
|
|
927
|
+
return files;
|
|
928
|
+
}
|
|
929
|
+
async listSessionFilesAsync(workspacePath) {
|
|
930
|
+
const seen = new Set();
|
|
931
|
+
const results = [];
|
|
932
|
+
for (const sessionsDir of getSessionsDirs()) {
|
|
933
|
+
for (const file of await findRolloutFilesAsync(sessionsDir)) {
|
|
934
|
+
if (seen.has(file.path))
|
|
935
|
+
continue;
|
|
936
|
+
seen.add(file.path);
|
|
937
|
+
if (workspacePath) {
|
|
938
|
+
const cwd = await readSessionCwdAsync(file.path);
|
|
939
|
+
if (!cwd || !cwdMatches(cwd, workspacePath))
|
|
940
|
+
continue;
|
|
941
|
+
file.workspacePath = cwd;
|
|
942
|
+
}
|
|
943
|
+
results.push(file);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return results.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
947
|
+
}
|
|
714
948
|
getAllProjectFolders(workspacePath) {
|
|
715
949
|
const folders = [];
|
|
716
950
|
const seenCwds = new Map();
|
|
@@ -765,6 +999,7 @@ class CodexProvider {
|
|
|
765
999
|
}
|
|
766
1000
|
return b.lastModified.getTime() - a.lastModified.getTime();
|
|
767
1001
|
});
|
|
1002
|
+
this.recordDatabaseFallback('getAllProjectFolders', true);
|
|
768
1003
|
return folders;
|
|
769
1004
|
}
|
|
770
1005
|
// --- File identification ---
|
|
@@ -784,13 +1019,43 @@ class CodexProvider {
|
|
|
784
1019
|
if (db) {
|
|
785
1020
|
const sessionId = this.getSessionId(sessionPath);
|
|
786
1021
|
const thread = db.getThread(sessionId);
|
|
787
|
-
if (thread?.title)
|
|
1022
|
+
if (thread?.title) {
|
|
1023
|
+
this.recordDatabaseFallback('extractSessionLabel', true);
|
|
788
1024
|
return truncate(thread.title, 60);
|
|
789
|
-
|
|
1025
|
+
}
|
|
1026
|
+
if (thread?.first_user_message) {
|
|
1027
|
+
this.recordDatabaseFallback('extractSessionLabel', true);
|
|
790
1028
|
return truncate(thread.first_user_message, 60);
|
|
1029
|
+
}
|
|
791
1030
|
}
|
|
792
1031
|
// File fallback: parse first user message
|
|
793
|
-
|
|
1032
|
+
const label = extractFirstUserMessage(sessionPath);
|
|
1033
|
+
this.recordDatabaseFallback('extractSessionLabel', true);
|
|
1034
|
+
return label;
|
|
1035
|
+
}
|
|
1036
|
+
async extractSessionLabelsAsync(sessionPaths) {
|
|
1037
|
+
const results = new Map();
|
|
1038
|
+
const byId = new Map(sessionPaths.map((sessionPath) => [this.getSessionId(sessionPath), sessionPath]));
|
|
1039
|
+
const db = await this.ensureDbAsync();
|
|
1040
|
+
const threads = await db.getThreadsByIdsAsync([...byId.keys()]);
|
|
1041
|
+
for (const thread of threads) {
|
|
1042
|
+
const sessionPath = byId.get(thread.id);
|
|
1043
|
+
if (!sessionPath)
|
|
1044
|
+
continue;
|
|
1045
|
+
const label = thread.title ?? thread.first_user_message;
|
|
1046
|
+
if (label)
|
|
1047
|
+
results.set(sessionPath, truncate(label, 60));
|
|
1048
|
+
}
|
|
1049
|
+
this.dbStatus = db.getRuntimeStatus();
|
|
1050
|
+
this.adoptDbIfAvailable(db);
|
|
1051
|
+
for (const sessionPath of sessionPaths) {
|
|
1052
|
+
if (!results.has(sessionPath)) {
|
|
1053
|
+
results.set(sessionPath, await extractFirstUserMessageAsync(sessionPath));
|
|
1054
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
this.recordDatabaseFallback('extractSessionLabelsAsync', true);
|
|
1058
|
+
return results;
|
|
794
1059
|
}
|
|
795
1060
|
// --- Data reading ---
|
|
796
1061
|
createReader(sessionPath) {
|
|
@@ -957,6 +1222,9 @@ class CodexProvider {
|
|
|
957
1222
|
getProjectsBaseDir() {
|
|
958
1223
|
return getSessionsDir();
|
|
959
1224
|
}
|
|
1225
|
+
getWatchRoots() {
|
|
1226
|
+
return getSessionsDirs();
|
|
1227
|
+
}
|
|
960
1228
|
// --- Stats ---
|
|
961
1229
|
readSessionStats(sessionPath) {
|
|
962
1230
|
const sessionId = extractSessionId(path.basename(sessionPath));
|
|
@@ -1036,9 +1304,28 @@ class CodexProvider {
|
|
|
1036
1304
|
this.db?.close();
|
|
1037
1305
|
this.db = null;
|
|
1038
1306
|
this.dbInitialized = false;
|
|
1307
|
+
this.dbStatus = { available: false, kind: 'db_missing' };
|
|
1039
1308
|
this.dynamicContextWindowLimit = null;
|
|
1040
1309
|
this.lastRateLimitsData = null;
|
|
1041
1310
|
this.lastTokenUsageData = null;
|
|
1042
1311
|
}
|
|
1312
|
+
recordDatabaseFallback(operation, usable) {
|
|
1313
|
+
if (!this.dbInitialized) {
|
|
1314
|
+
this.diagnostics.available(operation);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
const status = this.getRuntimeStatus();
|
|
1318
|
+
const diagnostic = (0, diagnostics_1.diagnosticFromRuntimeStatus)(this.id, 'query', status);
|
|
1319
|
+
if (!diagnostic) {
|
|
1320
|
+
this.diagnostics.available(operation);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
this.diagnostics.degraded(operation, status, {
|
|
1324
|
+
kind: diagnostic.kind,
|
|
1325
|
+
severity: diagnostic.severity,
|
|
1326
|
+
phase: diagnostic.phase,
|
|
1327
|
+
message: diagnostic.message,
|
|
1328
|
+
}, usable);
|
|
1329
|
+
}
|
|
1043
1330
|
}
|
|
1044
1331
|
exports.CodexProvider = CodexProvider;
|
|
@@ -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. */
|