coding-tool-x 3.8.8 → 3.8.10

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 (134) hide show
  1. package/README.md +56 -388
  2. package/dist/web/assets/AgentsDrawer-Bam7kBjG.js +1 -0
  3. package/dist/web/assets/Analytics-D5dUWXEa.js +1 -0
  4. package/dist/web/assets/AssetPathField-CLqalYv-.js +1 -0
  5. package/dist/web/assets/{ChatHistoryDrawer-CrfS0KYV.js → ChatHistoryDrawer-C1At65O3.js} +1 -1
  6. package/dist/web/assets/{CollapseItem-ClMsJuUu.js → CollapseItem-CVWojShK.js} +1 -1
  7. package/dist/web/assets/CommandsDrawer-BEoypGBf.js +1 -0
  8. package/dist/web/assets/ConfigExportDrawer-DBvFDDWK.js +1 -0
  9. package/dist/web/assets/ConfigTemplates-BpaWG-iC.js +1 -0
  10. package/dist/web/assets/{ConfigTemplatesDrawer-BqM32Im7.js → ConfigTemplatesDrawer-C2lvyKMf.js} +1 -1
  11. package/dist/web/assets/ConfigTemplatesPanel-CorOY7kY.js +1 -0
  12. package/dist/web/assets/{Divider-DsdbkHrG.js → Divider-BLLhMTSt.js} +1 -1
  13. package/dist/web/assets/FavoritesDrawer-uW88N6te.js +1 -0
  14. package/dist/web/assets/GatewayConvertDrawer-i5Wt9Dd9.js +1 -0
  15. package/dist/web/assets/Home-BIxWvm29.js +1 -0
  16. package/dist/web/assets/{MarkdownEditor-BMMUufSc.js → MarkdownEditor-Ul0bSGj-.js} +1 -1
  17. package/dist/web/assets/McpDrawer-Cc2xq77I.js +1 -0
  18. package/dist/web/assets/McpFormDrawer-DLHX_nrI.js +1 -0
  19. package/dist/web/assets/{PluginManager-C4kJlpVX.js → PluginManager-BGQ0xF2F.js} +1 -1
  20. package/dist/web/assets/{PluginsDrawer-05R4k15x.js → PluginsDrawer-STwHEqiF.js} +1 -1
  21. package/dist/web/assets/PluginsPanel-1zuNP7da.js +1 -0
  22. package/dist/web/assets/ProjectList-VbIYSHbH.css +1 -0
  23. package/dist/web/assets/ProjectList-_pe2nk9c.js +1 -0
  24. package/dist/web/assets/PromptsDrawer-CJN1xZk7.js +1 -0
  25. package/dist/web/assets/RecentSessionsDrawer-DX1HI8Nm.js +1 -0
  26. package/dist/web/assets/SessionCard-Op6PHvi0.js +1 -0
  27. package/dist/web/assets/SessionList-Da_TAEYr.js +1 -0
  28. package/dist/web/assets/SettingsDrawer-DKLd4Sjy.js +1 -0
  29. package/dist/web/assets/{SkillManager-D9jkNuRM.js → SkillManager-PcAa5EbF.js} +1 -1
  30. package/dist/web/assets/{SkillsDrawer-0Z4IefxC.js → SkillsDrawer-DPdU4p6C.js} +1 -1
  31. package/dist/web/assets/SkillsPanel-B9qzRqjd.js +1 -0
  32. package/dist/web/assets/Upload-e1M9Yp5z.js +1 -0
  33. package/dist/web/assets/{WorkspaceDrawer-eujtWpoq.js → WorkspaceDrawer-oS6mB1VZ.js} +1 -1
  34. package/dist/web/assets/{WorkspaceManager-CQ-8Ej68.js → WorkspaceManager-6VvRWe3r.js} +1 -1
  35. package/dist/web/assets/WorkspacePanel-CYq0Z8iY.js +1 -0
  36. package/dist/web/assets/client-BgcNg4Bt.js +1 -0
  37. package/dist/web/assets/headers-DbowkmZJ.js +1 -0
  38. package/dist/web/assets/{icons-DmFe4b5j.js → icons-B-AzZoeE.js} +1 -1
  39. package/dist/web/assets/index-BlUXw84U.js +2 -0
  40. package/dist/web/assets/index-Bn8BUnwP.css +1 -0
  41. package/dist/web/assets/{light-0eavxNvt.js → light-C2X7hegj.js} +1 -1
  42. package/dist/web/assets/platforms-DrzAtnjY.js +1 -0
  43. package/dist/web/assets/platforms-mqqe_82d.js +1 -0
  44. package/dist/web/assets/project-config-BVt5s-pH.js +1 -0
  45. package/dist/web/assets/sessions-53-Fr68H.js +1 -0
  46. package/dist/web/assets/{sessions-D4yQtfKb.js → sessions-CaWUaysM.js} +1 -1
  47. package/dist/web/assets/use-message-DBsCm7FJ.js +1 -0
  48. package/dist/web/assets/{useResponsiveDrawer-DZd5rNk5.js → useResponsiveDrawer-ChHZrTfl.js} +1 -1
  49. package/dist/web/assets/{workspaces-mjyPVGS8.js → workspaces-DQuZd4qw.js} +1 -1
  50. package/dist/web/index.html +8 -8
  51. package/package.json +4 -2
  52. package/src/commands/daemon.js +27 -4
  53. package/src/commands/export-config.js +17 -211
  54. package/src/config/model-metadata.json +180 -16
  55. package/src/platforms/channel-auth-registry.js +36 -6
  56. package/src/platforms/channel-auth-service.js +13 -3
  57. package/src/platforms/commands-service.js +28 -0
  58. package/src/platforms/driver-registry.js +1 -0
  59. package/src/platforms/drivers/claude/channels-implementation.js +79 -90
  60. package/src/platforms/drivers/claude/native-config-implementation.js +3 -4
  61. package/src/platforms/drivers/codex/channels-implementation.js +34 -49
  62. package/src/platforms/drivers/codex/native-config-implementation.js +3 -51
  63. package/src/platforms/drivers/dsh/api-operations.js +1 -1
  64. package/src/platforms/drivers/dsh/channels-implementation.js +28 -8
  65. package/src/platforms/drivers/dsh/channels.js +0 -18
  66. package/src/platforms/drivers/dsh/common.js +7 -0
  67. package/src/platforms/drivers/dsh/native-config.js +7 -2
  68. package/src/platforms/drivers/gemini/channels-implementation.js +47 -55
  69. package/src/platforms/drivers/gemini/native-config-implementation.js +8 -20
  70. package/src/platforms/drivers/generic-native-config.js +20 -0
  71. package/src/platforms/drivers/native-config-snapshot.js +30 -4
  72. package/src/platforms/drivers/omp/channels-implementation.js +51 -17
  73. package/src/platforms/drivers/omp/native-config-implementation.js +10 -17
  74. package/src/platforms/drivers/omp/prompts.js +58 -2
  75. package/src/platforms/drivers/omp/proxy-implementation.js +25 -1
  76. package/src/platforms/drivers/opencode/channels-implementation.js +31 -22
  77. package/src/platforms/drivers/opencode/channels.js +6 -1
  78. package/src/platforms/drivers/opencode/native-config-implementation.js +3 -67
  79. package/src/platforms/manifest-schema.js +35 -2
  80. package/src/platforms/manifests/claude.json +0 -8
  81. package/src/platforms/manifests/codex.json +0 -8
  82. package/src/platforms/manifests/dsh.json +6 -9
  83. package/src/platforms/manifests/gemini.json +0 -8
  84. package/src/platforms/manifests/omp.json +2 -9
  85. package/src/platforms/manifests/opencode.json +28 -8
  86. package/src/platforms/native-oauth-adapters.js +351 -51
  87. package/src/platforms/oauth-credentials-service.js +344 -13
  88. package/src/server/index.js +37 -1
  89. package/src/server/services/bounded-content.js +60 -0
  90. package/src/server/services/channel-balance.js +10 -4
  91. package/src/server/services/config-export-service.js +2 -2
  92. package/src/server/services/config-templates-service.js +5 -1
  93. package/src/server/services/plugins-service.js +162 -85
  94. package/src/server/services/session-history-maintenance-worker.js +71 -0
  95. package/src/server/services/session-history-maintenance.js +166 -0
  96. package/src/server/services/skill-service.js +23 -3
  97. package/src/server/services/speed-test.js +0 -72
  98. package/src/shared/channel-driver.js +16 -0
  99. package/src/shared/driver-factories/api.js +1 -0
  100. package/src/utils/native-config-patcher.js +474 -0
  101. package/dist/web/assets/AgentsDrawer-DRQuwi0m.js +0 -1
  102. package/dist/web/assets/Analytics-Cf5KwEdj.js +0 -1
  103. package/dist/web/assets/AssetPathField-DyniM0pF.js +0 -1
  104. package/dist/web/assets/CommandsDrawer-CzHelf56.js +0 -1
  105. package/dist/web/assets/ConfigExportDrawer-BEZ8wTby.js +0 -1
  106. package/dist/web/assets/ConfigTemplates-Crg3xWe5.js +0 -1
  107. package/dist/web/assets/ConfigTemplatesPanel-lTdutC8H.js +0 -1
  108. package/dist/web/assets/FavoritesDrawer-DDyWUBJR.js +0 -1
  109. package/dist/web/assets/GatewayConvertDrawer-Rigtt_5J.js +0 -1
  110. package/dist/web/assets/Home-tCSKmSHV.js +0 -1
  111. package/dist/web/assets/McpDrawer-CmE-d7VW.js +0 -1
  112. package/dist/web/assets/McpFormDrawer-ByvS18od.js +0 -1
  113. package/dist/web/assets/PluginsPanel-BCoEs7Og.js +0 -1
  114. package/dist/web/assets/ProjectList-BJt1OIaI.js +0 -1
  115. package/dist/web/assets/ProjectList-DG3yu8xZ.css +0 -1
  116. package/dist/web/assets/PromptsDrawer-DnwBf_oW.js +0 -1
  117. package/dist/web/assets/RecentSessionsDrawer-B7d-38wH.js +0 -1
  118. package/dist/web/assets/SessionCard-BUgjHQwX.js +0 -1
  119. package/dist/web/assets/SessionList-B-bXZRFk.js +0 -1
  120. package/dist/web/assets/SettingsDrawer-BITzjQLl.js +0 -1
  121. package/dist/web/assets/SkillsPanel-BKIVOj7n.js +0 -1
  122. package/dist/web/assets/SpeedTestDrawer-Bx1bYf5i.js +0 -1
  123. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +0 -1
  124. package/dist/web/assets/Upload-CzC4rGI4.js +0 -1
  125. package/dist/web/assets/WorkspacePanel-Cql5qE2R.js +0 -1
  126. package/dist/web/assets/client-UDwXBP9i.js +0 -1
  127. package/dist/web/assets/headers-BqDmkMjy.js +0 -1
  128. package/dist/web/assets/index-Cmmwj3Fx.js +0 -2
  129. package/dist/web/assets/index-Cnbe696C.css +0 -1
  130. package/dist/web/assets/platforms-BmzzwnSw.js +0 -1
  131. package/dist/web/assets/platforms-DWXgmy2w.js +0 -1
  132. package/dist/web/assets/project-config-B2baPzYL.js +0 -1
  133. package/dist/web/assets/sessions-DqlbocLJ.js +0 -1
  134. package/dist/web/assets/use-message-DqCwefG6.js +0 -1
@@ -3,8 +3,9 @@ const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { PATHS, NATIVE_PATHS } = require('../../../config/paths');
5
5
  const { resolveChannelWebsiteUrl } = require('../../../config/channel-preset-websites');
6
- const { clearNativeOAuth, readNativeOAuth, clearGeminiChannelConfig } = require('../../native-oauth-adapters');
6
+ const { readNativeOAuth, clearGeminiChannelConfig } = require('../../native-oauth-adapters');
7
7
  const { normalizeGatewaySourceType } = require('../../../shared/proxy-utils');
8
+ const { readJsoncFile, updateJsoncFile, updateEnvFile } = require('../../../utils/native-config-patcher');
8
9
  const {
9
10
  createSkippedResult,
10
11
  isLocalProxyBaseUrl,
@@ -84,12 +85,16 @@ function applyNativeGeminiOAuth(channel) {
84
85
  clearGeminiChannelConfig();
85
86
 
86
87
  const settingsPath = configuredNative.settings;
87
- const settings = readGeminiSettings();
88
- settings.security = settings.security || {};
89
- settings.security.auth = settings.security.auth || {};
90
- settings.security.auth.selectedType = 'oauth-personal';
91
- fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
92
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
88
+ updateJsoncFile(settingsPath, (settings) => {
89
+ settings.security = settings.security && typeof settings.security === 'object' && !Array.isArray(settings.security)
90
+ ? settings.security
91
+ : {};
92
+ settings.security.auth = settings.security.auth && typeof settings.security.auth === 'object'
93
+ && !Array.isArray(settings.security.auth)
94
+ ? settings.security.auth
95
+ : {};
96
+ settings.security.auth.selectedType = 'oauth-personal';
97
+ });
93
98
  console.log(`[Gemini Channels] Applied native OAuth channel ${channel.name}`);
94
99
  return channel;
95
100
  }
@@ -168,14 +173,11 @@ function readExistingGeminiEnv() {
168
173
 
169
174
  function writeGeminiEnv(env = {}) {
170
175
  const envPath = path.join(getGeminiDir(), '.env');
171
- const content = Object.entries(env)
172
- .map(([key, value]) => `${key}=${value}`)
173
- .join('\n');
174
-
175
- fs.writeFileSync(envPath, content ? `${content}\n` : '', 'utf8');
176
- if (process.platform !== 'win32') {
177
- fs.chmodSync(envPath, 0o600);
178
- }
176
+ const managedKeys = ['GOOGLE_GEMINI_BASE_URL', 'GEMINI_API_KEY', 'GEMINI_MODEL'];
177
+ updateEnvFile(envPath, Object.fromEntries(managedKeys.map(key => [
178
+ key,
179
+ Object.prototype.hasOwnProperty.call(env, key) ? env[key] : undefined
180
+ ])), { atomic: true, mode: 0o600 });
179
181
  }
180
182
 
181
183
  // 检查是否在代理模式
@@ -515,8 +517,6 @@ function applyChannelToSettings(channelId, channels = null) {
515
517
  return applyNativeGeminiOAuth(channel);
516
518
  }
517
519
 
518
- clearNativeOAuth('gemini');
519
-
520
520
  const geminiDir = getGeminiDir();
521
521
 
522
522
  if (!fs.existsSync(geminiDir)) {
@@ -532,22 +532,16 @@ function applyChannelToSettings(channelId, channels = null) {
532
532
 
533
533
  // 确保 settings.json 存在并配置正确的认证模式
534
534
  const settingsPath = path.join(geminiDir, 'settings.json');
535
- let settings = {};
536
-
537
- if (fs.existsSync(settingsPath)) {
538
- try {
539
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
540
- } catch (err) {
541
- console.warn('[Gemini Channels] Failed to read settings.json, creating new');
542
- }
543
- }
544
-
545
- // 设置认证模式为 gemini-api-key(第三方 API)
546
- settings.security = settings.security || {};
547
- settings.security.auth = settings.security.auth || {};
548
- settings.security.auth.selectedType = 'gemini-api-key';
549
-
550
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
535
+ updateJsoncFile(settingsPath, (settings) => {
536
+ settings.security = settings.security && typeof settings.security === 'object' && !Array.isArray(settings.security)
537
+ ? settings.security
538
+ : {};
539
+ settings.security.auth = settings.security.auth && typeof settings.security.auth === 'object'
540
+ && !Array.isArray(settings.security.auth)
541
+ ? settings.security.auth
542
+ : {};
543
+ settings.security.auth.selectedType = 'gemini-api-key';
544
+ });
551
545
 
552
546
  console.log(`[Gemini Channels] Applied channel ${channel.name} to .env`);
553
547
  return channel;
@@ -600,6 +594,17 @@ function writeGeminiConfigForMultiChannel(allChannels) {
600
594
  delete env.GEMINI_API_KEY;
601
595
  delete env.GEMINI_MODEL;
602
596
  writeGeminiEnv(env);
597
+ const settingsPath = path.join(geminiDir, 'settings.json');
598
+ if (fs.existsSync(settingsPath)) {
599
+ updateJsoncFile(settingsPath, (settings) => {
600
+ const auth = settings.security?.auth;
601
+ if (auth?.selectedType === 'gemini-api-key') {
602
+ delete auth.selectedType;
603
+ if (Object.keys(auth).length === 0) delete settings.security.auth;
604
+ if (Object.keys(settings.security).length === 0) delete settings.security;
605
+ }
606
+ });
607
+ }
603
608
  return;
604
609
  }
605
610
 
@@ -611,22 +616,13 @@ function writeGeminiConfigForMultiChannel(allChannels) {
611
616
 
612
617
  // 确保 settings.json 存在并配置正确的认证模式
613
618
  const settingsPath = path.join(geminiDir, 'settings.json');
614
- let settings = {};
615
-
616
- if (fs.existsSync(settingsPath)) {
617
- try {
618
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
619
- } catch (err) {
620
- console.warn('[Gemini Channels] Failed to read settings.json, creating new');
621
- }
622
- }
623
-
624
- // 设置认证模式为 gemini-api-key(第三方 API)
625
- settings.security = settings.security || {};
626
- settings.security.auth = settings.security.auth || {};
627
- settings.security.auth.selectedType = 'gemini-api-key';
628
-
629
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
619
+ updateJsoncFile(settingsPath, (settings) => {
620
+ settings.security = settings.security && typeof settings.security === 'object' ? settings.security : {};
621
+ settings.security.auth = settings.security.auth && typeof settings.security.auth === 'object'
622
+ ? settings.security.auth
623
+ : {};
624
+ settings.security.auth.selectedType = 'gemini-api-key';
625
+ });
630
626
  }
631
627
 
632
628
  // 获取所有启用的渠道(供调度器使用)
@@ -682,7 +678,7 @@ function disableAllChannels() {
682
678
  data.channels.forEach(ch => { ch.enabled = false; });
683
679
  saveChannels(data);
684
680
  if (!getGeminiProxyState().running) {
685
- clearGeminiChannelConfig();
681
+ writeGeminiConfigForMultiChannel(data.channels);
686
682
  }
687
683
  }
688
684
 
@@ -691,11 +687,7 @@ function readGeminiSettings() {
691
687
  if (!settingsPath || !fs.existsSync(settingsPath)) {
692
688
  return {};
693
689
  }
694
- try {
695
- return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
696
- } catch {
697
- return {};
698
- }
690
+ return readJsoncFile(settingsPath);
699
691
  }
700
692
 
701
693
  function normalizeGeminiChannel(channel = {}) {
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { NATIVE_PATHS } = require('../../../config/paths');
4
+ const { readJsoncFile, writeJsoncFile, updateEnvFile } = require('../../../utils/native-config-patcher');
4
5
  const DEFAULT_NATIVE_PATHS = NATIVE_PATHS.gemini;
5
6
  let nativePaths = DEFAULT_NATIVE_PATHS;
6
7
 
@@ -64,25 +65,14 @@ function readEnv() {
64
65
  }
65
66
  }
66
67
 
67
- // 将环境对象转换为 .env 字符串
68
- function envToString(env) {
69
- let content = '';
70
- for (const [key, value] of Object.entries(env)) {
71
- content += `${key}=${value}\n`;
72
- }
73
- return content;
74
- }
75
-
76
68
  // 写入 .env
77
69
  function writeEnv(env) {
78
70
  try {
79
- const content = envToString(env);
80
- fs.writeFileSync(getEnvPath(), content, 'utf8');
81
-
82
- // 设置文件权限为 600 (仅所有者可读写)
83
- if (process.platform !== 'win32') {
84
- fs.chmodSync(getEnvPath(), 0o600);
85
- }
71
+ const managedKeys = ['GOOGLE_GEMINI_BASE_URL', 'GEMINI_API_KEY', 'GEMINI_MODEL'];
72
+ updateEnvFile(getEnvPath(), Object.fromEntries(managedKeys.map(key => [
73
+ key,
74
+ Object.prototype.hasOwnProperty.call(env || {}, key) ? env[key] : undefined
75
+ ])), { atomic: true, mode: 0o600 });
86
76
  } catch (err) {
87
77
  throw new Error('Failed to write .env: ' + err.message);
88
78
  }
@@ -94,8 +84,7 @@ function readSettings() {
94
84
  if (!settingsExists()) {
95
85
  return {};
96
86
  }
97
- const content = fs.readFileSync(getSettingsPath(), 'utf8');
98
- return JSON.parse(content);
87
+ return readJsoncFile(getSettingsPath());
99
88
  } catch (err) {
100
89
  throw new Error('Failed to read settings.json: ' + err.message);
101
90
  }
@@ -104,8 +93,7 @@ function readSettings() {
104
93
  // 写入 settings.json
105
94
  function writeSettings(settings) {
106
95
  try {
107
- const content = JSON.stringify(settings, null, 2);
108
- fs.writeFileSync(getSettingsPath(), content, 'utf8');
96
+ writeJsoncFile(getSettingsPath(), settings);
109
97
  } catch (err) {
110
98
  throw new Error('Failed to write settings.json: ' + err.message);
111
99
  }
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { createNativeSnapshotMethods } = require('./native-config-snapshot');
4
+
5
+ function createDriver(context = {}) {
6
+ return {
7
+ platform: context.platform,
8
+ capability: 'nativeConfig',
9
+ ...context,
10
+ ...createNativeSnapshotMethods({}, {
11
+ platform: context.platform,
12
+ runtime: context.runtime,
13
+ manifest: context.manifest,
14
+ pathContext: context.pathContext,
15
+ paths: context.paths
16
+ })
17
+ };
18
+ }
19
+
20
+ module.exports = { createDriver };
@@ -83,17 +83,43 @@ function writeEntry(spec, entry, overwrite) {
83
83
  return 'success';
84
84
  }
85
85
 
86
- function createNativeSnapshotMethods(specs, { platform, runtime } = {}) {
86
+ function resolveSnapshotPath(spec, { pathContext, paths, manifest } = {}) {
87
+ const rawPath = typeof spec?.path === 'string' ? spec.path.trim() : '';
88
+ if (!rawPath) return null;
89
+
90
+ const nativePaths = pathContext?.native || paths || {};
91
+ if (typeof nativePaths[rawPath] === 'string' && nativePaths[rawPath].trim()) {
92
+ return nativePaths[rawPath];
93
+ }
94
+
95
+ const home = pathContext?.home || nativePaths.home || manifest?.paths?.home || process.cwd();
96
+ const expanded = rawPath.replace(/\{home\}/g, home);
97
+ return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(home, expanded);
98
+ }
99
+
100
+ function resolveNativeSnapshotSpecs(declarations, context = {}) {
101
+ return Object.fromEntries(Object.entries(declarations || {})
102
+ .map(([key, spec]) => [key, {
103
+ ...spec,
104
+ path: resolveSnapshotPath(spec, context)
105
+ }])
106
+ .filter(([, spec]) => spec.path));
107
+ }
108
+
109
+ function createNativeSnapshotMethods(specs, { platform, runtime, manifest, pathContext, paths } = {}) {
110
+ const resolvedSpecs = Object.keys(specs || {}).length > 0
111
+ ? specs
112
+ : resolveNativeSnapshotSpecs(manifest?.nativeConfigSnapshot, { manifest, pathContext, paths });
87
113
  const methods = {
88
114
  exportSnapshot() {
89
- return Object.fromEntries(Object.entries(specs || {})
115
+ return Object.fromEntries(Object.entries(resolvedSpecs || {})
90
116
  .map(([key, spec]) => [key, readEntry(spec)])
91
117
  .filter(([, entry]) => entry));
92
118
  },
93
119
  importSnapshot(snapshot = {}, { overwrite = true } = {}) {
94
120
  const result = { success: 0, imported: 0, skipped: 0, failed: 0 };
95
121
  for (const [key, entry] of Object.entries(snapshot || {})) {
96
- const status = writeEntry(specs[key], entry, overwrite);
122
+ const status = writeEntry(resolvedSpecs[key], entry, overwrite);
97
123
  if (status === 'success') {
98
124
  result.success += 1;
99
125
  result.imported += 1;
@@ -122,4 +148,4 @@ function createNativeSnapshotMethods(specs, { platform, runtime } = {}) {
122
148
  return methods;
123
149
  }
124
150
 
125
- module.exports = { createNativeSnapshotMethods };
151
+ module.exports = { createNativeSnapshotMethods, resolveNativeSnapshotSpecs };
@@ -10,6 +10,7 @@ const {
10
10
  writeManagedOmpProviders,
11
11
  removeManagedOmpProviders,
12
12
  getLastManagedOmpSyncResult,
13
+ getManagedProviderId,
13
14
  readModelsConfig,
14
15
  readOmpSettingsConfig,
15
16
  normalizeProviderId,
@@ -59,6 +60,15 @@ function selectLatestEnabledChannel(channels = []) {
59
60
  }, enabledChannels[0]);
60
61
  }
61
62
 
63
+ function getChannelManagedProviderId(channel = {}) {
64
+ if (typeof getManagedProviderId === 'function') {
65
+ return getManagedProviderId(channel);
66
+ }
67
+ const rawId = channel.managedProviderId || channel.providerKey || channel.provider || channel.name || channel.id;
68
+ const normalizedId = normalizeProviderId(rawId);
69
+ return normalizedId.startsWith('ctx-') ? normalizedId : `ctx-${normalizedId}`;
70
+ }
71
+
62
72
  function loadManagedOmpModeState() {
63
73
  ensureStorageDirMigrated();
64
74
  try {
@@ -269,16 +279,24 @@ class OmpChannelService extends BaseChannelService {
269
279
  }
270
280
 
271
281
  _applyToNativeSettings(channel) {
282
+ const knownProviderIds = this.getChannels().channels.map(getChannelManagedProviderId);
272
283
  if (isManagedOmpModeEnabled()) {
273
284
  const state = loadManagedOmpModeState();
274
285
  enableManagedOmpMode(channel.id, state?.gateway || null);
275
286
  writeManagedOmpProviders([channel], state?.gateway ? {
276
287
  gateway: state.gateway,
277
- activeChannelId: channel.id
278
- } : {});
288
+ activeChannelId: channel.id,
289
+ knownProviderIds
290
+ } : { knownProviderIds });
279
291
  return;
280
292
  }
281
- writeManagedOmpProviders([channel], {});
293
+ writeManagedOmpProviders([channel], { knownProviderIds });
294
+ }
295
+
296
+ disableAllChannels() {
297
+ const result = super.disableAllChannels();
298
+ this.syncOmpProvidersForCurrentMode(this.getChannels().channels);
299
+ return result;
282
300
  }
283
301
 
284
302
  _onAfterCreate(_channel, allChannels) {
@@ -296,13 +314,18 @@ class OmpChannelService extends BaseChannelService {
296
314
  syncOmpProvidersForCurrentMode(channels = this.getChannels().channels) {
297
315
  if (isManagedOmpModeEnabled()) {
298
316
  const state = loadManagedOmpModeState();
299
- return this.syncManagedOmpProviders(channels, state?.gateway ? {
300
- gateway: state.gateway,
301
- activeChannelId: state.activeChannelId
302
- } : {});
317
+ return this.syncManagedOmpProviders(channels, {
318
+ ...(state?.gateway ? {
319
+ gateway: state.gateway,
320
+ activeChannelId: state.activeChannelId
321
+ } : {}),
322
+ knownProviderIds: channels.map(getChannelManagedProviderId)
323
+ });
303
324
  }
304
325
  const activeChannel = selectLatestEnabledChannel(channels);
305
- return this.syncManagedOmpProviders(activeChannel ? [activeChannel] : [], {});
326
+ return this.syncManagedOmpProviders(activeChannel ? [activeChannel] : [], {
327
+ knownProviderIds: channels.map(getChannelManagedProviderId)
328
+ });
306
329
  }
307
330
 
308
331
  syncManagedOmpProviders(channels = this.getChannels().channels, options = {}) {
@@ -335,7 +358,9 @@ class OmpChannelService extends BaseChannelService {
335
358
 
336
359
  try {
337
360
  this.saveChannels(data);
338
- const sync = this.syncManagedOmpProviders([channel], {});
361
+ const sync = this.syncManagedOmpProviders([channel], {
362
+ knownProviderIds: data.channels.map(getChannelManagedProviderId)
363
+ });
339
364
  if (!wasEnabled) {
340
365
  clearOmpChannelBalanceCache(channel);
341
366
  }
@@ -739,24 +764,33 @@ function getCatalogMetadata({
739
764
  ))
740
765
  : [])
741
766
  ]
742
- .map(value => String(value || '').trim().toLowerCase())
743
- .filter(Boolean);
767
+ .map(value => String(value || '').trim())
768
+ .filter(Boolean)
769
+ .filter((value, index, values) => values.findIndex(item => item.toLowerCase() === value.toLowerCase()) === index);
744
770
  const normalizedProviderKey = String(providerKey || '').trim().toLowerCase();
745
771
 
746
772
  const allEntries = Object.entries(MODEL_METADATA)
747
773
  .filter(([, metadata]) => Array.isArray(metadata.toolTypes) && metadata.toolTypes.includes('omp'))
748
774
  .map(([id, metadata]) => ({ ...structuredClone(metadata), id }));
749
775
 
750
- const matchesRequestedId = entry => requestedIds.length > 0 && requestedIds.some(requested => (
751
- requested === entry.id.toLowerCase()
752
- || requested === String(entry.sourceId || '').toLowerCase()
753
- || requested === entry.id.split('/').pop().toLowerCase()
754
- ));
776
+ const findRequestedEntry = requestedId => allEntries.find(entry => {
777
+ const normalizedRequestedId = requestedId.toLowerCase();
778
+ return normalizedRequestedId === entry.id.toLowerCase()
779
+ || normalizedRequestedId === String(entry.sourceId || '').toLowerCase()
780
+ || normalizedRequestedId === entry.id.split('/').pop().toLowerCase();
781
+ });
755
782
  const matchesProvider = entry => (
756
783
  String(entry.provider || '').toLowerCase() === normalizedProviderKey
757
784
  || String(entry.sourceId || '').toLowerCase().startsWith(`${normalizedProviderKey}/`)
758
785
  );
759
- const requestedEntries = requestedIds.length > 0 ? allEntries.filter(matchesRequestedId) : [];
786
+ const requestedEntries = requestedIds.length > 0
787
+ ? requestedIds
788
+ .map(requestedId => {
789
+ const entry = findRequestedEntry(requestedId);
790
+ return entry ? { ...entry, id: requestedId } : null;
791
+ })
792
+ .filter(Boolean)
793
+ : [];
760
794
  const providerEntries = normalizedProviderKey ? allEntries.filter(matchesProvider) : [];
761
795
  const entries = requestedIds.length > 0 ? requestedEntries : providerEntries;
762
796
 
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { spawnSync } = require('child_process');
4
4
  const yaml = require('js-yaml');
5
+ const { writeYamlFile } = require('../../../utils/native-config-patcher');
5
6
  const ompConfig = require('./config');
6
7
  const { prepareManagedOmpChannels } = require('./gateway-routing');
7
8
  const { MODEL_METADATA, MODEL_ALIASES } = require('../../../config/model-metadata');
@@ -409,13 +410,7 @@ function readModelsConfig(filePath = getOmpPaths().modelsYml) {
409
410
  }
410
411
 
411
412
  function writeModelsConfig(config, filePath = getOmpPaths().modelsYml) {
412
- ensureOmpDir(path.dirname(filePath));
413
- const doc = yaml.dump(config || { providers: {} }, {
414
- lineWidth: 120,
415
- noRefs: true,
416
- sortKeys: false
417
- });
418
- writeFileAtomic(filePath, doc);
413
+ writeYamlFile(filePath, config || { providers: {} }, { atomic: true });
419
414
  }
420
415
 
421
416
  function getOmpSettingsPath(paths = getOmpPaths()) {
@@ -447,13 +442,7 @@ function readOmpSettingsConfig(filePath = getOmpSettingsPath()) {
447
442
  }
448
443
 
449
444
  function writeOmpSettingsConfig(config, filePath = getOmpSettingsPath()) {
450
- ensureOmpDir(path.dirname(filePath));
451
- const doc = yaml.dump(config || {}, {
452
- lineWidth: 120,
453
- noRefs: true,
454
- sortKeys: false
455
- });
456
- writeFileAtomic(filePath, doc);
445
+ writeYamlFile(filePath, config || {}, { atomic: true });
457
446
  }
458
447
 
459
448
  function getModelsBackupPrefix(filePath) {
@@ -669,9 +658,10 @@ function removeLegacyManagedExtension(paths = getOmpPaths()) {
669
658
  }
670
659
  }
671
660
 
672
- function pruneManagedProviders(providers = {}) {
661
+ function pruneManagedProviders(providers = {}, knownProviderIds = null) {
662
+ const keep = knownProviderIds instanceof Set ? knownProviderIds : null;
673
663
  Object.keys(providers).forEach((providerId) => {
674
- if (isManagedProviderId(providerId)) {
664
+ if (isManagedProviderId(providerId) && (!keep || !keep.has(providerId))) {
675
665
  delete providers[providerId];
676
666
  }
677
667
  });
@@ -684,7 +674,10 @@ function buildManagedModelsConfig(channels = [], baseConfig = readModelsConfig()
684
674
  ...(baseConfig.providers || {})
685
675
  }
686
676
  };
687
- pruneManagedProviders(next.providers);
677
+ const knownProviderIds = Array.isArray(options.knownProviderIds)
678
+ ? new Set(options.knownProviderIds)
679
+ : new Set((channels || []).map(getManagedProviderId));
680
+ pruneManagedProviders(next.providers, knownProviderIds);
688
681
 
689
682
  channels
690
683
  .filter(channel => channel && channel.enabled !== false && channel.baseUrl)
@@ -1,9 +1,65 @@
1
1
  'use strict';
2
2
 
3
- const { createPromptDriver } = require('../../../shared/driver-factories/prompts');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { ok, failed } = require('../../../shared/driver-result');
6
+ const { getOmpPaths } = require('./config');
7
+
8
+ const MANAGED_PROMPT_NAMESPACE = 'coding-tool-x';
9
+ const MANAGED_PROMPT_FILE = 'prompt.md';
10
+
11
+ function resolvePromptsDir(context = {}) {
12
+ const native = context.pathContext?.native || {};
13
+ return native.prompts
14
+ || context.paths?.prompts
15
+ || getOmpPaths(process.env, { resolveRuntime: false }).prompts;
16
+ }
17
+
18
+ function stripFrontmatter(content) {
19
+ return String(content || '').replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n(?:\r?\n)?/, '');
20
+ }
21
+
22
+ function buildTemplateContent(content) {
23
+ return `---\ndescription: "coding-tool-x prompt"\n---\n\n${content}`;
24
+ }
4
25
 
5
26
  function createDriver(context = {}) {
6
- return createPromptDriver({ ...context, platform: 'omp' });
27
+ const promptsDir = resolvePromptsDir(context);
28
+ const managedDir = path.join(promptsDir, MANAGED_PROMPT_NAMESPACE);
29
+ const promptPath = path.join(managedDir, MANAGED_PROMPT_FILE);
30
+
31
+ const invoke = (operation, args) => {
32
+ try {
33
+ if (operation === 'read') {
34
+ return ok('omp', 'prompts', operation,
35
+ fs.existsSync(promptPath) ? stripFrontmatter(fs.readFileSync(promptPath, 'utf8')) : '');
36
+ }
37
+
38
+ if (operation === 'write') {
39
+ const [content] = args;
40
+ if (typeof content !== 'string') throw new Error('提示词内容必须是字符串');
41
+ fs.mkdirSync(managedDir, { recursive: true });
42
+ fs.writeFileSync(promptPath, buildTemplateContent(content), 'utf8');
43
+ return ok('omp', 'prompts', operation, content);
44
+ }
45
+
46
+ if (fs.existsSync(managedDir)) {
47
+ fs.rmSync(managedDir, { recursive: true, force: true });
48
+ }
49
+ return ok('omp', 'prompts', operation, true);
50
+ } catch (error) {
51
+ return failed('omp', 'prompts', operation, error);
52
+ }
53
+ };
54
+
55
+ return {
56
+ platform: 'omp',
57
+ capability: 'prompts',
58
+ path: managedDir,
59
+ read: (...args) => invoke('read', args),
60
+ write: (...args) => invoke('write', args),
61
+ remove: (...args) => invoke('remove', args)
62
+ };
7
63
  }
8
64
 
9
65
  module.exports = { createDriver };
@@ -15,6 +15,7 @@ const {
15
15
  const { createOmpGateway } = require('./gateway');
16
16
  const { prepareManagedOmpChannels } = require('./gateway-routing');
17
17
  const { probeOmpAuthGateways } = require('./auth-gateway-client');
18
+ const { restoreStoredOmpOAuthCredentials } = require('../../oauth-credentials-service');
18
19
 
19
20
  let currentPort = null;
20
21
  let lastSyncResult = null;
@@ -33,11 +34,24 @@ function withLifecycleLock(operation) {
33
34
 
34
35
  async function startOmpProxyServerUnlocked(options = {}) {
35
36
  if (gateway.status().listening) {
37
+ const oauthRestore = restoreStoredOmpOAuthCredentials(getEnabledChannels());
38
+ if (oauthRestore.warnings.length > 0) {
39
+ lastSyncResult = {
40
+ ...(lastSyncResult || {}),
41
+ warnings: [
42
+ ...(lastSyncResult?.warnings || []),
43
+ ...oauthRestore.warnings
44
+ ]
45
+ };
46
+ }
36
47
  return {
37
48
  success: true,
38
49
  port: gateway.status().port,
39
50
  sync: lastSyncResult,
40
- warnings: lastSyncResult?.warnings || []
51
+ warnings: [
52
+ ...(lastSyncResult?.warnings || []),
53
+ ...(oauthRestore.warnings || [])
54
+ ]
41
55
  };
42
56
  }
43
57
  const preserveStartTime = options.preserveStartTime || false;
@@ -56,6 +70,7 @@ async function startOmpProxyServerUnlocked(options = {}) {
56
70
  });
57
71
  currentPort = gatewayStatus.port;
58
72
  try {
73
+ const oauthRestore = restoreStoredOmpOAuthCredentials(channels);
59
74
  const oauthSupport = await probeOmpAuthGateways(channels);
60
75
  const descriptor = {
61
76
  ...gateway.descriptor(),
@@ -71,10 +86,19 @@ async function startOmpProxyServerUnlocked(options = {}) {
71
86
  lastSyncResult = {
72
87
  ...lastSyncResult,
73
88
  warnings: [
89
+ ...(oauthRestore.warnings || []),
74
90
  ...(lastSyncResult?.warnings || []),
75
91
  ...oauthSupport.warnings
76
92
  ]
77
93
  };
94
+ } else if (oauthRestore.warnings.length > 0) {
95
+ lastSyncResult = {
96
+ ...lastSyncResult,
97
+ warnings: [
98
+ ...(oauthRestore.warnings || []),
99
+ ...(lastSyncResult?.warnings || [])
100
+ ]
101
+ };
78
102
  }
79
103
  } catch (error) {
80
104
  if (wasEnabled) {