yymaxapi 1.0.25 → 1.0.27

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.
Files changed (2) hide show
  1. package/bin/yymaxapi.js +343 -14
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -708,11 +708,146 @@ function writeConfigWithSync(paths, config) {
708
708
  if (gwEnv === 'wsl') {
709
709
  syncConfigToWsl(paths.openclawConfig);
710
710
  }
711
+ // 如果 Gateway 在 Docker 容器内,自动同步配置过去
712
+ if (gwEnv === 'docker' && _selectedDockerContainer) {
713
+ syncConfigToDocker(paths.openclawConfig, _selectedDockerContainer.id);
714
+ }
715
+ }
716
+
717
+ // ============ Docker 容器发现与操作 ============
718
+ let _dockerAvailCache = null;
719
+ let _dockerContainerCache = null;
720
+ let _selectedDockerContainer = null;
721
+
722
+ function isDockerAvailable() {
723
+ if (_dockerAvailCache !== null) return _dockerAvailCache;
724
+ const r = safeExec('docker info', { timeout: 5000 });
725
+ _dockerAvailCache = r.ok;
726
+ return _dockerAvailCache;
727
+ }
728
+
729
+ // 查找包含 openclaw/clawdbot/moltbot 的运行中容器
730
+ function findOpenclawDockerContainers() {
731
+ if (_dockerContainerCache !== null) return _dockerContainerCache;
732
+ if (!isDockerAvailable()) { _dockerContainerCache = []; return []; }
733
+
734
+ const ps = safeExec('docker ps --format "{{.ID}}\\t{{.Names}}\\t{{.Image}}\\t{{.Status}}"', { timeout: 10000 });
735
+ if (!ps.ok || !ps.output) { _dockerContainerCache = []; return []; }
736
+
737
+ const containers = [];
738
+ const lines = ps.output.split('\n').filter(Boolean);
739
+
740
+ for (const line of lines) {
741
+ const [id, name, image, ...statusParts] = line.split('\t');
742
+ const status = statusParts.join('\t');
743
+ if (!id) continue;
744
+
745
+ // 检查容器内是否有 openclaw/clawdbot/moltbot
746
+ for (const cli of ['openclaw', 'clawdbot', 'moltbot']) {
747
+ const check = safeExec(`docker exec ${id} which ${cli} 2>/dev/null`, { timeout: 5000 });
748
+ if (check.ok && check.output) {
749
+ containers.push({ id, name, image, status, cli, cliPath: check.output.trim() });
750
+ break; // 一个容器只记录一次
751
+ }
752
+ }
753
+
754
+ // 也检查 /opt/moltbot 等常见路径
755
+ if (!containers.find(c => c.id === id)) {
756
+ for (const p of ['/opt/moltbot/moltbot.mjs', '/opt/moltbot/bin/moltbot.mjs']) {
757
+ const check = safeExec(`docker exec ${id} test -f ${p} && echo found`, { timeout: 5000 });
758
+ if (check.ok && check.output && check.output.includes('found')) {
759
+ containers.push({ id, name, image, status, cli: 'node', cliPath: p });
760
+ break;
761
+ }
762
+ }
763
+ }
764
+ }
765
+
766
+ _dockerContainerCache = containers;
767
+ return containers;
768
+ }
769
+
770
+ // 交互式选择 Docker 容器
771
+ async function selectDockerContainer() {
772
+ if (_selectedDockerContainer) return _selectedDockerContainer;
773
+
774
+ const containers = findOpenclawDockerContainers();
775
+ if (containers.length === 0) return null;
776
+
777
+ if (containers.length === 1) {
778
+ _selectedDockerContainer = containers[0];
779
+ console.log(chalk.green(`✅ 自动选择 Docker 容器: ${containers[0].name} (${containers[0].image})`));
780
+ return _selectedDockerContainer;
781
+ }
782
+
783
+ // 多个容器,让用户选择
784
+ console.log(chalk.cyan('\n检测到多个包含 OpenClaw/Clawdbot/Moltbot 的 Docker 容器:'));
785
+ const { selected } = await inquirer.prompt([{
786
+ type: 'list',
787
+ name: 'selected',
788
+ message: '请选择要操作的容器:',
789
+ choices: containers.map((c, i) => ({
790
+ name: `${c.name} (${c.image}) [${c.cli}] - ${c.status}`,
791
+ value: i
792
+ }))
793
+ }]);
794
+
795
+ _selectedDockerContainer = containers[selected];
796
+ console.log(chalk.green(`✅ 已选择容器: ${_selectedDockerContainer.name}`));
797
+ return _selectedDockerContainer;
798
+ }
799
+
800
+ // 在 Docker 容器内执行命令
801
+ function execInDocker(containerId, cmd, options = {}) {
802
+ const escaped = cmd.replace(/'/g, "'\\''");
803
+ return safeExec(`docker exec ${containerId} bash -lc '${escaped}'`, { timeout: 30000, ...options });
804
+ }
805
+
806
+ // 在 Docker 容器内异步执行命令(后台启动 gateway 等)
807
+ function spawnDetachedInDocker(containerId, cmd, env = {}) {
808
+ try {
809
+ const child = spawn('docker', ['exec', '-d', containerId, 'bash', '-lc', cmd], {
810
+ detached: true,
811
+ stdio: 'ignore',
812
+ env: { ...process.env, ...env }
813
+ });
814
+ child.unref();
815
+ return true;
816
+ } catch { return false; }
817
+ }
818
+
819
+ // 获取 Docker 容器内的 CLI 二进制路径
820
+ function getDockerCliBinary(container) {
821
+ if (!container) return null;
822
+ return container.cliPath || null;
823
+ }
824
+
825
+ // 同步配置到 Docker 容器(将本地 openclaw.json 复制到容器内)
826
+ function syncConfigToDocker(localConfigPath, containerId) {
827
+ try {
828
+ // 尝试多个目标路径
829
+ const targets = [
830
+ '/root/.openclaw/openclaw.json',
831
+ '/root/.clawdbot/openclaw.json',
832
+ '/root/.moltbot/moltbot.json',
833
+ '/home/.openclaw/openclaw.json',
834
+ ];
835
+ // 先确定容器内哪个配置目录存在
836
+ for (const target of targets) {
837
+ const dir = path.posix.dirname(target);
838
+ safeExec(`docker exec ${containerId} mkdir -p "${dir}"`, { timeout: 5000 });
839
+ const cp = safeExec(`docker cp "${localConfigPath}" ${containerId}:${target}`, { timeout: 10000 });
840
+ if (cp.ok) {
841
+ console.log(chalk.gray(` 已同步配置到 Docker 容器: ${target}`));
842
+ return;
843
+ }
844
+ }
845
+ } catch { /* best-effort */ }
711
846
  }
712
847
 
713
848
  // ============ Gateway 环境检测(集中化) ============
714
- // 所有 WSL 相关逻辑统一通过 detectGatewayEnv() 路由
715
- // 返回 'native' | 'wsl',结果缓存,整个进程生命周期只检测一次
849
+ // 所有 WSL/Docker 相关逻辑统一通过 detectGatewayEnv() 路由
850
+ // 返回 'native' | 'wsl' | 'docker',结果缓存,整个进程生命周期只检测一次
716
851
 
717
852
  let _gwEnvCache = null;
718
853
  let _wslAvailCache = null;
@@ -758,7 +893,20 @@ function getWslCliBinary() {
758
893
 
759
894
  function detectGatewayEnv(port = 18789) {
760
895
  if (_gwEnvCache !== null) return _gwEnvCache;
896
+
897
+ // 非 Windows 平台:先检查本地 CLI,找不到再检查 Docker
761
898
  if (process.platform !== 'win32' || !isWslAvailable()) {
899
+ const nativeCli = resolveCliBinary();
900
+ if (nativeCli) {
901
+ _gwEnvCache = 'native';
902
+ return 'native';
903
+ }
904
+ // 本地没有 CLI,检查 Docker 容器
905
+ const dockerContainers = findOpenclawDockerContainers();
906
+ if (dockerContainers.length > 0) {
907
+ _gwEnvCache = 'docker';
908
+ return 'docker';
909
+ }
762
910
  _gwEnvCache = 'native';
763
911
  return 'native';
764
912
  }
@@ -787,17 +935,26 @@ function detectGatewayEnv(port = 18789) {
787
935
  _gwEnvCache = 'wsl';
788
936
  return 'wsl';
789
937
  }
938
+ // Fallback: 检查 Docker 容器
939
+ const dockerContainers = findOpenclawDockerContainers();
940
+ if (dockerContainers.length > 0) {
941
+ _gwEnvCache = 'docker';
942
+ return 'docker';
943
+ }
790
944
  }
791
945
  _gwEnvCache = 'native';
792
946
  return 'native';
793
947
  }
794
948
 
795
- // 在 Gateway 环境中执行命令(WSL 自动包裹 wsl -- bash -lc)
949
+ // 在 Gateway 环境中执行命令(WSL/Docker 自动包裹)
796
950
  function execInGatewayEnv(cmd, options = {}) {
797
951
  if (detectGatewayEnv() === 'wsl') {
798
952
  const escaped = cmd.replace(/'/g, "'\\''");
799
953
  return safeExec(`wsl -- bash -lc '${escaped}'`, options);
800
954
  }
955
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
956
+ return execInDocker(_selectedDockerContainer.id, cmd, options);
957
+ }
801
958
  return safeExec(cmd, options);
802
959
  }
803
960
 
@@ -807,6 +964,10 @@ function execAsyncInGatewayEnv(cmd, options = {}) {
807
964
  const escaped = cmd.replace(/'/g, "'\\''");
808
965
  return { cmd: `wsl -- bash -lc '${escaped}'`, options };
809
966
  }
967
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
968
+ const escaped = cmd.replace(/'/g, "'\\''");
969
+ return { cmd: `docker exec ${_selectedDockerContainer.id} bash -lc '${escaped}'`, options };
970
+ }
810
971
  return { cmd, options };
811
972
  }
812
973
 
@@ -848,6 +1009,14 @@ function cleanupAgentProcesses() {
848
1009
  execSync(`pkill -f '${name}.*agent' 2>/dev/null || true`, { stdio: 'ignore' });
849
1010
  }
850
1011
  }
1012
+ // Docker 容器内的 agent 也要清理
1013
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
1014
+ try {
1015
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
1016
+ safeExec(`docker exec ${_selectedDockerContainer.id} bash -c "pkill -f '${name}.*agent' 2>/dev/null || true"`, { timeout: 10000 });
1017
+ }
1018
+ } catch { /* ignore */ }
1019
+ }
851
1020
  console.log(chalk.gray('已清理残留 agent 进程'));
852
1021
  } catch { /* ignore */ }
853
1022
  }
@@ -1478,6 +1647,35 @@ function installMacGatewayDaemon() {
1478
1647
  async function tryAutoStartGateway(port, allowAutoDaemon) {
1479
1648
  const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
1480
1649
 
1650
+ // Docker 容器内启动 Gateway
1651
+ if (detectGatewayEnv() === 'docker') {
1652
+ const container = await selectDockerContainer();
1653
+ if (!container) return { started: false };
1654
+
1655
+ console.log(chalk.yellow(`⚠️ Gateway 未检测到运行,尝试在 Docker 容器 ${container.name} 内启动...`));
1656
+
1657
+ const dockerCmds = [];
1658
+ if (container.cli === 'node') {
1659
+ dockerCmds.push(`node ${container.cliPath} gateway`);
1660
+ } else {
1661
+ dockerCmds.push(`${container.cli} gateway`);
1662
+ }
1663
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
1664
+ dockerCmds.push(`${name} gateway`);
1665
+ }
1666
+
1667
+ for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
1668
+ console.log(chalk.yellow(`⚠️ 尝试在容器内启动 Gateway: ${cmd}`));
1669
+ if (spawnDetachedInDocker(container.id, cmd)) {
1670
+ if (await waitForGateway(port, '127.0.0.1', 15000)) {
1671
+ console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${container.name} 内启动`));
1672
+ return { started: true, method: 'docker', container: container.name };
1673
+ }
1674
+ }
1675
+ }
1676
+ return { started: false };
1677
+ }
1678
+
1481
1679
  if (process.platform === 'darwin' && allowAutoDaemon) {
1482
1680
  console.log(chalk.yellow('⚠️ Gateway 未检测到运行,尝试在 macOS 后台启动 (LaunchAgent)...'));
1483
1681
  const daemonResult = installMacGatewayDaemon();
@@ -1873,21 +2071,21 @@ async function presetClaude(paths, args = {}) {
1873
2071
 
1874
2072
  const sorted = speedResult.ranked || [];
1875
2073
  if (sorted.length > 0) {
2074
+ const defaultEp = ENDPOINTS[0];
1876
2075
  const { selectedIndex } = await inquirer.prompt([{
1877
2076
  type: 'list',
1878
2077
  name: 'selectedIndex',
1879
2078
  message: '选择节点:',
1880
2079
  choices: [
1881
- { name: `* 使用推荐节点 (${sorted[0].name}, ${sorted[0].latency}ms, 评分:${sorted[0].score})`, value: -1 },
1882
- new inquirer.Separator(' ---- 或手动选择 ----'),
2080
+ { name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
2081
+ new inquirer.Separator(' ---- 或按测速结果选择 ----'),
1883
2082
  ...sorted.map((e, i) => ({
1884
2083
  name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
1885
2084
  value: i
1886
2085
  }))
1887
2086
  ]
1888
2087
  }]);
1889
- const primaryIndex = selectedIndex === -1 ? 0 : selectedIndex;
1890
- selectedEndpoint = sorted[primaryIndex];
2088
+ selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
1891
2089
  if (speedResult.usedFallback) {
1892
2090
  console.log(chalk.yellow(`\n⚠ 当前使用备用节点\n`));
1893
2091
  }
@@ -2070,21 +2268,21 @@ async function presetCodex(paths, args = {}) {
2070
2268
 
2071
2269
  const sorted = speedResult.ranked || [];
2072
2270
  if (sorted.length > 0) {
2271
+ const defaultEp = ENDPOINTS[0];
2073
2272
  const { selectedIndex } = await inquirer.prompt([{
2074
2273
  type: 'list',
2075
2274
  name: 'selectedIndex',
2076
2275
  message: '选择节点:',
2077
2276
  choices: [
2078
- { name: `* 使用推荐节点 (${sorted[0].name}, ${sorted[0].latency}ms, 评分:${sorted[0].score})`, value: -1 },
2079
- new inquirer.Separator(' ---- 或手动选择 ----'),
2277
+ { name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
2278
+ new inquirer.Separator(' ---- 或按测速结果选择 ----'),
2080
2279
  ...sorted.map((e, i) => ({
2081
2280
  name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
2082
2281
  value: i
2083
2282
  }))
2084
2283
  ]
2085
2284
  }]);
2086
- const primaryIndex = selectedIndex === -1 ? 0 : selectedIndex;
2087
- selectedEndpoint = sorted[primaryIndex];
2285
+ selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
2088
2286
  if (speedResult.usedFallback) {
2089
2287
  console.log(chalk.yellow(`\n⚠ 当前使用备用节点\n`));
2090
2288
  }
@@ -2630,6 +2828,16 @@ async function testConnection(paths, args = {}) {
2630
2828
 
2631
2829
  const allowAutoDaemon = !(args['no-daemon'] || args.noDaemon);
2632
2830
 
2831
+ // 如果检测到 Docker 环境,先让用户选择容器
2832
+ if (detectGatewayEnv() === 'docker') {
2833
+ const container = await selectDockerContainer();
2834
+ if (!container) {
2835
+ console.log(chalk.red('❌ 未找到包含 OpenClaw/Clawdbot/Moltbot 的 Docker 容器'));
2836
+ console.log(chalk.gray(' 请确保容器正在运行且已安装 openclaw/clawdbot/moltbot'));
2837
+ return;
2838
+ }
2839
+ }
2840
+
2633
2841
  // 步骤0: 清理所有挂死的 agent 进程
2634
2842
  cleanupAgentProcesses();
2635
2843
 
@@ -2650,9 +2858,15 @@ async function testConnection(paths, args = {}) {
2650
2858
 
2651
2859
  if (!gatewayRunning) {
2652
2860
  gwSpinner.fail('Gateway 未运行');
2653
- console.log(chalk.gray(' 请在新的终端执行: openclaw gateway'));
2654
- console.log(chalk.gray(' 或: clawdbot gateway'));
2655
- console.log(chalk.gray(' 或: moltbot gateway'));
2861
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
2862
+ console.log(chalk.gray(` 请在 Docker 容器内启动 Gateway:`));
2863
+ console.log(chalk.gray(` docker exec -it ${_selectedDockerContainer.id} bash`));
2864
+ console.log(chalk.gray(` 然后执行: ${_selectedDockerContainer.cli === 'node' ? `node ${_selectedDockerContainer.cliPath}` : _selectedDockerContainer.cli} gateway`));
2865
+ } else {
2866
+ console.log(chalk.gray(' 请在新的终端执行: openclaw gateway'));
2867
+ console.log(chalk.gray(' 或: clawdbot gateway'));
2868
+ console.log(chalk.gray(' 或: moltbot gateway'));
2869
+ }
2656
2870
  return;
2657
2871
  }
2658
2872
 
@@ -2785,6 +2999,50 @@ async function restartGateway() {
2785
2999
 
2786
3000
  const gwEnv = detectGatewayEnv();
2787
3001
 
3002
+ // 如果 Gateway 在 Docker 容器里,通过 docker exec 重启
3003
+ if (gwEnv === 'docker') {
3004
+ const container = await selectDockerContainer();
3005
+ if (!container) {
3006
+ console.log(chalk.red('❌ 未找到可用的 Docker 容器'));
3007
+ return restartGatewayNative();
3008
+ }
3009
+ console.log(chalk.gray(` [检测] Gateway 运行在 Docker 容器: ${container.name} (${container.image})`));
3010
+
3011
+ const dockerCmds = [];
3012
+ if (container.cli === 'node') {
3013
+ // 脚本路径模式(如 /opt/moltbot/moltbot.mjs)
3014
+ dockerCmds.push(`node ${container.cliPath} gateway restart`);
3015
+ } else {
3016
+ dockerCmds.push(`${container.cli} gateway restart`);
3017
+ }
3018
+ // 通用回退
3019
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3020
+ dockerCmds.push(`${name} gateway restart`);
3021
+ }
3022
+
3023
+ return new Promise((resolve) => {
3024
+ let tried = 0;
3025
+ const tryNext = () => {
3026
+ if (tried >= dockerCmds.length) {
3027
+ console.log(chalk.yellow('Docker 容器内 Gateway 重启失败,尝试本地重启...'));
3028
+ restartGatewayNative().then(resolve);
3029
+ return;
3030
+ }
3031
+ const cmd = dockerCmds[tried++];
3032
+ const fullCmd = `docker exec ${container.id} bash -lc "${cmd}"`;
3033
+ exec(fullCmd, { timeout: 30000 }, (error) => {
3034
+ if (error) {
3035
+ tryNext();
3036
+ } else {
3037
+ console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
3038
+ resolve(true);
3039
+ }
3040
+ });
3041
+ };
3042
+ tryNext();
3043
+ });
3044
+ }
3045
+
2788
3046
  // 如果 Gateway 在 WSL 里,优先用 wsl -- 重启
2789
3047
  if (gwEnv === 'wsl') {
2790
3048
  console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
@@ -2828,6 +3086,50 @@ async function restartGateway() {
2828
3086
  async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort = 18789) {
2829
3087
  console.log(chalk.yellow('\n🔄 尝试强制重启 Gateway(杀旧进程 → 启动新进程)...'));
2830
3088
 
3089
+ // Docker 容器内强制重启
3090
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
3091
+ const cid = _selectedDockerContainer.id;
3092
+ const cName = _selectedDockerContainer.name;
3093
+ console.log(chalk.gray(` [Docker] 在容器 ${cName} 内强制重启...`));
3094
+
3095
+ // 1. 杀掉容器内旧 Gateway 进程
3096
+ try {
3097
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3098
+ safeExec(`docker exec ${cid} bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
3099
+ }
3100
+ safeExec(`docker exec ${cid} bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
3101
+ console.log(chalk.gray(' 已尝试清理容器内旧 Gateway 进程'));
3102
+ } catch { /* ignore */ }
3103
+
3104
+ await new Promise(resolve => setTimeout(resolve, 2000));
3105
+
3106
+ // 2. 启动新 Gateway
3107
+ const dockerCmds = [];
3108
+ if (_selectedDockerContainer.cli === 'node') {
3109
+ dockerCmds.push(`node ${_selectedDockerContainer.cliPath} gateway`);
3110
+ } else {
3111
+ dockerCmds.push(`${_selectedDockerContainer.cli} gateway`);
3112
+ }
3113
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3114
+ dockerCmds.push(`${name} gateway`);
3115
+ }
3116
+
3117
+ for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
3118
+ console.log(chalk.gray(` 尝试在容器内启动: ${cmd}`));
3119
+ if (spawnDetachedInDocker(cid, cmd)) {
3120
+ if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
3121
+ console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${cName} 内强制重启成功`));
3122
+ return true;
3123
+ }
3124
+ }
3125
+ }
3126
+
3127
+ console.log(chalk.red('❌ Docker 容器内强制重启也失败了'));
3128
+ console.log(chalk.gray(` 请手动进入容器重启: docker exec -it ${cid} bash`));
3129
+ console.log(chalk.gray(` 然后执行: openclaw gateway / clawdbot gateway / moltbot gateway`));
3130
+ return false;
3131
+ }
3132
+
2831
3133
  // 1. 杀掉旧 Gateway 进程
2832
3134
  try {
2833
3135
  if (process.platform === 'win32') {
@@ -2952,6 +3254,21 @@ async function restartGatewayNative() {
2952
3254
  }
2953
3255
  }
2954
3256
 
3257
+ // Docker: 追加 Docker 容器命令作为额外回退
3258
+ if (isDockerAvailable()) {
3259
+ const dockerContainers = findOpenclawDockerContainers();
3260
+ for (const c of dockerContainers) {
3261
+ if (c.cli === 'node') {
3262
+ commands.push(`docker exec ${c.id} bash -lc "node ${c.cliPath} gateway restart"`);
3263
+ } else {
3264
+ commands.push(`docker exec ${c.id} bash -lc "${c.cli} gateway restart"`);
3265
+ }
3266
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3267
+ commands.push(`docker exec ${c.id} bash -lc "${name} gateway restart"`);
3268
+ }
3269
+ }
3270
+ }
3271
+
2955
3272
  return new Promise((resolve) => {
2956
3273
  let tried = 0;
2957
3274
 
@@ -2969,6 +3286,18 @@ async function restartGatewayNative() {
2969
3286
  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`);
2970
3287
  if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
2971
3288
  }
3289
+ // Docker 诊断
3290
+ if (isDockerAvailable()) {
3291
+ const dContainers = findOpenclawDockerContainers();
3292
+ if (dContainers.length > 0) {
3293
+ console.log(chalk.gray(` [诊断] Docker 容器 (含 openclaw/clawdbot/moltbot):`));
3294
+ for (const c of dContainers) {
3295
+ console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
3296
+ }
3297
+ } else {
3298
+ console.log(chalk.gray(` [诊断] Docker 可用,但未找到含 openclaw/clawdbot/moltbot 的容器`));
3299
+ }
3300
+ }
2972
3301
  // 尝试强制重启
2973
3302
  forceRestartGateway(resolved, nodeInfo, useNode, env).then(resolve);
2974
3303
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {