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.
Files changed (2) hide show
  1. package/cli.js +81 -7
  2. 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 `docker compose` is unavailable.\nInstall Docker Desktop: https://docs.docker.com/get-docker/',
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 `docker compose` no esta disponible.\nInstala Docker Desktop: https://docs.docker.com/get-docker/',
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
- return runDockerCompose(['run', '--rm', '--entrypoint', 'openclaw', 'limbo', ...args], opts);
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
- const compose = fs.readFileSync(COMPOSE_FILE, 'utf8');
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
- fs.writeFileSync(COMPOSE_FILE, patched);
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...');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "limbo-ai",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Your personal AI memory agent — install and manage Limbo via npx",
5
5
  "type": "commonjs",
6
6
  "bin": {