openclawsetup 2.8.3 → 2.8.5

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 +100 -10
  2. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -856,6 +856,13 @@ function getDashboardToken(config) {
856
856
  return extractAuthToken(config?.raw || '') || '';
857
857
  }
858
858
 
859
+ function maskToken(token = '') {
860
+ if (!token || token === '<未配置>' || token === '<你的token>') return token;
861
+ const text = String(token);
862
+ if (text.length <= 8) return '***';
863
+ return `${text.slice(0, 4)}...${text.slice(-4)}`;
864
+ }
865
+
859
866
  function parseStatusOutput(statusOutput) {
860
867
  const text = (statusOutput || '').toLowerCase();
861
868
  const stoppedRegex = [
@@ -923,7 +930,7 @@ function probeGatewayStatus(cliName, timeout = STATUS_CMD_TIMEOUT_MS) {
923
930
 
924
931
  function getPortCheckOutput(port, timeout = PORT_CHECK_TIMEOUT_MS) {
925
932
  const cmd = platform() === 'win32'
926
- ? `netstat -ano | findstr :${port}`
933
+ ? `netstat -ano -p tcp | findstr LISTENING | findstr :${port}`
927
934
  : `lsof -nP -iTCP:${port} -sTCP:LISTEN 2>/dev/null || netstat -tlnp 2>/dev/null | grep :${port}`;
928
935
  return safeExec(cmd, { timeout });
929
936
  }
@@ -946,12 +953,33 @@ function isLikelyOpenClawProcess(output = '') {
946
953
  );
947
954
  }
948
955
 
956
+ function hasListeningState(output = '') {
957
+ if (!output) return false;
958
+ if (platform() !== 'win32') return Boolean(output.trim());
959
+ return output.toLowerCase().includes('listening');
960
+ }
961
+
949
962
  function gatewayHealthRequest(port, path = '/health') {
950
963
  const endpoint = `http://127.0.0.1:${port}${path}`;
951
964
  const curlCmd = `curl -sS --max-time 5 --connect-timeout 2 ${endpoint}`;
952
965
  return safeExec(curlCmd, { timeout: 7000 });
953
966
  }
954
967
 
968
+ function gatewayHttpStatusProbe(port, path = '/v1/responses') {
969
+ const endpoint = `http://127.0.0.1:${port}${path}`;
970
+ const cmd = `curl -sS -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 2 ${endpoint}`;
971
+ const result = safeExec(cmd, { timeout: 7000 });
972
+ if (!result.ok || !result.output) {
973
+ return { ok: false, status: 0 };
974
+ }
975
+ const match = String(result.output).match(/\d{3}/);
976
+ const status = match ? Number(match[0]) : 0;
977
+ if (!Number.isInteger(status) || status <= 0) {
978
+ return { ok: false, status: 0 };
979
+ }
980
+ return { ok: true, status };
981
+ }
982
+
955
983
  function parseHealthOutput(raw = '') {
956
984
  if (!raw) return { healthy: false, detail: '空响应' };
957
985
 
@@ -1149,6 +1177,39 @@ function selectHealthPort(config, strongMode = false) {
1149
1177
  return strongMode ? STRONG_PORT_CANDIDATES[0] : DEFAULT_GATEWAY_PORT;
1150
1178
  }
1151
1179
 
1180
+ function parsePortFromText(raw = '') {
1181
+ const matches = String(raw).match(/\b(\d{2,5})\b/g) || [];
1182
+ for (const item of matches) {
1183
+ const port = Number(item);
1184
+ if (Number.isInteger(port) && port > 0 && port <= 65535) {
1185
+ return port;
1186
+ }
1187
+ }
1188
+ return null;
1189
+ }
1190
+
1191
+ function getRuntimeGatewayPort(cliName, timeout = STATUS_CMD_TIMEOUT_MS) {
1192
+ if (!cliName) return null;
1193
+ const result = safeExec(`${cliName} config get gateway.port`, { timeout });
1194
+ if (!result.ok || !result.output) return null;
1195
+ return parsePortFromText(result.output);
1196
+ }
1197
+
1198
+ function resolveHealthPort(cliName, config, strongMode = false) {
1199
+ const runtimePort = getRuntimeGatewayPort(cliName);
1200
+ if (runtimePort) return { port: runtimePort, source: 'runtime' };
1201
+
1202
+ const candidate = Number(config?.port || DEFAULT_GATEWAY_PORT);
1203
+ if (Number.isInteger(candidate) && candidate > 0) {
1204
+ return { port: candidate, source: 'config' };
1205
+ }
1206
+
1207
+ return {
1208
+ port: strongMode ? STRONG_PORT_CANDIDATES[0] : DEFAULT_GATEWAY_PORT,
1209
+ source: 'default',
1210
+ };
1211
+ }
1212
+
1152
1213
  async function tryFixPortConflict(cliName, currentPort) {
1153
1214
  const availablePort = await findAvailablePort(currentPort);
1154
1215
  if (!availablePort || availablePort === currentPort) {
@@ -1240,6 +1301,7 @@ async function runOfficialDoctor(cliName, autoFix = false, strongMode = false) {
1240
1301
 
1241
1302
  async function verifyGatewayApi(port, cliName, autoFix = false, strongMode = false) {
1242
1303
  const paths = ['/health', '/api/health', '/status'];
1304
+ const probePaths = ['/v1/responses', '/v1/models', '/'];
1243
1305
 
1244
1306
  for (const path of paths) {
1245
1307
  const healthResult = gatewayHealthRequest(port, path);
@@ -1250,6 +1312,14 @@ async function verifyGatewayApi(port, cliName, autoFix = false, strongMode = fal
1250
1312
  }
1251
1313
  }
1252
1314
 
1315
+ for (const path of probePaths) {
1316
+ const probe = gatewayHttpStatusProbe(port, path);
1317
+ if (!probe.ok) continue;
1318
+ if (probe.status >= 200 && probe.status < 500) {
1319
+ return { ok: true, issue: null };
1320
+ }
1321
+ }
1322
+
1253
1323
  const issue = summarizeIssue(
1254
1324
  'error',
1255
1325
  'API 无响应',
@@ -1278,6 +1348,14 @@ async function verifyGatewayApi(port, cliName, autoFix = false, strongMode = fal
1278
1348
  }
1279
1349
  }
1280
1350
 
1351
+ for (const path of probePaths) {
1352
+ const probe = gatewayHttpStatusProbe(port, path);
1353
+ if (!probe.ok) continue;
1354
+ if (probe.status >= 200 && probe.status < 500) {
1355
+ return { ok: true, issue: null, fixed: true };
1356
+ }
1357
+ }
1358
+
1281
1359
  return { ok: false, issue, fixed: false };
1282
1360
  }
1283
1361
 
@@ -1909,8 +1987,9 @@ function showCompletionInfo(cliName) {
1909
1987
  function showDashboardAccessInfo() {
1910
1988
  const config = getConfigInfo();
1911
1989
  const port = config.port || 18789;
1912
- const token = getDashboardToken(config) || '<你的token>';
1913
- const dashboardUrl = `http://127.0.0.1:${port}/?token=${token}`;
1990
+ const tokenRaw = getDashboardToken(config) || '<你的token>';
1991
+ const tokenMasked = maskToken(tokenRaw);
1992
+ const dashboardUrl = `http://127.0.0.1:${port}/?token=${tokenMasked}`;
1914
1993
 
1915
1994
  if (detectVps()) {
1916
1995
  const serverIp = getServerIp() || '<服务器IP>';
@@ -2089,15 +2168,17 @@ async function runHealthCheck(cliName, autoFix = false, strongMode = false) {
2089
2168
  console.log(colors.cyan('检查 Gateway 进程...'));
2090
2169
  const statusProbe = probeGatewayStatus(activeCli, STATUS_CMD_TIMEOUT_MS);
2091
2170
  const statusState = statusProbe.state;
2092
- const portForStatus = selectHealthPort(config, strongMode);
2171
+ const statusPortResolved = resolveHealthPort(activeCli, config, strongMode);
2172
+ const portForStatus = statusPortResolved.port;
2093
2173
  const statusPortResult = getPortCheckOutput(portForStatus, PORT_CHECK_TIMEOUT_MS);
2094
2174
  const statusPortListening = Boolean(statusPortResult.ok && statusPortResult.output);
2095
2175
 
2096
- if (statusState === 'running' || (statusState !== 'running' && statusPortListening)) {
2176
+ const canUsePortFallback = statusState === 'unknown' || statusProbe.timedOut;
2177
+ if (statusState === 'running' || (canUsePortFallback && statusPortListening)) {
2097
2178
  log.success('Gateway 进程正在运行');
2098
2179
  if (statusProbe.timedOut) {
2099
2180
  log.hint('状态命令超时,已按端口监听判定为运行中');
2100
- } else if (statusState !== 'running' && statusPortListening) {
2181
+ } else if (canUsePortFallback && statusPortListening) {
2101
2182
  log.hint('状态命令返回非运行,但端口可访问(兼容判定)');
2102
2183
  }
2103
2184
  } else {
@@ -2127,13 +2208,18 @@ async function runHealthCheck(cliName, autoFix = false, strongMode = false) {
2127
2208
 
2128
2209
  // 4. 检查端口监听
2129
2210
  config = getConfigInfo();
2130
- let port = selectHealthPort(config, strongMode);
2211
+ let port = resolveHealthPort(activeCli, config, strongMode).port;
2131
2212
  console.log(colors.cyan(`检查端口监听 (${port})...`));
2132
2213
  let portHealthy = false;
2133
2214
 
2134
2215
  const portResult = getPortCheckOutput(port);
2135
2216
  if (portResult.ok && portResult.output) {
2136
- if (isLikelyOpenClawProcess(portResult.output)) {
2217
+ const listening = hasListeningState(portResult.output);
2218
+ const winLikelyGateway = platform() === 'win32'
2219
+ && listening
2220
+ && (statusState === 'running' || statusProbe.timedOut || statusState === 'unknown');
2221
+
2222
+ if (winLikelyGateway || isLikelyOpenClawProcess(portResult.output)) {
2137
2223
  log.success(`端口 ${port} 正在监听`);
2138
2224
  portHealthy = true;
2139
2225
  } else {
@@ -2178,7 +2264,7 @@ async function runHealthCheck(cliName, autoFix = false, strongMode = false) {
2178
2264
  if (recovered.ok) {
2179
2265
  await sleep(2500);
2180
2266
  const recheck = getPortCheckOutput(port);
2181
- if (recheck.ok && recheck.output) {
2267
+ if (recheck.ok && recheck.output && hasListeningState(recheck.output)) {
2182
2268
  log.success(`端口 ${port} 已恢复监听`);
2183
2269
  fixed.push(`端口监听已恢复(${port})`);
2184
2270
  portHealthy = true;
@@ -2408,7 +2494,8 @@ function testModelChat(cliName, timeoutMs = MODEL_CHAT_TIMEOUT_MS) {
2408
2494
 
2409
2495
  async function showStatusInfo(cliName) {
2410
2496
  const config = getConfigInfo();
2411
- const port = config.port || 18789;
2497
+ const portResolved = resolveHealthPort(cliName, config, false);
2498
+ const port = portResolved.port;
2412
2499
  const token = getDashboardToken(config) || '<未配置>';
2413
2500
  const dashboardUrl = `http://127.0.0.1:${port}/?token=${token}`;
2414
2501
 
@@ -2449,6 +2536,9 @@ async function showStatusInfo(cliName) {
2449
2536
  if (statusTimedOut) {
2450
2537
  console.log(colors.yellow(' ⚠ 状态命令超时,已自动切换端口探测模式'));
2451
2538
  }
2539
+ if (portResolved.source === 'runtime' && Number(config?.port) !== Number(port)) {
2540
+ console.log(colors.gray(` … 运行时端口为 ${port}(与本地配置文件不一致)`));
2541
+ }
2452
2542
 
2453
2543
  if (statusState === 'running') {
2454
2544
  console.log(colors.green(' ✓ Gateway 服务正在运行'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclawsetup",
3
- "version": "2.8.3",
3
+ "version": "2.8.5",
4
4
  "description": "OpenClaw 安装向导 - 智能安装、诊断、自动修复",
5
5
  "type": "module",
6
6
  "bin": {