Files
Codewalkers/apps/server/agent/lifecycle/cleanup-strategy.ts
Lukas May 28521e1c20 chore: merge main into cw/small-change-flow
Integrates main branch changes (headquarters dashboard, task retry count,
agent prompt persistence, remote sync improvements) with the initiative's
errand agent feature. Both features coexist in the merged result.

Key resolutions:
- Schema: take main's errands table (nullable projectId, no conflictFiles,
  with errandsRelations); migrate to 0035_faulty_human_fly
- Router: keep both errandProcedures and headquartersProcedures
- Errand prompt: take main's simpler version (no question-asking flow)
- Manager: take main's status check (running|idle only, no waiting_for_input)
- Tests: update to match removed conflictFiles field and undefined vs null
2026-03-06 16:48:12 +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');
}
}
}