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
@@ -16,17 +16,22 @@ const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homed
16
16
  const PROMPTS_FILE = PATHS.prompts;
17
17
 
18
18
  // 各平台提示词文件路径
19
- const CLAUDE_PROMPT_PATH = path.join(HOME_DIR, '.claude', 'CLAUDE.md');
19
+ const CLAUDE_CONFIG_DIR = NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings);
20
+ const CLAUDE_PROMPT_PATH = NATIVE_PATHS.claude.prompt || path.join(CLAUDE_CONFIG_DIR, 'CLAUDE.md');
20
21
  const CODEX_PROMPT_PATH = path.join(HOME_DIR, '.codex', 'AGENTS.md');
21
22
  const GEMINI_PROMPT_PATH = path.join(HOME_DIR, '.gemini', 'GEMINI.md');
22
23
  const OPENCODE_PROMPT_PATH = path.join(NATIVE_PATHS.opencode.config, 'AGENTS.md');
24
+ const OMP_PROMPTS_DIR = NATIVE_PATHS.omp?.prompts
25
+ || path.join(NATIVE_PATHS.omp?.dir || path.join(HOME_DIR, '.omp', 'agent'), 'prompts');
26
+ const MANAGED_OMP_PROMPTS_DIR = path.join(OMP_PROMPTS_DIR, 'coding-tool-x');
23
27
 
24
- function normalizeApps(apps = {}, defaults = { claude: true, codex: true, gemini: true, opencode: false }) {
28
+ function normalizeApps(apps = {}, defaults = { claude: true, codex: true, gemini: true, opencode: false, omp: false }) {
25
29
  return {
26
30
  claude: apps.claude !== undefined ? !!apps.claude : defaults.claude,
27
31
  codex: apps.codex !== undefined ? !!apps.codex : defaults.codex,
28
32
  gemini: apps.gemini !== undefined ? !!apps.gemini : defaults.gemini,
29
- opencode: apps.opencode !== undefined ? !!apps.opencode : defaults.opencode
33
+ opencode: apps.opencode !== undefined ? !!apps.opencode : defaults.opencode,
34
+ omp: apps.omp !== undefined ? !!apps.omp: defaults.omp
30
35
  };
31
36
  }
32
37
 
@@ -211,7 +216,7 @@ function initPromptsData() {
211
216
  name: '当前使用',
212
217
  description: '从现有配置导入',
213
218
  content: existingContent,
214
- apps: normalizeApps({ claude: true, codex: false, gemini: false, opencode: false }),
219
+ apps: normalizeApps({ claude: true, codex: false, gemini: false, opencode: false, omp: false }),
215
220
  isBuiltin: false,
216
221
  isImported: true,
217
222
  createdAt: Date.now(),
@@ -234,6 +239,7 @@ function initPromptsData() {
234
239
  preset.apps.codex !== normalizedApps.codex ||
235
240
  preset.apps.gemini !== normalizedApps.gemini ||
236
241
  preset.apps.opencode !== normalizedApps.opencode
242
+ || preset.apps.omp !== normalizedApps.omp
237
243
  ) {
238
244
  preset.apps = normalizedApps;
239
245
  updated = true;
@@ -307,6 +313,7 @@ function savePreset(preset) {
307
313
 
308
314
  const data = initPromptsData();
309
315
  const existing = data.presets[preset.id];
316
+ const wasActive = data.activePresetId === preset.id;
310
317
 
311
318
  // 内置模板不允许修改
312
319
  if (existing && existing.isBuiltin) {
@@ -325,6 +332,13 @@ function savePreset(preset) {
325
332
  data.presets[preset.id] = preset;
326
333
  writeJsonFile(PROMPTS_FILE, data);
327
334
 
335
+ if (wasActive) {
336
+ if (existing && normalizeApps(existing.apps).omp && !preset.apps.omp) {
337
+ deleteOmpPromptTemplate(existing);
338
+ }
339
+ syncPresetToAllPlatforms(preset);
340
+ }
341
+
328
342
  return preset;
329
343
  }
330
344
 
@@ -348,6 +362,10 @@ function deletePreset(id) {
348
362
  data.activePresetId = null;
349
363
  }
350
364
 
365
+ if (normalizeApps(preset.apps).omp) {
366
+ deleteOmpPromptTemplate(preset);
367
+ }
368
+
351
369
  delete data.presets[id];
352
370
  writeJsonFile(PROMPTS_FILE, data);
353
371
 
@@ -360,6 +378,7 @@ function deletePreset(id) {
360
378
  async function activatePreset(id) {
361
379
  const data = initPromptsData();
362
380
  const preset = data.presets[id];
381
+ const previousPreset = data.activePresetId ? data.presets[data.activePresetId] : null;
363
382
 
364
383
  if (!preset) {
365
384
  throw new Error(`预设 "${id}" 不存在`);
@@ -369,6 +388,10 @@ async function activatePreset(id) {
369
388
  data.activePresetId = id;
370
389
  writeJsonFile(PROMPTS_FILE, data);
371
390
 
391
+ if (previousPreset && previousPreset.id !== id && normalizeApps(previousPreset.apps).omp) {
392
+ deleteOmpPromptTemplate(previousPreset);
393
+ }
394
+
372
395
  // 同步到各平台
373
396
  await syncPresetToAllPlatforms(preset);
374
397
 
@@ -380,6 +403,7 @@ async function activatePreset(id) {
380
403
  */
381
404
  async function deactivatePrompt() {
382
405
  const data = initPromptsData();
406
+ const activePreset = data.activePresetId ? data.presets[data.activePresetId] : null;
383
407
 
384
408
  // 清空激活状态
385
409
  data.activePresetId = null;
@@ -390,7 +414,10 @@ async function deactivatePrompt() {
390
414
  claude: deleteFile(CLAUDE_PROMPT_PATH),
391
415
  codex: deleteFile(CODEX_PROMPT_PATH),
392
416
  gemini: deleteFile(GEMINI_PROMPT_PATH),
393
- opencode: deleteFile(OPENCODE_PROMPT_PATH)
417
+ opencode: deleteFile(OPENCODE_PROMPT_PATH),
418
+ omp: activePreset && normalizeApps(activePreset.apps).omp
419
+ ? deleteOmpPromptTemplate(activePreset)
420
+ : false
394
421
  };
395
422
 
396
423
  console.log('[Prompts] Deactivated and removed prompt files:', results);
@@ -405,7 +432,7 @@ async function deactivatePrompt() {
405
432
  /**
406
433
  * 同步预设到所有已启用的平台
407
434
  */
408
- async function syncPresetToAllPlatforms(preset) {
435
+ function syncPresetToAllPlatforms(preset) {
409
436
  const apps = normalizeApps(preset.apps);
410
437
  const { content } = preset;
411
438
 
@@ -428,6 +455,43 @@ async function syncPresetToAllPlatforms(preset) {
428
455
  writeTextFile(OPENCODE_PROMPT_PATH, content);
429
456
  console.log('[Prompts] Synced to OpenCode:', OPENCODE_PROMPT_PATH);
430
457
  }
458
+
459
+ if (apps.omp) {
460
+ const targetPath = writeOmpPromptTemplate(preset);
461
+ console.log('[Prompts] Synced to OMP:', targetPath);
462
+ }
463
+ }
464
+
465
+ function buildOmpPromptFileName(preset) {
466
+ const rawId = (preset?.id || 'preset').toString().trim();
467
+ const safeId = rawId.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').slice(0, 100) || 'preset';
468
+ return `${safeId}.md`;
469
+ }
470
+
471
+ function getOmpPromptTemplatePath(preset) {
472
+ return path.join(MANAGED_OMP_PROMPTS_DIR, buildOmpPromptFileName(preset));
473
+ }
474
+
475
+ function escapeFrontmatterValue(value) {
476
+ return String(value || '')
477
+ .replace(/\\/g, '\\\\')
478
+ .replace(/"/g, '\\"')
479
+ .replace(/\r?\n/g, ' ');
480
+ }
481
+
482
+ function buildOmpPromptTemplateContent(preset) {
483
+ const description = escapeFrontmatterValue(preset.description || preset.name || preset.id);
484
+ return `---\ndescription: "${description}"\n---\n\n${preset.content || ''}`;
485
+ }
486
+
487
+ function writeOmpPromptTemplate(preset) {
488
+ const filePath = getOmpPromptTemplatePath(preset);
489
+ writeTextFile(filePath, buildOmpPromptTemplateContent(preset));
490
+ return filePath;
491
+ }
492
+
493
+ function deleteOmpPromptTemplate(preset) {
494
+ return deleteFile(getOmpPromptTemplatePath(preset));
431
495
  }
432
496
 
433
497
  /**
@@ -443,11 +507,46 @@ function readPlatformPrompt(platform) {
443
507
  return readTextFile(GEMINI_PROMPT_PATH, '');
444
508
  case 'opencode':
445
509
  return readTextFile(OPENCODE_PROMPT_PATH, '');
510
+ case 'omp':
511
+ throw new Error('OMP 使用 prompts/ 目录中的模板文件,请通过平台状态查看模板列表');
446
512
  default:
447
513
  throw new Error(`无效的平台: ${platform}`);
448
514
  }
449
515
  }
450
516
 
517
+ function listOmpPromptTemplates() {
518
+ if (!fs.existsSync(OMP_PROMPTS_DIR)) {
519
+ return [];
520
+ }
521
+
522
+ const results = [];
523
+ const walk = (dir) => {
524
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
525
+ if (entry.name.startsWith('.')) continue;
526
+ const fullPath = path.join(dir, entry.name);
527
+ if (entry.isDirectory()) {
528
+ walk(fullPath);
529
+ } else if (entry.isFile() && path.extname(entry.name).toLowerCase() === '.md') {
530
+ const relativePath = path.relative(OMP_PROMPTS_DIR, fullPath);
531
+ results.push({
532
+ name: path.basename(entry.name, '.md'),
533
+ path: fullPath,
534
+ relativePath,
535
+ managed: relativePath.split(path.sep)[0] === 'coding-tool-x'
536
+ });
537
+ }
538
+ }
539
+ };
540
+
541
+ try {
542
+ walk(OMP_PROMPTS_DIR);
543
+ } catch (err) {
544
+ console.error(`[Prompts] Failed to list OMP prompt templates:`, err.message);
545
+ }
546
+
547
+ return results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
548
+ }
549
+
451
550
  /**
452
551
  * 获取各平台当前的提示词状态
453
552
  */
@@ -472,6 +571,12 @@ function getPlatformStatus() {
472
571
  path: OPENCODE_PROMPT_PATH,
473
572
  exists: fs.existsSync(OPENCODE_PROMPT_PATH),
474
573
  content: readTextFile(OPENCODE_PROMPT_PATH, '')
574
+ },
575
+ omp: {
576
+ path: OMP_PROMPTS_DIR,
577
+ managedPath: MANAGED_OMP_PROMPTS_DIR,
578
+ exists: fs.existsSync(OMP_PROMPTS_DIR),
579
+ templates: listOmpPromptTemplates()
475
580
  }
476
581
  };
477
582
  }
@@ -494,7 +599,7 @@ function importFromPlatform(platform, presetName) {
494
599
  name: presetName || `从 ${platform} 导入`,
495
600
  description: `从 ${platform} 导入的提示词`,
496
601
  content,
497
- apps: normalizeApps({ claude: false, codex: false, gemini: false, opencode: false }),
602
+ apps: normalizeApps({ claude: false, codex: false, gemini: false, opencode: false, omp: false }),
498
603
  isBuiltin: false,
499
604
  createdAt: Date.now(),
500
605
  updatedAt: Date.now()
@@ -9,6 +9,7 @@ function normalizeToolSource(source = '') {
9
9
  if (normalized === 'codex') return 'codex';
10
10
  if (normalized === 'gemini') return 'gemini';
11
11
  if (normalized === 'opencode') return 'opencode';
12
+ if (normalized === 'omp' || normalized === 'omp-agent') return 'omp';
12
13
  return 'claude';
13
14
  }
14
15
 
@@ -11,7 +11,11 @@ const PROXY_START_LOG_PATTERNS = {
11
11
  ],
12
12
  codex: [/Codex proxy server started on http:\/\/127\.0\.0\.1:\d+/],
13
13
  gemini: [/Gemini proxy server started on http:\/\/127\.0\.0\.1:\d+/],
14
- opencode: [/OpenCode proxy server started on http:\/\/127\.0\.0\.1:\d+/]
14
+ opencode: [/OpenCode proxy server started on http:\/\/127\.0\.0\.1:\d+/],
15
+ omp: [
16
+ /OMP models\.yml provider config enabled, port: \d+/,
17
+ /OMP models\.yml 受管 provider 已自动启用,端口: \d+/
18
+ ]
15
19
  };
16
20
 
17
21
  function getRuntimeFilePath(proxyType) {
@@ -17,17 +17,6 @@ const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
17
17
  const CODEX_PROJECTS_DIR = path.join(path.dirname(NATIVE_PATHS.codex.config), 'projects');
18
18
  const GEMINI_PROJECTS_DIR = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'projects');
19
19
 
20
- function resolveProjectsDir(config = {}) {
21
- return config.projectsDir || CLAUDE_PROJECTS_DIR;
22
- }
23
-
24
- function withResolvedProjectsDir(config = {}) {
25
- return {
26
- ...config,
27
- projectsDir: resolveProjectsDir(config)
28
- };
29
- }
30
-
31
20
  // Base directory for cc-tool data
32
21
  function getCcToolDir() {
33
22
  return PATHS.base;
@@ -193,7 +182,7 @@ function sliceClaudeSessionContentByUserMessage(content, afterUserMessageNumber)
193
182
 
194
183
  // Get all projects with stats (async version)
195
184
  async function getProjects(config) {
196
- const projectsDir = resolveProjectsDir(config);
185
+ const projectsDir = config.projectsDir;
197
186
 
198
187
  if (!fs.existsSync(projectsDir)) {
199
188
  return [];
@@ -394,17 +383,16 @@ function extractCwdFromSessionHeader(sessionFile) {
394
383
 
395
384
  // Get projects with detailed stats (with caching) - async version
396
385
  async function getProjectsWithStats(config, options = {}) {
397
- const resolvedConfig = withResolvedProjectsDir(config);
398
386
  if (!options.force) {
399
387
  // Check enhanced cache first
400
- const cacheKey = `${CacheKeys.PROJECTS}${resolvedConfig.projectsDir}`;
388
+ const cacheKey = `${CacheKeys.PROJECTS}${config.projectsDir}`;
401
389
  const enhancedCached = globalCache.get(cacheKey);
402
390
  if (enhancedCached) {
403
391
  return enhancedCached;
404
392
  }
405
393
 
406
394
  // Check old cache
407
- const cached = getCachedProjects(resolvedConfig);
395
+ const cached = getCachedProjects(config);
408
396
  if (cached) {
409
397
  globalCache.set(cacheKey, cached, 300000); // 5分钟
410
398
  return cached;
@@ -412,22 +400,22 @@ async function getProjectsWithStats(config, options = {}) {
412
400
  }
413
401
 
414
402
  try {
415
- const data = await buildProjectsWithStats(resolvedConfig);
403
+ const data = await buildProjectsWithStats(config);
416
404
  if (!Array.isArray(data)) {
417
- console.warn(`[getProjectsWithStats] Unexpected non-array result for ${resolvedConfig.projectsDir}, returning empty array.`);
405
+ console.warn(`[getProjectsWithStats] Unexpected non-array result for ${config.projectsDir}, returning empty array.`);
418
406
  return [];
419
407
  }
420
- setCachedProjects(resolvedConfig, data);
421
- globalCache.set(`${CacheKeys.PROJECTS}${resolvedConfig.projectsDir}`, data, 300000);
408
+ setCachedProjects(config, data);
409
+ globalCache.set(`${CacheKeys.PROJECTS}${config.projectsDir}`, data, 300000);
422
410
  return data;
423
411
  } catch (err) {
424
- console.error(`[getProjectsWithStats] Failed to build projects for ${resolvedConfig.projectsDir}:`, err);
412
+ console.error(`[getProjectsWithStats] Failed to build projects for ${config.projectsDir}:`, err);
425
413
  return [];
426
414
  }
427
415
  }
428
416
 
429
417
  async function buildProjectsWithStats(config) {
430
- const projectsDir = resolveProjectsDir(config);
418
+ const projectsDir = config.projectsDir;
431
419
 
432
420
  if (!fs.existsSync(projectsDir)) {
433
421
  return [];
@@ -494,7 +482,7 @@ async function buildProjectsWithStats(config) {
494
482
 
495
483
  // 获取 Claude 项目/会话数量(轻量统计)
496
484
  function getProjectAndSessionCounts(config) {
497
- const projectsDir = resolveProjectsDir(config);
485
+ const projectsDir = config.projectsDir;
498
486
  if (!fs.existsSync(projectsDir)) {
499
487
  return { projectCount: 0, sessionCount: 0 };
500
488
  }
@@ -682,8 +670,7 @@ async function getSessionsForProject(config, projectName) {
682
670
 
683
671
  // Delete a session
684
672
  function deleteSession(config, projectName, sessionId) {
685
- const resolvedConfig = withResolvedProjectsDir(config);
686
- const projectDir = path.join(resolvedConfig.projectsDir, projectName);
673
+ const projectDir = path.join(config.projectsDir, projectName);
687
674
  const sessionFile = path.join(projectDir, sessionId + '.jsonl');
688
675
 
689
676
  if (!fs.existsSync(sessionFile)) {
@@ -691,15 +678,14 @@ function deleteSession(config, projectName, sessionId) {
691
678
  }
692
679
 
693
680
  fs.unlinkSync(sessionFile);
694
- invalidateProjectsCache(resolvedConfig);
681
+ invalidateProjectsCache(config);
695
682
  invalidateSessionResultCache(projectName);
696
683
  return { success: true };
697
684
  }
698
685
 
699
686
  // Fork a session
700
687
  function forkSession(config, projectName, sessionId, options = {}) {
701
- const resolvedConfig = withResolvedProjectsDir(config);
702
- const projectDir = path.join(resolvedConfig.projectsDir, projectName);
688
+ const projectDir = path.join(config.projectsDir, projectName);
703
689
  const sessionFile = path.join(projectDir, sessionId + '.jsonl');
704
690
 
705
691
  if (!fs.existsSync(sessionFile)) {
@@ -728,7 +714,7 @@ function forkSession(config, projectName, sessionId, options = {}) {
728
714
  const { setAlias } = require('./alias');
729
715
  setAlias(newSessionId, options.alias);
730
716
  }
731
- invalidateProjectsCache(resolvedConfig);
717
+ invalidateProjectsCache(config);
732
718
  invalidateSessionResultCache(projectName);
733
719
 
734
720
  return {
@@ -780,8 +766,7 @@ function saveSessionOrder(projectName, order) {
780
766
 
781
767
  // Delete a project (remove the entire project directory)
782
768
  function deleteProject(config, projectName) {
783
- const resolvedConfig = withResolvedProjectsDir(config);
784
- const projectDir = path.join(resolvedConfig.projectsDir, projectName);
769
+ const projectDir = path.join(config.projectsDir, projectName);
785
770
 
786
771
  if (!fs.existsSync(projectDir)) {
787
772
  throw new Error('Project not found');
@@ -791,20 +776,19 @@ function deleteProject(config, projectName) {
791
776
  fs.rmSync(projectDir, { recursive: true, force: true });
792
777
 
793
778
  // Remove from order file if exists
794
- const order = getProjectOrder(resolvedConfig);
779
+ const order = getProjectOrder(config);
795
780
  const newOrder = order.filter(name => name !== projectName);
796
781
  if (newOrder.length !== order.length) {
797
- saveProjectOrder(resolvedConfig, newOrder);
782
+ saveProjectOrder(config, newOrder);
798
783
  }
799
784
 
800
- invalidateProjectsCache(resolvedConfig);
785
+ invalidateProjectsCache(config);
801
786
  return { success: true };
802
787
  }
803
788
 
804
789
  // Search sessions for keyword
805
790
  function searchSessions(config, projectName, keyword, contextLength = 15) {
806
- const resolvedConfig = withResolvedProjectsDir(config);
807
- const projectDir = path.join(resolvedConfig.projectsDir, projectName);
791
+ const projectDir = path.join(config.projectsDir, projectName);
808
792
 
809
793
  if (!fs.existsSync(projectDir)) {
810
794
  return [];
@@ -25,7 +25,7 @@ const {
25
25
  resolveInsideRoot
26
26
  } = require('./config-artifact-paths');
27
27
 
28
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
28
+ const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
29
29
  const SUPPORTED_REPO_PROVIDERS = ['github', 'gitlab', 'local'];
30
30
  const DEFAULT_GITHUB_HOST = 'https://github.com';
31
31
  const DEFAULT_GITLAB_HOST = 'https://gitlab.com';
@@ -173,12 +173,16 @@ const DEFAULT_REPOS_BY_PLATFORM = {
173
173
  claude: [],
174
174
  codex: [],
175
175
  gemini: [],
176
- opencode: []
176
+ opencode: [],
177
+ omp: []
177
178
  };
178
179
 
180
+ const CLAUDE_SKILLS_DIR = NATIVE_PATHS.claude.skills
181
+ || path.join(NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings), 'skills');
182
+
179
183
  const PLATFORM_CONFIG = {
180
184
  claude: {
181
- installDir: path.join(HOME_DIR, '.claude', 'skills'),
185
+ installDir: CLAUDE_SKILLS_DIR,
182
186
  storageDir: PATHS.localSkills.claude,
183
187
  reposFile: PATHS.skillRepos.claude,
184
188
  cacheFile: PATHS.skillCaches.claude
@@ -200,6 +204,12 @@ const PLATFORM_CONFIG = {
200
204
  storageDir: PATHS.localSkills.opencode,
201
205
  reposFile: PATHS.skillRepos.opencode,
202
206
  cacheFile: PATHS.skillCaches.opencode
207
+ },
208
+ omp: {
209
+ installDir: NATIVE_PATHS.omp.skills,
210
+ storageDir: PATHS.localSkills.omp,
211
+ reposFile: PATHS.skillRepos.omp,
212
+ cacheFile: PATHS.skillCaches.omp
203
213
  }
204
214
  };
205
215
 
@@ -13,6 +13,7 @@ const { getEffectiveApiKey: getClaudeEffectiveApiKey } = require('./channels');
13
13
  const { getEffectiveApiKey: getCodexEffectiveApiKey } = require('./codex-channels');
14
14
  const { getEffectiveApiKey: getGeminiEffectiveApiKey } = require('./gemini-channels');
15
15
  const { getEffectiveApiKey: getOpenCodeEffectiveApiKey } = require('./opencode-channels');
16
+ const { getEffectiveApiKey: getOmpEffectiveApiKey } = require('./omp-channels');
16
17
  const { loadClaudeRequestTemplate } = require('./request-logger');
17
18
 
18
19
  // 测试结果缓存
@@ -102,6 +103,8 @@ function resolveExplicitModel(channel, model) {
102
103
 
103
104
  function resolveEffectiveApiKey(channel, channelType) {
104
105
  switch (channelType) {
106
+ case 'omp':
107
+ return getOmpEffectiveApiKey(channel);
105
108
  case 'codex':
106
109
  return getCodexEffectiveApiKey(channel);
107
110
  case 'gemini':
@@ -376,11 +379,14 @@ function extractJsonPayloads(responseData) {
376
379
  * 测试单个渠道的连接速度和 API 功能
377
380
  * @param {Object} channel - 渠道配置
378
381
  * @param {number} timeout - 超时时间(毫秒)
379
- * @param {string} channelType - 渠道类型:'claude' | 'codex' | 'gemini'
382
+ * @param {string} channelType - 请求格式类型:'claude' | 'codex' | 'gemini' | 'opencode'
383
+ * @param {Object} options - 可选行为
384
+ * @param {string} options.authSourceType - API key 来源平台;OMP 使用 'omp',请求格式仍由 channelType 决定
380
385
  * @returns {Promise<Object>} 测试结果
381
386
  */
382
- async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType = 'claude') {
387
+ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType = 'claude', options = {}) {
383
388
  const sanitizedTimeout = sanitizeTimeout(timeout);
389
+ const authSourceType = options?.authSourceType || channelType;
384
390
 
385
391
  try {
386
392
  if (!channel.baseUrl) {
@@ -416,7 +422,7 @@ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType
416
422
  };
417
423
  }
418
424
 
419
- const effectiveApiKey = resolveEffectiveApiKey(channel, channelType);
425
+ const effectiveApiKey = resolveEffectiveApiKey(channel, authSourceType);
420
426
  if (!effectiveApiKey) {
421
427
  return {
422
428
  channelId: channel.id,
@@ -433,7 +439,7 @@ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType
433
439
 
434
440
  // 直接测试 API 功能(发送测试消息)
435
441
  // 不再单独测试网络连通性,因为直接 GET base_url 可能返回 404
436
- const apiResult = await testAPIFunctionality(
442
+ const aompResult = await testAPIFunctionality(
437
443
  testUrl,
438
444
  effectiveApiKey,
439
445
  sanitizedTimeout,
@@ -442,8 +448,8 @@ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType
442
448
  channel
443
449
  );
444
450
 
445
- const success = apiResult.success;
446
- const networkOk = apiResult.latency !== null; // 如果有延迟数据,说明网络是通的
451
+ const success = aompResult.success;
452
+ const networkOk = aompResult.latency !== null; // 如果有延迟数据,说明网络是通的
447
453
 
448
454
  // 缓存结果
449
455
  const finalResult = {
@@ -452,13 +458,13 @@ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType
452
458
  success,
453
459
  networkOk,
454
460
  apiOk: success,
455
- statusCode: apiResult.statusCode || null,
456
- error: success ? null : (apiResult.error || '测试失败'),
457
- latency: apiResult.latency ?? null, // 无论成功失败都保留延迟数据(保留 0ms)
461
+ statusCode: aompResult.statusCode || null,
462
+ error: success ? null : (aompResult.error || '测试失败'),
463
+ latency: aompResult.latency ?? null, // 无论成功失败都保留延迟数据(保留 0ms)
458
464
  testedAt: Date.now(),
459
- testedModel: apiResult.testedModel,
460
- availableModels: apiResult.availableModels,
461
- modelDetectionMethod: apiResult.modelDetectionMethod
465
+ testedModel: aompResult.testedModel,
466
+ availableModels: aompResult.availableModels,
467
+ modelDetectionMethod: aompResult.modelDetectionMethod
462
468
  };
463
469
 
464
470
  testResultsCache.set(channel.id, finalResult);
@@ -655,11 +661,11 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
655
661
 
656
662
  if (channelType === 'claude') {
657
663
  // Anthropic Messages API - 模拟 Claude Code 请求格式
658
- let apiPath = parsedUrl.pathname.replace(/\/$/, '');
659
- if (!apiPath.endsWith('/messages')) {
660
- apiPath = apiPath + (apiPath.endsWith('/v1') ? '/messages' : '/v1/messages');
664
+ let aompPath = parsedUrl.pathname.replace(/\/$/, '');
665
+ if (!aompPath.endsWith('/messages')) {
666
+ aompPath = aompPath + (aompPath.endsWith('/v1') ? '/messages' : '/v1/messages');
661
667
  }
662
- apiPath += '?beta=true';
668
+ aompPath += '?beta=true';
663
669
 
664
670
  testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'claude-sonnet-4-20250514';
665
671
  const sessionId = typeof crypto.randomUUID === 'function'
@@ -680,20 +686,20 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
680
686
  tools: toolsToUse
681
687
  };
682
688
  primaryRequestConfig = {
683
- apiPath,
689
+ aompPath,
684
690
  requestBody: JSON.stringify(requestPayload),
685
691
  headers: buildClaudeRequestHeaders(apiKey, { hasTools: true }),
686
692
  isStreamingResponse: true
687
693
  };
688
694
  // Fallback: non-streaming for gateways that don't support SSE
689
695
  fallbackRequestConfig = {
690
- apiPath,
696
+ aompPath,
691
697
  requestBody: JSON.stringify({ ...requestPayload, stream: false }),
692
698
  headers: buildClaudeRequestHeaders(apiKey, { hasTools: true }),
693
699
  isStreamingResponse: false
694
700
  };
695
701
  } else if (channelType === 'codex') {
696
- const apiPath = buildCodexResponsesPath(parsedUrl);
702
+ const aompPath = buildCodexResponsesPath(parsedUrl);
697
703
  testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gpt-5-codex';
698
704
  const codexSessionId = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
699
705
 
@@ -706,7 +712,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
706
712
  };
707
713
 
708
714
  primaryRequestConfig = {
709
- apiPath,
715
+ aompPath,
710
716
  requestBody: JSON.stringify({ ...baseBody, stream: false }),
711
717
  headers: {
712
718
  'Authorization': `Bearer ${apiKey || ''}`,
@@ -723,7 +729,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
723
729
  };
724
730
 
725
731
  fallbackRequestConfig = {
726
- apiPath,
732
+ aompPath,
727
733
  requestBody: JSON.stringify({ ...baseBody, stream: true }),
728
734
  headers: {
729
735
  ...primaryRequestConfig.headers,
@@ -736,7 +742,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
736
742
  const useCliFormat = shouldUseGeminiCliFormat(parsedUrl);
737
743
 
738
744
  const cliRequestConfig = {
739
- apiPath: buildGeminiCliGeneratePath(parsedUrl),
745
+ aompPath: buildGeminiCliGeneratePath(parsedUrl),
740
746
  requestBody: JSON.stringify({
741
747
  project: '',
742
748
  model: testModel,
@@ -758,7 +764,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
758
764
  };
759
765
 
760
766
  const nativeRequestConfig = {
761
- apiPath: buildGeminiNativeGeneratePath(parsedUrl, testModel),
767
+ aompPath: buildGeminiNativeGeneratePath(parsedUrl, testModel),
762
768
  requestBody: JSON.stringify({
763
769
  contents: [{ role: 'user', parts: [{ text: 'ping' }] }],
764
770
  generationConfig: { maxOutputTokens: 1, temperature: 0 }
@@ -777,12 +783,12 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
777
783
  fallbackRequestConfig = useCliFormat ? nativeRequestConfig : cliRequestConfig;
778
784
  } else {
779
785
  testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gpt-4o-mini';
780
- let apiPath = parsedUrl.pathname.replace(/\/$/, '');
781
- if (!apiPath.endsWith('/chat/completions')) {
782
- apiPath = apiPath + (apiPath.endsWith('/v1') ? '/chat/completions' : '/v1/chat/completions');
786
+ let aompPath = parsedUrl.pathname.replace(/\/$/, '');
787
+ if (!aompPath.endsWith('/chat/completions')) {
788
+ aompPath = aompPath + (aompPath.endsWith('/v1') ? '/chat/completions' : '/v1/chat/completions');
783
789
  }
784
790
  primaryRequestConfig = {
785
- apiPath,
791
+ aompPath,
786
792
  requestBody: JSON.stringify({
787
793
  model: testModel,
788
794
  max_tokens: 1,
@@ -802,7 +808,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
802
808
  const options = {
803
809
  hostname: parsedUrl.hostname,
804
810
  port: parsedUrl.port || (isHttps ? 443 : 80),
805
- path: requestConfig.apiPath,
811
+ path: requestConfig.aompPath,
806
812
  method: 'POST',
807
813
  timeout,
808
814
  headers: requestConfig.headers