/ src / lib / server / runtime / runtime-state.ts
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  }