limbo-ai 1.14.0 → 1.15.0
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/cli.js +81 -7
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -167,11 +167,12 @@ function composeContent() {
|
|
|
167
167
|
OPENCLAW_CONFIG_PATH: /home/limbo/.openclaw/openclaw.json
|
|
168
168
|
OPENCLAW_STATE_DIR: /home/limbo/.openclaw
|
|
169
169
|
LIMBO_PORT: "${PORT}"
|
|
170
|
+
NODE_OPTIONS: "\${LIMBO_NODE_OPTIONS:---max-old-space-size=1024}"
|
|
170
171
|
healthcheck:
|
|
171
172
|
test:
|
|
172
173
|
- CMD-SHELL
|
|
173
174
|
- >-
|
|
174
|
-
node -e "const s=require('net').connect(${PORT},'127.0.0.1');const
|
|
175
|
+
NODE_OPTIONS= node -e "const s=require('net').connect(${PORT},'127.0.0.1');const
|
|
175
176
|
done=(c)=>{try{s.destroy()}catch{};process.exit(c)};s.on('connect',()=>done(0));s.on('error',()=>done(1));setTimeout(()=>done(1),2000);"
|
|
176
177
|
interval: 30s
|
|
177
178
|
timeout: 10s
|
|
@@ -226,6 +227,7 @@ function composeContentHardened() {
|
|
|
226
227
|
OPENCLAW_CONFIG_PATH: /home/limbo/.openclaw/openclaw.json
|
|
227
228
|
OPENCLAW_STATE_DIR: /home/limbo/.openclaw
|
|
228
229
|
LIMBO_PORT: "${PORT}"
|
|
230
|
+
NODE_OPTIONS: "\${LIMBO_NODE_OPTIONS:---max-old-space-size=1024}"
|
|
229
231
|
HTTP_PROXY: http://squid:3128
|
|
230
232
|
HTTPS_PROXY: http://squid:3128
|
|
231
233
|
NO_PROXY: "127.0.0.1,localhost"
|
|
@@ -235,7 +237,7 @@ function composeContentHardened() {
|
|
|
235
237
|
test:
|
|
236
238
|
- CMD-SHELL
|
|
237
239
|
- >-
|
|
238
|
-
node -e "const s=require('net').connect(${PORT},'127.0.0.1');const
|
|
240
|
+
NODE_OPTIONS= node -e "const s=require('net').connect(${PORT},'127.0.0.1');const
|
|
239
241
|
done=(c)=>{try{s.destroy()}catch{};process.exit(c)};s.on('connect',()=>done(0));s.on('error',()=>done(1));setTimeout(()=>done(1),2000);"
|
|
240
242
|
interval: 30s
|
|
241
243
|
timeout: 10s
|
|
@@ -361,6 +363,12 @@ const TEXT = {
|
|
|
361
363
|
configFlowSlow: 'This may take a couple of minutes.',
|
|
362
364
|
configFlowDone: 'Configuration applied.',
|
|
363
365
|
configFlowFailed: 'Could not apply configuration. Check your settings and try again.',
|
|
366
|
+
configOom: 'Configuration failed: out of memory. The server does not have enough free RAM.',
|
|
367
|
+
configOomContainers: (n) => ` Found ${n} running Limbo container(s) using memory. Stop them first:\n npx limbo stop`,
|
|
368
|
+
configOomHint: ' Try closing other programs or upgrading to a server with more RAM.',
|
|
369
|
+
configOomOverride: ' If your server has enough RAM, increase the limit in .env:\n LIMBO_NODE_OPTIONS=--max-old-space-size=2048',
|
|
370
|
+
staleContainersFound: (n) => `Found ${n} running Limbo container(s). Stopping to free memory...`,
|
|
371
|
+
staleContainersStopped: 'Stopped existing containers.',
|
|
364
372
|
composing: 'Initializing...',
|
|
365
373
|
success: 'Limbo is running!',
|
|
366
374
|
gateway: 'Gateway',
|
|
@@ -373,7 +381,7 @@ const TEXT = {
|
|
|
373
381
|
nonTelegramHintTitle: 'No Telegram? You can still talk to Limbo through another agent.',
|
|
374
382
|
nonTelegramPromptIntro: 'Suggested prompt:',
|
|
375
383
|
nonTelegramPrompt: (token) => `Connect to my Limbo gateway at ws://127.0.0.1:${PORT} using token ${token}. Use Limbo as my memory layer: save notes, recall context, and update maps of content when I ask.`,
|
|
376
|
-
dockerMissing: 'Docker is not installed or
|
|
384
|
+
dockerMissing: 'Docker is not installed or the Compose plugin is missing.\n Docker Engine: https://docs.docker.com/engine/install/\n Compose plugin: sudo apt-get install docker-compose-plugin',
|
|
377
385
|
installMissing: 'Limbo is not installed. Run: npx limbo start',
|
|
378
386
|
helpTitle: 'limbo - personal AI memory agent',
|
|
379
387
|
usage: 'Usage',
|
|
@@ -475,6 +483,12 @@ const TEXT = {
|
|
|
475
483
|
configFlowSlow: 'Esto puede tardar un par de minutos.',
|
|
476
484
|
configFlowDone: 'Configuracion aplicada.',
|
|
477
485
|
configFlowFailed: 'No se pudo aplicar la configuracion. Revisa los ajustes e intenta de nuevo.',
|
|
486
|
+
configOom: 'La configuracion fallo: sin memoria. El servidor no tiene suficiente RAM libre.',
|
|
487
|
+
configOomContainers: (n) => ` Se encontraron ${n} container(s) de Limbo corriendo que usan memoria. Frenalos primero:\n npx limbo stop`,
|
|
488
|
+
configOomHint: ' Proba cerrando otros programas o usando un servidor con mas RAM.',
|
|
489
|
+
configOomOverride: ' Si tu servidor tiene suficiente RAM, podes aumentar el limite en .env:\n LIMBO_NODE_OPTIONS=--max-old-space-size=2048',
|
|
490
|
+
staleContainersFound: (n) => `Se encontraron ${n} container(s) de Limbo corriendo. Frenando para liberar memoria...`,
|
|
491
|
+
staleContainersStopped: 'Containers existentes frenados.',
|
|
478
492
|
composing: 'Inicializando...',
|
|
479
493
|
success: 'Limbo esta corriendo!',
|
|
480
494
|
gateway: 'Gateway',
|
|
@@ -487,7 +501,7 @@ const TEXT = {
|
|
|
487
501
|
nonTelegramHintTitle: 'Sin Telegram? Igual puedes hablar con Limbo desde otro agente.',
|
|
488
502
|
nonTelegramPromptIntro: 'Prompt sugerido:',
|
|
489
503
|
nonTelegramPrompt: (token) => `Conectate a mi gateway de Limbo en ws://127.0.0.1:${PORT} usando el token ${token}. Usa Limbo como mi capa de memoria: guarda notas, recupera contexto y actualiza maps of content cuando yo lo pida.`,
|
|
490
|
-
dockerMissing: 'Docker no esta instalado o
|
|
504
|
+
dockerMissing: 'Docker no esta instalado o falta el plugin Compose.\n Docker Engine: https://docs.docker.com/engine/install/\n Plugin Compose: sudo apt-get install docker-compose-plugin',
|
|
491
505
|
installMissing: 'Limbo no esta instalado. Corre: npx limbo start',
|
|
492
506
|
helpTitle: 'limbo - agente personal de memoria con AI',
|
|
493
507
|
usage: 'Uso',
|
|
@@ -968,7 +982,8 @@ function pullOrBuildImage(lang) {
|
|
|
968
982
|
}
|
|
969
983
|
|
|
970
984
|
function runOpenClaw(args, opts = {}) {
|
|
971
|
-
|
|
985
|
+
// 1024MB heap: openclaw config needs ~800MB; 512/768 OOM in 2GB VPS tests.
|
|
986
|
+
return runDockerCompose(['run', '--rm', '-e', 'NODE_OPTIONS=--max-old-space-size=1024', '--entrypoint', 'openclaw', 'limbo', ...args], opts);
|
|
972
987
|
}
|
|
973
988
|
|
|
974
989
|
// Fix volume ownership before any docker compose run commands.
|
|
@@ -986,10 +1001,52 @@ function ensureVolumePermissions() {
|
|
|
986
1001
|
], { stdio: 'pipe' });
|
|
987
1002
|
}
|
|
988
1003
|
|
|
1004
|
+
function isOomError(stderr) {
|
|
1005
|
+
return typeof stderr === 'string' && (
|
|
1006
|
+
stderr.includes('heap out of memory') ||
|
|
1007
|
+
stderr.includes('Allocation failed') ||
|
|
1008
|
+
stderr.includes('FATAL ERROR: Reached heap limit')
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function countRunningLimboContainers() {
|
|
1013
|
+
try {
|
|
1014
|
+
const result = spawnSync('docker', ['compose', 'ps', '-q', '--status', 'running'], {
|
|
1015
|
+
cwd: LIMBO_DIR, stdio: 'pipe', encoding: 'utf8',
|
|
1016
|
+
});
|
|
1017
|
+
if (result.status !== 0 || !result.stdout) return 0;
|
|
1018
|
+
return result.stdout.trim().split('\n').filter(Boolean).length;
|
|
1019
|
+
} catch { return 0; }
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function stopExistingContainers(lang) {
|
|
1023
|
+
const running = countRunningLimboContainers();
|
|
1024
|
+
if (running > 0) {
|
|
1025
|
+
warn(t(lang, 'staleContainersFound', running));
|
|
1026
|
+
runDockerCompose(['down', '--remove-orphans'], { stdio: 'pipe' });
|
|
1027
|
+
ok(t(lang, 'staleContainersStopped'));
|
|
1028
|
+
}
|
|
1029
|
+
return running;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function handleConfigOom(lang) {
|
|
1033
|
+
console.log('');
|
|
1034
|
+
die([
|
|
1035
|
+
t(lang, 'configOom'),
|
|
1036
|
+
countRunningLimboContainers() > 0
|
|
1037
|
+
? t(lang, 'configOomContainers', countRunningLimboContainers())
|
|
1038
|
+
: t(lang, 'configOomHint'),
|
|
1039
|
+
t(lang, 'configOomOverride'),
|
|
1040
|
+
].join('\n'));
|
|
1041
|
+
}
|
|
1042
|
+
|
|
989
1043
|
function applyOpenClawConfig(cfg) {
|
|
990
1044
|
header(t(cfg.language, 'configFlowStart'));
|
|
991
1045
|
log(t(cfg.language, 'configFlowSlow'));
|
|
992
1046
|
|
|
1047
|
+
// Stop existing containers to free memory before running config commands
|
|
1048
|
+
stopExistingContainers(cfg.language);
|
|
1049
|
+
|
|
993
1050
|
const setCommands = [
|
|
994
1051
|
['config', 'set', 'gateway.mode', 'local'],
|
|
995
1052
|
['config', 'set', 'gateway.port', String(PORT), '--strict-json'],
|
|
@@ -1014,6 +1071,7 @@ function applyOpenClawConfig(cfg) {
|
|
|
1014
1071
|
process.stdout.write(`\r${c.dim} [${step}/${total}] ${command.slice(1, 4).join(' ')}${c.reset}`.padEnd(60));
|
|
1015
1072
|
const result = runOpenClaw(command, { stdio: 'pipe' });
|
|
1016
1073
|
if (result.status !== 0) {
|
|
1074
|
+
if (isOomError(result.stderr)) handleConfigOom(cfg.language);
|
|
1017
1075
|
console.log('');
|
|
1018
1076
|
process.stdout.write(result.stdout || '');
|
|
1019
1077
|
process.stderr.write(result.stderr || '');
|
|
@@ -1029,6 +1087,7 @@ function applyOpenClawConfig(cfg) {
|
|
|
1029
1087
|
process.stdout.write(`\r${c.dim} [${step}/${total}] config validate${c.reset}`.padEnd(60));
|
|
1030
1088
|
const validateResult = runOpenClaw(['config', 'validate'], { stdio: 'pipe' });
|
|
1031
1089
|
if (validateResult.status !== 0) {
|
|
1090
|
+
if (isOomError(validateResult.stderr)) handleConfigOom(cfg.language);
|
|
1032
1091
|
console.log('');
|
|
1033
1092
|
process.stdout.write(validateResult.stdout || '');
|
|
1034
1093
|
process.stderr.write(validateResult.stderr || '');
|
|
@@ -1452,16 +1511,31 @@ function cmdUpdate() {
|
|
|
1452
1511
|
if (!fs.existsSync(COMPOSE_FILE)) die(t('en', 'installMissing'));
|
|
1453
1512
|
|
|
1454
1513
|
// Patch image tag to :latest in existing compose files (handles upgrades from pinned tags)
|
|
1455
|
-
|
|
1514
|
+
let compose = fs.readFileSync(COMPOSE_FILE, 'utf8');
|
|
1456
1515
|
const patched = compose.replace(
|
|
1457
1516
|
/image:\s*ghcr\.io\/tomasward1\/limbo:\S+/g,
|
|
1458
1517
|
`image: ${GHCR_IMAGE}:${DEFAULT_TAG}`
|
|
1459
1518
|
);
|
|
1460
1519
|
if (patched !== compose) {
|
|
1461
|
-
|
|
1520
|
+
compose = patched;
|
|
1521
|
+
fs.writeFileSync(COMPOSE_FILE, compose);
|
|
1462
1522
|
log('Patched compose image tag to :latest');
|
|
1463
1523
|
}
|
|
1464
1524
|
|
|
1525
|
+
// Inject NODE_OPTIONS into existing compose files to prevent OOM on low-memory VPS.
|
|
1526
|
+
// Uses LIMBO_NODE_OPTIONS env var with 1024MB default so users on bigger servers can override.
|
|
1527
|
+
if (!compose.includes('NODE_OPTIONS')) {
|
|
1528
|
+
const injected = compose.replace(
|
|
1529
|
+
/^(\s+)(LIMBO_PORT:\s*.+)$/m,
|
|
1530
|
+
'$1$2\n$1NODE_OPTIONS: "${LIMBO_NODE_OPTIONS:---max-old-space-size=1024}"'
|
|
1531
|
+
);
|
|
1532
|
+
if (injected !== compose) {
|
|
1533
|
+
compose = injected;
|
|
1534
|
+
fs.writeFileSync(COMPOSE_FILE, compose);
|
|
1535
|
+
log('Added NODE_OPTIONS to compose environment');
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1465
1539
|
log('Pulling latest image...');
|
|
1466
1540
|
run(`docker compose -f "${COMPOSE_FILE}" pull -q`);
|
|
1467
1541
|
log('Restarting...');
|