coding-tool-x 3.7.13 → 3.8.1
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/README.md +4 -6
- package/package.json +2 -2
- package/src/config/paths.js +1 -0
- package/src/server/api/codex-sessions.js +14 -14
- package/src/server/api/gemini-sessions.js +52 -14
- package/src/server/api/omp-projects.js +2 -2
- package/src/server/api/omp-sessions.js +61 -23
- package/src/server/api/sessions.js +44 -2
- package/src/server/services/codex-sessions.js +41 -233
- package/src/server/services/dashboard-snapshot-worker.js +5 -5
- package/src/server/services/gemini-sessions.js +67 -157
- package/src/server/services/omp-sessions.js +102 -105
- package/src/server/services/opencode-sessions.js +339 -546
- package/src/server/services/session-converter.js +4 -4
- package/src/server/services/session-history-adapters/claude.js +235 -0
- package/src/server/services/session-history-adapters/codex.js +158 -0
- package/src/server/services/session-history-adapters/gemini.js +292 -0
- package/src/server/services/session-history-adapters/index.js +20 -0
- package/src/server/services/session-history-adapters/omp.js +256 -0
- package/src/server/services/session-history-index.js +930 -0
- package/src/server/services/session-history-worker.js +98 -0
- package/src/server/services/session-snapshot-worker.js +7 -7
- package/src/server/services/sessions.js +167 -775
- package/src/server/services/sqlite-connection.js +78 -0
- package/src/server/services/workspace-service.js +3 -3
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const { getCodexDir } = require('./codex-config');
|
|
4
4
|
const { parseSession, parseSessionMeta, extractSessionMeta, readJSONL } = require('./codex-parser');
|
|
5
5
|
const { globalCache, CacheKeys } = require('./enhanced-cache');
|
|
6
|
+
const { listProjects: idxListProjects, listSessions: idxListSessions, getSessionStatus: idxGetSessionStatus, getRecentSessions: idxGetRecent, searchSessions: idxSearch } = require('./session-history-index');
|
|
6
7
|
|
|
7
8
|
const COUNTS_CACHE_TTL_MS = 30 * 1000;
|
|
8
9
|
const SCAN_FILES_CACHE_TTL_MS = 15 * 1000;
|
|
@@ -227,78 +228,15 @@ function normalizeSession(codexSession) {
|
|
|
227
228
|
* 聚合项目列表
|
|
228
229
|
* @returns {Array} 项目对象数组
|
|
229
230
|
*/
|
|
230
|
-
function getProjects(options = {}) {
|
|
231
|
-
|
|
232
|
-
invalidateCodexSessionCaches();
|
|
233
|
-
} else {
|
|
234
|
-
const cached = globalCache.get(CODEX_PROJECTS_CACHE_KEY);
|
|
235
|
-
if (cached) {
|
|
236
|
-
return cached;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const sessions = getAllSessions();
|
|
241
|
-
const projectMap = new Map();
|
|
242
|
-
|
|
243
|
-
sessions.forEach(session => {
|
|
244
|
-
const meta = session.meta;
|
|
245
|
-
|
|
246
|
-
// 优先使用 Git 仓库名,否则使用 cwd 的最后一级目录
|
|
247
|
-
const projectName = extractCodexProjectNameFromMeta(meta);
|
|
248
|
-
const projectPath = (typeof meta.cwd === 'string' && meta.cwd.trim()) ? meta.cwd.trim() : projectName;
|
|
249
|
-
if (!projectName) return;
|
|
250
|
-
|
|
251
|
-
if (!projectMap.has(projectName)) {
|
|
252
|
-
projectMap.set(projectName, {
|
|
253
|
-
name: projectName,
|
|
254
|
-
displayName: projectName,
|
|
255
|
-
fullPath: projectPath,
|
|
256
|
-
path: projectPath,
|
|
257
|
-
gitRepo: meta.git?.repositoryUrl,
|
|
258
|
-
branch: meta.git?.branch,
|
|
259
|
-
sessionCount: 0,
|
|
260
|
-
lastUsed: null,
|
|
261
|
-
source: 'codex'
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const project = projectMap.get(projectName);
|
|
266
|
-
project.sessionCount++;
|
|
267
|
-
|
|
268
|
-
// 更新最后活动时间
|
|
269
|
-
const sessionTime = session.mtimeMs || new Date(session.meta.timestamp || 0).getTime() || 0;
|
|
270
|
-
if (!project.lastUsed || sessionTime > project.lastUsed) {
|
|
271
|
-
project.lastUsed = sessionTime;
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// 获取保存的排序
|
|
231
|
+
async function getProjects(options = {}) {
|
|
232
|
+
const projects = await idxListProjects('codex', options);
|
|
276
233
|
const savedOrder = getProjectOrder();
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// 应用保存的排序
|
|
280
|
-
if (savedOrder.length > 0) {
|
|
281
|
-
const ordered = [];
|
|
282
|
-
const projectsMap = new Map(projects.map(p => [p.name, p]));
|
|
283
|
-
|
|
284
|
-
// 按保存的顺序添加项目
|
|
285
|
-
for (const projectName of savedOrder) {
|
|
286
|
-
if (projectsMap.has(projectName)) {
|
|
287
|
-
ordered.push(projectsMap.get(projectName));
|
|
288
|
-
projectsMap.delete(projectName);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// 添加剩余的新项目(不在保存顺序中的)
|
|
293
|
-
ordered.push(...projectsMap.values());
|
|
294
|
-
globalCache.set(CODEX_PROJECTS_CACHE_KEY, ordered, PROJECTS_CACHE_TTL_MS);
|
|
295
|
-
return ordered;
|
|
296
|
-
}
|
|
234
|
+
if (savedOrder.length === 0) return projects;
|
|
297
235
|
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
return
|
|
236
|
+
const byName = new Map(projects.map(project => [project.name, project]));
|
|
237
|
+
const ordered = savedOrder.flatMap(name => byName.has(name) ? [byName.get(name)] : []);
|
|
238
|
+
savedOrder.forEach(name => byName.delete(name));
|
|
239
|
+
return [...ordered, ...byName.values()];
|
|
302
240
|
}
|
|
303
241
|
|
|
304
242
|
/**
|
|
@@ -306,71 +244,23 @@ function getProjects(options = {}) {
|
|
|
306
244
|
* @param {string} projectName - 项目名称
|
|
307
245
|
* @returns {Array} 归一化的会话数组
|
|
308
246
|
*/
|
|
309
|
-
function getSessionsByProject(projectName, options = {}) {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const sessions = getAllSessions();
|
|
321
|
-
|
|
322
|
-
// 获取 fork 关系
|
|
323
|
-
const { getForkRelations } = require('./sessions');
|
|
324
|
-
const forkRelations = getForkRelations();
|
|
325
|
-
|
|
326
|
-
// 获取保存的排序
|
|
247
|
+
async function getSessionsByProject(projectName, options = {}) {
|
|
248
|
+
const indexed = await idxListSessions('codex', projectName, options);
|
|
249
|
+
const forkRelations = require('./sessions').getForkRelations();
|
|
250
|
+
const aliases = require('./alias').loadAliases();
|
|
251
|
+
const sessions = indexed.map(session => ({
|
|
252
|
+
...session,
|
|
253
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
254
|
+
forkedFrom: forkRelations[session.sessionId] || null,
|
|
255
|
+
alias: aliases[session.sessionId] || null
|
|
256
|
+
}));
|
|
327
257
|
const savedOrder = getSessionOrder(projectName);
|
|
258
|
+
if (savedOrder.length === 0) return sessions;
|
|
328
259
|
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return sessionProjectName === projectName;
|
|
334
|
-
})
|
|
335
|
-
.map(session => {
|
|
336
|
-
const normalized = normalizeSession(session);
|
|
337
|
-
// 添加 fork 关系
|
|
338
|
-
normalized.forkedFrom = forkRelations[normalized.sessionId] || null;
|
|
339
|
-
return normalized;
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
// 应用保存的排序
|
|
343
|
-
let orderedSessions = filteredSessions;
|
|
344
|
-
if (savedOrder.length > 0) {
|
|
345
|
-
const orderedFromSaved = [];
|
|
346
|
-
const sessionMap = new Map(filteredSessions.map(s => [s.sessionId, s]));
|
|
347
|
-
|
|
348
|
-
for (const sessionId of savedOrder) {
|
|
349
|
-
const session = sessionMap.get(sessionId);
|
|
350
|
-
if (session) {
|
|
351
|
-
orderedFromSaved.push(session);
|
|
352
|
-
sessionMap.delete(sessionId);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const newSessions = [...sessionMap.values()];
|
|
357
|
-
newSessions.sort((a, b) => {
|
|
358
|
-
return new Date(b.mtime).getTime() - new Date(a.mtime).getTime();
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// 新会话在前,旧会话在后(按保存顺序)
|
|
362
|
-
orderedSessions = [...newSessions, ...orderedFromSaved];
|
|
363
|
-
} else {
|
|
364
|
-
// 默认按时间倒序
|
|
365
|
-
orderedSessions.sort((a, b) => {
|
|
366
|
-
return new Date(b.mtime).getTime() - new Date(a.mtime).getTime();
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
globalCache.set(cacheKey, orderedSessions, PROJECT_SESSIONS_CACHE_TTL_MS);
|
|
371
|
-
codexSessionCacheKeys.add(cacheKey);
|
|
372
|
-
|
|
373
|
-
return orderedSessions;
|
|
260
|
+
const byId = new Map(sessions.map(session => [session.sessionId, session]));
|
|
261
|
+
const ordered = savedOrder.flatMap(id => byId.has(id) ? [byId.get(id)] : []);
|
|
262
|
+
savedOrder.forEach(id => byId.delete(id));
|
|
263
|
+
return [...byId.values(), ...ordered];
|
|
374
264
|
}
|
|
375
265
|
|
|
376
266
|
/**
|
|
@@ -378,23 +268,12 @@ function getSessionsByProject(projectName, options = {}) {
|
|
|
378
268
|
* @param {string} sessionId - 会话 ID
|
|
379
269
|
* @returns {Object|null} 归一化的会话对象
|
|
380
270
|
*/
|
|
381
|
-
function getSessionById(sessionId) {
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const session = parseSession(file.filePath);
|
|
389
|
-
if (!session) {
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return {
|
|
394
|
-
...normalizeSession(session),
|
|
395
|
-
messages: session.messages, // 包含完整消息
|
|
396
|
-
filePath: file.filePath
|
|
397
|
-
};
|
|
271
|
+
async function getSessionById(sessionId) {
|
|
272
|
+
const status = await idxGetSessionStatus('codex', sessionId);
|
|
273
|
+
if (!status) return null;
|
|
274
|
+
const session = parseSession(status.filePath);
|
|
275
|
+
if (!session) return null;
|
|
276
|
+
return { ...normalizeSession(session), messages: session.messages, filePath: status.filePath };
|
|
398
277
|
}
|
|
399
278
|
|
|
400
279
|
/**
|
|
@@ -402,56 +281,8 @@ function getSessionById(sessionId) {
|
|
|
402
281
|
* @param {string} keyword - 搜索关键词
|
|
403
282
|
* @returns {Array} 搜索结果
|
|
404
283
|
*/
|
|
405
|
-
function searchSessions(keyword) {
|
|
406
|
-
|
|
407
|
-
const results = [];
|
|
408
|
-
|
|
409
|
-
files.forEach(file => {
|
|
410
|
-
// 使用完整解析获取消息内容
|
|
411
|
-
const session = parseSession(file.filePath);
|
|
412
|
-
|
|
413
|
-
if (!session || !session.messages || !Array.isArray(session.messages)) {
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
session.messages.forEach((message, index) => {
|
|
418
|
-
if (message.role !== 'user' && message.role !== 'assistant') {
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const content = (message.content || '').toLowerCase();
|
|
423
|
-
const keywordLower = keyword.toLowerCase();
|
|
424
|
-
|
|
425
|
-
if (content.includes(keywordLower)) {
|
|
426
|
-
// 提取上下文
|
|
427
|
-
const startIndex = Math.max(0, content.indexOf(keywordLower) - 50);
|
|
428
|
-
const endIndex = Math.min(content.length, content.indexOf(keywordLower) + keyword.length + 50);
|
|
429
|
-
const context = content.substring(startIndex, endIndex);
|
|
430
|
-
|
|
431
|
-
// 确定项目名
|
|
432
|
-
let projectName;
|
|
433
|
-
if (session.meta?.git?.repositoryUrl) {
|
|
434
|
-
projectName = session.meta.git.repositoryUrl.split('/').pop().replace('.git', '');
|
|
435
|
-
} else if (session.meta?.cwd) {
|
|
436
|
-
projectName = path.basename(session.meta.cwd);
|
|
437
|
-
} else {
|
|
438
|
-
projectName = 'Unknown';
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
results.push({
|
|
442
|
-
sessionId: file.sessionId,
|
|
443
|
-
projectName,
|
|
444
|
-
messageIndex: index,
|
|
445
|
-
role: message.role,
|
|
446
|
-
context: (startIndex > 0 ? '...' : '') + context + (endIndex < content.length ? '...' : ''),
|
|
447
|
-
timestamp: message.timestamp,
|
|
448
|
-
source: 'codex'
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
return results;
|
|
284
|
+
async function searchSessions(keyword) {
|
|
285
|
+
return idxSearch('codex', keyword);
|
|
455
286
|
}
|
|
456
287
|
|
|
457
288
|
/**
|
|
@@ -545,39 +376,16 @@ function deleteProject(projectName) {
|
|
|
545
376
|
* @param {number} limit - 返回数量限制,默认 5
|
|
546
377
|
* @returns {Array} 最近会话数组
|
|
547
378
|
*/
|
|
548
|
-
function getRecentSessions(limit = 5) {
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const allNormalizedSessions = sessions.map(session => {
|
|
559
|
-
const normalized = normalizeSession(session);
|
|
560
|
-
|
|
561
|
-
// 添加项目信息
|
|
562
|
-
const projectName = extractCodexProjectNameFromMeta(session.meta) || 'Unknown';
|
|
563
|
-
const projectPath = (typeof session.meta.cwd === 'string' && session.meta.cwd.trim())
|
|
564
|
-
? session.meta.cwd
|
|
565
|
-
: projectName;
|
|
566
|
-
|
|
567
|
-
return {
|
|
568
|
-
...normalized,
|
|
569
|
-
forkedFrom: forkRelations[normalized.sessionId] || null,
|
|
570
|
-
alias: aliases[normalized.sessionId] || null,
|
|
571
|
-
projectName: projectName,
|
|
572
|
-
projectDisplayName: projectName,
|
|
573
|
-
projectFullPath: projectPath
|
|
574
|
-
};
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
// 按 mtime 倒序排序,取前 N 个
|
|
578
|
-
return allNormalizedSessions
|
|
579
|
-
.sort((a, b) => new Date(b.mtime).getTime() - new Date(a.mtime).getTime())
|
|
580
|
-
.slice(0, limit);
|
|
379
|
+
async function getRecentSessions(limit = 5) {
|
|
380
|
+
const indexed = await idxGetRecent('codex', limit);
|
|
381
|
+
const forkRelations = require('./sessions').getForkRelations();
|
|
382
|
+
const aliases = require('./alias').loadAliases();
|
|
383
|
+
return indexed.map(session => ({
|
|
384
|
+
...session,
|
|
385
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
386
|
+
forkedFrom: forkRelations[session.sessionId] || null,
|
|
387
|
+
alias: aliases[session.sessionId] || null
|
|
388
|
+
}));
|
|
581
389
|
}
|
|
582
390
|
|
|
583
391
|
/**
|
|
@@ -32,12 +32,12 @@ async function buildProjectsPayload(source, config = {}, options = {}) {
|
|
|
32
32
|
}
|
|
33
33
|
case 'codex': {
|
|
34
34
|
const { getProjects } = require('./codex-sessions');
|
|
35
|
-
const projects = getProjects({ force: options.force === true });
|
|
35
|
+
const projects = await getProjects({ force: options.force === true });
|
|
36
36
|
return { projects, currentProject: projects[0] ? projects[0].name : null };
|
|
37
37
|
}
|
|
38
38
|
case 'gemini': {
|
|
39
39
|
const { getProjects } = require('./gemini-sessions');
|
|
40
|
-
const projects = getProjects({ force: options.force === true });
|
|
40
|
+
const projects = await getProjects({ force: options.force === true });
|
|
41
41
|
return { projects, currentProject: projects[0] ? projects[0].name : null };
|
|
42
42
|
}
|
|
43
43
|
case 'opencode': {
|
|
@@ -47,7 +47,7 @@ async function buildProjectsPayload(source, config = {}, options = {}) {
|
|
|
47
47
|
}
|
|
48
48
|
case 'omp': {
|
|
49
49
|
const { getProjects } = require('./omp-sessions');
|
|
50
|
-
const projects = getProjects({ force: options.force === true });
|
|
50
|
+
const projects = await getProjects({ force: options.force === true });
|
|
51
51
|
return { projects, currentProject: projects[0] ? projects[0].name : null };
|
|
52
52
|
}
|
|
53
53
|
default:
|
|
@@ -55,10 +55,10 @@ async function buildProjectsPayload(source, config = {}, options = {}) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function buildCountsPayload(source, config = {}, options = {}) {
|
|
58
|
+
async function buildCountsPayload(source, config = {}, options = {}) {
|
|
59
59
|
switch (source) {
|
|
60
60
|
case 'claude':
|
|
61
|
-
return require('./sessions').getProjectAndSessionCounts(config
|
|
61
|
+
return require('./sessions').getProjectAndSessionCounts(config);
|
|
62
62
|
case 'codex':
|
|
63
63
|
return require('./codex-sessions').getProjectAndSessionCounts({ force: options.force === true });
|
|
64
64
|
case 'gemini':
|
|
@@ -4,6 +4,7 @@ const crypto = require('crypto');
|
|
|
4
4
|
const { HOME_DIR } = require('../../config/paths');
|
|
5
5
|
const { getGeminiDir } = require('./gemini-config');
|
|
6
6
|
const { resolveModelPricing } = require('../utils/pricing');
|
|
7
|
+
const { listProjects: idxListProjects, listSessions: idxListSessions, getSessionStatus: idxGetSessionStatus, getRecentSessions: idxGetRecent, searchSessions: idxSearch } = require('./session-history-index');
|
|
7
8
|
|
|
8
9
|
const HASH_RE = /^[a-f0-9]{64}$/;
|
|
9
10
|
const SESSION_FILE_RE = /^session-(.*)-([a-f0-9]+)\.(json|jsonl)$/;
|
|
@@ -628,59 +629,20 @@ function getProjectPath(projectHash, options = {}) {
|
|
|
628
629
|
* 聚合项目列表
|
|
629
630
|
* @returns {Array} 项目对象数组
|
|
630
631
|
*/
|
|
631
|
-
function getProjects(options = {}) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
// 如果找到了真实路径,使用目录名作为显示名称
|
|
646
|
-
let displayName;
|
|
647
|
-
if (projectPath) {
|
|
648
|
-
displayName = path.basename(projectPath);
|
|
649
|
-
// 如果是 home 目录,显示 ~
|
|
650
|
-
if (projectPath === HOME_DIR) {
|
|
651
|
-
displayName = '~';
|
|
652
|
-
}
|
|
653
|
-
} else {
|
|
654
|
-
// 未找到路径,使用 hash 前 8 位
|
|
655
|
-
displayName = `Project ${projectHash.substring(0, 8)}`;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
projectMap.set(projectHash, {
|
|
659
|
-
name: projectHash,
|
|
660
|
-
displayName,
|
|
661
|
-
path: projectPath,
|
|
662
|
-
fullPath: projectPath,
|
|
663
|
-
storageName: session.storageName,
|
|
664
|
-
sessionCount: 0,
|
|
665
|
-
lastUpdated: session.lastUpdated,
|
|
666
|
-
source: 'gemini'
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const project = projectMap.get(projectHash);
|
|
671
|
-
project.sessionCount++;
|
|
672
|
-
|
|
673
|
-
// 更新最后活动时间
|
|
674
|
-
if (new Date(session.lastUpdated) > new Date(project.lastUpdated)) {
|
|
675
|
-
project.lastUpdated = session.lastUpdated;
|
|
676
|
-
}
|
|
632
|
+
async function getProjects(options = {}) {
|
|
633
|
+
const projects = await idxListProjects('gemini', options);
|
|
634
|
+
const paths = buildPathMapping();
|
|
635
|
+
return projects.map(project => {
|
|
636
|
+
const fullPath = project.fullPath || paths.get(project.name) || null;
|
|
637
|
+
return {
|
|
638
|
+
...project,
|
|
639
|
+
displayName: fullPath ? path.basename(fullPath) : project.displayName,
|
|
640
|
+
path: fullPath,
|
|
641
|
+
fullPath,
|
|
642
|
+
storageName: fullPath ? path.basename(fullPath) : project.name,
|
|
643
|
+
lastUpdated: new Date(project.lastUsed).toISOString()
|
|
644
|
+
};
|
|
677
645
|
});
|
|
678
|
-
|
|
679
|
-
// 转换为数组并排序(按最后活动时间)
|
|
680
|
-
const projects = Array.from(projectMap.values());
|
|
681
|
-
projects.sort((a, b) => new Date(b.lastUpdated) - new Date(a.lastUpdated));
|
|
682
|
-
|
|
683
|
-
return projects;
|
|
684
646
|
}
|
|
685
647
|
|
|
686
648
|
/**
|
|
@@ -688,15 +650,25 @@ function getProjects(options = {}) {
|
|
|
688
650
|
* @param {string} projectHash - 项目 hash
|
|
689
651
|
* @returns {Array} 会话对象数组
|
|
690
652
|
*/
|
|
691
|
-
function getProjectSessions(projectHash, options = {}) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
.
|
|
653
|
+
async function getProjectSessions(projectHash, options = {}) {
|
|
654
|
+
const indexed = await idxListSessions('gemini', projectHash, options);
|
|
655
|
+
return indexed.map(session => ({
|
|
656
|
+
sessionId: session.sessionId,
|
|
657
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
658
|
+
size: session.size,
|
|
659
|
+
filePath: session.filePath,
|
|
660
|
+
gitBranch: null,
|
|
661
|
+
firstMessage: session.firstMessage,
|
|
662
|
+
forkedFrom: session.extra?.forkedFrom || null,
|
|
663
|
+
source: 'gemini',
|
|
664
|
+
tokens: session.tokens?.total ?? session.tokens ?? 0,
|
|
665
|
+
cost: session.extra?.cost || 0,
|
|
666
|
+
model: session.model || 'gemini-2.5-pro',
|
|
667
|
+
projectHash: session.projectName,
|
|
668
|
+
projectName: session.projectName,
|
|
669
|
+
storageName: session.extra?.storageName || session.projectName,
|
|
670
|
+
projectRoot: session.projectFullPath || null
|
|
671
|
+
}));
|
|
700
672
|
}
|
|
701
673
|
|
|
702
674
|
/**
|
|
@@ -704,15 +676,9 @@ function getProjectSessions(projectHash, options = {}) {
|
|
|
704
676
|
* @param {string} sessionId - 会话 ID
|
|
705
677
|
* @returns {Object|null} 完整会话数据
|
|
706
678
|
*/
|
|
707
|
-
function getSession(sessionId) {
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
if (!session) {
|
|
712
|
-
return null;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
return readSessionFull(session.filePath);
|
|
679
|
+
async function getSession(sessionId) {
|
|
680
|
+
const status = await idxGetSessionStatus('gemini', sessionId);
|
|
681
|
+
return status ? readSessionFull(status.filePath) : null;
|
|
716
682
|
}
|
|
717
683
|
|
|
718
684
|
/**
|
|
@@ -773,11 +739,13 @@ function saveProjectOrder(order) {
|
|
|
773
739
|
* @param {number} limit - 限制数量
|
|
774
740
|
* @returns {Array} 会话对象数组
|
|
775
741
|
*/
|
|
776
|
-
function getRecentSessions(limit = 5) {
|
|
777
|
-
const
|
|
778
|
-
return
|
|
779
|
-
|
|
780
|
-
.
|
|
742
|
+
async function getRecentSessions(limit = 5) {
|
|
743
|
+
const sessions = await idxGetRecent('gemini', limit);
|
|
744
|
+
return sessions.map(session => ({
|
|
745
|
+
...session,
|
|
746
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
747
|
+
projectHash: session.projectName
|
|
748
|
+
}));
|
|
781
749
|
}
|
|
782
750
|
|
|
783
751
|
/**
|
|
@@ -785,26 +753,16 @@ function getRecentSessions(limit = 5) {
|
|
|
785
753
|
* @param {string} sessionId - 会话 ID
|
|
786
754
|
* @returns {Object|null} 完整会话数据
|
|
787
755
|
*/
|
|
788
|
-
function getSessionById(sessionId) {
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (!
|
|
793
|
-
return null;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
const fullSession = readSessionFull(sessionMeta.filePath);
|
|
797
|
-
|
|
798
|
-
if (!fullSession) {
|
|
799
|
-
return null;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// 合并元数据
|
|
756
|
+
async function getSessionById(sessionId) {
|
|
757
|
+
const status = await idxGetSessionStatus('gemini', sessionId);
|
|
758
|
+
if (!status) return null;
|
|
759
|
+
const fullSession = readSessionFull(status.filePath);
|
|
760
|
+
if (!fullSession) return null;
|
|
803
761
|
return {
|
|
804
762
|
...fullSession,
|
|
805
|
-
filePath:
|
|
806
|
-
size:
|
|
807
|
-
mtime:
|
|
763
|
+
filePath: status.filePath,
|
|
764
|
+
size: status.size,
|
|
765
|
+
mtime: new Date(status.lastModified).toISOString(),
|
|
808
766
|
source: 'gemini'
|
|
809
767
|
};
|
|
810
768
|
}
|
|
@@ -815,70 +773,22 @@ function getSessionById(sessionId) {
|
|
|
815
773
|
* @param {number} contextLength - 上下文长度(可选)
|
|
816
774
|
* @returns {Array} 搜索结果数组
|
|
817
775
|
*/
|
|
818
|
-
function searchSessions(keyword, contextLength = 35) {
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
// 提取消息内容
|
|
835
|
-
if (msg.type === 'user' || msg.type === 'assistant' || msg.type === 'gemini') {
|
|
836
|
-
content = extractContentText(msg.content);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
// 搜索关键词
|
|
840
|
-
const lowerContent = content.toLowerCase();
|
|
841
|
-
const lowerKeyword = keyword.toLowerCase();
|
|
842
|
-
|
|
843
|
-
if (lowerContent.includes(lowerKeyword)) {
|
|
844
|
-
const keywordIndex = lowerContent.indexOf(lowerKeyword);
|
|
845
|
-
|
|
846
|
-
// 提取上下文
|
|
847
|
-
const start = Math.max(0, keywordIndex - contextLength);
|
|
848
|
-
const end = Math.min(content.length, keywordIndex + keyword.length + contextLength);
|
|
849
|
-
|
|
850
|
-
let context = content.substring(start, end);
|
|
851
|
-
|
|
852
|
-
// 添加省略号
|
|
853
|
-
if (start > 0) context = '...' + context;
|
|
854
|
-
if (end < content.length) context = context + '...';
|
|
855
|
-
|
|
856
|
-
matches.push({
|
|
857
|
-
messageIndex: index,
|
|
858
|
-
role: msg.type,
|
|
859
|
-
context,
|
|
860
|
-
timestamp: msg.timestamp
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
if (matches.length > 0) {
|
|
866
|
-
results.push({
|
|
867
|
-
sessionId: sessionMeta.sessionId,
|
|
868
|
-
projectHash: sessionMeta.projectHash,
|
|
869
|
-
firstMessage: sessionMeta.firstMessage,
|
|
870
|
-
lastUpdated: sessionMeta.lastUpdated,
|
|
871
|
-
matches,
|
|
872
|
-
matchCount: matches.length,
|
|
873
|
-
source: 'gemini'
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
// 按匹配数量排序
|
|
879
|
-
results.sort((a, b) => b.matchCount - a.matchCount);
|
|
880
|
-
|
|
881
|
-
return results;
|
|
776
|
+
async function searchSessions(keyword, contextLength = 35) {
|
|
777
|
+
const results = await idxSearch('gemini', keyword, { contextLength });
|
|
778
|
+
return results.map(result => ({
|
|
779
|
+
sessionId: result.sessionId,
|
|
780
|
+
projectHash: result.projectName,
|
|
781
|
+
firstMessage: result.firstMessage,
|
|
782
|
+
lastUpdated: result.updatedAt || null,
|
|
783
|
+
matches: result.matches.map(match => ({
|
|
784
|
+
messageIndex: match.ordinal,
|
|
785
|
+
role: match.type === 'gemini' ? 'gemini' : match.role,
|
|
786
|
+
context: match.context,
|
|
787
|
+
timestamp: match.timestamp
|
|
788
|
+
})),
|
|
789
|
+
matchCount: result.matchCount,
|
|
790
|
+
source: 'gemini'
|
|
791
|
+
}));
|
|
882
792
|
}
|
|
883
793
|
|
|
884
794
|
/**
|