yymaxapi 1.0.27 → 1.0.29
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 +108 -41
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -721,46 +721,87 @@ let _selectedDockerContainer = null;
|
|
|
721
721
|
|
|
722
722
|
function isDockerAvailable() {
|
|
723
723
|
if (_dockerAvailCache !== null) return _dockerAvailCache;
|
|
724
|
-
|
|
725
|
-
|
|
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;
|
|
726
732
|
return _dockerAvailCache;
|
|
727
733
|
}
|
|
728
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
|
+
|
|
729
745
|
// 查找包含 openclaw/clawdbot/moltbot 的运行中容器
|
|
730
746
|
function findOpenclawDockerContainers() {
|
|
731
747
|
if (_dockerContainerCache !== null) return _dockerContainerCache;
|
|
732
748
|
if (!isDockerAvailable()) { _dockerContainerCache = []; return []; }
|
|
733
749
|
|
|
734
|
-
const ps = safeExec('
|
|
750
|
+
const ps = safeExec(dockerCmd('ps --format "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"'), { timeout: 10000 });
|
|
735
751
|
if (!ps.ok || !ps.output) { _dockerContainerCache = []; return []; }
|
|
736
752
|
|
|
737
753
|
const containers = [];
|
|
738
754
|
const lines = ps.output.split('\n').filter(Boolean);
|
|
739
755
|
|
|
740
756
|
for (const line of lines) {
|
|
741
|
-
|
|
742
|
-
const
|
|
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(' ');
|
|
743
761
|
if (!id) continue;
|
|
744
762
|
|
|
745
|
-
// 检查容器内是否有 openclaw/clawdbot/moltbot
|
|
763
|
+
// 检查容器内是否有 openclaw/clawdbot/moltbot(用 command -v 代替 which,兼容精简镜像)
|
|
746
764
|
for (const cli of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
747
|
-
const check = safeExec(`
|
|
748
|
-
if (check.ok && check.output) {
|
|
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()) {
|
|
749
767
|
containers.push({ id, name, image, status, cli, cliPath: check.output.trim() });
|
|
750
|
-
break;
|
|
768
|
+
break;
|
|
751
769
|
}
|
|
752
770
|
}
|
|
753
771
|
|
|
754
|
-
//
|
|
772
|
+
// 也检查常见路径(不依赖 which/command -v)
|
|
755
773
|
if (!containers.find(c => c.id === id)) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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 });
|
|
760
789
|
break;
|
|
761
790
|
}
|
|
762
791
|
}
|
|
763
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
|
+
}
|
|
764
805
|
}
|
|
765
806
|
|
|
766
807
|
_dockerContainerCache = containers;
|
|
@@ -800,16 +841,27 @@ async function selectDockerContainer() {
|
|
|
800
841
|
// 在 Docker 容器内执行命令
|
|
801
842
|
function execInDocker(containerId, cmd, options = {}) {
|
|
802
843
|
const escaped = cmd.replace(/'/g, "'\\''");
|
|
803
|
-
|
|
844
|
+
// 尝试 sh -c,fallback bash -lc
|
|
845
|
+
const r = safeExec(dockerCmd(`exec ${containerId} sh -c '${escaped}'`), { timeout: 30000, ...options });
|
|
846
|
+
if (r.ok) return r;
|
|
847
|
+
return safeExec(dockerCmd(`exec ${containerId} bash -lc '${escaped}'`), { timeout: 30000, ...options });
|
|
804
848
|
}
|
|
805
849
|
|
|
806
850
|
// 在 Docker 容器内异步执行命令(后台启动 gateway 等)
|
|
807
|
-
|
|
851
|
+
// shell 参数: 'sh -c' | 'bash -lc' | 'bash -c'
|
|
852
|
+
function spawnDetachedInDocker(containerId, cmd, shell = 'sh -c') {
|
|
808
853
|
try {
|
|
809
|
-
const
|
|
854
|
+
const shellParts = shell.split(' ');
|
|
855
|
+
const shellBin = shellParts[0];
|
|
856
|
+
const shellFlag = shellParts.slice(1).join(' ');
|
|
857
|
+
const prefix = _dockerNeedsSudo ? 'sudo' : 'docker';
|
|
858
|
+
const args = _dockerNeedsSudo
|
|
859
|
+
? ['-n', 'docker', 'exec', '-d', containerId, shellBin, shellFlag, cmd]
|
|
860
|
+
: ['exec', '-d', containerId, shellBin, shellFlag, cmd];
|
|
861
|
+
const child = spawn(prefix, args.filter(Boolean), {
|
|
810
862
|
detached: true,
|
|
811
863
|
stdio: 'ignore',
|
|
812
|
-
env: { ...process.env
|
|
864
|
+
env: { ...process.env }
|
|
813
865
|
});
|
|
814
866
|
child.unref();
|
|
815
867
|
return true;
|
|
@@ -835,8 +887,8 @@ function syncConfigToDocker(localConfigPath, containerId) {
|
|
|
835
887
|
// 先确定容器内哪个配置目录存在
|
|
836
888
|
for (const target of targets) {
|
|
837
889
|
const dir = path.posix.dirname(target);
|
|
838
|
-
safeExec(`
|
|
839
|
-
const cp = safeExec(`
|
|
890
|
+
safeExec(dockerCmd(`exec ${containerId} mkdir -p "${dir}"`), { timeout: 5000 });
|
|
891
|
+
const cp = safeExec(dockerCmd(`cp "${localConfigPath}" ${containerId}:${target}`), { timeout: 10000 });
|
|
840
892
|
if (cp.ok) {
|
|
841
893
|
console.log(chalk.gray(` 已同步配置到 Docker 容器: ${target}`));
|
|
842
894
|
return;
|
|
@@ -966,7 +1018,7 @@ function execAsyncInGatewayEnv(cmd, options = {}) {
|
|
|
966
1018
|
}
|
|
967
1019
|
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
968
1020
|
const escaped = cmd.replace(/'/g, "'\\''");
|
|
969
|
-
return { cmd: `
|
|
1021
|
+
return { cmd: dockerCmd(`exec ${_selectedDockerContainer.id} bash -lc '${escaped}'`), options };
|
|
970
1022
|
}
|
|
971
1023
|
return { cmd, options };
|
|
972
1024
|
}
|
|
@@ -1013,7 +1065,7 @@ function cleanupAgentProcesses() {
|
|
|
1013
1065
|
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
1014
1066
|
try {
|
|
1015
1067
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
1016
|
-
safeExec(`
|
|
1068
|
+
safeExec(dockerCmd(`exec ${_selectedDockerContainer.id} sh -c "pkill -f '${name}.*agent' 2>/dev/null || true"`), { timeout: 10000 });
|
|
1017
1069
|
}
|
|
1018
1070
|
} catch { /* ignore */ }
|
|
1019
1071
|
}
|
|
@@ -1658,6 +1710,7 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
1658
1710
|
if (container.cli === 'node') {
|
|
1659
1711
|
dockerCmds.push(`node ${container.cliPath} gateway`);
|
|
1660
1712
|
} else {
|
|
1713
|
+
if (container.cliPath) dockerCmds.push(`${container.cliPath} gateway`);
|
|
1661
1714
|
dockerCmds.push(`${container.cli} gateway`);
|
|
1662
1715
|
}
|
|
1663
1716
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
@@ -1666,10 +1719,13 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
1666
1719
|
|
|
1667
1720
|
for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
|
|
1668
1721
|
console.log(chalk.yellow(`⚠️ 尝试在容器内启动 Gateway: ${cmd}`));
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1722
|
+
// 尝试多种 shell
|
|
1723
|
+
for (const shell of ['sh -c', 'bash -lc', 'bash -c']) {
|
|
1724
|
+
if (spawnDetachedInDocker(container.id, cmd, shell)) {
|
|
1725
|
+
if (await waitForGateway(port, '127.0.0.1', 15000)) {
|
|
1726
|
+
console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${container.name} 内启动`));
|
|
1727
|
+
return { started: true, method: 'docker', container: container.name };
|
|
1728
|
+
}
|
|
1673
1729
|
}
|
|
1674
1730
|
}
|
|
1675
1731
|
}
|
|
@@ -3010,26 +3066,34 @@ async function restartGateway() {
|
|
|
3010
3066
|
|
|
3011
3067
|
const dockerCmds = [];
|
|
3012
3068
|
if (container.cli === 'node') {
|
|
3013
|
-
// 脚本路径模式(如 /opt/moltbot/moltbot.mjs)
|
|
3014
3069
|
dockerCmds.push(`node ${container.cliPath} gateway restart`);
|
|
3015
3070
|
} else {
|
|
3071
|
+
if (container.cliPath) dockerCmds.push(`${container.cliPath} gateway restart`);
|
|
3016
3072
|
dockerCmds.push(`${container.cli} gateway restart`);
|
|
3017
3073
|
}
|
|
3018
|
-
// 通用回退
|
|
3019
3074
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3020
3075
|
dockerCmds.push(`${name} gateway restart`);
|
|
3021
3076
|
}
|
|
3022
3077
|
|
|
3078
|
+
// 每个命令尝试多种 shell(sh -c / bash -lc / bash -c)
|
|
3079
|
+
const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
|
|
3080
|
+
|
|
3023
3081
|
return new Promise((resolve) => {
|
|
3082
|
+
// 展开为 [cmd1+sh, cmd1+bash-lc, cmd1+bash-c, cmd2+sh, ...]
|
|
3083
|
+
const allAttempts = [];
|
|
3084
|
+
for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
|
|
3085
|
+
for (const shell of shellVariants) {
|
|
3086
|
+
allAttempts.push(dockerCmd(`exec ${container.id} ${shell} "${cmd}"`));
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3024
3089
|
let tried = 0;
|
|
3025
3090
|
const tryNext = () => {
|
|
3026
|
-
if (tried >=
|
|
3091
|
+
if (tried >= allAttempts.length) {
|
|
3027
3092
|
console.log(chalk.yellow('Docker 容器内 Gateway 重启失败,尝试本地重启...'));
|
|
3028
3093
|
restartGatewayNative().then(resolve);
|
|
3029
3094
|
return;
|
|
3030
3095
|
}
|
|
3031
|
-
const
|
|
3032
|
-
const fullCmd = `docker exec ${container.id} bash -lc "${cmd}"`;
|
|
3096
|
+
const fullCmd = allAttempts[tried++];
|
|
3033
3097
|
exec(fullCmd, { timeout: 30000 }, (error) => {
|
|
3034
3098
|
if (error) {
|
|
3035
3099
|
tryNext();
|
|
@@ -3095,9 +3159,9 @@ async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort
|
|
|
3095
3159
|
// 1. 杀掉容器内旧 Gateway 进程
|
|
3096
3160
|
try {
|
|
3097
3161
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3098
|
-
safeExec(`
|
|
3162
|
+
safeExec(dockerCmd(`exec ${cid} sh -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
|
|
3099
3163
|
}
|
|
3100
|
-
safeExec(`
|
|
3164
|
+
safeExec(dockerCmd(`exec ${cid} sh -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
|
|
3101
3165
|
console.log(chalk.gray(' 已尝试清理容器内旧 Gateway 进程'));
|
|
3102
3166
|
} catch { /* ignore */ }
|
|
3103
3167
|
|
|
@@ -3108,6 +3172,7 @@ async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort
|
|
|
3108
3172
|
if (_selectedDockerContainer.cli === 'node') {
|
|
3109
3173
|
dockerCmds.push(`node ${_selectedDockerContainer.cliPath} gateway`);
|
|
3110
3174
|
} else {
|
|
3175
|
+
if (_selectedDockerContainer.cliPath) dockerCmds.push(`${_selectedDockerContainer.cliPath} gateway`);
|
|
3111
3176
|
dockerCmds.push(`${_selectedDockerContainer.cli} gateway`);
|
|
3112
3177
|
}
|
|
3113
3178
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
@@ -3116,10 +3181,12 @@ async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort
|
|
|
3116
3181
|
|
|
3117
3182
|
for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
|
|
3118
3183
|
console.log(chalk.gray(` 尝试在容器内启动: ${cmd}`));
|
|
3119
|
-
|
|
3120
|
-
if (
|
|
3121
|
-
|
|
3122
|
-
|
|
3184
|
+
for (const shell of ['sh -c', 'bash -lc', 'bash -c']) {
|
|
3185
|
+
if (spawnDetachedInDocker(cid, cmd, shell)) {
|
|
3186
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
3187
|
+
console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${cName} 内强制重启成功`));
|
|
3188
|
+
return true;
|
|
3189
|
+
}
|
|
3123
3190
|
}
|
|
3124
3191
|
}
|
|
3125
3192
|
}
|
|
@@ -3259,12 +3326,12 @@ async function restartGatewayNative() {
|
|
|
3259
3326
|
const dockerContainers = findOpenclawDockerContainers();
|
|
3260
3327
|
for (const c of dockerContainers) {
|
|
3261
3328
|
if (c.cli === 'node') {
|
|
3262
|
-
commands.push(`
|
|
3263
|
-
} else {
|
|
3264
|
-
commands.push(`
|
|
3329
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath} gateway restart"`));
|
|
3330
|
+
} else if (c.cli !== 'unknown') {
|
|
3331
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} gateway restart"`));
|
|
3265
3332
|
}
|
|
3266
3333
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
3267
|
-
commands.push(`
|
|
3334
|
+
commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} gateway restart"`));
|
|
3268
3335
|
}
|
|
3269
3336
|
}
|
|
3270
3337
|
}
|