coding-tool-x 3.7.4 → 3.7.6
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/dist/web/assets/{Analytics-BmPIW7oK.js → Analytics-wCRLv26m.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-CG4j4ua4.js → ConfigTemplates-aa1blEHh.js} +1 -1
- package/dist/web/assets/Home-CqnBXFxj.css +1 -0
- package/dist/web/assets/Home-eIxO50-k.js +1 -0
- package/dist/web/assets/{PluginManager-CCJ7mo35.js → PluginManager-BAwGFNjN.js} +1 -1
- package/dist/web/assets/ProjectList-CRiAEixW.js +1 -0
- package/dist/web/assets/ProjectList-Dw2omgOH.css +1 -0
- package/dist/web/assets/SessionList-BQ0FW6Gd.js +1 -0
- package/dist/web/assets/SessionList-kYJ6-Ies.css +1 -0
- package/dist/web/assets/{SkillManager-DmOTHcQ7.js → SkillManager-Bhzs1O5S.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-DLVj0zlu.js → WorkspaceManager-CLdX-pTT.js} +1 -1
- package/dist/web/assets/index-B_nFqixU.css +1 -0
- package/dist/web/assets/index-ClMNA9Hf.js +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/server/api/codex-projects.js +7 -11
- package/src/server/api/codex-sessions.js +30 -21
- package/src/server/api/dashboard.js +24 -36
- package/src/server/api/gemini-projects.js +7 -11
- package/src/server/api/gemini-sessions.js +28 -26
- package/src/server/api/omp-channels.js +50 -7
- package/src/server/api/omp-projects.js +6 -11
- package/src/server/api/omp-sessions.js +31 -17
- package/src/server/api/opencode-projects.js +6 -11
- package/src/server/api/opencode-sessions.js +28 -21
- package/src/server/api/projects.js +7 -28
- package/src/server/api/sessions.js +32 -16
- package/src/server/services/channel-balance.js +2 -1
- package/src/server/services/codex-sessions.js +23 -10
- package/src/server/services/dashboard-snapshot-worker.js +216 -0
- package/src/server/services/gemini-sessions.js +15 -3
- package/src/server/services/native-oauth-adapters.js +2 -1
- package/src/server/services/omp-auth-provider-worker.js +29 -0
- package/src/server/services/omp-auth-providers.js +137 -11
- package/src/server/services/omp-config.js +7 -4
- package/src/server/services/omp-sessions.js +38 -14
- package/src/server/services/opencode-sessions.js +9 -6
- package/src/server/services/plugins-service.js +2 -1
- package/src/server/services/project-snapshots.js +8 -1
- package/src/server/services/session-snapshot-worker.js +158 -0
- package/src/server/services/session-snapshots.js +145 -0
- package/src/server/services/sessions.js +6 -4
- package/src/server/services/snapshot-cache.js +123 -13
- package/dist/web/assets/Home-B9yAAZrD.js +0 -1
- package/dist/web/assets/Home-DSTUSxrT.css +0 -1
- package/dist/web/assets/ProjectList-D3a0oRum.css +0 -1
- package/dist/web/assets/ProjectList-DVUs3o8x.js +0 -1
- package/dist/web/assets/SessionList-D12muXvx.css +0 -1
- package/dist/web/assets/SessionList-DwssBydw.js +0 -1
- package/dist/web/assets/index-BjdNYwSs.js +0 -2
- package/dist/web/assets/index-kQCC5dlu.css +0 -1
|
@@ -615,7 +615,11 @@ function normalizeSession(geminiSession) {
|
|
|
615
615
|
* @param {string} projectHash - 项目 hash
|
|
616
616
|
* @returns {string|null} 项目路径
|
|
617
617
|
*/
|
|
618
|
-
function getProjectPath(projectHash) {
|
|
618
|
+
function getProjectPath(projectHash, options = {}) {
|
|
619
|
+
if (options.force) {
|
|
620
|
+
pathMappingCache = null;
|
|
621
|
+
pathMappingCacheTime = 0;
|
|
622
|
+
}
|
|
619
623
|
const pathMapping = buildPathMapping();
|
|
620
624
|
return pathMapping.get(projectHash) || null;
|
|
621
625
|
}
|
|
@@ -624,7 +628,11 @@ function getProjectPath(projectHash) {
|
|
|
624
628
|
* 聚合项目列表
|
|
625
629
|
* @returns {Array} 项目对象数组
|
|
626
630
|
*/
|
|
627
|
-
function getProjects() {
|
|
631
|
+
function getProjects(options = {}) {
|
|
632
|
+
if (options.force) {
|
|
633
|
+
pathMappingCache = null;
|
|
634
|
+
pathMappingCacheTime = 0;
|
|
635
|
+
}
|
|
628
636
|
const sessions = getAllSessions();
|
|
629
637
|
const projectMap = new Map();
|
|
630
638
|
|
|
@@ -680,7 +688,11 @@ function getProjects() {
|
|
|
680
688
|
* @param {string} projectHash - 项目 hash
|
|
681
689
|
* @returns {Array} 会话对象数组
|
|
682
690
|
*/
|
|
683
|
-
function getProjectSessions(projectHash) {
|
|
691
|
+
function getProjectSessions(projectHash, options = {}) {
|
|
692
|
+
if (options.force) {
|
|
693
|
+
pathMappingCache = null;
|
|
694
|
+
pathMappingCacheTime = 0;
|
|
695
|
+
}
|
|
684
696
|
const allSessions = getAllSessions();
|
|
685
697
|
return allSessions
|
|
686
698
|
.filter(session => session.projectHash === projectHash)
|
|
@@ -1029,7 +1029,8 @@ function runOmpCommand(args = [], options = {}) {
|
|
|
1029
1029
|
...process.env,
|
|
1030
1030
|
...(options.env || {})
|
|
1031
1031
|
},
|
|
1032
|
-
timeout: options.timeout || 10000
|
|
1032
|
+
timeout: options.timeout || 10000,
|
|
1033
|
+
windowsHide: true
|
|
1033
1034
|
});
|
|
1034
1035
|
const status = result?.status === undefined || result?.status === null ? 0 : result.status;
|
|
1035
1036
|
return {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getOmpAuthProviderSnapshot } = require('./omp-auth-providers');
|
|
4
|
+
|
|
5
|
+
function attachWorkerHandler() {
|
|
6
|
+
process.on('message', (options = {}) => {
|
|
7
|
+
try {
|
|
8
|
+
const value = getOmpAuthProviderSnapshot(options);
|
|
9
|
+
if (process.send) {
|
|
10
|
+
process.send({ ok: true, value }, () => process.exit(0));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (process.send) {
|
|
15
|
+
process.send({ ok: false, error: error?.message || String(error) }, () => process.exit(1));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (process.env.CC_TOOL_OMP_AUTH_PROVIDER_WORKER === '1' || require.main === module) {
|
|
24
|
+
attachWorkerHandler();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
attachWorkerHandler
|
|
29
|
+
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
const
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { fork, spawnSync } = require('child_process');
|
|
2
3
|
const ompConfig = require('./omp-config');
|
|
3
4
|
|
|
4
5
|
const COMMAND_TIMEOUT_MS = 5000;
|
|
5
6
|
const TOKEN_LIST_TIMEOUT_MS = 3000;
|
|
6
7
|
const CACHE_TTL_MS = 15000;
|
|
8
|
+
const WARMUP_WORKER_TIMEOUT_MS = COMMAND_TIMEOUT_MS + 1000;
|
|
7
9
|
|
|
8
10
|
const LOGIN_PROVIDER_IDS = new Set([
|
|
9
11
|
'openai-codex',
|
|
@@ -50,6 +52,20 @@ const PROVIDER_ALIASES = {
|
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
let snapshotCache = {};
|
|
55
|
+
const warmupTimers = new Map();
|
|
56
|
+
const warmupErrors = new Map();
|
|
57
|
+
|
|
58
|
+
function getSnapshotCacheKey(options = {}) {
|
|
59
|
+
return options.accountCheck === false
|
|
60
|
+
? (options.includeStatus === false ? 'metadata' : 'metadata-status')
|
|
61
|
+
: 'full';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeSnapshotCache(cacheKey, loadedAt, value) {
|
|
65
|
+
snapshotCache[cacheKey] = { loadedAt, value };
|
|
66
|
+
warmupErrors.delete(cacheKey);
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
53
69
|
|
|
54
70
|
function normalizeProviderId(value = '') {
|
|
55
71
|
return String(value || '')
|
|
@@ -95,7 +111,8 @@ function runCommand(command, args = [], options = {}) {
|
|
|
95
111
|
...process.env,
|
|
96
112
|
...(options.env || {})
|
|
97
113
|
},
|
|
98
|
-
timeout: options.timeout || COMMAND_TIMEOUT_MS
|
|
114
|
+
timeout: options.timeout || COMMAND_TIMEOUT_MS,
|
|
115
|
+
windowsHide: true
|
|
99
116
|
});
|
|
100
117
|
if (typeof result === 'string' || Buffer.isBuffer(result)) {
|
|
101
118
|
return { status: 0, stdout: normalizeCommandOutput(result), stderr: '' };
|
|
@@ -205,9 +222,7 @@ function buildEmptySnapshot(runtime, reason) {
|
|
|
205
222
|
|
|
206
223
|
function getOmpAuthProviderSnapshot(options = {}) {
|
|
207
224
|
const now = Date.now();
|
|
208
|
-
const cacheKey = options
|
|
209
|
-
? (options.includeStatus === false ? 'metadata' : 'metadata-status')
|
|
210
|
-
: 'full';
|
|
225
|
+
const cacheKey = getSnapshotCacheKey(options);
|
|
211
226
|
const cached = snapshotCache[cacheKey];
|
|
212
227
|
if (!options.forceRefresh && cached && now - cached.loadedAt < CACHE_TTL_MS) {
|
|
213
228
|
return cached.value;
|
|
@@ -217,16 +232,14 @@ function getOmpAuthProviderSnapshot(options = {}) {
|
|
|
217
232
|
const runtime = options.runtime || ompConfig.resolveOmpRuntime(env, options.runtimeOptions || {});
|
|
218
233
|
if (!runtime || runtime.runtime !== 'omp' || !runtime.installed) {
|
|
219
234
|
const value = buildEmptySnapshot(runtime || null, 'omp-not-available');
|
|
220
|
-
|
|
221
|
-
return value;
|
|
235
|
+
return writeSnapshotCache(cacheKey, now, value);
|
|
222
236
|
}
|
|
223
237
|
|
|
224
238
|
const command = runtime.command;
|
|
225
239
|
const listResult = parseJsonCommand(command, ['auth-broker', 'list', '--json'], options);
|
|
226
240
|
if (!listResult.ok || !Array.isArray(listResult.value)) {
|
|
227
241
|
const value = buildEmptySnapshot(runtime, listResult.error || listResult.stderr || 'auth-provider-list-failed');
|
|
228
|
-
|
|
229
|
-
return value;
|
|
242
|
+
return writeSnapshotCache(cacheKey, now, value);
|
|
230
243
|
}
|
|
231
244
|
|
|
232
245
|
const supportedProviders = listResult.value
|
|
@@ -268,8 +281,112 @@ function getOmpAuthProviderSnapshot(options = {}) {
|
|
|
268
281
|
aliases: { ...PROVIDER_ALIASES },
|
|
269
282
|
checkedAt: new Date().toISOString()
|
|
270
283
|
};
|
|
271
|
-
|
|
272
|
-
|
|
284
|
+
return writeSnapshotCache(cacheKey, now, value);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getCachedOmpAuthProviderSnapshot(options = {}) {
|
|
288
|
+
const cacheKey = getSnapshotCacheKey(options);
|
|
289
|
+
const cached = snapshotCache[cacheKey];
|
|
290
|
+
if (!cached) return null;
|
|
291
|
+
return {
|
|
292
|
+
...cached.value,
|
|
293
|
+
stale: Date.now() - cached.loadedAt >= CACHE_TTL_MS
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function getOmpAuthProviderCacheMeta(options = {}) {
|
|
298
|
+
const cacheKey = getSnapshotCacheKey(options);
|
|
299
|
+
const cached = snapshotCache[cacheKey];
|
|
300
|
+
const stale = !cached || Date.now() - cached.loadedAt >= CACHE_TTL_MS;
|
|
301
|
+
const cachedReason = cached?.value?.available === false ? cached.value.reason || null : null;
|
|
302
|
+
return {
|
|
303
|
+
cached: Boolean(cached),
|
|
304
|
+
stale,
|
|
305
|
+
refreshing: warmupTimers.has(cacheKey),
|
|
306
|
+
fallback: !cached,
|
|
307
|
+
checkedAt: cached?.value?.checkedAt || null,
|
|
308
|
+
error: warmupErrors.get(cacheKey) || cachedReason || null
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function warmOmpAuthProviderSnapshot(options = {}) {
|
|
313
|
+
const cacheKey = getSnapshotCacheKey(options);
|
|
314
|
+
if (warmupTimers.has(cacheKey)) return;
|
|
315
|
+
|
|
316
|
+
const finishWarmup = (task, error = null, value = null) => {
|
|
317
|
+
if (!warmupTimers.has(cacheKey)) return;
|
|
318
|
+
if (task.timeout) clearTimeout(task.timeout);
|
|
319
|
+
if (task.child) {
|
|
320
|
+
task.child.removeAllListeners();
|
|
321
|
+
if (!task.child.killed) task.child.kill();
|
|
322
|
+
}
|
|
323
|
+
warmupTimers.delete(cacheKey);
|
|
324
|
+
if (error) {
|
|
325
|
+
warmupErrors.set(cacheKey, sanitizeIdentity(error?.message || String(error || 'auth-provider-warmup-failed')));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (value) {
|
|
329
|
+
writeSnapshotCache(cacheKey, Date.now(), value);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// Tests and callers that supply a function-based command runner cannot cross
|
|
334
|
+
// the process boundary. Production channel-list warmups use the worker path.
|
|
335
|
+
if (process.env.NODE_ENV === 'test' || options.commandRunner) {
|
|
336
|
+
const task = { timeout: null, child: null };
|
|
337
|
+
const timer = setTimeout(() => {
|
|
338
|
+
try {
|
|
339
|
+
finishWarmup(task, null, getOmpAuthProviderSnapshot(options));
|
|
340
|
+
} catch (error) {
|
|
341
|
+
finishWarmup(task, error);
|
|
342
|
+
}
|
|
343
|
+
}, 0);
|
|
344
|
+
task.timeout = timer;
|
|
345
|
+
warmupTimers.set(cacheKey, task);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const workerPath = path.join(__dirname, 'omp-auth-provider-worker.js');
|
|
350
|
+
const task = { timeout: null, child: null };
|
|
351
|
+
try {
|
|
352
|
+
const child = fork(workerPath, [], {
|
|
353
|
+
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
354
|
+
env: {
|
|
355
|
+
...process.env,
|
|
356
|
+
CC_TOOL_OMP_AUTH_PROVIDER_WORKER: '1'
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
task.child = child;
|
|
360
|
+
task.timeout = setTimeout(() => {
|
|
361
|
+
finishWarmup(task, new Error('auth-provider-warmup-timed-out'));
|
|
362
|
+
}, WARMUP_WORKER_TIMEOUT_MS);
|
|
363
|
+
warmupTimers.set(cacheKey, task);
|
|
364
|
+
|
|
365
|
+
child.on('message', (message) => {
|
|
366
|
+
if (message?.ok) {
|
|
367
|
+
finishWarmup(task, null, message.value);
|
|
368
|
+
} else {
|
|
369
|
+
finishWarmup(task, new Error(message?.error || 'auth-provider-warmup-failed'));
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
child.on('error', (error) => finishWarmup(task, error));
|
|
373
|
+
child.on('exit', (code, signal) => {
|
|
374
|
+
if (warmupTimers.get(cacheKey) === task) {
|
|
375
|
+
finishWarmup(task, new Error(`auth-provider-warmup-worker-exited-${code || signal || 'unknown'}`));
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
child.send({
|
|
379
|
+
accountCheck: options.accountCheck,
|
|
380
|
+
includeStatus: options.includeStatus,
|
|
381
|
+
forceRefresh: options.forceRefresh === true,
|
|
382
|
+
runtime: options.runtime,
|
|
383
|
+
runtimeOptions: options.runtimeOptions
|
|
384
|
+
});
|
|
385
|
+
} catch (error) {
|
|
386
|
+
// `fork` itself can fail before the task is registered. Preserve the
|
|
387
|
+
// failure for the non-blocking status endpoint instead of dropping it.
|
|
388
|
+
warmupErrors.set(cacheKey, sanitizeIdentity(error?.message || String(error || 'auth-provider-warmup-failed')));
|
|
389
|
+
}
|
|
273
390
|
}
|
|
274
391
|
|
|
275
392
|
function findAuthProviderForKey(providerKey, snapshot) {
|
|
@@ -280,6 +397,12 @@ function findAuthProviderForKey(providerKey, snapshot) {
|
|
|
280
397
|
|
|
281
398
|
function clearOmpAuthProviderCache() {
|
|
282
399
|
snapshotCache = {};
|
|
400
|
+
for (const task of warmupTimers.values()) {
|
|
401
|
+
if (task.timeout) clearTimeout(task.timeout);
|
|
402
|
+
if (task.child && !task.child.killed) task.child.kill();
|
|
403
|
+
}
|
|
404
|
+
warmupTimers.clear();
|
|
405
|
+
warmupErrors.clear();
|
|
283
406
|
}
|
|
284
407
|
|
|
285
408
|
module.exports = {
|
|
@@ -287,7 +410,10 @@ module.exports = {
|
|
|
287
410
|
ACCOUNT_CHECK_PROVIDER_IDS,
|
|
288
411
|
clearOmpAuthProviderCache,
|
|
289
412
|
findAuthProviderForKey,
|
|
413
|
+
getCachedOmpAuthProviderSnapshot,
|
|
414
|
+
getOmpAuthProviderCacheMeta,
|
|
290
415
|
getOmpAuthProviderSnapshot,
|
|
416
|
+
warmOmpAuthProviderSnapshot,
|
|
291
417
|
normalizeProviderId,
|
|
292
418
|
resolveProviderAlias,
|
|
293
419
|
sanitizeIdentity
|
|
@@ -61,7 +61,10 @@ function runOmpCommand(command, args = [], env = process.env, options = {}, stdi
|
|
|
61
61
|
encoding: 'utf8',
|
|
62
62
|
env: buildCommandEnv(env),
|
|
63
63
|
stdio,
|
|
64
|
-
timeout: options.timeout || 3000
|
|
64
|
+
timeout: options.timeout || 3000,
|
|
65
|
+
// On Windows, an npm/PowerShell command shim can otherwise create a visible
|
|
66
|
+
// console every time we probe OMP or resolve its configuration directory.
|
|
67
|
+
windowsHide: true
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -76,8 +79,8 @@ function readOmpAgentDirFromCommand(command, env = process.env, options = {}) {
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
function getOmpAgentDir(env = process.env, options = {}) {
|
|
79
|
-
const runtime = options.runtime || resolveOmpRuntime(env, options);
|
|
80
|
-
if (runtime
|
|
82
|
+
const runtime = options.runtime || (options.resolveRuntime === false ? null : resolveOmpRuntime(env, options));
|
|
83
|
+
if (runtime?.runtime === 'omp' && runtime.installed) {
|
|
81
84
|
const commandAgentDir = readOmpAgentDirFromCommand(runtime.command, env, options);
|
|
82
85
|
if (commandAgentDir) {
|
|
83
86
|
return commandAgentDir;
|
|
@@ -87,7 +90,7 @@ function getOmpAgentDir(env = process.env, options = {}) {
|
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
function getOmpPaths(env = process.env, options = {}) {
|
|
90
|
-
const runtime = options.runtime || resolveOmpRuntime(env, options);
|
|
93
|
+
const runtime = options.runtime || (options.resolveRuntime === false ? null : resolveOmpRuntime(env, options));
|
|
91
94
|
const agentDir = getOmpAgentDir(env, { ...options, runtime });
|
|
92
95
|
return {
|
|
93
96
|
agentDir,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { execFileSync } = require('child_process');
|
|
4
3
|
const { PATHS, HOME_DIR } = require('../../config/paths');
|
|
5
|
-
const { getOmpCommand, getOmpPaths,
|
|
4
|
+
const { getOmpCommand, getOmpPaths, resolveOmpRuntime } = require('./omp-config');
|
|
6
5
|
|
|
7
6
|
const PROJECT_ORDER_FILE = PATHS.ompProjectOrder;
|
|
8
7
|
const SESSION_ORDER_FILE = PATHS.ompSessionOrder;
|
|
8
|
+
const OMP_INSTALL_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
9
|
+
let ompInstallCache = { expiresAt: 0, value: false };
|
|
9
10
|
|
|
10
11
|
function ensureDir(dirPath) {
|
|
11
12
|
if (!fs.existsSync(dirPath)) {
|
|
@@ -211,7 +212,20 @@ function parseSessionFile(filePath) {
|
|
|
211
212
|
};
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
function
|
|
215
|
+
function getOmpSessionPaths() {
|
|
216
|
+
// Reading historical sessions normally only needs OMP's native directory
|
|
217
|
+
// convention. Avoid starting the CLI here: project/count snapshot workers
|
|
218
|
+
// invoke this path independently, which made Windows repeatedly open
|
|
219
|
+
// PowerShell shims. Keep the CLI lookup as a fallback for non-standard OMP
|
|
220
|
+
// locations that cannot be derived from the environment.
|
|
221
|
+
const nativePaths = getOmpPaths(process.env, { resolveRuntime: false });
|
|
222
|
+
if (fs.existsSync(nativePaths.agentDir) || fs.existsSync(nativePaths.sessions)) {
|
|
223
|
+
return nativePaths;
|
|
224
|
+
}
|
|
225
|
+
return getOmpPaths();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function scanSessionFiles(rootDir = getOmpSessionPaths().sessions) {
|
|
215
229
|
if (!fs.existsSync(rootDir)) {
|
|
216
230
|
return [];
|
|
217
231
|
}
|
|
@@ -259,7 +273,7 @@ function loadSessionOrder() {
|
|
|
259
273
|
return safeReadJson(SESSION_ORDER_FILE, {});
|
|
260
274
|
}
|
|
261
275
|
|
|
262
|
-
function getProjects() {
|
|
276
|
+
function getProjects(_options = {}) {
|
|
263
277
|
const sessions = getAllSessions();
|
|
264
278
|
const projectMap = new Map();
|
|
265
279
|
sessions.forEach((session) => {
|
|
@@ -299,7 +313,7 @@ function getProjects() {
|
|
|
299
313
|
return projects;
|
|
300
314
|
}
|
|
301
315
|
|
|
302
|
-
function getSessionsByProject(projectName) {
|
|
316
|
+
function getSessionsByProject(projectName, _options = {}) {
|
|
303
317
|
const orderMap = loadSessionOrder();
|
|
304
318
|
const order = Array.isArray(orderMap[projectName]) ? orderMap[projectName] : [];
|
|
305
319
|
const sessions = getAllSessions().filter(session => session.projectName === projectName);
|
|
@@ -425,7 +439,7 @@ function saveSessionOrder(projectName, order) {
|
|
|
425
439
|
safeWriteJson(SESSION_ORDER_FILE, data);
|
|
426
440
|
}
|
|
427
441
|
|
|
428
|
-
function getProjectAndSessionCounts() {
|
|
442
|
+
function getProjectAndSessionCounts(_options = {}) {
|
|
429
443
|
const sessions = getAllSessions();
|
|
430
444
|
return {
|
|
431
445
|
projectCount: new Set(sessions.map(session => session.projectName)).size,
|
|
@@ -454,15 +468,25 @@ function shellQuote(value) {
|
|
|
454
468
|
}
|
|
455
469
|
|
|
456
470
|
function isOmpCliInstalled() {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
execFileSync(getOmpCommand(), ['--version'], { stdio: 'ignore', timeout: 3000 });
|
|
462
|
-
return true;
|
|
463
|
-
} catch {
|
|
464
|
-
return false;
|
|
471
|
+
const now = Date.now();
|
|
472
|
+
if (ompInstallCache.expiresAt > now) {
|
|
473
|
+
return ompInstallCache.value;
|
|
465
474
|
}
|
|
475
|
+
|
|
476
|
+
let installed = false;
|
|
477
|
+
const nativePaths = getOmpSessionPaths();
|
|
478
|
+
if (fs.existsSync(nativePaths.agentDir)) {
|
|
479
|
+
installed = true;
|
|
480
|
+
} else {
|
|
481
|
+
const runtime = resolveOmpRuntime();
|
|
482
|
+
installed = Boolean(runtime && runtime.installed);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
ompInstallCache = {
|
|
486
|
+
value: installed,
|
|
487
|
+
expiresAt: now + OMP_INSTALL_CACHE_TTL_MS
|
|
488
|
+
};
|
|
489
|
+
return installed;
|
|
466
490
|
}
|
|
467
491
|
|
|
468
492
|
module.exports = {
|
|
@@ -493,7 +493,7 @@ function getProjectDisplayName(project) {
|
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
// 获取所有项目
|
|
496
|
-
function getProjects() {
|
|
496
|
+
function getProjects(_options = {}) {
|
|
497
497
|
const projects = getProjectRows().map((project) => ({
|
|
498
498
|
name: project.id,
|
|
499
499
|
displayName: getProjectDisplayName(project),
|
|
@@ -512,7 +512,7 @@ function getProjects() {
|
|
|
512
512
|
}
|
|
513
513
|
|
|
514
514
|
// 根据项目ID获取会话列表
|
|
515
|
-
function getSessionsByProjectId(projectId) {
|
|
515
|
+
function getSessionsByProjectId(projectId, _options = {}) {
|
|
516
516
|
const sessions = getSessionRowsByProjectId(projectId).map(session => normalizeSession(session, projectId));
|
|
517
517
|
const order = getSessionOrder(projectId);
|
|
518
518
|
|
|
@@ -536,8 +536,8 @@ function getSessionsByProjectId(projectId) {
|
|
|
536
536
|
}
|
|
537
537
|
|
|
538
538
|
// 根据项目名获取会话列表
|
|
539
|
-
function getSessionsByProject(projectName) {
|
|
540
|
-
return getSessionsByProjectId(projectName);
|
|
539
|
+
function getSessionsByProject(projectName, options = {}) {
|
|
540
|
+
return getSessionsByProjectId(projectName, options);
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
function getSessionLocation(sessionId) {
|
|
@@ -628,9 +628,12 @@ function getSessionMessages(sessionId) {
|
|
|
628
628
|
}
|
|
629
629
|
|
|
630
630
|
// 获取项目和会话数量统计
|
|
631
|
-
function getProjectAndSessionCounts() {
|
|
631
|
+
function getProjectAndSessionCounts(options = {}) {
|
|
632
632
|
const now = Date.now();
|
|
633
|
-
if (
|
|
633
|
+
if (options.force) {
|
|
634
|
+
countsCache.expiresAt = 0;
|
|
635
|
+
}
|
|
636
|
+
if (!options.force && countsCache.expiresAt > now) {
|
|
634
637
|
return countsCache.value;
|
|
635
638
|
}
|
|
636
639
|
|
|
@@ -6,6 +6,8 @@ const {
|
|
|
6
6
|
} = require('./snapshot-cache');
|
|
7
7
|
|
|
8
8
|
const PROJECT_SNAPSHOT_TTL_MS = 60 * 1000;
|
|
9
|
+
const PROJECT_SNAPSHOT_WAIT_ON_MISS_MS = process.env.NODE_ENV === 'test' ? 0 : 1500;
|
|
10
|
+
const PROJECT_SNAPSHOT_WAIT_ON_FORCE_MS = process.env.NODE_ENV === 'test' ? 0 : 1500;
|
|
9
11
|
|
|
10
12
|
function projectListKey(source) {
|
|
11
13
|
return `projects:list:${source}`;
|
|
@@ -33,7 +35,10 @@ async function getProjectListSnapshot(source, {
|
|
|
33
35
|
fallbackValue,
|
|
34
36
|
refresh,
|
|
35
37
|
force,
|
|
36
|
-
backgroundOnMiss: !force
|
|
38
|
+
backgroundOnMiss: !force,
|
|
39
|
+
staleWhileForce: true,
|
|
40
|
+
waitOnMissMs: PROJECT_SNAPSHOT_WAIT_ON_MISS_MS,
|
|
41
|
+
waitOnForceMs: PROJECT_SNAPSHOT_WAIT_ON_FORCE_MS
|
|
37
42
|
});
|
|
38
43
|
}
|
|
39
44
|
|
|
@@ -44,6 +49,8 @@ function invalidateProjectSnapshots(source) {
|
|
|
44
49
|
|
|
45
50
|
module.exports = {
|
|
46
51
|
PROJECT_SNAPSHOT_TTL_MS,
|
|
52
|
+
PROJECT_SNAPSHOT_WAIT_ON_MISS_MS,
|
|
53
|
+
PROJECT_SNAPSHOT_WAIT_ON_FORCE_MS,
|
|
47
54
|
projectListKey,
|
|
48
55
|
projectCountKey,
|
|
49
56
|
emptyProjectList,
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function totalSizeOf(sessions) {
|
|
6
|
+
return sessions.reduce((sum, session) => sum + (Number(session.size) || 0), 0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getAliases() {
|
|
10
|
+
const { loadAliases } = require('./alias');
|
|
11
|
+
return loadAliases();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function buildClaudePayload(projectName, config, options = {}) {
|
|
15
|
+
const {
|
|
16
|
+
getSessionsForProject,
|
|
17
|
+
parseRealProjectPath
|
|
18
|
+
} = require('./sessions');
|
|
19
|
+
|
|
20
|
+
const result = await getSessionsForProject(config, projectName, { force: options.force === true });
|
|
21
|
+
const { fullPath, projectName: displayName } = parseRealProjectPath(projectName);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
sessions: result.sessions,
|
|
25
|
+
totalSize: result.totalSize,
|
|
26
|
+
aliases: getAliases(),
|
|
27
|
+
projectInfo: {
|
|
28
|
+
name: projectName,
|
|
29
|
+
displayName,
|
|
30
|
+
fullPath
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildCodexPayload(projectName, options = {}) {
|
|
36
|
+
const { getSessionsByProject } = require('./codex-sessions');
|
|
37
|
+
const sessions = getSessionsByProject(projectName, { force: options.force === true });
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
sessions,
|
|
41
|
+
totalSize: totalSizeOf(sessions),
|
|
42
|
+
aliases: getAliases(),
|
|
43
|
+
projectInfo: {
|
|
44
|
+
name: projectName,
|
|
45
|
+
fullPath: projectName,
|
|
46
|
+
path: projectName,
|
|
47
|
+
displayName: projectName
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildGeminiPayload(projectHash, options = {}) {
|
|
53
|
+
const { getProjectSessions, getProjectPath } = require('./gemini-sessions');
|
|
54
|
+
const sessions = getProjectSessions(projectHash, { force: options.force === true });
|
|
55
|
+
const realPath = getProjectPath(projectHash, { force: options.force === true });
|
|
56
|
+
const displayName = realPath ? path.basename(realPath) : `Project ${projectHash.substring(0, 8)}`;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
sessions,
|
|
60
|
+
totalSize: totalSizeOf(sessions),
|
|
61
|
+
aliases: getAliases(),
|
|
62
|
+
projectInfo: {
|
|
63
|
+
name: projectHash,
|
|
64
|
+
fullPath: realPath || projectHash,
|
|
65
|
+
path: realPath || projectHash,
|
|
66
|
+
displayName
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildOpenCodePayload(projectName, options = {}) {
|
|
72
|
+
const { getSessionsByProject, getProjects } = require('./opencode-sessions');
|
|
73
|
+
const sessions = getSessionsByProject(projectName, { force: options.force === true });
|
|
74
|
+
const firstDirectory = sessions.find(session => session.directory)?.directory;
|
|
75
|
+
const project = firstDirectory ? null : getProjects({ force: options.force === true }).find(p => p.name === projectName);
|
|
76
|
+
const fullPath = firstDirectory || project?.fullPath || projectName;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
sessions,
|
|
80
|
+
totalSize: totalSizeOf(sessions),
|
|
81
|
+
aliases: getAliases(),
|
|
82
|
+
projectInfo: {
|
|
83
|
+
name: projectName,
|
|
84
|
+
fullPath,
|
|
85
|
+
path: project?.path || fullPath,
|
|
86
|
+
displayName: project?.displayName || (fullPath ? path.basename(fullPath) : projectName)
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildOmpPayload(projectName, options = {}) {
|
|
92
|
+
const { getProjects, getSessionsByProject } = require('./omp-sessions');
|
|
93
|
+
const sessions = getSessionsByProject(projectName, { force: options.force === true });
|
|
94
|
+
const firstDirectory = sessions.find(session => session.directory)?.directory;
|
|
95
|
+
let project = null;
|
|
96
|
+
try {
|
|
97
|
+
project = getProjects({ force: options.force === true }).find(p => p.name === projectName) || null;
|
|
98
|
+
} catch {
|
|
99
|
+
project = null;
|
|
100
|
+
}
|
|
101
|
+
const fullPath = project?.fullPath || project?.path || firstDirectory || projectName;
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
sessions,
|
|
105
|
+
totalSize: totalSizeOf(sessions),
|
|
106
|
+
aliases: getAliases(),
|
|
107
|
+
projectInfo: {
|
|
108
|
+
name: projectName,
|
|
109
|
+
fullPath,
|
|
110
|
+
path: project?.path || fullPath,
|
|
111
|
+
displayName: project?.displayName || (fullPath ? path.basename(fullPath) : projectName)
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function buildPayload({ source, projectName, config, options }) {
|
|
117
|
+
const snapshotOptions = options || {};
|
|
118
|
+
switch (source) {
|
|
119
|
+
case 'claude':
|
|
120
|
+
return buildClaudePayload(projectName, config || {}, snapshotOptions);
|
|
121
|
+
case 'codex':
|
|
122
|
+
return buildCodexPayload(projectName, snapshotOptions);
|
|
123
|
+
case 'gemini':
|
|
124
|
+
return buildGeminiPayload(projectName, snapshotOptions);
|
|
125
|
+
case 'opencode':
|
|
126
|
+
return buildOpenCodePayload(projectName, snapshotOptions);
|
|
127
|
+
case 'omp':
|
|
128
|
+
return buildOmpPayload(projectName, snapshotOptions);
|
|
129
|
+
default:
|
|
130
|
+
throw new Error(`Unsupported session snapshot source: ${source}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function attachWorkerHandler() {
|
|
135
|
+
process.on('message', async (message) => {
|
|
136
|
+
try {
|
|
137
|
+
const value = await buildPayload(message || {});
|
|
138
|
+
if (process.send) {
|
|
139
|
+
process.send({ ok: true, value }, () => process.exit(0));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (process.send) {
|
|
144
|
+
process.send({ ok: false, error: error?.message || String(error) }, () => process.exit(1));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
process.exit(0);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (process.env.CC_TOOL_SESSION_SNAPSHOT_WORKER === '1' || require.main === module) {
|
|
153
|
+
attachWorkerHandler();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
buildPayload
|
|
158
|
+
};
|