Files
Codewalkers/apps/server/agent/lifecycle/cleanup-strategy.ts
Lukas May 269a2d2616 feat: Extend AgentInfo with exitCode + add getAgentInputFiles/getAgentPrompt tRPC procedures
Adds exitCode to AgentInfo type and propagates it through all toAgentInfo()
implementations. Enhances getAgent to also return taskName and initiativeName
from their respective repositories. Adds two new filesystem-reading tRPC
procedures for the Agent Details tab: getAgentInputFiles (reads .cw/input/
files with binary detection, 500 KB cap, sorted) and getAgentPrompt (reads
.cw/agent-logs/<name>/PROMPT.md with 1 MB cap and structured ENOENT handling).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 21:39:29 +01:00

109 lines
3.7 KiB
TypeScript

/**
* 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<CleanupAction>;
executeCleanup(agent: AgentInfo, action: CleanupAction): Promise<void>;
}
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<CleanupAction> {
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<void> {
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');
}
}
}