export const createGracefulShutdownRuntime = (dependencies) => { const { process, shutdownTimeoutMs, getExitOnShutdown, getIsShuttingDown, setIsShuttingDown, syncToHmrState, openCodeWatcherRuntime, sessionRuntime, scheduledTasksRuntime, getHealthCheckInterval, clearHealthCheckInterval, getTerminalRuntime, setTerminalRuntime, getMessageStreamRuntime, setMessageStreamRuntime, shouldSkipOpenCodeStop, getOpenCodePort, getOpenCodeProcess, setOpenCodeProcess, killProcessOnPort, waitForPortRelease, getServer, getUiAuthController, setUiAuthController, getActiveTunnelController, setActiveTunnelController, tunnelAuthController, } = dependencies; let shutdownPromise = null; const runShutdown = async (options = {}) => { if (getIsShuttingDown()) return; setIsShuttingDown(true); syncToHmrState(); console.log('Starting graceful shutdown...'); const exitProcess = typeof options.exitProcess === 'boolean' ? options.exitProcess : getExitOnShutdown(); openCodeWatcherRuntime.stop(); sessionRuntime.dispose(); scheduledTasksRuntime?.stop?.(); const healthCheckInterval = getHealthCheckInterval(); if (healthCheckInterval) { clearHealthCheckInterval(healthCheckInterval); } const terminalRuntime = getTerminalRuntime(); if (terminalRuntime) { try { await terminalRuntime.shutdown(); } catch { } finally { setTerminalRuntime(null); } } const messageStreamRuntime = getMessageStreamRuntime(); if (messageStreamRuntime) { try { await messageStreamRuntime.close(); } catch { } finally { setMessageStreamRuntime(null); } } if (!shouldSkipOpenCodeStop()) { const portToKill = getOpenCodePort(); const openCodeProcess = getOpenCodeProcess(); if (openCodeProcess) { console.log('Stopping OpenCode process...'); try { await openCodeProcess.close(); } catch (error) { console.warn('Error closing OpenCode process:', error); } setOpenCodeProcess(null); } killProcessOnPort(portToKill); if (!(await waitForPortRelease(portToKill, 5000))) { console.warn(`Timed out waiting for OpenCode port ${portToKill} to be released during shutdown`); } } else { console.log('Skipping OpenCode shutdown (external server)'); } const server = getServer(); if (server) { let closeTimeout = null; try { await Promise.race([ new Promise((resolve) => { server.close(() => { console.log('HTTP server closed'); resolve(); }); }), new Promise((resolve) => { closeTimeout = setTimeout(() => { console.warn('Server close timeout reached, forcing shutdown'); resolve(); }, shutdownTimeoutMs); }), ]); } finally { if (closeTimeout) { clearTimeout(closeTimeout); } } } const uiAuthController = getUiAuthController(); if (uiAuthController) { uiAuthController.dispose(); setUiAuthController(null); } const activeTunnelController = getActiveTunnelController(); if (activeTunnelController) { console.log('Stopping active tunnel...'); activeTunnelController.stop(); setActiveTunnelController(null); tunnelAuthController.clearActiveTunnel(); } console.log('Graceful shutdown complete'); if (exitProcess) { process.exit(0); } }; const gracefulShutdown = (options = {}) => { if (shutdownPromise) return shutdownPromise; shutdownPromise = runShutdown(options); return shutdownPromise; }; return { gracefulShutdown, }; };