yymaxapi 1.0.48 → 1.0.50
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 +355 -292
- package/install.ps1 +1 -1
- package/install.sh +1 -2
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -138,6 +138,10 @@ const DEFAULT_API_CONFIG = {
|
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
+
const SKIP_API_VALIDATION = false;
|
|
142
|
+
|
|
143
|
+
const SKIP_API_VALIDATION = false;
|
|
144
|
+
|
|
141
145
|
function normalizeEndpoints(raw, fallback) {
|
|
142
146
|
if (!Array.isArray(raw)) return fallback;
|
|
143
147
|
const normalized = raw
|
|
@@ -371,6 +375,9 @@ function httpGetJson(url, headers = {}, timeout = 10000) {
|
|
|
371
375
|
}
|
|
372
376
|
|
|
373
377
|
async function validateApiKey(nodeUrl, apiKey) {
|
|
378
|
+
if (SKIP_API_VALIDATION) {
|
|
379
|
+
return { valid: true, skipped: true };
|
|
380
|
+
}
|
|
374
381
|
const verifyUrl = `${nodeUrl.replace(/\/+$/, '')}/user/api/v1/me`;
|
|
375
382
|
const maxRetries = 3;
|
|
376
383
|
const spinner = ora({ text: '正在验证 API Key...', spinner: 'dots' }).start();
|
|
@@ -1659,6 +1666,7 @@ function ensureGatewaySettings(config) {
|
|
|
1659
1666
|
if (isLocal && gateway.remote.token !== gateway.auth.token) {
|
|
1660
1667
|
gateway.remote.token = gateway.auth.token;
|
|
1661
1668
|
}
|
|
1669
|
+
|
|
1662
1670
|
}
|
|
1663
1671
|
|
|
1664
1672
|
function isPortOpen(port, host = '127.0.0.1', timeoutMs = 800) {
|
|
@@ -2662,10 +2670,21 @@ async function autoActivate(paths, args = {}) {
|
|
|
2662
2670
|
|
|
2663
2671
|
// 增量写入: 只添加/更新云翼 provider,保留其他已有配置
|
|
2664
2672
|
|
|
2665
|
-
// Claude 侧
|
|
2673
|
+
// Claude 侧 — 让用户选模型
|
|
2666
2674
|
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
2667
|
-
|
|
2668
|
-
|
|
2675
|
+
let claudeModelId = (args['claude-model'] || '').toString().trim();
|
|
2676
|
+
if (!claudeModelId && CLAUDE_MODELS.length > 1) {
|
|
2677
|
+
const { picked } = await inquirer.prompt([{
|
|
2678
|
+
type: 'list',
|
|
2679
|
+
name: 'picked',
|
|
2680
|
+
message: '选择 Claude 模型:',
|
|
2681
|
+
choices: CLAUDE_MODELS.map(m => ({ name: m.name, value: m.id })),
|
|
2682
|
+
default: CLAUDE_MODELS[0].id
|
|
2683
|
+
}]);
|
|
2684
|
+
claudeModelId = picked;
|
|
2685
|
+
}
|
|
2686
|
+
if (!claudeModelId) claudeModelId = CLAUDE_MODELS[0]?.id || 'claude-sonnet-4-6';
|
|
2687
|
+
const claudeModel = CLAUDE_MODELS.find(m => m.id === claudeModelId) || { id: claudeModelId, name: claudeModelId };
|
|
2669
2688
|
const claudeModelKey = `${claudeProviderName}/${claudeModelId}`;
|
|
2670
2689
|
|
|
2671
2690
|
config.models.providers[claudeProviderName] = {
|
|
@@ -2685,10 +2704,21 @@ async function autoActivate(paths, args = {}) {
|
|
|
2685
2704
|
config.auth.profiles[`${claudeProviderName}:default`] = { provider: claudeProviderName, mode: 'api_key' };
|
|
2686
2705
|
config.agents.defaults.models[claudeModelKey] = { alias: claudeProviderName };
|
|
2687
2706
|
|
|
2688
|
-
// Codex 侧
|
|
2707
|
+
// Codex 侧 — 让用户选模型
|
|
2689
2708
|
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
2690
|
-
|
|
2691
|
-
|
|
2709
|
+
let codexModelId = (args['codex-model'] || '').toString().trim();
|
|
2710
|
+
if (!codexModelId && CODEX_MODELS.length > 1) {
|
|
2711
|
+
const { pickedCodex } = await inquirer.prompt([{
|
|
2712
|
+
type: 'list',
|
|
2713
|
+
name: 'pickedCodex',
|
|
2714
|
+
message: '选择 Codex 模型:',
|
|
2715
|
+
choices: CODEX_MODELS.map(m => ({ name: m.name, value: m.id })),
|
|
2716
|
+
default: CODEX_MODELS[0].id
|
|
2717
|
+
}]);
|
|
2718
|
+
codexModelId = pickedCodex;
|
|
2719
|
+
}
|
|
2720
|
+
if (!codexModelId) codexModelId = CODEX_MODELS[0]?.id || 'gpt-5.3-codex';
|
|
2721
|
+
const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: codexModelId };
|
|
2692
2722
|
const codexModelKey = `${codexProviderName}/${codexModelId}`;
|
|
2693
2723
|
|
|
2694
2724
|
config.models.providers[codexProviderName] = {
|
|
@@ -2759,7 +2789,7 @@ async function autoActivate(paths, args = {}) {
|
|
|
2759
2789
|
const gwToken = config.gateway?.auth?.token;
|
|
2760
2790
|
if (gwToken) {
|
|
2761
2791
|
console.log(chalk.green(`\n🌐 Web Dashboard:`));
|
|
2762
|
-
console.log(chalk.cyan(` http://
|
|
2792
|
+
console.log(chalk.cyan(` http://127.0.0.1:${gwPort}/?token=${gwToken}`));
|
|
2763
2793
|
}
|
|
2764
2794
|
|
|
2765
2795
|
// ---- 测试连接 ----
|
|
@@ -3155,6 +3185,7 @@ async function main() {
|
|
|
3155
3185
|
{ name: ' 配置 Opencode', value: 'activate_opencode' },
|
|
3156
3186
|
new inquirer.Separator(' -- 工具 --'),
|
|
3157
3187
|
{ name: ' 切换模型', value: 'switch_model' },
|
|
3188
|
+
{ name: ` 权限管理${getToolsProfileTag(paths)}`, value: 'tools_profile' },
|
|
3158
3189
|
{ name: ' 测试连接', value: 'test_connection' },
|
|
3159
3190
|
{ name: ' 查看配置', value: 'view_config' },
|
|
3160
3191
|
{ name: ' 恢复默认', value: 'restore' },
|
|
@@ -3183,6 +3214,9 @@ async function main() {
|
|
|
3183
3214
|
case 'switch_model':
|
|
3184
3215
|
await switchModel(paths);
|
|
3185
3216
|
break;
|
|
3217
|
+
case 'tools_profile':
|
|
3218
|
+
await manageToolsProfile(paths);
|
|
3219
|
+
break;
|
|
3186
3220
|
case 'view_config':
|
|
3187
3221
|
await viewConfig(paths);
|
|
3188
3222
|
break;
|
|
@@ -3334,15 +3368,18 @@ async function selectNode(paths, type) {
|
|
|
3334
3368
|
}]
|
|
3335
3369
|
};
|
|
3336
3370
|
|
|
3337
|
-
//
|
|
3371
|
+
// 注册模型并设为主模型
|
|
3338
3372
|
const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
|
|
3339
3373
|
config.agents.defaults.models[modelKey] = { alias: apiConfig.providerName };
|
|
3374
|
+
config.agents.defaults.model.primary = modelKey;
|
|
3375
|
+
config.agents.defaults.model.fallbacks = (config.agents.defaults.model.fallbacks || []).filter(k => k !== modelKey);
|
|
3340
3376
|
|
|
3377
|
+
ensureGatewaySettings(config);
|
|
3341
3378
|
writeConfigWithSync(paths, config);
|
|
3342
3379
|
|
|
3343
3380
|
console.log(chalk.green(`\n✅ ${typeLabel} 节点配置完成!`));
|
|
3344
3381
|
console.log(chalk.cyan(` 节点: ${selectedEndpoint.name} (${selectedEndpoint.url})`));
|
|
3345
|
-
console.log(chalk.gray(` 模型: ${modelConfig.name}`));
|
|
3382
|
+
console.log(chalk.gray(` 模型: ${modelConfig.name} (主模型)`));
|
|
3346
3383
|
console.log(chalk.gray(` API Key: ${oldApiKey ? '已设置' : '未设置'}`));
|
|
3347
3384
|
}
|
|
3348
3385
|
|
|
@@ -3475,15 +3512,39 @@ async function switchModel(paths) {
|
|
|
3475
3512
|
}
|
|
3476
3513
|
|
|
3477
3514
|
const selectedProvider = selected.split('/')[0];
|
|
3515
|
+
const selectedModelId = selected.split('/')[1];
|
|
3478
3516
|
config.agents.defaults.model.primary = selected;
|
|
3479
3517
|
if (!config.agents.defaults.models[selected]) {
|
|
3480
3518
|
config.agents.defaults.models[selected] = { alias: selectedProvider };
|
|
3481
3519
|
}
|
|
3520
|
+
|
|
3521
|
+
// 同步更新 provider.models,确保 primary 指向的模型在列表中
|
|
3522
|
+
const providerConfig = providers[selectedProvider];
|
|
3523
|
+
if (providerConfig && selectedModelId) {
|
|
3524
|
+
const existingModel = (providerConfig.models || []).find(m => m.id === selectedModelId);
|
|
3525
|
+
if (!existingModel) {
|
|
3526
|
+
const allModels = [...CLAUDE_MODELS, ...CODEX_MODELS];
|
|
3527
|
+
const knownModel = allModels.find(m => m.id === selectedModelId);
|
|
3528
|
+
const apiType = providerConfig.api || '';
|
|
3529
|
+
const isAnthropic = apiType.startsWith('anthropic');
|
|
3530
|
+
const defaultCtx = isAnthropic ? (API_CONFIG.claude?.contextWindow || 200000) : (API_CONFIG.codex?.contextWindow || 200000);
|
|
3531
|
+
const defaultMax = isAnthropic ? (API_CONFIG.claude?.maxTokens || 8192) : (API_CONFIG.codex?.maxTokens || 8192);
|
|
3532
|
+
providerConfig.models = [{
|
|
3533
|
+
id: selectedModelId,
|
|
3534
|
+
name: knownModel ? knownModel.name : selectedModelId,
|
|
3535
|
+
contextWindow: defaultCtx,
|
|
3536
|
+
maxTokens: defaultMax
|
|
3537
|
+
}];
|
|
3538
|
+
} else {
|
|
3539
|
+
providerConfig.models = [existingModel];
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3482
3543
|
config.agents.defaults.model.fallbacks = (config.agents.defaults.model.fallbacks || []).filter(k => k !== selected);
|
|
3483
|
-
// 把旧的 primary 加入 fallbacks (如果存在且不同)
|
|
3484
3544
|
if (primary && primary !== selected && !config.agents.defaults.model.fallbacks.includes(primary)) {
|
|
3485
3545
|
config.agents.defaults.model.fallbacks.push(primary);
|
|
3486
3546
|
}
|
|
3547
|
+
ensureGatewaySettings(config);
|
|
3487
3548
|
writeConfigWithSync(paths, config);
|
|
3488
3549
|
|
|
3489
3550
|
const selectedProviderConfig = providers[selectedProvider];
|
|
@@ -3493,15 +3554,96 @@ async function switchModel(paths) {
|
|
|
3493
3554
|
if (selectedProviderConfig) {
|
|
3494
3555
|
console.log(chalk.gray(` 节点: ${selectedProviderConfig.baseUrl}`));
|
|
3495
3556
|
}
|
|
3496
|
-
console.log(chalk.yellow('\n💡 切换后建议重启 Gateway: openclaw gateway restart'));
|
|
3497
3557
|
|
|
3498
3558
|
const gwPort = config.gateway?.port || 18789;
|
|
3499
3559
|
const gwToken = config.gateway?.auth?.token;
|
|
3560
|
+
|
|
3561
|
+
// 自动重启 Gateway 使切换立即生效
|
|
3562
|
+
if (await isPortOpen(gwPort)) {
|
|
3563
|
+
const gwSpinner = ora({ text: '重启 Gateway 使模型切换生效...', spinner: 'dots' }).start();
|
|
3564
|
+
const ok = await restartGateway({ silent: true });
|
|
3565
|
+
if (ok) {
|
|
3566
|
+
gwSpinner.succeed('Gateway 已重启,模型切换已生效');
|
|
3567
|
+
} else {
|
|
3568
|
+
gwSpinner.fail('Gateway 重启失败,请手动重启: openclaw gateway restart');
|
|
3569
|
+
}
|
|
3570
|
+
} else {
|
|
3571
|
+
console.log(chalk.yellow('\n⚠️ Gateway 未运行,模型切换将在下次启动 Gateway 时生效'));
|
|
3572
|
+
console.log(chalk.gray(' 启动 Gateway: openclaw gateway'));
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3500
3575
|
if (gwToken) {
|
|
3501
3576
|
console.log(chalk.green(`\n🌐 Web Dashboard:`));
|
|
3502
|
-
console.log(chalk.cyan(` http://
|
|
3577
|
+
console.log(chalk.cyan(` http://127.0.0.1:${gwPort}/?token=${gwToken}`));
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
// ============ 权限管理 (tools.profile) ============
|
|
3581
|
+
const TOOLS_PROFILES = [
|
|
3582
|
+
{ id: 'full', name: '完整模式', desc: '编码 + 系统工具 + 文件操作(Claude Code / Codex 必需)' },
|
|
3583
|
+
{ id: 'messaging', name: '聊天模式', desc: '仅对话,无文件/系统工具(安全但功能受限)' },
|
|
3584
|
+
];
|
|
3585
|
+
|
|
3586
|
+
function getToolsProfileTag(paths) {
|
|
3587
|
+
try {
|
|
3588
|
+
const config = readConfig(paths.openclawConfig);
|
|
3589
|
+
const profile = config?.tools?.profile || 'messaging';
|
|
3590
|
+
if (profile === 'full') return chalk.green(' [完整]');
|
|
3591
|
+
return chalk.yellow(' [仅聊天]');
|
|
3592
|
+
} catch {
|
|
3593
|
+
return '';
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
async function manageToolsProfile(paths) {
|
|
3598
|
+
console.log(chalk.cyan('🔐 权限管理\n'));
|
|
3599
|
+
console.log(chalk.gray('OpenClaw 3.2+ 新增 tools.profile 控制:'));
|
|
3600
|
+
console.log(chalk.gray(' full = 完整编码能力(读写文件、执行命令、系统操作)'));
|
|
3601
|
+
console.log(chalk.gray(' messaging = 仅聊天(无法操作文件和系统,适合纯对话场景)'));
|
|
3602
|
+
console.log(chalk.yellow('\n⚠️ 使用 Claude Code / Codex 编码,必须选择「完整模式」\n'));
|
|
3603
|
+
|
|
3604
|
+
const config = readConfig(paths.openclawConfig) || {};
|
|
3605
|
+
const current = config?.tools?.profile || 'messaging';
|
|
3606
|
+
const currentLabel = current === 'full' ? '完整模式' : current === 'messaging' ? '聊天模式' : current;
|
|
3607
|
+
console.log(chalk.gray(`当前: ${currentLabel}\n`));
|
|
3608
|
+
|
|
3609
|
+
const { selected } = await inquirer.prompt([{
|
|
3610
|
+
type: 'list',
|
|
3611
|
+
name: 'selected',
|
|
3612
|
+
message: '选择权限模式:',
|
|
3613
|
+
choices: TOOLS_PROFILES.map(p => ({
|
|
3614
|
+
name: p.id === current ? `${p.name} — ${p.desc} (当前)` : `${p.name} — ${p.desc}`,
|
|
3615
|
+
value: p.id,
|
|
3616
|
+
})),
|
|
3617
|
+
default: current,
|
|
3618
|
+
}]);
|
|
3619
|
+
|
|
3620
|
+
if (selected === current) {
|
|
3621
|
+
console.log(chalk.gray('\n权限模式未变更'));
|
|
3622
|
+
return;
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
if (!config.tools) config.tools = {};
|
|
3626
|
+
config.tools.profile = selected;
|
|
3627
|
+
ensureGatewaySettings(config);
|
|
3628
|
+
writeConfigWithSync(paths, config);
|
|
3629
|
+
|
|
3630
|
+
const label = TOOLS_PROFILES.find(p => p.id === selected)?.name || selected;
|
|
3631
|
+
console.log(chalk.green(`\n✅ 已切换到: ${label}`));
|
|
3632
|
+
|
|
3633
|
+
const gwPort = config.gateway?.port || 18789;
|
|
3634
|
+
if (await isPortOpen(gwPort)) {
|
|
3635
|
+
const gwSpinner = ora({ text: '重启 Gateway 使权限变更生效...', spinner: 'dots' }).start();
|
|
3636
|
+
const ok = await restartGateway({ silent: true });
|
|
3637
|
+
if (ok) {
|
|
3638
|
+
gwSpinner.succeed('Gateway 已重启,权限变更已生效');
|
|
3639
|
+
} else {
|
|
3640
|
+
gwSpinner.fail('Gateway 重启失败,请手动重启: openclaw gateway restart');
|
|
3641
|
+
}
|
|
3642
|
+
} else {
|
|
3643
|
+
console.log(chalk.gray(' Gateway 未运行,变更将在下次启动时生效'));
|
|
3503
3644
|
}
|
|
3504
3645
|
}
|
|
3646
|
+
|
|
3505
3647
|
// ============ 测试连接 ============
|
|
3506
3648
|
async function testConnection(paths, args = {}) {
|
|
3507
3649
|
console.log(chalk.cyan('🧪 测试 OpenClaw Gateway 连接\n'));
|
|
@@ -3560,7 +3702,7 @@ async function testConnection(paths, args = {}) {
|
|
|
3560
3702
|
console.log(chalk.gray(`当前激活: ${typeLabel}`));
|
|
3561
3703
|
console.log(chalk.gray(`中转节点: ${provider.baseUrl}`));
|
|
3562
3704
|
console.log(chalk.gray(`模型: ${primary}`));
|
|
3563
|
-
console.log(chalk.gray(`Gateway: http://
|
|
3705
|
+
console.log(chalk.gray(`Gateway: http://127.0.0.1:${gatewayPort}\n`));
|
|
3564
3706
|
// 获取 Gateway token
|
|
3565
3707
|
const gatewayToken = config.gateway?.auth?.token;
|
|
3566
3708
|
if (!gatewayToken) {
|
|
@@ -3584,11 +3726,8 @@ async function testConnection(paths, args = {}) {
|
|
|
3584
3726
|
cleanupAgentProcesses();
|
|
3585
3727
|
|
|
3586
3728
|
// 步骤1: 先重启 Gateway 使配置生效
|
|
3587
|
-
|
|
3588
|
-
const restartOk = await restartGateway();
|
|
3589
|
-
|
|
3590
|
-
// 等待 Gateway 启动
|
|
3591
|
-
const gwSpinner = ora({ text: '等待 Gateway 启动...', spinner: 'dots' }).start();
|
|
3729
|
+
const gwSpinner = ora({ text: '步骤 1/2: 重启 Gateway 使配置生效...', spinner: 'dots' }).start();
|
|
3730
|
+
const restartOk = await restartGateway({ silent: true });
|
|
3592
3731
|
|
|
3593
3732
|
let gatewayRunning = false;
|
|
3594
3733
|
|
|
@@ -3687,7 +3826,7 @@ async function testConnection(paths, args = {}) {
|
|
|
3687
3826
|
}
|
|
3688
3827
|
}
|
|
3689
3828
|
|
|
3690
|
-
console.log(chalk.gray(` 端点: http://
|
|
3829
|
+
console.log(chalk.gray(` 端点: http://127.0.0.1:${gatewayPort}/v1/responses`));
|
|
3691
3830
|
const startTime = Date.now();
|
|
3692
3831
|
let result = await testGatewayApi(gatewayPort, gatewayToken, primary);
|
|
3693
3832
|
const latency = Date.now() - startTime;
|
|
@@ -3748,168 +3887,229 @@ async function testConnection(paths, args = {}) {
|
|
|
3748
3887
|
}
|
|
3749
3888
|
|
|
3750
3889
|
// ============ 重启 Gateway ============
|
|
3751
|
-
async function restartGateway() {
|
|
3752
|
-
console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
|
|
3890
|
+
async function restartGateway({ silent = false } = {}) {
|
|
3891
|
+
if (!silent) console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
|
|
3753
3892
|
|
|
3754
3893
|
const gwEnv = detectGatewayEnv();
|
|
3894
|
+
const configPaths = getConfigPath();
|
|
3895
|
+
const gwConfig = readConfig(configPaths.openclawConfig);
|
|
3896
|
+
const gatewayPort = gwConfig?.gateway?.port || 18789;
|
|
3755
3897
|
|
|
3756
|
-
//
|
|
3898
|
+
// Docker 容器内重启
|
|
3757
3899
|
if (gwEnv === 'docker') {
|
|
3758
|
-
const
|
|
3759
|
-
if (
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
}
|
|
3763
|
-
console.log(chalk.gray(` [检测] Gateway 运行在 Docker 容器: ${container.name} (${container.image})`));
|
|
3900
|
+
const result = await restartGatewayDocker(gatewayPort, silent);
|
|
3901
|
+
if (result) return true;
|
|
3902
|
+
if (!silent) console.log(chalk.yellow('Docker 容器内重启失败,尝试本地重启...'));
|
|
3903
|
+
}
|
|
3764
3904
|
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3905
|
+
// WSL 内重启
|
|
3906
|
+
if (gwEnv === 'wsl') {
|
|
3907
|
+
const result = await restartGatewayWsl(gatewayPort, silent);
|
|
3908
|
+
if (result) return true;
|
|
3909
|
+
if (!silent) console.log(chalk.yellow('WSL 内重启失败,尝试 Windows 原生重启...'));
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
return restartGatewayNative(silent);
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
function buildDockerInnerCmds(container, verb) {
|
|
3916
|
+
const cmds = [];
|
|
3917
|
+
if (container.cli === 'node') {
|
|
3918
|
+
cmds.push(`node ${container.cliPath} ${verb}`);
|
|
3919
|
+
} else {
|
|
3920
|
+
if (container.cliPath) cmds.push(`${container.cliPath} ${verb}`);
|
|
3921
|
+
cmds.push(`${container.cli} ${verb}`);
|
|
3922
|
+
}
|
|
3923
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3924
|
+
cmds.push(`${name} ${verb}`);
|
|
3925
|
+
}
|
|
3926
|
+
return [...new Set(cmds)].filter(Boolean);
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
async function restartGatewayDocker(gatewayPort, silent = false) {
|
|
3930
|
+
const container = await selectDockerContainer();
|
|
3931
|
+
if (!container) return false;
|
|
3932
|
+
if (!silent) console.log(chalk.gray(` [检测] Gateway 运行在 Docker 容器: ${container.name} (${container.image})`));
|
|
3933
|
+
|
|
3934
|
+
const cid = container.id;
|
|
3935
|
+
const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
|
|
3936
|
+
|
|
3937
|
+
// 策略 A:exec restart 命令(短超时),然后端口探测
|
|
3938
|
+
for (const cmd of buildDockerInnerCmds(container, 'gateway restart')) {
|
|
3939
|
+
for (const shell of shellVariants) {
|
|
3940
|
+
safeExec(dockerCmd(`exec ${cid} ${shell} "${cmd}"`), { timeout: 8000 });
|
|
3941
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
|
|
3942
|
+
if (!silent) console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
|
|
3943
|
+
return true;
|
|
3944
|
+
}
|
|
3771
3945
|
}
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
// 策略 B:杀容器内旧进程 → spawn 启动新 Gateway → 端口探测
|
|
3949
|
+
if (!silent) console.log(chalk.gray(' Docker 内常规重启未生效,尝试杀进程后重新启动...'));
|
|
3950
|
+
try {
|
|
3772
3951
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3773
|
-
|
|
3952
|
+
safeExec(dockerCmd(`exec ${cid} sh -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
|
|
3774
3953
|
}
|
|
3954
|
+
safeExec(dockerCmd(`exec ${cid} sh -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
|
|
3955
|
+
} catch { /* ignore */ }
|
|
3956
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3775
3957
|
|
|
3776
|
-
|
|
3777
|
-
const
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
|
|
3783
|
-
for (const shell of shellVariants) {
|
|
3784
|
-
allAttempts.push(dockerCmd(`exec ${container.id} ${shell} "${cmd}"`));
|
|
3958
|
+
for (const cmd of buildDockerInnerCmds(container, 'gateway')) {
|
|
3959
|
+
for (const shell of shellVariants) {
|
|
3960
|
+
if (spawnDetachedInDocker(cid, cmd, shell)) {
|
|
3961
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
3962
|
+
if (!silent) console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
|
|
3963
|
+
return true;
|
|
3785
3964
|
}
|
|
3786
3965
|
}
|
|
3787
|
-
|
|
3788
|
-
const tryNext = () => {
|
|
3789
|
-
if (tried >= allAttempts.length) {
|
|
3790
|
-
console.log(chalk.yellow('Docker 容器内 Gateway 重启失败,尝试本地重启...'));
|
|
3791
|
-
restartGatewayNative().then(resolve);
|
|
3792
|
-
return;
|
|
3793
|
-
}
|
|
3794
|
-
const fullCmd = allAttempts[tried++];
|
|
3795
|
-
exec(fullCmd, { timeout: 30000 }, (error) => {
|
|
3796
|
-
if (error) {
|
|
3797
|
-
tryNext();
|
|
3798
|
-
} else {
|
|
3799
|
-
console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
|
|
3800
|
-
resolve(true);
|
|
3801
|
-
}
|
|
3802
|
-
});
|
|
3803
|
-
};
|
|
3804
|
-
tryNext();
|
|
3805
|
-
});
|
|
3966
|
+
}
|
|
3806
3967
|
}
|
|
3807
3968
|
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
|
|
3811
|
-
const wslCli = getWslCliBinary();
|
|
3812
|
-
return new Promise((resolve) => {
|
|
3813
|
-
const wslCmds = wslCli
|
|
3814
|
-
? [`wsl -- bash -lc "${wslCli} gateway restart"`]
|
|
3815
|
-
: [
|
|
3816
|
-
'wsl -- bash -lc "openclaw gateway restart"',
|
|
3817
|
-
'wsl -- bash -lc "clawdbot gateway restart"',
|
|
3818
|
-
'wsl -- bash -lc "moltbot gateway restart"',
|
|
3819
|
-
];
|
|
3820
|
-
let tried = 0;
|
|
3821
|
-
const tryNext = () => {
|
|
3822
|
-
if (tried >= wslCmds.length) {
|
|
3823
|
-
console.log(chalk.yellow('WSL 内 Gateway 重启失败,尝试 Windows 原生重启...'));
|
|
3824
|
-
restartGatewayNative().then(resolve);
|
|
3825
|
-
return;
|
|
3826
|
-
}
|
|
3827
|
-
const cmd = wslCmds[tried++];
|
|
3828
|
-
exec(cmd, { timeout: 30000 }, (error, stdout, stderr) => {
|
|
3829
|
-
const errStr = (stderr || '').toLowerCase();
|
|
3830
|
-
const wslGarbled = errStr.includes('wsl:') && errStr.includes('localhost') && errStr.includes('nat');
|
|
3969
|
+
return false;
|
|
3970
|
+
}
|
|
3831
3971
|
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3972
|
+
async function restartGatewayWsl(gatewayPort, silent = false) {
|
|
3973
|
+
if (!silent) console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
|
|
3974
|
+
const wslCli = getWslCliBinary();
|
|
3975
|
+
const names = ['openclaw', 'clawdbot', 'moltbot'];
|
|
3976
|
+
|
|
3977
|
+
// 构建 WSL 重启命令
|
|
3978
|
+
const wslRestartCmds = [];
|
|
3979
|
+
if (wslCli) wslRestartCmds.push(`wsl -- bash -lc "${wslCli} gateway restart"`);
|
|
3980
|
+
for (const name of names) wslRestartCmds.push(`wsl -- bash -lc "${name} gateway restart"`);
|
|
3981
|
+
|
|
3982
|
+
// 策略 A:exec restart(短超时)+ 端口探测
|
|
3983
|
+
for (const cmd of wslRestartCmds) {
|
|
3984
|
+
safeExec(cmd, { timeout: 8000 });
|
|
3985
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
|
|
3986
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (WSL)'));
|
|
3987
|
+
return true;
|
|
3988
|
+
}
|
|
3842
3989
|
}
|
|
3843
3990
|
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3991
|
+
// 策略 B:杀 WSL 内旧进程 → spawn 启动 → 端口探测
|
|
3992
|
+
if (!silent) console.log(chalk.gray(' WSL 内常规重启未生效,尝试杀进程后重新启动...'));
|
|
3993
|
+
try {
|
|
3994
|
+
for (const name of names) {
|
|
3995
|
+
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
3996
|
+
}
|
|
3997
|
+
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
3998
|
+
} catch { /* ignore */ }
|
|
3999
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3850
4000
|
|
|
3851
|
-
|
|
3852
|
-
if (
|
|
3853
|
-
|
|
3854
|
-
const cName = _selectedDockerContainer.name;
|
|
3855
|
-
console.log(chalk.gray(` [Docker] 在容器 ${cName} 内强制重启...`));
|
|
4001
|
+
const wslStartCmds = [];
|
|
4002
|
+
if (wslCli) wslStartCmds.push(`wsl -- bash -lc "${wslCli} gateway"`);
|
|
4003
|
+
for (const name of names) wslStartCmds.push(`wsl -- bash -lc "${name} gateway"`);
|
|
3856
4004
|
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
4005
|
+
for (const cmd of wslStartCmds) {
|
|
4006
|
+
if (spawnDetached(cmd, process.env)) {
|
|
4007
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
4008
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (WSL)'));
|
|
4009
|
+
return true;
|
|
3861
4010
|
}
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
} catch { /* ignore */ }
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
3865
4013
|
|
|
3866
|
-
|
|
4014
|
+
return false;
|
|
4015
|
+
}
|
|
3867
4016
|
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
4017
|
+
async function restartGatewayNative(silent = false) {
|
|
4018
|
+
const { cliBinary: resolved, nodeMajor } = getCliMeta();
|
|
4019
|
+
const nodeInfo = findCompatibleNode(nodeMajor);
|
|
4020
|
+
const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null) };
|
|
4021
|
+
const useNode = resolved && nodeInfo && isNodeShebang(resolved);
|
|
4022
|
+
|
|
4023
|
+
const configPaths = getConfigPath();
|
|
4024
|
+
const gwConfig = readConfig(configPaths.openclawConfig);
|
|
4025
|
+
const gatewayPort = gwConfig?.gateway?.port || 18789;
|
|
4026
|
+
|
|
4027
|
+
// 策略 A:尝试 exec "gateway restart"(短超时),然后端口探测
|
|
4028
|
+
const restartCmds = buildGatewayCommands(resolved, nodeInfo, useNode, 'restart');
|
|
4029
|
+
if (restartCmds.length > 0) {
|
|
4030
|
+
// 只尝试第一条(最精确的路径),避免逐条超时累积
|
|
4031
|
+
safeExec(restartCmds[0], { timeout: 8000, env });
|
|
4032
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
|
|
4033
|
+
if (!silent) console.log(chalk.green(`✅ Gateway 已重启`));
|
|
4034
|
+
return true;
|
|
3875
4035
|
}
|
|
3876
|
-
|
|
3877
|
-
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
// 策略 B:杀旧进程 → spawn 后台启动新 Gateway → 端口探测
|
|
4039
|
+
if (!silent) console.log(chalk.gray(' 常规重启未生效,尝试杀进程后重新启动...'));
|
|
4040
|
+
await killGatewayProcesses(gatewayPort);
|
|
4041
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
4042
|
+
|
|
4043
|
+
const startCmds = buildGatewayCommands(resolved, nodeInfo, useNode, 'start');
|
|
4044
|
+
for (const cmd of startCmds) {
|
|
4045
|
+
if (spawnDetached(cmd, env)) {
|
|
4046
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 10000)) {
|
|
4047
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启'));
|
|
4048
|
+
return true;
|
|
4049
|
+
}
|
|
4050
|
+
break; // spawn 成功但端口未通,不再逐条重试
|
|
3878
4051
|
}
|
|
4052
|
+
}
|
|
3879
4053
|
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
4054
|
+
// 策略 C:用 login shell 启动(加载 nvm/fnm 等 PATH)
|
|
4055
|
+
if (process.platform !== 'win32') {
|
|
4056
|
+
if (!silent) console.log(chalk.gray(' 尝试通过 login shell 启动...'));
|
|
4057
|
+
let launched = false;
|
|
4058
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4059
|
+
if (launched) break;
|
|
4060
|
+
for (const sh of ['/bin/zsh', '/bin/bash']) {
|
|
4061
|
+
if (!fs.existsSync(sh)) continue;
|
|
4062
|
+
if (spawnDetached(`${sh} -lc '${name} gateway'`, env)) {
|
|
4063
|
+
launched = true;
|
|
4064
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 10000)) {
|
|
4065
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (login shell)'));
|
|
3886
4066
|
return true;
|
|
3887
4067
|
}
|
|
4068
|
+
break;
|
|
3888
4069
|
}
|
|
3889
4070
|
}
|
|
3890
4071
|
}
|
|
4072
|
+
}
|
|
3891
4073
|
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
console.log(chalk.
|
|
3895
|
-
|
|
4074
|
+
// 全部失败,输出诊断
|
|
4075
|
+
if (!silent) {
|
|
4076
|
+
console.log(chalk.red(`❌ 重启失败: 找不到 openclaw/clawdbot/moltbot 命令`));
|
|
4077
|
+
console.log(chalk.gray(` 请手动运行: openclaw gateway restart`));
|
|
4078
|
+
console.log(chalk.gray(` 或: clawdbot gateway restart`));
|
|
4079
|
+
console.log(chalk.gray(` 或: moltbot gateway restart`));
|
|
4080
|
+
printGatewayDiagnostics(resolved);
|
|
4081
|
+
}
|
|
4082
|
+
return false;
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
function buildGatewayCommands(resolved, nodeInfo, useNode, action) {
|
|
4086
|
+
const verb = action === 'start' ? 'gateway' : 'gateway restart';
|
|
4087
|
+
const commands = [];
|
|
4088
|
+
|
|
4089
|
+
if (resolved) {
|
|
4090
|
+
if (useNode && nodeInfo) {
|
|
4091
|
+
commands.push(`"${nodeInfo.path}" "${resolved}" ${verb}`);
|
|
4092
|
+
} else {
|
|
4093
|
+
commands.push(`"${resolved}" ${verb}`);
|
|
4094
|
+
}
|
|
3896
4095
|
}
|
|
3897
4096
|
|
|
3898
|
-
|
|
4097
|
+
const names = ['openclaw', 'clawdbot', 'moltbot'];
|
|
4098
|
+
for (const name of names) commands.push(`${name} ${verb}`);
|
|
4099
|
+
|
|
4100
|
+
return [...new Set(commands)].filter(Boolean);
|
|
4101
|
+
}
|
|
4102
|
+
|
|
4103
|
+
async function killGatewayProcesses(gatewayPort = 18789) {
|
|
3899
4104
|
try {
|
|
3900
4105
|
if (process.platform === 'win32') {
|
|
3901
|
-
// Windows: 通过 taskkill 杀掉占用端口的 node 进程
|
|
3902
4106
|
const findPid = safeExec(`netstat -ano | findstr ":${gatewayPort}"`, { timeout: 5000 });
|
|
3903
4107
|
if (findPid.ok && findPid.output) {
|
|
3904
|
-
const
|
|
3905
|
-
for (const line of lines) {
|
|
4108
|
+
for (const line of findPid.output.split('\n').filter(l => l.includes('LISTENING'))) {
|
|
3906
4109
|
const pid = line.trim().split(/\s+/).pop();
|
|
3907
|
-
if (pid && /^\d+$/.test(pid)) {
|
|
3908
|
-
safeExec(`taskkill /F /PID ${pid}`, { timeout: 5000 });
|
|
3909
|
-
}
|
|
4110
|
+
if (pid && /^\d+$/.test(pid)) safeExec(`taskkill /F /PID ${pid}`, { timeout: 5000 });
|
|
3910
4111
|
}
|
|
3911
4112
|
}
|
|
3912
|
-
// Windows + WSL: 也清理 WSL 内的 gateway 进程
|
|
3913
4113
|
if (isWslAvailable()) {
|
|
3914
4114
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3915
4115
|
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
@@ -3917,173 +4117,36 @@ async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort
|
|
|
3917
4117
|
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
3918
4118
|
}
|
|
3919
4119
|
} else {
|
|
3920
|
-
// Linux/macOS: pkill gateway 相关进程
|
|
3921
4120
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3922
4121
|
safeExec(`pkill -f '${name}.*gateway' 2>/dev/null || true`);
|
|
3923
4122
|
}
|
|
3924
|
-
// 备用:通过端口找进程并杀掉
|
|
3925
4123
|
const lsof = safeExec(`lsof -ti :${gatewayPort} 2>/dev/null`);
|
|
3926
4124
|
if (lsof.ok && lsof.output) {
|
|
3927
4125
|
for (const pid of lsof.output.trim().split('\n').filter(Boolean)) {
|
|
3928
|
-
if (/^\d+$/.test(pid.trim())) {
|
|
3929
|
-
safeExec(`kill -9 ${pid.trim()} 2>/dev/null || true`);
|
|
3930
|
-
}
|
|
4126
|
+
if (/^\d+$/.test(pid.trim())) safeExec(`kill -9 ${pid.trim()} 2>/dev/null || true`);
|
|
3931
4127
|
}
|
|
3932
4128
|
}
|
|
3933
4129
|
}
|
|
3934
|
-
console.log(chalk.gray(' 已尝试清理旧 Gateway 进程'));
|
|
3935
4130
|
} catch { /* ignore */ }
|
|
3936
|
-
|
|
3937
|
-
// 2. 等待端口释放
|
|
3938
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3939
|
-
|
|
3940
|
-
// 3. 用已找到的 CLI 路径启动新 Gateway
|
|
3941
|
-
const startCmds = [];
|
|
3942
|
-
|
|
3943
|
-
// 优先用已解析的完整路径
|
|
3944
|
-
if (resolved) {
|
|
3945
|
-
if (useNode && nodeInfo) {
|
|
3946
|
-
startCmds.push(`"${nodeInfo.path}" "${resolved}" gateway`);
|
|
3947
|
-
} else {
|
|
3948
|
-
startCmds.push(`"${resolved}" gateway`);
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
|
|
3952
|
-
// Fallback: login shell 方式(加载 nvm 等 PATH)
|
|
3953
|
-
if (process.platform !== 'win32') {
|
|
3954
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3955
|
-
startCmds.push(`bash -lc '${name} gateway'`);
|
|
3956
|
-
}
|
|
3957
|
-
} else {
|
|
3958
|
-
// Windows: 先尝试原生命令
|
|
3959
|
-
startCmds.push('openclaw gateway', 'clawdbot gateway', 'moltbot gateway');
|
|
3960
|
-
// Windows + WSL: 也尝试通过 WSL 启动 gateway
|
|
3961
|
-
if (isWslAvailable()) {
|
|
3962
|
-
const wslCli = getWslCliBinary();
|
|
3963
|
-
if (wslCli) {
|
|
3964
|
-
startCmds.push(`wsl -- bash -lc "${wslCli} gateway"`);
|
|
3965
|
-
}
|
|
3966
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3967
|
-
startCmds.push(`wsl -- bash -lc "${name} gateway"`);
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
|
|
3972
|
-
for (const cmd of [...new Set(startCmds)].filter(Boolean)) {
|
|
3973
|
-
console.log(chalk.gray(` 尝试启动: ${cmd}`));
|
|
3974
|
-
if (spawnDetached(cmd, env)) {
|
|
3975
|
-
// 等待新 Gateway 启动
|
|
3976
|
-
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
3977
|
-
console.log(chalk.green('✅ Gateway 已强制重启成功'));
|
|
3978
|
-
console.log(chalk.gray(' 现在可以在 Web/Telegram/Discord 等渠道测试对话了'));
|
|
3979
|
-
return true;
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
|
|
3984
|
-
console.log(chalk.red('❌ 强制重启也失败了'));
|
|
3985
|
-
console.log(chalk.gray(' 请手动重启 Gateway:'));
|
|
3986
|
-
console.log(chalk.gray(' 1. 关闭旧进程: pkill -f "gateway" 或 taskkill'));
|
|
3987
|
-
console.log(chalk.gray(' 2. 启动新进程: openclaw gateway / clawdbot gateway / moltbot gateway'));
|
|
3988
|
-
return false;
|
|
3989
4131
|
}
|
|
3990
4132
|
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
const
|
|
3994
|
-
|
|
3995
|
-
const
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
const commands = resolved
|
|
3999
|
-
? [
|
|
4000
|
-
useNode ? `"${nodeInfo.path}" "${resolved}" gateway restart` : `"${resolved}" gateway restart`
|
|
4001
|
-
]
|
|
4002
|
-
: [
|
|
4003
|
-
'openclaw gateway restart',
|
|
4004
|
-
'clawdbot gateway restart',
|
|
4005
|
-
'moltbot gateway restart',
|
|
4006
|
-
'npx openclaw gateway restart',
|
|
4007
|
-
'npx clawdbot gateway restart',
|
|
4008
|
-
'npx moltbot gateway restart'
|
|
4009
|
-
];
|
|
4010
|
-
|
|
4011
|
-
// Windows + WSL: 追加 WSL 命令作为额外回退
|
|
4012
|
-
if (process.platform === 'win32' && isWslAvailable()) {
|
|
4013
|
-
const wslCli = getWslCliBinary();
|
|
4014
|
-
if (wslCli) {
|
|
4015
|
-
commands.push(`wsl -- bash -lc "${wslCli} gateway restart"`);
|
|
4016
|
-
}
|
|
4017
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4018
|
-
commands.push(`wsl -- bash -lc "${name} gateway restart"`);
|
|
4019
|
-
}
|
|
4133
|
+
function printGatewayDiagnostics(resolved) {
|
|
4134
|
+
console.log(chalk.gray(`\n [诊断] resolveCliBinary = ${resolved || 'null'}`));
|
|
4135
|
+
const npmPrefix = safeExec('npm prefix -g');
|
|
4136
|
+
if (npmPrefix.ok) console.log(chalk.gray(` [诊断] npm prefix -g = ${npmPrefix.output}`));
|
|
4137
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4138
|
+
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`);
|
|
4139
|
+
if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
|
|
4020
4140
|
}
|
|
4021
|
-
|
|
4022
|
-
// Docker: 追加 Docker 容器命令作为额外回退
|
|
4023
4141
|
if (isDockerAvailable()) {
|
|
4024
|
-
const
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} gateway restart"`));
|
|
4030
|
-
}
|
|
4031
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4032
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} gateway restart"`));
|
|
4142
|
+
const dContainers = findOpenclawDockerContainers();
|
|
4143
|
+
if (dContainers.length > 0) {
|
|
4144
|
+
console.log(chalk.gray(` [诊断] Docker 容器 (含 openclaw/clawdbot/moltbot):`));
|
|
4145
|
+
for (const c of dContainers) {
|
|
4146
|
+
console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
|
|
4033
4147
|
}
|
|
4034
4148
|
}
|
|
4035
4149
|
}
|
|
4036
|
-
|
|
4037
|
-
return new Promise((resolve) => {
|
|
4038
|
-
let tried = 0;
|
|
4039
|
-
|
|
4040
|
-
const tryNext = () => {
|
|
4041
|
-
if (tried >= commands.length) {
|
|
4042
|
-
console.log(chalk.red(`❌ 重启失败: 找不到 openclaw/clawdbot/moltbot 命令`));
|
|
4043
|
-
console.log(chalk.gray(` 请手动运行: openclaw gateway restart`));
|
|
4044
|
-
console.log(chalk.gray(` 或: clawdbot gateway restart`));
|
|
4045
|
-
console.log(chalk.gray(` 或: moltbot gateway restart`));
|
|
4046
|
-
// 诊断信息
|
|
4047
|
-
console.log(chalk.gray(`\n [诊断] resolveCliBinary = ${resolved || 'null'}`));
|
|
4048
|
-
const npmPrefix = safeExec('npm prefix -g');
|
|
4049
|
-
if (npmPrefix.ok) console.log(chalk.gray(` [诊断] npm prefix -g = ${npmPrefix.output}`));
|
|
4050
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4051
|
-
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`);
|
|
4052
|
-
if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
|
|
4053
|
-
}
|
|
4054
|
-
// Docker 诊断
|
|
4055
|
-
if (isDockerAvailable()) {
|
|
4056
|
-
const dContainers = findOpenclawDockerContainers();
|
|
4057
|
-
if (dContainers.length > 0) {
|
|
4058
|
-
console.log(chalk.gray(` [诊断] Docker 容器 (含 openclaw/clawdbot/moltbot):`));
|
|
4059
|
-
for (const c of dContainers) {
|
|
4060
|
-
console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
|
|
4061
|
-
}
|
|
4062
|
-
} else {
|
|
4063
|
-
console.log(chalk.gray(` [诊断] Docker 可用,但未找到含 openclaw/clawdbot/moltbot 的容器`));
|
|
4064
|
-
}
|
|
4065
|
-
}
|
|
4066
|
-
// 尝试强制重启
|
|
4067
|
-
forceRestartGateway(resolved, nodeInfo, useNode, env).then(resolve);
|
|
4068
|
-
return;
|
|
4069
|
-
}
|
|
4070
|
-
|
|
4071
|
-
const cmd = commands[tried];
|
|
4072
|
-
tried++;
|
|
4073
|
-
|
|
4074
|
-
exec(cmd, { timeout: 30000, env }, (error) => {
|
|
4075
|
-
if (error) {
|
|
4076
|
-
tryNext();
|
|
4077
|
-
} else {
|
|
4078
|
-
console.log(chalk.green(`✅ Gateway 已重启`));
|
|
4079
|
-
console.log(chalk.gray(` 现在可以在 Web/Telegram/Discord 等渠道测试对话了`));
|
|
4080
|
-
resolve(true);
|
|
4081
|
-
}
|
|
4082
|
-
});
|
|
4083
|
-
};
|
|
4084
|
-
|
|
4085
|
-
tryNext();
|
|
4086
|
-
});
|
|
4087
4150
|
}
|
|
4088
4151
|
|
|
4089
4152
|
// Gateway API 测试 - 通过本地 Gateway 端口测试
|
package/install.ps1
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# maxapi 一键环境安装脚本(Windows PowerShell)
|
|
3
3
|
# 用法(Node.js 不存在时,先装 Node.js 再 npx):
|
|
4
4
|
# .\install.ps1
|
|
5
|
-
# $env:PACKAGE='
|
|
5
|
+
# $env:PACKAGE='maxai'; .\install.ps1
|
|
6
6
|
# ============================================================
|
|
7
7
|
|
|
8
8
|
$ErrorActionPreference = "Stop"
|
package/install.sh
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
# maxapi 一键环境安装脚本(macOS / Linux / WSL)
|
|
4
4
|
# 用法(Node.js 不存在时,先装 Node.js 再 npx):
|
|
5
5
|
# bash install.sh
|
|
6
|
-
# bash install.sh llmaxapi
|
|
7
6
|
# bash install.sh --no-run
|
|
8
7
|
# ============================================================
|
|
9
8
|
set -euo pipefail
|
|
@@ -16,7 +15,7 @@ MIN_NODE_MAJOR=18
|
|
|
16
15
|
for arg in "$@"; do
|
|
17
16
|
case "$arg" in
|
|
18
17
|
--no-run) NO_RUN=true ;;
|
|
19
|
-
yymaxapi
|
|
18
|
+
yymaxapi) PACKAGE="$arg" ;;
|
|
20
19
|
esac
|
|
21
20
|
done
|
|
22
21
|
|