openclawsetup 2.8.1 → 2.8.3

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 (2) hide show
  1. package/bin/cli.mjs +109 -23
  2. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -83,6 +83,9 @@ const log = {
83
83
 
84
84
  const DEFAULT_GATEWAY_PORT = 18789;
85
85
  const STRONG_FIX_MAX_PASSES = 3;
86
+ const STATUS_CMD_TIMEOUT_MS = platform() === 'win32' ? 8000 : 5000;
87
+ const PORT_CHECK_TIMEOUT_MS = platform() === 'win32' ? 6000 : 4000;
88
+ const MODEL_CHAT_TIMEOUT_MS = platform() === 'win32' ? 25000 : 18000;
86
89
  const STRONG_PORT_CANDIDATES = [
87
90
  18789,
88
91
  18790,
@@ -855,26 +858,81 @@ function getDashboardToken(config) {
855
858
 
856
859
  function parseStatusOutput(statusOutput) {
857
860
  const text = (statusOutput || '').toLowerCase();
858
- const runningKeywords = ['running', 'active', '已运行', '运行中', '在线'];
859
- const stoppedKeywords = ['stopped', 'inactive', 'not running', '未运行', '停止'];
861
+ const stoppedRegex = [
862
+ /\bnot\s+running\b/,
863
+ /\bstopped\b/,
864
+ /\binactive\b/,
865
+ /\boffline\b/,
866
+ ];
867
+ const runningRegex = [
868
+ /\brunning\b/,
869
+ /\bactive\b/,
870
+ /\bonline\b/,
871
+ ];
872
+ const stoppedKeywords = ['未运行', '已停止', '停止', '离线'];
873
+ const runningKeywords = ['已运行', '运行中', '在线'];
860
874
 
861
- if (runningKeywords.some((word) => text.includes(word))) return 'running';
875
+ if (stoppedRegex.some((pattern) => pattern.test(text))) return 'stopped';
862
876
  if (stoppedKeywords.some((word) => text.includes(word))) return 'stopped';
877
+ if (runningRegex.some((pattern) => pattern.test(text))) return 'running';
878
+ if (runningKeywords.some((word) => text.includes(word))) return 'running';
863
879
  return 'unknown';
864
880
  }
865
881
 
866
- function getPortCheckOutput(port) {
882
+ function isCommandTimeout(result) {
883
+ if (!result || result.ok) return false;
884
+ const raw = `${result.error || ''} ${result.stderr || ''}`.toLowerCase();
885
+ return raw.includes('timed out') || raw.includes('etimedout') || raw.includes('sigterm') || raw.includes('killed');
886
+ }
887
+
888
+ function probeGatewayStatus(cliName, timeout = STATUS_CMD_TIMEOUT_MS) {
889
+ const attempts = [
890
+ `${cliName} status`,
891
+ `${cliName} gateway status`,
892
+ ];
893
+
894
+ let state = 'unknown';
895
+ let output = '';
896
+ let timedOut = false;
897
+ let error = '';
898
+
899
+ for (const cmd of attempts) {
900
+ const result = safeExec(cmd, { timeout });
901
+ if (isCommandTimeout(result)) {
902
+ timedOut = true;
903
+ continue;
904
+ }
905
+ if (!result.ok) {
906
+ if (!error) error = result.stderr || result.error || '';
907
+ continue;
908
+ }
909
+ if (!result.output) continue;
910
+
911
+ output = result.output;
912
+ const parsed = parseStatusOutput(result.output);
913
+ if (parsed === 'running') {
914
+ return { state: 'running', output, timedOut, error };
915
+ }
916
+ if (parsed === 'stopped') {
917
+ state = 'stopped';
918
+ }
919
+ }
920
+
921
+ return { state, output, timedOut, error };
922
+ }
923
+
924
+ function getPortCheckOutput(port, timeout = PORT_CHECK_TIMEOUT_MS) {
867
925
  const cmd = platform() === 'win32'
868
926
  ? `netstat -ano | findstr :${port}`
869
927
  : `lsof -nP -iTCP:${port} -sTCP:LISTEN 2>/dev/null || netstat -tlnp 2>/dev/null | grep :${port}`;
870
- return safeExec(cmd);
928
+ return safeExec(cmd, { timeout });
871
929
  }
872
930
 
873
- function getPortConflictDetail(port) {
931
+ function getPortConflictDetail(port, timeout = PORT_CHECK_TIMEOUT_MS) {
874
932
  const cmd = platform() === 'win32'
875
933
  ? `netstat -ano | findstr :${port} | findstr LISTENING`
876
934
  : `lsof -nP -iTCP:${port} -sTCP:LISTEN 2>/dev/null | head -8`;
877
- return safeExec(cmd);
935
+ return safeExec(cmd, { timeout });
878
936
  }
879
937
 
880
938
  function isLikelyOpenClawProcess(output = '') {
@@ -1372,12 +1430,18 @@ function getOnboardHelp(cliName) {
1372
1430
  return '';
1373
1431
  }
1374
1432
 
1433
+ function extractOnboardFlags(helpText = '') {
1434
+ const matches = helpText.toLowerCase().match(/--[a-z0-9][a-z0-9-]*/g) || [];
1435
+ return new Set(matches);
1436
+ }
1437
+
1375
1438
  function buildOnboardArgsFromHelp(helpText, options) {
1376
1439
  const help = helpText.toLowerCase();
1440
+ const flagSet = extractOnboardFlags(helpText);
1377
1441
  const args = [];
1378
1442
  const enabled = [];
1379
1443
 
1380
- const pickFlag = (flags) => flags.find((flag) => help.includes(flag));
1444
+ const pickFlag = (flags) => flags.find((flag) => flagSet.has(flag));
1381
1445
 
1382
1446
  const yesFlag = pickFlag(['--yes', '--assume-yes', '--accept', '--agree']);
1383
1447
  if (yesFlag) {
@@ -1390,10 +1454,10 @@ function buildOnboardArgsFromHelp(helpText, options) {
1390
1454
  args.push(installDaemonFlag);
1391
1455
  }
1392
1456
 
1393
- if (help.includes('--quickstart')) {
1457
+ if (flagSet.has('--quickstart')) {
1394
1458
  args.push('--quickstart');
1395
1459
  enabled.push('quickstart');
1396
- } else if (help.includes('--mode') && (help.includes('quickstart') || help.includes('quick start'))) {
1460
+ } else if (flagSet.has('--mode') && (help.includes('quickstart') || help.includes('quick start'))) {
1397
1461
  args.push('--mode', 'quickstart');
1398
1462
  enabled.push('quickstart');
1399
1463
  }
@@ -1414,10 +1478,10 @@ function buildOnboardArgsFromHelp(helpText, options) {
1414
1478
  }
1415
1479
  }
1416
1480
 
1417
- if (help.includes('--ui') && (help.includes('web') || help.includes('dashboard'))) {
1481
+ if (flagSet.has('--ui') && (help.includes('web') || help.includes('dashboard'))) {
1418
1482
  args.push('--ui', 'web');
1419
1483
  enabled.push('ui-web');
1420
- } else if (help.includes('--web')) {
1484
+ } else if (flagSet.has('--web')) {
1421
1485
  args.push('--web');
1422
1486
  enabled.push('ui-web');
1423
1487
  }
@@ -2023,16 +2087,26 @@ async function runHealthCheck(cliName, autoFix = false, strongMode = false) {
2023
2087
 
2024
2088
  // 3. 检查 Gateway 进程
2025
2089
  console.log(colors.cyan('检查 Gateway 进程...'));
2026
- const statusResult = safeExec(`${activeCli} status`);
2027
- const statusState = statusResult.ok ? parseStatusOutput(statusResult.output) : 'unknown';
2090
+ const statusProbe = probeGatewayStatus(activeCli, STATUS_CMD_TIMEOUT_MS);
2091
+ const statusState = statusProbe.state;
2092
+ const portForStatus = selectHealthPort(config, strongMode);
2093
+ const statusPortResult = getPortCheckOutput(portForStatus, PORT_CHECK_TIMEOUT_MS);
2094
+ const statusPortListening = Boolean(statusPortResult.ok && statusPortResult.output);
2028
2095
 
2029
- if (statusResult.ok && statusState === 'running') {
2096
+ if (statusState === 'running' || (statusState !== 'running' && statusPortListening)) {
2030
2097
  log.success('Gateway 进程正在运行');
2098
+ if (statusProbe.timedOut) {
2099
+ log.hint('状态命令超时,已按端口监听判定为运行中');
2100
+ } else if (statusState !== 'running' && statusPortListening) {
2101
+ log.hint('状态命令返回非运行,但端口可访问(兼容判定)');
2102
+ }
2031
2103
  } else {
2032
2104
  const issue = summarizeIssue(
2033
2105
  'error',
2034
2106
  'Gateway 未运行',
2035
- statusResult.ok ? 'Gateway 状态为停止/未知' : (statusResult.stderr || statusResult.error || '状态检查失败'),
2107
+ statusProbe.output
2108
+ ? 'Gateway 状态为停止/未知'
2109
+ : (statusProbe.error || '状态检查失败'),
2036
2110
  `运行 ${activeCli} gateway start 启动服务`,
2037
2111
  `${activeCli} gateway start`,
2038
2112
  );
@@ -2288,10 +2362,12 @@ async function runHealthCheck(cliName, autoFix = false, strongMode = false) {
2288
2362
 
2289
2363
  // ============ 交互式菜单 ============
2290
2364
 
2291
- function testModelChat(cliName) {
2365
+ function testModelChat(cliName, timeoutMs = MODEL_CHAT_TIMEOUT_MS) {
2292
2366
  return new Promise((resolve) => {
2293
- const cmd = `${cliName} agent --session-id openclawsetup-test --message "请用一句话回复你的模型名称" --json --timeout 60`;
2294
- exec(cmd, { timeout: 65000 }, (error, stdout, stderr) => {
2367
+ const agentTimeoutSec = Math.max(12, Math.floor(timeoutMs / 1000));
2368
+ const execTimeoutMs = Math.max(15000, timeoutMs + 5000);
2369
+ const cmd = `${cliName} agent --session-id openclawsetup-test --message "请用一句话回复你的模型名称" --json --timeout ${agentTimeoutSec}`;
2370
+ exec(cmd, { timeout: execTimeoutMs }, (error, stdout, stderr) => {
2295
2371
  if (error) {
2296
2372
  const errMsg = (stderr || stdout || error.message || '').trim();
2297
2373
  // 提取关键错误信息
@@ -2337,6 +2413,7 @@ async function showStatusInfo(cliName) {
2337
2413
  const dashboardUrl = `http://127.0.0.1:${port}/?token=${token}`;
2338
2414
 
2339
2415
  console.log(colors.bold(colors.cyan('\n📊 OpenClaw 状态信息\n')));
2416
+ console.log(colors.gray(' … 正在检查服务状态(超时自动跳过)...'));
2340
2417
 
2341
2418
  // 服务状态(兼容不同 CLI 版本,避免 Windows 下误判)
2342
2419
  const statusAttempts = [
@@ -2345,9 +2422,14 @@ async function showStatusInfo(cliName) {
2345
2422
  ];
2346
2423
  let statusState = 'unknown';
2347
2424
  let statusOutput = '';
2425
+ let statusTimedOut = false;
2348
2426
 
2349
2427
  for (const attempt of statusAttempts) {
2350
- const statusResult = safeExec(attempt.cmd);
2428
+ const statusResult = safeExec(attempt.cmd, { timeout: STATUS_CMD_TIMEOUT_MS });
2429
+ if (!statusResult.ok && /timed out|etimedout|SIGTERM|killed/i.test(`${statusResult.error || ''} ${statusResult.stderr || ''}`)) {
2430
+ statusTimedOut = true;
2431
+ continue;
2432
+ }
2351
2433
  if (!statusResult.ok || !statusResult.output) continue;
2352
2434
  statusOutput = statusResult.output;
2353
2435
  const parsed = parseStatusOutput(statusResult.output);
@@ -2361,9 +2443,13 @@ async function showStatusInfo(cliName) {
2361
2443
  }
2362
2444
 
2363
2445
  // 端口检查(作为状态命令兜底)
2364
- const portResult = getPortCheckOutput(port);
2446
+ const portResult = getPortCheckOutput(port, PORT_CHECK_TIMEOUT_MS);
2365
2447
  const portListening = Boolean(portResult.ok && portResult.output);
2366
2448
 
2449
+ if (statusTimedOut) {
2450
+ console.log(colors.yellow(' ⚠ 状态命令超时,已自动切换端口探测模式'));
2451
+ }
2452
+
2367
2453
  if (statusState === 'running') {
2368
2454
  console.log(colors.green(' ✓ Gateway 服务正在运行'));
2369
2455
  } else if (portListening) {
@@ -2413,8 +2499,8 @@ async function showStatusInfo(cliName) {
2413
2499
 
2414
2500
  // 模型对话测试
2415
2501
  if (primaryModel) {
2416
- console.log(colors.gray('正在测试模型对话...'));
2417
- const testResult = await testModelChat(cliName);
2502
+ console.log(colors.gray(`正在测试模型对话(最长 ${Math.floor(MODEL_CHAT_TIMEOUT_MS / 1000)} 秒)...`));
2503
+ const testResult = await testModelChat(cliName, MODEL_CHAT_TIMEOUT_MS);
2418
2504
  if (testResult.success) {
2419
2505
  console.log(colors.green(' ✓ 模型对话正常'));
2420
2506
  if (testResult.message) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclawsetup",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
4
4
  "description": "OpenClaw 安装向导 - 智能安装、诊断、自动修复",
5
5
  "type": "module",
6
6
  "bin": {