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.
- package/CHANGELOG.md +9 -0
- package/README.md +1 -1
- package/dist/web/assets/{Analytics-Ck_00Brj.js → Analytics-8dRv1aYW.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-Cpw2vUa8.js → ConfigTemplates-BQRXSICO.js} +1 -1
- package/dist/web/assets/Home-BtzWhKIa.js +1 -0
- package/dist/web/assets/Home-DYVy4cGZ.css +1 -0
- package/dist/web/assets/{PluginManager-2RlvEJCT.js → PluginManager-BTa-Ygf6.js} +1 -1
- package/dist/web/assets/{ProjectList-_ecCngpl.js → ProjectList-F_cB8ldA.js} +1 -1
- package/dist/web/assets/{SessionList-DlWcw3hO.js → SessionList-LOrbPQvu.js} +1 -1
- package/dist/web/assets/SkillManager-BsX8Qpj0.css +1 -0
- package/dist/web/assets/SkillManager-BzI_t-yV.js +1 -0
- package/dist/web/assets/{WorkspaceManager-3mqghi09.js → WorkspaceManager-Db9RGiz7.js} +1 -1
- package/dist/web/assets/index-DIWJmyL6.js +2 -0
- package/dist/web/assets/{index-BceXgSSe.css → index-bJRG3RXH.css} +1 -1
- package/dist/web/index.html +2 -2
- package/docs/superpowers/plans/2026-08-01-omp-skill-scan-settings.md +759 -0
- package/docs/superpowers/specs/2026-08-01-omp-skill-scan-settings-design.md +224 -0
- package/package.json +1 -1
- package/src/commands/proxy-control.js +12 -12
- package/src/commands/toggle-proxy.js +9 -9
- package/src/config/paths.js +53 -29
- package/src/index.js +1 -1
- package/src/server/api/plugins.js +119 -47
- package/src/server/api/skills.js +63 -10
- package/src/server/omp-proxy-server.js +17 -4
- package/src/server/services/channel-health.js +5 -0
- package/src/server/services/channel-sync-utils.js +1 -1
- package/src/server/services/model-definition-schema.js +2 -1
- package/src/server/services/omp-channels.js +213 -26
- package/src/server/services/omp-config.js +71 -6
- package/src/server/services/omp-native-plugin-adapter.js +456 -0
- package/src/server/services/omp-settings-manager.js +3 -1
- package/src/server/services/omp-skill-discovery.js +358 -0
- package/src/server/services/omp-skill-settings-service.js +97 -0
- package/src/server/services/pi-omp-migration.js +273 -0
- package/src/server/services/platform-resolution.js +44 -0
- package/src/server/services/plugins-service.js +85 -561
- package/src/server/services/project-path-validation.js +65 -0
- package/src/server/services/skill-service.js +169 -77
- package/dist/web/assets/Home-Bft3Kztl.js +0 -1
- package/dist/web/assets/Home-CHZJkkjH.css +0 -1
- package/dist/web/assets/SkillManager-CnY7IXkX.js +0 -1
- package/dist/web/assets/SkillManager-D7pd-d_P.css +0 -1
- 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
|
|
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
|
|
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 {
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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,
|
package/src/server/api/skills.js
CHANGED
|
@@ -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
|
|
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
|
|
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 {
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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();
|