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
package/dist/web/index.html
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<link rel="icon" href="/favicon.ico">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>coding-tool-x · AI 编程工作台</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-ClMNA9Hf.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/markdown-BNJfEgCA.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vue-vendor-B5RqjEjQ.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/naive-ui-CVyvdmg1.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/icons-nGdSKQn2.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/vendors-DQA4Hw9v.js">
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B_nFqixU.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
-
const {
|
|
3
|
+
const { saveProjectOrder, deleteProject } = require('../services/codex-sessions');
|
|
4
4
|
const { isCodexInstalled } = require('../services/codex-config');
|
|
5
5
|
const {
|
|
6
6
|
emptyProjectList,
|
|
7
7
|
getProjectListSnapshot,
|
|
8
8
|
invalidateProjectSnapshots
|
|
9
9
|
} = require('../services/project-snapshots');
|
|
10
|
+
const { invalidateSessionSnapshots } = require('../services/session-snapshots');
|
|
11
|
+
const { runDashboardSnapshotWorker } = require('../services/dashboard-snapshot-worker');
|
|
10
12
|
|
|
11
13
|
const DEBUG_CODEX_PERF = process.env.DEBUG_CODEX_PERF === '1';
|
|
12
14
|
|
|
@@ -18,14 +20,6 @@ function logPerf(route, startMs, detail = '') {
|
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
module.exports = (config) => {
|
|
21
|
-
function buildProjectsPayload() {
|
|
22
|
-
const projects = getProjects();
|
|
23
|
-
return {
|
|
24
|
-
projects,
|
|
25
|
-
currentProject: projects[0] ? projects[0].name : null
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
/**
|
|
30
24
|
* GET /api/codex/projects
|
|
31
25
|
* 获取所有 Codex 项目列表
|
|
@@ -44,10 +38,11 @@ module.exports = (config) => {
|
|
|
44
38
|
});
|
|
45
39
|
}
|
|
46
40
|
|
|
41
|
+
const force = req.query?.fresh === '1';
|
|
47
42
|
const snapshot = await getProjectListSnapshot('codex', {
|
|
48
43
|
fallbackValue: emptyProjectList(null),
|
|
49
|
-
force
|
|
50
|
-
refresh:
|
|
44
|
+
force,
|
|
45
|
+
refresh: () => runDashboardSnapshotWorker('projects', 'codex', config, { force })
|
|
51
46
|
});
|
|
52
47
|
logPerf('GET /api/codex/projects', startMs, `projects=${snapshot.value.projects.length} stale=${snapshot.meta.stale}`);
|
|
53
48
|
|
|
@@ -109,6 +104,7 @@ module.exports = (config) => {
|
|
|
109
104
|
const { projectName } = req.params;
|
|
110
105
|
const result = deleteProject(projectName);
|
|
111
106
|
invalidateProjectSnapshots('codex');
|
|
107
|
+
invalidateSessionSnapshots('codex', projectName);
|
|
112
108
|
|
|
113
109
|
res.json(result);
|
|
114
110
|
} catch (err) {
|
|
@@ -11,6 +11,14 @@ const {
|
|
|
11
11
|
} = require('../services/codex-sessions');
|
|
12
12
|
const { isCodexInstalled } = require('../services/codex-config');
|
|
13
13
|
const { loadAliases } = require('../services/alias');
|
|
14
|
+
const {
|
|
15
|
+
defaultProjectInfo,
|
|
16
|
+
emptySessionList,
|
|
17
|
+
getSessionListSnapshot,
|
|
18
|
+
invalidateSessionSnapshots,
|
|
19
|
+
runSessionSnapshotWorker
|
|
20
|
+
} = require('../services/session-snapshots');
|
|
21
|
+
const { invalidateProjectSnapshots } = require('../services/project-snapshots');
|
|
14
22
|
|
|
15
23
|
const DEBUG_CODEX_PERF = process.env.DEBUG_CODEX_PERF === '1';
|
|
16
24
|
|
|
@@ -151,7 +159,7 @@ module.exports = (config) => {
|
|
|
151
159
|
* GET /api/codex/sessions/:projectName
|
|
152
160
|
* 获取项目的所有会话
|
|
153
161
|
*/
|
|
154
|
-
router.get('/:projectName', (req, res) => {
|
|
162
|
+
router.get('/:projectName', async (req, res) => {
|
|
155
163
|
const startMs = Date.now();
|
|
156
164
|
try {
|
|
157
165
|
if (!isCodexInstalled()) {
|
|
@@ -160,27 +168,18 @@ module.exports = (config) => {
|
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
const { projectName } = req.params;
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const aliases = loadAliases();
|
|
172
|
-
logPerf('GET /api/codex/sessions/:projectName', startMs, `project=${projectName}, sessions=${sessions.length}`);
|
|
173
|
-
|
|
174
|
-
res.json({
|
|
175
|
-
sessions,
|
|
176
|
-
totalSize,
|
|
177
|
-
aliases, // 返回所有别名
|
|
178
|
-
projectInfo: {
|
|
179
|
-
name: projectName,
|
|
180
|
-
fullPath: projectName,
|
|
181
|
-
displayName: projectName
|
|
182
|
-
}
|
|
171
|
+
const force = req.query?.fresh === '1';
|
|
172
|
+
const snapshot = await getSessionListSnapshot('codex', projectName, {
|
|
173
|
+
fallbackValue: emptySessionList(projectName, {
|
|
174
|
+
aliases: loadAliases(),
|
|
175
|
+
projectInfo: defaultProjectInfo(projectName)
|
|
176
|
+
}),
|
|
177
|
+
force,
|
|
178
|
+
refresh: () => runSessionSnapshotWorker('codex', projectName, {}, { force })
|
|
183
179
|
});
|
|
180
|
+
logPerf('GET /api/codex/sessions/:projectName', startMs, `project=${projectName}, sessions=${snapshot.value.sessions.length}, stale=${snapshot.meta.stale}`);
|
|
181
|
+
|
|
182
|
+
res.json({ ...snapshot.value, meta: snapshot.meta });
|
|
184
183
|
} catch (err) {
|
|
185
184
|
console.error('[Codex API] Failed to get sessions:', err);
|
|
186
185
|
logPerf('GET /api/codex/sessions/:projectName', startMs, `error=${err.message}`);
|
|
@@ -453,6 +452,8 @@ module.exports = (config) => {
|
|
|
453
452
|
|
|
454
453
|
const { sessionId } = req.params;
|
|
455
454
|
const result = deleteSession(sessionId);
|
|
455
|
+
invalidateSessionSnapshots('codex', req.params.projectName);
|
|
456
|
+
invalidateProjectSnapshots('codex');
|
|
456
457
|
|
|
457
458
|
res.json(result);
|
|
458
459
|
} catch (err) {
|
|
@@ -502,6 +503,11 @@ module.exports = (config) => {
|
|
|
502
503
|
}
|
|
503
504
|
});
|
|
504
505
|
|
|
506
|
+
if (deletedSessionIds.length > 0) {
|
|
507
|
+
invalidateSessionSnapshots('codex', req.params.projectName);
|
|
508
|
+
invalidateProjectSnapshots('codex');
|
|
509
|
+
}
|
|
510
|
+
|
|
505
511
|
res.json({
|
|
506
512
|
success: failed.length === 0,
|
|
507
513
|
requestedCount: uniqueSessionIds.length,
|
|
@@ -527,6 +533,8 @@ module.exports = (config) => {
|
|
|
527
533
|
|
|
528
534
|
const { sessionId } = req.params;
|
|
529
535
|
const result = forkSession(sessionId, normalizeForkOptions(req.body));
|
|
536
|
+
invalidateSessionSnapshots('codex', req.params.projectName);
|
|
537
|
+
invalidateProjectSnapshots('codex');
|
|
530
538
|
|
|
531
539
|
res.json(result);
|
|
532
540
|
} catch (err) {
|
|
@@ -553,6 +561,7 @@ module.exports = (config) => {
|
|
|
553
561
|
}
|
|
554
562
|
|
|
555
563
|
saveSessionOrder(projectName, order);
|
|
564
|
+
invalidateSessionSnapshots('codex', projectName);
|
|
556
565
|
|
|
557
566
|
res.json({ success: true });
|
|
558
567
|
} catch (err) {
|
|
@@ -5,34 +5,17 @@ const { loadConfig } = require('../../config/loader');
|
|
|
5
5
|
// Services
|
|
6
6
|
const { loadUIConfig } = require('../services/ui-config');
|
|
7
7
|
const { loadFavorites } = require('../services/favorites');
|
|
8
|
-
const { getAllChannels } = require('../services/channels');
|
|
9
8
|
const { getProxyStatus } = require('../proxy-server');
|
|
10
9
|
const { getCodexProxyStatus } = require('../codex-proxy-server');
|
|
11
10
|
const { getGeminiProxyStatus } = require('../gemini-proxy-server');
|
|
12
11
|
const { getOpenCodeProxyStatus } = require('../opencode-proxy-server');
|
|
13
12
|
const { getOmpProxyStatus } = require('../omp-proxy-server');
|
|
14
|
-
const { getProjectAndSessionCounts: getClaudeCounts } = require('../services/sessions');
|
|
15
|
-
const { getProjectAndSessionCounts: getCodexCounts } = require('../services/codex-sessions');
|
|
16
|
-
const { getProjectAndSessionCounts: getGeminiCounts } = require('../services/gemini-sessions');
|
|
17
|
-
const { getProjectAndSessionCounts: getOpenCodeCounts } = require('../services/opencode-sessions');
|
|
18
|
-
const { getProjectAndSessionCounts: getOmpCounts } = require('../services/omp-sessions');
|
|
19
|
-
|
|
20
|
-
// Channel-specific services
|
|
21
|
-
const { getChannels: getCodexChannels } = require('../services/codex-channels');
|
|
22
|
-
const { getChannels: getGeminiChannels } = require('../services/gemini-channels');
|
|
23
|
-
const { getChannels: getOpenCodeChannels } = require('../services/opencode-channels');
|
|
24
|
-
const { getChannels: getOmpChannels } = require('../services/omp-channels');
|
|
25
|
-
|
|
26
|
-
// Statistics
|
|
27
|
-
const { getTodayStatistics: getClaudeTodayStatistics } = require('../services/claude-statistics-service');
|
|
28
|
-
const { getTodayStatistics: getCodexTodayStatistics } = require('../services/codex-statistics-service');
|
|
29
|
-
const { getTodayStatistics: getGeminiTodayStatistics } = require('../services/gemini-statistics-service');
|
|
30
|
-
const { getTodayStatistics: getOpenCodeTodayStatistics } = require('../services/opencode-statistics-service');
|
|
31
|
-
const { getTodayStatistics: getOmpTodayStatistics } = require('../services/omp-statistics-service');
|
|
32
13
|
const { getSnapshot } = require('../services/snapshot-cache');
|
|
33
14
|
const { projectCountKey } = require('../services/project-snapshots');
|
|
15
|
+
const { runDashboardSnapshotWorker } = require('../services/dashboard-snapshot-worker');
|
|
34
16
|
|
|
35
17
|
const SNAPSHOT_TTL_MS = 60 * 1000;
|
|
18
|
+
const DASHBOARD_SNAPSHOT_DEFER_MS = 0;
|
|
36
19
|
const EMPTY_COUNTS = { projectCount: 0, sessionCount: 0 };
|
|
37
20
|
const EMPTY_STATS = { requests: 0, tokens: 0, cost: 0, byModel: {}, byChannel: {} };
|
|
38
21
|
|
|
@@ -90,11 +73,15 @@ function formatStats(stats) {
|
|
|
90
73
|
return { ...EMPTY_STATS };
|
|
91
74
|
}
|
|
92
75
|
|
|
93
|
-
async function readDashboardSnapshot(key, fallbackValue,
|
|
76
|
+
async function readDashboardSnapshot(kind, source, key, fallbackValue, config = {}, options = {}) {
|
|
77
|
+
const force = options.force === true;
|
|
94
78
|
return getSnapshot(key, {
|
|
95
79
|
ttlMs: SNAPSHOT_TTL_MS,
|
|
96
80
|
fallbackValue,
|
|
97
|
-
|
|
81
|
+
force,
|
|
82
|
+
staleWhileForce: force,
|
|
83
|
+
deferMs: DASHBOARD_SNAPSHOT_DEFER_MS,
|
|
84
|
+
refresh: () => runDashboardSnapshotWorker(kind, source, config, { force })
|
|
98
85
|
});
|
|
99
86
|
}
|
|
100
87
|
|
|
@@ -139,6 +126,7 @@ function dashboardMeta(parts) {
|
|
|
139
126
|
router.get('/init', async (req, res) => {
|
|
140
127
|
try {
|
|
141
128
|
const config = loadConfig();
|
|
129
|
+
const force = req.query?.fresh === '1' || req.query?.force === '1';
|
|
142
130
|
|
|
143
131
|
const uiConfig = safeRead('uiConfig', () => loadUIConfig(), {});
|
|
144
132
|
const favorites = safeRead('favorites', () => loadFavorites(), {});
|
|
@@ -167,21 +155,21 @@ router.get('/init', async (req, res) => {
|
|
|
167
155
|
opencodeCounts,
|
|
168
156
|
ompCounts
|
|
169
157
|
] = await Promise.all([
|
|
170
|
-
readDashboardSnapshot(channelSnapshotKey('claude'), [],
|
|
171
|
-
readDashboardSnapshot(channelSnapshotKey('codex'), { channels: [] },
|
|
172
|
-
readDashboardSnapshot(channelSnapshotKey('gemini'), { channels: [] },
|
|
173
|
-
readDashboardSnapshot(channelSnapshotKey('opencode'), { channels: [] },
|
|
174
|
-
readDashboardSnapshot(channelSnapshotKey('omp'), { channels: [] },
|
|
175
|
-
readDashboardSnapshot(todayStatsSnapshotKey('claude'), null,
|
|
176
|
-
readDashboardSnapshot(todayStatsSnapshotKey('codex'), null,
|
|
177
|
-
readDashboardSnapshot(todayStatsSnapshotKey('gemini'), null,
|
|
178
|
-
readDashboardSnapshot(todayStatsSnapshotKey('opencode'), null,
|
|
179
|
-
readDashboardSnapshot(todayStatsSnapshotKey('omp'), null,
|
|
180
|
-
readDashboardSnapshot(projectCountKey('claude'), EMPTY_COUNTS,
|
|
181
|
-
readDashboardSnapshot(projectCountKey('codex'), EMPTY_COUNTS,
|
|
182
|
-
readDashboardSnapshot(projectCountKey('gemini'), EMPTY_COUNTS,
|
|
183
|
-
readDashboardSnapshot(projectCountKey('opencode'), EMPTY_COUNTS,
|
|
184
|
-
readDashboardSnapshot(projectCountKey('omp'), EMPTY_COUNTS,
|
|
158
|
+
readDashboardSnapshot('channels', 'claude', channelSnapshotKey('claude'), [], config, { force }),
|
|
159
|
+
readDashboardSnapshot('channels', 'codex', channelSnapshotKey('codex'), { channels: [] }, config, { force }),
|
|
160
|
+
readDashboardSnapshot('channels', 'gemini', channelSnapshotKey('gemini'), { channels: [] }, config, { force }),
|
|
161
|
+
readDashboardSnapshot('channels', 'opencode', channelSnapshotKey('opencode'), { channels: [] }, config, { force }),
|
|
162
|
+
readDashboardSnapshot('channels', 'omp', channelSnapshotKey('omp'), { channels: [] }, config, { force }),
|
|
163
|
+
readDashboardSnapshot('todayStats', 'claude', todayStatsSnapshotKey('claude'), null, config, { force }),
|
|
164
|
+
readDashboardSnapshot('todayStats', 'codex', todayStatsSnapshotKey('codex'), null, config, { force }),
|
|
165
|
+
readDashboardSnapshot('todayStats', 'gemini', todayStatsSnapshotKey('gemini'), null, config, { force }),
|
|
166
|
+
readDashboardSnapshot('todayStats', 'opencode', todayStatsSnapshotKey('opencode'), null, config, { force }),
|
|
167
|
+
readDashboardSnapshot('todayStats', 'omp', todayStatsSnapshotKey('omp'), null, config, { force }),
|
|
168
|
+
readDashboardSnapshot('counts', 'claude', projectCountKey('claude'), EMPTY_COUNTS, config, { force }),
|
|
169
|
+
readDashboardSnapshot('counts', 'codex', projectCountKey('codex'), EMPTY_COUNTS, config, { force }),
|
|
170
|
+
readDashboardSnapshot('counts', 'gemini', projectCountKey('gemini'), EMPTY_COUNTS, config, { force }),
|
|
171
|
+
readDashboardSnapshot('counts', 'opencode', projectCountKey('opencode'), EMPTY_COUNTS, config, { force }),
|
|
172
|
+
readDashboardSnapshot('counts', 'omp', projectCountKey('omp'), EMPTY_COUNTS, config, { force })
|
|
185
173
|
]);
|
|
186
174
|
|
|
187
175
|
const parts = {
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
-
const {
|
|
3
|
+
const { saveProjectOrder, deleteProject } = require('../services/gemini-sessions');
|
|
4
4
|
const { isGeminiInstalled } = require('../services/gemini-config');
|
|
5
5
|
const {
|
|
6
6
|
emptyProjectList,
|
|
7
7
|
getProjectListSnapshot,
|
|
8
8
|
invalidateProjectSnapshots
|
|
9
9
|
} = require('../services/project-snapshots');
|
|
10
|
+
const { invalidateSessionSnapshots } = require('../services/session-snapshots');
|
|
11
|
+
const { runDashboardSnapshotWorker } = require('../services/dashboard-snapshot-worker');
|
|
10
12
|
|
|
11
13
|
module.exports = (config) => {
|
|
12
|
-
function buildProjectsPayload() {
|
|
13
|
-
const projects = getProjects();
|
|
14
|
-
return {
|
|
15
|
-
projects,
|
|
16
|
-
currentProject: projects[0] ? projects[0].name : null
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
/**
|
|
21
15
|
* GET /api/gemini/projects
|
|
22
16
|
* 获取所有 Gemini 项目列表
|
|
@@ -33,10 +27,11 @@ module.exports = (config) => {
|
|
|
33
27
|
});
|
|
34
28
|
}
|
|
35
29
|
|
|
30
|
+
const force = req.query?.fresh === '1';
|
|
36
31
|
const snapshot = await getProjectListSnapshot('gemini', {
|
|
37
32
|
fallbackValue: emptyProjectList(null),
|
|
38
|
-
force
|
|
39
|
-
refresh:
|
|
33
|
+
force,
|
|
34
|
+
refresh: () => runDashboardSnapshotWorker('projects', 'gemini', config, { force })
|
|
40
35
|
});
|
|
41
36
|
res.json({ ...snapshot.value, meta: snapshot.meta });
|
|
42
37
|
} catch (err) {
|
|
@@ -95,6 +90,7 @@ module.exports = (config) => {
|
|
|
95
90
|
const { projectHash } = req.params;
|
|
96
91
|
const result = deleteProject(projectHash);
|
|
97
92
|
invalidateProjectSnapshots('gemini');
|
|
93
|
+
invalidateSessionSnapshots('gemini', projectHash);
|
|
98
94
|
|
|
99
95
|
res.json(result);
|
|
100
96
|
} catch (err) {
|
|
@@ -13,6 +13,14 @@ const {
|
|
|
13
13
|
} = require('../services/gemini-sessions');
|
|
14
14
|
const { isGeminiInstalled } = require('../services/gemini-config');
|
|
15
15
|
const { loadAliases } = require('../services/alias');
|
|
16
|
+
const {
|
|
17
|
+
defaultProjectInfo,
|
|
18
|
+
emptySessionList,
|
|
19
|
+
getSessionListSnapshot,
|
|
20
|
+
invalidateSessionSnapshots,
|
|
21
|
+
runSessionSnapshotWorker
|
|
22
|
+
} = require('../services/session-snapshots');
|
|
23
|
+
const { invalidateProjectSnapshots } = require('../services/project-snapshots');
|
|
16
24
|
|
|
17
25
|
function normalizeForkOptions(body = {}) {
|
|
18
26
|
const alias = typeof body.alias === 'string' ? body.alias.trim() : '';
|
|
@@ -90,39 +98,23 @@ module.exports = (config) => {
|
|
|
90
98
|
* GET /api/gemini/sessions/:projectHash
|
|
91
99
|
* 获取项目的所有会话
|
|
92
100
|
*/
|
|
93
|
-
router.get('/:projectHash', (req, res) => {
|
|
101
|
+
router.get('/:projectHash', async (req, res) => {
|
|
94
102
|
try {
|
|
95
103
|
if (!isGeminiInstalled()) {
|
|
96
104
|
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
const { projectHash } = req.params;
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const aliases = loadAliases();
|
|
109
|
-
|
|
110
|
-
// 使用彩虹表解析真实路径
|
|
111
|
-
const realPath = getProjectPath(projectHash);
|
|
112
|
-
const path = require('path');
|
|
113
|
-
const displayName = realPath ? path.basename(realPath) : `Project ${projectHash.substring(0, 8)}`;
|
|
114
|
-
|
|
115
|
-
res.json({
|
|
116
|
-
sessions,
|
|
117
|
-
totalSize,
|
|
118
|
-
aliases,
|
|
119
|
-
projectInfo: {
|
|
120
|
-
name: projectHash,
|
|
121
|
-
fullPath: realPath || projectHash,
|
|
122
|
-
path: realPath || projectHash,
|
|
123
|
-
displayName
|
|
124
|
-
}
|
|
108
|
+
const force = req.query?.fresh === '1';
|
|
109
|
+
const snapshot = await getSessionListSnapshot('gemini', projectHash, {
|
|
110
|
+
fallbackValue: emptySessionList(projectHash, {
|
|
111
|
+
aliases: loadAliases(),
|
|
112
|
+
projectInfo: defaultProjectInfo(projectHash)
|
|
113
|
+
}),
|
|
114
|
+
force,
|
|
115
|
+
refresh: () => runSessionSnapshotWorker('gemini', projectHash, {}, { force })
|
|
125
116
|
});
|
|
117
|
+
res.json({ ...snapshot.value, meta: snapshot.meta });
|
|
126
118
|
} catch (err) {
|
|
127
119
|
console.error('[Gemini API] Failed to get sessions:', err);
|
|
128
120
|
res.status(500).json({ error: err.message });
|
|
@@ -328,6 +320,8 @@ module.exports = (config) => {
|
|
|
328
320
|
|
|
329
321
|
const { sessionId } = req.params;
|
|
330
322
|
const result = deleteSession(sessionId);
|
|
323
|
+
invalidateSessionSnapshots('gemini', req.params.projectHash);
|
|
324
|
+
invalidateProjectSnapshots('gemini');
|
|
331
325
|
|
|
332
326
|
res.json(result);
|
|
333
327
|
} catch (err) {
|
|
@@ -377,6 +371,11 @@ module.exports = (config) => {
|
|
|
377
371
|
}
|
|
378
372
|
});
|
|
379
373
|
|
|
374
|
+
if (deletedSessionIds.length > 0) {
|
|
375
|
+
invalidateSessionSnapshots('gemini', req.params.projectHash);
|
|
376
|
+
invalidateProjectSnapshots('gemini');
|
|
377
|
+
}
|
|
378
|
+
|
|
380
379
|
res.json({
|
|
381
380
|
success: failed.length === 0,
|
|
382
381
|
requestedCount: uniqueSessionIds.length,
|
|
@@ -402,6 +401,8 @@ module.exports = (config) => {
|
|
|
402
401
|
|
|
403
402
|
const { sessionId } = req.params;
|
|
404
403
|
const result = forkSession(sessionId, normalizeForkOptions(req.body));
|
|
404
|
+
invalidateSessionSnapshots('gemini', req.params.projectHash);
|
|
405
|
+
invalidateProjectSnapshots('gemini');
|
|
405
406
|
|
|
406
407
|
res.json(result);
|
|
407
408
|
} catch (err) {
|
|
@@ -428,6 +429,7 @@ module.exports = (config) => {
|
|
|
428
429
|
}
|
|
429
430
|
|
|
430
431
|
saveSessionOrder(projectHash, order);
|
|
432
|
+
invalidateSessionSnapshots('gemini', projectHash);
|
|
431
433
|
|
|
432
434
|
res.json({ success: true });
|
|
433
435
|
} catch (err) {
|
|
@@ -23,9 +23,13 @@ const {
|
|
|
23
23
|
} = require('../services/model-detector');
|
|
24
24
|
const {
|
|
25
25
|
findAuthProviderForKey,
|
|
26
|
-
|
|
26
|
+
getCachedOmpAuthProviderSnapshot,
|
|
27
|
+
getOmpAuthProviderCacheMeta,
|
|
28
|
+
warmOmpAuthProviderSnapshot
|
|
27
29
|
} = require('../services/omp-auth-providers');
|
|
28
30
|
|
|
31
|
+
const CHANNEL_LIST_AUTH_OPTIONS = { accountCheck: false, includeStatus: false };
|
|
32
|
+
|
|
29
33
|
function uniqueModels(models = []) {
|
|
30
34
|
const seen = new Set();
|
|
31
35
|
const result = [];
|
|
@@ -136,19 +140,53 @@ function attachAuthProvider(channel, snapshot) {
|
|
|
136
140
|
};
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
function getAuthSnapshotForChannelList() {
|
|
144
|
+
const snapshot = getCachedOmpAuthProviderSnapshot(CHANNEL_LIST_AUTH_OPTIONS);
|
|
145
|
+
const shouldWarm = !snapshot || snapshot.stale;
|
|
146
|
+
if (shouldWarm) {
|
|
147
|
+
warmOmpAuthProviderSnapshot(CHANNEL_LIST_AUTH_OPTIONS);
|
|
148
|
+
}
|
|
149
|
+
const meta = getOmpAuthProviderCacheMeta(CHANNEL_LIST_AUTH_OPTIONS);
|
|
150
|
+
return {
|
|
151
|
+
snapshot,
|
|
152
|
+
meta: {
|
|
153
|
+
...meta,
|
|
154
|
+
stale: !snapshot || Boolean(snapshot.stale),
|
|
155
|
+
refreshing: shouldWarm || meta.refreshing,
|
|
156
|
+
fallback: !snapshot
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildUnavailableAuthProviderMeta(error) {
|
|
162
|
+
return {
|
|
163
|
+
cached: false,
|
|
164
|
+
stale: false,
|
|
165
|
+
refreshing: false,
|
|
166
|
+
fallback: true,
|
|
167
|
+
checkedAt: new Date().toISOString(),
|
|
168
|
+
error: error || 'omp-not-available'
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
139
172
|
module.exports = () => {
|
|
140
173
|
router.get('/', (req, res) => {
|
|
141
174
|
try {
|
|
142
175
|
if (!isOmpInstalled()) {
|
|
143
|
-
return res.json({
|
|
176
|
+
return res.json({
|
|
177
|
+
channels: [],
|
|
178
|
+
installed: false,
|
|
179
|
+
error: 'OMP CLI not installed',
|
|
180
|
+
authProviderMeta: buildUnavailableAuthProviderMeta('OMP CLI not installed')
|
|
181
|
+
});
|
|
144
182
|
}
|
|
145
183
|
const data = getChannels();
|
|
146
|
-
const
|
|
184
|
+
const { snapshot: authSnapshot, meta: authProviderMeta } = getAuthSnapshotForChannelList();
|
|
147
185
|
const channels = (data.channels || []).map(ch => ({
|
|
148
186
|
...attachAuthProvider(ch, authSnapshot),
|
|
149
187
|
health: getChannelHealthStatus(ch.id, 'omp')
|
|
150
188
|
}));
|
|
151
|
-
res.json({ channels, installed: true });
|
|
189
|
+
res.json({ channels, installed: true, authProviderMeta });
|
|
152
190
|
} catch (err) {
|
|
153
191
|
console.error('[OMP Channels API] Failed to get channels:', err);
|
|
154
192
|
res.status(500).json({ error: err.message });
|
|
@@ -158,14 +196,19 @@ module.exports = () => {
|
|
|
158
196
|
router.get('/enabled', (req, res) => {
|
|
159
197
|
try {
|
|
160
198
|
if (!isOmpInstalled()) {
|
|
161
|
-
return res.json({
|
|
199
|
+
return res.json({
|
|
200
|
+
channels: [],
|
|
201
|
+
installed: false,
|
|
202
|
+
error: 'OMP CLI not installed',
|
|
203
|
+
authProviderMeta: buildUnavailableAuthProviderMeta('OMP CLI not installed')
|
|
204
|
+
});
|
|
162
205
|
}
|
|
163
206
|
const data = getChannels();
|
|
164
|
-
const
|
|
207
|
+
const { snapshot: authSnapshot, meta: authProviderMeta } = getAuthSnapshotForChannelList();
|
|
165
208
|
const channels = (data.channels || [])
|
|
166
209
|
.filter(ch => ch.enabled !== false)
|
|
167
210
|
.map(ch => ({ ...attachAuthProvider(ch, authSnapshot), health: getChannelHealthStatus(ch.id, 'omp') }));
|
|
168
|
-
res.json({ channels, installed: true });
|
|
211
|
+
res.json({ channels, installed: true, authProviderMeta });
|
|
169
212
|
} catch (err) {
|
|
170
213
|
console.error('[OMP Channels API] Failed to get enabled channels:', err);
|
|
171
214
|
res.status(500).json({ error: err.message });
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
3
|
const {
|
|
4
|
-
getProjects,
|
|
5
4
|
saveProjectOrder,
|
|
6
5
|
deleteProject,
|
|
7
6
|
isOmpInstalled
|
|
@@ -11,20 +10,14 @@ const {
|
|
|
11
10
|
getProjectListSnapshot,
|
|
12
11
|
invalidateProjectSnapshots
|
|
13
12
|
} = require('../services/project-snapshots');
|
|
13
|
+
const { invalidateSessionSnapshots } = require('../services/session-snapshots');
|
|
14
|
+
const { runDashboardSnapshotWorker } = require('../services/dashboard-snapshot-worker');
|
|
14
15
|
|
|
15
16
|
function isNotFoundError(error) {
|
|
16
17
|
return !!(error && error.message === 'Project not found');
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
module.exports = () => {
|
|
20
|
-
function buildProjectsPayload() {
|
|
21
|
-
const projects = getProjects();
|
|
22
|
-
return {
|
|
23
|
-
projects,
|
|
24
|
-
currentProject: projects[0] ? projects[0].name : null
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
router.get('/', async (req, res) => {
|
|
29
22
|
try {
|
|
30
23
|
if (!isOmpInstalled()) {
|
|
@@ -36,10 +29,11 @@ module.exports = () => {
|
|
|
36
29
|
});
|
|
37
30
|
}
|
|
38
31
|
|
|
32
|
+
const force = req.query?.fresh === '1';
|
|
39
33
|
const snapshot = await getProjectListSnapshot('omp', {
|
|
40
34
|
fallbackValue: emptyProjectList(null),
|
|
41
|
-
force
|
|
42
|
-
refresh:
|
|
35
|
+
force,
|
|
36
|
+
refresh: () => runDashboardSnapshotWorker('projects', 'omp', {}, { force })
|
|
43
37
|
});
|
|
44
38
|
res.json({ ...snapshot.value, meta: snapshot.meta });
|
|
45
39
|
} catch (err) {
|
|
@@ -79,6 +73,7 @@ module.exports = () => {
|
|
|
79
73
|
}
|
|
80
74
|
const result = deleteProject(req.params.projectName);
|
|
81
75
|
invalidateProjectSnapshots('omp');
|
|
76
|
+
invalidateSessionSnapshots('omp', req.params.projectName);
|
|
82
77
|
res.json(result);
|
|
83
78
|
} catch (err) {
|
|
84
79
|
if (isNotFoundError(err)) {
|