yymaxapi 1.0.26 → 1.0.28
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 +380 -6
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -708,11 +708,191 @@ 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
|
+
// 优先用 docker ps(权限要求更低),fallback 到 docker info
|
|
725
|
+
const r = safeExec('docker ps --no-trunc -q', { timeout: 8000 });
|
|
726
|
+
if (r.ok) { _dockerAvailCache = true; return true; }
|
|
727
|
+
const r2 = safeExec('docker info', { timeout: 8000 });
|
|
728
|
+
if (r2.ok) { _dockerAvailCache = true; return true; }
|
|
729
|
+
// 可能需要 sudo
|
|
730
|
+
const r3 = safeExec('sudo -n docker ps -q', { timeout: 8000 });
|
|
731
|
+
_dockerAvailCache = r3.ok;
|
|
732
|
+
return _dockerAvailCache;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// 封装 docker 命令(自动处理 sudo)
|
|
736
|
+
let _dockerNeedsSudo = null;
|
|
737
|
+
function dockerCmd(cmd) {
|
|
738
|
+
if (_dockerNeedsSudo === null) {
|
|
739
|
+
const test = safeExec('docker ps -q', { timeout: 5000 });
|
|
740
|
+
_dockerNeedsSudo = !test.ok;
|
|
741
|
+
}
|
|
742
|
+
return _dockerNeedsSudo ? `sudo -n docker ${cmd}` : `docker ${cmd}`;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// 查找包含 openclaw/clawdbot/moltbot 的运行中容器
|
|
746
|
+
function findOpenclawDockerContainers() {
|
|
747
|
+
if (_dockerContainerCache !== null) return _dockerContainerCache;
|
|
748
|
+
if (!isDockerAvailable()) { _dockerContainerCache = []; return []; }
|
|
749
|
+
|
|
750
|
+
const ps = safeExec(dockerCmd('ps --format "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"'), { timeout: 10000 });
|
|
751
|
+
if (!ps.ok || !ps.output) { _dockerContainerCache = []; return []; }
|
|
752
|
+
|
|
753
|
+
const containers = [];
|
|
754
|
+
const lines = ps.output.split('\n').filter(Boolean);
|
|
755
|
+
|
|
756
|
+
for (const line of lines) {
|
|
757
|
+
// 兼容 tab 分隔和空格分隔
|
|
758
|
+
const parts = line.includes('\t') ? line.split('\t') : line.split(/\s{2,}/);
|
|
759
|
+
const [id, name, image, ...statusParts] = parts;
|
|
760
|
+
const status = statusParts.join(' ');
|
|
761
|
+
if (!id) continue;
|
|
762
|
+
|
|
763
|
+
// 检查容器内是否有 openclaw/clawdbot/moltbot(用 command -v 代替 which,兼容精简镜像)
|
|
764
|
+
for (const cli of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
765
|
+
const check = safeExec(dockerCmd(`exec ${id} sh -c "command -v ${cli} 2>/dev/null || which ${cli} 2>/dev/null"`), { timeout: 8000 });
|
|
766
|
+
if (check.ok && check.output && check.output.trim()) {
|
|
767
|
+
containers.push({ id, name, image, status, cli, cliPath: check.output.trim() });
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// 也检查常见路径(不依赖 which/command -v)
|
|
773
|
+
if (!containers.find(c => c.id === id)) {
|
|
774
|
+
const knownPaths = [
|
|
775
|
+
'/opt/moltbot/moltbot.mjs',
|
|
776
|
+
'/opt/moltbot/bin/moltbot.mjs',
|
|
777
|
+
'/opt/moltbot/bin/moltbot.js',
|
|
778
|
+
'/opt/moltbot/src/moltbot.mjs',
|
|
779
|
+
'/usr/local/bin/openclaw',
|
|
780
|
+
'/usr/local/bin/clawdbot',
|
|
781
|
+
'/usr/local/bin/moltbot',
|
|
782
|
+
];
|
|
783
|
+
for (const p of knownPaths) {
|
|
784
|
+
const check = safeExec(dockerCmd(`exec ${id} sh -c "test -f '${p}' && echo '${p}'"`), { timeout: 5000 });
|
|
785
|
+
if (check.ok && check.output && check.output.trim()) {
|
|
786
|
+
const isMjs = p.endsWith('.mjs') || p.endsWith('.js');
|
|
787
|
+
const cliName = isMjs ? 'node' : path.basename(p);
|
|
788
|
+
containers.push({ id, name, image, status, cli: cliName, cliPath: p });
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// 最后检查容器镜像名/容器名是否包含关键字(即使内部 CLI 路径未知)
|
|
795
|
+
if (!containers.find(c => c.id === id)) {
|
|
796
|
+
const combined = `${name} ${image}`.toLowerCase();
|
|
797
|
+
if (/openclaw|clawdbot|moltbot/.test(combined)) {
|
|
798
|
+
// 尝试在容器内找任何 node 进程带 gateway 关键字
|
|
799
|
+
const findProc = safeExec(dockerCmd(`exec ${id} sh -c "ps aux 2>/dev/null | grep -E 'openclaw|clawdbot|moltbot' | grep -v grep | head -1"`), { timeout: 5000 });
|
|
800
|
+
if (findProc.ok && findProc.output && findProc.output.trim()) {
|
|
801
|
+
containers.push({ id, name, image, status, cli: 'unknown', cliPath: '' });
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
_dockerContainerCache = containers;
|
|
808
|
+
return containers;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// 交互式选择 Docker 容器
|
|
812
|
+
async function selectDockerContainer() {
|
|
813
|
+
if (_selectedDockerContainer) return _selectedDockerContainer;
|
|
814
|
+
|
|
815
|
+
const containers = findOpenclawDockerContainers();
|
|
816
|
+
if (containers.length === 0) return null;
|
|
817
|
+
|
|
818
|
+
if (containers.length === 1) {
|
|
819
|
+
_selectedDockerContainer = containers[0];
|
|
820
|
+
console.log(chalk.green(`✅ 自动选择 Docker 容器: ${containers[0].name} (${containers[0].image})`));
|
|
821
|
+
return _selectedDockerContainer;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// 多个容器,让用户选择
|
|
825
|
+
console.log(chalk.cyan('\n检测到多个包含 OpenClaw/Clawdbot/Moltbot 的 Docker 容器:'));
|
|
826
|
+
const { selected } = await inquirer.prompt([{
|
|
827
|
+
type: 'list',
|
|
828
|
+
name: 'selected',
|
|
829
|
+
message: '请选择要操作的容器:',
|
|
830
|
+
choices: containers.map((c, i) => ({
|
|
831
|
+
name: `${c.name} (${c.image}) [${c.cli}] - ${c.status}`,
|
|
832
|
+
value: i
|
|
833
|
+
}))
|
|
834
|
+
}]);
|
|
835
|
+
|
|
836
|
+
_selectedDockerContainer = containers[selected];
|
|
837
|
+
console.log(chalk.green(`✅ 已选择容器: ${_selectedDockerContainer.name}`));
|
|
838
|
+
return _selectedDockerContainer;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// 在 Docker 容器内执行命令
|
|
842
|
+
function execInDocker(containerId, cmd, options = {}) {
|
|
843
|
+
const escaped = cmd.replace(/'/g, "'\\''");
|
|
844
|
+
return safeExec(dockerCmd(`exec ${containerId} bash -lc '${escaped}'`), { timeout: 30000, ...options });
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// 在 Docker 容器内异步执行命令(后台启动 gateway 等)
|
|
848
|
+
function spawnDetachedInDocker(containerId, cmd, env = {}) {
|
|
849
|
+
try {
|
|
850
|
+
const prefix = _dockerNeedsSudo ? 'sudo' : 'docker';
|
|
851
|
+
const args = _dockerNeedsSudo
|
|
852
|
+
? ['-n', 'docker', 'exec', '-d', containerId, 'bash', '-lc', cmd]
|
|
853
|
+
: ['exec', '-d', containerId, 'bash', '-lc', cmd];
|
|
854
|
+
const child = spawn(prefix, args, {
|
|
855
|
+
detached: true,
|
|
856
|
+
stdio: 'ignore',
|
|
857
|
+
env: { ...process.env, ...env }
|
|
858
|
+
});
|
|
859
|
+
child.unref();
|
|
860
|
+
return true;
|
|
861
|
+
} catch { return false; }
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// 获取 Docker 容器内的 CLI 二进制路径
|
|
865
|
+
function getDockerCliBinary(container) {
|
|
866
|
+
if (!container) return null;
|
|
867
|
+
return container.cliPath || null;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// 同步配置到 Docker 容器(将本地 openclaw.json 复制到容器内)
|
|
871
|
+
function syncConfigToDocker(localConfigPath, containerId) {
|
|
872
|
+
try {
|
|
873
|
+
// 尝试多个目标路径
|
|
874
|
+
const targets = [
|
|
875
|
+
'/root/.openclaw/openclaw.json',
|
|
876
|
+
'/root/.clawdbot/openclaw.json',
|
|
877
|
+
'/root/.moltbot/moltbot.json',
|
|
878
|
+
'/home/.openclaw/openclaw.json',
|
|
879
|
+
];
|
|
880
|
+
// 先确定容器内哪个配置目录存在
|
|
881
|
+
for (const target of targets) {
|
|
882
|
+
const dir = path.posix.dirname(target);
|
|
883
|
+
safeExec(dockerCmd(`exec ${containerId} mkdir -p "${dir}"`), { timeout: 5000 });
|
|
884
|
+
const cp = safeExec(dockerCmd(`cp "${localConfigPath}" ${containerId}:${target}`), { timeout: 10000 });
|
|
885
|
+
if (cp.ok) {
|
|
886
|
+
console.log(chalk.gray(` 已同步配置到 Docker 容器: ${target}`));
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
} catch { /* best-effort */ }
|
|
711
891
|
}
|
|
712
892
|
|
|
713
893
|
// ============ Gateway 环境检测(集中化) ============
|
|
714
|
-
// 所有 WSL 相关逻辑统一通过 detectGatewayEnv() 路由
|
|
715
|
-
// 返回 'native' | 'wsl',结果缓存,整个进程生命周期只检测一次
|
|
894
|
+
// 所有 WSL/Docker 相关逻辑统一通过 detectGatewayEnv() 路由
|
|
895
|
+
// 返回 'native' | 'wsl' | 'docker',结果缓存,整个进程生命周期只检测一次
|
|
716
896
|
|
|
717
897
|
let _gwEnvCache = null;
|
|
718
898
|
let _wslAvailCache = null;
|
|
@@ -758,7 +938,20 @@ function getWslCliBinary() {
|
|
|
758
938
|
|
|
759
939
|
function detectGatewayEnv(port = 18789) {
|
|
760
940
|
if (_gwEnvCache !== null) return _gwEnvCache;
|
|
941
|
+
|
|
942
|
+
// 非 Windows 平台:先检查本地 CLI,找不到再检查 Docker
|
|
761
943
|
if (process.platform !== 'win32' || !isWslAvailable()) {
|
|
944
|
+
const nativeCli = resolveCliBinary();
|
|
945
|
+
if (nativeCli) {
|
|
946
|
+
_gwEnvCache = 'native';
|
|
947
|
+
return 'native';
|
|
948
|
+
}
|
|
949
|
+
// 本地没有 CLI,检查 Docker 容器
|
|
950
|
+
const dockerContainers = findOpenclawDockerContainers();
|
|
951
|
+
if (dockerContainers.length > 0) {
|
|
952
|
+
_gwEnvCache = 'docker';
|
|
953
|
+
return 'docker';
|
|
954
|
+
}
|
|
762
955
|
_gwEnvCache = 'native';
|
|
763
956
|
return 'native';
|
|
764
957
|
}
|
|
@@ -787,17 +980,26 @@ function detectGatewayEnv(port = 18789) {
|
|
|
787
980
|
_gwEnvCache = 'wsl';
|
|
788
981
|
return 'wsl';
|
|
789
982
|
}
|
|
983
|
+
// Fallback: 检查 Docker 容器
|
|
984
|
+
const dockerContainers = findOpenclawDockerContainers();
|
|
985
|
+
if (dockerContainers.length > 0) {
|
|
986
|
+
_gwEnvCache = 'docker';
|
|
987
|
+
return 'docker';
|
|
988
|
+
}
|
|
790
989
|
}
|
|
791
990
|
_gwEnvCache = 'native';
|
|
792
991
|
return 'native';
|
|
793
992
|
}
|
|
794
993
|
|
|
795
|
-
// 在 Gateway 环境中执行命令(WSL
|
|
994
|
+
// 在 Gateway 环境中执行命令(WSL/Docker 自动包裹)
|
|
796
995
|
function execInGatewayEnv(cmd, options = {}) {
|
|
797
996
|
if (detectGatewayEnv() === 'wsl') {
|
|
798
997
|
const escaped = cmd.replace(/'/g, "'\\''");
|
|
799
998
|
return safeExec(`wsl -- bash -lc '${escaped}'`, options);
|
|
800
999
|
}
|
|
1000
|
+
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
1001
|
+
return execInDocker(_selectedDockerContainer.id, cmd, options);
|
|
1002
|
+
}
|
|
801
1003
|
return safeExec(cmd, options);
|
|
802
1004
|
}
|
|
803
1005
|
|
|
@@ -807,6 +1009,10 @@ function execAsyncInGatewayEnv(cmd, options = {}) {
|
|
|
807
1009
|
const escaped = cmd.replace(/'/g, "'\\''");
|
|
808
1010
|
return { cmd: `wsl -- bash -lc '${escaped}'`, options };
|
|
809
1011
|
}
|
|
1012
|
+
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
1013
|
+
const escaped = cmd.replace(/'/g, "'\\''");
|
|
1014
|
+
return { cmd: dockerCmd(`exec ${_selectedDockerContainer.id} bash -lc '${escaped}'`), options };
|
|
1015
|
+
}
|
|
810
1016
|
return { cmd, options };
|
|
811
1017
|
}
|
|
812
1018
|
|
|
@@ -848,6 +1054,14 @@ function cleanupAgentProcesses() {
|
|
|
848
1054
|
execSync(`pkill -f '${name}.*agent' 2>/dev/null || true`, { stdio: 'ignore' });
|
|
849
1055
|
}
|
|
850
1056
|
}
|
|
1057
|
+
// Docker 容器内的 agent 也要清理
|
|
1058
|
+
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
1059
|
+
try {
|
|
1060
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
1061
|
+
safeExec(dockerCmd(`exec ${_selectedDockerContainer.id} bash -c "pkill -f '${name}.*agent' 2>/dev/null || true"`), { timeout: 10000 });
|
|
1062
|
+
}
|
|
1063
|
+
} catch { /* ignore */ }
|
|
1064
|
+
}
|
|
851
1065
|
console.log(chalk.gray('已清理残留 agent 进程'));
|
|
852
1066
|
} catch { /* ignore */ }
|
|
853
1067
|
}
|
|
@@ -1478,6 +1692,35 @@ function installMacGatewayDaemon() {
|
|
|
1478
1692
|
async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
1479
1693
|
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
1480
1694
|
|
|
1695
|
+
// Docker 容器内启动 Gateway
|
|
1696
|
+
if (detectGatewayEnv() === 'docker') {
|
|
1697
|
+
const container = await selectDockerContainer();
|
|
1698
|
+
if (!container) return { started: false };
|
|
1699
|
+
|
|
1700
|
+
console.log(chalk.yellow(`⚠️ Gateway 未检测到运行,尝试在 Docker 容器 ${container.name} 内启动...`));
|
|
1701
|
+
|
|
1702
|
+
const dockerCmds = [];
|
|
1703
|
+
if (container.cli === 'node') {
|
|
1704
|
+
dockerCmds.push(`node ${container.cliPath} gateway`);
|
|
1705
|
+
} else {
|
|
1706
|
+
dockerCmds.push(`${container.cli} gateway`);
|
|
1707
|
+
}
|
|
1708
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
1709
|
+
dockerCmds.push(`${name} gateway`);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
|
|
1713
|
+
console.log(chalk.yellow(`⚠️ 尝试在容器内启动 Gateway: ${cmd}`));
|
|
1714
|
+
if (spawnDetachedInDocker(container.id, cmd)) {
|
|
1715
|
+
if (await waitForGateway(port, '127.0.0.1', 15000)) {
|
|
1716
|
+
console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${container.name} 内启动`));
|
|
1717
|
+
return { started: true, method: 'docker', container: container.name };
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
return { started: false };
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1481
1724
|
if (process.platform === 'darwin' && allowAutoDaemon) {
|
|
1482
1725
|
console.log(chalk.yellow('⚠️ Gateway 未检测到运行,尝试在 macOS 后台启动 (LaunchAgent)...'));
|
|
1483
1726
|
const daemonResult = installMacGatewayDaemon();
|
|
@@ -2630,6 +2873,16 @@ async function testConnection(paths, args = {}) {
|
|
|
2630
2873
|
|
|
2631
2874
|
const allowAutoDaemon = !(args['no-daemon'] || args.noDaemon);
|
|
2632
2875
|
|
|
2876
|
+
// 如果检测到 Docker 环境,先让用户选择容器
|
|
2877
|
+
if (detectGatewayEnv() === 'docker') {
|
|
2878
|
+
const container = await selectDockerContainer();
|
|
2879
|
+
if (!container) {
|
|
2880
|
+
console.log(chalk.red('❌ 未找到包含 OpenClaw/Clawdbot/Moltbot 的 Docker 容器'));
|
|
2881
|
+
console.log(chalk.gray(' 请确保容器正在运行且已安装 openclaw/clawdbot/moltbot'));
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2633
2886
|
// 步骤0: 清理所有挂死的 agent 进程
|
|
2634
2887
|
cleanupAgentProcesses();
|
|
2635
2888
|
|
|
@@ -2650,9 +2903,15 @@ async function testConnection(paths, args = {}) {
|
|
|
2650
2903
|
|
|
2651
2904
|
if (!gatewayRunning) {
|
|
2652
2905
|
gwSpinner.fail('Gateway 未运行');
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2906
|
+
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
2907
|
+
console.log(chalk.gray(` 请在 Docker 容器内启动 Gateway:`));
|
|
2908
|
+
console.log(chalk.gray(` docker exec -it ${_selectedDockerContainer.id} bash`));
|
|
2909
|
+
console.log(chalk.gray(` 然后执行: ${_selectedDockerContainer.cli === 'node' ? `node ${_selectedDockerContainer.cliPath}` : _selectedDockerContainer.cli} gateway`));
|
|
2910
|
+
} else {
|
|
2911
|
+
console.log(chalk.gray(' 请在新的终端执行: openclaw gateway'));
|
|
2912
|
+
console.log(chalk.gray(' 或: clawdbot gateway'));
|
|
2913
|
+
console.log(chalk.gray(' 或: moltbot gateway'));
|
|
2914
|
+
}
|
|
2656
2915
|
return;
|
|
2657
2916
|
}
|
|
2658
2917
|
|
|
@@ -2785,6 +3044,50 @@ async function restartGateway() {
|
|
|
2785
3044
|
|
|
2786
3045
|
const gwEnv = detectGatewayEnv();
|
|
2787
3046
|
|
|
3047
|
+
// 如果 Gateway 在 Docker 容器里,通过 docker exec 重启
|
|
3048
|
+
if (gwEnv === 'docker') {
|
|
3049
|
+
const container = await selectDockerContainer();
|
|
3050
|
+
if (!container) {
|
|
3051
|
+
console.log(chalk.red('❌ 未找到可用的 Docker 容器'));
|
|
3052
|
+
return restartGatewayNative();
|
|
3053
|
+
}
|
|
3054
|
+
console.log(chalk.gray(` [检测] Gateway 运行在 Docker 容器: ${container.name} (${container.image})`));
|
|
3055
|
+
|
|
3056
|
+
const dockerCmds = [];
|
|
3057
|
+
if (container.cli === 'node') {
|
|
3058
|
+
// 脚本路径模式(如 /opt/moltbot/moltbot.mjs)
|
|
3059
|
+
dockerCmds.push(`node ${container.cliPath} gateway restart`);
|
|
3060
|
+
} else {
|
|
3061
|
+
dockerCmds.push(`${container.cli} gateway restart`);
|
|
3062
|
+
}
|
|
3063
|
+
// 通用回退
|
|
3064
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3065
|
+
dockerCmds.push(`${name} gateway restart`);
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
return new Promise((resolve) => {
|
|
3069
|
+
let tried = 0;
|
|
3070
|
+
const tryNext = () => {
|
|
3071
|
+
if (tried >= dockerCmds.length) {
|
|
3072
|
+
console.log(chalk.yellow('Docker 容器内 Gateway 重启失败,尝试本地重启...'));
|
|
3073
|
+
restartGatewayNative().then(resolve);
|
|
3074
|
+
return;
|
|
3075
|
+
}
|
|
3076
|
+
const cmd = dockerCmds[tried++];
|
|
3077
|
+
const fullCmd = dockerCmd(`exec ${container.id} bash -lc "${cmd}"`);
|
|
3078
|
+
exec(fullCmd, { timeout: 30000 }, (error) => {
|
|
3079
|
+
if (error) {
|
|
3080
|
+
tryNext();
|
|
3081
|
+
} else {
|
|
3082
|
+
console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
|
|
3083
|
+
resolve(true);
|
|
3084
|
+
}
|
|
3085
|
+
});
|
|
3086
|
+
};
|
|
3087
|
+
tryNext();
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
|
|
2788
3091
|
// 如果 Gateway 在 WSL 里,优先用 wsl -- 重启
|
|
2789
3092
|
if (gwEnv === 'wsl') {
|
|
2790
3093
|
console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
|
|
@@ -2828,6 +3131,50 @@ async function restartGateway() {
|
|
|
2828
3131
|
async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort = 18789) {
|
|
2829
3132
|
console.log(chalk.yellow('\n🔄 尝试强制重启 Gateway(杀旧进程 → 启动新进程)...'));
|
|
2830
3133
|
|
|
3134
|
+
// Docker 容器内强制重启
|
|
3135
|
+
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
3136
|
+
const cid = _selectedDockerContainer.id;
|
|
3137
|
+
const cName = _selectedDockerContainer.name;
|
|
3138
|
+
console.log(chalk.gray(` [Docker] 在容器 ${cName} 内强制重启...`));
|
|
3139
|
+
|
|
3140
|
+
// 1. 杀掉容器内旧 Gateway 进程
|
|
3141
|
+
try {
|
|
3142
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3143
|
+
safeExec(dockerCmd(`exec ${cid} bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
|
|
3144
|
+
}
|
|
3145
|
+
safeExec(dockerCmd(`exec ${cid} bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
|
|
3146
|
+
console.log(chalk.gray(' 已尝试清理容器内旧 Gateway 进程'));
|
|
3147
|
+
} catch { /* ignore */ }
|
|
3148
|
+
|
|
3149
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
3150
|
+
|
|
3151
|
+
// 2. 启动新 Gateway
|
|
3152
|
+
const dockerCmds = [];
|
|
3153
|
+
if (_selectedDockerContainer.cli === 'node') {
|
|
3154
|
+
dockerCmds.push(`node ${_selectedDockerContainer.cliPath} gateway`);
|
|
3155
|
+
} else {
|
|
3156
|
+
dockerCmds.push(`${_selectedDockerContainer.cli} gateway`);
|
|
3157
|
+
}
|
|
3158
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3159
|
+
dockerCmds.push(`${name} gateway`);
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
|
|
3163
|
+
console.log(chalk.gray(` 尝试在容器内启动: ${cmd}`));
|
|
3164
|
+
if (spawnDetachedInDocker(cid, cmd)) {
|
|
3165
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
3166
|
+
console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${cName} 内强制重启成功`));
|
|
3167
|
+
return true;
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
console.log(chalk.red('❌ Docker 容器内强制重启也失败了'));
|
|
3173
|
+
console.log(chalk.gray(` 请手动进入容器重启: docker exec -it ${cid} bash`));
|
|
3174
|
+
console.log(chalk.gray(` 然后执行: openclaw gateway / clawdbot gateway / moltbot gateway`));
|
|
3175
|
+
return false;
|
|
3176
|
+
}
|
|
3177
|
+
|
|
2831
3178
|
// 1. 杀掉旧 Gateway 进程
|
|
2832
3179
|
try {
|
|
2833
3180
|
if (process.platform === 'win32') {
|
|
@@ -2952,6 +3299,21 @@ async function restartGatewayNative() {
|
|
|
2952
3299
|
}
|
|
2953
3300
|
}
|
|
2954
3301
|
|
|
3302
|
+
// Docker: 追加 Docker 容器命令作为额外回退
|
|
3303
|
+
if (isDockerAvailable()) {
|
|
3304
|
+
const dockerContainers = findOpenclawDockerContainers();
|
|
3305
|
+
for (const c of dockerContainers) {
|
|
3306
|
+
if (c.cli === 'node') {
|
|
3307
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath} gateway restart"`));
|
|
3308
|
+
} else if (c.cli !== 'unknown') {
|
|
3309
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} gateway restart"`));
|
|
3310
|
+
}
|
|
3311
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3312
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} gateway restart"`));
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
|
|
2955
3317
|
return new Promise((resolve) => {
|
|
2956
3318
|
let tried = 0;
|
|
2957
3319
|
|
|
@@ -2969,6 +3331,18 @@ async function restartGatewayNative() {
|
|
|
2969
3331
|
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
3332
|
if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
|
|
2971
3333
|
}
|
|
3334
|
+
// Docker 诊断
|
|
3335
|
+
if (isDockerAvailable()) {
|
|
3336
|
+
const dContainers = findOpenclawDockerContainers();
|
|
3337
|
+
if (dContainers.length > 0) {
|
|
3338
|
+
console.log(chalk.gray(` [诊断] Docker 容器 (含 openclaw/clawdbot/moltbot):`));
|
|
3339
|
+
for (const c of dContainers) {
|
|
3340
|
+
console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
|
|
3341
|
+
}
|
|
3342
|
+
} else {
|
|
3343
|
+
console.log(chalk.gray(` [诊断] Docker 可用,但未找到含 openclaw/clawdbot/moltbot 的容器`));
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
2972
3346
|
// 尝试强制重启
|
|
2973
3347
|
forceRestartGateway(resolved, nodeInfo, useNode, env).then(resolve);
|
|
2974
3348
|
return;
|