yymaxapi 1.0.48 → 1.0.49
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 +365 -287
- package/install.ps1 +1 -1
- package/install.sh +1 -2
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -1659,6 +1659,7 @@ function ensureGatewaySettings(config) {
|
|
|
1659
1659
|
if (isLocal && gateway.remote.token !== gateway.auth.token) {
|
|
1660
1660
|
gateway.remote.token = gateway.auth.token;
|
|
1661
1661
|
}
|
|
1662
|
+
|
|
1662
1663
|
}
|
|
1663
1664
|
|
|
1664
1665
|
function isPortOpen(port, host = '127.0.0.1', timeoutMs = 800) {
|
|
@@ -2662,10 +2663,21 @@ async function autoActivate(paths, args = {}) {
|
|
|
2662
2663
|
|
|
2663
2664
|
// 增量写入: 只添加/更新云翼 provider,保留其他已有配置
|
|
2664
2665
|
|
|
2665
|
-
// Claude 侧
|
|
2666
|
+
// Claude 侧 — 让用户选模型
|
|
2666
2667
|
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
2667
|
-
|
|
2668
|
-
|
|
2668
|
+
let claudeModelId = (args['claude-model'] || '').toString().trim();
|
|
2669
|
+
if (!claudeModelId && CLAUDE_MODELS.length > 1) {
|
|
2670
|
+
const { picked } = await inquirer.prompt([{
|
|
2671
|
+
type: 'list',
|
|
2672
|
+
name: 'picked',
|
|
2673
|
+
message: '选择 Claude 模型:',
|
|
2674
|
+
choices: CLAUDE_MODELS.map(m => ({ name: m.name, value: m.id })),
|
|
2675
|
+
default: CLAUDE_MODELS[0].id
|
|
2676
|
+
}]);
|
|
2677
|
+
claudeModelId = picked;
|
|
2678
|
+
}
|
|
2679
|
+
if (!claudeModelId) claudeModelId = CLAUDE_MODELS[0]?.id || 'claude-sonnet-4-6';
|
|
2680
|
+
const claudeModel = CLAUDE_MODELS.find(m => m.id === claudeModelId) || { id: claudeModelId, name: claudeModelId };
|
|
2669
2681
|
const claudeModelKey = `${claudeProviderName}/${claudeModelId}`;
|
|
2670
2682
|
|
|
2671
2683
|
config.models.providers[claudeProviderName] = {
|
|
@@ -2685,10 +2697,21 @@ async function autoActivate(paths, args = {}) {
|
|
|
2685
2697
|
config.auth.profiles[`${claudeProviderName}:default`] = { provider: claudeProviderName, mode: 'api_key' };
|
|
2686
2698
|
config.agents.defaults.models[claudeModelKey] = { alias: claudeProviderName };
|
|
2687
2699
|
|
|
2688
|
-
// Codex 侧
|
|
2700
|
+
// Codex 侧 — 让用户选模型
|
|
2689
2701
|
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
2690
|
-
|
|
2691
|
-
|
|
2702
|
+
let codexModelId = (args['codex-model'] || '').toString().trim();
|
|
2703
|
+
if (!codexModelId && CODEX_MODELS.length > 1) {
|
|
2704
|
+
const { pickedCodex } = await inquirer.prompt([{
|
|
2705
|
+
type: 'list',
|
|
2706
|
+
name: 'pickedCodex',
|
|
2707
|
+
message: '选择 Codex 模型:',
|
|
2708
|
+
choices: CODEX_MODELS.map(m => ({ name: m.name, value: m.id })),
|
|
2709
|
+
default: CODEX_MODELS[0].id
|
|
2710
|
+
}]);
|
|
2711
|
+
codexModelId = pickedCodex;
|
|
2712
|
+
}
|
|
2713
|
+
if (!codexModelId) codexModelId = CODEX_MODELS[0]?.id || 'gpt-5.3-codex';
|
|
2714
|
+
const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: codexModelId };
|
|
2692
2715
|
const codexModelKey = `${codexProviderName}/${codexModelId}`;
|
|
2693
2716
|
|
|
2694
2717
|
config.models.providers[codexProviderName] = {
|
|
@@ -3155,6 +3178,7 @@ async function main() {
|
|
|
3155
3178
|
{ name: ' 配置 Opencode', value: 'activate_opencode' },
|
|
3156
3179
|
new inquirer.Separator(' -- 工具 --'),
|
|
3157
3180
|
{ name: ' 切换模型', value: 'switch_model' },
|
|
3181
|
+
{ name: ` 权限管理${getToolsProfileTag(paths)}`, value: 'tools_profile' },
|
|
3158
3182
|
{ name: ' 测试连接', value: 'test_connection' },
|
|
3159
3183
|
{ name: ' 查看配置', value: 'view_config' },
|
|
3160
3184
|
{ name: ' 恢复默认', value: 'restore' },
|
|
@@ -3183,6 +3207,9 @@ async function main() {
|
|
|
3183
3207
|
case 'switch_model':
|
|
3184
3208
|
await switchModel(paths);
|
|
3185
3209
|
break;
|
|
3210
|
+
case 'tools_profile':
|
|
3211
|
+
await manageToolsProfile(paths);
|
|
3212
|
+
break;
|
|
3186
3213
|
case 'view_config':
|
|
3187
3214
|
await viewConfig(paths);
|
|
3188
3215
|
break;
|
|
@@ -3334,15 +3361,18 @@ async function selectNode(paths, type) {
|
|
|
3334
3361
|
}]
|
|
3335
3362
|
};
|
|
3336
3363
|
|
|
3337
|
-
//
|
|
3364
|
+
// 注册模型并设为主模型
|
|
3338
3365
|
const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
|
|
3339
3366
|
config.agents.defaults.models[modelKey] = { alias: apiConfig.providerName };
|
|
3367
|
+
config.agents.defaults.model.primary = modelKey;
|
|
3368
|
+
config.agents.defaults.model.fallbacks = (config.agents.defaults.model.fallbacks || []).filter(k => k !== modelKey);
|
|
3340
3369
|
|
|
3370
|
+
ensureGatewaySettings(config);
|
|
3341
3371
|
writeConfigWithSync(paths, config);
|
|
3342
3372
|
|
|
3343
3373
|
console.log(chalk.green(`\n✅ ${typeLabel} 节点配置完成!`));
|
|
3344
3374
|
console.log(chalk.cyan(` 节点: ${selectedEndpoint.name} (${selectedEndpoint.url})`));
|
|
3345
|
-
console.log(chalk.gray(` 模型: ${modelConfig.name}`));
|
|
3375
|
+
console.log(chalk.gray(` 模型: ${modelConfig.name} (主模型)`));
|
|
3346
3376
|
console.log(chalk.gray(` API Key: ${oldApiKey ? '已设置' : '未设置'}`));
|
|
3347
3377
|
}
|
|
3348
3378
|
|
|
@@ -3475,15 +3505,39 @@ async function switchModel(paths) {
|
|
|
3475
3505
|
}
|
|
3476
3506
|
|
|
3477
3507
|
const selectedProvider = selected.split('/')[0];
|
|
3508
|
+
const selectedModelId = selected.split('/')[1];
|
|
3478
3509
|
config.agents.defaults.model.primary = selected;
|
|
3479
3510
|
if (!config.agents.defaults.models[selected]) {
|
|
3480
3511
|
config.agents.defaults.models[selected] = { alias: selectedProvider };
|
|
3481
3512
|
}
|
|
3513
|
+
|
|
3514
|
+
// 同步更新 provider.models,确保 primary 指向的模型在列表中
|
|
3515
|
+
const providerConfig = providers[selectedProvider];
|
|
3516
|
+
if (providerConfig && selectedModelId) {
|
|
3517
|
+
const existingModel = (providerConfig.models || []).find(m => m.id === selectedModelId);
|
|
3518
|
+
if (!existingModel) {
|
|
3519
|
+
const allModels = [...CLAUDE_MODELS, ...CODEX_MODELS];
|
|
3520
|
+
const knownModel = allModels.find(m => m.id === selectedModelId);
|
|
3521
|
+
const apiType = providerConfig.api || '';
|
|
3522
|
+
const isAnthropic = apiType.startsWith('anthropic');
|
|
3523
|
+
const defaultCtx = isAnthropic ? (API_CONFIG.claude?.contextWindow || 200000) : (API_CONFIG.codex?.contextWindow || 200000);
|
|
3524
|
+
const defaultMax = isAnthropic ? (API_CONFIG.claude?.maxTokens || 8192) : (API_CONFIG.codex?.maxTokens || 8192);
|
|
3525
|
+
providerConfig.models = [{
|
|
3526
|
+
id: selectedModelId,
|
|
3527
|
+
name: knownModel ? knownModel.name : selectedModelId,
|
|
3528
|
+
contextWindow: defaultCtx,
|
|
3529
|
+
maxTokens: defaultMax
|
|
3530
|
+
}];
|
|
3531
|
+
} else {
|
|
3532
|
+
providerConfig.models = [existingModel];
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3482
3536
|
config.agents.defaults.model.fallbacks = (config.agents.defaults.model.fallbacks || []).filter(k => k !== selected);
|
|
3483
|
-
// 把旧的 primary 加入 fallbacks (如果存在且不同)
|
|
3484
3537
|
if (primary && primary !== selected && !config.agents.defaults.model.fallbacks.includes(primary)) {
|
|
3485
3538
|
config.agents.defaults.model.fallbacks.push(primary);
|
|
3486
3539
|
}
|
|
3540
|
+
ensureGatewaySettings(config);
|
|
3487
3541
|
writeConfigWithSync(paths, config);
|
|
3488
3542
|
|
|
3489
3543
|
const selectedProviderConfig = providers[selectedProvider];
|
|
@@ -3493,15 +3547,96 @@ async function switchModel(paths) {
|
|
|
3493
3547
|
if (selectedProviderConfig) {
|
|
3494
3548
|
console.log(chalk.gray(` 节点: ${selectedProviderConfig.baseUrl}`));
|
|
3495
3549
|
}
|
|
3496
|
-
console.log(chalk.yellow('\n💡 切换后建议重启 Gateway: openclaw gateway restart'));
|
|
3497
3550
|
|
|
3498
3551
|
const gwPort = config.gateway?.port || 18789;
|
|
3499
3552
|
const gwToken = config.gateway?.auth?.token;
|
|
3553
|
+
|
|
3554
|
+
// 自动重启 Gateway 使切换立即生效
|
|
3555
|
+
if (await isPortOpen(gwPort)) {
|
|
3556
|
+
const gwSpinner = ora({ text: '重启 Gateway 使模型切换生效...', spinner: 'dots' }).start();
|
|
3557
|
+
const ok = await restartGateway({ silent: true });
|
|
3558
|
+
if (ok) {
|
|
3559
|
+
gwSpinner.succeed('Gateway 已重启,模型切换已生效');
|
|
3560
|
+
} else {
|
|
3561
|
+
gwSpinner.fail('Gateway 重启失败,请手动重启: openclaw gateway restart');
|
|
3562
|
+
}
|
|
3563
|
+
} else {
|
|
3564
|
+
console.log(chalk.yellow('\n⚠️ Gateway 未运行,模型切换将在下次启动 Gateway 时生效'));
|
|
3565
|
+
console.log(chalk.gray(' 启动 Gateway: openclaw gateway'));
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3500
3568
|
if (gwToken) {
|
|
3501
3569
|
console.log(chalk.green(`\n🌐 Web Dashboard:`));
|
|
3502
3570
|
console.log(chalk.cyan(` http://localhost:${gwPort}/?token=${gwToken}`));
|
|
3503
3571
|
}
|
|
3504
3572
|
}
|
|
3573
|
+
// ============ 权限管理 (tools.profile) ============
|
|
3574
|
+
const TOOLS_PROFILES = [
|
|
3575
|
+
{ id: 'full', name: '完整模式', desc: '编码 + 系统工具 + 文件操作(Claude Code / Codex 必需)' },
|
|
3576
|
+
{ id: 'messaging', name: '聊天模式', desc: '仅对话,无文件/系统工具(安全但功能受限)' },
|
|
3577
|
+
];
|
|
3578
|
+
|
|
3579
|
+
function getToolsProfileTag(paths) {
|
|
3580
|
+
try {
|
|
3581
|
+
const config = readConfig(paths.openclawConfig);
|
|
3582
|
+
const profile = config?.tools?.profile || 'messaging';
|
|
3583
|
+
if (profile === 'full') return chalk.green(' [完整]');
|
|
3584
|
+
return chalk.yellow(' [仅聊天]');
|
|
3585
|
+
} catch {
|
|
3586
|
+
return '';
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
async function manageToolsProfile(paths) {
|
|
3591
|
+
console.log(chalk.cyan('🔐 权限管理\n'));
|
|
3592
|
+
console.log(chalk.gray('OpenClaw 3.2+ 新增 tools.profile 控制:'));
|
|
3593
|
+
console.log(chalk.gray(' full = 完整编码能力(读写文件、执行命令、系统操作)'));
|
|
3594
|
+
console.log(chalk.gray(' messaging = 仅聊天(无法操作文件和系统,适合纯对话场景)'));
|
|
3595
|
+
console.log(chalk.yellow('\n⚠️ 使用 Claude Code / Codex 编码,必须选择「完整模式」\n'));
|
|
3596
|
+
|
|
3597
|
+
const config = readConfig(paths.openclawConfig) || {};
|
|
3598
|
+
const current = config?.tools?.profile || 'messaging';
|
|
3599
|
+
const currentLabel = current === 'full' ? '完整模式' : current === 'messaging' ? '聊天模式' : current;
|
|
3600
|
+
console.log(chalk.gray(`当前: ${currentLabel}\n`));
|
|
3601
|
+
|
|
3602
|
+
const { selected } = await inquirer.prompt([{
|
|
3603
|
+
type: 'list',
|
|
3604
|
+
name: 'selected',
|
|
3605
|
+
message: '选择权限模式:',
|
|
3606
|
+
choices: TOOLS_PROFILES.map(p => ({
|
|
3607
|
+
name: p.id === current ? `${p.name} — ${p.desc} (当前)` : `${p.name} — ${p.desc}`,
|
|
3608
|
+
value: p.id,
|
|
3609
|
+
})),
|
|
3610
|
+
default: current,
|
|
3611
|
+
}]);
|
|
3612
|
+
|
|
3613
|
+
if (selected === current) {
|
|
3614
|
+
console.log(chalk.gray('\n权限模式未变更'));
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
if (!config.tools) config.tools = {};
|
|
3619
|
+
config.tools.profile = selected;
|
|
3620
|
+
ensureGatewaySettings(config);
|
|
3621
|
+
writeConfigWithSync(paths, config);
|
|
3622
|
+
|
|
3623
|
+
const label = TOOLS_PROFILES.find(p => p.id === selected)?.name || selected;
|
|
3624
|
+
console.log(chalk.green(`\n✅ 已切换到: ${label}`));
|
|
3625
|
+
|
|
3626
|
+
const gwPort = config.gateway?.port || 18789;
|
|
3627
|
+
if (await isPortOpen(gwPort)) {
|
|
3628
|
+
const gwSpinner = ora({ text: '重启 Gateway 使权限变更生效...', spinner: 'dots' }).start();
|
|
3629
|
+
const ok = await restartGateway({ silent: true });
|
|
3630
|
+
if (ok) {
|
|
3631
|
+
gwSpinner.succeed('Gateway 已重启,权限变更已生效');
|
|
3632
|
+
} else {
|
|
3633
|
+
gwSpinner.fail('Gateway 重启失败,请手动重启: openclaw gateway restart');
|
|
3634
|
+
}
|
|
3635
|
+
} else {
|
|
3636
|
+
console.log(chalk.gray(' Gateway 未运行,变更将在下次启动时生效'));
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3505
3640
|
// ============ 测试连接 ============
|
|
3506
3641
|
async function testConnection(paths, args = {}) {
|
|
3507
3642
|
console.log(chalk.cyan('🧪 测试 OpenClaw Gateway 连接\n'));
|
|
@@ -3584,11 +3719,8 @@ async function testConnection(paths, args = {}) {
|
|
|
3584
3719
|
cleanupAgentProcesses();
|
|
3585
3720
|
|
|
3586
3721
|
// 步骤1: 先重启 Gateway 使配置生效
|
|
3587
|
-
|
|
3588
|
-
const restartOk = await restartGateway();
|
|
3589
|
-
|
|
3590
|
-
// 等待 Gateway 启动
|
|
3591
|
-
const gwSpinner = ora({ text: '等待 Gateway 启动...', spinner: 'dots' }).start();
|
|
3722
|
+
const gwSpinner = ora({ text: '步骤 1/2: 重启 Gateway 使配置生效...', spinner: 'dots' }).start();
|
|
3723
|
+
const restartOk = await restartGateway({ silent: true });
|
|
3592
3724
|
|
|
3593
3725
|
let gatewayRunning = false;
|
|
3594
3726
|
|
|
@@ -3748,342 +3880,288 @@ async function testConnection(paths, args = {}) {
|
|
|
3748
3880
|
}
|
|
3749
3881
|
|
|
3750
3882
|
// ============ 重启 Gateway ============
|
|
3751
|
-
async function restartGateway() {
|
|
3752
|
-
console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
|
|
3883
|
+
async function restartGateway({ silent = false } = {}) {
|
|
3884
|
+
if (!silent) console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
|
|
3753
3885
|
|
|
3754
3886
|
const gwEnv = detectGatewayEnv();
|
|
3887
|
+
const configPaths = getConfigPath();
|
|
3888
|
+
const gwConfig = readConfig(configPaths.openclawConfig);
|
|
3889
|
+
const gatewayPort = gwConfig?.gateway?.port || 18789;
|
|
3755
3890
|
|
|
3756
|
-
//
|
|
3891
|
+
// Docker 容器内重启
|
|
3757
3892
|
if (gwEnv === 'docker') {
|
|
3758
|
-
const
|
|
3759
|
-
if (
|
|
3760
|
-
|
|
3761
|
-
return restartGatewayNative();
|
|
3762
|
-
}
|
|
3763
|
-
console.log(chalk.gray(` [检测] Gateway 运行在 Docker 容器: ${container.name} (${container.image})`));
|
|
3764
|
-
|
|
3765
|
-
const dockerCmds = [];
|
|
3766
|
-
if (container.cli === 'node') {
|
|
3767
|
-
dockerCmds.push(`node ${container.cliPath} gateway restart`);
|
|
3768
|
-
} else {
|
|
3769
|
-
if (container.cliPath) dockerCmds.push(`${container.cliPath} gateway restart`);
|
|
3770
|
-
dockerCmds.push(`${container.cli} gateway restart`);
|
|
3771
|
-
}
|
|
3772
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3773
|
-
dockerCmds.push(`${name} gateway restart`);
|
|
3774
|
-
}
|
|
3775
|
-
|
|
3776
|
-
// 每个命令尝试多种 shell(sh -c / bash -lc / bash -c)
|
|
3777
|
-
const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
|
|
3778
|
-
|
|
3779
|
-
return new Promise((resolve) => {
|
|
3780
|
-
// 展开为 [cmd1+sh, cmd1+bash-lc, cmd1+bash-c, cmd2+sh, ...]
|
|
3781
|
-
const allAttempts = [];
|
|
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}"`));
|
|
3785
|
-
}
|
|
3786
|
-
}
|
|
3787
|
-
let tried = 0;
|
|
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
|
-
});
|
|
3893
|
+
const result = await restartGatewayDocker(gatewayPort, silent);
|
|
3894
|
+
if (result) return true;
|
|
3895
|
+
if (!silent) console.log(chalk.yellow('Docker 容器内重启失败,尝试本地重启...'));
|
|
3806
3896
|
}
|
|
3807
3897
|
|
|
3808
|
-
//
|
|
3898
|
+
// WSL 内重启
|
|
3809
3899
|
if (gwEnv === 'wsl') {
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
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');
|
|
3831
|
-
|
|
3832
|
-
if (error || errStr.includes('/usr/bin/env:') || wslGarbled) {
|
|
3833
|
-
tryNext();
|
|
3834
|
-
} else {
|
|
3835
|
-
console.log(chalk.green('Gateway 已重启 (WSL)'));
|
|
3836
|
-
resolve(true);
|
|
3837
|
-
}
|
|
3838
|
-
});
|
|
3839
|
-
};
|
|
3840
|
-
tryNext();
|
|
3841
|
-
});
|
|
3900
|
+
const result = await restartGatewayWsl(gatewayPort, silent);
|
|
3901
|
+
if (result) return true;
|
|
3902
|
+
if (!silent) console.log(chalk.yellow('WSL 内重启失败,尝试 Windows 原生重启...'));
|
|
3842
3903
|
}
|
|
3843
3904
|
|
|
3844
|
-
return restartGatewayNative();
|
|
3905
|
+
return restartGatewayNative(silent);
|
|
3845
3906
|
}
|
|
3846
3907
|
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3908
|
+
function buildDockerInnerCmds(container, verb) {
|
|
3909
|
+
const cmds = [];
|
|
3910
|
+
if (container.cli === 'node') {
|
|
3911
|
+
cmds.push(`node ${container.cliPath} ${verb}`);
|
|
3912
|
+
} else {
|
|
3913
|
+
if (container.cliPath) cmds.push(`${container.cliPath} ${verb}`);
|
|
3914
|
+
cmds.push(`${container.cli} ${verb}`);
|
|
3915
|
+
}
|
|
3916
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3917
|
+
cmds.push(`${name} ${verb}`);
|
|
3918
|
+
}
|
|
3919
|
+
return [...new Set(cmds)].filter(Boolean);
|
|
3920
|
+
}
|
|
3856
3921
|
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
}
|
|
3862
|
-
safeExec(dockerCmd(`exec ${cid} sh -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
|
|
3863
|
-
console.log(chalk.gray(' 已尝试清理容器内旧 Gateway 进程'));
|
|
3864
|
-
} catch { /* ignore */ }
|
|
3922
|
+
async function restartGatewayDocker(gatewayPort, silent = false) {
|
|
3923
|
+
const container = await selectDockerContainer();
|
|
3924
|
+
if (!container) return false;
|
|
3925
|
+
if (!silent) console.log(chalk.gray(` [检测] Gateway 运行在 Docker 容器: ${container.name} (${container.image})`));
|
|
3865
3926
|
|
|
3866
|
-
|
|
3927
|
+
const cid = container.id;
|
|
3928
|
+
const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
|
|
3867
3929
|
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3930
|
+
// 策略 A:exec restart 命令(短超时),然后端口探测
|
|
3931
|
+
for (const cmd of buildDockerInnerCmds(container, 'gateway restart')) {
|
|
3932
|
+
for (const shell of shellVariants) {
|
|
3933
|
+
safeExec(dockerCmd(`exec ${cid} ${shell} "${cmd}"`), { timeout: 8000 });
|
|
3934
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
|
|
3935
|
+
if (!silent) console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
|
|
3936
|
+
return true;
|
|
3937
|
+
}
|
|
3875
3938
|
}
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
// 策略 B:杀容器内旧进程 → spawn 启动新 Gateway → 端口探测
|
|
3942
|
+
if (!silent) console.log(chalk.gray(' Docker 内常规重启未生效,尝试杀进程后重新启动...'));
|
|
3943
|
+
try {
|
|
3876
3944
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3877
|
-
|
|
3945
|
+
safeExec(dockerCmd(`exec ${cid} sh -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
|
|
3878
3946
|
}
|
|
3947
|
+
safeExec(dockerCmd(`exec ${cid} sh -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
|
|
3948
|
+
} catch { /* ignore */ }
|
|
3949
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3879
3950
|
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
if (
|
|
3884
|
-
if (
|
|
3885
|
-
|
|
3886
|
-
return true;
|
|
3887
|
-
}
|
|
3951
|
+
for (const cmd of buildDockerInnerCmds(container, 'gateway')) {
|
|
3952
|
+
for (const shell of shellVariants) {
|
|
3953
|
+
if (spawnDetachedInDocker(cid, cmd, shell)) {
|
|
3954
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
3955
|
+
if (!silent) console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
|
|
3956
|
+
return true;
|
|
3888
3957
|
}
|
|
3889
3958
|
}
|
|
3890
3959
|
}
|
|
3960
|
+
}
|
|
3891
3961
|
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3962
|
+
return false;
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
async function restartGatewayWsl(gatewayPort, silent = false) {
|
|
3966
|
+
if (!silent) console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
|
|
3967
|
+
const wslCli = getWslCliBinary();
|
|
3968
|
+
const names = ['openclaw', 'clawdbot', 'moltbot'];
|
|
3969
|
+
|
|
3970
|
+
// 构建 WSL 重启命令
|
|
3971
|
+
const wslRestartCmds = [];
|
|
3972
|
+
if (wslCli) wslRestartCmds.push(`wsl -- bash -lc "${wslCli} gateway restart"`);
|
|
3973
|
+
for (const name of names) wslRestartCmds.push(`wsl -- bash -lc "${name} gateway restart"`);
|
|
3974
|
+
|
|
3975
|
+
// 策略 A:exec restart(短超时)+ 端口探测
|
|
3976
|
+
for (const cmd of wslRestartCmds) {
|
|
3977
|
+
safeExec(cmd, { timeout: 8000 });
|
|
3978
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
|
|
3979
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (WSL)'));
|
|
3980
|
+
return true;
|
|
3981
|
+
}
|
|
3896
3982
|
}
|
|
3897
3983
|
|
|
3898
|
-
//
|
|
3984
|
+
// 策略 B:杀 WSL 内旧进程 → spawn 启动 → 端口探测
|
|
3985
|
+
if (!silent) console.log(chalk.gray(' WSL 内常规重启未生效,尝试杀进程后重新启动...'));
|
|
3899
3986
|
try {
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
const findPid = safeExec(`netstat -ano | findstr ":${gatewayPort}"`, { timeout: 5000 });
|
|
3903
|
-
if (findPid.ok && findPid.output) {
|
|
3904
|
-
const lines = findPid.output.split('\n').filter(l => l.includes('LISTENING'));
|
|
3905
|
-
for (const line of lines) {
|
|
3906
|
-
const pid = line.trim().split(/\s+/).pop();
|
|
3907
|
-
if (pid && /^\d+$/.test(pid)) {
|
|
3908
|
-
safeExec(`taskkill /F /PID ${pid}`, { timeout: 5000 });
|
|
3909
|
-
}
|
|
3910
|
-
}
|
|
3911
|
-
}
|
|
3912
|
-
// Windows + WSL: 也清理 WSL 内的 gateway 进程
|
|
3913
|
-
if (isWslAvailable()) {
|
|
3914
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3915
|
-
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
3916
|
-
}
|
|
3917
|
-
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
3918
|
-
}
|
|
3919
|
-
} else {
|
|
3920
|
-
// Linux/macOS: pkill gateway 相关进程
|
|
3921
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3922
|
-
safeExec(`pkill -f '${name}.*gateway' 2>/dev/null || true`);
|
|
3923
|
-
}
|
|
3924
|
-
// 备用:通过端口找进程并杀掉
|
|
3925
|
-
const lsof = safeExec(`lsof -ti :${gatewayPort} 2>/dev/null`);
|
|
3926
|
-
if (lsof.ok && lsof.output) {
|
|
3927
|
-
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
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
}
|
|
3987
|
+
for (const name of names) {
|
|
3988
|
+
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
3933
3989
|
}
|
|
3934
|
-
|
|
3990
|
+
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
3935
3991
|
} catch { /* ignore */ }
|
|
3936
|
-
|
|
3937
|
-
// 2. 等待端口释放
|
|
3938
3992
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3939
3993
|
|
|
3940
|
-
|
|
3941
|
-
|
|
3994
|
+
const wslStartCmds = [];
|
|
3995
|
+
if (wslCli) wslStartCmds.push(`wsl -- bash -lc "${wslCli} gateway"`);
|
|
3996
|
+
for (const name of names) wslStartCmds.push(`wsl -- bash -lc "${name} gateway"`);
|
|
3942
3997
|
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3998
|
+
for (const cmd of wslStartCmds) {
|
|
3999
|
+
if (spawnDetached(cmd, process.env)) {
|
|
4000
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
4001
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (WSL)'));
|
|
4002
|
+
return true;
|
|
4003
|
+
}
|
|
3949
4004
|
}
|
|
3950
4005
|
}
|
|
3951
4006
|
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
4007
|
+
return false;
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
async function restartGatewayNative(silent = false) {
|
|
4011
|
+
const { cliBinary: resolved, nodeMajor } = getCliMeta();
|
|
4012
|
+
const nodeInfo = findCompatibleNode(nodeMajor);
|
|
4013
|
+
const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null) };
|
|
4014
|
+
const useNode = resolved && nodeInfo && isNodeShebang(resolved);
|
|
4015
|
+
|
|
4016
|
+
const configPaths = getConfigPath();
|
|
4017
|
+
const gwConfig = readConfig(configPaths.openclawConfig);
|
|
4018
|
+
const gatewayPort = gwConfig?.gateway?.port || 18789;
|
|
4019
|
+
|
|
4020
|
+
// 策略 A:先尝试 exec "gateway restart"(给 8s 超时——足够杀旧进程)
|
|
4021
|
+
// 注意:该命令通常会启动前台 daemon 不退出,超时是正常的
|
|
4022
|
+
const restartCmds = buildGatewayCommands(resolved, nodeInfo, useNode, 'restart');
|
|
4023
|
+
|
|
4024
|
+
let execAttempted = false;
|
|
4025
|
+
for (const cmd of restartCmds) {
|
|
4026
|
+
execAttempted = true;
|
|
4027
|
+
const result = safeExec(cmd, { timeout: 8000, env });
|
|
4028
|
+
// 不管是否 "成功"(通常会超时),检查端口是否可达
|
|
4029
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
|
|
4030
|
+
if (!silent) console.log(chalk.green(`✅ Gateway 已重启`));
|
|
4031
|
+
return true;
|
|
3969
4032
|
}
|
|
3970
4033
|
}
|
|
3971
4034
|
|
|
3972
|
-
|
|
3973
|
-
|
|
4035
|
+
// 策略 B:杀旧进程 → spawn 后台启动新 Gateway → 端口探测
|
|
4036
|
+
if (!silent) console.log(chalk.gray(' 常规重启未生效,尝试杀进程后重新启动...'));
|
|
4037
|
+
await killGatewayProcesses(gatewayPort);
|
|
4038
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
4039
|
+
|
|
4040
|
+
const startCmds = buildGatewayCommands(resolved, nodeInfo, useNode, 'start');
|
|
4041
|
+
for (const cmd of startCmds) {
|
|
3974
4042
|
if (spawnDetached(cmd, env)) {
|
|
3975
|
-
// 等待新 Gateway 启动
|
|
3976
4043
|
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
3977
|
-
console.log(chalk.green('✅ Gateway
|
|
3978
|
-
console.log(chalk.gray(' 现在可以在 Web/Telegram/Discord 等渠道测试对话了'));
|
|
4044
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启'));
|
|
3979
4045
|
return true;
|
|
3980
4046
|
}
|
|
3981
4047
|
}
|
|
3982
4048
|
}
|
|
3983
4049
|
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
4050
|
+
// 策略 C:用 login shell 启动(加载 nvm/fnm 等 PATH)
|
|
4051
|
+
if (process.platform !== 'win32') {
|
|
4052
|
+
if (!silent) console.log(chalk.gray(' 尝试通过 login shell 启动...'));
|
|
4053
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4054
|
+
for (const sh of ['/bin/zsh', '/bin/bash']) {
|
|
4055
|
+
if (!fs.existsSync(sh)) continue;
|
|
4056
|
+
if (spawnDetached(`${sh} -lc '${name} gateway'`, env)) {
|
|
4057
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
4058
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (login shell)'));
|
|
4059
|
+
return true;
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
// 全部失败,输出诊断
|
|
4067
|
+
if (!silent) {
|
|
4068
|
+
console.log(chalk.red(`❌ 重启失败: 找不到 openclaw/clawdbot/moltbot 命令`));
|
|
4069
|
+
console.log(chalk.gray(` 请手动运行: openclaw gateway restart`));
|
|
4070
|
+
console.log(chalk.gray(` 或: clawdbot gateway restart`));
|
|
4071
|
+
console.log(chalk.gray(` 或: moltbot gateway restart`));
|
|
4072
|
+
printGatewayDiagnostics(resolved);
|
|
4073
|
+
}
|
|
3988
4074
|
return false;
|
|
3989
4075
|
}
|
|
3990
4076
|
|
|
3991
|
-
|
|
3992
|
-
const
|
|
3993
|
-
const
|
|
3994
|
-
const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null) };
|
|
3995
|
-
const useNode = resolved && nodeInfo && isNodeShebang(resolved);
|
|
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
|
-
];
|
|
4077
|
+
function buildGatewayCommands(resolved, nodeInfo, useNode, action) {
|
|
4078
|
+
const verb = action === 'start' ? 'gateway' : 'gateway restart';
|
|
4079
|
+
const commands = [];
|
|
4010
4080
|
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
commands.push(`
|
|
4081
|
+
if (resolved) {
|
|
4082
|
+
if (useNode && nodeInfo) {
|
|
4083
|
+
commands.push(`"${nodeInfo.path}" "${resolved}" ${verb}`);
|
|
4084
|
+
} else {
|
|
4085
|
+
commands.push(`"${resolved}" ${verb}`);
|
|
4016
4086
|
}
|
|
4017
|
-
|
|
4018
|
-
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
const names = ['openclaw', 'clawdbot', 'moltbot'];
|
|
4090
|
+
if (process.platform === 'win32') {
|
|
4091
|
+
for (const name of names) commands.push(`${name} ${verb}`);
|
|
4092
|
+
if (isWslAvailable()) {
|
|
4093
|
+
const wslCli = getWslCliBinary();
|
|
4094
|
+
if (wslCli) commands.push(`wsl -- bash -lc "${wslCli} ${verb}"`);
|
|
4095
|
+
for (const name of names) commands.push(`wsl -- bash -lc "${name} ${verb}"`);
|
|
4019
4096
|
}
|
|
4097
|
+
} else {
|
|
4098
|
+
for (const name of names) commands.push(`${name} ${verb}`);
|
|
4020
4099
|
}
|
|
4021
4100
|
|
|
4022
|
-
// Docker: 追加 Docker 容器命令作为额外回退
|
|
4023
4101
|
if (isDockerAvailable()) {
|
|
4024
|
-
const
|
|
4025
|
-
for (const c of
|
|
4102
|
+
const containers = findOpenclawDockerContainers();
|
|
4103
|
+
for (const c of containers) {
|
|
4026
4104
|
if (c.cli === 'node') {
|
|
4027
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath}
|
|
4105
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath} ${verb}"`));
|
|
4028
4106
|
} else if (c.cli !== 'unknown') {
|
|
4029
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli}
|
|
4107
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} ${verb}"`));
|
|
4030
4108
|
}
|
|
4031
|
-
for (const name of
|
|
4032
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "${name}
|
|
4109
|
+
for (const name of names) {
|
|
4110
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} ${verb}"`));
|
|
4033
4111
|
}
|
|
4034
4112
|
}
|
|
4035
4113
|
}
|
|
4036
4114
|
|
|
4037
|
-
return new
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
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()}`));
|
|
4115
|
+
return [...new Set(commands)].filter(Boolean);
|
|
4116
|
+
}
|
|
4117
|
+
|
|
4118
|
+
async function killGatewayProcesses(gatewayPort = 18789) {
|
|
4119
|
+
try {
|
|
4120
|
+
if (process.platform === 'win32') {
|
|
4121
|
+
const findPid = safeExec(`netstat -ano | findstr ":${gatewayPort}"`, { timeout: 5000 });
|
|
4122
|
+
if (findPid.ok && findPid.output) {
|
|
4123
|
+
for (const line of findPid.output.split('\n').filter(l => l.includes('LISTENING'))) {
|
|
4124
|
+
const pid = line.trim().split(/\s+/).pop();
|
|
4125
|
+
if (pid && /^\d+$/.test(pid)) safeExec(`taskkill /F /PID ${pid}`, { timeout: 5000 });
|
|
4053
4126
|
}
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
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
|
-
}
|
|
4127
|
+
}
|
|
4128
|
+
if (isWslAvailable()) {
|
|
4129
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4130
|
+
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
4065
4131
|
}
|
|
4066
|
-
|
|
4067
|
-
forceRestartGateway(resolved, nodeInfo, useNode, env).then(resolve);
|
|
4068
|
-
return;
|
|
4132
|
+
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
4069
4133
|
}
|
|
4070
|
-
|
|
4071
|
-
const
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
console.log(chalk.green(`✅ Gateway 已重启`));
|
|
4079
|
-
console.log(chalk.gray(` 现在可以在 Web/Telegram/Discord 等渠道测试对话了`));
|
|
4080
|
-
resolve(true);
|
|
4134
|
+
} else {
|
|
4135
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4136
|
+
safeExec(`pkill -f '${name}.*gateway' 2>/dev/null || true`);
|
|
4137
|
+
}
|
|
4138
|
+
const lsof = safeExec(`lsof -ti :${gatewayPort} 2>/dev/null`);
|
|
4139
|
+
if (lsof.ok && lsof.output) {
|
|
4140
|
+
for (const pid of lsof.output.trim().split('\n').filter(Boolean)) {
|
|
4141
|
+
if (/^\d+$/.test(pid.trim())) safeExec(`kill -9 ${pid.trim()} 2>/dev/null || true`);
|
|
4081
4142
|
}
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
} catch { /* ignore */ }
|
|
4146
|
+
}
|
|
4084
4147
|
|
|
4085
|
-
|
|
4086
|
-
});
|
|
4148
|
+
function printGatewayDiagnostics(resolved) {
|
|
4149
|
+
console.log(chalk.gray(`\n [诊断] resolveCliBinary = ${resolved || 'null'}`));
|
|
4150
|
+
const npmPrefix = safeExec('npm prefix -g');
|
|
4151
|
+
if (npmPrefix.ok) console.log(chalk.gray(` [诊断] npm prefix -g = ${npmPrefix.output}`));
|
|
4152
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4153
|
+
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`);
|
|
4154
|
+
if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
|
|
4155
|
+
}
|
|
4156
|
+
if (isDockerAvailable()) {
|
|
4157
|
+
const dContainers = findOpenclawDockerContainers();
|
|
4158
|
+
if (dContainers.length > 0) {
|
|
4159
|
+
console.log(chalk.gray(` [诊断] Docker 容器 (含 openclaw/clawdbot/moltbot):`));
|
|
4160
|
+
for (const c of dContainers) {
|
|
4161
|
+
console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4087
4165
|
}
|
|
4088
4166
|
|
|
4089
4167
|
// 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
|
|