@axhub/genie 0.2.8 → 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/ReviewApp-C9K--AQE.js +1 -0
- package/dist/assets/{_basePickBy-CqJbRZ9y.js → _basePickBy-DR_8uFCo.js} +1 -1
- package/dist/assets/{_baseUniq-BS8YH8jO.js → _baseUniq-D0njlQ_7.js} +1 -1
- package/dist/assets/{arc-BBmKEN-S.js → arc-CKlr_Rec.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-N5lcb82R.js → architectureDiagram-2XIMDMQ5-BmO_uLUH.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-DTMwHuLn.js → blockDiagram-WCTKOSBZ-DhAeO-56.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-BTKlkXI9.js → c4Diagram-IC4MRINW-C67kFoXx.js} +1 -1
- package/dist/assets/channel-V3MBjKys.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-DUdoTxAc.js → chunk-4BX2VUAB-mLLagvJi.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Bm_92xe4.js → chunk-55IACEB6-Lx-hOjlM.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CGW0g62g.js → chunk-FMBD7UC4-Bt-XmVUV.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-DYkTH3w1.js → chunk-JSJVCQXG-Cya6gaDV.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-C9oTlISU.js → chunk-KX2RTZJC-Bd7Ig6tF.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-CM50ygWP.js → chunk-NQ4KR5QH-5UAE0Vg-.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-7dzpYeNJ.js → chunk-QZHKN3VN-BAxZ8m7w.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-Cm9nQrsr.js → chunk-WL4C6EOR-DjDPvUUP.js} +1 -1
- 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-Ccp_p0JZ.js → cose-bilkent-S5V4N54A-D-60XrkJ.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-fBwTLUp9.js → dagre-KLK3FWXG-bqu3ZS4K.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-CeNVmFUp.js → diagram-E7M64L7V-BueeqoYm.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-CtavyLGa.js → diagram-IFDJBPK2-D4fDv2E7.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-CpQTjQwc.js → diagram-P4PSJMXO-WqipY3fN.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-B8R5vwhd.js → erDiagram-INFDFZHY-D0oVnO-x.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-BvkVVwIQ.js → flowDiagram-PKNHOUZH-DzbGyxrr.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-DOu3hSNa.js → ganttDiagram-A5KZAMGK-BwhbbgCP.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-C7zT67YE.js → gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js} +1 -1
- package/dist/assets/{graph-D11wiwHo.js → graph-DzKos-N0.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-Babpthg-.js → highlighted-body-TPN3WLV5-CKDMgz3X.js} +1 -1
- package/dist/assets/{index-DFxzgWoO.js → index-DiQlHzGj.js} +2 -2
- package/dist/assets/index-Drat2nB9.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-BmA7IpQG.js → infoDiagram-LFFYTUFH-BFicZbTf.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-BEquZd3E.js → ishikawaDiagram-PHBUUO56-CtihxDxl.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-BfemGz7f.js → journeyDiagram-4ABVD52K-Du00J8_d.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-CWja3mln.js → kanban-definition-K7BYSVSG-BJi9S0iQ.js} +1 -1
- package/dist/assets/{layout-BLUNf-PJ.js → layout-B80Sityu.js} +1 -1
- package/dist/assets/{linear-DukIV_Xv.js → linear-sRQLOf5H.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-SgtM28qI.js → mermaid-O7DHMXV3-CBuVs4eJ.js} +6 -6
- package/dist/assets/{mindmap-definition-YRQLILUH-4UjqXITU.js → mindmap-definition-YRQLILUH-C5IL_xi-.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-8AxqJd0M.js → pieDiagram-SKSYHLDU-CeTwlJ8z.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-D60m8V8r.js → quadrantDiagram-337W2JSQ-COfUcLWt.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-zqh9jBVf.js → requirementDiagram-Z7DCOOCP-DSb-CJ5B.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-CDZILTLI.js → sankeyDiagram-WA2Y5GQK-8jtuVb45.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-7BReFd0L.js → sequenceDiagram-2WXFIKYE-C2VpkMwA.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-HPTVdIG4.js → stateDiagram-RAJIS63D-fmwMqxxc.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-CTVllFgr.js → timeline-definition-YZTLITO2-Dx1hP5lg.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-BtyxboJZ.js → treemap-KZPCXAKY-CkLOdYCZ.js} +1 -1
- package/dist/assets/{vendor-codemirror-Dz7_EqNA.js → vendor-codemirror-BxPY6emf.js} +1 -1
- package/dist/assets/{vendor-react-Cpt6D04s.js → vendor-react-xmA_f8ig.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-D96ZI6Mg.js → vennDiagram-LZ73GAT5-D6KWcnln.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-eRk-39YO.js → xychartDiagram-JWTSCODW-6fh6qmzN.js} +1 -1
- package/dist/index.html +5 -5
- package/package.json +3 -3
- package/server/acp-runtime/client.js +9 -2
- package/server/acp-runtime/session-store.js +4 -4
- package/server/cli.js +23 -2
- package/server/external-agent/service.js +24 -6
- package/server/external-agent/ws.js +63 -3
- package/server/index.js +34 -5
- package/server/projects.js +536 -161
- 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/runtimeState.js +8 -0
- package/shared/conversationEvents.js +78 -14
- package/dist/assets/App-CTKZtqB1.js +0 -460
- package/dist/assets/ReviewApp-DM6BNAzR.js +0 -1
- package/dist/assets/channel-1oJBvF-0.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +0 -1
- package/dist/assets/clone-CinxIlEu.js +0 -1
- package/dist/assets/index-YCFGDVKw.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +0 -1
- 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/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);
|
|
998
|
+
|
|
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
|
+
));
|
|
739
1015
|
|
|
740
|
-
|
|
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 = [];
|
|
@@ -1336,7 +1660,7 @@ async function getCodexSessions(projectPath, options = {}) {
|
|
|
1336
1660
|
const sessions = [];
|
|
1337
1661
|
const normalizedProjectPath = normalizeComparableProjectPath(projectPath);
|
|
1338
1662
|
const jsonlFiles = await findFilesRecursively(
|
|
1339
|
-
|
|
1663
|
+
getCodexSessionsDir(),
|
|
1340
1664
|
(entryName) => entryName.endsWith('.jsonl')
|
|
1341
1665
|
);
|
|
1342
1666
|
|
|
@@ -1346,6 +1670,7 @@ async function getCodexSessions(projectPath, options = {}) {
|
|
|
1346
1670
|
const sessionData = await parseCodexSessionFile(filePath);
|
|
1347
1671
|
|
|
1348
1672
|
if (sessionData && normalizeComparableProjectPath(sessionData.cwd) === normalizedProjectPath) {
|
|
1673
|
+
cacheCodexSessionFilePath(sessionData.id, filePath);
|
|
1349
1674
|
sessions.push({
|
|
1350
1675
|
id: sessionData.id,
|
|
1351
1676
|
summary: sessionData.summary || 'Codex Session',
|
|
@@ -1374,15 +1699,17 @@ async function getCodexSessions(projectPath, options = {}) {
|
|
|
1374
1699
|
}
|
|
1375
1700
|
}
|
|
1376
1701
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1702
|
+
function getOpencodeSessionDirCandidates() {
|
|
1703
|
+
return [
|
|
1704
|
+
path.join(os.homedir(), '.opencode', 'sessions'),
|
|
1705
|
+
path.join(os.homedir(), '.config', 'opencode', 'sessions')
|
|
1706
|
+
];
|
|
1707
|
+
}
|
|
1381
1708
|
|
|
1382
1709
|
async function findOpencodeSessionFiles() {
|
|
1383
1710
|
const files = [];
|
|
1384
1711
|
|
|
1385
|
-
for (const baseDir of
|
|
1712
|
+
for (const baseDir of getOpencodeSessionDirCandidates()) {
|
|
1386
1713
|
const discovered = await findFilesRecursively(
|
|
1387
1714
|
baseDir,
|
|
1388
1715
|
(entryName, _fullPath, entry) => entry.isFile() && entryName.endsWith('.jsonl')
|
|
@@ -1647,6 +1974,34 @@ async function findOpencodeSessionFileById(sessionId) {
|
|
|
1647
1974
|
return null;
|
|
1648
1975
|
}
|
|
1649
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
|
+
|
|
1650
2005
|
async function getOpencodeSessionMessages(sessionId, limit = null, offset = 0) {
|
|
1651
2006
|
try {
|
|
1652
2007
|
const sessionFilePath = await findOpencodeSessionFileById(sessionId);
|
|
@@ -1857,6 +2212,30 @@ async function parseGeminiSessionFile(filePath) {
|
|
|
1857
2212
|
};
|
|
1858
2213
|
}
|
|
1859
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
|
+
|
|
1860
2239
|
async function buildCodexSessionsLookup(projectPaths, options = {}) {
|
|
1861
2240
|
const { limit = 5 } = options;
|
|
1862
2241
|
const normalizedProjectPaths = Array.from(new Set(
|
|
@@ -1870,7 +2249,7 @@ async function buildCodexSessionsLookup(projectPaths, options = {}) {
|
|
|
1870
2249
|
const cacheKey = JSON.stringify({ projectPaths: normalizedProjectPaths, limit });
|
|
1871
2250
|
return getCachedProviderSessionLookup('codex', cacheKey, async () => {
|
|
1872
2251
|
const jsonlFiles = await findFilesRecursively(
|
|
1873
|
-
|
|
2252
|
+
getCodexSessionsDir(),
|
|
1874
2253
|
(entryName) => entryName.endsWith('.jsonl')
|
|
1875
2254
|
);
|
|
1876
2255
|
|
|
@@ -1879,6 +2258,7 @@ async function buildCodexSessionsLookup(projectPaths, options = {}) {
|
|
|
1879
2258
|
try {
|
|
1880
2259
|
const sessionData = await parseCodexSessionFile(filePath);
|
|
1881
2260
|
if (sessionData?.id) {
|
|
2261
|
+
cacheCodexSessionFilePath(sessionData.id, filePath);
|
|
1882
2262
|
sessions.push({
|
|
1883
2263
|
id: sessionData.id,
|
|
1884
2264
|
summary: sessionData.summary || 'Codex Session',
|
|
@@ -1980,19 +2360,8 @@ async function buildGeminiSessionsLookup(projectPaths, options = {}) {
|
|
|
1980
2360
|
|
|
1981
2361
|
async function getGeminiSessionMessages(sessionId, limit = null, offset = 0) {
|
|
1982
2362
|
try {
|
|
1983
|
-
const
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
for (const filePath of sessionFiles) {
|
|
1987
|
-
try {
|
|
1988
|
-
const raw = await fs.readFile(filePath, 'utf8');
|
|
1989
|
-
const parsed = JSON.parse(raw);
|
|
1990
|
-
if (parsed?.sessionId === sessionId) {
|
|
1991
|
-
sessionFilePath = filePath;
|
|
1992
|
-
break;
|
|
1993
|
-
}
|
|
1994
|
-
} catch {}
|
|
1995
|
-
}
|
|
2363
|
+
const sessionMetadata = await getGeminiSessionMetadata(sessionId);
|
|
2364
|
+
const sessionFilePath = sessionMetadata?.filePath || null;
|
|
1996
2365
|
|
|
1997
2366
|
if (!sessionFilePath) {
|
|
1998
2367
|
return { messages: [], total: 0, hasMore: false };
|
|
@@ -2143,6 +2512,7 @@ async function parseCodexSessionFile(filePath) {
|
|
|
2143
2512
|
}
|
|
2144
2513
|
|
|
2145
2514
|
if (sessionMeta) {
|
|
2515
|
+
cacheCodexSessionFilePath(sessionMeta.id, filePath);
|
|
2146
2516
|
return {
|
|
2147
2517
|
...sessionMeta,
|
|
2148
2518
|
model: lastResolvedModel || sessionMeta.model || null,
|
|
@@ -2165,35 +2535,18 @@ async function parseCodexSessionFile(filePath) {
|
|
|
2165
2535
|
// Get messages for a specific Codex session
|
|
2166
2536
|
async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
2167
2537
|
try {
|
|
2168
|
-
const
|
|
2169
|
-
|
|
2170
|
-
// Find the session file by searching for the session ID
|
|
2171
|
-
const findSessionFile = async (dir) => {
|
|
2172
|
-
try {
|
|
2173
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2174
|
-
for (const entry of entries) {
|
|
2175
|
-
const fullPath = path.join(dir, entry.name);
|
|
2176
|
-
if (entry.isDirectory()) {
|
|
2177
|
-
const found = await findSessionFile(fullPath);
|
|
2178
|
-
if (found) return found;
|
|
2179
|
-
} else if (entry.name.includes(sessionId) && entry.name.endsWith('.jsonl')) {
|
|
2180
|
-
return fullPath;
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
} catch (error) {
|
|
2184
|
-
// Skip directories we can't read
|
|
2185
|
-
}
|
|
2186
|
-
return null;
|
|
2187
|
-
};
|
|
2188
|
-
|
|
2189
|
-
const sessionFilePath = await findSessionFile(codexSessionsDir);
|
|
2538
|
+
const sessionFilePath = await resolveCodexSessionFile(sessionId);
|
|
2190
2539
|
|
|
2191
2540
|
if (!sessionFilePath) {
|
|
2192
2541
|
console.warn(`Codex session file not found for session ${sessionId}`);
|
|
2193
2542
|
return { messages: [], total: 0, hasMore: false };
|
|
2194
2543
|
}
|
|
2195
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;
|
|
2196
2548
|
const messages = [];
|
|
2549
|
+
let total = 0;
|
|
2197
2550
|
let tokenUsage = null;
|
|
2198
2551
|
const fileStream = fsSync.createReadStream(sessionFilePath);
|
|
2199
2552
|
const rl = readline.createInterface({
|
|
@@ -2218,6 +2571,11 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2218
2571
|
.join('\n');
|
|
2219
2572
|
};
|
|
2220
2573
|
|
|
2574
|
+
const appendMessage = (message) => {
|
|
2575
|
+
total += 1;
|
|
2576
|
+
appendBoundedMessage(messages, message, maxBufferedMessages);
|
|
2577
|
+
};
|
|
2578
|
+
|
|
2221
2579
|
for await (const line of rl) {
|
|
2222
2580
|
if (line.trim()) {
|
|
2223
2581
|
try {
|
|
@@ -2243,7 +2601,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2243
2601
|
|
|
2244
2602
|
// Only add if there's actual content
|
|
2245
2603
|
if (visibleTextContent?.trim()) {
|
|
2246
|
-
|
|
2604
|
+
appendMessage({
|
|
2247
2605
|
type: role === 'user' ? 'user' : 'assistant',
|
|
2248
2606
|
timestamp: entry.timestamp,
|
|
2249
2607
|
message: {
|
|
@@ -2260,7 +2618,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2260
2618
|
.filter(Boolean)
|
|
2261
2619
|
.join('\n');
|
|
2262
2620
|
if (summaryText?.trim()) {
|
|
2263
|
-
|
|
2621
|
+
appendMessage({
|
|
2264
2622
|
type: 'thinking',
|
|
2265
2623
|
timestamp: entry.timestamp,
|
|
2266
2624
|
message: {
|
|
@@ -2286,7 +2644,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2286
2644
|
}
|
|
2287
2645
|
}
|
|
2288
2646
|
|
|
2289
|
-
|
|
2647
|
+
appendMessage({
|
|
2290
2648
|
type: 'tool_use',
|
|
2291
2649
|
timestamp: entry.timestamp,
|
|
2292
2650
|
toolName: toolName,
|
|
@@ -2296,7 +2654,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2296
2654
|
}
|
|
2297
2655
|
|
|
2298
2656
|
if (entry.type === 'response_item' && entry.payload?.type === 'function_call_output') {
|
|
2299
|
-
|
|
2657
|
+
appendMessage({
|
|
2300
2658
|
type: 'tool_result',
|
|
2301
2659
|
timestamp: entry.timestamp,
|
|
2302
2660
|
toolCallId: entry.payload.call_id,
|
|
@@ -2326,7 +2684,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2326
2684
|
}
|
|
2327
2685
|
}
|
|
2328
2686
|
|
|
2329
|
-
|
|
2687
|
+
appendMessage({
|
|
2330
2688
|
type: 'tool_use',
|
|
2331
2689
|
timestamp: entry.timestamp,
|
|
2332
2690
|
toolName: 'Edit',
|
|
@@ -2338,7 +2696,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2338
2696
|
toolCallId: entry.payload.call_id
|
|
2339
2697
|
});
|
|
2340
2698
|
} else {
|
|
2341
|
-
|
|
2699
|
+
appendMessage({
|
|
2342
2700
|
type: 'tool_use',
|
|
2343
2701
|
timestamp: entry.timestamp,
|
|
2344
2702
|
toolName: toolName,
|
|
@@ -2349,7 +2707,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2349
2707
|
}
|
|
2350
2708
|
|
|
2351
2709
|
if (entry.type === 'response_item' && entry.payload?.type === 'custom_tool_call_output') {
|
|
2352
|
-
|
|
2710
|
+
appendMessage({
|
|
2353
2711
|
type: 'tool_result',
|
|
2354
2712
|
timestamp: entry.timestamp,
|
|
2355
2713
|
toolCallId: entry.payload.call_id,
|
|
@@ -2363,29 +2721,31 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2363
2721
|
}
|
|
2364
2722
|
}
|
|
2365
2723
|
|
|
2366
|
-
// Sort by timestamp
|
|
2367
|
-
messages.sort((a, b) => new Date(a.timestamp || 0) - new Date(b.timestamp || 0));
|
|
2368
|
-
|
|
2369
|
-
const total = messages.length;
|
|
2370
|
-
|
|
2371
2724
|
// Apply pagination if limit is specified
|
|
2372
|
-
if (
|
|
2373
|
-
const
|
|
2374
|
-
const
|
|
2725
|
+
if (normalizedLimit !== null) {
|
|
2726
|
+
const endIndex = Math.max(0, messages.length - normalizedOffset);
|
|
2727
|
+
const startIndex = Math.max(0, endIndex - normalizedLimit);
|
|
2375
2728
|
const paginatedMessages = messages.slice(startIndex, endIndex);
|
|
2376
|
-
const hasMore =
|
|
2729
|
+
const hasMore = total > normalizedOffset + paginatedMessages.length;
|
|
2377
2730
|
|
|
2378
2731
|
return {
|
|
2379
2732
|
messages: paginatedMessages,
|
|
2380
2733
|
total,
|
|
2381
2734
|
hasMore,
|
|
2382
|
-
offset,
|
|
2383
|
-
limit,
|
|
2735
|
+
offset: normalizedOffset,
|
|
2736
|
+
limit: normalizedLimit,
|
|
2384
2737
|
tokenUsage
|
|
2385
2738
|
};
|
|
2386
2739
|
}
|
|
2387
2740
|
|
|
2388
|
-
return {
|
|
2741
|
+
return {
|
|
2742
|
+
messages,
|
|
2743
|
+
total,
|
|
2744
|
+
hasMore: false,
|
|
2745
|
+
offset: normalizedOffset,
|
|
2746
|
+
limit: normalizedLimit,
|
|
2747
|
+
tokenUsage
|
|
2748
|
+
};
|
|
2389
2749
|
|
|
2390
2750
|
} catch (error) {
|
|
2391
2751
|
console.error(`Error reading Codex session messages for ${sessionId}:`, error);
|
|
@@ -2393,37 +2753,45 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
2393
2753
|
}
|
|
2394
2754
|
}
|
|
2395
2755
|
|
|
2396
|
-
async function
|
|
2397
|
-
|
|
2398
|
-
|
|
2756
|
+
async function getCodexSessionMetadata(sessionId) {
|
|
2757
|
+
if (!sessionId) {
|
|
2758
|
+
return null;
|
|
2759
|
+
}
|
|
2399
2760
|
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
for (const entry of entries) {
|
|
2405
|
-
const fullPath = path.join(dir, entry.name);
|
|
2406
|
-
if (entry.isDirectory()) {
|
|
2407
|
-
files.push(...await findJsonlFiles(fullPath));
|
|
2408
|
-
} else if (entry.name.endsWith('.jsonl')) {
|
|
2409
|
-
files.push(fullPath);
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
} catch (error) {}
|
|
2413
|
-
return files;
|
|
2414
|
-
};
|
|
2761
|
+
const sessionFilePath = await resolveCodexSessionFile(sessionId);
|
|
2762
|
+
if (!sessionFilePath) {
|
|
2763
|
+
return null;
|
|
2764
|
+
}
|
|
2415
2765
|
|
|
2416
|
-
|
|
2766
|
+
const sessionData = await parseCodexSessionFile(sessionFilePath);
|
|
2767
|
+
if (!sessionData) {
|
|
2768
|
+
return null;
|
|
2769
|
+
}
|
|
2417
2770
|
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
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}`);
|
|
2424
2790
|
}
|
|
2425
2791
|
|
|
2426
|
-
|
|
2792
|
+
await fs.unlink(sessionFilePath);
|
|
2793
|
+
codexSessionFileCache.delete(sessionId);
|
|
2794
|
+
return true;
|
|
2427
2795
|
} catch (error) {
|
|
2428
2796
|
console.error(`Error deleting Codex session ${sessionId}:`, error);
|
|
2429
2797
|
throw error;
|
|
@@ -2432,7 +2800,10 @@ async function deleteCodexSession(sessionId) {
|
|
|
2432
2800
|
|
|
2433
2801
|
export {
|
|
2434
2802
|
getProjects,
|
|
2803
|
+
getProjectsList,
|
|
2804
|
+
getProjectDetails,
|
|
2435
2805
|
getSessions,
|
|
2806
|
+
getClaudeSessionMetadata,
|
|
2436
2807
|
getSessionMessages,
|
|
2437
2808
|
parseJsonlSessions,
|
|
2438
2809
|
renameProject,
|
|
@@ -2445,11 +2816,15 @@ export {
|
|
|
2445
2816
|
extractProjectDirectory,
|
|
2446
2817
|
clearProjectDirectoryCache,
|
|
2447
2818
|
clearProviderSessionLookupCaches,
|
|
2819
|
+
findCodexSessionFilePathBySessionIdHint,
|
|
2448
2820
|
getCodexSessions,
|
|
2449
2821
|
getCodexSessionMessages,
|
|
2822
|
+
getCodexSessionMetadata,
|
|
2450
2823
|
getOpencodeSessions,
|
|
2824
|
+
getOpencodeSessionMetadata,
|
|
2451
2825
|
getOpencodeSessionMessages,
|
|
2452
2826
|
getGeminiSessions,
|
|
2827
|
+
getGeminiSessionMetadata,
|
|
2453
2828
|
getGeminiSessionMessages,
|
|
2454
2829
|
deleteCodexSession,
|
|
2455
2830
|
deleteOpencodeSession
|