fluxy-bot 0.5.22 → 0.5.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.5.22",
3
+ "version": "0.5.24",
4
4
  "description": "Self-hosted, self-evolving AI agent with its own dashboard.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,4 +1,5 @@
1
1
  import { spawn, type ChildProcess } from 'child_process';
2
+ import fs from 'fs';
2
3
  import path from 'path';
3
4
  import { PKG_DIR } from '../shared/paths.js';
4
5
  import { log } from '../shared/logger.js';
@@ -10,6 +11,8 @@ let intentionallyStopped = false;
10
11
  const MAX_RESTARTS = 3;
11
12
  const STABLE_THRESHOLD = 30_000; // 30s — if backend ran this long, it wasn't a crash loop
12
13
 
14
+ const LOG_FILE = path.join(PKG_DIR, 'workspace', '.backend.log');
15
+
13
16
  export function getBackendPort(basePort: number): number {
14
17
  return basePort + 4;
15
18
  }
@@ -19,6 +22,9 @@ export function spawnBackend(port: number): ChildProcess {
19
22
  lastSpawnTime = Date.now();
20
23
  intentionallyStopped = false;
21
24
 
25
+ // Clear log file on each restart — only keeps current run
26
+ try { fs.writeFileSync(LOG_FILE, ''); } catch {}
27
+
22
28
  child = spawn(process.execPath, ['--import', 'tsx/esm', backendPath], {
23
29
  cwd: path.join(PKG_DIR, 'workspace'),
24
30
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -27,10 +33,12 @@ export function spawnBackend(port: number): ChildProcess {
27
33
 
28
34
  child.stdout?.on('data', (d) => {
29
35
  process.stdout.write(d);
36
+ try { fs.appendFileSync(LOG_FILE, d); } catch {}
30
37
  });
31
38
 
32
39
  child.stderr?.on('data', (d) => {
33
40
  process.stderr.write(d);
41
+ try { fs.appendFileSync(LOG_FILE, d); } catch {}
34
42
  });
35
43
 
36
44
  child.on('exit', (code) => {
@@ -56,10 +64,26 @@ export function spawnBackend(port: number): ChildProcess {
56
64
  return child;
57
65
  }
58
66
 
59
- export function stopBackend(): void {
60
- intentionallyStopped = true;
61
- child?.kill();
62
- child = null;
67
+ /** Stop the backend and wait for the process to fully exit before resolving.
68
+ * This prevents port collisions when restarting (old process must release the port first). */
69
+ export function stopBackend(): Promise<void> {
70
+ return new Promise((resolve) => {
71
+ if (!child || child.exitCode !== null) {
72
+ child = null;
73
+ resolve();
74
+ return;
75
+ }
76
+ intentionallyStopped = true;
77
+ const dying = child;
78
+ child = null;
79
+ dying.once('exit', () => resolve());
80
+ dying.kill();
81
+ // Safety: force kill after 3s if SIGTERM doesn't work
82
+ setTimeout(() => {
83
+ try { dying.kill('SIGKILL'); } catch {}
84
+ resolve();
85
+ }, 3000);
86
+ });
63
87
  }
64
88
 
65
89
  export function isBackendAlive(): boolean {
@@ -507,8 +507,7 @@ export async function startSupervisor() {
507
507
  console.log('[supervisor] Agent turn ended — restarting backend');
508
508
  pendingBackendRestart = false;
509
509
  resetBackendRestarts();
510
- stopBackend();
511
- spawnBackend(backendPort);
510
+ stopBackend().then(() => spawnBackend(backendPort));
512
511
  }
513
512
  return; // don't forward bot:done to client
514
513
  }
@@ -640,9 +639,9 @@ export async function startSupervisor() {
640
639
  startScheduler({
641
640
  broadcastFluxy,
642
641
  workerApi,
643
- restartBackend: () => {
642
+ restartBackend: async () => {
644
643
  resetBackendRestarts();
645
- stopBackend();
644
+ await stopBackend();
646
645
  spawnBackend(backendPort);
647
646
  },
648
647
  getModel: () => loadConfig().ai.model,
@@ -662,10 +661,10 @@ export async function startSupervisor() {
662
661
  return;
663
662
  }
664
663
  if (backendRestartTimer) clearTimeout(backendRestartTimer);
665
- backendRestartTimer = setTimeout(() => {
664
+ backendRestartTimer = setTimeout(async () => {
666
665
  log.info(`[watcher] ${reason} — restarting backend...`);
667
666
  resetBackendRestarts();
668
- stopBackend();
667
+ await stopBackend();
669
668
  spawnBackend(backendPort);
670
669
  }, 1000);
671
670
  }
@@ -715,18 +714,27 @@ export async function startSupervisor() {
715
714
  }
716
715
  }
717
716
 
718
- // Poll until the full chain is actually working (avoid 502s)
719
- // Cache-busting query param prevents browsers/CDN from serving stale 502s
717
+ // Poll until the full relay→tunnel→server chain is actually working.
718
+ // A 502/503 means the relay can't reach the tunnel yet; anything else
719
+ // (200, 401, 403, etc.) means the chain is live.
720
720
  const probeUrl = config.relay?.url || tunnelUrl;
721
721
  let ready = false;
722
- for (let i = 0; i < 20; i++) {
722
+ log.info(`Readiness probe: polling ${probeUrl}`);
723
+ for (let i = 0; i < 30; i++) {
723
724
  try {
724
725
  const res = await fetch(probeUrl + `/api/health?_cb=${Date.now()}`, {
725
726
  signal: AbortSignal.timeout(3000),
726
- headers: { 'Cache-Control': 'no-cache' },
727
+ headers: { 'Cache-Control': 'no-cache, no-store' },
727
728
  });
728
- if (res.ok) { ready = true; break; }
729
- } catch {}
729
+ log.info(`Readiness probe #${i + 1}: ${res.status}`);
730
+ // Any non-502/503 means the relay is reaching the tunnel
731
+ if (res.status !== 502 && res.status !== 503) {
732
+ ready = true;
733
+ break;
734
+ }
735
+ } catch (err) {
736
+ log.info(`Readiness probe #${i + 1}: ${err instanceof Error ? err.message : 'error'}`);
737
+ }
730
738
  await new Promise(r => setTimeout(r, 1000));
731
739
  }
732
740
  if (!ready) {
@@ -790,7 +798,7 @@ export async function startSupervisor() {
790
798
  delete latestConfig.tunnelUrl;
791
799
  saveConfig(latestConfig);
792
800
  stopWorker();
793
- stopBackend();
801
+ await stopBackend();
794
802
  stopTunnel();
795
803
  console.log('[supervisor] Stopping Vite dev servers...');
796
804
  await stopViteDevServers();
@@ -230,7 +230,7 @@ The supervisor manages the backend process. You don't need to manage it yourself
230
230
 
231
231
  **During your turn:** The backend does NOT restart mid-turn. All your edits are batched — the backend restarts once when you're done. This means if you're writing multi-file changes, everything applies atomically.
232
232
 
233
- **If the backend crashes:** It auto-restarts up to 3 times. If it keeps crashing, check `backend/index.ts` for syntax errors or bad imports.
233
+ **If the backend crashes:** It auto-restarts up to 3 times. If it keeps crashing, read `.backend.log` to see the error output, then fix the code. The log file is cleared on each restart so it only contains the current/last run — no need to truncate it yourself.
234
234
 
235
235
  **NEVER do these:**
236
236
  - Never `kill` processes or run `pkill`/`killall` — you don't manage the supervisor or its children