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 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
- const claudeModelId = 'claude-sonnet-4-6';
2668
- const claudeModel = CLAUDE_MODELS.find(m => m.id === claudeModelId) || { id: claudeModelId, name: 'Claude Sonnet 4.6' };
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
- const codexModelId = 'gpt-5.3-codex';
2691
- const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: 'GPT 5.3 Codex' };
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
- console.log(chalk.cyan('步骤 1/2: 重启 Gateway 使配置生效...'));
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
- // 如果 Gateway 在 Docker 容器里,通过 docker exec 重启
3891
+ // Docker 容器内重启
3757
3892
  if (gwEnv === 'docker') {
3758
- const container = await selectDockerContainer();
3759
- if (!container) {
3760
- console.log(chalk.red('❌ 未找到可用的 Docker 容器'));
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
- // 如果 Gateway 在 WSL 里,优先用 wsl -- 重启
3898
+ // WSL 内重启
3809
3899
  if (gwEnv === 'wsl') {
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');
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
- // 强制重启 Gateway:杀掉旧进程,再用已找到的 CLI 路径启动新 Gateway
3848
- async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort = 18789) {
3849
- console.log(chalk.yellow('\n🔄 尝试强制重启 Gateway(杀旧进程 → 启动新进程)...'));
3850
-
3851
- // Docker 容器内强制重启
3852
- if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
3853
- const cid = _selectedDockerContainer.id;
3854
- const cName = _selectedDockerContainer.name;
3855
- console.log(chalk.gray(` [Docker] 在容器 ${cName} 内强制重启...`));
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
- // 1. 杀掉容器内旧 Gateway 进程
3858
- try {
3859
- for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3860
- safeExec(dockerCmd(`exec ${cid} sh -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
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
- await new Promise(resolve => setTimeout(resolve, 2000));
3927
+ const cid = container.id;
3928
+ const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
3867
3929
 
3868
- // 2. 启动新 Gateway
3869
- const dockerCmds = [];
3870
- if (_selectedDockerContainer.cli === 'node') {
3871
- dockerCmds.push(`node ${_selectedDockerContainer.cliPath} gateway`);
3872
- } else {
3873
- if (_selectedDockerContainer.cliPath) dockerCmds.push(`${_selectedDockerContainer.cliPath} gateway`);
3874
- dockerCmds.push(`${_selectedDockerContainer.cli} gateway`);
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
- dockerCmds.push(`${name} gateway`);
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
- for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
3881
- console.log(chalk.gray(` 尝试在容器内启动: ${cmd}`));
3882
- for (const shell of ['sh -c', 'bash -lc', 'bash -c']) {
3883
- if (spawnDetachedInDocker(cid, cmd, shell)) {
3884
- if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
3885
- console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${cName} 内强制重启成功`));
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
- console.log(chalk.red('❌ Docker 容器内强制重启也失败了'));
3893
- console.log(chalk.gray(` 请手动进入容器重启: docker exec -it ${cid} bash`));
3894
- console.log(chalk.gray(` 然后执行: openclaw gateway / clawdbot gateway / moltbot gateway`));
3895
- return false;
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
- // 1. 杀掉旧 Gateway 进程
3984
+ // 策略 B:杀 WSL 内旧进程 → spawn 启动 → 端口探测
3985
+ if (!silent) console.log(chalk.gray(' WSL 内常规重启未生效,尝试杀进程后重新启动...'));
3899
3986
  try {
3900
- if (process.platform === 'win32') {
3901
- // Windows: 通过 taskkill 杀掉占用端口的 node 进程
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
- console.log(chalk.gray(' 已尝试清理旧 Gateway 进程'));
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
- // 3. 用已找到的 CLI 路径启动新 Gateway
3941
- const startCmds = [];
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
- if (resolved) {
3945
- if (useNode && nodeInfo) {
3946
- startCmds.push(`"${nodeInfo.path}" "${resolved}" gateway`);
3947
- } else {
3948
- startCmds.push(`"${resolved}" gateway`);
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
- // 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
- }
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
- for (const cmd of [...new Set(startCmds)].filter(Boolean)) {
3973
- console.log(chalk.gray(` 尝试启动: ${cmd}`));
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
- 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'));
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
- async function restartGatewayNative() {
3992
- const { cliBinary: resolved, nodeMajor } = getCliMeta();
3993
- const nodeInfo = findCompatibleNode(nodeMajor);
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
- // 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"`);
4081
+ if (resolved) {
4082
+ if (useNode && nodeInfo) {
4083
+ commands.push(`"${nodeInfo.path}" "${resolved}" ${verb}`);
4084
+ } else {
4085
+ commands.push(`"${resolved}" ${verb}`);
4016
4086
  }
4017
- for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
4018
- commands.push(`wsl -- bash -lc "${name} gateway restart"`);
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 dockerContainers = findOpenclawDockerContainers();
4025
- for (const c of dockerContainers) {
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} gateway restart"`));
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} gateway restart"`));
4107
+ commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} ${verb}"`));
4030
4108
  }
4031
- for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
4032
- commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} gateway restart"`));
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 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()}`));
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
- // 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
- }
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 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);
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
- tryNext();
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='llmaxapi'; .\install.ps1
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|llmaxapi) PACKAGE="$arg" ;;
18
+ yymaxapi) PACKAGE="$arg" ;;
20
19
  esac
21
20
  done
22
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {