linkgravity 1.7.4 → 1.7.5

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/cli.js CHANGED
@@ -342,7 +342,10 @@ function launchBlocker() {
342
342
  return null;
343
343
  }
344
344
 
345
- function getPm2Proc() {
345
+ // pm2 keys an app by name AND script path, so an install that moved (node version switch, npm prefix
346
+ // change, npm link) registers a second app under the same name instead of replacing the first, and
347
+ // every name-based pm2 command then acts on both at once.
348
+ function getPm2Procs() {
346
349
  const jlist = spawnSync(process.execPath, [PM2_BIN, 'jlist'], { stdio: 'pipe' });
347
350
  if (jlist.status !== 0) return null;
348
351
 
@@ -353,7 +356,7 @@ function getPm2Proc() {
353
356
  if (out[i] !== '[') continue;
354
357
  try {
355
358
  const procs = JSON.parse(out.slice(i));
356
- if (Array.isArray(procs)) return procs.find((p) => p.name === LGY_PM2_NAME) || null;
359
+ if (Array.isArray(procs)) return procs.filter((p) => p.name === LGY_PM2_NAME);
357
360
  } catch (e) {}
358
361
  }
359
362
 
@@ -363,6 +366,71 @@ function getPm2Proc() {
363
366
  return null;
364
367
  }
365
368
 
369
+ function isOurRegistration(proc) {
370
+ const registered = proc.pm2_env.pm_exec_path || '';
371
+ if (!isWin) return registered === LGY_SCRIPT_PATH;
372
+ return registered.toLowerCase() === LGY_SCRIPT_PATH.toLowerCase();
373
+ }
374
+
375
+ function printRegistrations(procs) {
376
+ for (const proc of procs) {
377
+ const mine = isOurRegistration(proc) ? ' <- this install' : '';
378
+ console.log(
379
+ ` ${String(proc.pm2_env.status).padEnd(8)} ${proc.pm2_env.pm_exec_path}${mine}`,
380
+ );
381
+ }
382
+ }
383
+
384
+ let duplicatesReported = false;
385
+ function pickPm2Proc(procs) {
386
+ if (!procs || procs.length === 0) return null;
387
+
388
+ if (procs.length > 1 && !duplicatesReported) {
389
+ duplicatesReported = true;
390
+ console.log(
391
+ `\n${color.yellow}⚠${color.reset} pm2 has ${procs.length} apps registered as '${LGY_PM2_NAME}' - only this install's should be:`,
392
+ );
393
+ printRegistrations(procs);
394
+ console.log(
395
+ ` Everything below reports on one of them. Run ${color.cyan}lgy start${color.reset} to drop the stale ones.\n`,
396
+ );
397
+ }
398
+
399
+ const isOnline = (proc) => proc.pm2_env.status === 'online';
400
+ return (
401
+ procs.find((proc) => isOnline(proc) && isOurRegistration(proc)) ||
402
+ procs.find(isOnline) ||
403
+ procs.find(isOurRegistration) ||
404
+ procs[0]
405
+ );
406
+ }
407
+
408
+ function getPm2Proc() {
409
+ return pickPm2Proc(getPm2Procs());
410
+ }
411
+
412
+ function startDaemon() {
413
+ runPm2([
414
+ 'start',
415
+ LGY_SCRIPT_PATH,
416
+ '--interpreter',
417
+ daemonPython,
418
+ '--name',
419
+ LGY_PM2_NAME,
420
+ '--update-env',
421
+ ]);
422
+ }
423
+
424
+ // The saved autostart list keeps the removed paths until pm2 save runs, so a reboot restores them.
425
+ function clearRegistrations(procs) {
426
+ console.log(
427
+ `\n${color.yellow}⚠${color.reset} pm2 has ${procs.length} app(s) registered as '${LGY_PM2_NAME}', not all from this install:`,
428
+ );
429
+ printRegistrations(procs);
430
+ console.log(' Removing all of them and registering this install alone.\n');
431
+ runPm2(['delete', LGY_PM2_NAME]);
432
+ }
433
+
366
434
  // Best-effort: pm2 has no API for "is this registered to start on boot", so this checks the OS directly and returns null (unknown) if that check itself isn't available.
367
435
  function isAutostartEnabled() {
368
436
  if (isWin) return null;
@@ -403,8 +471,11 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
403
471
  const pkg = require('../package.json');
404
472
  console.log(`linkgravity v${pkg.version}`);
405
473
  } else if (cmd === 'start') {
406
- const existing = getPm2Proc();
407
- if (existing && existing.pm2_env.status === 'online') {
474
+ const registered = getPm2Procs() || [];
475
+ const stale = registered.filter((proc) => !isOurRegistration(proc));
476
+ const existing = registered.find(isOurRegistration);
477
+
478
+ if (!stale.length && existing && existing.pm2_env.status === 'online') {
408
479
  console.log(
409
480
  `\n${color.yellow}⚠${color.reset} LinkGravity is already running. ` +
410
481
  `Use ${color.cyan}lgy restart${color.reset} to apply changes, or ${color.cyan}lgy stop${color.reset} first.\n`,
@@ -425,16 +496,11 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
425
496
 
426
497
  repairHookRegistration();
427
498
 
499
+ if (stale.length) clearRegistrations(registered);
500
+
428
501
  info('Starting LinkGravity daemon...');
429
- runPm2([
430
- 'start',
431
- LGY_SCRIPT_PATH,
432
- '--interpreter',
433
- daemonPython,
434
- '--name',
435
- LGY_PM2_NAME,
436
- '--update-env',
437
- ]);
502
+ startDaemon();
503
+ if (stale.length) runPm2(['save']);
438
504
  verifyStartup().then((ok) => process.exit(ok ? 0 : 1));
439
505
  } else if (cmd === 'stop') {
440
506
  info('Stopping LinkGravity daemon...');
@@ -442,6 +508,18 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
442
508
  runPm2(['reset', LGY_PM2_NAME]);
443
509
  success('Daemon stopped successfully.\n');
444
510
  } else if (cmd === 'restart') {
511
+ const registered = getPm2Procs() || [];
512
+ if (registered.some((proc) => !isOurRegistration(proc))) {
513
+ console.log(
514
+ `\n${color.yellow}⚠${color.reset} pm2 has ${registered.length} app(s) registered as '${LGY_PM2_NAME}', and a restart would start every one of them:`,
515
+ );
516
+ printRegistrations(registered);
517
+ console.log(
518
+ ` Run ${color.cyan}lgy start${color.reset} instead - it drops the stale ones first.\n`,
519
+ );
520
+ process.exit(1);
521
+ }
522
+
445
523
  info('Restarting LinkGravity daemon...');
446
524
  runPm2(['restart', LGY_PM2_NAME, '--update-env']);
447
525
  runPm2(['reset', LGY_PM2_NAME]);
@@ -640,8 +718,10 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
640
718
  process.exit(0);
641
719
  }
642
720
 
643
- const procBeforeUpdate = getPm2Proc();
721
+ const registeredBeforeUpdate = getPm2Procs() || [];
722
+ const procBeforeUpdate = pickPm2Proc(registeredBeforeUpdate);
644
723
  const wasOnline = !!procBeforeUpdate && procBeforeUpdate.pm2_env.status === 'online';
724
+ const hadStale = registeredBeforeUpdate.some((proc) => !isOurRegistration(proc));
645
725
 
646
726
  info(`Updating: v${currentVersion} -> v${latestVersion}...`);
647
727
  const installResult = runNpm(['install', '-g', 'linkgravity@latest'], { stdio: 'inherit' });
@@ -666,15 +746,20 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
666
746
  process.exit(0);
667
747
  }
668
748
  info("Daemon wasn't running - starting it fresh...");
669
- runPm2([
670
- 'start',
671
- LGY_SCRIPT_PATH,
672
- '--interpreter',
673
- daemonPython,
674
- '--name',
675
- LGY_PM2_NAME,
676
- '--update-env',
677
- ]);
749
+ startDaemon();
750
+ verifyStartup().then((ok) => process.exit(ok ? 0 : 1));
751
+ } else if (hadStale) {
752
+ clearRegistrations(registeredBeforeUpdate);
753
+ if (!wasOnline) {
754
+ runPm2(['save']);
755
+ success(
756
+ `Daemon was stopped - leaving it stopped. Run 'lgy start' when you're ready.\n`,
757
+ );
758
+ process.exit(0);
759
+ }
760
+ info('Starting the daemon from this install...');
761
+ startDaemon();
762
+ runPm2(['save']);
678
763
  verifyStartup().then((ok) => process.exit(ok ? 0 : 1));
679
764
  } else if (wasOnline) {
680
765
  info('Restarting daemon to apply the update...');
@@ -753,7 +838,9 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
753
838
  spawnSync(process.argv[0], [process.argv[1], action], { stdio: 'inherit' });
754
839
  })();
755
840
  } else {
756
- console.log(
841
+ // Not stdout: an eval "$(lgy ...)" line in a shell rc would run this message as commands.
842
+ console.error(
757
843
  `\n❌ Unknown command: ${cmd || 'none'}\n💡 Run 'lgy help' to see available commands.`,
758
844
  );
845
+ process.exit(1);
759
846
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkgravity",
3
- "version": "1.7.4",
3
+ "version": "1.7.5",
4
4
  "description": "Discord/Telegram bot bridge for the Antigravity (agy) CLI, with voice interaction support",
5
5
  "scripts": {
6
6
  "start": "node npm-scripts/run-dev.js",
@@ -583,7 +583,7 @@ class VoiceCog(commands.Cog):
583
583
  wake_syllables = len(re.sub(r"[^\w가-힣]", "", matched_wake_word or ""))
584
584
  min_prefix_similarity = 0.55 if wake_syllables <= 2 else 0.35
585
585
  if is_waking_up and prefix_similarity is not None and prefix_similarity < min_prefix_similarity:
586
- self.logger.info(
586
+ self.logger.debug(
587
587
  f"STT: ignoring wake - '{text}' doesn't resemble '{matched_wake_word}' "
588
588
  f"(prefix similarity {prefix_similarity:.2f}, needed {min_prefix_similarity:.2f})"
589
589
  )
package/src/main_slack.py CHANGED
@@ -258,7 +258,7 @@ async def run_slack(stop_event: asyncio.Event) -> None:
258
258
 
259
259
  app, adapter = build_app()
260
260
  try:
261
- await adapter.resolve_bot_user_id()
261
+ bot_user_id = await adapter.resolve_bot_user_id()
262
262
  except SlackApiError as e:
263
263
  logger.critical(f"Slack auth_test failed - check slack_bot_token: {e}")
264
264
  return
@@ -266,6 +266,9 @@ async def run_slack(stop_event: asyncio.Event) -> None:
266
266
  handler = AsyncSocketModeHandler(app, SLACK_APP_TOKEN)
267
267
  logger.info("✅ Slack bot starting (Socket Mode)...")
268
268
  await handler.connect_async()
269
+ # cli.js's verifyStartup() waits for this exact sentence - without it a Slack-only install
270
+ # never reports a successful startup and every lgy start/restart/update times out.
271
+ logger.info(f"✅ Bot is fully online and ready! Logged in as {bot_user_id}")
269
272
  platform_health.set_status("slack", "running")
270
273
  try:
271
274
  await stop_event.wait()