coding-tool-x 3.5.13 → 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-DM3yF1Oo.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.13",
3
+ "version": "3.6.0",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,3 +1,4 @@
1
+ const fs = require('fs');
1
2
  const pm2 = require('pm2');
2
3
  const path = require('path');
3
4
  const chalk = require('chalk');
@@ -12,6 +13,16 @@ const {
12
13
  } = require('../utils/port-helper');
13
14
 
14
15
  const PM2_APP_NAME = 'cc-tool';
16
+ const STARTUP_LOG_FILE = 'cc-tool-out.log';
17
+ const CURRENT_PM2_FORK_PATH = resolveCurrentPm2ForkPath();
18
+
19
+ function resolveCurrentPm2ForkPath() {
20
+ try {
21
+ return require.resolve('pm2/lib/ProcessContainerFork');
22
+ } catch {
23
+ return '';
24
+ }
25
+ }
15
26
 
16
27
  /**
17
28
  * 连接到 PM2
@@ -74,6 +85,18 @@ function deletePM2Process(name) {
74
85
  });
75
86
  }
76
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
+
77
100
  /**
78
101
  * 获取 Coding-Tool 进程
79
102
  */
@@ -86,6 +109,113 @@ function sleep(ms) {
86
109
  return new Promise(resolve => setTimeout(resolve, ms));
87
110
  }
88
111
 
112
+ function normalizePathForComparison(filePath) {
113
+ return path.normalize(String(filePath || ''));
114
+ }
115
+
116
+ function getFileSize(filePath) {
117
+ try {
118
+ return fs.statSync(filePath).size;
119
+ } catch {
120
+ return 0;
121
+ }
122
+ }
123
+
124
+ function readLogChunkSince(filePath, startOffset = 0, maxBytes = 32768) {
125
+ try {
126
+ const stat = fs.statSync(filePath);
127
+ if (stat.size <= startOffset) {
128
+ return '';
129
+ }
130
+
131
+ const safeOffset = Math.max(0, startOffset);
132
+ const readStart = Math.max(safeOffset, stat.size - maxBytes);
133
+ const length = stat.size - readStart;
134
+ if (length <= 0) {
135
+ return '';
136
+ }
137
+
138
+ const fd = fs.openSync(filePath, 'r');
139
+ try {
140
+ const buffer = Buffer.alloc(length);
141
+ fs.readSync(fd, buffer, 0, length, readStart);
142
+ return buffer.toString('utf8');
143
+ } finally {
144
+ fs.closeSync(fd);
145
+ }
146
+ } catch {
147
+ return '';
148
+ }
149
+ }
150
+
151
+ function extractMissingPm2ForkScriptPath(logText = '') {
152
+ const match = String(logText).match(/Cannot find module '([^']*ProcessContainerFork\.js)'/);
153
+ return match ? match[1] : '';
154
+ }
155
+
156
+ function detectStalePm2RuntimeIssue(logText = '', currentForkPath = CURRENT_PM2_FORK_PATH) {
157
+ const missingPath = extractMissingPm2ForkScriptPath(logText);
158
+ if (!missingPath || !currentForkPath) {
159
+ return null;
160
+ }
161
+
162
+ if (normalizePathForComparison(missingPath) === normalizePathForComparison(currentForkPath)) {
163
+ return null;
164
+ }
165
+
166
+ return {
167
+ missingPath,
168
+ currentPath: currentForkPath
169
+ };
170
+ }
171
+
172
+ function updatePM2Daemon() {
173
+ return new Promise((resolve, reject) => {
174
+ pm2.update((err, result) => {
175
+ if (err) {
176
+ reject(err);
177
+ } else {
178
+ resolve(result);
179
+ }
180
+ });
181
+ });
182
+ }
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
+
89
219
  function isPortOwnedByPid(port, pid) {
90
220
  if (!pid || pid <= 0) {
91
221
  return false;
@@ -257,104 +387,193 @@ async function waitForServiceReady(port, timeoutMs = 15000, intervalMs = 500) {
257
387
  };
258
388
  }
259
389
 
260
- /**
261
- * 启动服务(后台)
262
- */
263
- async function handleStart() {
264
- try {
265
- await connectPM2();
390
+ function buildStartOptions(port, enableHost, enableHttps) {
391
+ const pmArgs = ['ui', '--daemon'];
392
+ if (enableHost) {
393
+ pmArgs.push('--host');
394
+ }
395
+ if (enableHttps) {
396
+ pmArgs.push('--https');
397
+ }
266
398
 
267
- // 检查是否已经在运行
268
- const existing = await getCCToolProcess();
269
- if (existing && existing.pm2_env.status === 'online') {
270
- console.log(chalk.yellow('\n[WARN] 服务已在运行中\n'));
271
- console.log(chalk.gray(`进程 ID: ${existing.pid}`));
272
- console.log(chalk.gray(`运行时长: ${formatUptime(existing.pm2_env.pm_uptime)}`));
273
- console.log(chalk.gray('\n使用 ') + chalk.cyan('ctx status') + chalk.gray(' 查看详细状态'));
274
- console.log(chalk.gray('使用 ') + chalk.cyan('ctx restart') + chalk.gray(' 重启服务\n'));
275
- disconnectPM2();
276
- return;
277
- }
399
+ return {
400
+ name: PM2_APP_NAME,
401
+ script: path.join(__dirname, '../index.js'),
402
+ args: pmArgs,
403
+ interpreter: 'node',
404
+ autorestart: true,
405
+ max_memory_restart: '500M',
406
+ env: {
407
+ NODE_ENV: 'production',
408
+ CC_TOOL_PORT: port
409
+ },
410
+ output: path.join(PATHS.logs, STARTUP_LOG_FILE),
411
+ error: path.join(PATHS.logs, STARTUP_LOG_FILE),
412
+ merge_logs: true,
413
+ log_date_format: 'YYYY-MM-DD HH:mm:ss'
414
+ };
415
+ }
278
416
 
279
- const config = loadConfig();
280
- const port = config.ports?.webUI || 19999;
417
+ async function attemptStartService(startOptions, port, options = {}) {
418
+ const allowPm2Refresh = options.allowPm2Refresh !== false;
419
+ const logPath = path.join(PATHS.logs, STARTUP_LOG_FILE);
420
+ const logOffset = getFileSize(logPath);
281
421
 
282
- // 检查是否启用 LAN 访问 (--host 标志)
283
- const enableHost = process.argv.includes('--host');
284
- const enableHttps = process.argv.includes('--https');
285
- const pmArgs = ['ui', '--daemon'];
286
- if (enableHost) {
287
- pmArgs.push('--host');
288
- }
289
- if (enableHttps) {
290
- pmArgs.push('--https');
291
- }
292
- require('fs').mkdirSync(PATHS.logs, { recursive: true });
293
-
294
- // 启动 PM2 进程
295
- pm2.start({
296
- name: PM2_APP_NAME,
297
- script: path.join(__dirname, '../index.js'),
298
- args: pmArgs,
299
- interpreter: 'node',
300
- autorestart: true,
301
- max_memory_restart: '500M',
302
- env: {
303
- NODE_ENV: 'production',
304
- CC_TOOL_PORT: port
305
- },
306
- output: path.join(PATHS.logs, 'cc-tool-out.log'),
307
- error: path.join(PATHS.logs, 'cc-tool-out.log'),
308
- merge_logs: true,
309
- log_date_format: 'YYYY-MM-DD HH:mm:ss'
310
- }, async (err) => {
422
+ return new Promise((resolve, reject) => {
423
+ pm2.start(startOptions, async (err) => {
311
424
  if (err) {
312
- console.error(chalk.red('\n[ERROR] 启动服务失败:'), err.message);
313
- disconnectPM2();
314
- process.exit(1);
425
+ reject(err);
426
+ return;
315
427
  }
316
428
 
317
- let readyState = null;
318
429
  try {
319
- readyState = await waitForServiceReady(port);
320
- if (!readyState.ready) {
321
- const statusText = readyState.process?.pm2_env?.status || 'unknown';
322
- console.error(chalk.red('\n[ERROR] Coding-Tool 服务启动失败,进程未就绪\n'));
323
- console.error(chalk.gray(`PM2 状态: ${statusText}`));
324
- printPortToolIssue(readyState.degradedPortCheckIssue);
325
- console.error(chalk.yellow('[TIP] 请使用 ctx logs ui 查看详细日志\n'));
326
-
327
- pm2.delete(PM2_APP_NAME, () => {
328
- pm2.dump(() => {
329
- disconnectPM2();
330
- process.exit(1);
430
+ const readyState = await waitForServiceReady(port);
431
+ if (readyState.ready) {
432
+ resolve({ ok: true, readyState });
433
+ return;
434
+ }
435
+
436
+ const recentLogText = readLogChunkSince(logPath, logOffset);
437
+ const staleRuntimeIssue = allowPm2Refresh ? detectStalePm2RuntimeIssue(recentLogText) : null;
438
+
439
+ if (staleRuntimeIssue) {
440
+ console.log(chalk.yellow('\n[WARN] 检测到 PM2 守护进程仍引用旧的 pm2 运行时,正在自动刷新...'));
441
+ console.log(chalk.gray(`旧路径: ${staleRuntimeIssue.missingPath}`));
442
+ console.log(chalk.gray(`当前路径: ${staleRuntimeIssue.currentPath}`));
443
+
444
+ await updatePM2Daemon();
445
+
446
+ const recoveredState = await waitForServiceReady(port, 5000, 500);
447
+ if (recoveredState.ready) {
448
+ resolve({
449
+ ok: true,
450
+ readyState: recoveredState,
451
+ recoveredFromStalePm2: staleRuntimeIssue
331
452
  });
453
+ return;
454
+ }
455
+
456
+ try {
457
+ await deletePM2Process(PM2_APP_NAME);
458
+ } catch {
459
+ // ignore stale process cleanup errors; the next retry will surface real failures
460
+ }
461
+
462
+ resolve({
463
+ retry: true,
464
+ recoveredFromStalePm2: staleRuntimeIssue
332
465
  });
333
466
  return;
334
467
  }
468
+
469
+ resolve({
470
+ ok: false,
471
+ readyState
472
+ });
335
473
  } catch (checkError) {
336
- console.error(chalk.red('\n[ERROR] 启动后健康检查失败:'), checkError.message);
474
+ reject(checkError);
475
+ }
476
+ });
477
+ });
478
+ }
479
+
480
+ function cleanupFailedStart() {
481
+ return new Promise((resolve) => {
482
+ pm2.delete(PM2_APP_NAME, async () => {
483
+ await finalizePM2Session({ persist: true });
484
+ resolve();
485
+ });
486
+ });
487
+ }
488
+
489
+ /**
490
+ * 启动服务(后台)
491
+ */
492
+ async function handleStart() {
493
+ try {
494
+ const config = loadConfig();
495
+ const port = config.ports?.webUI || 19999;
496
+ const enableHost = process.argv.includes('--host');
497
+ const enableHttps = process.argv.includes('--https');
498
+ fs.mkdirSync(PATHS.logs, { recursive: true });
499
+
500
+ let readyState = null;
501
+ let recoveredFromStalePm2 = null;
502
+
503
+ for (let attempt = 0; attempt < 2; attempt += 1) {
504
+ await connectPM2();
505
+
506
+ const existing = await getCCToolProcess();
507
+ if (existing && existing.pm2_env.status === 'online') {
508
+ if (recoveredFromStalePm2) {
509
+ readyState = {
510
+ ready: true,
511
+ process: existing,
512
+ degradedPortCheckIssue: getPortToolIssue()
513
+ };
514
+ break;
515
+ }
516
+
517
+ console.log(chalk.yellow('\n[WARN] 服务已在运行中\n'));
518
+ console.log(chalk.gray(`进程 ID: ${existing.pid}`));
519
+ console.log(chalk.gray(`运行时长: ${formatUptime(existing.pm2_env.pm_uptime)}`));
520
+ console.log(chalk.gray('\n使用 ') + chalk.cyan('ctx status') + chalk.gray(' 查看详细状态'));
521
+ console.log(chalk.gray('使用 ') + chalk.cyan('ctx restart') + chalk.gray(' 重启服务\n'));
337
522
  disconnectPM2();
338
- process.exit(1);
523
+ return;
339
524
  }
340
525
 
341
- console.log(chalk.green('\n[OK] Coding-Tool 服务已启动(后台运行)\n'));
342
- console.log(chalk.gray(`Web UI: ${enableHttps ? 'https' : 'http'}://localhost:${port}`));
343
- printPortToolIssue(readyState.degradedPortCheckIssue);
344
- if (enableHost) {
345
- console.log(chalk.yellow(`[WARN] LAN 访问已启用 (${enableHttps ? 'https' : 'http'}://<your-ip>:${port})`));
526
+ const startResult = await attemptStartService(
527
+ buildStartOptions(port, enableHost, enableHttps),
528
+ port,
529
+ { allowPm2Refresh: attempt === 0 }
530
+ );
531
+
532
+ if (startResult.retry) {
533
+ recoveredFromStalePm2 = startResult.recoveredFromStalePm2 || recoveredFromStalePm2;
534
+ disconnectPM2();
535
+ continue;
346
536
  }
347
- console.log(chalk.gray('\n可以安全关闭此终端窗口'));
348
- console.log(chalk.gray('\n常用命令:'));
349
- console.log(chalk.gray(' ') + chalk.cyan('ctx status') + chalk.gray(' - 查看服务状态'));
350
- console.log(chalk.gray(' ') + chalk.cyan('ctx logs') + chalk.gray(' - 查看实时日志'));
351
- console.log(chalk.gray(' ') + chalk.cyan('ctx stop') + chalk.gray(' - 停止服务\n'));
352
-
353
- // 保存进程列表
354
- pm2.dump((err) => {
537
+
538
+ if (!startResult.ok) {
539
+ readyState = startResult.readyState;
540
+ const statusText = readyState.process?.pm2_env?.status || 'unknown';
541
+ console.error(chalk.red('\n[ERROR] Coding-Tool 服务启动失败,进程未就绪\n'));
542
+ console.error(chalk.gray(`PM2 状态: ${statusText}`));
543
+ printPortToolIssue(readyState.degradedPortCheckIssue);
544
+ console.error(chalk.yellow('[TIP] 请使用 ctx logs ui 查看详细日志\n'));
545
+ await cleanupFailedStart();
355
546
  disconnectPM2();
356
- });
357
- });
547
+ process.exit(1);
548
+ }
549
+
550
+ readyState = startResult.readyState;
551
+ recoveredFromStalePm2 = startResult.recoveredFromStalePm2 || recoveredFromStalePm2;
552
+ break;
553
+ }
554
+
555
+ if (!readyState || !readyState.ready) {
556
+ console.error(chalk.red('\n[ERROR] Coding-Tool 服务启动失败,PM2 守护进程刷新后仍未恢复\n'));
557
+ disconnectPM2();
558
+ process.exit(1);
559
+ }
560
+
561
+ console.log(chalk.green('\n[OK] Coding-Tool 服务已启动(后台运行)\n'));
562
+ console.log(chalk.gray(`Web UI: ${enableHttps ? 'https' : 'http'}://localhost:${port}`));
563
+ if (recoveredFromStalePm2) {
564
+ console.log(chalk.green('[OK] 已自动修复旧 PM2 运行时路径残留'));
565
+ }
566
+ printPortToolIssue(readyState.degradedPortCheckIssue);
567
+ if (enableHost) {
568
+ console.log(chalk.yellow(`[WARN] LAN 访问已启用 (${enableHttps ? 'https' : 'http'}://<your-ip>:${port})`));
569
+ }
570
+ console.log(chalk.gray('\n可以安全关闭此终端窗口'));
571
+ console.log(chalk.gray('\n常用命令:'));
572
+ console.log(chalk.gray(' ') + chalk.cyan('ctx status') + chalk.gray(' - 查看服务状态'));
573
+ console.log(chalk.gray(' ') + chalk.cyan('ctx logs') + chalk.gray(' - 查看实时日志'));
574
+ console.log(chalk.gray(' ') + chalk.cyan('ctx stop') + chalk.gray(' - 停止服务\n'));
575
+
576
+ await finalizePM2Session({ persist: true });
358
577
  } catch (error) {
359
578
  console.error(chalk.red('启动失败:'), error.message);
360
579
  disconnectPM2();
@@ -374,9 +593,7 @@ async function handleStop() {
374
593
  const stopResult = await stopAllManagedInstances(existing, config);
375
594
  printStopResult(stopResult);
376
595
 
377
- pm2.dump(() => {
378
- disconnectPM2();
379
- });
596
+ await finalizePM2Session({ persist: true });
380
597
  } catch (error) {
381
598
  console.error(chalk.red('停止失败:'), error.message);
382
599
  disconnectPM2();
@@ -398,19 +615,9 @@ async function handleRestart() {
398
615
  return;
399
616
  }
400
617
 
401
- pm2.restart(PM2_APP_NAME, (err) => {
402
- if (err) {
403
- console.error(chalk.red('\n[ERROR] 重启服务失败:'), err.message);
404
- disconnectPM2();
405
- process.exit(1);
406
- }
407
-
408
- console.log(chalk.green('\n[OK] Coding-Tool 服务已重启\n'));
409
-
410
- pm2.dump((err) => {
411
- disconnectPM2();
412
- });
413
- });
618
+ await restartPM2Process(PM2_APP_NAME);
619
+ console.log(chalk.green('\n[OK] Coding-Tool 服务已重启\n'));
620
+ await finalizePM2Session({ persist: true });
414
621
  } catch (error) {
415
622
  console.error(chalk.red('重启失败:'), error.message);
416
623
  disconnectPM2();
@@ -446,7 +653,6 @@ async function handleStatus() {
446
653
  }
447
654
 
448
655
  // 代理服务状态(从运行时文件检测)
449
- const fs = require('fs');
450
656
  ensureStorageDirMigrated();
451
657
  const claudeActive = fs.existsSync(PATHS.activeChannel.claude);
452
658
  const codexActive = fs.existsSync(PATHS.activeChannel.codex);
@@ -516,6 +722,8 @@ module.exports = {
516
722
  handleRestart,
517
723
  handleStatus,
518
724
  _test: {
725
+ detectStalePm2RuntimeIssue,
726
+ extractMissingPm2ForkScriptPath,
519
727
  shouldTreatPortOwnershipAsReady,
520
728
  getManagedPorts,
521
729
  shouldStopPM2Process
@@ -1,6 +1,7 @@
1
1
  const PRESET_WEBSITE_SOURCES = {
2
2
  claude: [
3
3
  { presetId: 'official', baseUrl: 'https://api.anthropic.com', websiteUrl: 'https://www.anthropic.com' },
4
+ { presetId: 'openai', baseUrl: 'https://api.openai.com/v1', websiteUrl: 'https://platform.openai.com' },
4
5
  { presetId: 'deepseek', baseUrl: 'https://api.deepseek.com/anthropic', websiteUrl: 'https://platform.deepseek.com' },
5
6
  { presetId: 'zhipu', baseUrl: 'https://open.bigmodel.cn/api/anthropic', websiteUrl: 'https://open.bigmodel.cn' },
6
7
  { presetId: 'kimi', baseUrl: 'https://api.moonshot.cn/anthropic', websiteUrl: 'https://platform.moonshot.cn' },
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: