coding-tool-x 3.7.11 → 3.7.13

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 (44) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +1 -1
  3. package/dist/web/assets/{Analytics-Ck_00Brj.js → Analytics-8dRv1aYW.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-Cpw2vUa8.js → ConfigTemplates-BQRXSICO.js} +1 -1
  5. package/dist/web/assets/Home-BtzWhKIa.js +1 -0
  6. package/dist/web/assets/Home-DYVy4cGZ.css +1 -0
  7. package/dist/web/assets/{PluginManager-2RlvEJCT.js → PluginManager-BTa-Ygf6.js} +1 -1
  8. package/dist/web/assets/{ProjectList-_ecCngpl.js → ProjectList-F_cB8ldA.js} +1 -1
  9. package/dist/web/assets/{SessionList-DlWcw3hO.js → SessionList-LOrbPQvu.js} +1 -1
  10. package/dist/web/assets/SkillManager-BsX8Qpj0.css +1 -0
  11. package/dist/web/assets/SkillManager-BzI_t-yV.js +1 -0
  12. package/dist/web/assets/{WorkspaceManager-3mqghi09.js → WorkspaceManager-Db9RGiz7.js} +1 -1
  13. package/dist/web/assets/index-DIWJmyL6.js +2 -0
  14. package/dist/web/assets/{index-BceXgSSe.css → index-bJRG3RXH.css} +1 -1
  15. package/dist/web/index.html +2 -2
  16. package/docs/superpowers/plans/2026-08-01-omp-skill-scan-settings.md +759 -0
  17. package/docs/superpowers/specs/2026-08-01-omp-skill-scan-settings-design.md +224 -0
  18. package/package.json +1 -1
  19. package/src/commands/proxy-control.js +12 -12
  20. package/src/commands/toggle-proxy.js +9 -9
  21. package/src/config/paths.js +53 -29
  22. package/src/index.js +1 -1
  23. package/src/server/api/plugins.js +119 -47
  24. package/src/server/api/skills.js +63 -10
  25. package/src/server/omp-proxy-server.js +17 -4
  26. package/src/server/services/channel-health.js +5 -0
  27. package/src/server/services/channel-sync-utils.js +1 -1
  28. package/src/server/services/model-definition-schema.js +2 -1
  29. package/src/server/services/omp-channels.js +213 -26
  30. package/src/server/services/omp-config.js +71 -6
  31. package/src/server/services/omp-native-plugin-adapter.js +456 -0
  32. package/src/server/services/omp-settings-manager.js +3 -1
  33. package/src/server/services/omp-skill-discovery.js +358 -0
  34. package/src/server/services/omp-skill-settings-service.js +97 -0
  35. package/src/server/services/pi-omp-migration.js +273 -0
  36. package/src/server/services/platform-resolution.js +44 -0
  37. package/src/server/services/plugins-service.js +85 -561
  38. package/src/server/services/project-path-validation.js +65 -0
  39. package/src/server/services/skill-service.js +169 -77
  40. package/dist/web/assets/Home-Bft3Kztl.js +0 -1
  41. package/dist/web/assets/Home-CHZJkkjH.css +0 -1
  42. package/dist/web/assets/SkillManager-CnY7IXkX.js +0 -1
  43. package/dist/web/assets/SkillManager-D7pd-d_P.css +0 -1
  44. package/dist/web/assets/index-k5oP7c-A.js +0 -2
@@ -8,13 +8,14 @@ const express = require('express');
8
8
  const { PluginsService } = require('../services/plugins-service');
9
9
  const { maskToken } = require('../services/oauth-utils');
10
10
  const { sendApiError } = require('./validation-errors');
11
+ const { resolveManagedPlatform } = require('../services/platform-resolution');
12
+ const { validateKnownProjectCwd } = require('../services/project-path-validation');
11
13
 
12
14
  const router = express.Router();
13
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
14
15
  const pluginServices = new Map();
15
16
 
16
17
  function resolvePlatform(rawPlatform) {
17
- return SUPPORTED_PLATFORMS.includes(rawPlatform) ? rawPlatform : 'claude';
18
+ return resolveManagedPlatform(rawPlatform);
18
19
  }
19
20
 
20
21
  function getPlatform(req) {
@@ -22,11 +23,31 @@ function getPlatform(req) {
22
23
  }
23
24
 
24
25
  function getPluginsService(req) {
25
- const platform = getPlatform(req);
26
+ const resolution = getPlatform(req);
27
+ const platform = resolution.platform;
26
28
  if (!pluginServices.has(platform)) {
27
29
  pluginServices.set(platform, new PluginsService(platform));
28
30
  }
29
- return { platform, service: pluginServices.get(platform) };
31
+ return {
32
+ platform,
33
+ service: pluginServices.get(platform),
34
+ warning: resolution.warning
35
+ };
36
+ }
37
+
38
+ async function getRequestOptions(req) {
39
+ const scope = String(req.query?.scope || req.body?.scope || '').trim();
40
+ if (scope && scope !== 'user' && scope !== 'project') {
41
+ throw new Error('Invalid scope: expected "user" or "project"');
42
+ }
43
+ const cwd = await validateKnownProjectCwd(req.query?.cwd || req.body?.cwd);
44
+ if (scope === 'project' && !cwd) {
45
+ throw new Error('Project scope requires a valid cwd');
46
+ }
47
+ return {
48
+ ...(cwd ? { cwd } : {}),
49
+ ...(scope ? { scope } : {})
50
+ };
30
51
  }
31
52
 
32
53
  function extractRepoPayload(source = {}) {
@@ -43,7 +64,8 @@ function extractRepoPayload(source = {}) {
43
64
  localPath: repo.localPath || source.localPath || '',
44
65
  repoUrl: repo.repoUrl || repo.url || source.repoUrl || source.url || '',
45
66
  token: repo.token || source.token || '',
46
- marketplace: repo.marketplace || source.marketplace || ''
67
+ marketplace: repo.marketplace || source.marketplace || '',
68
+ source: repo.source || repo.sourceUri || source.source || source.sourceUri || ''
47
69
  };
48
70
  }
49
71
 
@@ -65,6 +87,10 @@ function sanitizeRepos(service, repos = []) {
65
87
  return (Array.isArray(repos) ? repos : []).map(sanitizeRepo);
66
88
  }
67
89
 
90
+ function hasRequestOptions(options = {}) {
91
+ return Object.keys(options).length > 0;
92
+ }
93
+
68
94
  /**
69
95
  * 获取平台插件能力
70
96
  * GET /api/plugins/capabilities
@@ -91,15 +117,19 @@ router.get('/capabilities', (req, res) => {
91
117
  * 获取插件列表
92
118
  * GET /api/plugins
93
119
  */
94
- router.get('/', (req, res) => {
120
+ router.get('/', async (req, res) => {
95
121
  try {
96
- const { platform, service } = getPluginsService(req);
97
- const result = service.listPlugins();
122
+ const { platform, service, warning } = getPluginsService(req);
123
+ const options = await getRequestOptions(req);
124
+ const result = platform === 'omp' || hasRequestOptions(options)
125
+ ? service.listPlugins(options)
126
+ : service.listPlugins();
98
127
 
99
128
  res.json({
100
129
  success: true,
101
130
  platform,
102
- ...result
131
+ ...result,
132
+ ...(warning ? { warnings: [warning] } : {})
103
133
  });
104
134
  } catch (err) {
105
135
  console.error('[Plugins API] List plugins error:', err);
@@ -113,18 +143,22 @@ router.get('/', (req, res) => {
113
143
  */
114
144
  router.get('/market', async (req, res) => {
115
145
  try {
116
- const { platform, service } = getPluginsService(req);
146
+ const { platform, service, warning } = getPluginsService(req);
147
+ const options = await getRequestOptions(req);
117
148
  const forceRefresh = req.query.refresh === '1';
118
149
  if (forceRefresh) {
119
150
  console.log(`[Plugins API] Refreshing market plugins for ${platform}...`);
120
151
  }
121
- const plugins = await service.getMarketPlugins(forceRefresh);
152
+ const plugins = platform === 'omp' || hasRequestOptions(options)
153
+ ? await service.getMarketPlugins(forceRefresh, options)
154
+ : await service.getMarketPlugins(forceRefresh);
122
155
  console.log(`[Plugins API] ${platform}: ${plugins.length} market plugins loaded (refresh=${forceRefresh})`);
123
156
 
124
157
  res.json({
125
158
  success: true,
126
159
  platform,
127
- plugins
160
+ plugins,
161
+ ...(warning ? { warnings: [warning] } : {})
128
162
  });
129
163
  } catch (err) {
130
164
  console.error('[Plugins API] Get market plugins error:', err);
@@ -139,14 +173,15 @@ router.get('/market', async (req, res) => {
139
173
  */
140
174
  router.post('/install', async (req, res) => {
141
175
  try {
142
- const { platform, service } = getPluginsService(req);
143
- const { directory, repo, gitUrl, source } = req.body;
176
+ const { platform, service, warning } = getPluginsService(req);
177
+ const options = await getRequestOptions(req);
178
+ const { directory, repo, gitUrl, source, pluginId } = req.body;
144
179
  const hasDirectoryField = Object.prototype.hasOwnProperty.call(req.body, 'directory');
145
180
 
146
181
  // Support both new format (directory + repo) and legacy format (gitUrl)
147
182
  let installUrl;
148
- if (source) {
149
- installUrl = source;
183
+ if (pluginId || source) {
184
+ installUrl = pluginId || source;
150
185
  } else if (repo && hasDirectoryField) {
151
186
  installUrl = '';
152
187
  } else if (gitUrl) {
@@ -158,15 +193,25 @@ router.post('/install', async (req, res) => {
158
193
  });
159
194
  }
160
195
 
161
- const result = await service.installPlugin(
162
- installUrl,
163
- repo && hasDirectoryField
164
- ? {
165
- ...extractRepoPayload({ repo }),
166
- directory: directory || ''
167
- }
168
- : null
169
- );
196
+ const repoMetadata = repo && hasDirectoryField
197
+ ? {
198
+ ...extractRepoPayload({ repo }),
199
+ directory: directory || ''
200
+ }
201
+ : platform === 'omp' ? {
202
+ ...(req.body.metadata && typeof req.body.metadata === 'object' ? req.body.metadata : {}),
203
+ name: req.body.name || req.body.metadata?.name,
204
+ pluginId: pluginId || req.body.metadata?.pluginId,
205
+ pluginKind: req.body.pluginKind || req.body.metadata?.pluginKind,
206
+ marketplace: req.body.marketplace || req.body.metadata?.marketplace,
207
+ installSource: req.body.installSource || req.body.metadata?.installSource,
208
+ version: req.body.version || req.body.metadata?.version,
209
+ description: req.body.description || req.body.metadata?.description,
210
+ resourceTypes: req.body.resourceTypes || req.body.metadata?.resourceTypes
211
+ } : null;
212
+ const result = platform === 'omp' || hasRequestOptions(options)
213
+ ? await service.installPlugin(installUrl, repoMetadata, options)
214
+ : await service.installPlugin(installUrl, repoMetadata);
170
215
 
171
216
  if (!result.success) {
172
217
  return res.status(400).json({
@@ -179,7 +224,8 @@ router.post('/install', async (req, res) => {
179
224
  success: true,
180
225
  platform,
181
226
  plugin: result.plugin,
182
- message: `Plugin "${result.plugin.name}" installed successfully`
227
+ message: `Plugin "${result.plugin.name}" installed successfully`,
228
+ ...(warning ? { warnings: [warning] } : {})
183
229
  });
184
230
  } catch (err) {
185
231
  console.error('[Plugins API] Install plugin error:', err);
@@ -193,14 +239,23 @@ router.post('/install', async (req, res) => {
193
239
  * 获取插件仓库列表
194
240
  * GET /api/plugins/repos
195
241
  */
196
- router.get('/repos', (req, res) => {
242
+ router.get('/repos', async (req, res) => {
197
243
  try {
198
- const { platform, service } = getPluginsService(req);
199
- const repos = service.getRepos();
244
+ const { platform, service, warning } = getPluginsService(req);
245
+ const options = await getRequestOptions(req);
246
+ const repos = platform === 'omp' || hasRequestOptions(options)
247
+ ? service.getRepos(options)
248
+ : service.getRepos();
200
249
  res.json({
201
250
  success: true,
202
251
  platform,
203
- repos: sanitizeRepos(service, repos)
252
+ repos: sanitizeRepos(service, repos),
253
+ ...((warning || service.getMigrationWarnings?.().length) ? {
254
+ warnings: [
255
+ ...(warning ? [warning] : []),
256
+ ...(service.getMigrationWarnings?.() || [])
257
+ ]
258
+ } : {})
204
259
  });
205
260
  } catch (err) {
206
261
  console.error('[Plugins API] Get repos error:', err);
@@ -213,20 +268,23 @@ router.get('/repos', (req, res) => {
213
268
  * POST /api/plugins/repos
214
269
  * Body: { url, name, description }
215
270
  */
216
- router.post('/repos', (req, res) => {
271
+ router.post('/repos', async (req, res) => {
217
272
  try {
218
273
  const { platform, service } = getPluginsService(req);
274
+ const options = await getRequestOptions(req);
219
275
  const repo = extractRepoPayload(req.body);
220
276
  repo.enabled = req.body.enabled !== false;
221
277
 
222
- if (!repo.localPath && !repo.projectPath && (!repo.owner || !repo.name) && !repo.repoUrl) {
278
+ if (!repo.source && !repo.localPath && !repo.projectPath && (!repo.owner || !repo.name) && !repo.repoUrl) {
223
279
  return res.status(400).json({
224
280
  success: false,
225
281
  message: 'Missing repo info'
226
282
  });
227
283
  }
228
284
 
229
- const repos = service.addRepo(repo);
285
+ const repos = platform === 'omp' || hasRequestOptions(options)
286
+ ? service.addRepo(repo, options)
287
+ : service.addRepo(repo);
230
288
 
231
289
  res.json({
232
290
  success: true,
@@ -240,11 +298,14 @@ router.post('/repos', (req, res) => {
240
298
  }
241
299
  });
242
300
 
243
- router.delete('/repos', (req, res) => {
301
+ router.delete('/repos', async (req, res) => {
244
302
  try {
245
303
  const { platform, service } = getPluginsService(req);
304
+ const options = await getRequestOptions(req);
246
305
  const { id = '', owner = '', name = '' } = req.query;
247
- const repos = service.removeRepo(owner, name, id);
306
+ const repos = platform === 'omp' || hasRequestOptions(options)
307
+ ? service.removeRepo(owner, name, id, options)
308
+ : service.removeRepo(owner, name, id);
248
309
 
249
310
  res.json({
250
311
  success: true,
@@ -376,7 +437,8 @@ router.put('/repos/auth', (req, res) => {
376
437
  router.post('/repos/sync', async (req, res) => {
377
438
  try {
378
439
  const { platform, service } = getPluginsService(req);
379
- const result = await service.syncRepos();
440
+ const options = await getRequestOptions(req);
441
+ const result = await service.syncRepos(options);
380
442
 
381
443
  res.json({
382
444
  success: true,
@@ -397,7 +459,8 @@ router.post('/repos/sync', async (req, res) => {
397
459
  router.post('/sync', async (req, res) => {
398
460
  try {
399
461
  const { platform, service } = getPluginsService(req);
400
- const result = await service.syncPlugins();
462
+ const options = await getRequestOptions(req);
463
+ const result = await service.syncPlugins(options);
401
464
 
402
465
  res.json({
403
466
  success: true,
@@ -472,12 +535,13 @@ router.get('/:name/readme', async (req, res) => {
472
535
  * 获取单个插件详情
473
536
  * GET /api/plugins/:name
474
537
  */
475
- router.get('/:name', (req, res) => {
538
+ router.get('/:name', async (req, res) => {
476
539
  try {
477
540
  const { platform, service } = getPluginsService(req);
478
541
  const { name } = req.params;
542
+ const options = await getRequestOptions(req);
479
543
 
480
- const plugin = service.getPlugin(name);
544
+ const plugin = service.getPlugin(req.query.pluginId || name, options);
481
545
 
482
546
  if (!plugin) {
483
547
  return res.status(404).json({
@@ -501,12 +565,14 @@ router.get('/:name', (req, res) => {
501
565
  * 卸载插件
502
566
  * DELETE /api/plugins/:name
503
567
  */
504
- router.delete('/:name', (req, res) => {
568
+ router.delete('/:name', async (req, res) => {
505
569
  try {
506
570
  const { platform, service } = getPluginsService(req);
507
571
  const { name } = req.params;
572
+ const options = await getRequestOptions(req);
573
+ const pluginId = req.query.pluginId || name;
508
574
 
509
- const result = service.uninstallPlugin(name);
575
+ const result = service.uninstallPlugin(pluginId, options);
510
576
 
511
577
  if (!result.success) {
512
578
  return res.status(400).json({
@@ -531,11 +597,12 @@ router.delete('/:name', (req, res) => {
531
597
  * PUT /api/plugins/:name/toggle
532
598
  * Body: { enabled }
533
599
  */
534
- router.put('/:name/toggle', (req, res) => {
600
+ router.put('/:name/toggle', async (req, res) => {
535
601
  try {
536
602
  const { platform, service } = getPluginsService(req);
537
603
  const { name } = req.params;
538
- const { enabled } = req.body;
604
+ const { enabled, pluginId = name } = req.body;
605
+ const options = await getRequestOptions(req);
539
606
 
540
607
  if (typeof enabled !== 'boolean') {
541
608
  return res.status(400).json({
@@ -544,7 +611,9 @@ router.put('/:name/toggle', (req, res) => {
544
611
  });
545
612
  }
546
613
 
547
- const plugin = service.togglePlugin(name, enabled);
614
+ const plugin = platform === 'omp' || hasRequestOptions(options)
615
+ ? service.togglePlugin(pluginId, enabled, options)
616
+ : service.togglePlugin(pluginId, enabled);
548
617
 
549
618
  res.json({
550
619
  success: true,
@@ -563,11 +632,12 @@ router.put('/:name/toggle', (req, res) => {
563
632
  * PUT /api/plugins/:name/config
564
633
  * Body: { config }
565
634
  */
566
- router.put('/:name/config', (req, res) => {
635
+ router.put('/:name/config', async (req, res) => {
567
636
  try {
568
637
  const { platform, service } = getPluginsService(req);
569
638
  const { name } = req.params;
570
- const { config } = req.body;
639
+ const { config, pluginId = name } = req.body;
640
+ const options = await getRequestOptions(req);
571
641
 
572
642
  if (!config || typeof config !== 'object') {
573
643
  return res.status(400).json({
@@ -576,7 +646,9 @@ router.put('/:name/config', (req, res) => {
576
646
  });
577
647
  }
578
648
 
579
- const result = service.updatePluginConfig(name, config);
649
+ const result = platform === 'omp' || hasRequestOptions(options)
650
+ ? service.updatePluginConfig(pluginId, config, options)
651
+ : service.updatePluginConfig(pluginId, config);
580
652
 
581
653
  res.json({
582
654
  success: true,
@@ -6,13 +6,18 @@ const express = require('express');
6
6
  const { SkillService } = require('../services/skill-service');
7
7
  const { maskToken } = require('../services/oauth-utils');
8
8
  const { sendApiError } = require('./validation-errors');
9
+ const { resolveManagedPlatform } = require('../services/platform-resolution');
10
+ const { validateKnownProjectCwd } = require('../services/project-path-validation');
11
+ const {
12
+ readOmpSkillSettings,
13
+ updateOmpSkillSettings
14
+ } = require('../services/omp-skill-settings-service');
9
15
 
10
16
  const router = express.Router();
11
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
12
17
  const skillServices = new Map();
13
18
 
14
19
  function resolvePlatform(rawPlatform) {
15
- return SUPPORTED_PLATFORMS.includes(rawPlatform) ? rawPlatform : 'claude';
20
+ return resolveManagedPlatform(rawPlatform);
16
21
  }
17
22
 
18
23
  function getPlatform(req) {
@@ -20,11 +25,16 @@ function getPlatform(req) {
20
25
  }
21
26
 
22
27
  function getSkillService(req) {
23
- const platform = getPlatform(req);
28
+ const resolution = getPlatform(req);
29
+ const platform = resolution.platform;
24
30
  if (!skillServices.has(platform)) {
25
31
  skillServices.set(platform, new SkillService(platform));
26
32
  }
27
- return { platform, service: skillServices.get(platform) };
33
+ return {
34
+ platform,
35
+ service: skillServices.get(platform),
36
+ warning: resolution.warning
37
+ };
28
38
  }
29
39
 
30
40
  function extractRepoPayload(source = {}) {
@@ -69,19 +79,21 @@ function sanitizeRepos(service, repos = []) {
69
79
  */
70
80
  router.get('/', async (req, res) => {
71
81
  try {
72
- const { platform, service } = getSkillService(req);
82
+ const { platform, service, warning } = getSkillService(req);
73
83
  const forceRefresh = req.query.refresh === '1';
84
+ const cwd = await validateKnownProjectCwd(req.query.cwd);
74
85
  if (forceRefresh) {
75
86
  console.log(`[Skills API] Refreshing skills for ${platform}...`);
76
87
  }
77
- const skills = await service.listSkills(forceRefresh);
88
+ const skills = await service.listSkills(forceRefresh, { cwd });
78
89
  console.log(`[Skills API] ${platform}: ${skills.length} skills loaded (refresh=${forceRefresh})`);
79
90
  res.json({
80
91
  success: true,
81
92
  platform,
82
93
  skills,
83
94
  total: skills.length,
84
- installed: skills.filter(s => s.installed).length
95
+ installed: skills.filter(s => s.installed).length,
96
+ ...(warning ? { warnings: [warning] } : {})
85
97
  });
86
98
  } catch (err) {
87
99
  console.error('[Skills API] List skills error:', err);
@@ -89,6 +101,34 @@ router.get('/', async (req, res) => {
89
101
  }
90
102
  });
91
103
 
104
+ /**
105
+ * 获取 OMP 技能设置
106
+ * GET /api/skills/omp-settings
107
+ */
108
+ router.get('/omp-settings', (req, res) => {
109
+ try {
110
+ const settings = readOmpSkillSettings();
111
+ res.json({ success: true, settings });
112
+ } catch (err) {
113
+ console.error('[Skills API] Get OMP skill settings error:', err);
114
+ sendApiError(res, err);
115
+ }
116
+ });
117
+
118
+ /**
119
+ * 更新 OMP 技能设置
120
+ * PUT /api/skills/omp-settings
121
+ */
122
+ router.put('/omp-settings', (req, res) => {
123
+ try {
124
+ const settings = updateOmpSkillSettings(req.body);
125
+ res.json({ success: true, settings });
126
+ } catch (err) {
127
+ console.error('[Skills API] Update OMP skill settings error:', err);
128
+ sendApiError(res, err);
129
+ }
130
+ });
131
+
92
132
  /**
93
133
  * 获取技能详情(完整内容)
94
134
  * GET /api/skills/detail/:directory
@@ -257,10 +297,10 @@ router.post('/create', (req, res) => {
257
297
  * POST /api/skills/uninstall
258
298
  * Body: { directory }
259
299
  */
260
- router.post('/uninstall', (req, res) => {
300
+ router.post('/uninstall', async (req, res) => {
261
301
  try {
262
302
  const { platform, service } = getSkillService(req);
263
- const { directory } = req.body;
303
+ const { directory, scope } = req.body;
264
304
 
265
305
  if (!directory) {
266
306
  return res.status(400).json({
@@ -269,7 +309,20 @@ router.post('/uninstall', (req, res) => {
269
309
  });
270
310
  }
271
311
 
272
- const result = service.uninstallSkill(directory);
312
+ const cwd = await validateKnownProjectCwd(req.body.cwd);
313
+ if (scope && scope !== 'user' && scope !== 'project') {
314
+ throw new Error('Invalid scope: expected "user" or "project"');
315
+ }
316
+ if (scope === 'project' && !cwd) {
317
+ throw new Error('Project scope requires a valid cwd');
318
+ }
319
+ const options = {
320
+ ...(cwd ? { cwd } : {}),
321
+ ...(scope ? { scope } : {})
322
+ };
323
+ const result = platform === 'omp' && Object.keys(options).length > 0
324
+ ? service.uninstallSkill(directory, options)
325
+ : service.uninstallSkill(directory);
273
326
 
274
327
  res.json({
275
328
  success: true,
@@ -1,15 +1,16 @@
1
- const crypto = require('crypto');
2
1
  const { loadConfig } = require('../config/loader');
3
2
  const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
4
3
  const {
5
4
  syncManagedOmpProviders,
6
5
  disableManagedOmpProviders,
6
+ activateStaticOmpChannel,
7
7
  getEnabledChannels,
8
8
  isManagedOmpModeEnabled,
9
9
  enableManagedOmpMode,
10
10
  disableManagedOmpMode,
11
11
  loadManagedOmpActiveChannelId,
12
- loadManagedOmpModeState
12
+ loadManagedOmpModeState,
13
+ getOrCreateOmpGatewaySecret
13
14
  } = require('./services/omp-channels');
14
15
  const {
15
16
  startOmpSessionLogObserver,
@@ -51,7 +52,7 @@ async function startOmpProxyServerUnlocked(options = {}) {
51
52
  activeChannelId: loadManagedOmpActiveChannelId()
52
53
  };
53
54
  const channels = getEnabledChannels();
54
- const secret = crypto.randomBytes(32).toString('hex');
55
+ const secret = getOrCreateOmpGatewaySecret();
55
56
  const desiredPort = options.port ?? config.ports?.ompProxy ?? 20092;
56
57
  const gatewayStatus = await gateway.start({
57
58
  host: '127.0.0.1',
@@ -115,10 +116,22 @@ async function stopOmpProxyServerUnlocked(options = {}) {
115
116
  warnings: lastSyncResult?.warnings || []
116
117
  };
117
118
  }
119
+ const state = loadManagedOmpModeState?.() || {
120
+ activeChannelId: loadManagedOmpActiveChannelId()
121
+ };
122
+ const enabledChannels = getEnabledChannels();
123
+ const activeChannel = enabledChannels.find(channel => channel.id === state.activeChannelId)
124
+ || enabledChannels[0]
125
+ || null;
118
126
  gateway.beginDraining();
119
127
  stopOmpSessionLogObserver();
120
128
  try {
121
- lastSyncResult = disableManagedOmpProviders();
129
+ if (activeChannel) {
130
+ const handoff = activateStaticOmpChannel(activeChannel.id);
131
+ lastSyncResult = handoff?.sync || lastSyncResult;
132
+ } else {
133
+ lastSyncResult = disableManagedOmpProviders();
134
+ }
122
135
  } catch (error) {
123
136
  startOmpSessionLogObserver();
124
137
  gateway.cancelDraining();
@@ -143,6 +143,11 @@ function recordFailure(channelId, source = 'claude', error) {
143
143
  health.consecutiveSuccesses = 0;
144
144
  health.lastCheckTime = now;
145
145
 
146
+ // omp 动态切换按请求即时回退其他渠道,失败渠道无需冻结(冻结会将其排除在后续分配之外)
147
+ if (source === 'omp') {
148
+ return;
149
+ }
150
+
146
151
  // 如果当前是健康状态或检测中状态,检查是否需要冻结
147
152
  if (health.status === 'healthy' || health.status === 'checking') {
148
153
  if (health.consecutiveFailures >= healthConfig.failureThreshold) {
@@ -289,7 +289,7 @@ function upsertSyncedChannels({
289
289
  result.warnings.push(`${toolType}: missing baseUrl, skipped current channel sync`);
290
290
  continue;
291
291
  }
292
- if (!normalizeString(candidate.apiKey)) {
292
+ if (!normalizeString(candidate.apiKey) && candidate.authMode !== 'none') {
293
293
  result.skipped += 1;
294
294
  result.warnings.push(`${toolType}: missing API key or OAuth-only config, skipped current channel sync`);
295
295
  continue;
@@ -390,7 +390,8 @@ function normalizeCatalogModelList(value) {
390
390
  const warnings = [];
391
391
  for (const rawModel of Array.isArray(value) ? value : []) {
392
392
  const rawId = String(rawModel?.id || rawModel?.name || '').trim();
393
- const normalized = normalizeModelSpec(rawModel, rawId);
393
+ const { provider: _provider, selector: _selector, ...modelFields } = isPlainObject(rawModel) ? rawModel : {};
394
+ const normalized = normalizeModelSpec(modelFields, rawId);
394
395
  if (!normalized.spec?.id) continue;
395
396
  warnings.push(...normalized.warnings.map(message => `${normalized.spec.id}: ${message}`));
396
397
  const key = normalized.spec.id.toLowerCase();