runtime-state.ts
1 import type { ChildProcess } from 'node:child_process' 2 3 import { hmrSingleton } from '@/lib/shared-utils' 4 5 export type ActiveSessionProcess = { 6 runId?: string | null 7 source?: string 8 kill: (signal?: NodeJS.Signals | number) => boolean | void 9 } 10 11 export interface DevServerRuntime { 12 proc: ChildProcess 13 url: string 14 } 15 16 interface RuntimeStateRegistry { 17 activeSessionProcesses: Map<string, ActiveSessionProcess> 18 devServers: Map<string, DevServerRuntime> 19 } 20 21 const state = hmrSingleton<RuntimeStateRegistry>('__swarmclaw_runtime_state__', () => ({ 22 activeSessionProcesses: new Map<string, ActiveSessionProcess>(), 23 devServers: new Map<string, DevServerRuntime>(), 24 })) 25 26 if (!state.activeSessionProcesses) state.activeSessionProcesses = new Map<string, ActiveSessionProcess>() 27 if (!state.devServers) state.devServers = new Map<string, DevServerRuntime>() 28 29 export const activeSessionProcesses = state.activeSessionProcesses 30 export const devServers = state.devServers 31 32 export function getActiveSessionProcess(sessionId: string): ActiveSessionProcess | undefined { 33 return state.activeSessionProcesses.get(sessionId) 34 } 35 36 export function hasActiveSessionProcess(sessionId: string): boolean { 37 return state.activeSessionProcesses.has(sessionId) 38 } 39 40 export function registerActiveSessionProcess(sessionId: string, process: ActiveSessionProcess): void { 41 state.activeSessionProcesses.set(sessionId, process) 42 } 43 44 export function stopActiveSessionProcess(sessionId: string, signal?: NodeJS.Signals | number): boolean { 45 const process = state.activeSessionProcesses.get(sessionId) 46 if (!process) return false 47 try { 48 process.kill(signal) 49 } catch { 50 // Ignore process teardown errors during cleanup. 51 } 52 state.activeSessionProcesses.delete(sessionId) 53 return true 54 } 55 56 export function clearActiveSessionProcess(sessionId: string): void { 57 state.activeSessionProcesses.delete(sessionId) 58 } 59 60 export function getDevServer(sessionId: string): DevServerRuntime | undefined { 61 return state.devServers.get(sessionId) 62 } 63 64 export function hasDevServer(sessionId: string): boolean { 65 return state.devServers.has(sessionId) 66 } 67 68 export function registerDevServer(sessionId: string, runtime: DevServerRuntime): void { 69 state.devServers.set(sessionId, runtime) 70 } 71 72 export function updateDevServerUrl(sessionId: string, url: string): void { 73 const runtime = state.devServers.get(sessionId) 74 if (!runtime) return 75 runtime.url = url 76 } 77 78 export function stopDevServer(sessionId: string): boolean { 79 const runtime = state.devServers.get(sessionId) 80 if (!runtime) return false 81 try { 82 runtime.proc.kill('SIGTERM') 83 } catch { 84 // Ignore process teardown errors during cleanup. 85 } 86 if (typeof runtime.proc.pid === 'number') { 87 try { 88 process.kill(-runtime.proc.pid, 'SIGTERM') 89 } catch { 90 // Ignore process-group teardown errors when the child is already gone. 91 } 92 } 93 state.devServers.delete(sessionId) 94 return true 95 } 96 97 export function clearDevServer(sessionId: string): void { 98 state.devServers.delete(sessionId) 99 }