@axhub/genie 0.2.7 → 0.2.9
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/LICENSE +21 -675
- package/dist/api-docs.html +2 -2
- package/dist/assets/App-GBcTeeUS.js +460 -0
- package/dist/assets/App-qxJ8_QYu.css +32 -0
- package/dist/assets/ReviewApp-C9K--AQE.js +1 -0
- package/dist/assets/{_basePickBy-C19AekOu.js → _basePickBy-DR_8uFCo.js} +1 -1
- package/dist/assets/{_baseUniq-JsnevLw_.js → _baseUniq-D0njlQ_7.js} +1 -1
- package/dist/assets/{arc-BLpcuBlf.js → arc-CKlr_Rec.js} +1 -1
- package/dist/assets/architectureDiagram-2XIMDMQ5-BmO_uLUH.js +36 -0
- package/dist/assets/{blockDiagram-WCTKOSBZ-DQBLwsUS.js → blockDiagram-WCTKOSBZ-DhAeO-56.js} +3 -3
- package/dist/assets/c4Diagram-IC4MRINW-C67kFoXx.js +10 -0
- package/dist/assets/channel-V3MBjKys.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-De63kbgc.js → chunk-4BX2VUAB-mLLagvJi.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DtTDDdM9.js → chunk-55IACEB6-Lx-hOjlM.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DHuwd8tw.js → chunk-FMBD7UC4-Bt-XmVUV.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-BgytFtmO.js → chunk-JSJVCQXG-Cya6gaDV.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-nZdp86aN.js → chunk-KX2RTZJC-Bd7Ig6tF.js} +1 -1
- package/dist/assets/chunk-NQ4KR5QH-5UAE0Vg-.js +220 -0
- package/dist/assets/{chunk-QZHKN3VN-DvUQ3mnO.js → chunk-QZHKN3VN-BAxZ8m7w.js} +1 -1
- package/dist/assets/chunk-WL4C6EOR-DjDPvUUP.js +189 -0
- package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +1 -0
- package/dist/assets/clone-BbMGfZwt.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-D-60XrkJ.js +1 -0
- package/dist/assets/cytoscape.esm-2ZfV8NB5.js +331 -0
- package/dist/assets/{dagre-KLK3FWXG-CHYIvW47.js → dagre-KLK3FWXG-bqu3ZS4K.js} +1 -1
- package/dist/assets/diagram-E7M64L7V-BueeqoYm.js +24 -0
- package/dist/assets/{diagram-IFDJBPK2-Dzsiln_C.js → diagram-IFDJBPK2-D4fDv2E7.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-DKnGbUpE.js → diagram-P4PSJMXO-WqipY3fN.js} +1 -1
- package/dist/assets/erDiagram-INFDFZHY-D0oVnO-x.js +70 -0
- package/dist/assets/{flowDiagram-PKNHOUZH-BAZ2-jKp.js → flowDiagram-PKNHOUZH-DzbGyxrr.js} +4 -4
- package/dist/assets/ganttDiagram-A5KZAMGK-BwhbbgCP.js +292 -0
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BflpyjGy.js → gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js} +1 -1
- package/dist/assets/{graph-suelaXFh.js → graph-DzKos-N0.js} +1 -1
- package/dist/assets/highlighted-body-TPN3WLV5-CKDMgz3X.js +1 -0
- package/dist/assets/index-DiQlHzGj.js +2 -0
- package/dist/assets/index-Drat2nB9.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-pfD1FA3p.js → infoDiagram-LFFYTUFH-BFicZbTf.js} +1 -1
- package/dist/assets/ishikawaDiagram-PHBUUO56-CtihxDxl.js +70 -0
- package/dist/assets/journeyDiagram-4ABVD52K-Du00J8_d.js +139 -0
- package/dist/assets/{kanban-definition-K7BYSVSG-FWinmur1.js → kanban-definition-K7BYSVSG-BJi9S0iQ.js} +5 -5
- package/dist/assets/{layout-vcz43XvZ.js → layout-B80Sityu.js} +1 -1
- package/dist/assets/{linear-le4gc0vx.js → linear-sRQLOf5H.js} +1 -1
- package/dist/assets/mermaid-O7DHMXV3-CBuVs4eJ.js +1038 -0
- package/dist/assets/mindmap-definition-YRQLILUH-C5IL_xi-.js +68 -0
- package/dist/assets/{pieDiagram-SKSYHLDU-C7PKDh3b.js → pieDiagram-SKSYHLDU-CeTwlJ8z.js} +2 -2
- package/dist/assets/quadrantDiagram-337W2JSQ-COfUcLWt.js +7 -0
- package/dist/assets/requirementDiagram-Z7DCOOCP-DSb-CJ5B.js +73 -0
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-4gulcOP4.js → sankeyDiagram-WA2Y5GQK-8jtuVb45.js} +3 -3
- package/dist/assets/sequenceDiagram-2WXFIKYE-C2VpkMwA.js +145 -0
- package/dist/assets/{stateDiagram-RAJIS63D-CB4Vl7qM.js → stateDiagram-RAJIS63D-fmwMqxxc.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +1 -0
- package/dist/assets/timeline-definition-YZTLITO2-Dx1hP5lg.js +61 -0
- package/dist/assets/{treemap-KZPCXAKY-DZSEE6Hz.js → treemap-KZPCXAKY-CkLOdYCZ.js} +58 -58
- package/dist/assets/vendor-codemirror-BxPY6emf.js +39 -0
- package/dist/assets/vendor-react-xmA_f8ig.js +59 -0
- package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
- package/dist/assets/{vennDiagram-LZ73GAT5-8E_G06fI.js → vennDiagram-LZ73GAT5-D6KWcnln.js} +4 -4
- package/dist/assets/xychartDiagram-JWTSCODW-6fh6qmzN.js +7 -0
- package/dist/index.html +5 -5
- package/package.json +36 -35
- package/server/acp-runtime/client.js +91 -17
- package/server/acp-runtime/index.js +5 -16
- package/server/acp-runtime/session-store.js +4 -4
- package/server/channels/runtime/AgentRuntimeAdapter.js +1 -10
- package/server/claude-sdk.js +1 -3
- package/server/cli.js +159 -2
- package/server/external-agent/service.js +24 -6
- package/server/external-agent/ws.js +63 -3
- package/server/gemini-cli.js +1 -3
- package/server/index.js +120 -19
- package/server/openai-codex.js +1 -3
- package/server/opencode-cli.js +1 -3
- package/server/projects.js +654 -236
- package/server/routes/cc-connect.js +1131 -0
- package/server/routes/cli-auth.js +1 -73
- package/server/routes/commands.js +4 -9
- package/server/routes/projects.js +45 -24
- package/server/routes/session-core.js +149 -86
- package/server/session-core/eventStore.js +45 -18
- package/server/session-core/providerAdapters.js +50 -13
- package/server/session-core/providerDiscovery.js +8 -3
- package/server/session-core/runtimeState.js +8 -0
- package/server/utils/ccConnectManager.js +390 -0
- package/server/utils/ccConnectState.js +575 -0
- package/server/utils/resolveCommandPath.js +71 -0
- package/server/utils/workspaceRoots.js +154 -0
- package/shared/conversationEvents.js +78 -14
- package/dist/assets/App-BWSqiXAT.js +0 -220
- package/dist/assets/App-DrlLKa8f.css +0 -1
- package/dist/assets/ReviewApp-nz3mbArg.js +0 -1
- package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +0 -36
- package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +0 -10
- package/dist/assets/channel-DkFNxV_H.js +0 -1
- package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +0 -220
- package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +0 -189
- package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +0 -1
- package/dist/assets/clone-C0lCEIEO.js +0 -1
- package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +0 -1
- package/dist/assets/cytoscape.esm-5J0xJHOV.js +0 -321
- package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +0 -24
- package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +0 -70
- package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +0 -292
- package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +0 -1
- package/dist/assets/index-B01NxbUv.css +0 -1
- package/dist/assets/index-DW5pGgQ_.js +0 -2
- package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +0 -70
- package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +0 -139
- package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +0 -870
- package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +0 -68
- package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +0 -7
- package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +0 -73
- package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +0 -145
- package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +0 -1
- package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +0 -61
- package/dist/assets/vendor-codemirror-CyOKkaQZ.js +0 -31
- package/dist/assets/vendor-react-CP4yFTs7.js +0 -8
- package/dist/assets/vendor-xterm-DfcmCpbH.js +0 -66
- package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +0 -7
- package/server/_legacy-providers/README.md +0 -30
- package/server/_legacy-providers/claude-sdk.js +0 -956
- package/server/_legacy-providers/gemini-cli.js +0 -368
- package/server/_legacy-providers/openai-codex.js +0 -705
- package/server/_legacy-providers/opencode-cli.js +0 -674
- package/server/acp-runtime/client.test.js +0 -688
- package/server/acp-runtime/session-store.test.js +0 -89
- package/server/cli.test.js +0 -76
- package/server/external-agent/service.test.js +0 -53
- package/server/external-agent/ws.test.js +0 -289
- package/shared/conversationEvents.test.js +0 -403
package/server/projects.js
CHANGED
|
@@ -51,13 +51,62 @@ import os from 'os';
|
|
|
51
51
|
import path from 'path';
|
|
52
52
|
import readline from 'readline';
|
|
53
53
|
import crypto from 'crypto';
|
|
54
|
+
import { fileURLToPath } from 'url';
|
|
54
55
|
import { parseCodexTokenCountInfo } from './utils/codexTokenUsage.js';
|
|
55
56
|
import { CODEX_MODELS } from '../shared/modelConstants.js';
|
|
56
57
|
|
|
58
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
59
|
+
const __dirname = path.dirname(__filename);
|
|
60
|
+
|
|
57
61
|
const KNOWN_CODEX_MODELS = new Set(
|
|
58
62
|
(CODEX_MODELS?.OPTIONS || []).map((option) => String(option?.value || '').trim().toLowerCase()).filter(Boolean)
|
|
59
63
|
);
|
|
60
64
|
|
|
65
|
+
const PROJECT_CONFIG_PERMISSION_ERROR_CODES = new Set(['EACCES', 'EPERM', 'EROFS']);
|
|
66
|
+
|
|
67
|
+
function getPrimaryProjectConfigPath() {
|
|
68
|
+
return path.join(os.homedir(), '.claude', 'project-config.json');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getFallbackProjectConfigPath() {
|
|
72
|
+
const dataFilePath = process.env.DATA_FILE_PATH || path.join(__dirname, 'database', 'state.json');
|
|
73
|
+
return path.join(path.dirname(dataFilePath), 'project-config.json');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getProjectConfigPaths() {
|
|
77
|
+
return [...new Set([
|
|
78
|
+
getPrimaryProjectConfigPath(),
|
|
79
|
+
getFallbackProjectConfigPath()
|
|
80
|
+
])];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isProjectConfigPermissionError(error) {
|
|
84
|
+
return Boolean(error && PROJECT_CONFIG_PERMISSION_ERROR_CODES.has(error.code));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeProjectConfig(rawConfig) {
|
|
88
|
+
if (!rawConfig || typeof rawConfig !== 'object' || Array.isArray(rawConfig)) {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return rawConfig;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function writeProjectConfigFile(configPath, config) {
|
|
96
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
97
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function removeProjectConfigFile(configPath) {
|
|
101
|
+
try {
|
|
102
|
+
await fs.rm(configPath, { force: true });
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error.code !== 'ENOENT') {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
61
110
|
function isRecognizedCodexModel(value) {
|
|
62
111
|
if (typeof value !== 'string') return false;
|
|
63
112
|
const normalized = value.trim().toLowerCase();
|
|
@@ -97,7 +146,12 @@ function extractVisibleUserMessage(value) {
|
|
|
97
146
|
return '';
|
|
98
147
|
}
|
|
99
148
|
|
|
100
|
-
if (
|
|
149
|
+
if (
|
|
150
|
+
text.startsWith('# AGENTS.md instructions for ') ||
|
|
151
|
+
text.includes('<environment_context>') ||
|
|
152
|
+
text.startsWith('<subagent_notification>') ||
|
|
153
|
+
text.startsWith('</subagent_notification>')
|
|
154
|
+
) {
|
|
101
155
|
return '';
|
|
102
156
|
}
|
|
103
157
|
|
|
@@ -128,12 +182,16 @@ function isInjectedContextContent(value) {
|
|
|
128
182
|
|
|
129
183
|
if (
|
|
130
184
|
text.startsWith('# AGENTS.md instructions for ') ||
|
|
131
|
-
text.startsWith('[DYNAMIC CONTEXT V1]')
|
|
185
|
+
text.startsWith('[DYNAMIC CONTEXT V1]') ||
|
|
186
|
+
text.startsWith('<subagent_notification>') ||
|
|
187
|
+
text.startsWith('</subagent_notification>')
|
|
132
188
|
) {
|
|
133
189
|
return true;
|
|
134
190
|
}
|
|
135
191
|
|
|
136
|
-
return /^<dynamic_context(?:\s|>)/i.test(text)
|
|
192
|
+
return /^<dynamic_context(?:\s|>)/i.test(text)
|
|
193
|
+
|| text.includes('<environment_context>')
|
|
194
|
+
|| /<subagent_notification(?:\s|>)/i.test(text);
|
|
137
195
|
}
|
|
138
196
|
|
|
139
197
|
// Import TaskMaster detection functions
|
|
@@ -266,7 +324,14 @@ async function detectTaskMasterFolder(projectPath) {
|
|
|
266
324
|
|
|
267
325
|
// Cache for extracted project directories
|
|
268
326
|
const projectDirectoryCache = new Map();
|
|
327
|
+
const PROJECT_LIST_CACHE_TTL_MS = 15000;
|
|
269
328
|
const PROVIDER_SESSION_LOOKUP_CACHE_TTL_MS = 5000;
|
|
329
|
+
const projectListCache = {
|
|
330
|
+
data: null,
|
|
331
|
+
promise: null,
|
|
332
|
+
expiresAt: 0
|
|
333
|
+
};
|
|
334
|
+
const codexSessionFileCache = new Map();
|
|
270
335
|
const providerSessionLookupCache = {
|
|
271
336
|
codex: { key: null, data: null, promise: null, expiresAt: 0 },
|
|
272
337
|
gemini: { key: null, data: null, promise: null, expiresAt: 0 },
|
|
@@ -343,9 +408,81 @@ async function findFilesRecursively(rootDir, matcher) {
|
|
|
343
408
|
return discoveredFiles;
|
|
344
409
|
}
|
|
345
410
|
|
|
411
|
+
function getCodexSessionsDir() {
|
|
412
|
+
return path.join(os.homedir(), '.codex', 'sessions');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function cacheCodexSessionFilePath(sessionId, filePath) {
|
|
416
|
+
if (!sessionId || !filePath) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
codexSessionFileCache.set(sessionId, filePath);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function findCodexSessionFilePathBySessionIdHint(sessionId, filePaths = []) {
|
|
424
|
+
const normalizedSessionId = String(sessionId || '').trim();
|
|
425
|
+
if (!normalizedSessionId || !Array.isArray(filePaths) || filePaths.length === 0) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return filePaths.find((filePath) => {
|
|
430
|
+
const basename = path.basename(filePath, '.jsonl');
|
|
431
|
+
return basename === normalizedSessionId || basename.includes(normalizedSessionId);
|
|
432
|
+
}) || null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function resolveCodexSessionFile(sessionId) {
|
|
436
|
+
if (!sessionId) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const cachedPath = codexSessionFileCache.get(sessionId);
|
|
441
|
+
if (cachedPath) {
|
|
442
|
+
try {
|
|
443
|
+
await fs.access(cachedPath);
|
|
444
|
+
return cachedPath;
|
|
445
|
+
} catch (_) {
|
|
446
|
+
codexSessionFileCache.delete(sessionId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const jsonlFiles = await findFilesRecursively(
|
|
451
|
+
getCodexSessionsDir(),
|
|
452
|
+
(entryName) => entryName.endsWith('.jsonl')
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
const hintedFilePath = findCodexSessionFilePathBySessionIdHint(sessionId, jsonlFiles);
|
|
456
|
+
if (hintedFilePath) {
|
|
457
|
+
cacheCodexSessionFilePath(sessionId, hintedFilePath);
|
|
458
|
+
return hintedFilePath;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
for (const filePath of jsonlFiles) {
|
|
462
|
+
try {
|
|
463
|
+
const sessionData = await parseCodexSessionFile(filePath);
|
|
464
|
+
if (sessionData?.id === sessionId) {
|
|
465
|
+
cacheCodexSessionFilePath(sessionId, filePath);
|
|
466
|
+
return filePath;
|
|
467
|
+
}
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.warn(`Could not parse Codex session file ${filePath}:`, error.message);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function clearProjectListCache() {
|
|
477
|
+
projectListCache.data = null;
|
|
478
|
+
projectListCache.promise = null;
|
|
479
|
+
projectListCache.expiresAt = 0;
|
|
480
|
+
}
|
|
481
|
+
|
|
346
482
|
// Clear cache when needed (called when project files change)
|
|
347
483
|
function clearProjectDirectoryCache() {
|
|
348
484
|
projectDirectoryCache.clear();
|
|
485
|
+
clearProjectListCache();
|
|
349
486
|
}
|
|
350
487
|
|
|
351
488
|
function clearProviderSessionLookupCaches() {
|
|
@@ -357,6 +494,27 @@ function clearProviderSessionLookupCaches() {
|
|
|
357
494
|
}
|
|
358
495
|
}
|
|
359
496
|
|
|
497
|
+
function appendBoundedMessage(buffer, message, maxSize = null) {
|
|
498
|
+
if (!message) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (maxSize === null) {
|
|
503
|
+
buffer.push(message);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (maxSize <= 0) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (buffer.length === maxSize) {
|
|
512
|
+
buffer.shift();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
buffer.push(message);
|
|
516
|
+
}
|
|
517
|
+
|
|
360
518
|
async function getCachedProviderSessionLookup(providerName, cacheKey, buildLookup) {
|
|
361
519
|
const cacheEntry = providerSessionLookupCache[providerName];
|
|
362
520
|
|
|
@@ -393,31 +551,54 @@ async function getCachedProviderSessionLookup(providerName, cacheKey, buildLooku
|
|
|
393
551
|
|
|
394
552
|
// Load project configuration file
|
|
395
553
|
async function loadProjectConfig() {
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
554
|
+
const mergedConfig = {};
|
|
555
|
+
|
|
556
|
+
for (const configPath of getProjectConfigPaths()) {
|
|
557
|
+
try {
|
|
558
|
+
const configData = await fs.readFile(configPath, 'utf8');
|
|
559
|
+
Object.assign(mergedConfig, normalizeProjectConfig(JSON.parse(configData)));
|
|
560
|
+
} catch (error) {
|
|
561
|
+
// Return merged config from any readable location.
|
|
562
|
+
}
|
|
403
563
|
}
|
|
564
|
+
|
|
565
|
+
return mergedConfig;
|
|
404
566
|
}
|
|
405
567
|
|
|
406
568
|
// Save project configuration file
|
|
407
569
|
async function saveProjectConfig(config) {
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
570
|
+
const normalizedConfig = normalizeProjectConfig(config);
|
|
571
|
+
const [primaryConfigPath, ...fallbackConfigPaths] = getProjectConfigPaths();
|
|
572
|
+
let lastPermissionError = null;
|
|
573
|
+
|
|
574
|
+
for (const configPath of [primaryConfigPath, ...fallbackConfigPaths]) {
|
|
575
|
+
try {
|
|
576
|
+
await writeProjectConfigFile(configPath, normalizedConfig);
|
|
577
|
+
|
|
578
|
+
if (configPath === primaryConfigPath) {
|
|
579
|
+
for (const fallbackConfigPath of fallbackConfigPaths) {
|
|
580
|
+
await removeProjectConfigFile(fallbackConfigPath);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
clearProjectDirectoryCache();
|
|
585
|
+
return;
|
|
586
|
+
} catch (error) {
|
|
587
|
+
if (isProjectConfigPermissionError(error)) {
|
|
588
|
+
lastPermissionError = error;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
|
|
416
592
|
throw error;
|
|
417
593
|
}
|
|
418
594
|
}
|
|
419
|
-
|
|
420
|
-
|
|
595
|
+
|
|
596
|
+
if (lastPermissionError) {
|
|
597
|
+
const saveError = new Error('Unable to save the project list because both Claude config storage and the app data directory are not writable.');
|
|
598
|
+
saveError.code = lastPermissionError.code;
|
|
599
|
+
saveError.cause = lastPermissionError;
|
|
600
|
+
throw saveError;
|
|
601
|
+
}
|
|
421
602
|
}
|
|
422
603
|
|
|
423
604
|
// Generate better display name from path
|
|
@@ -568,15 +749,17 @@ async function extractProjectDirectory(projectName) {
|
|
|
568
749
|
}
|
|
569
750
|
}
|
|
570
751
|
|
|
571
|
-
|
|
752
|
+
function cloneProjectList(projects = []) {
|
|
753
|
+
return projects.map((project) => ({ ...project }));
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async function collectProjectDefinitions(progressCallback = null) {
|
|
572
757
|
const claudeDir = path.join(os.homedir(), '.claude', 'projects');
|
|
573
758
|
const config = await loadProjectConfig();
|
|
574
|
-
const projects = [];
|
|
575
759
|
const existingProjects = new Set();
|
|
576
760
|
const projectDefinitions = [];
|
|
577
761
|
let totalProjects = 0;
|
|
578
762
|
let processedProjects = 0;
|
|
579
|
-
let directories = [];
|
|
580
763
|
|
|
581
764
|
try {
|
|
582
765
|
// Check if the .claude/projects directory exists
|
|
@@ -584,7 +767,7 @@ async function getProjects(progressCallback = null) {
|
|
|
584
767
|
|
|
585
768
|
// First, get existing Claude projects from the file system
|
|
586
769
|
const entries = await fs.readdir(claudeDir, { withFileTypes: true });
|
|
587
|
-
directories = entries.filter(e => e.isDirectory());
|
|
770
|
+
const directories = entries.filter(e => e.isDirectory());
|
|
588
771
|
|
|
589
772
|
// Build set of existing project names for later
|
|
590
773
|
directories.forEach(e => existingProjects.add(e.name));
|
|
@@ -675,69 +858,173 @@ async function getProjects(progressCallback = null) {
|
|
|
675
858
|
}
|
|
676
859
|
}
|
|
677
860
|
|
|
678
|
-
|
|
679
|
-
projectDefinitions
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
861
|
+
return {
|
|
862
|
+
projectDefinitions,
|
|
863
|
+
totalProjects
|
|
864
|
+
};
|
|
865
|
+
}
|
|
683
866
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
867
|
+
async function getProjectsList(progressCallback = null) {
|
|
868
|
+
if (
|
|
869
|
+
projectListCache.data &&
|
|
870
|
+
projectListCache.expiresAt > Date.now()
|
|
871
|
+
) {
|
|
872
|
+
const cachedProjects = cloneProjectList(projectListCache.data);
|
|
873
|
+
if (progressCallback) {
|
|
874
|
+
progressCallback({
|
|
875
|
+
phase: 'complete',
|
|
876
|
+
current: cachedProjects.length,
|
|
877
|
+
total: cachedProjects.length
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
return cachedProjects;
|
|
881
|
+
}
|
|
689
882
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
883
|
+
if (!progressCallback && projectListCache.promise) {
|
|
884
|
+
return cloneProjectList(await projectListCache.promise);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const loadProjectsList = async () => {
|
|
888
|
+
const { projectDefinitions, totalProjects } = await collectProjectDefinitions(progressCallback);
|
|
889
|
+
const lightweightProjects = projectDefinitions.map((definition) => ({
|
|
693
890
|
name: definition.name,
|
|
694
891
|
path: definition.path,
|
|
695
892
|
displayName: definition.displayName,
|
|
696
893
|
fullPath: definition.fullPath,
|
|
697
894
|
isCustomName: definition.isCustomName,
|
|
698
|
-
isManuallyAdded: !!definition.isManuallyAdded
|
|
699
|
-
|
|
700
|
-
codexSessions: codexSessionsByProjectPath.get(normalizedProjectPath) || [],
|
|
701
|
-
opencodeSessions: opencodeSessionsByProjectPath.get(normalizedProjectPath) || [],
|
|
702
|
-
geminiSessions: geminiSessionsByProjectPath.get(normalizedProjectPath) || []
|
|
703
|
-
};
|
|
895
|
+
isManuallyAdded: !!definition.isManuallyAdded
|
|
896
|
+
}));
|
|
704
897
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
console.warn(`Could not load sessions for project ${definition.name}:`, e.message);
|
|
715
|
-
}
|
|
898
|
+
projectListCache.data = lightweightProjects;
|
|
899
|
+
projectListCache.expiresAt = Date.now() + PROJECT_LIST_CACHE_TTL_MS;
|
|
900
|
+
|
|
901
|
+
if (progressCallback) {
|
|
902
|
+
progressCallback({
|
|
903
|
+
phase: 'complete',
|
|
904
|
+
current: totalProjects,
|
|
905
|
+
total: totalProjects
|
|
906
|
+
});
|
|
716
907
|
}
|
|
717
908
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
909
|
+
return lightweightProjects;
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
if (progressCallback) {
|
|
913
|
+
return loadProjectsList();
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
projectListCache.promise = loadProjectsList().finally(() => {
|
|
917
|
+
projectListCache.promise = null;
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
return cloneProjectList(await projectListCache.promise);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
async function buildProjectFromDefinition(definition, providerLookups = null) {
|
|
924
|
+
const normalizedProjectPath = normalizeComparableProjectPath(definition.fullPath);
|
|
925
|
+
const project = {
|
|
926
|
+
name: definition.name,
|
|
927
|
+
path: definition.path,
|
|
928
|
+
displayName: definition.displayName,
|
|
929
|
+
fullPath: definition.fullPath,
|
|
930
|
+
isCustomName: definition.isCustomName,
|
|
931
|
+
isManuallyAdded: !!definition.isManuallyAdded,
|
|
932
|
+
sessions: [],
|
|
933
|
+
codexSessions: [],
|
|
934
|
+
opencodeSessions: [],
|
|
935
|
+
geminiSessions: [],
|
|
936
|
+
sessionMeta: {
|
|
937
|
+
hasMore: false,
|
|
938
|
+
total: 0
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
try {
|
|
943
|
+
if (providerLookups) {
|
|
944
|
+
project.codexSessions = providerLookups.codexSessionsByProjectPath?.get(normalizedProjectPath) || [];
|
|
945
|
+
project.opencodeSessions = providerLookups.opencodeSessionsByProjectPath?.get(normalizedProjectPath) || [];
|
|
946
|
+
project.geminiSessions = providerLookups.geminiSessionsByProjectPath?.get(normalizedProjectPath) || [];
|
|
947
|
+
} else {
|
|
948
|
+
const [codexSessions, opencodeSessions, geminiSessions] = await Promise.all([
|
|
949
|
+
getCodexSessions(definition.fullPath, { limit: 5 }),
|
|
950
|
+
getOpencodeSessions(definition.fullPath, { limit: 5 }),
|
|
951
|
+
getGeminiSessions(definition.fullPath, { limit: 5 })
|
|
952
|
+
]);
|
|
953
|
+
project.codexSessions = codexSessions;
|
|
954
|
+
project.opencodeSessions = opencodeSessions;
|
|
955
|
+
project.geminiSessions = geminiSessions;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (!definition.isManuallyAdded) {
|
|
959
|
+
const sessionResult = await getSessions(definition.name, 5, 0);
|
|
960
|
+
project.sessions = sessionResult.sessions || [];
|
|
961
|
+
project.sessionMeta = {
|
|
962
|
+
hasMore: sessionResult.hasMore,
|
|
963
|
+
total: sessionResult.total
|
|
737
964
|
};
|
|
738
965
|
}
|
|
966
|
+
} catch (error) {
|
|
967
|
+
console.warn(`Could not load session details for project ${definition.name}:`, error.message);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
try {
|
|
971
|
+
const taskMasterResult = await detectTaskMasterFolder(definition.fullPath);
|
|
972
|
+
const taskMasterStatus = definition.isManuallyAdded
|
|
973
|
+
? (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles ? 'taskmaster-only' : 'not-configured')
|
|
974
|
+
: (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles ? 'configured' : 'not-configured');
|
|
975
|
+
|
|
976
|
+
project.taskmaster = {
|
|
977
|
+
status: taskMasterStatus,
|
|
978
|
+
hasTaskmaster: taskMasterResult.hasTaskmaster,
|
|
979
|
+
hasEssentialFiles: taskMasterResult.hasEssentialFiles,
|
|
980
|
+
metadata: taskMasterResult.metadata
|
|
981
|
+
};
|
|
982
|
+
} catch (error) {
|
|
983
|
+
console.warn(`TaskMaster detection failed for project ${definition.name}:`, error.message);
|
|
984
|
+
project.taskmaster = {
|
|
985
|
+
status: 'error',
|
|
986
|
+
hasTaskmaster: false,
|
|
987
|
+
hasEssentialFiles: false,
|
|
988
|
+
error: error.message
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
return project;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
async function getProjectDetails(projectName) {
|
|
996
|
+
const projectList = await getProjectsList();
|
|
997
|
+
const definition = projectList.find((project) => project.name === projectName);
|
|
739
998
|
|
|
740
|
-
|
|
999
|
+
if (!definition) {
|
|
1000
|
+
throw new Error(`Project not found: ${projectName}`);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return buildProjectFromDefinition(definition);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
async function getProjects(progressCallback = null) {
|
|
1007
|
+
const projects = [];
|
|
1008
|
+
const { projectDefinitions, totalProjects } = await collectProjectDefinitions(progressCallback);
|
|
1009
|
+
|
|
1010
|
+
const uniqueProjectPaths = Array.from(new Set(
|
|
1011
|
+
projectDefinitions
|
|
1012
|
+
.map((definition) => normalizeComparableProjectPath(definition.fullPath))
|
|
1013
|
+
.filter(Boolean)
|
|
1014
|
+
));
|
|
1015
|
+
|
|
1016
|
+
const [codexSessionsByProjectPath, geminiSessionsByProjectPath, opencodeSessionsByProjectPath] = await Promise.all([
|
|
1017
|
+
buildCodexSessionsLookup(uniqueProjectPaths, { limit: 5 }),
|
|
1018
|
+
buildGeminiSessionsLookup(uniqueProjectPaths, { limit: 5 }),
|
|
1019
|
+
buildOpencodeSessionsLookup(uniqueProjectPaths, { limit: 5 })
|
|
1020
|
+
]);
|
|
1021
|
+
|
|
1022
|
+
for (const definition of projectDefinitions) {
|
|
1023
|
+
projects.push(await buildProjectFromDefinition(definition, {
|
|
1024
|
+
codexSessionsByProjectPath,
|
|
1025
|
+
opencodeSessionsByProjectPath,
|
|
1026
|
+
geminiSessionsByProjectPath
|
|
1027
|
+
}));
|
|
741
1028
|
}
|
|
742
1029
|
|
|
743
1030
|
// Emit completion after all projects (including manual) are processed
|
|
@@ -880,6 +1167,43 @@ async function getSessions(projectName, limit = 5, offset = 0) {
|
|
|
880
1167
|
}
|
|
881
1168
|
}
|
|
882
1169
|
|
|
1170
|
+
async function getClaudeSessionMetadata(sessionId) {
|
|
1171
|
+
try {
|
|
1172
|
+
const normalizedSessionId = String(sessionId || '').trim();
|
|
1173
|
+
if (!normalizedSessionId) {
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const claudeProjectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
1178
|
+
const jsonlFiles = await findFilesRecursively(
|
|
1179
|
+
claudeProjectsDir,
|
|
1180
|
+
(entryName) => entryName.endsWith('.jsonl') && !entryName.startsWith('agent-')
|
|
1181
|
+
);
|
|
1182
|
+
|
|
1183
|
+
for (const filePath of jsonlFiles) {
|
|
1184
|
+
try {
|
|
1185
|
+
const result = await parseJsonlSessions(filePath);
|
|
1186
|
+
const matchedSession = (result?.sessions || []).find((session) => session?.id === normalizedSessionId);
|
|
1187
|
+
if (matchedSession) {
|
|
1188
|
+
return {
|
|
1189
|
+
...matchedSession,
|
|
1190
|
+
filePath,
|
|
1191
|
+
projectName: path.basename(path.dirname(filePath)),
|
|
1192
|
+
provider: 'claude'
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
} catch {}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
return null;
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
if (error?.code !== 'ENOENT') {
|
|
1201
|
+
console.error(`Error reading Claude session metadata for ${sessionId}:`, error);
|
|
1202
|
+
}
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
883
1207
|
async function parseJsonlSessions(filePath) {
|
|
884
1208
|
const sessions = new Map();
|
|
885
1209
|
const entries = [];
|
|
@@ -1107,14 +1431,27 @@ async function getSessionMessages(projectName, sessionId, limit = null, offset =
|
|
|
1107
1431
|
// Rename a project's display name
|
|
1108
1432
|
async function renameProject(projectName, newDisplayName) {
|
|
1109
1433
|
const config = await loadProjectConfig();
|
|
1434
|
+
const existingConfig = config[projectName] && typeof config[projectName] === 'object'
|
|
1435
|
+
? config[projectName]
|
|
1436
|
+
: {};
|
|
1437
|
+
const trimmedDisplayName = typeof newDisplayName === 'string'
|
|
1438
|
+
? newDisplayName.trim()
|
|
1439
|
+
: '';
|
|
1110
1440
|
|
|
1111
|
-
if (!
|
|
1112
|
-
// Remove custom name
|
|
1113
|
-
|
|
1441
|
+
if (!trimmedDisplayName) {
|
|
1442
|
+
// Remove only the custom display name while preserving other metadata such as
|
|
1443
|
+
// manuallyAdded/originalPath so externally added projects do not disappear.
|
|
1444
|
+
const { displayName: _removedDisplayName, ...remainingConfig } = existingConfig;
|
|
1445
|
+
|
|
1446
|
+
if (Object.keys(remainingConfig).length === 0) {
|
|
1447
|
+
delete config[projectName];
|
|
1448
|
+
} else {
|
|
1449
|
+
config[projectName] = remainingConfig;
|
|
1450
|
+
}
|
|
1114
1451
|
} else {
|
|
1115
|
-
// Set custom display name
|
|
1116
1452
|
config[projectName] = {
|
|
1117
|
-
|
|
1453
|
+
...existingConfig,
|
|
1454
|
+
displayName: trimmedDisplayName
|
|
1118
1455
|
};
|
|
1119
1456
|
}
|
|
1120
1457
|
|
|
@@ -1252,7 +1589,41 @@ async function addProjectManually(projectPath, displayName = null) {
|
|
|
1252
1589
|
const projectDir = path.join(os.homedir(), '.claude', 'projects', projectName);
|
|
1253
1590
|
|
|
1254
1591
|
if (config[projectName]) {
|
|
1255
|
-
|
|
1592
|
+
const existingConfig = config[projectName] && typeof config[projectName] === 'object'
|
|
1593
|
+
? config[projectName]
|
|
1594
|
+
: {};
|
|
1595
|
+
const hasManualMetadata = Boolean(existingConfig.manuallyAdded || existingConfig.originalPath || existingConfig.path);
|
|
1596
|
+
|
|
1597
|
+
if (hasManualMetadata) {
|
|
1598
|
+
throw new Error(`Project already configured for path: ${absolutePath}`);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// Recover historical broken entries that lost their manual-project metadata
|
|
1602
|
+
// during a rename, which otherwise makes the project invisible in the sidebar
|
|
1603
|
+
// while still blocking re-adding the same workspace.
|
|
1604
|
+
config[projectName] = {
|
|
1605
|
+
...existingConfig,
|
|
1606
|
+
manuallyAdded: true,
|
|
1607
|
+
originalPath: absolutePath
|
|
1608
|
+
};
|
|
1609
|
+
|
|
1610
|
+
if (displayName) {
|
|
1611
|
+
config[projectName].displayName = displayName;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
await saveProjectConfig(config);
|
|
1615
|
+
|
|
1616
|
+
return {
|
|
1617
|
+
name: projectName,
|
|
1618
|
+
path: absolutePath,
|
|
1619
|
+
fullPath: absolutePath,
|
|
1620
|
+
displayName: config[projectName].displayName || await generateDisplayName(projectName, absolutePath),
|
|
1621
|
+
isManuallyAdded: true,
|
|
1622
|
+
sessions: [],
|
|
1623
|
+
codexSessions: [],
|
|
1624
|
+
opencodeSessions: [],
|
|
1625
|
+
geminiSessions: []
|
|
1626
|
+
};
|
|
1256
1627
|
}
|
|
1257
1628
|
|
|
1258
1629
|
// Allow adding projects even if the directory exists - this enables tracking
|
|
@@ -1289,7 +1660,7 @@ async function getCodexSessions(projectPath, options = {}) {
|
|
|
1289
1660
|
const sessions = [];
|
|
1290
1661
|
const normalizedProjectPath = normalizeComparableProjectPath(projectPath);
|
|
1291
1662
|
const jsonlFiles = await findFilesRecursively(
|
|
1292
|
-
|
|
1663
|
+
getCodexSessionsDir(),
|
|
1293
1664
|
(entryName) => entryName.endsWith('.jsonl')
|
|
1294
1665
|
);
|
|
1295
1666
|
|
|
@@ -1299,6 +1670,7 @@ async function getCodexSessions(projectPath, options = {}) {
|
|
|
1299
1670
|
const sessionData = await parseCodexSessionFile(filePath);
|
|
1300
1671
|
|
|
1301
1672
|
if (sessionData && normalizeComparableProjectPath(sessionData.cwd) === normalizedProjectPath) {
|
|
1673
|
+
cacheCodexSessionFilePath(sessionData.id, filePath);
|
|
1302
1674
|
sessions.push({
|
|
1303
1675
|
id: sessionData.id,
|
|
1304
1676
|
summary: sessionData.summary || 'Codex Session',
|
|
@@ -1327,15 +1699,17 @@ async function getCodexSessions(projectPath, options = {}) {
|
|
|
1327
1699
|
}
|
|
1328
1700
|
}
|
|
1329
1701
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1702
|
+
function getOpencodeSessionDirCandidates() {
|
|
1703
|
+
return [
|
|
1704
|
+
path.join(os.homedir(), '.opencode', 'sessions'),
|
|
1705
|
+
path.join(os.homedir(), '.config', 'opencode', 'sessions')
|
|
1706
|
+
];
|
|
1707
|
+
}
|
|
1334
1708
|
|
|
1335
1709
|
async function findOpencodeSessionFiles() {
|
|
1336
1710
|
const files = [];
|
|
1337
1711
|
|
|
1338
|
-
for (const baseDir of
|
|
1712
|
+
for (const baseDir of getOpencodeSessionDirCandidates()) {
|
|
1339
1713
|
const discovered = await findFilesRecursively(
|
|
1340
1714
|
baseDir,
|
|
1341
1715
|
(entryName, _fullPath, entry) => entry.isFile() && entryName.endsWith('.jsonl')
|
|
@@ -1600,6 +1974,34 @@ async function findOpencodeSessionFileById(sessionId) {
|
|
|
1600
1974
|
return null;
|
|
1601
1975
|
}
|
|
1602
1976
|
|
|
1977
|
+
async function getOpencodeSessionMetadata(sessionId) {
|
|
1978
|
+
try {
|
|
1979
|
+
const normalizedSessionId = String(sessionId || '').trim();
|
|
1980
|
+
if (!normalizedSessionId) {
|
|
1981
|
+
return null;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
const sessionFilePath = await findOpencodeSessionFileById(normalizedSessionId);
|
|
1985
|
+
if (!sessionFilePath) {
|
|
1986
|
+
return null;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
const sessionData = await parseOpencodeSessionFile(sessionFilePath);
|
|
1990
|
+
if (!sessionData) {
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
return {
|
|
1995
|
+
...sessionData,
|
|
1996
|
+
filePath: sessionFilePath,
|
|
1997
|
+
provider: 'opencode'
|
|
1998
|
+
};
|
|
1999
|
+
} catch (error) {
|
|
2000
|
+
console.error(`Error reading OpenCode session metadata for ${sessionId}:`, error);
|
|
2001
|
+
return null;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
|
|
1603
2005
|
async function getOpencodeSessionMessages(sessionId, limit = null, offset = 0) {
|
|
1604
2006
|
try {
|
|
1605
2007
|
const sessionFilePath = await findOpencodeSessionFileById(sessionId);
|
|
@@ -1730,43 +2132,42 @@ async function deleteOpencodeSession(sessionId) {
|
|
|
1730
2132
|
}
|
|
1731
2133
|
}
|
|
1732
2134
|
|
|
1733
|
-
async function
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
2135
|
+
async function findGeminiSessionFiles() {
|
|
2136
|
+
return findFilesRecursively(
|
|
2137
|
+
path.join(os.homedir(), '.gemini', 'tmp'),
|
|
2138
|
+
(entryName, fullPath) => (
|
|
2139
|
+
entryName.endsWith('.json') &&
|
|
2140
|
+
/[\\/]chats[\\/]/.test(fullPath)
|
|
2141
|
+
)
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
1742
2144
|
|
|
1743
|
-
|
|
1744
|
-
|
|
2145
|
+
function inferGeminiProjectHash(filePath, parsedSession = null) {
|
|
2146
|
+
const explicitHash = typeof parsedSession?.projectHash === 'string'
|
|
2147
|
+
? parsedSession.projectHash.trim()
|
|
2148
|
+
: '';
|
|
2149
|
+
if (explicitHash) {
|
|
2150
|
+
return explicitHash.toLowerCase();
|
|
2151
|
+
}
|
|
1745
2152
|
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
}
|
|
2153
|
+
const parentDir = path.basename(path.dirname(path.dirname(filePath)));
|
|
2154
|
+
if (/^[a-f0-9]{64}$/i.test(parentDir)) {
|
|
2155
|
+
return parentDir.toLowerCase();
|
|
2156
|
+
}
|
|
1751
2157
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
.filter(e => e.isFile() && e.name.endsWith('.json'))
|
|
1755
|
-
.map(e => path.join(chatsDir, e.name));
|
|
2158
|
+
return '';
|
|
2159
|
+
}
|
|
1756
2160
|
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
} catch (error) {
|
|
1764
|
-
console.warn(`Could not parse Gemini session file ${filePath}:`, error.message);
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
2161
|
+
async function getGeminiSessions(projectPath, options = {}) {
|
|
2162
|
+
const { limit = 5 } = options;
|
|
2163
|
+
const normalizedProjectPath = normalizeComparableProjectPath(projectPath);
|
|
2164
|
+
if (!normalizedProjectPath) {
|
|
2165
|
+
return [];
|
|
2166
|
+
}
|
|
1767
2167
|
|
|
1768
|
-
|
|
1769
|
-
|
|
2168
|
+
try {
|
|
2169
|
+
const lookup = await buildGeminiSessionsLookup([normalizedProjectPath], { limit });
|
|
2170
|
+
return lookup.get(normalizedProjectPath) || [];
|
|
1770
2171
|
} catch (error) {
|
|
1771
2172
|
console.error('Error fetching Gemini sessions:', error);
|
|
1772
2173
|
return [];
|
|
@@ -1805,11 +2206,36 @@ async function parseGeminiSessionFile(filePath) {
|
|
|
1805
2206
|
lastActivity,
|
|
1806
2207
|
createdAt: data?.startTime || lastActivity,
|
|
1807
2208
|
model: messages.filter(m => m?.model).slice(-1)[0]?.model || null,
|
|
2209
|
+
projectHash: inferGeminiProjectHash(filePath, data),
|
|
1808
2210
|
provider: 'gemini',
|
|
1809
2211
|
filePath
|
|
1810
2212
|
};
|
|
1811
2213
|
}
|
|
1812
2214
|
|
|
2215
|
+
async function getGeminiSessionMetadata(sessionId) {
|
|
2216
|
+
try {
|
|
2217
|
+
const normalizedSessionId = String(sessionId || '').trim();
|
|
2218
|
+
if (!normalizedSessionId) {
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
const sessionFiles = await findGeminiSessionFiles();
|
|
2223
|
+
for (const filePath of sessionFiles) {
|
|
2224
|
+
try {
|
|
2225
|
+
const sessionData = await parseGeminiSessionFile(filePath);
|
|
2226
|
+
if (sessionData?.id === normalizedSessionId) {
|
|
2227
|
+
return sessionData;
|
|
2228
|
+
}
|
|
2229
|
+
} catch {}
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
return null;
|
|
2233
|
+
} catch (error) {
|
|
2234
|
+
console.error(`Error reading Gemini session metadata for ${sessionId}:`, error);
|
|
2235
|
+
return null;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
1813
2239
|
async function buildCodexSessionsLookup(projectPaths, options = {}) {
|
|
1814
2240
|
const { limit = 5 } = options;
|
|
1815
2241
|
const normalizedProjectPaths = Array.from(new Set(
|
|
@@ -1823,7 +2249,7 @@ async function buildCodexSessionsLookup(projectPaths, options = {}) {
|
|
|
1823
2249
|
const cacheKey = JSON.stringify({ projectPaths: normalizedProjectPaths, limit });
|
|
1824
2250
|
return getCachedProviderSessionLookup('codex', cacheKey, async () => {
|
|
1825
2251
|
const jsonlFiles = await findFilesRecursively(
|
|
1826
|
-
|
|
2252
|
+
getCodexSessionsDir(),
|
|
1827
2253
|
(entryName) => entryName.endsWith('.jsonl')
|
|
1828
2254
|
);
|
|
1829
2255
|
|
|
@@ -1832,6 +2258,7 @@ async function buildCodexSessionsLookup(projectPaths, options = {}) {
|
|
|
1832
2258
|
try {
|
|
1833
2259
|
const sessionData = await parseCodexSessionFile(filePath);
|
|
1834
2260
|
if (sessionData?.id) {
|
|
2261
|
+
cacheCodexSessionFilePath(sessionData.id, filePath);
|
|
1835
2262
|
sessions.push({
|
|
1836
2263
|
id: sessionData.id,
|
|
1837
2264
|
summary: sessionData.summary || 'Codex Session',
|
|
@@ -1884,73 +2311,58 @@ async function buildGeminiSessionsLookup(projectPaths, options = {}) {
|
|
|
1884
2311
|
(projectPaths || []).map(normalizeComparableProjectPath).filter(Boolean)
|
|
1885
2312
|
));
|
|
1886
2313
|
|
|
2314
|
+
if (normalizedProjectPaths.length === 0) {
|
|
2315
|
+
return new Map();
|
|
2316
|
+
}
|
|
2317
|
+
|
|
1887
2318
|
const cacheKey = JSON.stringify({ projectPaths: normalizedProjectPaths, limit });
|
|
1888
2319
|
return getCachedProviderSessionLookup('gemini', cacheKey, async () => {
|
|
1889
|
-
const
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2320
|
+
const projectHashToPath = new Map(
|
|
2321
|
+
normalizedProjectPaths.map((normalizedProjectPath) => ([
|
|
2322
|
+
crypto.createHash('sha256').update(normalizedProjectPath).digest('hex'),
|
|
2323
|
+
normalizedProjectPath
|
|
2324
|
+
]))
|
|
2325
|
+
);
|
|
2326
|
+
const sessionsByProjectPath = new Map(
|
|
2327
|
+
normalizedProjectPaths.map((normalizedProjectPath) => [normalizedProjectPath, []])
|
|
2328
|
+
);
|
|
2329
|
+
const sessionFiles = await findGeminiSessionFiles();
|
|
1894
2330
|
|
|
2331
|
+
for (const filePath of sessionFiles) {
|
|
1895
2332
|
try {
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2333
|
+
const sessionData = await parseGeminiSessionFile(filePath);
|
|
2334
|
+
if (!sessionData?.id) {
|
|
2335
|
+
continue;
|
|
2336
|
+
}
|
|
1900
2337
|
|
|
1901
|
-
const
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
const sessionData = await parseGeminiSessionFile(filePath);
|
|
1905
|
-
if (sessionData?.id) {
|
|
1906
|
-
sessions.push(sessionData);
|
|
1907
|
-
}
|
|
1908
|
-
} catch (error) {
|
|
1909
|
-
console.warn(`Could not parse Gemini session file ${filePath}:`, error.message);
|
|
1910
|
-
}
|
|
2338
|
+
const normalizedProjectPath = projectHashToPath.get(sessionData.projectHash || '');
|
|
2339
|
+
if (!normalizedProjectPath) {
|
|
2340
|
+
continue;
|
|
1911
2341
|
}
|
|
1912
2342
|
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
limit > 0 ? sessions.slice(0, limit) : sessions
|
|
1917
|
-
);
|
|
1918
|
-
} catch (_) {
|
|
1919
|
-
sessionsByProjectPath.set(normalizedProjectPath, []);
|
|
2343
|
+
sessionsByProjectPath.get(normalizedProjectPath)?.push(sessionData);
|
|
2344
|
+
} catch (error) {
|
|
2345
|
+
console.warn(`Could not parse Gemini session file ${filePath}:`, error.message);
|
|
1920
2346
|
}
|
|
1921
2347
|
}
|
|
1922
2348
|
|
|
2349
|
+
for (const [normalizedProjectPath, sessions] of sessionsByProjectPath.entries()) {
|
|
2350
|
+
sessions.sort((a, b) => new Date(b.lastActivity || 0) - new Date(a.lastActivity || 0));
|
|
2351
|
+
sessionsByProjectPath.set(
|
|
2352
|
+
normalizedProjectPath,
|
|
2353
|
+
limit > 0 ? sessions.slice(0, limit) : sessions
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
1923
2357
|
return sessionsByProjectPath;
|
|
1924
2358
|
});
|
|
1925
2359
|
}
|
|
1926
2360
|
|
|
1927
2361
|
async function getGeminiSessionMessages(sessionId, limit = null, offset = 0) {
|
|
1928
2362
|
try {
|
|
1929
|
-
const
|
|
1930
|
-
const
|
|
1931
|
-
try {
|
|
1932
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1933
|
-
for (const entry of entries) {
|
|
1934
|
-
const fullPath = path.join(dir, entry.name);
|
|
1935
|
-
if (entry.isDirectory()) {
|
|
1936
|
-
const found = await findSessionFile(fullPath);
|
|
1937
|
-
if (found) return found;
|
|
1938
|
-
continue;
|
|
1939
|
-
}
|
|
1940
|
-
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
1941
|
-
try {
|
|
1942
|
-
const raw = await fs.readFile(fullPath, 'utf8');
|
|
1943
|
-
const parsed = JSON.parse(raw);
|
|
1944
|
-
if (parsed?.sessionId === sessionId) {
|
|
1945
|
-
return fullPath;
|
|
1946
|
-
}
|
|
1947
|
-
} catch {}
|
|
1948
|
-
}
|
|
1949
|
-
} catch {}
|
|
1950
|
-
return null;
|
|
1951
|
-
};
|
|
2363
|
+
const sessionMetadata = await getGeminiSessionMetadata(sessionId);
|
|
2364
|
+
const sessionFilePath = sessionMetadata?.filePath || null;
|
|
1952
2365
|
|
|
1953
|
-
const sessionFilePath = await findSessionFile(geminiTmpDir);
|
|
1954
2366
|
if (!sessionFilePath) {
|
|
1955
2367
|
return { messages: [], total: 0, hasMore: false };
|
|
1956
2368
|
}
|
|
@@ -2100,6 +2512,7 @@ async function parseCodexSessionFile(filePath) {
|
|
|
2100
2512
|
}
|
|
2101
2513
|
|
|
2102
2514
|
if (sessionMeta) {
|
|
2515
|
+
cacheCodexSessionFilePath(sessionMeta.id, filePath);
|
|
2103
2516
|
return {
|
|
2104
2517
|
...sessionMeta,
|
|
2105
2518
|
model: lastResolvedModel || sessionMeta.model || null,
|
|
@@ -2122,35 +2535,18 @@ async function parseCodexSessionFile(filePath) {
|
|
|
2122
2535
|
// Get messages for a specific Codex session
|
|
2123
2536
|
async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
2124
2537
|
try {
|
|
2125
|
-
const
|
|
2126
|
-
|
|
2127
|
-
// Find the session file by searching for the session ID
|
|
2128
|
-
const findSessionFile = async (dir) => {
|
|
2129
|
-
try {
|
|
2130
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2131
|
-
for (const entry of entries) {
|
|
2132
|
-
const fullPath = path.join(dir, entry.name);
|
|
2133
|
-
if (entry.isDirectory()) {
|
|
2134
|
-
const found = await findSessionFile(fullPath);
|
|
2135
|
-
if (found) return found;
|
|
2136
|
-
} else if (entry.name.includes(sessionId) && entry.name.endsWith('.jsonl')) {
|
|
2137
|
-
return fullPath;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
} catch (error) {
|
|
2141
|
-
// Skip directories we can't read
|
|
2142
|
-
}
|
|
2143
|
-
return null;
|
|
2144
|
-
};
|
|
2145
|
-
|
|
2146
|
-
const sessionFilePath = await findSessionFile(codexSessionsDir);
|
|
2538
|
+
const sessionFilePath = await resolveCodexSessionFile(sessionId);
|
|
2147
2539
|
|
|
2148
2540
|
if (!sessionFilePath) {
|
|
2149
2541
|
console.warn(`Codex session file not found for session ${sessionId}`);
|
|
2150
2542
|
return { messages: [], total: 0, hasMore: false };
|
|
2151
2543
|
}
|
|
2152
2544
|
|
|
2545
|
+
const normalizedLimit = limit === null ? null : Math.max(0, Number(limit) || 0);
|
|
2546
|
+
const normalizedOffset = Math.max(0, Number(offset) || 0);
|
|
2547
|
+
const maxBufferedMessages = normalizedLimit === null ? null : normalizedLimit + normalizedOffset;
|
|
2153
2548
|
const messages = [];
|
|
2549
|
+
let total = 0;
|
|
2154
2550
|
let tokenUsage = null;
|
|
2155
2551
|
const fileStream = fsSync.createReadStream(sessionFilePath);
|
|
2156
2552
|
const rl = readline.createInterface({
|
|
@@ -2175,6 +2571,11 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2175
2571
|
.join('\n');
|
|
2176
2572
|
};
|
|
2177
2573
|
|
|
2574
|
+
const appendMessage = (message) => {
|
|
2575
|
+
total += 1;
|
|
2576
|
+
appendBoundedMessage(messages, message, maxBufferedMessages);
|
|
2577
|
+
};
|
|
2578
|
+
|
|
2178
2579
|
for await (const line of rl) {
|
|
2179
2580
|
if (line.trim()) {
|
|
2180
2581
|
try {
|
|
@@ -2200,7 +2601,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2200
2601
|
|
|
2201
2602
|
// Only add if there's actual content
|
|
2202
2603
|
if (visibleTextContent?.trim()) {
|
|
2203
|
-
|
|
2604
|
+
appendMessage({
|
|
2204
2605
|
type: role === 'user' ? 'user' : 'assistant',
|
|
2205
2606
|
timestamp: entry.timestamp,
|
|
2206
2607
|
message: {
|
|
@@ -2217,7 +2618,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2217
2618
|
.filter(Boolean)
|
|
2218
2619
|
.join('\n');
|
|
2219
2620
|
if (summaryText?.trim()) {
|
|
2220
|
-
|
|
2621
|
+
appendMessage({
|
|
2221
2622
|
type: 'thinking',
|
|
2222
2623
|
timestamp: entry.timestamp,
|
|
2223
2624
|
message: {
|
|
@@ -2243,7 +2644,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2243
2644
|
}
|
|
2244
2645
|
}
|
|
2245
2646
|
|
|
2246
|
-
|
|
2647
|
+
appendMessage({
|
|
2247
2648
|
type: 'tool_use',
|
|
2248
2649
|
timestamp: entry.timestamp,
|
|
2249
2650
|
toolName: toolName,
|
|
@@ -2253,7 +2654,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2253
2654
|
}
|
|
2254
2655
|
|
|
2255
2656
|
if (entry.type === 'response_item' && entry.payload?.type === 'function_call_output') {
|
|
2256
|
-
|
|
2657
|
+
appendMessage({
|
|
2257
2658
|
type: 'tool_result',
|
|
2258
2659
|
timestamp: entry.timestamp,
|
|
2259
2660
|
toolCallId: entry.payload.call_id,
|
|
@@ -2283,7 +2684,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2283
2684
|
}
|
|
2284
2685
|
}
|
|
2285
2686
|
|
|
2286
|
-
|
|
2687
|
+
appendMessage({
|
|
2287
2688
|
type: 'tool_use',
|
|
2288
2689
|
timestamp: entry.timestamp,
|
|
2289
2690
|
toolName: 'Edit',
|
|
@@ -2295,7 +2696,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2295
2696
|
toolCallId: entry.payload.call_id
|
|
2296
2697
|
});
|
|
2297
2698
|
} else {
|
|
2298
|
-
|
|
2699
|
+
appendMessage({
|
|
2299
2700
|
type: 'tool_use',
|
|
2300
2701
|
timestamp: entry.timestamp,
|
|
2301
2702
|
toolName: toolName,
|
|
@@ -2306,7 +2707,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2306
2707
|
}
|
|
2307
2708
|
|
|
2308
2709
|
if (entry.type === 'response_item' && entry.payload?.type === 'custom_tool_call_output') {
|
|
2309
|
-
|
|
2710
|
+
appendMessage({
|
|
2310
2711
|
type: 'tool_result',
|
|
2311
2712
|
timestamp: entry.timestamp,
|
|
2312
2713
|
toolCallId: entry.payload.call_id,
|
|
@@ -2320,29 +2721,31 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2320
2721
|
}
|
|
2321
2722
|
}
|
|
2322
2723
|
|
|
2323
|
-
// Sort by timestamp
|
|
2324
|
-
messages.sort((a, b) => new Date(a.timestamp || 0) - new Date(b.timestamp || 0));
|
|
2325
|
-
|
|
2326
|
-
const total = messages.length;
|
|
2327
|
-
|
|
2328
2724
|
// Apply pagination if limit is specified
|
|
2329
|
-
if (
|
|
2330
|
-
const
|
|
2331
|
-
const
|
|
2725
|
+
if (normalizedLimit !== null) {
|
|
2726
|
+
const endIndex = Math.max(0, messages.length - normalizedOffset);
|
|
2727
|
+
const startIndex = Math.max(0, endIndex - normalizedLimit);
|
|
2332
2728
|
const paginatedMessages = messages.slice(startIndex, endIndex);
|
|
2333
|
-
const hasMore =
|
|
2729
|
+
const hasMore = total > normalizedOffset + paginatedMessages.length;
|
|
2334
2730
|
|
|
2335
2731
|
return {
|
|
2336
2732
|
messages: paginatedMessages,
|
|
2337
2733
|
total,
|
|
2338
2734
|
hasMore,
|
|
2339
|
-
offset,
|
|
2340
|
-
limit,
|
|
2735
|
+
offset: normalizedOffset,
|
|
2736
|
+
limit: normalizedLimit,
|
|
2341
2737
|
tokenUsage
|
|
2342
2738
|
};
|
|
2343
2739
|
}
|
|
2344
2740
|
|
|
2345
|
-
return {
|
|
2741
|
+
return {
|
|
2742
|
+
messages,
|
|
2743
|
+
total,
|
|
2744
|
+
hasMore: false,
|
|
2745
|
+
offset: normalizedOffset,
|
|
2746
|
+
limit: normalizedLimit,
|
|
2747
|
+
tokenUsage
|
|
2748
|
+
};
|
|
2346
2749
|
|
|
2347
2750
|
} catch (error) {
|
|
2348
2751
|
console.error(`Error reading Codex session messages for ${sessionId}:`, error);
|
|
@@ -2350,37 +2753,45 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2350
2753
|
}
|
|
2351
2754
|
}
|
|
2352
2755
|
|
|
2353
|
-
async function
|
|
2354
|
-
|
|
2355
|
-
|
|
2756
|
+
async function getCodexSessionMetadata(sessionId) {
|
|
2757
|
+
if (!sessionId) {
|
|
2758
|
+
return null;
|
|
2759
|
+
}
|
|
2356
2760
|
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
for (const entry of entries) {
|
|
2362
|
-
const fullPath = path.join(dir, entry.name);
|
|
2363
|
-
if (entry.isDirectory()) {
|
|
2364
|
-
files.push(...await findJsonlFiles(fullPath));
|
|
2365
|
-
} else if (entry.name.endsWith('.jsonl')) {
|
|
2366
|
-
files.push(fullPath);
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
} catch (error) {}
|
|
2370
|
-
return files;
|
|
2371
|
-
};
|
|
2761
|
+
const sessionFilePath = await resolveCodexSessionFile(sessionId);
|
|
2762
|
+
if (!sessionFilePath) {
|
|
2763
|
+
return null;
|
|
2764
|
+
}
|
|
2372
2765
|
|
|
2373
|
-
|
|
2766
|
+
const sessionData = await parseCodexSessionFile(sessionFilePath);
|
|
2767
|
+
if (!sessionData) {
|
|
2768
|
+
return null;
|
|
2769
|
+
}
|
|
2374
2770
|
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2771
|
+
const metadata = {
|
|
2772
|
+
...sessionData,
|
|
2773
|
+
filePath: sessionFilePath,
|
|
2774
|
+
provider: 'codex'
|
|
2775
|
+
};
|
|
2776
|
+
|
|
2777
|
+
if (metadata.git === undefined) {
|
|
2778
|
+
delete metadata.git;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
return metadata;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
async function deleteCodexSession(sessionId) {
|
|
2785
|
+
try {
|
|
2786
|
+
const sessionFilePath = await resolveCodexSessionFile(sessionId);
|
|
2787
|
+
|
|
2788
|
+
if (!sessionFilePath) {
|
|
2789
|
+
throw new Error(`Codex session file not found for session ${sessionId}`);
|
|
2381
2790
|
}
|
|
2382
2791
|
|
|
2383
|
-
|
|
2792
|
+
await fs.unlink(sessionFilePath);
|
|
2793
|
+
codexSessionFileCache.delete(sessionId);
|
|
2794
|
+
return true;
|
|
2384
2795
|
} catch (error) {
|
|
2385
2796
|
console.error(`Error deleting Codex session ${sessionId}:`, error);
|
|
2386
2797
|
throw error;
|
|
@@ -2389,7 +2800,10 @@ async function deleteCodexSession(sessionId) {
|
|
|
2389
2800
|
|
|
2390
2801
|
export {
|
|
2391
2802
|
getProjects,
|
|
2803
|
+
getProjectsList,
|
|
2804
|
+
getProjectDetails,
|
|
2392
2805
|
getSessions,
|
|
2806
|
+
getClaudeSessionMetadata,
|
|
2393
2807
|
getSessionMessages,
|
|
2394
2808
|
parseJsonlSessions,
|
|
2395
2809
|
renameProject,
|
|
@@ -2402,11 +2816,15 @@ export {
|
|
|
2402
2816
|
extractProjectDirectory,
|
|
2403
2817
|
clearProjectDirectoryCache,
|
|
2404
2818
|
clearProviderSessionLookupCaches,
|
|
2819
|
+
findCodexSessionFilePathBySessionIdHint,
|
|
2405
2820
|
getCodexSessions,
|
|
2406
2821
|
getCodexSessionMessages,
|
|
2822
|
+
getCodexSessionMetadata,
|
|
2407
2823
|
getOpencodeSessions,
|
|
2824
|
+
getOpencodeSessionMetadata,
|
|
2408
2825
|
getOpencodeSessionMessages,
|
|
2409
2826
|
getGeminiSessions,
|
|
2827
|
+
getGeminiSessionMetadata,
|
|
2410
2828
|
getGeminiSessionMessages,
|
|
2411
2829
|
deleteCodexSession,
|
|
2412
2830
|
deleteOpencodeSession
|