coding-tool-x 3.9.4 → 3.9.5-261012

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -23,6 +23,8 @@
23
23
  - **依赖安全升级** - adm-zip 升至 0.6.0(GHSA-xcpc-8h2w-3j85)、js-yaml 升至 4.3.1(GHSA-h67p-54hq-rp68 / GHSA-52cp-r559-cp3m / GHSA-5p4m-2wfm-xmqj)、pm2 升至 7.0.3(GHSA-x5gf-qvw8-r2rm),overrides 同步更新 basic-ftp / qs / systeminformation / js-yaml;`npm audit` 报告归零
24
24
 
25
25
  ### Fixed
26
+ - **代理连接与内存回收** - 修复各 CLI 请求残留、客户端断开后的上游取消和渠道释放;后台退出时排空代理请求,延长上游空闲超时;OMP 重试取消失败响应,过滤连接保活头,并增加内存压力回收。
27
+ - **CLI 状态和错误子命令** - 修复 `ctx status` 的代理 Driver 状态解析;错误子命令返回非零退出码,不再进入控制面。
26
28
  - **Claude / Gemini 原生 wire 兼容** - OpenCode Claude / Gemini 代理、模型探测和测速统一使用 Anthropic Messages 与 Google Generative AI(含 Gemini CLI)原生请求格式、认证头与目标 URL;OMP managed provider 写入 `anthropic-messages` / `google-generative-ai` 标识,避免代理、adapter 与探测各自维护不一致的请求格式
27
29
  - **OMP Codex Responses 路径兼容** - managed gateway 将 OMP 原生 `openai-codex-responses` 的 `/codex/responses` 请求映射到 OpenAI-compatible 渠道的 `/v1/responses`,避免 Codex-only 服务返回 HTTP 404
28
30
  - **OMP 渠道失败不再冻结** - OMP 动态切换开启后,失败渠道不再进入冻结状态。OMP 网关按请求即时回退其他渠道,冻结会导致短暂故障的渠道在恢复前被排除在后续分配之外
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.9.4",
3
+ "version": "3.9.5-261012",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -299,7 +299,13 @@ async function getProxyStatusEntries({ registry = getPlatformRegistry(), runtime
299
299
  entries.push({ platform, state: { status: 'unsupported', ...context }, port: getPlatformPort(platform, config) });
300
300
  continue;
301
301
  }
302
- const state = await Promise.resolve(driver.status());
302
+ const result = await Promise.resolve(driver.status());
303
+ // Proxy drivers use the shared Driver contract and wrap successful
304
+ // service status payloads in { status: 'ok', data }. Keep compatibility
305
+ // with legacy drivers that return the service payload directly.
306
+ const state = result && result.status === 'ok'
307
+ ? (result.data && typeof result.data === 'object' ? result.data : {})
308
+ : result;
303
309
  if (state && typeof state.status === 'string' && state.status !== 'ok') {
304
310
  entries.push({ platform, state: { ...state, ...context }, port: getPlatformPort(platform, config) });
305
311
  } else {
@@ -508,8 +514,10 @@ function buildStartOptions(port, enableHost, enableHttps) {
508
514
  interpreter: 'node',
509
515
  windowsHide: true,
510
516
  autorestart: true,
511
- kill_timeout: 5000,
517
+ // Let active OMP streams drain before PM2 forcibly terminates the process.
518
+ kill_timeout: 125000,
512
519
  max_memory_restart: '500M',
520
+ node_args: '--expose-gc',
513
521
  env,
514
522
  output: path.join(PATHS.logs, STARTUP_LOG_FILE),
515
523
  error: path.join(PATHS.logs, STARTUP_LOG_FILE),
@@ -761,9 +769,9 @@ async function handleStatus() {
761
769
  console.log(chalk.gray(` ${label}${chalk.gray('[N/A] 不支持')}`));
762
770
  continue;
763
771
  }
764
- if (state.status === 'failed') {
772
+ if (state.status && state.status !== 'ok') {
765
773
  console.log(chalk.red(` ${label}${chalk.red('[ERROR] 查询失败')}`) +
766
- chalk.gray(` (${state.error || 'unknown error'})`));
774
+ chalk.gray(` (${state.error || state.status || 'unknown error'})`));
767
775
  continue;
768
776
  }
769
777
  console.log(chalk.gray(` ${label}`) + (state.running
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "source": "models.dev",
3
3
  "sourceUrl": "https://models.dev/api.json",
4
- "lastUpdated": "2026-09-28",
4
+ "lastUpdated": "2026-10-09",
5
5
  "defaultModels": {
6
6
  "claude": [
7
7
  "claude-opus-4-7",
@@ -18,7 +18,9 @@
18
18
  "claude-opus-4-5",
19
19
  "claude-haiku-4-5",
20
20
  "claude-sonnet-4-5",
21
- "claude-opus-5-5"
21
+ "claude-opus-5-5",
22
+ "claude-sonnet-5-5",
23
+ "claude-haiku-5-5"
22
24
  ],
23
25
  "codex": [
24
26
  "gpt-5.5",
@@ -58,7 +60,8 @@
58
60
  "gpt-6-luna",
59
61
  "gpt-6-sol",
60
62
  "gpt-daybreak-blue-latest",
61
- "gpt-daybreak-red-latest"
63
+ "gpt-daybreak-red-latest",
64
+ "gpt-6.1-sol"
62
65
  ],
63
66
  "gemini": [
64
67
  "gemini-3.1-pro-preview",
@@ -95,6 +98,7 @@
95
98
  "claude-opus-4.7": "claude-opus-4-7",
96
99
  "claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
97
100
  "claude-sonnet-4-6": "claude-sonnet-4-6",
101
+ "codestral-2508": "mistral/codestral-2508",
98
102
  "codestral-latest": "mistral/codestral-latest",
99
103
  "deepseek-flash": "deepseek/deepseek-flash",
100
104
  "deepseek-v4-flash": "deepseek/deepseek-v4-flash",
@@ -142,6 +146,7 @@
142
146
  "kimi-k3": "moonshotai/kimi-k3",
143
147
  "mistral-large-2411": "mistral/mistral-large-2411",
144
148
  "mistral-large-2512": "mistral/mistral-large-2512",
149
+ "mistral-large-4": "mistral/mistral-large-4",
145
150
  "mistral-large-latest": "mistral/mistral-large-latest",
146
151
  "mistral-medium-2505": "mistral/mistral-medium-2505",
147
152
  "mistral-medium-2508": "mistral/mistral-medium-2508",
@@ -1506,6 +1511,33 @@
1506
1511
  "omp"
1507
1512
  ]
1508
1513
  },
1514
+ "claude-haiku-5-5": {
1515
+ "id": "claude-haiku-5-5",
1516
+ "input": [
1517
+ "text",
1518
+ "image"
1519
+ ],
1520
+ "limit": {
1521
+ "context": 1000000,
1522
+ "output": 128000
1523
+ },
1524
+ "name": "Claude Haiku 5.5",
1525
+ "pricing": {
1526
+ "cacheCreation": 0.125,
1527
+ "cacheRead": 0.01,
1528
+ "input": 0.1,
1529
+ "output": 0.5
1530
+ },
1531
+ "provider": "anthropic",
1532
+ "reasoning": true,
1533
+ "sourceId": "anthropic/claude-haiku-5-5",
1534
+ "supportsTools": true,
1535
+ "toolTypes": [
1536
+ "claude",
1537
+ "opencode",
1538
+ "omp"
1539
+ ]
1540
+ },
1509
1541
  "claude-opus-4-5": {
1510
1542
  "id": "claude-opus-4-5",
1511
1543
  "input": [
@@ -1702,7 +1734,7 @@
1702
1734
  "image"
1703
1735
  ],
1704
1736
  "limit": {
1705
- "context": 1000000,
1737
+ "context": 200000,
1706
1738
  "output": 64000
1707
1739
  },
1708
1740
  "name": "Claude Sonnet 4.5 (latest)",
@@ -1729,7 +1761,7 @@
1729
1761
  "image"
1730
1762
  ],
1731
1763
  "limit": {
1732
- "context": 1000000,
1764
+ "context": 200000,
1733
1765
  "output": 64000
1734
1766
  },
1735
1767
  "name": "Claude Sonnet 4.5",
@@ -1803,6 +1835,33 @@
1803
1835
  "omp"
1804
1836
  ]
1805
1837
  },
1838
+ "claude-sonnet-5-5": {
1839
+ "id": "claude-sonnet-5-5",
1840
+ "input": [
1841
+ "text",
1842
+ "image"
1843
+ ],
1844
+ "limit": {
1845
+ "context": 1000000,
1846
+ "output": 128000
1847
+ },
1848
+ "name": "Claude Sonnet 5.5",
1849
+ "pricing": {
1850
+ "cacheCreation": 2.5,
1851
+ "cacheRead": 0.1,
1852
+ "input": 2,
1853
+ "output": 10
1854
+ },
1855
+ "provider": "anthropic",
1856
+ "reasoning": true,
1857
+ "sourceId": "anthropic/claude-sonnet-5-5",
1858
+ "supportsTools": true,
1859
+ "toolTypes": [
1860
+ "claude",
1861
+ "opencode",
1862
+ "omp"
1863
+ ]
1864
+ },
1806
1865
  "deepseek/deepseek-flash": {
1807
1866
  "id": "deepseek/deepseek-flash",
1808
1867
  "input": [
@@ -1889,9 +1948,9 @@
1889
1948
  },
1890
1949
  "name": "DeepSeek V4 Pro",
1891
1950
  "pricing": {
1892
- "cacheRead": 0.003625,
1893
- "input": 0.435,
1894
- "output": 0.87
1951
+ "cacheRead": 0.022,
1952
+ "input": 0.66,
1953
+ "output": 1.98
1895
1954
  },
1896
1955
  "provider": "deepseek",
1897
1956
  "reasoning": true,
@@ -3181,6 +3240,33 @@
3181
3240
  "omp"
3182
3241
  ]
3183
3242
  },
3243
+ "gpt-6.1-sol": {
3244
+ "id": "gpt-6.1-sol",
3245
+ "input": [
3246
+ "text",
3247
+ "image"
3248
+ ],
3249
+ "limit": {
3250
+ "context": 1050000,
3251
+ "output": 128000
3252
+ },
3253
+ "name": "GPT-6.1 Sol",
3254
+ "pricing": {
3255
+ "cacheCreation": 2.5,
3256
+ "cacheRead": 0.1,
3257
+ "input": 2,
3258
+ "output": 10
3259
+ },
3260
+ "provider": "openai",
3261
+ "reasoning": true,
3262
+ "sourceId": "openai/gpt-6.1-sol",
3263
+ "supportsTools": true,
3264
+ "toolTypes": [
3265
+ "codex",
3266
+ "opencode",
3267
+ "omp"
3268
+ ]
3269
+ },
3184
3270
  "gpt-daybreak-blue-latest": {
3185
3271
  "id": "gpt-daybreak-blue-latest",
3186
3272
  "input": [
@@ -3261,6 +3347,30 @@
3261
3347
  "omp"
3262
3348
  ]
3263
3349
  },
3350
+ "mistral/codestral-2508": {
3351
+ "id": "mistral/codestral-2508",
3352
+ "input": [
3353
+ "text"
3354
+ ],
3355
+ "limit": {
3356
+ "context": 256000,
3357
+ "output": 8192
3358
+ },
3359
+ "name": "Codestral 25.08",
3360
+ "pricing": {
3361
+ "cacheRead": 0.03,
3362
+ "input": 0.3,
3363
+ "output": 0.9
3364
+ },
3365
+ "provider": "mistral",
3366
+ "reasoning": false,
3367
+ "sourceId": "mistral/codestral-2508",
3368
+ "supportsTools": true,
3369
+ "toolTypes": [
3370
+ "opencode",
3371
+ "omp"
3372
+ ]
3373
+ },
3264
3374
  "mistral/codestral-latest": {
3265
3375
  "id": "mistral/codestral-latest",
3266
3376
  "input": [
@@ -3272,6 +3382,7 @@
3272
3382
  },
3273
3383
  "name": "Codestral (latest)",
3274
3384
  "pricing": {
3385
+ "cacheRead": 0.03,
3275
3386
  "input": 0.3,
3276
3387
  "output": 0.9
3277
3388
  },
@@ -3319,6 +3430,7 @@
3319
3430
  },
3320
3431
  "name": "Mistral Large 3",
3321
3432
  "pricing": {
3433
+ "cacheRead": 0.05,
3322
3434
  "input": 0.5,
3323
3435
  "output": 1.5
3324
3436
  },
@@ -3331,6 +3443,31 @@
3331
3443
  "omp"
3332
3444
  ]
3333
3445
  },
3446
+ "mistral/mistral-large-4": {
3447
+ "id": "mistral/mistral-large-4",
3448
+ "input": [
3449
+ "text",
3450
+ "image"
3451
+ ],
3452
+ "limit": {
3453
+ "context": 1048576,
3454
+ "output": 262144
3455
+ },
3456
+ "name": "Mistral Large 4",
3457
+ "pricing": {
3458
+ "cacheRead": 0.07,
3459
+ "input": 0.68,
3460
+ "output": 2.09
3461
+ },
3462
+ "provider": "mistral",
3463
+ "reasoning": true,
3464
+ "sourceId": "mistral/mistral-large-4",
3465
+ "supportsTools": true,
3466
+ "toolTypes": [
3467
+ "opencode",
3468
+ "omp"
3469
+ ]
3470
+ },
3334
3471
  "mistral/mistral-large-latest": {
3335
3472
  "id": "mistral/mistral-large-latest",
3336
3473
  "input": [
@@ -3343,6 +3480,7 @@
3343
3480
  },
3344
3481
  "name": "Mistral Large (latest)",
3345
3482
  "pricing": {
3483
+ "cacheRead": 0.05,
3346
3484
  "input": 0.5,
3347
3485
  "output": 1.5
3348
3486
  },
@@ -3415,6 +3553,7 @@
3415
3553
  },
3416
3554
  "name": "Mistral Medium 3.5",
3417
3555
  "pricing": {
3556
+ "cacheRead": 0.15,
3418
3557
  "input": 1.5,
3419
3558
  "output": 7.5
3420
3559
  },
@@ -3439,6 +3578,7 @@
3439
3578
  },
3440
3579
  "name": "Mistral Medium (latest)",
3441
3580
  "pricing": {
3581
+ "cacheRead": 0.15,
3442
3582
  "input": 1.5,
3443
3583
  "output": 7.5
3444
3584
  },
@@ -3505,11 +3645,12 @@
3505
3645
  "image"
3506
3646
  ],
3507
3647
  "limit": {
3508
- "context": 256000,
3648
+ "context": 262144,
3509
3649
  "output": 256000
3510
3650
  },
3511
3651
  "name": "Mistral Small 4",
3512
3652
  "pricing": {
3653
+ "cacheRead": 0.015,
3513
3654
  "input": 0.15,
3514
3655
  "output": 0.6
3515
3656
  },
@@ -3529,11 +3670,12 @@
3529
3670
  "image"
3530
3671
  ],
3531
3672
  "limit": {
3532
- "context": 256000,
3673
+ "context": 262144,
3533
3674
  "output": 256000
3534
3675
  },
3535
3676
  "name": "Mistral Small (latest)",
3536
3677
  "pricing": {
3678
+ "cacheRead": 0.015,
3537
3679
  "input": 0.15,
3538
3680
  "output": 0.6
3539
3681
  },
@@ -3778,6 +3920,7 @@
3778
3920
  },
3779
3921
  "name": "Kimi K3",
3780
3922
  "pricing": {
3923
+ "cacheCreation": 3,
3781
3924
  "cacheRead": 0.3,
3782
3925
  "input": 3,
3783
3926
  "output": 15
package/src/index.js CHANGED
@@ -164,6 +164,11 @@ async function dispatchPlatformCommand(platform, args = [], dependencies = {}) {
164
164
  const channel = platform.key;
165
165
  const options = dependencies.registry ? { registry: dependencies.registry } : {};
166
166
 
167
+ if (args.length > 1) {
168
+ reportInvalidCommand('参数', args[1], `ctx ${platform.command || platform.key} ${action}`);
169
+ return;
170
+ }
171
+
167
172
  switch (action) {
168
173
  case 'start':
169
174
  await proxyStart(channel, options);
@@ -178,11 +183,23 @@ async function dispatchPlatformCommand(platform, args = [], dependencies = {}) {
178
183
  await proxyStatus(channel, options);
179
184
  return;
180
185
  default:
181
- console.log(chalk.red(`\n[ERROR] 未知操作: ${action}\n`));
182
- console.log(chalk.gray('支持的操作: start, stop, restart, status\n'));
186
+ reportInvalidCommand('操作', action, '支持的操作: start, stop, restart, status');
183
187
  }
184
188
  }
185
189
 
190
+ function reportInvalidCommand(kind, value, usage = '') {
191
+ console.error(chalk.red(`\n[ERROR] 未知${kind}: ${value}\n`));
192
+ if (usage) console.error(chalk.gray(`${usage}\n`));
193
+ process.exitCode = 1;
194
+ }
195
+
196
+ function validateCommandOptions(command, args, allowedOptions) {
197
+ const invalidOption = args.find(argument => !allowedOptions.has(argument));
198
+ if (!invalidOption) return true;
199
+ reportInvalidCommand('参数', invalidOption, `用法: ${command}`);
200
+ return false;
201
+ }
202
+
186
203
  let processShutdownPromise = null;
187
204
 
188
205
  async function stopOwnedOmpGatewayBeforeExit() {
@@ -193,7 +210,7 @@ async function stopOwnedOmpGatewayBeforeExit() {
193
210
  } = require('./platforms/drivers/omp/proxy-implementation');
194
211
  if (getOmpProxyStatus().running) {
195
212
  const preserveManagedMode = process.argv.includes('--daemon');
196
- await stopOmpProxyServer({ forceAfterMs: 1500, preserveManagedMode });
213
+ await stopOmpProxyServer({ forceAfterMs: 120000, preserveManagedMode });
197
214
  }
198
215
  } catch (error) {
199
216
  console.error(chalk.yellow(`[WARN] OMP 动态网关退出清理失败: ${error.message}`));
@@ -203,12 +220,21 @@ async function stopOwnedOmpGatewayBeforeExit() {
203
220
  function shutdownProcess(code = 0, error = null) {
204
221
  if (processShutdownPromise) return processShutdownPromise;
205
222
  processShutdownPromise = (async () => {
223
+ try {
224
+ require('./server/services/memory-reclaimer').stopMemoryReclaimer();
225
+ } catch (memoryError) {
226
+ console.error(chalk.yellow(`[WARN] 内存回收器退出清理失败: ${memoryError.message}`));
227
+ }
206
228
  try {
207
229
  require('./server/services/native-log-observer').shutdownNativeCliLogObserver();
208
230
  } catch (observerError) {
209
231
  console.error(chalk.yellow(`[WARN] CLI 原生会话日志退出清理失败: ${observerError.message}`));
210
232
  }
211
- await stopOwnedOmpGatewayBeforeExit();
233
+ await Promise.all([
234
+ stopOwnedOmpGatewayBeforeExit(),
235
+ require('./server/services/server-shutdown').drainProxyServers({ forceAfterMs: 120000 })
236
+ .catch(drainError => console.error(`[WARN] Proxy drain failed: ${drainError.message}`))
237
+ ]);
212
238
  eventBus.emitSync('cli:shutdown', {});
213
239
  PluginManager.shutdownPlugins();
214
240
  try {
@@ -282,6 +308,11 @@ async function main({ menuDependencies } = {}) {
282
308
  if (args[0] === 'daemon') {
283
309
  const subCommand = args[1] || 'status';
284
310
 
311
+ if (['start', 'stop', 'restart', 'status'].includes(subCommand) && args.length > 2) {
312
+ reportInvalidCommand('参数', args[2], `ctx daemon ${subCommand}`);
313
+ return;
314
+ }
315
+
285
316
  if (subCommand === 'start') {
286
317
  await handleStart();
287
318
  return finishCli(0);
@@ -314,25 +345,37 @@ async function main({ menuDependencies } = {}) {
314
345
  return;
315
346
  }
316
347
 
317
- console.log(chalk.red(`\n[ERROR] 未知 daemon 子命令: ${subCommand}\n`));
318
- console.log(chalk.gray('支持的命令: start, stop, restart, status, logs\n'));
348
+ reportInvalidCommand('daemon 子命令', subCommand, '支持的命令: start, stop, restart, status, logs');
319
349
  return;
320
350
  }
321
351
 
322
352
  // reset 命令 - 恢复默认配置
323
353
  if (args[0] === 'reset') {
354
+ if (args.length > 1) {
355
+ reportInvalidCommand('参数', args[1], '用法: ctx reset');
356
+ return;
357
+ }
324
358
  await resetConfig();
325
359
  return;
326
360
  }
327
361
 
328
362
  // security 命令 - 安全设置
329
363
  if (args[0] === 'security') {
330
- const { showSecurityHelp, handleSecurityReset } = require('./commands/security');
331
364
  const subCommand = args[1] || 'help';
332
365
 
366
+ if (!['reset', 'disable', 'off', 'help'].includes(subCommand)) {
367
+ reportInvalidCommand('security 子命令', subCommand, '支持的命令: reset, disable, off, help');
368
+ return;
369
+ }
370
+ const { showSecurityHelp, handleSecurityReset } = require('./commands/security');
371
+
333
372
  if (subCommand === 'reset' || subCommand === 'disable' || subCommand === 'off') {
373
+ if (args.length > 2) {
374
+ reportInvalidCommand('参数', args[2], `ctx security ${subCommand}`);
375
+ return;
376
+ }
334
377
  await handleSecurityReset();
335
- } else {
378
+ } else if (subCommand === 'help') {
336
379
  showSecurityHelp();
337
380
  }
338
381
  return;
@@ -340,31 +383,52 @@ async function main({ menuDependencies } = {}) {
340
383
 
341
384
  // start 命令 - 启动服务(后台)
342
385
  if (args[0] === 'start') {
386
+ if (!validateCommandOptions('ctx start [--host|--hosts] [--https]', args.slice(1), new Set(['--host', '--hosts', '--https']))) return;
343
387
  await handleStart();
344
388
  return finishCli(0);
345
389
  }
346
390
 
347
391
  // stop 命令 - 停止服务
348
392
  if (args[0] === 'stop') {
393
+ if (args.length > 1) {
394
+ reportInvalidCommand('参数', args[1], '用法: ctx stop');
395
+ return;
396
+ }
349
397
  await handleStop();
350
398
  return finishCli(0);
351
399
  }
352
400
 
353
401
  // restart 命令 - 重启服务
354
402
  if (args[0] === 'restart') {
403
+ if (!validateCommandOptions('ctx restart [--host|--hosts] [--https]', args.slice(1), new Set(['--host', '--hosts', '--https']))) return;
355
404
  await handleRestart();
356
405
  return finishCli(0);
357
406
  }
358
407
 
359
408
  // status 命令 - 查看服务状态
360
409
  if (args[0] === 'status') {
410
+ if (args.length > 1) {
411
+ reportInvalidCommand('参数', args[1], '用法: ctx status');
412
+ return;
413
+ }
361
414
  await handleStatus();
362
415
  return finishCli(0);
363
416
  }
364
417
 
365
418
  // ui 命令 - Web UI 管理
366
419
  if (args[0] === 'ui') {
367
- const subCommand = args[1];
420
+ const commandArgs = args.slice(1);
421
+ const subCommand = commandArgs[0] && !commandArgs[0].startsWith('-') ? commandArgs[0] : null;
422
+ const optionArgs = subCommand ? commandArgs.slice(1) : commandArgs;
423
+ const allowedOptions = new Set(['--https', '--host', '--hosts', '--daemon']);
424
+
425
+ if (subCommand && !['start', 'stop', 'restart'].includes(subCommand)) {
426
+ reportInvalidCommand('ui 子命令', subCommand, '支持的命令: start, stop, restart');
427
+ return;
428
+ }
429
+ if (!validateCommandOptions(`ctx ui${subCommand ? ` ${subCommand}` : ''}`, optionArgs,
430
+ subCommand === 'stop' ? new Set() : allowedOptions)) return;
431
+
368
432
  if (subCommand === 'start') {
369
433
  await handleStart(); // UI start 实际上就是启动整个服务
370
434
  return finishCli(0);
@@ -440,12 +504,17 @@ async function main({ menuDependencies } = {}) {
440
504
 
441
505
  // doctor 命令 - 系统诊断
442
506
  if (args[0] === 'doctor') {
507
+ if (args.length > 1) {
508
+ reportInvalidCommand('参数', args[1], '用法: ctx doctor');
509
+ return;
510
+ }
443
511
  await handleDoctor();
444
512
  return;
445
513
  }
446
514
 
447
515
  // update 命令 - 检查并更新到最新版本
448
516
  if (args[0] === 'update') {
517
+ if (!validateCommandOptions('ctx update [--check]', args.slice(1), new Set(['--check']))) return;
449
518
  const checkOnly = args.includes('--check');
450
519
  await handleUpdate({ checkOnly });
451
520
  return;
@@ -453,6 +522,10 @@ async function main({ menuDependencies } = {}) {
453
522
 
454
523
  // port 命令 - 配置端口
455
524
  if (args[0] === 'port') {
525
+ if (args.length > 1) {
526
+ reportInvalidCommand('参数', args[1], '用法: ctx port');
527
+ return;
528
+ }
456
529
  const { handlePortConfig } = require('./commands/port-config');
457
530
  await handlePortConfig();
458
531
  return;
@@ -460,9 +533,18 @@ async function main({ menuDependencies } = {}) {
460
533
 
461
534
  // 代理命令
462
535
  if (args[0] === 'proxy') {
463
- const { handleProxyStart, handleProxyStop, handleProxyStatus } = require('./commands/proxy');
464
536
  const subCommand = args[1] || 'start';
465
537
 
538
+ if (!['start', 'stop', 'status'].includes(subCommand)) {
539
+ reportInvalidCommand('proxy 子命令', subCommand, '支持的命令: start, stop, status');
540
+ return;
541
+ }
542
+ if (args.length > 2) {
543
+ reportInvalidCommand('参数', args[2], `ctx proxy ${subCommand}`);
544
+ return;
545
+ }
546
+ const { handleProxyStart, handleProxyStop, handleProxyStatus } = require('./commands/proxy');
547
+
466
548
  switch (subCommand) {
467
549
  case 'start':
468
550
  await handleProxyStart();
@@ -476,10 +558,6 @@ async function main({ menuDependencies } = {}) {
476
558
  handleProxyStatus();
477
559
  return;
478
560
 
479
- default:
480
- // 默认执行 start
481
- await handleProxyStart();
482
- return;
483
561
  }
484
562
  }
485
563
 
@@ -508,6 +586,12 @@ async function main({ menuDependencies } = {}) {
508
586
  return;
509
587
  }
510
588
 
589
+ if (args.length > 0) {
590
+ reportInvalidCommand('命令', args[0], '运行 ctx --help 查看可用命令');
591
+ PluginManager.shutdownPlugins();
592
+ return;
593
+ }
594
+
511
595
  while (true) {
512
596
  // 显示主菜单
513
597
  const { showMainMenu } = require('./ui/menu');
@@ -7,7 +7,7 @@ const { createDecodedStream } = require('../../../server/services/response-decod
7
7
  const { parseNonStreamingUsage } = require('../../../shared/response-usage-parser');
8
8
  const { convertClaudeToOpenCodePayload } = require('../opencode/gateway-converter');
9
9
 
10
- const REQUEST_TIMEOUT_MS = 120000;
10
+ const { PROXY_IDLE_TIMEOUT_MS: REQUEST_TIMEOUT_MS } = require('../../../server/services/server-shutdown');
11
11
 
12
12
  function getRequestPathname(urlPath = '') {
13
13
  try {