diff --git a/apps/desktop/src/main/lib/terminal-host/client.ts b/apps/desktop/src/main/lib/terminal-host/client.ts index 7994d8fc3c1..6bbbdc8cf3e 100644 --- a/apps/desktop/src/main/lib/terminal-host/client.ts +++ b/apps/desktop/src/main/lib/terminal-host/client.ts @@ -73,6 +73,7 @@ const SOCKET_PATH = join(SUPERSET_HOME_DIR, "terminal-host.sock"); const TOKEN_PATH = join(SUPERSET_HOME_DIR, "terminal-host.token"); const PID_PATH = join(SUPERSET_HOME_DIR, "terminal-host.pid"); const SPAWN_LOCK_PATH = join(SUPERSET_HOME_DIR, "terminal-host.spawn.lock"); +const SCRIPT_MTIME_PATH = join(SUPERSET_HOME_DIR, "terminal-host.mtime"); // Connection timeouts const CONNECT_TIMEOUT_MS = 5000; @@ -295,6 +296,18 @@ export class TerminalHostClient extends EventEmitter { */ private async connectAndAuthenticate(): Promise { for (let attempt = 0; attempt < 2; attempt++) { + if (attempt === 0 && process.env.NODE_ENV === "development") { + if (this.isDaemonScriptStale()) { + if (DEBUG_CLIENT) { + console.log( + "[TerminalHostClient] Daemon script rebuilt, restarting...", + ); + } + this.killDaemonFromPidFile(); + await this.waitForDaemonShutdown(); + } + } + let controlConnected = await this.tryConnectControl(); if (!controlConnected) { await this.spawnDaemon(); @@ -354,6 +367,47 @@ export class TerminalHostClient extends EventEmitter { throw new Error("Failed to connect after protocol upgrade"); } + /** + * Check if the daemon script has been rebuilt since the daemon was spawned. + * Only used in development mode to detect stale daemons. + */ + private isDaemonScriptStale(): boolean { + try { + if (!existsSync(SCRIPT_MTIME_PATH)) { + return false; // No mtime file = first run or manual cleanup + } + + const savedMtime = readFileSync(SCRIPT_MTIME_PATH, "utf-8").trim(); + const scriptPath = this.getDaemonScriptPath(); + + if (!existsSync(scriptPath)) { + return false; + } + + const currentMtime = statSync(scriptPath).mtimeMs.toString(); + return savedMtime !== currentMtime; + } catch { + return false; // On error, don't restart + } + } + + /** + * Save the daemon script's mtime to detect rebuilds. + */ + private saveDaemonScriptMtime(): void { + try { + const scriptPath = this.getDaemonScriptPath(); + if (!existsSync(scriptPath)) { + return; + } + + const mtime = statSync(scriptPath).mtimeMs.toString(); + writeFileSync(SCRIPT_MTIME_PATH, mtime, { mode: 0o600 }); + } catch { + // Best-effort + } + } + private killDaemonFromPidFile(): void { if (!existsSync(PID_PATH)) return; @@ -1087,6 +1141,11 @@ export class TerminalHostClient extends EventEmitter { } await this.waitForDaemon(); + // In development mode, save the script mtime to detect rebuilds + if (process.env.NODE_ENV === "development") { + this.saveDaemonScriptMtime(); + } + if (DEBUG_CLIENT) { console.log("[TerminalHostClient] Daemon started successfully"); }