@tritard/waterbrother 0.16.13 → 0.16.14

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/package.json +1 -1
  2. package/src/cli.js +60 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.16.13",
3
+ "version": "0.16.14",
4
4
  "description": "Waterbrother: bring-your-own-model coding CLI with local tools, sessions, operator modes, and approval controls",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -2731,22 +2731,21 @@ async function maybeAutostartGateway(runtime, { cwd, io = console } = {}) {
2731
2731
  : [];
2732
2732
 
2733
2733
  if (!gateway.enabled || !startupChannels.includes("telegram")) {
2734
- return { attempted: false, started: false, reason: "not-configured" };
2734
+ return { attempted: false, started: false, reason: "not-configured", child: null };
2735
2735
  }
2736
2736
 
2737
2737
  const telegram = runtime?.channels?.telegram || {};
2738
2738
  if (!telegram.enabled || !String(telegram.botToken || "").trim()) {
2739
- return { attempted: false, started: false, reason: "telegram-not-ready" };
2739
+ return { attempted: false, started: false, reason: "telegram-not-ready", child: null };
2740
2740
  }
2741
2741
 
2742
2742
  const processInfo = await loadGatewayProcessInfo("telegram");
2743
2743
  if (isProcessAlive(processInfo.pid)) {
2744
- return { attempted: true, started: false, reason: "already-running", pid: processInfo.pid };
2744
+ return { attempted: true, started: false, reason: "already-running", pid: processInfo.pid, child: null };
2745
2745
  }
2746
2746
 
2747
2747
  const child = spawn(process.execPath, [BIN_PATH, "gateway", "run", "telegram", "--skip-onboarding"], {
2748
2748
  cwd,
2749
- detached: true,
2750
2749
  stdio: "ignore",
2751
2750
  env: {
2752
2751
  ...process.env,
@@ -2762,7 +2761,35 @@ async function maybeAutostartGateway(runtime, { cwd, io = console } = {}) {
2762
2761
  });
2763
2762
 
2764
2763
  io.log?.(dim(`telegram gateway autostarted in background (pid ${child.pid})`));
2765
- return { attempted: true, started: true, pid: child.pid };
2764
+ return { attempted: true, started: true, pid: child.pid, child };
2765
+ }
2766
+
2767
+ async function stopManagedGateway(service, gatewayHandle, { io = console } = {}) {
2768
+ const pid = Number(gatewayHandle?.pid || 0);
2769
+ if (!Number.isFinite(pid) || pid <= 0 || !isProcessAlive(pid)) {
2770
+ return false;
2771
+ }
2772
+ try {
2773
+ process.kill(pid, "SIGTERM");
2774
+ } catch {
2775
+ return false;
2776
+ }
2777
+
2778
+ const deadline = Date.now() + 2500;
2779
+ while (Date.now() < deadline) {
2780
+ if (!isProcessAlive(pid)) {
2781
+ await saveGatewayProcessInfo(service, { pid: 0, startedAt: "", command: "" });
2782
+ return true;
2783
+ }
2784
+ await new Promise((resolve) => setTimeout(resolve, 100));
2785
+ }
2786
+
2787
+ try {
2788
+ process.kill(pid, "SIGKILL");
2789
+ } catch {}
2790
+ await saveGatewayProcessInfo(service, { pid: 0, startedAt: "", command: "" });
2791
+ io.log?.(dim(`${service} gateway forced down on TUI exit`));
2792
+ return true;
2766
2793
  }
2767
2794
 
2768
2795
  async function chooseFromInteractiveMenu({ title, options, defaultIndex = 0 }) {
@@ -6534,7 +6561,30 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
6534
6561
  }
6535
6562
  }
6536
6563
 
6537
- await maybeAutostartGateway(context.runtime, { cwd: context.cwd });
6564
+ const gatewayHandle = await maybeAutostartGateway(context.runtime, { cwd: context.cwd });
6565
+ let gatewayCleanupDone = false;
6566
+ const cleanupManagedGateway = async () => {
6567
+ if (gatewayCleanupDone) return;
6568
+ gatewayCleanupDone = true;
6569
+ if (gatewayHandle?.started && gatewayHandle?.child) {
6570
+ await stopManagedGateway("telegram", gatewayHandle, { io: console });
6571
+ }
6572
+ };
6573
+ const cleanupManagedGatewaySync = () => {
6574
+ if (gatewayCleanupDone) return;
6575
+ gatewayCleanupDone = true;
6576
+ const pid = Number(gatewayHandle?.pid || 0);
6577
+ if (Number.isFinite(pid) && pid > 0 && isProcessAlive(pid)) {
6578
+ try {
6579
+ process.kill(pid, "SIGTERM");
6580
+ } catch {}
6581
+ }
6582
+ };
6583
+ const handleExitSignal = () => {
6584
+ cleanupManagedGatewaySync();
6585
+ };
6586
+ process.once("SIGINT", handleExitSignal);
6587
+ process.once("SIGTERM", handleExitSignal);
6538
6588
  printInteractiveHeader({ runtime: context.runtime, agent, cwd: context.cwd });
6539
6589
  console.log("Interactive mode. Type /exit to quit, /help for commands.");
6540
6590
  if (process.stdout.isTTY) {
@@ -8808,6 +8858,10 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
8808
8858
  }
8809
8859
  }
8810
8860
 
8861
+ await cleanupManagedGateway();
8862
+ process.off("SIGINT", handleExitSignal);
8863
+ process.off("SIGTERM", handleExitSignal);
8864
+
8811
8865
  // Kill any running preview server
8812
8866
  killPreview();
8813
8867