@adversity/coding-tool-x 3.0.0 → 3.0.2

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.
@@ -5,12 +5,12 @@
5
5
  <link rel="icon" href="/favicon.ico">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>CC-TOOL - ClaudeCode增强工作助手</title>
8
- <script type="module" crossorigin src="/assets/index-AtwYwBZD.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DfPKAt9R.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/vue-vendor-6JaYHOiI.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendors-D2HHw_aW.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/icons-BlzwYoRU.js">
12
- <link rel="modulepreload" crossorigin href="/assets/naive-ui-BcSq2wzw.js">
13
- <link rel="stylesheet" crossorigin href="/assets/index-BNHWEpD4.css">
12
+ <link rel="modulepreload" crossorigin href="/assets/naive-ui-B1TP-0TP.js">
13
+ <link rel="stylesheet" crossorigin href="/assets/index-TjhcaFRe.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adversity/coding-tool-x",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -7,11 +7,13 @@ const {
7
7
  updateChannel,
8
8
  deleteChannel,
9
9
  getCurrentSettings,
10
- getBestChannelForRestore
10
+ getBestChannelForRestore,
11
+ updateClaudeSettingsWithModelConfig
11
12
  } = require('../services/channels');
12
13
  const { getSchedulerState } = require('../services/channel-scheduler');
13
14
  const { getChannelHealthStatus, getAllChannelHealthStatus, resetChannelHealth } = require('../services/channel-health');
14
15
  const { testChannelSpeed, testMultipleChannels, getLatencyLevel } = require('../services/speed-test');
16
+ const { fetchModelsFromProvider } = require('../services/model-detector');
15
17
  const { broadcastLog, broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
16
18
 
17
19
  // GET /api/channels - Get all channels with health status
@@ -147,6 +149,10 @@ router.post('/:id/apply-to-settings', async (req, res) => {
147
149
  const { stopProxyServer } = require('../proxy-server');
148
150
  await stopProxyServer({ clearStartTime: false });
149
151
 
152
+ // Re-apply channel settings after proxy stop to prevent race condition
153
+ // (stopProxyServer restores backup, then we overwrite it with current channel)
154
+ updateClaudeSettingsWithModelConfig(channel);
155
+
150
156
  console.log(`✅ 已停���动态切换,默认使用当前渠道`);
151
157
  broadcastLog({
152
158
  type: 'action',
@@ -215,6 +221,51 @@ router.post('/:id/speed-test', async (req, res) => {
215
221
  }
216
222
  });
217
223
 
224
+ // GET /api/channels/:id/models - Get available models for a channel
225
+ router.get('/:id/models', async (req, res) => {
226
+ try {
227
+ const { id } = req.params;
228
+ const VALID_CHANNEL_TYPES = ['claude', 'codex', 'gemini', 'openai_compatible'];
229
+ const { type = 'claude' } = req.query;
230
+
231
+ if (!VALID_CHANNEL_TYPES.includes(type)) {
232
+ return res.status(400).json({ error: 'Invalid channel type', channelId: id });
233
+ }
234
+
235
+ const channels = getAllChannels();
236
+ const channel = channels.find(ch => ch.id === id);
237
+
238
+ if (!channel) {
239
+ return res.status(404).json({ error: '渠道不存在' });
240
+ }
241
+
242
+ // If no type specified or type is 'claude', auto-detect
243
+ let channelType = type;
244
+ if (!type || type === 'claude') {
245
+ const { detectChannelType } = require('../services/model-detector');
246
+ channelType = detectChannelType(channel);
247
+ console.log(`[API] Auto-detected channel type: ${channelType} for ${channel.name}`);
248
+ }
249
+
250
+ const result = await fetchModelsFromProvider(channel, channelType);
251
+
252
+ res.json({
253
+ channelId: id,
254
+ models: result.models,
255
+ supported: result.supported,
256
+ cached: result.cached,
257
+ fetchedAt: result.lastChecked || new Date().toISOString(),
258
+ error: result.error
259
+ });
260
+ } catch (error) {
261
+ console.error('Error fetching channel models:', error);
262
+ res.status(500).json({
263
+ error: '获取模型列表失败',
264
+ channelId: req.params.id
265
+ });
266
+ }
267
+ });
268
+
218
269
  // POST /api/channels/speed-test-all - Test all channels speed
219
270
  router.post('/speed-test-all', async (req, res) => {
220
271
  try {
@@ -25,7 +25,8 @@ function buildPreviewSummary(data) {
25
25
  counts: {
26
26
  permissionTemplates: (data.data.permissionTemplates || []).length,
27
27
  configTemplates: (data.data.configTemplates || []).length,
28
- channels: (data.data.channels || []).length
28
+ channels: (data.data.channels || []).length,
29
+ plugins: (data.data.plugins || []).length
29
30
  },
30
31
  items: {
31
32
  permissionTemplates: (data.data.permissionTemplates || []).map(t => ({
@@ -42,6 +43,11 @@ function buildPreviewSummary(data) {
42
43
  id: c.id,
43
44
  name: c.name,
44
45
  type: c.type
46
+ })),
47
+ plugins: (data.data.plugins || []).map(p => ({
48
+ name: p.name,
49
+ type: p.type,
50
+ version: p.version
45
51
  }))
46
52
  }
47
53
  };
@@ -36,7 +36,23 @@ function saveActiveChannelId(channelId) {
36
36
  fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
37
37
  }
38
38
 
39
- // 从 settings.json 找到当前激活的渠道
39
+
40
+ // 加载上次激活的渠道ID
41
+ function loadActiveChannelId() {
42
+ const filePath = path.join(os.homedir(), '.claude', 'cc-tool', 'active-channel.json');
43
+ try {
44
+ if (fs.existsSync(filePath)) {
45
+ const content = fs.readFileSync(filePath, 'utf8');
46
+ const data = JSON.parse(content);
47
+ return data.activeChannelId || null;
48
+ }
49
+ } catch (error) {
50
+ console.error('[Proxy] Error loading active channel ID:', error);
51
+ }
52
+ return null;
53
+ }
54
+
55
+ // 从 settings.json 找到当前激活的渠道(多级回退策略)
40
56
  function findActiveChannelFromSettings() {
41
57
  try {
42
58
  const settings = readSettings();
@@ -49,25 +65,57 @@ function findActiveChannelFromSettings() {
49
65
 
50
66
  // 如果 apiKey 仍为空,尝试从 apiKeyHelper 提取
51
67
  if (!apiKey && settings?.apiKeyHelper) {
52
- const match = settings.apiKeyHelper.match(/['"]([^'"]+)['"]/);
68
+ const match = settings.apiKeyHelper.match(/['\"]([^'\"]+)['\"]/)
53
69
  if (match && match[1]) {
54
70
  apiKey = match[1];
55
71
  }
56
72
  }
57
73
 
58
74
  if (!baseUrl || !apiKey || baseUrl.includes('127.0.0.1')) {
75
+ console.log('[Proxy] Invalid settings: empty baseUrl/apiKey or localhost detected');
59
76
  return null;
60
77
  }
61
78
 
62
- // 找到匹配的渠道
63
79
  const channels = getAllChannels();
64
- const matchingChannel = channels.find(ch =>
80
+
81
+ // Level 1: Exact match (baseUrl + apiKey)
82
+ let matchingChannel = channels.find(ch =>
65
83
  ch.baseUrl === baseUrl && ch.apiKey === apiKey
66
84
  );
67
85
 
68
- return matchingChannel;
86
+ if (matchingChannel) {
87
+ console.log(`[Proxy] Level 1 - Exact match: ${matchingChannel.name}`);
88
+ return matchingChannel;
89
+ }
90
+
91
+ // Level 2: Match by baseUrl only (when apiKey differs)
92
+ matchingChannel = channels.find(ch => ch.baseUrl === baseUrl);
93
+ if (matchingChannel) {
94
+ console.log(`[Proxy] Level 2 - Matched by baseUrl only: ${matchingChannel.name}`);
95
+ return matchingChannel;
96
+ }
97
+
98
+ // Level 3: Use active-channel.json for last known active channel
99
+ const activeChannelId = loadActiveChannelId();
100
+ if (activeChannelId) {
101
+ matchingChannel = channels.find(ch => ch.id === activeChannelId);
102
+ if (matchingChannel) {
103
+ console.log(`[Proxy] Level 3 - Using last active channel: ${matchingChannel.name}`);
104
+ return matchingChannel;
105
+ }
106
+ }
107
+
108
+ // Level 4: Return first enabled channel as last resort
109
+ matchingChannel = channels.find(ch => ch.enabled !== false);
110
+ if (matchingChannel) {
111
+ console.log(`[Proxy] Level 4 - Using first enabled channel: ${matchingChannel.name}`);
112
+ return matchingChannel;
113
+ }
114
+
115
+ console.log('[Proxy] No matching channel found after all fallback levels');
116
+ return null;
69
117
  } catch (err) {
70
- console.error('Error finding active channel:', err);
118
+ console.error('[Proxy] Error finding active channel:', err);
71
119
  return null;
72
120
  }
73
121
  }
@@ -105,6 +153,15 @@ router.post('/start', async (req, res) => {
105
153
  });
106
154
  }
107
155
 
156
+ // Fix 4: Validate enabled channels count before starting proxy
157
+ const allChannels = getAllChannels();
158
+ const enabledCount = allChannels.filter(ch => ch.enabled !== false).length;
159
+ if (enabledCount === 0) {
160
+ return res.status(400).json({
161
+ error: '没有启用的渠道。请至少启用一个渠道后再启动动态切换。'
162
+ });
163
+ }
164
+
108
165
  // 2. 从 settings.json 找到当前使用的渠道
109
166
  const currentChannel = findActiveChannelFromSettings();
110
167
  if (!currentChannel) {
@@ -187,7 +187,8 @@ function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig = {}) {
187
187
  maxConcurrency: extraConfig.maxConcurrency,
188
188
  presetId: extraConfig.presetId || 'official',
189
189
  modelConfig: extraConfig.modelConfig || null,
190
- proxyUrl: extraConfig.proxyUrl || ''
190
+ proxyUrl: extraConfig.proxyUrl || '',
191
+ speedTestModel: extraConfig.speedTestModel || null
191
192
  });
192
193
 
193
194
  data.channels.push(newChannel);
@@ -203,6 +204,9 @@ function updateChannel(id, updates) {
203
204
  throw new Error('Channel not found');
204
205
  }
205
206
 
207
+ // Store old channel data before updates
208
+ const oldChannel = { ...data.channels[index] };
209
+
206
210
  const merged = { ...data.channels[index], ...updates };
207
211
  data.channels[index] = applyChannelDefaults({
208
212
  ...merged,
@@ -211,10 +215,49 @@ function updateChannel(id, updates) {
211
215
  enabled: merged.enabled,
212
216
  presetId: merged.presetId,
213
217
  modelConfig: merged.modelConfig,
214
- proxyUrl: merged.proxyUrl
218
+ proxyUrl: merged.proxyUrl,
219
+ speedTestModel: merged.speedTestModel
215
220
  });
216
221
 
222
+ // Get proxy status
223
+ const { getProxyStatus } = require('../proxy-server');
224
+ const proxyStatus = getProxyStatus();
225
+ const isProxyRunning = proxyStatus.running;
226
+
227
+ // Fix 1: Detect enabled toggle (false → true) when proxy is OFF
228
+ if (!isProxyRunning && !oldChannel.enabled && data.channels[index].enabled) {
229
+ console.log(`[Settings-sync] Proxy is OFF and channel "${data.channels[index].name}" was enabled, syncing settings.json...`);
230
+ updateClaudeSettingsWithModelConfig(data.channels[index]);
231
+ }
232
+
233
+ // Fix 2: Single-channel enforcement when proxy is OFF
234
+ if (!isProxyRunning && data.channels[index].enabled && !oldChannel.enabled) {
235
+ // Disable all other channels
236
+ data.channels.forEach((ch, i) => {
237
+ if (i !== index && ch.enabled) {
238
+ ch.enabled = false;
239
+ }
240
+ });
241
+ console.log(`[Single-channel mode] Enabled "${data.channels[index].name}", disabled all others`);
242
+ }
243
+
244
+ // Fix 3: Prevent disabling last enabled channel when proxy is OFF
245
+ if (!isProxyRunning && !data.channels[index].enabled && oldChannel.enabled) {
246
+ const enabledCount = data.channels.filter(ch => ch.enabled).length;
247
+ if (enabledCount === 0) {
248
+ throw new Error('无法禁用最后一个启用的渠道。请先启用其他渠道或启动动态切换。');
249
+ }
250
+ }
251
+
217
252
  saveChannels(data);
253
+
254
+ // Auto-sync settings.json if this is the currently active channel
255
+ const currentSettings = getCurrentSettings();
256
+ if (currentSettings && currentSettings.baseUrl === oldChannel.baseUrl) {
257
+ console.log(`[Auto-sync] Channel "${data.channels[index].name}" is active, syncing settings.json...`);
258
+ updateClaudeSettingsWithModelConfig(data.channels[index]);
259
+ }
260
+
218
261
  return data.channels[index];
219
262
  }
220
263
 
@@ -343,5 +386,6 @@ module.exports = {
343
386
  deleteChannel,
344
387
  applyChannelToSettings,
345
388
  getBestChannelForRestore,
346
- updateClaudeSettings
389
+ updateClaudeSettings,
390
+ updateClaudeSettingsWithModelConfig
347
391
  };
@@ -226,6 +226,37 @@ function updateChannel(channelId, updates) {
226
226
  };
227
227
 
228
228
  data.channels[index] = newChannel;
229
+
230
+ // Get proxy status
231
+ const { getCodexProxyStatus } = require('../codex-proxy-server');
232
+ const proxyStatus = getCodexProxyStatus();
233
+ const isProxyRunning = proxyStatus.running;
234
+
235
+ // Fix 1: Detect enabled toggle (false → true) when proxy is OFF
236
+ if (!isProxyRunning && !oldChannel.enabled && data.channels[index].enabled) {
237
+ console.log(`[Codex Settings-sync] Proxy is OFF and channel "${data.channels[index].name}" was enabled, syncing config.toml...`);
238
+ applyChannelToSettings(channelId);
239
+ }
240
+
241
+ // Fix 2: Single-channel enforcement when proxy is OFF
242
+ if (!isProxyRunning && data.channels[index].enabled && !oldChannel.enabled) {
243
+ // Disable all other channels
244
+ data.channels.forEach((ch, i) => {
245
+ if (i !== index && ch.enabled) {
246
+ ch.enabled = false;
247
+ }
248
+ });
249
+ console.log(`[Codex Single-channel mode] Enabled "${data.channels[index].name}", disabled all others`);
250
+ }
251
+
252
+ // Fix 3: Prevent disabling last enabled channel when proxy is OFF
253
+ if (!isProxyRunning && !data.channels[index].enabled && oldChannel.enabled) {
254
+ const enabledCount = data.channels.filter(ch => ch.enabled).length;
255
+ if (enabledCount === 0) {
256
+ throw new Error('无法禁用最后一个启用的渠道。请先启用其他渠道或启动动态切换。');
257
+ }
258
+ }
259
+
229
260
  saveChannels(data);
230
261
 
231
262
  // 处理环境变量更新