UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

202 lines (201 loc) 8.55 kB
/** * Conversation Fork Cleanup Utilities * * Provides memory leak prevention for Task Mode by: * 1. Setting TTL on message lists (24h default) * 2. Cleaning up completed task messages * 3. Limiting message history size * 4. Auto-cleanup of orphaned forks */ import { execSync } from 'child_process'; // FIX: Default to 'localhost' for CLI mode (host execution), not 'cfn-redis' (Docker) const redisHost = process.env.CFN_REDIS_HOST || 'localhost'; const redisPort = process.env.CFN_REDIS_PORT || '6379'; const DEFAULT_OPTIONS = { messageTTL: 86400, maxMessagesPerAgent: 100, autoCleanupForks: true }; /** * Set TTL on message list to prevent indefinite accumulation */ export function setMessageListTTL(taskId, agentId, ttlSeconds = DEFAULT_OPTIONS.messageTTL) { const key = `swarm:${taskId}:${agentId}:messages`; try { execSync(`redis-cli -h ${redisHost} -p ${redisPort} expire "${key}" ${ttlSeconds}`, { encoding: 'utf8' }); console.log(`[conversation-cleanup] Set TTL ${ttlSeconds}s on ${key}`); } catch (error) { console.error(`[conversation-cleanup] Failed to set TTL:`, error); } } /** * Trim message list to max size (FIFO - keep recent messages) */ export function trimMessageList(taskId, agentId, maxMessages = DEFAULT_OPTIONS.maxMessagesPerAgent) { const key = `swarm:${taskId}:${agentId}:messages`; try { // Keep only the last N messages (0 = oldest, -N = keep last N) const start = -maxMessages; execSync(`redis-cli -h ${redisHost} -p ${redisPort} ltrim "${key}" ${start} -1`, { encoding: 'utf8' }); console.log(`[conversation-cleanup] Trimmed ${key} to ${maxMessages} messages`); } catch (error) { console.error(`[conversation-cleanup] Failed to trim messages:`, error); } } /** * Clean up all messages and forks for a completed task */ export function cleanupTaskMessages(taskId, agentId) { try { // Delete main message list const messagesKey = `swarm:${taskId}:${agentId}:messages`; execSync(`redis-cli -h ${redisHost} -p ${redisPort} del "${messagesKey}"`, { encoding: 'utf8' }); // Delete all fork message lists const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`; const forkKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys "${forkPattern}"`, { encoding: 'utf8' }).trim().split('\n').filter((k)=>k); if (forkKeys.length > 0) { execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${forkKeys.join(' ')}`, { encoding: 'utf8' }); } // Delete all fork metadata const forkMetaPattern = `swarm:${taskId}:${agentId}:fork:*:meta`; const metaKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys "${forkMetaPattern}"`, { encoding: 'utf8' }).trim().split('\n').filter((k)=>k); if (metaKeys.length > 0) { execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${metaKeys.join(' ')}`, { encoding: 'utf8' }); } // Delete current fork pointer const currentForkKey = `swarm:${taskId}:${agentId}:current-fork`; execSync(`redis-cli -h ${redisHost} -p ${redisPort} del "${currentForkKey}"`, { encoding: 'utf8' }); console.log(`[conversation-cleanup] Cleaned up task ${taskId} agent ${agentId}`); console.log(`[conversation-cleanup] Removed: 1 message list, ${forkKeys.length} fork snapshots, ${metaKeys.length} fork metadata`); } catch (error) { console.error(`[conversation-cleanup] Failed to cleanup task messages:`, error); } } /** * Clean up orphaned forks (metadata expired but messages remain) */ export function cleanupOrphanedForks(taskId, agentId) { try { // Find all fork message keys const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`; const forkMessageKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys "${forkPattern}"`, { encoding: 'utf8' }).trim().split('\n').filter((k)=>k); if (forkMessageKeys.length === 0) { return; } const orphanedKeys = []; for (const messageKey of forkMessageKeys){ // Extract fork ID from key: swarm:task:agent:fork:FORK_ID:messages const parts = messageKey.split(':'); const forkId = parts[parts.length - 2]; // Check if metadata exists const metaKey = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`; const metaExists = execSync(`redis-cli -h ${redisHost} -p ${redisPort} exists "${metaKey}"`, { encoding: 'utf8' }).trim(); if (metaExists === '0') { // Metadata expired but messages remain = orphaned orphanedKeys.push(messageKey); } } if (orphanedKeys.length > 0) { execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${orphanedKeys.join(' ')}`, { encoding: 'utf8' }); console.log(`[conversation-cleanup] Removed ${orphanedKeys.length} orphaned fork snapshots`); } } catch (error) { console.error(`[conversation-cleanup] Failed to cleanup orphaned forks:`, error); } } /** * Get memory usage statistics for a task */ export function getTaskMemoryStats(taskId, agentId) { try { // Count messages in main list const messagesKey = `swarm:${taskId}:${agentId}:messages`; const messageCount = parseInt(execSync(`redis-cli -h ${redisHost} -p ${redisPort} llen "${messagesKey}"`, { encoding: 'utf8' }).trim(), 10); // Count forks const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`; const forkKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys "${forkPattern}"`, { encoding: 'utf8' }).trim().split('\n').filter((k)=>k); // Estimate size (average message ~5KB) const estimatedSizeKB = messageCount * 5; return { messageCount, forkCount: forkKeys.length, estimatedSizeKB }; } catch (error) { console.error(`[conversation-cleanup] Failed to get memory stats:`, error); return { messageCount: 0, forkCount: 0, estimatedSizeKB: 0 }; } } /** * Configure automatic cleanup options for a task */ export function configureAutoCleanup(taskId, agentId, options = {}) { const config = { ...DEFAULT_OPTIONS, ...options }; // Set TTL on message list setMessageListTTL(taskId, agentId, config.messageTTL); // Trim to max size trimMessageList(taskId, agentId, config.maxMessagesPerAgent); // Cleanup orphaned forks if (config.autoCleanupForks) { cleanupOrphanedForks(taskId, agentId); } console.log(`[conversation-cleanup] Auto-cleanup configured for ${taskId}/${agentId}`); console.log(`[conversation-cleanup] - TTL: ${config.messageTTL}s`); console.log(`[conversation-cleanup] - Max messages: ${config.maxMessagesPerAgent}`); console.log(`[conversation-cleanup] - Auto-cleanup forks: ${config.autoCleanupForks}`); } /** * Emergency cleanup - remove all conversation data for all tasks * USE WITH CAUTION: This will delete ALL conversation history */ export function emergencyCleanupAll() { try { const patterns = [ 'swarm:*:*:messages', 'swarm:*:*:fork:*:messages', 'swarm:*:*:fork:*:meta', 'swarm:*:*:current-fork' ]; let totalDeleted = 0; for (const pattern of patterns){ const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys "${pattern}"`, { encoding: 'utf8' }).trim().split('\n').filter((k)=>k); if (keys.length > 0) { execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${keys.join(' ')}`, { encoding: 'utf8' }); totalDeleted += keys.length; } } console.log(`[conversation-cleanup] EMERGENCY CLEANUP: Deleted ${totalDeleted} conversation keys`); } catch (error) { console.error(`[conversation-cleanup] Failed emergency cleanup:`, error); } } //# sourceMappingURL=conversation-fork-cleanup.js.map