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.
- package/bin/cli.mjs +100 -10
- 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
|
|
1913
|
-
const
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 服务正在运行'));
|