coding-tool-x 3.6.13 → 3.7.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.
Files changed (88) hide show
  1. package/README.md +45 -33
  2. package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dpje9L84.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-Cz6kasek.js} +1 -1
  4. package/dist/web/assets/Home-BwTaYu3q.js +1 -0
  5. package/dist/web/assets/Home-Jom_ZcdY.css +1 -0
  6. package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-DsN-xNXx.js} +1 -1
  7. package/dist/web/assets/ProjectList-4BCObimU.js +1 -0
  8. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-D3a0oRum.css} +1 -1
  9. package/dist/web/assets/SessionList-D4c8FRzq.js +1 -0
  10. package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-C-KYbXB5.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-B-BlPJHX.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-Btfvh0GG.js +2 -0
  14. package/dist/web/assets/index-CLNa6DhH.css +1 -0
  15. package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
  16. package/dist/web/index.html +4 -4
  17. package/package.json +3 -1
  18. package/src/commands/channels.js +85 -24
  19. package/src/commands/daemon.js +7 -4
  20. package/src/commands/doctor.js +17 -8
  21. package/src/commands/logs.js +46 -6
  22. package/src/commands/port-config.js +83 -75
  23. package/src/commands/proxy-control.js +59 -26
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/switch.js +5 -1
  26. package/src/commands/toggle-proxy.js +131 -53
  27. package/src/commands/ui.js +3 -4
  28. package/src/commands/workspace.js +3 -35
  29. package/src/config/default.js +19 -2
  30. package/src/config/loader.js +5 -76
  31. package/src/config/paths.js +96 -11
  32. package/src/index.js +10 -16
  33. package/src/server/api/commands.js +1 -1
  34. package/src/server/api/config-export.js +4 -2
  35. package/src/server/api/config-registry.js +40 -34
  36. package/src/server/api/config.js +5 -1
  37. package/src/server/api/dashboard.js +22 -6
  38. package/src/server/api/mcp.js +5 -3
  39. package/src/server/api/omp-channels.js +349 -0
  40. package/src/server/api/omp-config.js +164 -0
  41. package/src/server/api/omp-projects.js +76 -0
  42. package/src/server/api/omp-proxy.js +154 -0
  43. package/src/server/api/omp-sessions.js +334 -0
  44. package/src/server/api/omp-statistics.js +40 -0
  45. package/src/server/api/plugins.js +1 -1
  46. package/src/server/api/prompts.js +3 -2
  47. package/src/server/api/settings.js +2 -0
  48. package/src/server/api/skills.js +1 -1
  49. package/src/server/api/workspaces.js +3 -41
  50. package/src/server/index.js +41 -0
  51. package/src/server/omp-proxy-server.js +66 -0
  52. package/src/server/services/agents-service.js +2 -1
  53. package/src/server/services/base/proxy-utils.js +1 -0
  54. package/src/server/services/channel-balance.js +151 -1
  55. package/src/server/services/channel-scheduler.js +7 -1
  56. package/src/server/services/commands-service.js +11 -2
  57. package/src/server/services/config-export-service.js +296 -39
  58. package/src/server/services/config-registry-service.js +20 -18
  59. package/src/server/services/config-sync-manager.js +115 -6
  60. package/src/server/services/config-sync-service.js +4 -2
  61. package/src/server/services/config-templates-service.js +183 -45
  62. package/src/server/services/mcp-service.js +217 -33
  63. package/src/server/services/notification-hooks.js +138 -6
  64. package/src/server/services/omp-auth-providers.js +294 -0
  65. package/src/server/services/omp-channels.js +94 -0
  66. package/src/server/services/omp-config.js +269 -0
  67. package/src/server/services/omp-sessions.js +490 -0
  68. package/src/server/services/omp-settings-manager.js +975 -0
  69. package/src/server/services/omp-statistics-service.js +317 -0
  70. package/src/server/services/plugins-service.js +673 -11
  71. package/src/server/services/prompts-service.js +112 -7
  72. package/src/server/services/proxy-log-helper.js +1 -0
  73. package/src/server/services/proxy-runtime.js +5 -1
  74. package/src/server/services/sessions.js +19 -35
  75. package/src/server/services/skill-service.js +13 -3
  76. package/src/server/services/speed-test.js +34 -28
  77. package/src/server/services/ui-config.js +50 -12
  78. package/src/server/services/workspace-service.js +117 -225
  79. package/src/server/websocket-server.js +7 -1
  80. package/src/shared/platforms.js +201 -0
  81. package/src/utils/session.js +4 -10
  82. package/dist/web/assets/Home-BiaKZMah.js +0 -1
  83. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  84. package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
  85. package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
  86. package/dist/web/assets/index-BpBFF3b2.js +0 -2
  87. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  88. package/src/utils/cli-flags.js +0 -7
@@ -0,0 +1,154 @@
1
+ const express = require('express');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const router = express.Router();
5
+ const {
6
+ startOmpProxyServer,
7
+ stopOmpProxyServer,
8
+ getOmpProxyStatus
9
+ } = require('../omp-proxy-server');
10
+ const {
11
+ getChannels,
12
+ getEnabledChannels,
13
+ markChannelAsRecentlyUsed
14
+ } = require('../services/omp-channels');
15
+ const { getSchedulerState } = require('../services/channel-scheduler');
16
+ const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
17
+
18
+ function sanitizeChannel(channel) {
19
+ if (!channel) return null;
20
+ return {
21
+ id: channel.id,
22
+ name: channel.name,
23
+ baseUrl: channel.baseUrl,
24
+ websiteUrl: channel.websiteUrl
25
+ };
26
+ }
27
+
28
+ function selectLatestEnabledChannel(channels) {
29
+ const enabledChannels = (channels || []).filter(ch => ch.enabled !== false);
30
+ if (enabledChannels.length === 0) return null;
31
+ return enabledChannels.reduce((latest, current) => {
32
+ const latestTs = Number(latest?.updatedAt || latest?.createdAt || 0);
33
+ const currentTs = Number(current?.updatedAt || current?.createdAt || 0);
34
+ return currentTs > latestTs ? current : latest;
35
+ }, enabledChannels[0]);
36
+ }
37
+
38
+ function saveActiveChannelId(channelId) {
39
+ ensureStorageDirMigrated();
40
+ const filePath = PATHS.activeChannel.omp;
41
+ const dir = path.dirname(filePath);
42
+ if (!fs.existsSync(dir)) {
43
+ fs.mkdirSync(dir, { recursive: true });
44
+ }
45
+ fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
46
+ }
47
+
48
+ function loadActiveChannelId() {
49
+ ensureStorageDirMigrated();
50
+ try {
51
+ if (!fs.existsSync(PATHS.activeChannel.omp)) return null;
52
+ const data = JSON.parse(fs.readFileSync(PATHS.activeChannel.omp, 'utf8'));
53
+ return data.activeChannelId || null;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function removeActiveChannelFile() {
60
+ ensureStorageDirMigrated();
61
+ if (fs.existsSync(PATHS.activeChannel.omp)) {
62
+ fs.unlinkSync(PATHS.activeChannel.omp);
63
+ }
64
+ }
65
+
66
+ function resolveActiveChannel(channels, activeChannelId = null) {
67
+ if (!Array.isArray(channels) || channels.length === 0) return null;
68
+ if (activeChannelId) {
69
+ const matched = channels.find(channel => channel.id === activeChannelId);
70
+ if (matched) return matched;
71
+ }
72
+ return selectLatestEnabledChannel(channels)
73
+ || channels.find(channel => channel.enabled !== false)
74
+ || channels[0]
75
+ || null;
76
+ }
77
+
78
+ router.get('/status', (req, res) => {
79
+ try {
80
+ const proxy = getOmpProxyStatus();
81
+ const { channels } = getChannels();
82
+ const enabledChannels = channels.filter(ch => ch.enabled !== false);
83
+ const activeChannel = resolveActiveChannel(channels, loadActiveChannelId());
84
+ res.json({
85
+ proxy,
86
+ config: {
87
+ mode: 'models-yml-provider-config',
88
+ nativeProxyProtocol: false
89
+ },
90
+ activeChannel: sanitizeChannel(activeChannel),
91
+ enabledChannelsCount: enabledChannels.length,
92
+ totalChannelsCount: channels.length
93
+ });
94
+ } catch (error) {
95
+ res.status(500).json({ error: error.message });
96
+ }
97
+ });
98
+
99
+ router.post('/start', async (req, res) => {
100
+ try {
101
+ const enabledChannels = getEnabledChannels();
102
+ if (enabledChannels.length === 0) {
103
+ return res.status(400).json({
104
+ error: 'No enabled OMP channel found. Please create and enable a channel first.'
105
+ });
106
+ }
107
+
108
+ let currentChannel = selectLatestEnabledChannel(enabledChannels) || enabledChannels[0];
109
+ currentChannel = markChannelAsRecentlyUsed(currentChannel.id);
110
+ saveActiveChannelId(currentChannel.id);
111
+
112
+ const proxyResult = await startOmpProxyServer();
113
+ if (!proxyResult.success) {
114
+ return res.status(500).json({ error: 'Failed to enable OMP models.yml provider config' });
115
+ }
116
+
117
+ const { broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
118
+ const updatedStatus = getOmpProxyStatus();
119
+ const { channels: latestChannels } = getChannels();
120
+ const activeChannel = latestChannels.find(channel => channel.id === currentChannel.id) || currentChannel;
121
+ broadcastProxyState('omp', updatedStatus, activeChannel, latestChannels);
122
+ broadcastSchedulerState('omp', getSchedulerState('omp'));
123
+
124
+ res.json({
125
+ success: true,
126
+ port: proxyResult.port,
127
+ activeChannel: sanitizeChannel(activeChannel),
128
+ mode: 'models-yml-provider-config',
129
+ sync: proxyResult.sync || null,
130
+ warnings: proxyResult.warnings || [],
131
+ message: `OMP models.yml provider config enabled, active channel: ${activeChannel.name}`
132
+ });
133
+ } catch (error) {
134
+ console.error('[OMP Proxy] Start failed:', error);
135
+ res.status(500).json({ error: error.message });
136
+ }
137
+ });
138
+
139
+ router.post('/stop', async (req, res) => {
140
+ try {
141
+ const result = await stopOmpProxyServer();
142
+ removeActiveChannelFile();
143
+ const { broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
144
+ const { channels } = getChannels();
145
+ broadcastProxyState('omp', getOmpProxyStatus(), null, channels);
146
+ broadcastSchedulerState('omp', getSchedulerState('omp'));
147
+ res.json({ success: true, ...result });
148
+ } catch (error) {
149
+ console.error('[OMP Proxy] Stop failed:', error);
150
+ res.status(500).json({ error: error.message });
151
+ }
152
+ });
153
+
154
+ module.exports = router;
@@ -0,0 +1,334 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const {
4
+ buildLaunchCommand,
5
+ getProjects,
6
+ getSessionsByProject,
7
+ getSessionById,
8
+ getSessionMessages,
9
+ getRecentSessions,
10
+ searchSessions,
11
+ deleteSession,
12
+ forkSession,
13
+ saveSessionOrder,
14
+ isOmpInstalled,
15
+ HOME_DIR
16
+ } = require('../services/omp-sessions');
17
+ const { loadAliases } = require('../services/alias');
18
+ const { broadcastLog } = require('../websocket-server');
19
+
20
+ function isNotFoundError(error) {
21
+ if (!error || !error.message) return false;
22
+ return error.message === 'Session not found' || error.message === 'Project not found';
23
+ }
24
+
25
+ function quoteForShell(value) {
26
+ return `"${String(value).replace(/"/g, '\\"')}"`;
27
+ }
28
+
29
+ module.exports = () => {
30
+ router.get('/search/global', (req, res) => {
31
+ try {
32
+ if (!isOmpInstalled()) {
33
+ return res.status(404).json({ error: 'OMP CLI not installed' });
34
+ }
35
+ const { keyword } = req.query;
36
+ const parsedContextLength = req.query.context ? parseInt(req.query.context, 10) : 35;
37
+ const contextLength = Number.isFinite(parsedContextLength) ? parsedContextLength : 35;
38
+ if (!keyword) {
39
+ return res.status(400).json({ error: 'Keyword is required' });
40
+ }
41
+ const sessions = searchSessions(keyword, contextLength);
42
+ const totalMatches = sessions.reduce((sum, session) => sum + (session.matchCount || 0), 0);
43
+ res.json({ keyword, totalMatches, sessions, source: 'omp' });
44
+ } catch (err) {
45
+ console.error('[OMP API] Failed to search sessions:', err);
46
+ res.status(500).json({ error: err.message });
47
+ }
48
+ });
49
+
50
+ router.get('/recent/list', (req, res) => {
51
+ try {
52
+ if (!isOmpInstalled()) {
53
+ return res.status(404).json({ error: 'OMP CLI not installed' });
54
+ }
55
+ const limit = parseInt(req.query.limit, 10) || 5;
56
+ res.json({ sessions: getRecentSessions(limit), source: 'omp' });
57
+ } catch (err) {
58
+ console.error('[OMP API] Failed to get recent sessions:', err);
59
+ res.status(500).json({ error: err.message });
60
+ }
61
+ });
62
+
63
+ router.get('/:projectName/search', (req, res) => {
64
+ try {
65
+ if (!isOmpInstalled()) {
66
+ return res.status(404).json({ error: 'OMP CLI not installed' });
67
+ }
68
+ const { projectName } = req.params;
69
+ const { keyword } = req.query;
70
+ const parsedContextLength = req.query.context ? parseInt(req.query.context, 10) : 35;
71
+ const contextLength = Number.isFinite(parsedContextLength) ? parsedContextLength : 35;
72
+ if (!keyword) {
73
+ return res.status(400).json({ error: 'Keyword is required' });
74
+ }
75
+ const sessions = searchSessions(keyword, contextLength, projectName);
76
+ const totalMatches = sessions.reduce((sum, session) => sum + (session.matchCount || 0), 0);
77
+ res.json({ keyword, totalMatches, sessions });
78
+ } catch (err) {
79
+ console.error('[OMP API] Failed to search project sessions:', err);
80
+ res.status(500).json({ error: err.message });
81
+ }
82
+ });
83
+
84
+ router.get('/:projectName', (req, res) => {
85
+ try {
86
+ if (!isOmpInstalled()) {
87
+ return res.status(404).json({ error: 'OMP CLI not installed' });
88
+ }
89
+ const { projectName } = req.params;
90
+ const sessions = getSessionsByProject(projectName);
91
+ const aliases = loadAliases();
92
+ const project = getProjects().find(p => p.name === projectName);
93
+ const totalSize = sessions.reduce((sum, session) => sum + (session.size || 0), 0);
94
+ res.json({
95
+ sessions,
96
+ totalSize,
97
+ aliases,
98
+ projectInfo: {
99
+ name: projectName,
100
+ fullPath: project?.fullPath || project?.path || projectName,
101
+ path: project?.path || projectName,
102
+ displayName: project?.displayName || projectName
103
+ }
104
+ });
105
+ } catch (err) {
106
+ console.error('[OMP API] Failed to get sessions:', err);
107
+ res.status(500).json({ error: err.message });
108
+ }
109
+ });
110
+
111
+ router.get('/:projectName/:sessionId/status', (req, res) => {
112
+ try {
113
+ if (!isOmpInstalled()) {
114
+ return res.status(404).json({ error: 'OMP CLI not installed' });
115
+ }
116
+ const session = getSessionById(req.params.sessionId);
117
+ if (!session) {
118
+ return res.status(404).json({ error: 'Session not found' });
119
+ }
120
+ res.json({
121
+ sessionId: req.params.sessionId,
122
+ lastModified: session.mtime || null,
123
+ size: session.size || 0
124
+ });
125
+ } catch (err) {
126
+ console.error('[OMP API] Failed to get session status:', err);
127
+ res.status(500).json({ error: err.message });
128
+ }
129
+ });
130
+
131
+ router.get('/:projectName/:sessionId/outline', (req, res) => {
132
+ try {
133
+ if (!isOmpInstalled()) {
134
+ return res.status(404).json({ error: 'OMP CLI not installed' });
135
+ }
136
+ const session = getSessionById(req.params.sessionId);
137
+ if (!session) {
138
+ return res.status(404).json({ error: 'Session not found' });
139
+ }
140
+ let userMessageNumber = 0;
141
+ const items = [];
142
+ for (const msg of getSessionMessages(req.params.sessionId)) {
143
+ if (msg.type !== 'user') continue;
144
+ const preview = typeof msg.content === 'string' ? msg.content.trim() : '';
145
+ if (!preview) continue;
146
+ userMessageNumber += 1;
147
+ items.push({
148
+ userMessageNumber,
149
+ preview: preview.length > 42 ? `${preview.slice(0, 42)}...` : preview,
150
+ timestamp: msg.timestamp || null
151
+ });
152
+ }
153
+ res.json({ sessionId: req.params.sessionId, items });
154
+ } catch (err) {
155
+ console.error('[OMP API] Failed to get session outline:', err);
156
+ res.status(500).json({ error: err.message });
157
+ }
158
+ });
159
+
160
+ router.get('/:projectName/:sessionId/messages', (req, res) => {
161
+ try {
162
+ if (!isOmpInstalled()) {
163
+ return res.status(404).json({ error: 'OMP CLI not installed' });
164
+ }
165
+ const { sessionId } = req.params;
166
+ const { page = 1, limit = 20, order = 'desc' } = req.query;
167
+ const session = getSessionById(sessionId);
168
+ if (!session) {
169
+ return res.status(404).json({ error: 'Session not found' });
170
+ }
171
+ let userMessageNumber = 0;
172
+ const convertedMessages = getSessionMessages(sessionId).map((message) => {
173
+ if (message.type !== 'user') return message;
174
+ userMessageNumber += 1;
175
+ return { ...message, userMessageNumber };
176
+ });
177
+ const pageNum = parseInt(page, 10) || 1;
178
+ const limitNum = parseInt(limit, 10) || 20;
179
+ const orderedMessages = order === 'desc' ? [...convertedMessages].reverse() : convertedMessages;
180
+ const start = (pageNum - 1) * limitNum;
181
+ const end = start + limitNum;
182
+ res.json({
183
+ messages: orderedMessages.slice(start, end),
184
+ metadata: {
185
+ gitBranch: null,
186
+ gitRepository: null,
187
+ cwd: session?.directory || null,
188
+ provider: session?.provider || '',
189
+ model: session?.model || 'omp'
190
+ },
191
+ pagination: {
192
+ page: pageNum,
193
+ limit: limitNum,
194
+ total: orderedMessages.length,
195
+ hasMore: end < orderedMessages.length
196
+ }
197
+ });
198
+ } catch (err) {
199
+ console.error('[OMP API] Failed to get session messages:', err);
200
+ res.status(500).json({ error: err.message });
201
+ }
202
+ });
203
+
204
+ router.delete('/:projectName/:sessionId', (req, res) => {
205
+ try {
206
+ if (!isOmpInstalled()) {
207
+ return res.status(404).json({ error: 'OMP CLI not installed' });
208
+ }
209
+ res.json(deleteSession(req.params.sessionId));
210
+ } catch (err) {
211
+ if (isNotFoundError(err)) {
212
+ return res.status(404).json({ error: err.message });
213
+ }
214
+ console.error('[OMP API] Failed to delete session:', err);
215
+ res.status(500).json({ error: err.message });
216
+ }
217
+ });
218
+
219
+ router.post('/:projectName/batch-delete', (req, res) => {
220
+ try {
221
+ if (!isOmpInstalled()) {
222
+ return res.status(404).json({ error: 'OMP CLI not installed' });
223
+ }
224
+ const { sessionIds } = req.body || {};
225
+ if (!Array.isArray(sessionIds) || sessionIds.length === 0) {
226
+ return res.status(400).json({ error: 'sessionIds must be a non-empty array' });
227
+ }
228
+ const uniqueSessionIds = Array.from(new Set(
229
+ sessionIds
230
+ .filter(sessionId => typeof sessionId === 'string')
231
+ .map(sessionId => sessionId.trim())
232
+ .filter(Boolean)
233
+ ));
234
+ if (uniqueSessionIds.length === 0) {
235
+ return res.status(400).json({ error: 'sessionIds must be a non-empty array' });
236
+ }
237
+ const deletedSessionIds = [];
238
+ const failed = [];
239
+ uniqueSessionIds.forEach((sessionId) => {
240
+ try {
241
+ deleteSession(sessionId);
242
+ deletedSessionIds.push(sessionId);
243
+ } catch (err) {
244
+ failed.push({ sessionId, error: err.message });
245
+ }
246
+ });
247
+ res.json({
248
+ success: failed.length === 0,
249
+ requestedCount: uniqueSessionIds.length,
250
+ deletedCount: deletedSessionIds.length,
251
+ deletedSessionIds,
252
+ failed
253
+ });
254
+ } catch (err) {
255
+ console.error('[OMP API] Failed to batch delete sessions:', err);
256
+ res.status(500).json({ error: err.message });
257
+ }
258
+ });
259
+
260
+ router.post('/:projectName/:sessionId/fork', (req, res) => {
261
+ try {
262
+ if (!isOmpInstalled()) {
263
+ return res.status(404).json({ error: 'OMP CLI not installed' });
264
+ }
265
+ res.json(forkSession(req.params.sessionId, req.body || {}));
266
+ } catch (err) {
267
+ if (isNotFoundError(err)) {
268
+ return res.status(404).json({ error: err.message });
269
+ }
270
+ console.error('[OMP API] Failed to fork session:', err);
271
+ res.status(500).json({ error: err.message });
272
+ }
273
+ });
274
+
275
+ router.post('/:projectName/order', (req, res) => {
276
+ try {
277
+ if (!isOmpInstalled()) {
278
+ return res.status(404).json({ error: 'OMP CLI not installed' });
279
+ }
280
+ const { order } = req.body || {};
281
+ if (!Array.isArray(order)) {
282
+ return res.status(400).json({ error: 'order must be an array' });
283
+ }
284
+ saveSessionOrder(req.params.projectName, order);
285
+ res.json({ success: true });
286
+ } catch (err) {
287
+ console.error('[OMP API] Failed to save session order:', err);
288
+ res.status(500).json({ error: err.message });
289
+ }
290
+ });
291
+
292
+ router.post('/:projectName/:sessionId/launch', (req, res) => {
293
+ try {
294
+ if (!isOmpInstalled()) {
295
+ return res.status(404).json({ error: 'OMP CLI not installed' });
296
+ }
297
+ const { projectName, sessionId } = req.params;
298
+ const session = getSessionById(sessionId);
299
+ if (!session) {
300
+ return res.status(404).json({ error: 'Session not found' });
301
+ }
302
+ const project = getProjects().find(p => p.name === projectName);
303
+ const cwd = session.directory || project?.fullPath || HOME_DIR;
304
+ const command = buildLaunchCommand(sessionId, cwd, req.body || {});
305
+ const copyCommand = `cd ${quoteForShell(cwd)} && ${command}`;
306
+
307
+ broadcastLog({
308
+ type: 'action',
309
+ action: 'launch_pi_session',
310
+ message: `复制 OMP 会话启动命令 ${sessionId.substring(0, 8)}`,
311
+ sessionId,
312
+ tool: 'omp',
313
+ toolType: 'omp',
314
+ source: 'omp',
315
+ timestamp: Date.now()
316
+ });
317
+
318
+ res.json({
319
+ success: true,
320
+ cwd,
321
+ sessionFile: session.filePath,
322
+ sessionId,
323
+ tool: 'omp',
324
+ command,
325
+ copyCommand
326
+ });
327
+ } catch (err) {
328
+ console.error('[OMP API] Failed to prepare launch command:', err);
329
+ res.status(500).json({ error: err.message });
330
+ }
331
+ });
332
+
333
+ return router;
334
+ };
@@ -0,0 +1,40 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const {
4
+ getStatistics,
5
+ getDailyStatistics,
6
+ getTodayStatistics
7
+ } = require('../services/omp-statistics-service');
8
+
9
+ router.get('/summary', (req, res) => {
10
+ try {
11
+ res.json(getStatistics());
12
+ } catch (error) {
13
+ console.error('[OMP] Failed to get statistics:', error);
14
+ res.status(500).json({ error: 'Failed to get statistics' });
15
+ }
16
+ });
17
+
18
+ router.get('/today', (req, res) => {
19
+ try {
20
+ res.json(getTodayStatistics());
21
+ } catch (error) {
22
+ console.error('[OMP] Failed to get today statistics:', error);
23
+ res.status(500).json({ error: 'Failed to get today statistics' });
24
+ }
25
+ });
26
+
27
+ router.get('/daily/:date', (req, res) => {
28
+ try {
29
+ const { date } = req.params;
30
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
31
+ return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
32
+ }
33
+ res.json(getDailyStatistics(date));
34
+ } catch (error) {
35
+ console.error('[OMP] Failed to get daily statistics:', error);
36
+ res.status(500).json({ error: 'Failed to get daily statistics' });
37
+ }
38
+ });
39
+
40
+ module.exports = router;
@@ -10,7 +10,7 @@ const { maskToken } = require('../services/oauth-utils');
10
10
  const { sendApiError } = require('./validation-errors');
11
11
 
12
12
  const router = express.Router();
13
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
13
+ const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
14
14
  const pluginServices = new Map();
15
15
 
16
16
  function resolvePlatform(rawPlatform) {
@@ -5,6 +5,7 @@
5
5
  const express = require('express');
6
6
  const router = express.Router();
7
7
  const promptsService = require('../services/prompts-service');
8
+ const PROMPT_FILE_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
8
9
 
9
10
  /**
10
11
  * GET /api/prompts/presets
@@ -203,7 +204,7 @@ router.get('/platform/:platform', (req, res) => {
203
204
  try {
204
205
  const { platform } = req.params;
205
206
 
206
- if (!['claude', 'codex', 'gemini', 'opencode'].includes(platform)) {
207
+ if (!PROMPT_FILE_PLATFORMS.includes(platform)) {
207
208
  return res.status(400).json({
208
209
  success: false,
209
210
  error: `无效的平台: ${platform}`
@@ -234,7 +235,7 @@ router.post('/import/:platform', (req, res) => {
234
235
  const { platform } = req.params;
235
236
  const { name } = req.body;
236
237
 
237
- if (!['claude', 'codex', 'gemini', 'opencode'].includes(platform)) {
238
+ if (!PROMPT_FILE_PLATFORMS.includes(platform)) {
238
239
  return res.status(400).json({
239
240
  success: false,
240
241
  error: `无效的平台: ${platform}`
@@ -90,6 +90,7 @@ function handleSaveModelSettings(req, res) {
90
90
  const config = loadConfig();
91
91
  const newConfig = {
92
92
  ...config,
93
+ projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
93
94
  modelMetadataOverrides: overrides && typeof overrides === 'object'
94
95
  ? overrides
95
96
  : (config.modelMetadataOverrides || {})
@@ -123,6 +124,7 @@ function handleDeleteModelOverride(req, res) {
123
124
 
124
125
  const newConfig = {
125
126
  ...config,
127
+ projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
126
128
  modelMetadataOverrides: overrides
127
129
  };
128
130
  saveConfig(newConfig);
@@ -8,7 +8,7 @@ const { maskToken } = require('../services/oauth-utils');
8
8
  const { sendApiError } = require('./validation-errors');
9
9
 
10
10
  const router = express.Router();
11
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
11
+ const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
12
12
  const skillServices = new Map();
13
13
 
14
14
  function resolvePlatform(rawPlatform) {
@@ -33,24 +33,6 @@ function validateBranchName(branchName) {
33
33
  }
34
34
  }
35
35
 
36
- function normalizeBranchMode(branchMode) {
37
- if (typeof branchMode !== 'string' || !branchMode.trim()) {
38
- return '';
39
- }
40
- return branchMode.trim();
41
- }
42
-
43
- function validateBranchMode(branchMode) {
44
- const normalized = normalizeBranchMode(branchMode);
45
- if (!normalized) {
46
- return { valid: true, normalized: '' };
47
- }
48
- if (normalized !== 'existing' && normalized !== 'new') {
49
- return { valid: false, normalized, message: `分支模式不合法: ${normalized}` };
50
- }
51
- return { valid: true, normalized };
52
- }
53
-
54
36
  /**
55
37
  * GET /api/workspaces
56
38
  * 获取所有工作区列表
@@ -153,7 +135,7 @@ router.get('/check-git/*', (req, res) => {
153
135
 
154
136
  /**
155
137
  * GET /api/workspaces/available-projects
156
- * 获取所有渠道(Claude/Codex/Gemini)的项目并集
138
+ * 获取所有渠道(Claude/Codex/Gemini/OpenCode/OMP)的项目并集
157
139
  */
158
140
  router.get('/available-projects', async (req, res) => {
159
141
  try {
@@ -244,17 +226,6 @@ router.post('/', (req, res) => {
244
226
  });
245
227
  }
246
228
 
247
- const branchModeValidation = validateBranchMode(proj.branchMode);
248
- if (!branchModeValidation.valid) {
249
- return res.status(400).json({
250
- success: false,
251
- message: branchModeValidation.message
252
- });
253
- }
254
- if (branchModeValidation.normalized) {
255
- proj.branchMode = branchModeValidation.normalized;
256
- }
257
-
258
229
  const normalizedBranch = normalizeBranchName(proj.branch);
259
230
  if (normalizedBranch) {
260
231
  const branchValidation = validateBranchName(normalizedBranch);
@@ -358,10 +329,9 @@ router.put('/:id/last-used', (req, res) => {
358
329
  router.post('/:id/projects', (req, res) => {
359
330
  try {
360
331
  const { id } = req.params;
361
- const { sourcePath, name, createWorktree, branch, baseBranch, branchMode } = req.body;
332
+ const { sourcePath, name, createWorktree, branch, baseBranch } = req.body;
362
333
  const normalizedBranch = normalizeBranchName(branch);
363
334
  const normalizedBaseBranch = normalizeBranchName(baseBranch);
364
- const branchModeValidation = validateBranchMode(branchMode);
365
335
 
366
336
  if (!sourcePath || !sourcePath.trim()) {
367
337
  return res.status(400).json({
@@ -370,13 +340,6 @@ router.post('/:id/projects', (req, res) => {
370
340
  });
371
341
  }
372
342
 
373
- if (!branchModeValidation.valid) {
374
- return res.status(400).json({
375
- success: false,
376
- message: branchModeValidation.message
377
- });
378
- }
379
-
380
343
  if (normalizedBranch) {
381
344
  const branchValidation = validateBranchName(normalizedBranch);
382
345
  if (!branchValidation.valid) {
@@ -401,7 +364,6 @@ router.post('/:id/projects', (req, res) => {
401
364
  sourcePath,
402
365
  name,
403
366
  createWorktree,
404
- branchMode: branchModeValidation.normalized || branchMode,
405
367
  branch: normalizedBranch || branch,
406
368
  baseBranch: normalizedBaseBranch || baseBranch
407
369
  });
@@ -449,7 +411,7 @@ router.delete('/:id/projects/:projectName', (req, res) => {
449
411
  /**
450
412
  * POST /api/workspaces/:id/launch
451
413
  * 获取在工作区启动 CLI 工具的命令
452
- * Body: { tool: 'claude' | 'codex' | 'gemini', projectName?: string }
414
+ * Body: { tool: 'claude' | 'codex' | 'gemini' | 'opencode' | 'omp' | 'omp', projectName?: string }
453
415
  */
454
416
  router.post('/:id/launch', (req, res) => {
455
417
  try {