4runr-os 2.10.71 → 2.10.73

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.
@@ -1571,22 +1571,32 @@ async function handleGatewayConnect(ctx) {
1571
1571
  level: 'success',
1572
1572
  message: `Gateway already listening on port ${port}.`,
1573
1573
  });
1574
- // CRITICAL FIX: Check /ready to ensure DB/Redis are actually up (not just the process)
1574
+ // Ensure Postgres + Redis are up before trusting /ready (disconnect stops containers).
1575
+ if (bundleCheck.exists && bundleCheck.path) {
1576
+ activityLog.push({
1577
+ timestamp: getCurrentTime(),
1578
+ level: 'info',
1579
+ message: 'Ensuring Postgres + Redis Docker stack is running...',
1580
+ });
1581
+ const redisUrl = await tryTuiAutostartDockerComposeStack(bundleCheck.path, activityLog, getCurrentTime);
1582
+ if (redisUrl) {
1583
+ await new Promise((r) => setTimeout(r, 2000));
1584
+ }
1585
+ }
1575
1586
  activityLog.push({
1576
1587
  timestamp: getCurrentTime(),
1577
1588
  level: 'info',
1578
1589
  message: 'Verifying dependencies (DB, Redis)...',
1579
1590
  });
1580
1591
  try {
1581
- const verify = await client.verify({ readyTimeoutMs: 8000 });
1592
+ const verify = await client.verify({ readyTimeoutMs: 12000 });
1582
1593
  if (!verify.ok || !verify.ready?.ready) {
1583
1594
  const errMsg = verify.error || (verify.ready ? 'Dependencies not ready' : 'Ready check failed');
1584
1595
  activityLog.push({
1585
1596
  timestamp: getCurrentTime(),
1586
1597
  level: 'error',
1587
- message: `Gateway is alive but dependencies are down: ${errMsg}`,
1598
+ message: `Gateway is alive but dependencies are not ready: ${errMsg}`,
1588
1599
  });
1589
- // Show which dependencies are down
1590
1600
  if (verify.readyChecks) {
1591
1601
  for (const check of verify.readyChecks) {
1592
1602
  if (check.status === 'down') {
@@ -1599,74 +1609,29 @@ async function handleGatewayConnect(ctx) {
1599
1609
  }
1600
1610
  }
1601
1611
  }
1602
- // Try to auto-restart Docker stack if we have the bundle
1603
- if (bundleCheck.exists && bundleCheck.path) {
1604
- activityLog.push({
1605
- timestamp: getCurrentTime(),
1606
- level: 'info',
1607
- message: 'Attempting to restart Docker containers...',
1608
- });
1609
- const redisUrl = await tryTuiAutostartDockerComposeStack(bundleCheck.path, activityLog, getCurrentTime);
1610
- if (redisUrl) {
1611
- await new Promise((r) => setTimeout(r, 3000));
1612
- // Re-verify
1613
- const verifyRetry = await client.verify({ readyTimeoutMs: 8000 });
1614
- if (verifyRetry.ok && verifyRetry.ready?.ready) {
1615
- activityLog.push({
1616
- timestamp: getCurrentTime(),
1617
- level: 'success',
1618
- message: 'Docker containers restarted — Gateway is now ready!',
1619
- });
1620
- }
1621
- else {
1622
- activityLog.push({
1623
- timestamp: getCurrentTime(),
1624
- level: 'error',
1625
- message: 'Docker restart did not fix the issue. Check containers manually: docker ps',
1626
- });
1627
- ctx.server.sendResponse(ctx.ws, ctx.id, {
1628
- success: false,
1629
- error: `Gateway dependencies (DB/Redis) are down. Docker restart failed. Run: docker compose -f "${path.join(bundleCheck.path, 'docker-compose.local.yml')}" up -d`,
1630
- data: { activityLog, bundleStatus: bundleCheck, portStatus: { inUse: true, process: processInfo } },
1631
- });
1632
- return;
1633
- }
1634
- }
1635
- else {
1636
- activityLog.push({
1637
- timestamp: getCurrentTime(),
1638
- level: 'error',
1639
- message: 'Could not restart Docker stack. Start manually: docker compose up -d',
1640
- });
1641
- ctx.server.sendResponse(ctx.ws, ctx.id, {
1642
- success: false,
1643
- error: `Gateway is alive but DB/Redis are down. Start Docker manually: docker compose -f "${path.join(bundleCheck.path, 'docker-compose.local.yml')}" up -d`,
1644
- data: { activityLog, bundleStatus: bundleCheck, portStatus: { inUse: true, process: processInfo } },
1645
- });
1646
- return;
1647
- }
1648
- }
1649
- else {
1650
- activityLog.push({
1651
- timestamp: getCurrentTime(),
1652
- level: 'error',
1653
- message: 'Gateway dependencies down but cannot auto-restart (no bundle path).',
1654
- });
1655
- ctx.server.sendResponse(ctx.ws, ctx.id, {
1656
- success: false,
1657
- error: `Gateway is alive but DB/Redis are down. Start Docker manually or restart the Gateway.`,
1658
- data: { activityLog, bundleStatus: bundleCheck, portStatus: { inUse: true, process: processInfo } },
1659
- });
1660
- return;
1661
- }
1612
+ const composeHint = bundleCheck.path
1613
+ ? `docker compose -p 4runr -f "${path.join(bundleCheck.path, 'docker-compose.local.yml')}" up -d`
1614
+ : 'docker start 4runr-postgres 4runr-redis';
1615
+ ctx.server.sendResponse(ctx.ws, ctx.id, {
1616
+ success: false,
1617
+ error: `Gateway dependencies (DB/Redis) are not ready. Run: ${composeHint}`,
1618
+ data: { activityLog, bundleStatus: bundleCheck, portStatus: { inUse: true, process: processInfo } },
1619
+ });
1620
+ return;
1662
1621
  }
1663
1622
  }
1664
1623
  catch (readyErr) {
1665
1624
  activityLog.push({
1666
1625
  timestamp: getCurrentTime(),
1667
- level: 'warning',
1668
- message: `/ready check failed: ${errorMessage(readyErr)}. Proceeding (Gateway may be in degraded state).`,
1626
+ level: 'error',
1627
+ message: `/ready check failed: ${errorMessage(readyErr)}`,
1628
+ });
1629
+ ctx.server.sendResponse(ctx.ws, ctx.id, {
1630
+ success: false,
1631
+ error: `Could not verify Gateway dependencies: ${errorMessage(readyErr)}`,
1632
+ data: { activityLog, bundleStatus: bundleCheck, portStatus: { inUse: true, process: processInfo } },
1669
1633
  });
1634
+ return;
1670
1635
  }
1671
1636
  activityLog.push({
1672
1637
  timestamp: getCurrentTime(),
@@ -2025,12 +1990,37 @@ async function handleGatewayConnect(ctx) {
2025
1990
  });
2026
1991
  }
2027
1992
  }
1993
+ function isLocalGatewayHost(url) {
1994
+ const trimmed = url.trim();
1995
+ if (!trimmed)
1996
+ return false;
1997
+ try {
1998
+ const host = new URL(trimmed).hostname.toLowerCase();
1999
+ return host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0' || host === '::1';
2000
+ }
2001
+ catch {
2002
+ return false;
2003
+ }
2004
+ }
2028
2005
  async function handleGatewayDisconnect(ctx) {
2006
+ const url = process.env.GATEWAY_URL || '';
2007
+ const wasLocalhost = isLocalGatewayHost(url);
2029
2008
  tuiActiveGatewayClient = null;
2030
2009
  delete process.env.GATEWAY_URL;
2010
+ let message;
2011
+ if (wasLocalhost) {
2012
+ const { shutdownLocalGatewayStack } = await import('./watchdog.js');
2013
+ shutdownLocalGatewayStack();
2014
+ message =
2015
+ 'Disconnected — local Gateway stopped and Postgres/Redis containers stopped (data volumes kept).';
2016
+ }
2031
2017
  ctx.server.sendResponse(ctx.ws, ctx.id, {
2032
2018
  success: true,
2033
- data: { connected: false },
2019
+ data: {
2020
+ connected: false,
2021
+ localShutdown: wasLocalhost,
2022
+ message,
2023
+ },
2034
2024
  });
2035
2025
  }
2036
2026
  async function handleGatewayStatus(ctx) {