/** * CleanupStrategy — Centralized cleanup logic based on debug mode and agent state. * * Determines when and how to clean up agent workdirs and resources. * Supports archive mode for debugging vs. immediate cleanup for production. */ import { createModuleLogger } from '../../logger/index.js'; import type { CleanupManager } from '../cleanup-manager.js'; const log = createModuleLogger('cleanup-strategy'); export type CleanupAction = 'remove' | 'archive' | 'preserve'; export interface AgentInfo { id: string; name: string; status: string; initiativeId?: string | null; worktreeId: string; exitCode?: number | null; } export interface CleanupStrategy { shouldCleanup(agent: AgentInfo, isDebugMode: boolean): Promise; executeCleanup(agent: AgentInfo, action: CleanupAction): Promise; } export class DefaultCleanupStrategy implements CleanupStrategy { constructor(private cleanupManager: CleanupManager) {} /** * Determine what cleanup action should be taken for an agent. * Considers agent status and debug mode setting. */ async shouldCleanup(agent: AgentInfo, isDebugMode: boolean): Promise { log.debug({ agentId: agent.id, name: agent.name, status: agent.status, isDebugMode }, 'evaluating cleanup action for agent'); // Never cleanup agents waiting for user input if (agent.status === 'waiting_for_input') { log.debug({ agentId: agent.id, status: agent.status }, 'preserving agent waiting for input'); return 'preserve'; } // Never cleanup running agents if (agent.status === 'running') { log.debug({ agentId: agent.id, status: agent.status }, 'preserving running agent'); return 'preserve'; } // For completed/idle/crashed agents, decide based on debug mode if (agent.status === 'idle' || agent.status === 'completed' || agent.status === 'crashed') { if (isDebugMode) { log.debug({ agentId: agent.id, status: agent.status }, 'archiving agent in debug mode'); return 'archive'; } else { log.debug({ agentId: agent.id, status: agent.status }, 'removing agent in production mode'); return 'remove'; } } // For stopped agents, clean up immediately regardless of debug mode if (agent.status === 'stopped') { log.debug({ agentId: agent.id, status: agent.status }, 'removing stopped agent'); return 'remove'; } // Default to preserve for any unrecognized status log.debug({ agentId: agent.id, status: agent.status }, 'preserving agent with unrecognized status'); return 'preserve'; } /** * Execute the determined cleanup action. */ async executeCleanup(agent: AgentInfo, action: CleanupAction): Promise { log.debug({ agentId: agent.id, name: agent.name, action }, 'executing cleanup action'); switch (action) { case 'remove': await this.cleanupManager.removeAgentWorktrees(agent.name, agent.initiativeId ?? null); await this.cleanupManager.removeAgentBranches(agent.name, agent.initiativeId ?? null); await this.cleanupManager.removeAgentLogs(agent.id); log.info({ agentId: agent.id, name: agent.name }, 'agent workdir and resources removed'); break; case 'archive': await this.cleanupManager.archiveForDebug(agent.worktreeId, agent.id); log.info({ agentId: agent.id, name: agent.name }, 'agent workdir archived for debugging'); break; case 'preserve': log.debug({ agentId: agent.id, name: agent.name }, 'agent workdir preserved'); break; default: log.warn({ agentId: agent.id, action }, 'unknown cleanup action, preserving by default'); } } }