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.
- package/bin/yymaxapi.js +343 -14
- 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
|
|
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: `*
|
|
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
|
-
|
|
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: `*
|
|
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
|
-
|
|
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
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
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;
|