coding-tool-x 3.5.14 → 3.6.0

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,7 +5,7 @@
5
5
  <link rel="icon" href="/favicon.ico">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>coding-tool-x · AI 编程工作台</title>
8
- <script type="module" crossorigin src="/assets/index-Bvdezm5-.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CLra7uzu.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/markdown-BNJfEgCA.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vue-vendor-B5RqjEjQ.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/naive-ui-DM897mIP.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.5.14",
3
+ "version": "3.6.0",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -85,6 +85,18 @@ function deletePM2Process(name) {
85
85
  });
86
86
  }
87
87
 
88
+ function restartPM2Process(name) {
89
+ return new Promise((resolve, reject) => {
90
+ pm2.restart(name, (err) => {
91
+ if (err) {
92
+ reject(err);
93
+ } else {
94
+ resolve();
95
+ }
96
+ });
97
+ });
98
+ }
99
+
88
100
  /**
89
101
  * 获取 Coding-Tool 进程
90
102
  */
@@ -169,6 +181,41 @@ function updatePM2Daemon() {
169
181
  });
170
182
  }
171
183
 
184
+ function dumpPM2State(timeoutMs = 2000) {
185
+ return new Promise((resolve) => {
186
+ let settled = false;
187
+ const timer = setTimeout(() => {
188
+ if (settled) {
189
+ return;
190
+ }
191
+ settled = true;
192
+ resolve({ timedOut: true, err: null });
193
+ }, timeoutMs);
194
+
195
+ pm2.dump((err) => {
196
+ if (settled) {
197
+ return;
198
+ }
199
+ settled = true;
200
+ clearTimeout(timer);
201
+ resolve({ timedOut: false, err: err || null });
202
+ });
203
+ });
204
+ }
205
+
206
+ async function finalizePM2Session(options = {}) {
207
+ const shouldPersist = options.persist === true;
208
+ if (shouldPersist) {
209
+ const result = await dumpPM2State(options.timeoutMs);
210
+ if (result.err) {
211
+ console.log(chalk.yellow(`[WARN] 保存 PM2 状态失败: ${result.err.message}`));
212
+ } else if (result.timedOut) {
213
+ console.log(chalk.yellow('[WARN] 保存 PM2 状态超时,继续关闭 CLI 连接'));
214
+ }
215
+ }
216
+ disconnectPM2();
217
+ }
218
+
172
219
  function isPortOwnedByPid(port, pid) {
173
220
  if (!pid || pid <= 0) {
174
221
  return false;
@@ -432,10 +479,9 @@ async function attemptStartService(startOptions, port, options = {}) {
432
479
 
433
480
  function cleanupFailedStart() {
434
481
  return new Promise((resolve) => {
435
- pm2.delete(PM2_APP_NAME, () => {
436
- pm2.dump(() => {
437
- resolve();
438
- });
482
+ pm2.delete(PM2_APP_NAME, async () => {
483
+ await finalizePM2Session({ persist: true });
484
+ resolve();
439
485
  });
440
486
  });
441
487
  }
@@ -527,9 +573,7 @@ async function handleStart() {
527
573
  console.log(chalk.gray(' ') + chalk.cyan('ctx logs') + chalk.gray(' - 查看实时日志'));
528
574
  console.log(chalk.gray(' ') + chalk.cyan('ctx stop') + chalk.gray(' - 停止服务\n'));
529
575
 
530
- pm2.dump(() => {
531
- disconnectPM2();
532
- });
576
+ await finalizePM2Session({ persist: true });
533
577
  } catch (error) {
534
578
  console.error(chalk.red('启动失败:'), error.message);
535
579
  disconnectPM2();
@@ -549,9 +593,7 @@ async function handleStop() {
549
593
  const stopResult = await stopAllManagedInstances(existing, config);
550
594
  printStopResult(stopResult);
551
595
 
552
- pm2.dump(() => {
553
- disconnectPM2();
554
- });
596
+ await finalizePM2Session({ persist: true });
555
597
  } catch (error) {
556
598
  console.error(chalk.red('停止失败:'), error.message);
557
599
  disconnectPM2();
@@ -573,19 +615,9 @@ async function handleRestart() {
573
615
  return;
574
616
  }
575
617
 
576
- pm2.restart(PM2_APP_NAME, (err) => {
577
- if (err) {
578
- console.error(chalk.red('\n[ERROR] 重启服务失败:'), err.message);
579
- disconnectPM2();
580
- process.exit(1);
581
- }
582
-
583
- console.log(chalk.green('\n[OK] Coding-Tool 服务已重启\n'));
584
-
585
- pm2.dump((err) => {
586
- disconnectPM2();
587
- });
588
- });
618
+ await restartPM2Process(PM2_APP_NAME);
619
+ console.log(chalk.green('\n[OK] Coding-Tool 服务已重启\n'));
620
+ await finalizePM2Session({ persist: true });
589
621
  } catch (error) {
590
622
  console.error(chalk.red('重启失败:'), error.message);
591
623
  disconnectPM2();
package/src/index.js CHANGED
@@ -31,6 +31,12 @@ function getVersion() {
31
31
  return packageJson.version;
32
32
  }
33
33
 
34
+ function finishCli(code = 0) {
35
+ eventBus.emitSync('cli:shutdown', {});
36
+ PluginManager.shutdownPlugins();
37
+ process.exit(code);
38
+ }
39
+
34
40
  // 显示帮助信息
35
41
  function showHelp() {
36
42
  const version = getVersion();
@@ -156,19 +162,19 @@ async function main() {
156
162
 
157
163
  if (subCommand === 'start') {
158
164
  await handleStart();
159
- return;
165
+ return finishCli(0);
160
166
  }
161
167
  if (subCommand === 'stop') {
162
168
  await handleStop();
163
- return;
169
+ return finishCli(0);
164
170
  }
165
171
  if (subCommand === 'restart') {
166
172
  await handleRestart();
167
- return;
173
+ return finishCli(0);
168
174
  }
169
175
  if (subCommand === 'status') {
170
176
  await handleStatus();
171
- return;
177
+ return finishCli(0);
172
178
  }
173
179
  if (subCommand === 'logs') {
174
180
  const options = {};
@@ -213,25 +219,25 @@ async function main() {
213
219
  // start 命令 - 启动服务(后台)
214
220
  if (args[0] === 'start') {
215
221
  await handleStart();
216
- return;
222
+ return finishCli(0);
217
223
  }
218
224
 
219
225
  // stop 命令 - 停止服务
220
226
  if (args[0] === 'stop') {
221
227
  await handleStop();
222
- return;
228
+ return finishCli(0);
223
229
  }
224
230
 
225
231
  // restart 命令 - 重启服务
226
232
  if (args[0] === 'restart') {
227
233
  await handleRestart();
228
- return;
234
+ return finishCli(0);
229
235
  }
230
236
 
231
237
  // status 命令 - 查看服务状态
232
238
  if (args[0] === 'status') {
233
239
  await handleStatus();
234
- return;
240
+ return finishCli(0);
235
241
  }
236
242
 
237
243
  // ui 命令 - Web UI 管理
@@ -239,10 +245,13 @@ async function main() {
239
245
  const subCommand = args[1];
240
246
  if (subCommand === 'start') {
241
247
  await handleStart(); // UI start 实际上就是启动整个服务
248
+ return finishCli(0);
242
249
  } else if (subCommand === 'stop') {
243
250
  await handleStop();
251
+ return finishCli(0);
244
252
  } else if (subCommand === 'restart') {
245
253
  await handleRestart();
254
+ return finishCli(0);
246
255
  } else {
247
256
  // 默认前台运行
248
257
  const { handleUI } = require('./commands/ui');
@@ -670,9 +679,7 @@ async function main() {
670
679
 
671
680
  case 'exit':
672
681
  console.log('\n[BYE] 再见!\n');
673
- eventBus.emitSync('cli:shutdown', {});
674
- PluginManager.shutdownPlugins();
675
- process.exit(0);
682
+ finishCli(0);
676
683
  break;
677
684
 
678
685
  default:
@@ -134,7 +134,6 @@ module.exports = (config) => {
134
134
  providerKey,
135
135
  baseUrl,
136
136
  apiKey,
137
- requiresOpenaiAuth,
138
137
  websiteUrl,
139
138
  enabled,
140
139
  weight,
@@ -149,7 +148,7 @@ module.exports = (config) => {
149
148
  return res.status(400).json({ error: 'Missing required fields: name, providerKey, baseUrl' });
150
149
  }
151
150
 
152
- if (!apiKey && requiresOpenaiAuth !== true) {
151
+ if (!apiKey) {
153
152
  return res.status(400).json({ error: 'Missing required fields: apiKey' });
154
153
  }
155
154
 
@@ -159,7 +158,7 @@ module.exports = (config) => {
159
158
  }
160
159
 
161
160
  // wireApi 固定为 'responses' (OpenAI Responses API 格式)
162
- const channel = createChannel(name, providerKey, baseUrl, requiresOpenaiAuth === true ? '' : apiKey, 'responses', {
161
+ const channel = createChannel(name, providerKey, baseUrl, apiKey, 'responses', {
163
162
  websiteUrl,
164
163
  enabled,
165
164
  weight,
@@ -168,7 +167,7 @@ module.exports = (config) => {
168
167
  speedTestModel: speedTestModel || null,
169
168
  presetId: presetId || null,
170
169
  gatewaySourceType,
171
- requiresOpenaiAuth: requiresOpenaiAuth === true
170
+ requiresOpenaiAuth: false
172
171
  });
173
172
  res.json(channel);
174
173
  broadcastSchedulerState('codex', getSchedulerState('codex'));
@@ -191,9 +190,6 @@ module.exports = (config) => {
191
190
  const { channelId } = req.params;
192
191
  const updates = req.body;
193
192
  const currentChannel = getChannels().channels.find(channel => channel.id === channelId) || null;
194
- const finalRequiresOpenaiAuth = Object.prototype.hasOwnProperty.call(updates, 'requiresOpenaiAuth')
195
- ? updates.requiresOpenaiAuth === true
196
- : currentChannel?.requiresOpenaiAuth === true;
197
193
  const finalApiKey = Object.prototype.hasOwnProperty.call(updates, 'apiKey')
198
194
  ? updates.apiKey
199
195
  : currentChannel?.apiKey;
@@ -204,15 +200,10 @@ module.exports = (config) => {
204
200
  }
205
201
  }
206
202
  if (currentChannel) {
207
- if (!finalRequiresOpenaiAuth && !finalApiKey) {
203
+ if (!finalApiKey) {
208
204
  return res.status(400).json({ error: 'Missing required fields: apiKey' });
209
205
  }
210
- if (Object.prototype.hasOwnProperty.call(updates, 'requiresOpenaiAuth')) {
211
- updates.requiresOpenaiAuth = finalRequiresOpenaiAuth;
212
- }
213
- if (finalRequiresOpenaiAuth) {
214
- updates.apiKey = '';
215
- }
206
+ updates.requiresOpenaiAuth = false;
216
207
  }
217
208
 
218
209
  const channel = updateChannel(channelId, updates);
@@ -34,8 +34,8 @@ function resolveCurrentManagedChannel(channels = []) {
34
34
  return allChannels.find(ch => ch.enabled !== false) || null;
35
35
  }
36
36
 
37
- function channelRequiresOpenaiAuth(channel) {
38
- return channel?.requiresOpenaiAuth === true;
37
+ function channelRequiresOpenaiAuth() {
38
+ return false;
39
39
  }
40
40
 
41
41
  function buildManagedCodexEnvMap(channels = [], { includeProxyKey = false, activeChannel = null } = {}) {
@@ -150,7 +150,7 @@ class CodexChannelService extends BaseChannelService {
150
150
  ch.speedTestModel = ch.speedTestModel || null;
151
151
  ch.modelRedirects = Array.isArray(ch.modelRedirects) ? ch.modelRedirects : [];
152
152
  ch.gatewaySourceType = ch.gatewaySourceType || 'codex';
153
- ch.requiresOpenaiAuth = ch.requiresOpenaiAuth === true;
153
+ ch.requiresOpenaiAuth = false;
154
154
  ch.queryParams = ch.queryParams || {};
155
155
  return ch;
156
156
  }
@@ -135,10 +135,14 @@ function buildFailureLogPayload({
135
135
  stage,
136
136
  timestamp = Date.now()
137
137
  }) {
138
- const errorMessage = String(
138
+ const rawErrorMessage = String(
139
139
  error?.message
140
- || message
141
140
  || error
141
+ || ''
142
+ ).trim();
143
+ const displayMessage = String(
144
+ message
145
+ || rawErrorMessage
142
146
  || 'Request failed'
143
147
  );
144
148
 
@@ -149,8 +153,9 @@ function buildFailureLogPayload({
149
153
  time: formatRealtimeTime(timestamp),
150
154
  channel: channel || 'Unknown',
151
155
  model: model || '',
152
- message: errorMessage,
153
- error: errorMessage,
156
+ message: displayMessage,
157
+ error: displayMessage,
158
+ rawError: rawErrorMessage || undefined,
154
159
  statusCode: Number.isFinite(Number(statusCode)) ? Number(statusCode) : null,
155
160
  stage: stage || 'proxy',
156
161
  inputTokens: 0,