yymaxapi 1.0.100 → 1.0.102
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/yymaxapi.js +174 -105
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -254,6 +254,34 @@ function getDefaultCodexModel() {
|
|
|
254
254
|
return CODEX_MODELS[0] || { id: 'gpt-5.4', name: 'GPT 5.4' };
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
function getExternalClaudeProviderKey() {
|
|
258
|
+
return String(API_CONFIG?.claude?.providerName || 'yunyi-claude').trim() || 'yunyi-claude';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function getExternalCodexProviderKey() {
|
|
262
|
+
const providerKey = String(API_CONFIG?.codex?.providerName || '').trim();
|
|
263
|
+
if (!providerKey) return 'yunyi-codex';
|
|
264
|
+
return providerKey === 'yunyi' ? 'yunyi-codex' : providerKey;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function getExternalModelKey(type, modelId) {
|
|
268
|
+
const providerKey = type === 'codex' ? getExternalCodexProviderKey() : getExternalClaudeProviderKey();
|
|
269
|
+
return `${providerKey}/${modelId}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function getProviderBrandPrefix() {
|
|
273
|
+
const providerKeys = [getExternalClaudeProviderKey(), getExternalCodexProviderKey()];
|
|
274
|
+
if (providerKeys.some(key => key.startsWith('maxapi'))) return 'MAXAPI';
|
|
275
|
+
if (providerKeys.some(key => key.startsWith('yunyi'))) return '云翼';
|
|
276
|
+
return 'OpenClaw';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function isUnifiedSingleModelMode() {
|
|
280
|
+
return CLAUDE_MODELS.length === 1
|
|
281
|
+
&& CODEX_MODELS.length === 1
|
|
282
|
+
&& CLAUDE_MODELS[0].id === CODEX_MODELS[0].id;
|
|
283
|
+
}
|
|
284
|
+
|
|
257
285
|
function buildProviderModelMap(models) {
|
|
258
286
|
const mapped = {};
|
|
259
287
|
for (const model of models || []) {
|
|
@@ -268,7 +296,7 @@ function getClaudeSwitchHint() {
|
|
|
268
296
|
}
|
|
269
297
|
|
|
270
298
|
function getOpencodeSwitchHint() {
|
|
271
|
-
return [...CLAUDE_MODELS, ...CODEX_MODELS].map(model => model.name).join(' / ');
|
|
299
|
+
return [...new Set([...CLAUDE_MODELS, ...CODEX_MODELS].map(model => model.name))].join(' / ');
|
|
272
300
|
}
|
|
273
301
|
|
|
274
302
|
async function promptClaudeModelSelection(args = {}, message = '选择 Claude 模型:') {
|
|
@@ -297,16 +325,29 @@ async function promptOpencodeDefaultModelSelection(args = {}, message = '选择
|
|
|
297
325
|
const fallbackClaude = getDefaultClaudeModel();
|
|
298
326
|
const fallbackCodex = getDefaultCodexModel();
|
|
299
327
|
const requested = (args['default-model'] || args.model || args['claude-model'] || args['codex-model'] || '').toString().trim();
|
|
328
|
+
const claudeProviderKey = getExternalClaudeProviderKey();
|
|
329
|
+
const codexProviderKey = getExternalCodexProviderKey();
|
|
330
|
+
|
|
331
|
+
if (isUnifiedSingleModelMode()) {
|
|
332
|
+
const unifiedModel = CLAUDE_MODELS[0] || CODEX_MODELS[0] || fallbackClaude;
|
|
333
|
+
return {
|
|
334
|
+
type: 'claude',
|
|
335
|
+
providerKey: claudeProviderKey,
|
|
336
|
+
modelId: unifiedModel.id,
|
|
337
|
+
modelName: unifiedModel.name,
|
|
338
|
+
modelKey: getExternalModelKey('claude', unifiedModel.id)
|
|
339
|
+
};
|
|
340
|
+
}
|
|
300
341
|
|
|
301
342
|
if (requested) {
|
|
302
343
|
const inClaude = CLAUDE_MODELS.find(model => model.id === requested);
|
|
303
344
|
if (inClaude) {
|
|
304
345
|
return {
|
|
305
346
|
type: 'claude',
|
|
306
|
-
providerKey:
|
|
347
|
+
providerKey: claudeProviderKey,
|
|
307
348
|
modelId: inClaude.id,
|
|
308
349
|
modelName: inClaude.name,
|
|
309
|
-
modelKey:
|
|
350
|
+
modelKey: getExternalModelKey('claude', inClaude.id)
|
|
310
351
|
};
|
|
311
352
|
}
|
|
312
353
|
|
|
@@ -314,10 +355,10 @@ async function promptOpencodeDefaultModelSelection(args = {}, message = '选择
|
|
|
314
355
|
if (inCodex) {
|
|
315
356
|
return {
|
|
316
357
|
type: 'codex',
|
|
317
|
-
providerKey:
|
|
358
|
+
providerKey: codexProviderKey,
|
|
318
359
|
modelId: inCodex.id,
|
|
319
360
|
modelName: inCodex.name,
|
|
320
|
-
modelKey:
|
|
361
|
+
modelKey: getExternalModelKey('codex', inCodex.id)
|
|
321
362
|
};
|
|
322
363
|
}
|
|
323
364
|
}
|
|
@@ -350,20 +391,20 @@ async function promptOpencodeDefaultModelSelection(args = {}, message = '选择
|
|
|
350
391
|
const model = CODEX_MODELS.find(item => item.id === pickedId) || fallbackCodex;
|
|
351
392
|
return {
|
|
352
393
|
type: 'codex',
|
|
353
|
-
providerKey:
|
|
394
|
+
providerKey: codexProviderKey,
|
|
354
395
|
modelId: model.id,
|
|
355
396
|
modelName: model.name,
|
|
356
|
-
modelKey:
|
|
397
|
+
modelKey: getExternalModelKey('codex', model.id)
|
|
357
398
|
};
|
|
358
399
|
}
|
|
359
400
|
|
|
360
401
|
const model = CLAUDE_MODELS.find(item => item.id === pickedId) || fallbackClaude;
|
|
361
402
|
return {
|
|
362
403
|
type: 'claude',
|
|
363
|
-
providerKey:
|
|
404
|
+
providerKey: claudeProviderKey,
|
|
364
405
|
modelId: model.id,
|
|
365
406
|
modelName: model.name,
|
|
366
|
-
modelKey:
|
|
407
|
+
modelKey: getExternalModelKey('claude', model.id)
|
|
367
408
|
};
|
|
368
409
|
}
|
|
369
410
|
|
|
@@ -371,6 +412,7 @@ async function promptOpencodeDefaultModelSelection(args = {}, message = '选择
|
|
|
371
412
|
const BACKUP_FILENAME = 'openclaw-default.json.bak';
|
|
372
413
|
const BACKUP_DIR_NAME = 'backups';
|
|
373
414
|
const MAX_BACKUPS = 10;
|
|
415
|
+
const GATEWAY_CLI_NAMES = ['openclaw'];
|
|
374
416
|
const EXTRA_BIN_DIRS = [
|
|
375
417
|
path.join(os.homedir(), '.npm-global', 'bin'),
|
|
376
418
|
path.join(os.homedir(), '.local', 'bin'),
|
|
@@ -864,13 +906,14 @@ function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
|
|
|
864
906
|
const markerEnd = '# <<< maxapi codex <<<';
|
|
865
907
|
const topMarker = '# >>> maxapi codex top >>>';
|
|
866
908
|
const topMarkerEnd = '# <<< maxapi codex top <<<';
|
|
867
|
-
const providerKey =
|
|
909
|
+
const providerKey = getExternalCodexProviderKey();
|
|
910
|
+
const providerLabel = `${getProviderBrandPrefix()} Codex`;
|
|
911
|
+
const removableProviderKeys = [...new Set([providerKey, 'yunyi-codex', 'maxapi-codex'])];
|
|
868
912
|
// 确保 base_url 以 /v1 结尾(Codex CLI 要求)
|
|
869
913
|
let normalizedUrl = baseUrl.replace(/\/+$/, '');
|
|
870
914
|
if (!normalizedUrl.endsWith('/v1')) normalizedUrl += '/v1';
|
|
871
915
|
try {
|
|
872
916
|
let existing = '';
|
|
873
|
-
const escapedProviderKey = providerKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
874
917
|
if (fs.existsSync(configPath)) {
|
|
875
918
|
existing = fs.readFileSync(configPath, 'utf8');
|
|
876
919
|
// 移除旧的 maxapi section(provider block)
|
|
@@ -879,8 +922,11 @@ function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
|
|
|
879
922
|
// 移除旧的 maxapi top-level block
|
|
880
923
|
const topRe = new RegExp(`${topMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${topMarkerEnd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`, 'g');
|
|
881
924
|
existing = existing.replace(topRe, '');
|
|
882
|
-
//
|
|
883
|
-
|
|
925
|
+
// 兼容旧版:移除历史遗留的 provider block、旧 openclaw-relay、旧 yunyi opencode 标记块
|
|
926
|
+
for (const removableProviderKey of removableProviderKeys) {
|
|
927
|
+
const escapedProviderKey = removableProviderKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
928
|
+
existing = existing.replace(new RegExp(`\\[model_providers\\.${escapedProviderKey}\\]\\n(?:(?!\\[)[^\\n]*\\n?)*`, 'g'), '');
|
|
929
|
+
}
|
|
884
930
|
existing = existing.replace(/\[model_providers\.openclaw-relay\]\n(?:(?!\[)[^\n]*\n?)*/g, '');
|
|
885
931
|
existing = existing.replace(/# >>> yunyi opencode >>>[\s\S]*?# <<< yunyi opencode <<<\n?/g, '');
|
|
886
932
|
existing = existing.replace(/\n{3,}/g, '\n\n').trim();
|
|
@@ -900,7 +946,7 @@ function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
|
|
|
900
946
|
const providerBlock = [
|
|
901
947
|
marker,
|
|
902
948
|
`[model_providers.${providerKey}]`,
|
|
903
|
-
`name = "
|
|
949
|
+
`name = "${providerLabel}"`,
|
|
904
950
|
`base_url = "${normalizedUrl}"`,
|
|
905
951
|
`wire_api = "responses"`,
|
|
906
952
|
`experimental_bearer_token = "${apiKey}"`,
|
|
@@ -931,10 +977,13 @@ function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
|
|
|
931
977
|
} catch { /* 非关键,静默失败 */ }
|
|
932
978
|
}
|
|
933
979
|
|
|
934
|
-
function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKey =
|
|
980
|
+
function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKey = getExternalModelKey('claude', getDefaultClaudeModel().id)) {
|
|
935
981
|
const home = os.homedir();
|
|
936
982
|
const claudeUrl = claudeBaseUrl.replace(/\/+$/, '');
|
|
937
983
|
const codexUrl = (codexBaseUrl || '').replace(/\/+$/, '');
|
|
984
|
+
const claudeProviderKey = getExternalClaudeProviderKey();
|
|
985
|
+
const codexProviderKey = getExternalCodexProviderKey();
|
|
986
|
+
const brandPrefix = getProviderBrandPrefix();
|
|
938
987
|
|
|
939
988
|
// ---- 1. opencode.json (CLI + 桌面版) ----
|
|
940
989
|
const configDir = process.platform === 'win32'
|
|
@@ -950,8 +999,8 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKe
|
|
|
950
999
|
if (!existing.provider) existing.provider = {};
|
|
951
1000
|
|
|
952
1001
|
// Claude provider (@ai-sdk/anthropic)
|
|
953
|
-
existing.provider[
|
|
954
|
-
name:
|
|
1002
|
+
existing.provider[claudeProviderKey] = {
|
|
1003
|
+
name: `${brandPrefix} Claude`,
|
|
955
1004
|
npm: '@ai-sdk/anthropic',
|
|
956
1005
|
models: buildProviderModelMap(CLAUDE_MODELS),
|
|
957
1006
|
options: { apiKey, baseURL: `${claudeUrl}/v1` }
|
|
@@ -959,8 +1008,8 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKe
|
|
|
959
1008
|
|
|
960
1009
|
// Codex provider (@ai-sdk/openai)
|
|
961
1010
|
if (codexUrl) {
|
|
962
|
-
existing.provider[
|
|
963
|
-
name:
|
|
1011
|
+
existing.provider[codexProviderKey] = {
|
|
1012
|
+
name: `${brandPrefix} Codex`,
|
|
964
1013
|
npm: '@ai-sdk/openai',
|
|
965
1014
|
models: buildProviderModelMap(CODEX_MODELS),
|
|
966
1015
|
options: { apiKey, baseURL: codexUrl }
|
|
@@ -978,7 +1027,8 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKe
|
|
|
978
1027
|
|
|
979
1028
|
// 从 disabled_providers 中移除我们的 provider
|
|
980
1029
|
if (Array.isArray(existing.disabled_providers)) {
|
|
981
|
-
|
|
1030
|
+
const enabledProviders = new Set([claudeProviderKey, codexProviderKey, 'yunyi-claude', 'yunyi-codex', 'maxapi', 'maxapi-codex']);
|
|
1031
|
+
existing.disabled_providers = existing.disabled_providers.filter(p => !enabledProviders.has(p));
|
|
982
1032
|
if (existing.disabled_providers.length === 0) delete existing.disabled_providers;
|
|
983
1033
|
}
|
|
984
1034
|
|
|
@@ -1036,7 +1086,7 @@ function syncExternalTools(type, baseUrl, apiKey, extra = {}) {
|
|
|
1036
1086
|
synced.push('Codex CLI config');
|
|
1037
1087
|
}
|
|
1038
1088
|
if (type === 'claude' && extra.codexBaseUrl) {
|
|
1039
|
-
writeOpencodeConfig(baseUrl, extra.codexBaseUrl, apiKey, extra.opencodeDefaultModelKey ||
|
|
1089
|
+
writeOpencodeConfig(baseUrl, extra.codexBaseUrl, apiKey, extra.opencodeDefaultModelKey || getExternalModelKey('claude', extra.claudeModelId || getDefaultClaudeModel().id));
|
|
1040
1090
|
synced.push('Opencode config');
|
|
1041
1091
|
}
|
|
1042
1092
|
} catch { /* ignore */ }
|
|
@@ -1102,7 +1152,7 @@ function dockerCmd(cmd) {
|
|
|
1102
1152
|
return _dockerNeedsSudo ? `sudo -n docker ${cmd}` : `docker ${cmd}`;
|
|
1103
1153
|
}
|
|
1104
1154
|
|
|
1105
|
-
// 查找包含 openclaw
|
|
1155
|
+
// 查找包含 openclaw 的运行中容器
|
|
1106
1156
|
function findOpenclawDockerContainers() {
|
|
1107
1157
|
if (_dockerContainerCache !== null) return _dockerContainerCache;
|
|
1108
1158
|
if (!isDockerAvailable()) { _dockerContainerCache = []; return []; }
|
|
@@ -1120,7 +1170,7 @@ function findOpenclawDockerContainers() {
|
|
|
1120
1170
|
const status = statusParts.join(' ');
|
|
1121
1171
|
if (!id) continue;
|
|
1122
1172
|
|
|
1123
|
-
// 检查容器内是否有 openclaw
|
|
1173
|
+
// 检查容器内是否有 openclaw(用 command -v 代替 which,兼容精简镜像)
|
|
1124
1174
|
for (const cli of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
1125
1175
|
const check = safeExec(dockerCmd(`exec ${id} sh -c "command -v ${cli} 2>/dev/null || which ${cli} 2>/dev/null"`), { timeout: 8000 });
|
|
1126
1176
|
if (check.ok && check.output && check.output.trim()) {
|
|
@@ -1294,7 +1344,7 @@ let _wslCliBinaryCache = undefined;
|
|
|
1294
1344
|
|
|
1295
1345
|
function getWslCliBinary() {
|
|
1296
1346
|
if (_wslCliBinaryCache !== undefined) return _wslCliBinaryCache;
|
|
1297
|
-
for (const name of
|
|
1347
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
1298
1348
|
try {
|
|
1299
1349
|
const result = execFileSync('wsl', ['bash', '-lc', `which ${name} 2>/dev/null`], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim();
|
|
1300
1350
|
if (result && result.startsWith('/')) {
|
|
@@ -1408,7 +1458,7 @@ function cleanupAgentProcesses() {
|
|
|
1408
1458
|
try {
|
|
1409
1459
|
const { execSync } = require('child_process');
|
|
1410
1460
|
if (process.platform === 'win32') {
|
|
1411
|
-
for (const name of
|
|
1461
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
1412
1462
|
try {
|
|
1413
1463
|
execSync(`wmic process where "commandline like '%${name}%agent%' and not commandline like '%gateway%'" call terminate 2>nul`, { stdio: 'ignore', timeout: 10000 });
|
|
1414
1464
|
} catch { /* ignore */ }
|
|
@@ -1419,18 +1469,18 @@ function cleanupAgentProcesses() {
|
|
|
1419
1469
|
// WSL 内的 agent 也要清理
|
|
1420
1470
|
if (detectGatewayEnv() === 'wsl') {
|
|
1421
1471
|
try {
|
|
1422
|
-
execSync('wsl -- bash -lc "pkill -f \'openclaw.*agent\' 2>/dev/null
|
|
1472
|
+
execSync('wsl -- bash -lc "pkill -f \'openclaw.*agent\' 2>/dev/null || true"', { stdio: 'ignore', timeout: 10000 });
|
|
1423
1473
|
} catch { /* ignore */ }
|
|
1424
1474
|
}
|
|
1425
1475
|
} else {
|
|
1426
|
-
for (const name of
|
|
1476
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
1427
1477
|
execSync(`pkill -f '${name}.*agent' 2>/dev/null || true`, { stdio: 'ignore' });
|
|
1428
1478
|
}
|
|
1429
1479
|
}
|
|
1430
1480
|
// Docker 容器内的 agent 也要清理
|
|
1431
1481
|
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
1432
1482
|
try {
|
|
1433
|
-
for (const name of
|
|
1483
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
1434
1484
|
safeExec(dockerCmd(`exec ${_selectedDockerContainer.id} sh -c "pkill -f '${name}.*agent' 2>/dev/null || true"`), { timeout: 10000 });
|
|
1435
1485
|
}
|
|
1436
1486
|
} catch { /* ignore */ }
|
|
@@ -3160,6 +3210,14 @@ function shellQuote(value) {
|
|
|
3160
3210
|
return `"${str.replace(/(["\\$`])/g, '\\$1')}"`;
|
|
3161
3211
|
}
|
|
3162
3212
|
|
|
3213
|
+
function escapeSingleQuotedShell(value) {
|
|
3214
|
+
return String(value || '').replace(/'/g, "'\\''");
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
function buildLoginShellCommand(shellPath, command) {
|
|
3218
|
+
return `${shellPath} -lc '${escapeSingleQuotedShell(command)}'`;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3163
3221
|
function escapeXml(value) {
|
|
3164
3222
|
return String(value)
|
|
3165
3223
|
.replace(/&/g, '&')
|
|
@@ -3203,7 +3261,7 @@ function resolveCliBinary() {
|
|
|
3203
3261
|
}
|
|
3204
3262
|
}
|
|
3205
3263
|
|
|
3206
|
-
const candidates =
|
|
3264
|
+
const candidates = GATEWAY_CLI_NAMES;
|
|
3207
3265
|
const isWin = process.platform === 'win32';
|
|
3208
3266
|
const searchDirs = (process.env.PATH || '').split(path.delimiter).concat(EXTRA_BIN_DIRS);
|
|
3209
3267
|
for (const name of candidates) {
|
|
@@ -3222,30 +3280,6 @@ function resolveCliBinary() {
|
|
|
3222
3280
|
}
|
|
3223
3281
|
}
|
|
3224
3282
|
|
|
3225
|
-
const moltbotRoots = [];
|
|
3226
|
-
if (process.env.MOLTBOT_ROOT) moltbotRoots.push(process.env.MOLTBOT_ROOT);
|
|
3227
|
-
moltbotRoots.push('/opt/moltbot');
|
|
3228
|
-
moltbotRoots.push('/opt/moltbot/app');
|
|
3229
|
-
|
|
3230
|
-
const scriptCandidates = [];
|
|
3231
|
-
for (const root of moltbotRoots) {
|
|
3232
|
-
scriptCandidates.push(
|
|
3233
|
-
path.join(root, 'moltbot.mjs'),
|
|
3234
|
-
path.join(root, 'bin', 'moltbot.mjs'),
|
|
3235
|
-
path.join(root, 'bin', 'moltbot.js'),
|
|
3236
|
-
path.join(root, 'src', 'moltbot.mjs'),
|
|
3237
|
-
path.join(root, 'src', 'moltbot.js')
|
|
3238
|
-
);
|
|
3239
|
-
}
|
|
3240
|
-
|
|
3241
|
-
for (const candidate of scriptCandidates) {
|
|
3242
|
-
try {
|
|
3243
|
-
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
3244
|
-
return candidate;
|
|
3245
|
-
}
|
|
3246
|
-
} catch { }
|
|
3247
|
-
}
|
|
3248
|
-
|
|
3249
3283
|
// Fallback: use login shell to find the binary (loads .zshrc/.bashrc PATH)
|
|
3250
3284
|
for (const name of candidates) {
|
|
3251
3285
|
if (process.platform === 'win32') {
|
|
@@ -3312,8 +3346,7 @@ function getCliMeta() {
|
|
|
3312
3346
|
const cliBinary = resolveCliBinary();
|
|
3313
3347
|
const cliName = cliBinary ? path.basename(cliBinary) : '';
|
|
3314
3348
|
const cliLower = cliName.toLowerCase();
|
|
3315
|
-
const
|
|
3316
|
-
const nodeMajor = isMoltbot ? 24 : 22;
|
|
3349
|
+
const nodeMajor = 22;
|
|
3317
3350
|
return { cliBinary, cliName, nodeMajor };
|
|
3318
3351
|
}
|
|
3319
3352
|
|
|
@@ -3399,7 +3432,7 @@ function readOpencodeCliConfig() {
|
|
|
3399
3432
|
const config = readJsonIfExists(configPath) || {};
|
|
3400
3433
|
return {
|
|
3401
3434
|
configPath,
|
|
3402
|
-
modelKey: config.model ||
|
|
3435
|
+
modelKey: config.model || getExternalModelKey('claude', getDefaultClaudeModel().id),
|
|
3403
3436
|
configured: fs.existsSync(configPath),
|
|
3404
3437
|
config
|
|
3405
3438
|
};
|
|
@@ -3410,7 +3443,7 @@ function readCodexCliConfig() {
|
|
|
3410
3443
|
const configRaw = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
|
3411
3444
|
const auth = readJsonIfExists(authPath) || {};
|
|
3412
3445
|
const model = (configRaw.match(/^model\s*=\s*"([^"]+)"\s*$/m) || [])[1] || getDefaultCodexModel().id;
|
|
3413
|
-
const provider = (configRaw.match(/^model_provider\s*=\s*"([^"]+)"\s*$/m) || [])[1] ||
|
|
3446
|
+
const provider = (configRaw.match(/^model_provider\s*=\s*"([^"]+)"\s*$/m) || [])[1] || getExternalCodexProviderKey();
|
|
3414
3447
|
const providerBlockRegex = new RegExp(`\\[model_providers\\.${escapeRegExp(provider)}\\]([\\s\\S]*?)(?=\\n\\[|$)`, 'm');
|
|
3415
3448
|
const providerBlock = (configRaw.match(providerBlockRegex) || [])[1] || '';
|
|
3416
3449
|
const baseUrl = (providerBlock.match(/base_url\s*=\s*"([^"]+)"/) || [])[1] || '';
|
|
@@ -3826,7 +3859,7 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
3826
3859
|
if (container.cliPath) dockerCmds.push(`${container.cliPath} gateway`);
|
|
3827
3860
|
dockerCmds.push(`${container.cli} gateway`);
|
|
3828
3861
|
}
|
|
3829
|
-
for (const name of
|
|
3862
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
3830
3863
|
dockerCmds.push(`${name} gateway`);
|
|
3831
3864
|
}
|
|
3832
3865
|
|
|
@@ -3858,7 +3891,7 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
3858
3891
|
}
|
|
3859
3892
|
} else {
|
|
3860
3893
|
if (daemonResult.reason === 'cli-not-found') {
|
|
3861
|
-
console.log(chalk.red('❌ 未找到 openclaw
|
|
3894
|
+
console.log(chalk.red('❌ 未找到 openclaw 命令,无法自动启动 Gateway'));
|
|
3862
3895
|
} else {
|
|
3863
3896
|
console.log(chalk.red(`❌ 自动启动失败: ${daemonResult.reason}`));
|
|
3864
3897
|
}
|
|
@@ -3902,15 +3935,9 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
3902
3935
|
const nodeInfo = findCompatibleNode(nodeMajor);
|
|
3903
3936
|
const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null) };
|
|
3904
3937
|
const useNode = cliBinary && nodeInfo && isNodeShebang(cliBinary);
|
|
3905
|
-
const
|
|
3906
|
-
? (useNode ? `"${nodeInfo.path}" "${cliBinary}" gateway` : `"${cliBinary}" gateway`)
|
|
3907
|
-
: null;
|
|
3938
|
+
const candidates = buildGatewayCommands(cliBinary, nodeInfo, useNode, 'start');
|
|
3908
3939
|
|
|
3909
|
-
const candidates
|
|
3910
|
-
if (cliCmd) candidates.push(cliCmd);
|
|
3911
|
-
candidates.push('openclaw gateway', 'clawdbot gateway', 'moltbot gateway');
|
|
3912
|
-
|
|
3913
|
-
for (const cmd of [...new Set(candidates)].filter(Boolean)) {
|
|
3940
|
+
for (const cmd of candidates) {
|
|
3914
3941
|
console.log(chalk.yellow(`⚠️ 尝试启动 Gateway: ${cmd}`));
|
|
3915
3942
|
if (spawnDetached(cmd, env)) {
|
|
3916
3943
|
if (await waitForGateway(port, '127.0.0.1', 10000)) {
|
|
@@ -3920,6 +3947,22 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
3920
3947
|
}
|
|
3921
3948
|
}
|
|
3922
3949
|
|
|
3950
|
+
if (process.platform !== 'win32') {
|
|
3951
|
+
for (const sh of ['/bin/zsh', '/bin/bash']) {
|
|
3952
|
+
if (!fs.existsSync(sh)) continue;
|
|
3953
|
+
for (const cmd of candidates) {
|
|
3954
|
+
const loginShellCmd = buildLoginShellCommand(sh, cmd);
|
|
3955
|
+
console.log(chalk.yellow(`⚠️ 尝试启动 Gateway: ${loginShellCmd}`));
|
|
3956
|
+
if (spawnDetached(loginShellCmd, env)) {
|
|
3957
|
+
if (await waitForGateway(port, '127.0.0.1', 15000)) {
|
|
3958
|
+
console.log(chalk.green('✅ Gateway 已通过 login shell 启动'));
|
|
3959
|
+
return { started: true, method: 'cli-login-shell', cmd: loginShellCmd };
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3923
3966
|
return { started: false };
|
|
3924
3967
|
}
|
|
3925
3968
|
|
|
@@ -4369,7 +4412,7 @@ async function presetClaude(paths, args = {}) {
|
|
|
4369
4412
|
if (yunyiLayoutResult.applied) {
|
|
4370
4413
|
syncManagedYunyiAuthProfiles(paths, config);
|
|
4371
4414
|
}
|
|
4372
|
-
syncExternalTools('claude', baseUrl, apiKey, { claudeModelId: modelId, opencodeDefaultModelKey:
|
|
4415
|
+
syncExternalTools('claude', baseUrl, apiKey, { claudeModelId: modelId, opencodeDefaultModelKey: getExternalModelKey('claude', modelId) });
|
|
4373
4416
|
writeSpinner.succeed('配置写入完成');
|
|
4374
4417
|
|
|
4375
4418
|
console.log(chalk.green('\n✅ Claude 节点配置完成!'));
|
|
@@ -4621,15 +4664,14 @@ async function autoActivate(paths, args = {}) {
|
|
|
4621
4664
|
}
|
|
4622
4665
|
|
|
4623
4666
|
// ---- 选模型(Claude + GPT 合并展示) ----
|
|
4624
|
-
const isSingleMode =
|
|
4625
|
-
&& CLAUDE_MODELS[0].id === CODEX_MODELS[0].id;
|
|
4667
|
+
const isSingleMode = isUnifiedSingleModelMode();
|
|
4626
4668
|
|
|
4627
4669
|
let selectedModelId;
|
|
4628
4670
|
let selectedType; // 'claude' or 'codex'
|
|
4629
4671
|
|
|
4630
4672
|
if (isSingleMode) {
|
|
4631
4673
|
selectedModelId = CLAUDE_MODELS[0].id;
|
|
4632
|
-
selectedType = 'claude';
|
|
4674
|
+
selectedType = String(args.primary || '').trim() === 'codex' ? 'codex' : 'claude';
|
|
4633
4675
|
} else {
|
|
4634
4676
|
const modelArg = (args.model || args['claude-model'] || args['codex-model'] || '').toString().trim();
|
|
4635
4677
|
if (modelArg) {
|
|
@@ -4725,9 +4767,9 @@ async function autoActivate(paths, args = {}) {
|
|
|
4725
4767
|
|
|
4726
4768
|
// 主模型 = 用户选的,备选 = 另一个
|
|
4727
4769
|
const primaryModelKey = isClaudePrimary ? claudeModelKey : codexModelKey;
|
|
4728
|
-
const fallbackModelKey = isClaudePrimary ? codexModelKey : claudeModelKey;
|
|
4770
|
+
const fallbackModelKey = isSingleMode ? '' : (isClaudePrimary ? codexModelKey : claudeModelKey);
|
|
4729
4771
|
config.agents.defaults.model.primary = primaryModelKey;
|
|
4730
|
-
config.agents.defaults.model.fallbacks = [fallbackModelKey];
|
|
4772
|
+
config.agents.defaults.model.fallbacks = fallbackModelKey ? [fallbackModelKey] : [];
|
|
4731
4773
|
const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
|
|
4732
4774
|
force: true,
|
|
4733
4775
|
endpointUrl: selectedEndpoint.url,
|
|
@@ -4755,7 +4797,7 @@ async function autoActivate(paths, args = {}) {
|
|
|
4755
4797
|
console.log(chalk.gray(` 已重置 ${selectionResult.agentId} 的活动会话映射`));
|
|
4756
4798
|
}
|
|
4757
4799
|
}
|
|
4758
|
-
const opencodeDefaultModelKey = isClaudePrimary ?
|
|
4800
|
+
const opencodeDefaultModelKey = isClaudePrimary ? getExternalModelKey('claude', claudeModelId) : getExternalModelKey('codex', codexModelId);
|
|
4759
4801
|
try { syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl, claudeModelId, opencodeDefaultModelKey }); } catch { /* ignore */ }
|
|
4760
4802
|
try { syncExternalTools('codex', codexBaseUrl, apiKey, { modelId: codexModelId }); } catch { /* ignore */ }
|
|
4761
4803
|
writeSpinner.succeed('配置写入完成');
|
|
@@ -5205,7 +5247,7 @@ async function yycodeQuickSetup(paths) {
|
|
|
5205
5247
|
if (yunyiLayoutResult.applied) {
|
|
5206
5248
|
syncManagedYunyiAuthProfiles(paths, config);
|
|
5207
5249
|
}
|
|
5208
|
-
try { syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl, claudeModelId, opencodeDefaultModelKey:
|
|
5250
|
+
try { syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl, claudeModelId, opencodeDefaultModelKey: getExternalModelKey('codex', codexModelId) }); } catch { /* ignore */ }
|
|
5209
5251
|
try { syncExternalTools('codex', codexBaseUrl, apiKey, { modelId: codexModelId }); } catch { /* ignore */ }
|
|
5210
5252
|
writeSpinner.succeed('配置写入完成');
|
|
5211
5253
|
|
|
@@ -5359,6 +5401,8 @@ function getConfigStatusLine(paths) {
|
|
|
5359
5401
|
'claude-yunyi': 'Claude(包月)',
|
|
5360
5402
|
'yunyi': 'Codex(包月)',
|
|
5361
5403
|
'heibai': 'MAXAPI(按量)',
|
|
5404
|
+
'maxapi': 'MAXAPI(按量)',
|
|
5405
|
+
'maxapi-codex': 'MAXAPI(按量)',
|
|
5362
5406
|
};
|
|
5363
5407
|
|
|
5364
5408
|
const parts = [];
|
|
@@ -5569,6 +5613,8 @@ async function switchModel(paths) {
|
|
|
5569
5613
|
'claude-yunyi': '云翼 Claude (包月)',
|
|
5570
5614
|
'yunyi': '云翼 Codex (包月)',
|
|
5571
5615
|
'heibai': 'MAXAPI (按量)',
|
|
5616
|
+
'maxapi': 'MAXAPI (按量)',
|
|
5617
|
+
'maxapi-codex': 'MAXAPI (按量)',
|
|
5572
5618
|
};
|
|
5573
5619
|
|
|
5574
5620
|
const claudeProviderName = API_CONFIG.claude.providerName;
|
|
@@ -5577,8 +5623,25 @@ async function switchModel(paths) {
|
|
|
5577
5623
|
// 预设模型列表
|
|
5578
5624
|
const choices = [];
|
|
5579
5625
|
const presetKeys = new Set();
|
|
5580
|
-
|
|
5581
|
-
|
|
5626
|
+
const unifiedSingleMode = isUnifiedSingleModelMode();
|
|
5627
|
+
const unifiedModel = unifiedSingleMode ? (CLAUDE_MODELS[0] || CODEX_MODELS[0] || null) : null;
|
|
5628
|
+
|
|
5629
|
+
if (unifiedSingleMode && unifiedModel) {
|
|
5630
|
+
const preferredProviders = [
|
|
5631
|
+
primary.split('/')[0],
|
|
5632
|
+
providers[claudeProviderName] ? claudeProviderName : '',
|
|
5633
|
+
providers[codexProviderName] ? codexProviderName : '',
|
|
5634
|
+
Object.keys(providers)[0] || ''
|
|
5635
|
+
].filter(Boolean);
|
|
5636
|
+
const unifiedProviderName = preferredProviders[0];
|
|
5637
|
+
const modelKey = `${unifiedProviderName}/${unifiedModel.id}`;
|
|
5638
|
+
const isCurrent = modelKey === primary;
|
|
5639
|
+
choices.push({
|
|
5640
|
+
name: isCurrent ? `${unifiedModel.name} (当前)` : unifiedModel.name,
|
|
5641
|
+
value: modelKey,
|
|
5642
|
+
});
|
|
5643
|
+
presetKeys.add(modelKey);
|
|
5644
|
+
} else if (CLAUDE_MODELS.length > 0) {
|
|
5582
5645
|
choices.push(new inquirer.Separator(' -- Claude --'));
|
|
5583
5646
|
for (const m of CLAUDE_MODELS) {
|
|
5584
5647
|
const pName = providers[claudeProviderName] ? claudeProviderName : Object.keys(providers)[0];
|
|
@@ -5592,7 +5655,7 @@ async function switchModel(paths) {
|
|
|
5592
5655
|
}
|
|
5593
5656
|
}
|
|
5594
5657
|
|
|
5595
|
-
if (CODEX_MODELS.length > 0) {
|
|
5658
|
+
if (!unifiedSingleMode && CODEX_MODELS.length > 0) {
|
|
5596
5659
|
choices.push(new inquirer.Separator(' -- GPT --'));
|
|
5597
5660
|
for (const m of CODEX_MODELS) {
|
|
5598
5661
|
const pName = providers[codexProviderName] ? codexProviderName : Object.keys(providers)[0];
|
|
@@ -5610,6 +5673,12 @@ async function switchModel(paths) {
|
|
|
5610
5673
|
const otherModels = [];
|
|
5611
5674
|
for (const [providerName, providerConfig] of Object.entries(providers)) {
|
|
5612
5675
|
for (const m of (providerConfig.models || [])) {
|
|
5676
|
+
if (unifiedSingleMode
|
|
5677
|
+
&& unifiedModel
|
|
5678
|
+
&& m.id === unifiedModel.id
|
|
5679
|
+
&& (providerName === claudeProviderName || providerName === codexProviderName)) {
|
|
5680
|
+
continue;
|
|
5681
|
+
}
|
|
5613
5682
|
const modelKey = `${providerName}/${m.id}`;
|
|
5614
5683
|
if (!presetKeys.has(modelKey)) {
|
|
5615
5684
|
otherModels.push({ modelKey, name: m.name || m.id, providerName });
|
|
@@ -5618,6 +5687,12 @@ async function switchModel(paths) {
|
|
|
5618
5687
|
}
|
|
5619
5688
|
const registeredKeys = Object.keys(config.agents?.defaults?.models || {});
|
|
5620
5689
|
for (const modelKey of registeredKeys) {
|
|
5690
|
+
if (unifiedSingleMode && unifiedModel) {
|
|
5691
|
+
const [providerName, modelId] = modelKey.split('/');
|
|
5692
|
+
if (modelId === unifiedModel.id && (providerName === claudeProviderName || providerName === codexProviderName)) {
|
|
5693
|
+
continue;
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5621
5696
|
if (presetKeys.has(modelKey) || otherModels.some(o => o.modelKey === modelKey)) continue;
|
|
5622
5697
|
const [pName, mId] = modelKey.split('/');
|
|
5623
5698
|
if (!pName || !mId) continue;
|
|
@@ -5887,7 +5962,7 @@ async function testConnection(paths, args = {}) {
|
|
|
5887
5962
|
const container = await selectDockerContainer();
|
|
5888
5963
|
if (!container) {
|
|
5889
5964
|
console.log(chalk.red('❌ 未找到包含 OpenClaw/Clawdbot/Moltbot 的 Docker 容器'));
|
|
5890
|
-
console.log(chalk.gray(' 请确保容器正在运行且已安装 openclaw
|
|
5965
|
+
console.log(chalk.gray(' 请确保容器正在运行且已安装 openclaw'));
|
|
5891
5966
|
return;
|
|
5892
5967
|
}
|
|
5893
5968
|
}
|
|
@@ -5927,8 +6002,6 @@ async function testConnection(paths, args = {}) {
|
|
|
5927
6002
|
console.log(chalk.gray(` 然后执行: ${_selectedDockerContainer.cli === 'node' ? `node ${_selectedDockerContainer.cliPath}` : _selectedDockerContainer.cli} gateway`));
|
|
5928
6003
|
} else {
|
|
5929
6004
|
console.log(chalk.gray(' 请在新的终端执行: openclaw gateway'));
|
|
5930
|
-
console.log(chalk.gray(' 或: clawdbot gateway'));
|
|
5931
|
-
console.log(chalk.gray(' 或: moltbot gateway'));
|
|
5932
6005
|
}
|
|
5933
6006
|
return;
|
|
5934
6007
|
}
|
|
@@ -5938,7 +6011,7 @@ async function testConnection(paths, args = {}) {
|
|
|
5938
6011
|
if (!restartOk) {
|
|
5939
6012
|
console.log(chalk.yellow('⚠️ Gateway 未能通过常规方式重启,当前使用的可能是之前的 Gateway 进程'));
|
|
5940
6013
|
console.log(chalk.yellow(' 新配置可能未生效。如 bot 不回复,请手动重启 Gateway:'));
|
|
5941
|
-
console.log(chalk.gray(' openclaw gateway restart
|
|
6014
|
+
console.log(chalk.gray(' openclaw gateway restart'));
|
|
5942
6015
|
}
|
|
5943
6016
|
|
|
5944
6017
|
// 步骤2: 通过 Gateway 端点测试(优先使用 CLI agent)
|
|
@@ -6063,7 +6136,7 @@ async function testConnection(paths, args = {}) {
|
|
|
6063
6136
|
console.log(chalk.gray(`\n 建议操作:`));
|
|
6064
6137
|
console.log(chalk.gray(` 1) 复制最新地址并重新打开浏览器(不要用旧书签)`));
|
|
6065
6138
|
console.log(chalk.cyan(` http://127.0.0.1:${gatewayPort}/#token=${gatewayToken}`));
|
|
6066
|
-
console.log(chalk.gray(` 2) 执行 Gateway 重启:openclaw gateway restart
|
|
6139
|
+
console.log(chalk.gray(` 2) 执行 Gateway 重启:openclaw gateway restart`));
|
|
6067
6140
|
console.log(chalk.gray(` 3) 若仍 401,检查是否存在多个配置目录(.openclaw 与 .clawdbot)`));
|
|
6068
6141
|
}
|
|
6069
6142
|
|
|
@@ -6073,7 +6146,7 @@ async function testConnection(paths, args = {}) {
|
|
|
6073
6146
|
console.log(chalk.gray(` 如遇问题,尝试更新 Gateway: npm install -g openclaw@latest && openclaw gateway restart`));
|
|
6074
6147
|
}
|
|
6075
6148
|
|
|
6076
|
-
console.log(chalk.gray(`\n 提示: 如果 Gateway 未运行,请执行: openclaw gateway
|
|
6149
|
+
console.log(chalk.gray(`\n 提示: 如果 Gateway 未运行,请执行: openclaw gateway`));
|
|
6077
6150
|
}
|
|
6078
6151
|
} catch (error) {
|
|
6079
6152
|
console.log(chalk.red(`❌ 测试失败: ${error.message}`));
|
|
@@ -6117,7 +6190,7 @@ function buildDockerInnerCmds(container, verb) {
|
|
|
6117
6190
|
if (container.cliPath) cmds.push(`${container.cliPath} ${verb}`);
|
|
6118
6191
|
cmds.push(`${container.cli} ${verb}`);
|
|
6119
6192
|
}
|
|
6120
|
-
for (const name of
|
|
6193
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6121
6194
|
cmds.push(`${name} ${verb}`);
|
|
6122
6195
|
}
|
|
6123
6196
|
return [...new Set(cmds)].filter(Boolean);
|
|
@@ -6150,7 +6223,7 @@ async function restartGatewayDocker(gatewayPort, silent = false) {
|
|
|
6150
6223
|
// 策略 B:杀容器内旧进程 → spawn 启动新 Gateway → 端口探测
|
|
6151
6224
|
if (!silent) console.log(chalk.gray(' Docker 内常规重启未生效,尝试杀进程后重新启动...'));
|
|
6152
6225
|
try {
|
|
6153
|
-
for (const name of
|
|
6226
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6154
6227
|
safeExec(dockerCmd(`exec ${cid} sh -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
|
|
6155
6228
|
}
|
|
6156
6229
|
safeExec(dockerCmd(`exec ${cid} sh -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
|
|
@@ -6174,7 +6247,7 @@ async function restartGatewayDocker(gatewayPort, silent = false) {
|
|
|
6174
6247
|
async function restartGatewayWsl(gatewayPort, silent = false) {
|
|
6175
6248
|
if (!silent) console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
|
|
6176
6249
|
const wslCli = getWslCliBinary();
|
|
6177
|
-
const names =
|
|
6250
|
+
const names = GATEWAY_CLI_NAMES;
|
|
6178
6251
|
const portWasOpenBefore = await isPortOpen(gatewayPort, '127.0.0.1', 500);
|
|
6179
6252
|
const beforeSignature = portWasOpenBefore ? getGatewayProcessSignature(gatewayPort, 'wsl') : '';
|
|
6180
6253
|
|
|
@@ -6267,13 +6340,11 @@ async function restartGatewayNative(silent = false) {
|
|
|
6267
6340
|
// 策略 C:用 login shell 启动(加载 nvm/fnm 等 PATH)
|
|
6268
6341
|
if (process.platform !== 'win32') {
|
|
6269
6342
|
if (!silent) console.log(chalk.gray(' 尝试通过 login shell 启动...'));
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
if (
|
|
6275
|
-
if (spawnDetached(`${sh} -lc '${name} gateway'`, env)) {
|
|
6276
|
-
launched = true;
|
|
6343
|
+
for (const sh of ['/bin/zsh', '/bin/bash']) {
|
|
6344
|
+
if (!fs.existsSync(sh)) continue;
|
|
6345
|
+
for (const cmd of startCmds) {
|
|
6346
|
+
const loginShellCmd = buildLoginShellCommand(sh, cmd);
|
|
6347
|
+
if (spawnDetached(loginShellCmd, env)) {
|
|
6277
6348
|
if (await waitForGateway(gatewayPort, '127.0.0.1', 10000)) {
|
|
6278
6349
|
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (login shell)'));
|
|
6279
6350
|
return true;
|
|
@@ -6286,10 +6357,8 @@ async function restartGatewayNative(silent = false) {
|
|
|
6286
6357
|
|
|
6287
6358
|
// 全部失败,输出诊断
|
|
6288
6359
|
if (!silent) {
|
|
6289
|
-
console.log(chalk.red(
|
|
6360
|
+
console.log(chalk.red('❌ 重启失败: 找不到 openclaw 命令'));
|
|
6290
6361
|
console.log(chalk.gray(` 请手动运行: openclaw gateway restart`));
|
|
6291
|
-
console.log(chalk.gray(` 或: clawdbot gateway restart`));
|
|
6292
|
-
console.log(chalk.gray(` 或: moltbot gateway restart`));
|
|
6293
6362
|
printGatewayDiagnostics(resolved);
|
|
6294
6363
|
}
|
|
6295
6364
|
return false;
|
|
@@ -6307,7 +6376,7 @@ function buildGatewayCommands(resolved, nodeInfo, useNode, action) {
|
|
|
6307
6376
|
}
|
|
6308
6377
|
}
|
|
6309
6378
|
|
|
6310
|
-
const names =
|
|
6379
|
+
const names = GATEWAY_CLI_NAMES;
|
|
6311
6380
|
for (const name of names) commands.push(`${name} ${verb}`);
|
|
6312
6381
|
|
|
6313
6382
|
return [...new Set(commands)].filter(Boolean);
|
|
@@ -6324,13 +6393,13 @@ async function killGatewayProcesses(gatewayPort = 18789) {
|
|
|
6324
6393
|
}
|
|
6325
6394
|
}
|
|
6326
6395
|
if (isWslAvailable()) {
|
|
6327
|
-
for (const name of
|
|
6396
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6328
6397
|
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
6329
6398
|
}
|
|
6330
6399
|
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
6331
6400
|
}
|
|
6332
6401
|
} else {
|
|
6333
|
-
for (const name of
|
|
6402
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6334
6403
|
safeExec(`pkill -f '${name}.*gateway' 2>/dev/null || true`);
|
|
6335
6404
|
}
|
|
6336
6405
|
const lsof = safeExec(`lsof -ti :${gatewayPort} 2>/dev/null`);
|
|
@@ -6347,14 +6416,14 @@ function printGatewayDiagnostics(resolved) {
|
|
|
6347
6416
|
console.log(chalk.gray(`\n [诊断] resolveCliBinary = ${resolved || 'null'}`));
|
|
6348
6417
|
const npmPrefix = safeExec('npm prefix -g');
|
|
6349
6418
|
if (npmPrefix.ok) console.log(chalk.gray(` [诊断] npm prefix -g = ${npmPrefix.output}`));
|
|
6350
|
-
for (const name of
|
|
6419
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6351
6420
|
const which = safeExec(process.platform === 'win32' ? `where ${name} 2>nul` : `/bin/zsh -lc "command -v ${name}" 2>/dev/null || /bin/bash -lc "command -v ${name}" 2>/dev/null`);
|
|
6352
6421
|
if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
|
|
6353
6422
|
}
|
|
6354
6423
|
if (isDockerAvailable()) {
|
|
6355
6424
|
const dContainers = findOpenclawDockerContainers();
|
|
6356
6425
|
if (dContainers.length > 0) {
|
|
6357
|
-
console.log(chalk.gray(
|
|
6426
|
+
console.log(chalk.gray(' [诊断] Docker 容器 (含 openclaw):'));
|
|
6358
6427
|
for (const c of dContainers) {
|
|
6359
6428
|
console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
|
|
6360
6429
|
}
|
|
@@ -6445,7 +6514,7 @@ function testGatewayApi(port, token, model, endpoint = '/v1/chat/completions') {
|
|
|
6445
6514
|
|
|
6446
6515
|
req.on('error', (e) => {
|
|
6447
6516
|
if (e.code === 'ECONNREFUSED') {
|
|
6448
|
-
resolve({ success: false, error: 'Gateway 未运行,请先启动: openclaw gateway
|
|
6517
|
+
resolve({ success: false, error: 'Gateway 未运行,请先启动: openclaw gateway' });
|
|
6449
6518
|
} else {
|
|
6450
6519
|
resolve({ success: false, error: e.message });
|
|
6451
6520
|
}
|
|
@@ -6472,14 +6541,14 @@ function testGatewayViaAgent(model, agentId = '') {
|
|
|
6472
6541
|
const agentCmd = `${wslCli} ${subcommand}`;
|
|
6473
6542
|
cmd = `wsl -- bash -c '${agentCmd.replace(/'/g, "'\\''")}'`;
|
|
6474
6543
|
} else {
|
|
6475
|
-
const agentCmd = `openclaw ${subcommand}
|
|
6544
|
+
const agentCmd = `openclaw ${subcommand}`;
|
|
6476
6545
|
cmd = `wsl -- bash -lc '${agentCmd.replace(/'/g, "'\\''")}'`;
|
|
6477
6546
|
}
|
|
6478
6547
|
execOpts = { timeout: 120000 };
|
|
6479
6548
|
} else {
|
|
6480
6549
|
const { cliBinary, nodeMajor } = getCliMeta();
|
|
6481
6550
|
if (!cliBinary) {
|
|
6482
|
-
resolve({ success: false, usedCli: false, error: '未找到 openclaw
|
|
6551
|
+
resolve({ success: false, usedCli: false, error: '未找到 openclaw 命令' });
|
|
6483
6552
|
return;
|
|
6484
6553
|
}
|
|
6485
6554
|
const nodeInfo = findCompatibleNode(nodeMajor);
|