| 180 | } |
| 181 | |
| 182 | export class BackgroundProcessManager extends EventEmitter<BackgroundProcessManagerEvents> { |
| 183 | // NOTE: This map is in-memory only. Background processes use nohup/setsid so they |
| 184 | // could survive app restarts, but we kill all tracked processes on shutdown via |
| 185 | // dispose(). Rehydrating from meta.json on startup is out of scope for now. |
| 186 | // All per-process state (read position, output lock) is stored in BackgroundProcess |
| 187 | // so cleanup is automatic when the process is removed from this map. |
| 188 | private processes = new Map<string, BackgroundProcess>(); |
| 189 | |
| 190 | // Base directory for process output files |
| 191 | private readonly bgOutputDir: string; |
| 192 | // Tracks foreground processes (started via runtime.exec) that can be backgrounded |
| 193 | // Key is toolCallId to support multiple parallel foreground processes per workspace |
| 194 | private foregroundProcesses = new Map<string, ForegroundProcess>(); |
| 195 | // Tracks workspaces with queued messages (for bash_output to return early) |
| 196 | private queuedMessageWorkspaces = new Set<string>(); |
| 197 | |
| 198 | constructor(bgOutputDir: string) { |
| 199 | super(); |
| 200 | // Background bash status can have many concurrent subscribers (e.g. multiple workspaces). |
| 201 | // Raise the default listener cap to avoid noisy MaxListenersExceededWarning. |
| 202 | this.setMaxListeners(50); |
| 203 | this.bgOutputDir = bgOutputDir; |
| 204 | } |
| 205 | |
| 206 | /** |
| 207 | * Mark whether a workspace has a queued user message. |
| 208 | * Used by bash_output to return early when user has sent a new message. |
| 209 | */ |
| 210 | setMessageQueued(workspaceId: string, queued: boolean): void { |
| 211 | if (queued) { |
| 212 | this.queuedMessageWorkspaces.add(workspaceId); |
| 213 | } else { |
| 214 | this.queuedMessageWorkspaces.delete(workspaceId); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * Check if a workspace has a queued user message. |
| 220 | */ |
| 221 | hasQueuedMessage(workspaceId: string): boolean { |
| 222 | return this.queuedMessageWorkspaces.has(workspaceId); |
| 223 | } |
| 224 | |
| 225 | /** Emit a change event for a workspace */ |
| 226 | private emitChange(workspaceId: string): void { |
| 227 | this.emit("change", workspaceId); |
| 228 | } |
| 229 | |
| 230 | private createMonitorState( |
| 231 | config: BackgroundProcessMonitorConfig, |
| 232 | options: { pollIntervalMs: number } |
| 233 | ): BackgroundProcessMonitorState { |
| 234 | assert(config.filter.length > 0, "BackgroundProcessMonitorConfig requires a filter"); |
| 235 | assert(config.cooldownMs >= 0, "BackgroundProcessMonitorConfig cooldown must be non-negative"); |
| 236 | assert(options.pollIntervalMs > 0, "monitor poll interval must be positive"); |
| 237 | return { |
| 238 | ...config, |
| 239 | matchesCount: 0, |
nothing calls this directly
no outgoing calls
no test coverage detected