yymaxapi 1.0.47 → 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 +386 -295
- 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;
|
|
@@ -3219,20 +3246,32 @@ function getConfigStatusLine(paths) {
|
|
|
3219
3246
|
const primary = config?.agents?.defaults?.model?.primary || '';
|
|
3220
3247
|
const primaryProvider = primary.split('/')[0] || '';
|
|
3221
3248
|
|
|
3222
|
-
const
|
|
3249
|
+
const KNOWN_PROVIDERS = {
|
|
3223
3250
|
'claude-yunyi': 'Claude(包月)',
|
|
3224
3251
|
'yunyi': 'Codex(包月)',
|
|
3225
3252
|
'heibai': 'MAXAPI(按量)',
|
|
3226
3253
|
};
|
|
3227
3254
|
|
|
3228
3255
|
const parts = [];
|
|
3256
|
+
let otherCount = 0;
|
|
3229
3257
|
for (const p of providers) {
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3258
|
+
if (KNOWN_PROVIDERS[p]) {
|
|
3259
|
+
const label = KNOWN_PROVIDERS[p];
|
|
3260
|
+
const isActive = p === primaryProvider;
|
|
3261
|
+
parts.push(isActive ? chalk.green(`${label} ✓`) : chalk.yellow(`${label} ○`));
|
|
3262
|
+
} else if (p === primaryProvider) {
|
|
3263
|
+
// 未知 provider 但是当前主力,显示名称
|
|
3264
|
+
parts.push(chalk.green(`${p} ✓`));
|
|
3265
|
+
} else {
|
|
3266
|
+
otherCount++;
|
|
3267
|
+
}
|
|
3233
3268
|
}
|
|
3234
3269
|
|
|
3235
|
-
if (
|
|
3270
|
+
if (otherCount > 0) {
|
|
3271
|
+
parts.push(chalk.gray(`+${otherCount} 其他`));
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
if (parts.length === 0 && otherCount === 0) {
|
|
3236
3275
|
return chalk.gray('当前状态: 未配置任何模型');
|
|
3237
3276
|
}
|
|
3238
3277
|
|
|
@@ -3322,15 +3361,18 @@ async function selectNode(paths, type) {
|
|
|
3322
3361
|
}]
|
|
3323
3362
|
};
|
|
3324
3363
|
|
|
3325
|
-
//
|
|
3364
|
+
// 注册模型并设为主模型
|
|
3326
3365
|
const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
|
|
3327
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);
|
|
3328
3369
|
|
|
3370
|
+
ensureGatewaySettings(config);
|
|
3329
3371
|
writeConfigWithSync(paths, config);
|
|
3330
3372
|
|
|
3331
3373
|
console.log(chalk.green(`\n✅ ${typeLabel} 节点配置完成!`));
|
|
3332
3374
|
console.log(chalk.cyan(` 节点: ${selectedEndpoint.name} (${selectedEndpoint.url})`));
|
|
3333
|
-
console.log(chalk.gray(` 模型: ${modelConfig.name}`));
|
|
3375
|
+
console.log(chalk.gray(` 模型: ${modelConfig.name} (主模型)`));
|
|
3334
3376
|
console.log(chalk.gray(` API Key: ${oldApiKey ? '已设置' : '未设置'}`));
|
|
3335
3377
|
}
|
|
3336
3378
|
|
|
@@ -3463,15 +3505,39 @@ async function switchModel(paths) {
|
|
|
3463
3505
|
}
|
|
3464
3506
|
|
|
3465
3507
|
const selectedProvider = selected.split('/')[0];
|
|
3508
|
+
const selectedModelId = selected.split('/')[1];
|
|
3466
3509
|
config.agents.defaults.model.primary = selected;
|
|
3467
3510
|
if (!config.agents.defaults.models[selected]) {
|
|
3468
3511
|
config.agents.defaults.models[selected] = { alias: selectedProvider };
|
|
3469
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
|
+
|
|
3470
3536
|
config.agents.defaults.model.fallbacks = (config.agents.defaults.model.fallbacks || []).filter(k => k !== selected);
|
|
3471
|
-
// 把旧的 primary 加入 fallbacks (如果存在且不同)
|
|
3472
3537
|
if (primary && primary !== selected && !config.agents.defaults.model.fallbacks.includes(primary)) {
|
|
3473
3538
|
config.agents.defaults.model.fallbacks.push(primary);
|
|
3474
3539
|
}
|
|
3540
|
+
ensureGatewaySettings(config);
|
|
3475
3541
|
writeConfigWithSync(paths, config);
|
|
3476
3542
|
|
|
3477
3543
|
const selectedProviderConfig = providers[selectedProvider];
|
|
@@ -3481,15 +3547,96 @@ async function switchModel(paths) {
|
|
|
3481
3547
|
if (selectedProviderConfig) {
|
|
3482
3548
|
console.log(chalk.gray(` 节点: ${selectedProviderConfig.baseUrl}`));
|
|
3483
3549
|
}
|
|
3484
|
-
console.log(chalk.yellow('\n💡 切换后建议重启 Gateway: openclaw gateway restart'));
|
|
3485
3550
|
|
|
3486
3551
|
const gwPort = config.gateway?.port || 18789;
|
|
3487
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
|
+
|
|
3488
3568
|
if (gwToken) {
|
|
3489
3569
|
console.log(chalk.green(`\n🌐 Web Dashboard:`));
|
|
3490
3570
|
console.log(chalk.cyan(` http://localhost:${gwPort}/?token=${gwToken}`));
|
|
3491
3571
|
}
|
|
3492
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
|
+
|
|
3493
3640
|
// ============ 测试连接 ============
|
|
3494
3641
|
async function testConnection(paths, args = {}) {
|
|
3495
3642
|
console.log(chalk.cyan('🧪 测试 OpenClaw Gateway 连接\n'));
|
|
@@ -3511,9 +3658,10 @@ async function testConnection(paths, args = {}) {
|
|
|
3511
3658
|
console.log(chalk.yellow('⚠️ 请先设置主模型'));
|
|
3512
3659
|
return;
|
|
3513
3660
|
}
|
|
3514
|
-
//
|
|
3515
|
-
const
|
|
3516
|
-
const
|
|
3661
|
+
// 优先选云翼/maxapi provider
|
|
3662
|
+
const preferOrder = ['claude-yunyi', 'yunyi', 'heibai'];
|
|
3663
|
+
const preferred = preferOrder.find(n => providerNames.includes(n));
|
|
3664
|
+
const firstP = preferred || providerNames.find(n => n.includes('claude')) || providerNames[0];
|
|
3517
3665
|
const firstModels = providers[firstP]?.models || [];
|
|
3518
3666
|
if (firstModels.length > 0) {
|
|
3519
3667
|
primary = `${firstP}/${firstModels[0].id}`;
|
|
@@ -3571,11 +3719,8 @@ async function testConnection(paths, args = {}) {
|
|
|
3571
3719
|
cleanupAgentProcesses();
|
|
3572
3720
|
|
|
3573
3721
|
// 步骤1: 先重启 Gateway 使配置生效
|
|
3574
|
-
|
|
3575
|
-
const restartOk = await restartGateway();
|
|
3576
|
-
|
|
3577
|
-
// 等待 Gateway 启动
|
|
3578
|
-
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 });
|
|
3579
3724
|
|
|
3580
3725
|
let gatewayRunning = false;
|
|
3581
3726
|
|
|
@@ -3735,342 +3880,288 @@ async function testConnection(paths, args = {}) {
|
|
|
3735
3880
|
}
|
|
3736
3881
|
|
|
3737
3882
|
// ============ 重启 Gateway ============
|
|
3738
|
-
async function restartGateway() {
|
|
3739
|
-
console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
|
|
3883
|
+
async function restartGateway({ silent = false } = {}) {
|
|
3884
|
+
if (!silent) console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
|
|
3740
3885
|
|
|
3741
3886
|
const gwEnv = detectGatewayEnv();
|
|
3887
|
+
const configPaths = getConfigPath();
|
|
3888
|
+
const gwConfig = readConfig(configPaths.openclawConfig);
|
|
3889
|
+
const gatewayPort = gwConfig?.gateway?.port || 18789;
|
|
3742
3890
|
|
|
3743
|
-
//
|
|
3891
|
+
// Docker 容器内重启
|
|
3744
3892
|
if (gwEnv === 'docker') {
|
|
3745
|
-
const
|
|
3746
|
-
if (
|
|
3747
|
-
|
|
3748
|
-
return restartGatewayNative();
|
|
3749
|
-
}
|
|
3750
|
-
console.log(chalk.gray(` [检测] Gateway 运行在 Docker 容器: ${container.name} (${container.image})`));
|
|
3751
|
-
|
|
3752
|
-
const dockerCmds = [];
|
|
3753
|
-
if (container.cli === 'node') {
|
|
3754
|
-
dockerCmds.push(`node ${container.cliPath} gateway restart`);
|
|
3755
|
-
} else {
|
|
3756
|
-
if (container.cliPath) dockerCmds.push(`${container.cliPath} gateway restart`);
|
|
3757
|
-
dockerCmds.push(`${container.cli} gateway restart`);
|
|
3758
|
-
}
|
|
3759
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3760
|
-
dockerCmds.push(`${name} gateway restart`);
|
|
3761
|
-
}
|
|
3762
|
-
|
|
3763
|
-
// 每个命令尝试多种 shell(sh -c / bash -lc / bash -c)
|
|
3764
|
-
const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
|
|
3765
|
-
|
|
3766
|
-
return new Promise((resolve) => {
|
|
3767
|
-
// 展开为 [cmd1+sh, cmd1+bash-lc, cmd1+bash-c, cmd2+sh, ...]
|
|
3768
|
-
const allAttempts = [];
|
|
3769
|
-
for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
|
|
3770
|
-
for (const shell of shellVariants) {
|
|
3771
|
-
allAttempts.push(dockerCmd(`exec ${container.id} ${shell} "${cmd}"`));
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
let tried = 0;
|
|
3775
|
-
const tryNext = () => {
|
|
3776
|
-
if (tried >= allAttempts.length) {
|
|
3777
|
-
console.log(chalk.yellow('Docker 容器内 Gateway 重启失败,尝试本地重启...'));
|
|
3778
|
-
restartGatewayNative().then(resolve);
|
|
3779
|
-
return;
|
|
3780
|
-
}
|
|
3781
|
-
const fullCmd = allAttempts[tried++];
|
|
3782
|
-
exec(fullCmd, { timeout: 30000 }, (error) => {
|
|
3783
|
-
if (error) {
|
|
3784
|
-
tryNext();
|
|
3785
|
-
} else {
|
|
3786
|
-
console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
|
|
3787
|
-
resolve(true);
|
|
3788
|
-
}
|
|
3789
|
-
});
|
|
3790
|
-
};
|
|
3791
|
-
tryNext();
|
|
3792
|
-
});
|
|
3893
|
+
const result = await restartGatewayDocker(gatewayPort, silent);
|
|
3894
|
+
if (result) return true;
|
|
3895
|
+
if (!silent) console.log(chalk.yellow('Docker 容器内重启失败,尝试本地重启...'));
|
|
3793
3896
|
}
|
|
3794
3897
|
|
|
3795
|
-
//
|
|
3898
|
+
// WSL 内重启
|
|
3796
3899
|
if (gwEnv === 'wsl') {
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
const wslCmds = wslCli
|
|
3801
|
-
? [`wsl -- bash -lc "${wslCli} gateway restart"`]
|
|
3802
|
-
: [
|
|
3803
|
-
'wsl -- bash -lc "openclaw gateway restart"',
|
|
3804
|
-
'wsl -- bash -lc "clawdbot gateway restart"',
|
|
3805
|
-
'wsl -- bash -lc "moltbot gateway restart"',
|
|
3806
|
-
];
|
|
3807
|
-
let tried = 0;
|
|
3808
|
-
const tryNext = () => {
|
|
3809
|
-
if (tried >= wslCmds.length) {
|
|
3810
|
-
console.log(chalk.yellow('WSL 内 Gateway 重启失败,尝试 Windows 原生重启...'));
|
|
3811
|
-
restartGatewayNative().then(resolve);
|
|
3812
|
-
return;
|
|
3813
|
-
}
|
|
3814
|
-
const cmd = wslCmds[tried++];
|
|
3815
|
-
exec(cmd, { timeout: 30000 }, (error, stdout, stderr) => {
|
|
3816
|
-
const errStr = (stderr || '').toLowerCase();
|
|
3817
|
-
const wslGarbled = errStr.includes('wsl:') && errStr.includes('localhost') && errStr.includes('nat');
|
|
3818
|
-
|
|
3819
|
-
if (error || errStr.includes('/usr/bin/env:') || wslGarbled) {
|
|
3820
|
-
tryNext();
|
|
3821
|
-
} else {
|
|
3822
|
-
console.log(chalk.green('Gateway 已重启 (WSL)'));
|
|
3823
|
-
resolve(true);
|
|
3824
|
-
}
|
|
3825
|
-
});
|
|
3826
|
-
};
|
|
3827
|
-
tryNext();
|
|
3828
|
-
});
|
|
3900
|
+
const result = await restartGatewayWsl(gatewayPort, silent);
|
|
3901
|
+
if (result) return true;
|
|
3902
|
+
if (!silent) console.log(chalk.yellow('WSL 内重启失败,尝试 Windows 原生重启...'));
|
|
3829
3903
|
}
|
|
3830
3904
|
|
|
3831
|
-
return restartGatewayNative();
|
|
3905
|
+
return restartGatewayNative(silent);
|
|
3832
3906
|
}
|
|
3833
3907
|
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
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
|
+
}
|
|
3843
3921
|
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
}
|
|
3849
|
-
safeExec(dockerCmd(`exec ${cid} sh -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
|
|
3850
|
-
console.log(chalk.gray(' 已尝试清理容器内旧 Gateway 进程'));
|
|
3851
|
-
} 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})`));
|
|
3852
3926
|
|
|
3853
|
-
|
|
3927
|
+
const cid = container.id;
|
|
3928
|
+
const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
|
|
3854
3929
|
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
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
|
+
}
|
|
3862
3938
|
}
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
// 策略 B:杀容器内旧进程 → spawn 启动新 Gateway → 端口探测
|
|
3942
|
+
if (!silent) console.log(chalk.gray(' Docker 内常规重启未生效,尝试杀进程后重新启动...'));
|
|
3943
|
+
try {
|
|
3863
3944
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3864
|
-
|
|
3945
|
+
safeExec(dockerCmd(`exec ${cid} sh -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
|
|
3865
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));
|
|
3866
3950
|
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
if (
|
|
3871
|
-
if (
|
|
3872
|
-
|
|
3873
|
-
return true;
|
|
3874
|
-
}
|
|
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;
|
|
3875
3957
|
}
|
|
3876
3958
|
}
|
|
3877
3959
|
}
|
|
3960
|
+
}
|
|
3878
3961
|
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
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
|
+
}
|
|
3883
3982
|
}
|
|
3884
3983
|
|
|
3885
|
-
//
|
|
3984
|
+
// 策略 B:杀 WSL 内旧进程 → spawn 启动 → 端口探测
|
|
3985
|
+
if (!silent) console.log(chalk.gray(' WSL 内常规重启未生效,尝试杀进程后重新启动...'));
|
|
3886
3986
|
try {
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
const findPid = safeExec(`netstat -ano | findstr ":${gatewayPort}"`, { timeout: 5000 });
|
|
3890
|
-
if (findPid.ok && findPid.output) {
|
|
3891
|
-
const lines = findPid.output.split('\n').filter(l => l.includes('LISTENING'));
|
|
3892
|
-
for (const line of lines) {
|
|
3893
|
-
const pid = line.trim().split(/\s+/).pop();
|
|
3894
|
-
if (pid && /^\d+$/.test(pid)) {
|
|
3895
|
-
safeExec(`taskkill /F /PID ${pid}`, { timeout: 5000 });
|
|
3896
|
-
}
|
|
3897
|
-
}
|
|
3898
|
-
}
|
|
3899
|
-
// Windows + WSL: 也清理 WSL 内的 gateway 进程
|
|
3900
|
-
if (isWslAvailable()) {
|
|
3901
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3902
|
-
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
3903
|
-
}
|
|
3904
|
-
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
3905
|
-
}
|
|
3906
|
-
} else {
|
|
3907
|
-
// Linux/macOS: pkill gateway 相关进程
|
|
3908
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3909
|
-
safeExec(`pkill -f '${name}.*gateway' 2>/dev/null || true`);
|
|
3910
|
-
}
|
|
3911
|
-
// 备用:通过端口找进程并杀掉
|
|
3912
|
-
const lsof = safeExec(`lsof -ti :${gatewayPort} 2>/dev/null`);
|
|
3913
|
-
if (lsof.ok && lsof.output) {
|
|
3914
|
-
for (const pid of lsof.output.trim().split('\n').filter(Boolean)) {
|
|
3915
|
-
if (/^\d+$/.test(pid.trim())) {
|
|
3916
|
-
safeExec(`kill -9 ${pid.trim()} 2>/dev/null || true`);
|
|
3917
|
-
}
|
|
3918
|
-
}
|
|
3919
|
-
}
|
|
3987
|
+
for (const name of names) {
|
|
3988
|
+
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
3920
3989
|
}
|
|
3921
|
-
|
|
3990
|
+
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
3922
3991
|
} catch { /* ignore */ }
|
|
3923
|
-
|
|
3924
|
-
// 2. 等待端口释放
|
|
3925
3992
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3926
3993
|
|
|
3927
|
-
|
|
3928
|
-
|
|
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"`);
|
|
3929
3997
|
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
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
|
+
}
|
|
3936
4004
|
}
|
|
3937
4005
|
}
|
|
3938
4006
|
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
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;
|
|
3956
4032
|
}
|
|
3957
4033
|
}
|
|
3958
4034
|
|
|
3959
|
-
|
|
3960
|
-
|
|
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) {
|
|
3961
4042
|
if (spawnDetached(cmd, env)) {
|
|
3962
|
-
// 等待新 Gateway 启动
|
|
3963
4043
|
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
3964
|
-
console.log(chalk.green('✅ Gateway
|
|
3965
|
-
console.log(chalk.gray(' 现在可以在 Web/Telegram/Discord 等渠道测试对话了'));
|
|
4044
|
+
if (!silent) console.log(chalk.green('✅ Gateway 已重启'));
|
|
3966
4045
|
return true;
|
|
3967
4046
|
}
|
|
3968
4047
|
}
|
|
3969
4048
|
}
|
|
3970
4049
|
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
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
|
+
}
|
|
3975
4074
|
return false;
|
|
3976
4075
|
}
|
|
3977
4076
|
|
|
3978
|
-
|
|
3979
|
-
const
|
|
3980
|
-
const
|
|
3981
|
-
const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null) };
|
|
3982
|
-
const useNode = resolved && nodeInfo && isNodeShebang(resolved);
|
|
4077
|
+
function buildGatewayCommands(resolved, nodeInfo, useNode, action) {
|
|
4078
|
+
const verb = action === 'start' ? 'gateway' : 'gateway restart';
|
|
4079
|
+
const commands = [];
|
|
3983
4080
|
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
: [
|
|
3990
|
-
'openclaw gateway restart',
|
|
3991
|
-
'clawdbot gateway restart',
|
|
3992
|
-
'moltbot gateway restart',
|
|
3993
|
-
'npx openclaw gateway restart',
|
|
3994
|
-
'npx clawdbot gateway restart',
|
|
3995
|
-
'npx moltbot gateway restart'
|
|
3996
|
-
];
|
|
3997
|
-
|
|
3998
|
-
// Windows + WSL: 追加 WSL 命令作为额外回退
|
|
3999
|
-
if (process.platform === 'win32' && isWslAvailable()) {
|
|
4000
|
-
const wslCli = getWslCliBinary();
|
|
4001
|
-
if (wslCli) {
|
|
4002
|
-
commands.push(`wsl -- bash -lc "${wslCli} gateway restart"`);
|
|
4081
|
+
if (resolved) {
|
|
4082
|
+
if (useNode && nodeInfo) {
|
|
4083
|
+
commands.push(`"${nodeInfo.path}" "${resolved}" ${verb}`);
|
|
4084
|
+
} else {
|
|
4085
|
+
commands.push(`"${resolved}" ${verb}`);
|
|
4003
4086
|
}
|
|
4004
|
-
|
|
4005
|
-
|
|
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}"`);
|
|
4006
4096
|
}
|
|
4097
|
+
} else {
|
|
4098
|
+
for (const name of names) commands.push(`${name} ${verb}`);
|
|
4007
4099
|
}
|
|
4008
4100
|
|
|
4009
|
-
// Docker: 追加 Docker 容器命令作为额外回退
|
|
4010
4101
|
if (isDockerAvailable()) {
|
|
4011
|
-
const
|
|
4012
|
-
for (const c of
|
|
4102
|
+
const containers = findOpenclawDockerContainers();
|
|
4103
|
+
for (const c of containers) {
|
|
4013
4104
|
if (c.cli === 'node') {
|
|
4014
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath}
|
|
4105
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath} ${verb}"`));
|
|
4015
4106
|
} else if (c.cli !== 'unknown') {
|
|
4016
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli}
|
|
4107
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} ${verb}"`));
|
|
4017
4108
|
}
|
|
4018
|
-
for (const name of
|
|
4019
|
-
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}"`));
|
|
4020
4111
|
}
|
|
4021
4112
|
}
|
|
4022
4113
|
}
|
|
4023
4114
|
|
|
4024
|
-
return new
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
const npmPrefix = safeExec('npm prefix -g');
|
|
4036
|
-
if (npmPrefix.ok) console.log(chalk.gray(` [诊断] npm prefix -g = ${npmPrefix.output}`));
|
|
4037
|
-
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4038
|
-
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`);
|
|
4039
|
-
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 });
|
|
4040
4126
|
}
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
console.log(chalk.gray(` [诊断] Docker 容器 (含 openclaw/clawdbot/moltbot):`));
|
|
4046
|
-
for (const c of dContainers) {
|
|
4047
|
-
console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
|
|
4048
|
-
}
|
|
4049
|
-
} else {
|
|
4050
|
-
console.log(chalk.gray(` [诊断] Docker 可用,但未找到含 openclaw/clawdbot/moltbot 的容器`));
|
|
4051
|
-
}
|
|
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 });
|
|
4052
4131
|
}
|
|
4053
|
-
|
|
4054
|
-
forceRestartGateway(resolved, nodeInfo, useNode, env).then(resolve);
|
|
4055
|
-
return;
|
|
4132
|
+
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
4056
4133
|
}
|
|
4057
|
-
|
|
4058
|
-
const
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
console.log(chalk.green(`✅ Gateway 已重启`));
|
|
4066
|
-
console.log(chalk.gray(` 现在可以在 Web/Telegram/Discord 等渠道测试对话了`));
|
|
4067
|
-
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`);
|
|
4068
4142
|
}
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
} catch { /* ignore */ }
|
|
4146
|
+
}
|
|
4071
4147
|
|
|
4072
|
-
|
|
4073
|
-
});
|
|
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
|
+
}
|
|
4074
4165
|
}
|
|
4075
4166
|
|
|
4076
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
|
|