UNPKG

devflow-ai

Version:

Enterprise-grade AI agent orchestration with swarm management UI dashboard

1,268 lines (1,080 loc) 36.8 kB
/** * Claude Code Coordination Interface * * This module provides the interface layer for coordinating with Claude Code * instances, managing agent spawning through the claude CLI, handling process * lifecycle, and enabling seamless communication between the swarm system * and individual Claude agents. */ import { EventEmitter } from 'node:events'; import { spawn, ChildProcess } from 'node:child_process'; import { performance } from 'node:perf_hooks'; import * as path from 'node:path'; import * as fs from 'node:fs/promises'; import { Logger } from '../core/logger.js'; import { generateId } from '../utils/helpers.js'; import { MemoryManager } from '../memory/manager.js'; import TaskExecutor, { ClaudeExecutionOptions, ExecutionResult, ExecutionContext } from './executor.js'; import { SwarmAgent, SwarmTask, TaskDefinition, AgentState, AgentCapabilities, TaskResult, SwarmExecutionContext, } from './types.js'; export interface ClaudeCodeConfig { claudeExecutablePath: string; defaultModel: string; maxTokens: number; temperature: number; timeout: number; maxConcurrentAgents: number; enableStreaming: boolean; enableLogging: boolean; workingDirectory: string; environmentVariables: Record<string, string>; agentPoolSize: number; processRecycling: boolean; healthCheckInterval: number; } export interface ClaudeAgent { id: string; processId: number; process: ChildProcess; type: string; capabilities: string[]; status: 'initializing' | 'idle' | 'busy' | 'error' | 'terminated'; currentTask?: string; spawnedAt: Date; lastActivity: Date; totalTasks: number; totalDuration: number; metrics: ClaudeAgentMetrics; } export interface ClaudeAgentMetrics { tasksCompleted: number; tasksFailed: number; averageResponseTime: number; totalTokensUsed: number; memoryUsage: number; cpuUsage: number; errorRate: number; successRate: number; } export interface ClaudeTaskExecution { id: string; taskId: string; agentId: string; startTime: Date; endTime?: Date; status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'; input: any; output?: any; error?: string; duration?: number; tokensUsed?: number; retryCount: number; maxRetries: number; } export interface ClaudeSpawnOptions { type: string; name?: string; capabilities?: string[]; systemPrompt?: string; model?: string; maxTokens?: number; temperature?: number; workingDirectory?: string; environment?: Record<string, string>; tools?: string[]; priority?: number; } export interface ProcessPool { idle: ClaudeAgent[]; busy: ClaudeAgent[]; failed: ClaudeAgent[]; totalSpawned: number; totalTerminated: number; recyclingEnabled: boolean; maxAge: number; maxTasks: number; } export class ClaudeCodeInterface extends EventEmitter { private logger: Logger; private config: ClaudeCodeConfig; private memoryManager: MemoryManager; private processPool: ProcessPool; private activeExecutions: Map<string, ClaudeTaskExecution> = new Map(); private agents: Map<string, ClaudeAgent> = new Map(); private taskExecutor: TaskExecutor; private healthCheckInterval?: NodeJS.Timeout; private isInitialized: boolean = false; constructor( config: Partial<ClaudeCodeConfig> = {}, memoryManager: MemoryManager ) { super(); this.logger = new Logger('ClaudeCodeInterface'); this.config = this.createDefaultConfig(config); this.memoryManager = memoryManager; this.processPool = this.initializeProcessPool(); this.taskExecutor = new TaskExecutor({ timeoutMs: this.config.timeout, enableMetrics: true, captureOutput: true, streamOutput: this.config.enableStreaming, }); this.setupEventHandlers(); } /** * Initialize the Claude Code interface */ async initialize(): Promise<void> { if (this.isInitialized) { this.logger.warn('Claude Code interface already initialized'); return; } this.logger.info('Initializing Claude Code interface...'); try { // Verify Claude executable exists await this.verifyClaudeExecutable(); // Initialize task executor await this.taskExecutor.initialize(); // Pre-warm agent pool if configured if (this.config.agentPoolSize > 0) { await this.prewarmAgentPool(); } // Start health checks this.startHealthChecks(); this.isInitialized = true; this.logger.info('Claude Code interface initialized successfully', { poolSize: this.processPool.idle.length, maxConcurrent: this.config.maxConcurrentAgents, }); this.emit('initialized'); } catch (error) { this.logger.error('Failed to initialize Claude Code interface', error); throw error; } } /** * Shutdown the interface gracefully */ async shutdown(): Promise<void> { if (!this.isInitialized) return; this.logger.info('Shutting down Claude Code interface...'); try { // Stop health checks if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); } // Cancel active executions const cancellationPromises = Array.from(this.activeExecutions.keys()) .map(executionId => this.cancelExecution(executionId, 'Interface shutdown')); await Promise.allSettled(cancellationPromises); // Terminate all agents await this.terminateAllAgents(); // Shutdown task executor await this.taskExecutor.shutdown(); this.isInitialized = false; this.logger.info('Claude Code interface shut down successfully'); this.emit('shutdown'); } catch (error) { this.logger.error('Error during Claude Code interface shutdown', error); throw error; } } /** * Spawn a new Claude agent with specified configuration */ async spawnAgent(options: ClaudeSpawnOptions): Promise<string> { this.logger.info('Spawning Claude agent', { type: options.type, name: options.name, capabilities: options.capabilities, }); try { // Check if we can spawn more agents if (this.getTotalActiveAgents() >= this.config.maxConcurrentAgents) { throw new Error('Maximum concurrent agents limit reached'); } // Build Claude command const command = this.buildClaudeCommand(options); // Spawn process const process = spawn(command.executable, command.args, { cwd: options.workingDirectory || this.config.workingDirectory, env: { ...process.env, ...this.config.environmentVariables, ...options.environment, }, stdio: ['pipe', 'pipe', 'pipe'], detached: false, }); if (!process.pid) { throw new Error('Failed to spawn Claude process'); } // Create agent record const agentId = generateId('claude-agent'); const agent: ClaudeAgent = { id: agentId, processId: process.pid, process, type: options.type, capabilities: options.capabilities || [], status: 'initializing', spawnedAt: new Date(), lastActivity: new Date(), totalTasks: 0, totalDuration: 0, metrics: this.initializeAgentMetrics(), }; this.agents.set(agentId, agent); this.processPool.idle.push(agent); this.processPool.totalSpawned++; // Setup process event handlers this.setupProcessEventHandlers(agent); // Wait for agent to be ready await this.waitForAgentReady(agent); agent.status = 'idle'; agent.lastActivity = new Date(); this.logger.info('Claude agent spawned successfully', { agentId, processId: process.pid, type: options.type, }); this.emit('agent:spawned', { agentId, type: options.type, processId: process.pid, }); return agentId; } catch (error) { this.logger.error('Failed to spawn Claude agent', { type: options.type, error: error instanceof Error ? error.message : String(error), }); throw error; } } /** * Execute a task using a Claude agent */ async executeTask( taskDefinition: TaskDefinition, agentId?: string, options: Partial<ClaudeExecutionOptions> = {} ): Promise<ClaudeTaskExecution> { const executionId = generateId('claude-execution'); this.logger.info('Executing task with Claude agent', { executionId, taskId: taskDefinition.id.id, agentId, }); try { // Get or select agent const agent = agentId ? this.agents.get(agentId) : await this.selectOptimalAgent(taskDefinition); if (!agent) { throw new Error(agentId ? `Agent not found: ${agentId}` : 'No suitable agent available'); } if (agent.status !== 'idle') { throw new Error(`Agent ${agent.id} is not available (status: ${agent.status})`); } // Create execution record const execution: ClaudeTaskExecution = { id: executionId, taskId: taskDefinition.id.id, agentId: agent.id, startTime: new Date(), status: 'queued', input: { task: taskDefinition, options, }, retryCount: 0, maxRetries: options.maxRetries || 3, }; this.activeExecutions.set(executionId, execution); // Update agent status agent.status = 'busy'; agent.currentTask = executionId; agent.lastActivity = new Date(); // Move agent from idle to busy pool this.moveAgentToBusyPool(agent); // Execute task execution.status = 'running'; const result = await this.executeTaskWithAgent(agent, taskDefinition, options); // Update execution record execution.endTime = new Date(); execution.duration = execution.endTime.getTime() - execution.startTime.getTime(); execution.output = result.result; execution.tokensUsed = result.metadata?.tokensUsed; if (result.success) { execution.status = 'completed'; agent.metrics.tasksCompleted++; } else { execution.status = 'failed'; execution.error = result.error; agent.metrics.tasksFailed++; } // Update agent metrics this.updateAgentMetrics(agent, execution); // Return agent to idle pool this.returnAgentToIdlePool(agent); this.logger.info('Task execution completed', { executionId, success: result.success, duration: execution.duration, tokensUsed: execution.tokensUsed, }); this.emit('task:completed', { executionId, taskId: taskDefinition.id.id, agentId: agent.id, success: result.success, duration: execution.duration, }); return execution; } catch (error) { const execution = this.activeExecutions.get(executionId); if (execution) { execution.status = 'failed'; execution.error = error instanceof Error ? error.message : String(error); execution.endTime = new Date(); execution.duration = execution.endTime.getTime() - execution.startTime.getTime(); // Return agent to pool if it was assigned const agent = this.agents.get(execution.agentId); if (agent) { this.returnAgentToIdlePool(agent); } } this.logger.error('Task execution failed', { executionId, error: error instanceof Error ? error.message : String(error), }); throw error; } finally { this.activeExecutions.delete(executionId); } } /** * Cancel a running task execution */ async cancelExecution(executionId: string, reason: string): Promise<void> { const execution = this.activeExecutions.get(executionId); if (!execution) { throw new Error(`Execution not found: ${executionId}`); } this.logger.info('Cancelling task execution', { executionId, reason, taskId: execution.taskId, agentId: execution.agentId, }); try { execution.status = 'cancelled'; execution.error = reason; execution.endTime = new Date(); execution.duration = execution.endTime.getTime() - execution.startTime.getTime(); // Cancel agent task if running const agent = this.agents.get(execution.agentId); if (agent && agent.currentTask === executionId) { await this.cancelAgentTask(agent); this.returnAgentToIdlePool(agent); } this.emit('task:cancelled', { executionId, reason, taskId: execution.taskId, agentId: execution.agentId, }); } finally { this.activeExecutions.delete(executionId); } } /** * Terminate a specific agent */ async terminateAgent(agentId: string, reason: string = 'Manual termination'): Promise<void> { const agent = this.agents.get(agentId); if (!agent) { throw new Error(`Agent not found: ${agentId}`); } this.logger.info('Terminating Claude agent', { agentId, processId: agent.processId, reason, }); try { // Cancel current task if any if (agent.currentTask) { await this.cancelExecution(agent.currentTask, 'Agent termination'); } // Update status agent.status = 'terminated'; // Terminate process await this.terminateProcess(agent.process); // Remove from pools and agents map this.removeAgentFromPools(agent); this.agents.delete(agentId); this.processPool.totalTerminated++; this.logger.info('Claude agent terminated successfully', { agentId, reason, totalTasks: agent.totalTasks, totalDuration: agent.totalDuration, }); this.emit('agent:terminated', { agentId, reason, metrics: agent.metrics, }); } catch (error) { this.logger.error('Error terminating agent', { agentId, error: error instanceof Error ? error.message : String(error), }); throw error; } } /** * Get agent status and metrics */ getAgentStatus(agentId: string): ClaudeAgent | null { return this.agents.get(agentId) || null; } /** * Get all active agents */ getAllAgents(): ClaudeAgent[] { return Array.from(this.agents.values()); } /** * Get execution status */ getExecutionStatus(executionId: string): ClaudeTaskExecution | null { return this.activeExecutions.get(executionId) || null; } /** * Get comprehensive interface metrics */ getInterfaceMetrics(): { agents: { total: number; idle: number; busy: number; failed: number; terminated: number; }; executions: { active: number; completed: number; failed: number; cancelled: number; }; performance: { averageResponseTime: number; totalTokensUsed: number; successRate: number; throughput: number; }; pool: { totalSpawned: number; totalTerminated: number; recyclingEnabled: boolean; poolUtilization: number; }; } { const agents = Array.from(this.agents.values()); const executions = Array.from(this.activeExecutions.values()); const totalCompleted = agents.reduce((sum, a) => sum + a.metrics.tasksCompleted, 0); const totalFailed = agents.reduce((sum, a) => sum + a.metrics.tasksFailed, 0); const totalTokens = agents.reduce((sum, a) => sum + a.metrics.totalTokensUsed, 0); const avgResponseTime = agents.length > 0 ? agents.reduce((sum, a) => sum + a.metrics.averageResponseTime, 0) / agents.length : 0; return { agents: { total: agents.length, idle: this.processPool.idle.length, busy: this.processPool.busy.length, failed: this.processPool.failed.length, terminated: this.processPool.totalTerminated, }, executions: { active: executions.filter(e => e.status === 'running').length, completed: totalCompleted, failed: totalFailed, cancelled: executions.filter(e => e.status === 'cancelled').length, }, performance: { averageResponseTime: avgResponseTime, totalTokensUsed: totalTokens, successRate: totalCompleted + totalFailed > 0 ? totalCompleted / (totalCompleted + totalFailed) : 0, throughput: this.calculateThroughput(), }, pool: { totalSpawned: this.processPool.totalSpawned, totalTerminated: this.processPool.totalTerminated, recyclingEnabled: this.processPool.recyclingEnabled, poolUtilization: this.calculatePoolUtilization(), }, }; } // Private methods private async verifyClaudeExecutable(): Promise<void> { try { const { spawn } = await import('node:child_process'); const process = spawn(this.config.claudeExecutablePath, ['--version'], { stdio: ['ignore', 'pipe', 'pipe'], }); return new Promise((resolve, reject) => { let output = ''; process.stdout?.on('data', (data) => { output += data.toString(); }); process.on('close', (code) => { if (code === 0) { this.logger.info('Claude executable verified', { path: this.config.claudeExecutablePath, version: output.trim(), }); resolve(); } else { reject(new Error(`Claude executable verification failed with code ${code}`)); } }); process.on('error', reject); }); } catch (error) { throw new Error(`Claude executable not found: ${this.config.claudeExecutablePath}`); } } private async prewarmAgentPool(): Promise<void> { this.logger.info('Pre-warming agent pool', { targetSize: this.config.agentPoolSize, }); const promises: Promise<string>[] = []; for (let i = 0; i < this.config.agentPoolSize; i++) { promises.push(this.spawnAgent({ type: 'general', name: `pool-agent-${i}`, capabilities: ['general'], })); } const results = await Promise.allSettled(promises); const successful = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; this.logger.info('Agent pool pre-warming completed', { successful, failed, targetSize: this.config.agentPoolSize, }); } private buildClaudeCommand(options: ClaudeSpawnOptions): { executable: string; args: string[]; } { const args: string[] = []; // Add model args.push('--model', options.model || this.config.defaultModel); // Add max tokens args.push('--max-tokens', String(options.maxTokens || this.config.maxTokens)); // Add temperature args.push('--temperature', String(options.temperature || this.config.temperature)); // Add system prompt if provided if (options.systemPrompt) { args.push('--system', options.systemPrompt); } // Add tools if specified if (options.tools && options.tools.length > 0) { args.push('--allowedTools', options.tools.join(',')); } // Enable streaming if configured if (this.config.enableStreaming) { args.push('--stream'); } // Skip permissions for swarm execution args.push('--dangerously-skip-permissions'); return { executable: this.config.claudeExecutablePath, args, }; } private setupProcessEventHandlers(agent: ClaudeAgent): void { const { process } = agent; process.on('exit', (code, signal) => { this.logger.info('Claude agent process exited', { agentId: agent.id, processId: agent.processId, code, signal, }); if (agent.status !== 'terminated') { agent.status = 'error'; this.moveAgentToFailedPool(agent); } this.emit('agent:exited', { agentId: agent.id, code, signal, }); }); process.on('error', (error) => { this.logger.error('Claude agent process error', { agentId: agent.id, processId: agent.processId, error: error.message, }); agent.status = 'error'; this.moveAgentToFailedPool(agent); this.emit('agent:error', { agentId: agent.id, error: error.message, }); }); // Handle stdout/stderr if needed if (this.config.enableLogging) { process.stdout?.on('data', (data) => { this.logger.debug('Agent stdout', { agentId: agent.id, data: data.toString().trim(), }); }); process.stderr?.on('data', (data) => { this.logger.debug('Agent stderr', { agentId: agent.id, data: data.toString().trim(), }); }); } } private async waitForAgentReady(agent: ClaudeAgent, timeout: number = 30000): Promise<void> { return new Promise((resolve, reject) => { const startTime = Date.now(); const checkInterval = 1000; // 1 second const checkReady = () => { const elapsed = Date.now() - startTime; if (elapsed > timeout) { reject(new Error(`Agent ${agent.id} failed to become ready within ${timeout}ms`)); return; } // Check if process is still running if (agent.process.killed || agent.process.exitCode !== null) { reject(new Error(`Agent ${agent.id} process terminated during initialization`)); return; } // For now, assume agent is ready after a short delay // In a real implementation, you might check for specific output or response if (elapsed > 2000) { // 2 seconds resolve(); } else { setTimeout(checkReady, checkInterval); } }; checkReady(); }); } private async selectOptimalAgent(taskDefinition: TaskDefinition): Promise<ClaudeAgent | null> { const availableAgents = this.processPool.idle.filter(agent => agent.status === 'idle'); if (availableAgents.length === 0) { // Try to spawn a new agent if under limit if (this.getTotalActiveAgents() < this.config.maxConcurrentAgents) { const agentId = await this.spawnAgent({ type: 'task-specific', capabilities: taskDefinition.requirements.capabilities, }); return this.agents.get(agentId) || null; } return null; } // Select agent based on capabilities and performance const scoredAgents = availableAgents.map(agent => ({ agent, score: this.calculateAgentScore(agent, taskDefinition), })); scoredAgents.sort((a, b) => b.score - a.score); return scoredAgents[0].agent; } private calculateAgentScore(agent: ClaudeAgent, taskDefinition: TaskDefinition): number { let score = 0; // Capability match const requiredCapabilities = taskDefinition.requirements.capabilities; const matchingCapabilities = agent.capabilities.filter(cap => requiredCapabilities.includes(cap) ); score += (matchingCapabilities.length / requiredCapabilities.length) * 100; // Performance metrics score += agent.metrics.successRate * 50; score += Math.max(0, 50 - agent.metrics.averageResponseTime / 1000) * 10; // Prefer faster agents // Load balancing - prefer agents with fewer completed tasks const maxTasks = Math.max(...this.processPool.idle.map(a => a.totalTasks), 1); score += (1 - agent.totalTasks / maxTasks) * 20; return score; } private async executeTaskWithAgent( agent: ClaudeAgent, taskDefinition: TaskDefinition, options: Partial<ClaudeExecutionOptions> ): Promise<ExecutionResult> { const startTime = performance.now(); try { // Create execution context for the agent const context: ExecutionContext = { task: taskDefinition, agent: this.convertToAgentState(agent), workingDirectory: options.workingDirectory || this.config.workingDirectory, tempDirectory: path.join(this.config.workingDirectory, 'temp', agent.id), logDirectory: path.join(this.config.workingDirectory, 'logs', agent.id), environment: { ...this.config.environmentVariables, CLAUDE_AGENT_ID: agent.id, CLAUDE_TASK_ID: taskDefinition.id.id, }, resources: { maxMemory: taskDefinition.requirements.memoryRequired || 512 * 1024 * 1024, maxCpuTime: taskDefinition.requirements.maxDuration || 300000, maxDiskSpace: 1024 * 1024 * 1024, maxNetworkConnections: 10, maxFileHandles: 100, priority: 1, }, }; // Execute using task executor const result = await this.taskExecutor.executeClaudeTask( taskDefinition, context.agent, { model: options.model || this.config.defaultModel, maxTokens: options.maxTokens || this.config.maxTokens, temperature: options.temperature || this.config.temperature, timeout: options.timeout || this.config.timeout, claudePath: this.config.claudeExecutablePath, ...options, } ); const duration = performance.now() - startTime; // Update agent activity agent.lastActivity = new Date(); agent.totalTasks++; agent.totalDuration += duration; return result; } catch (error) { const duration = performance.now() - startTime; agent.totalDuration += duration; throw error; } } private convertToAgentState(agent: ClaudeAgent): AgentState { // Convert ClaudeAgent to AgentState for compatibility return { id: { id: agent.id, swarmId: 'claude-interface', type: agent.type as any, instance: 1, }, name: `Claude-${agent.id}`, type: agent.type as any, status: agent.status as any, capabilities: this.createAgentCapabilities(agent.capabilities), metrics: { tasksCompleted: agent.metrics.tasksCompleted, tasksFailed: agent.metrics.tasksFailed, averageExecutionTime: agent.metrics.averageResponseTime, successRate: agent.metrics.successRate, cpuUsage: agent.metrics.cpuUsage, memoryUsage: agent.metrics.memoryUsage, diskUsage: 0, networkUsage: 0, codeQuality: 0.8, testCoverage: 0.7, bugRate: 0.1, userSatisfaction: 0.9, totalUptime: Date.now() - agent.spawnedAt.getTime(), lastActivity: agent.lastActivity, responseTime: agent.metrics.averageResponseTime, }, currentTask: agent.currentTask ? { id: agent.currentTask, swarmId: 'claude-interface', sequence: 0, priority: 1, } : undefined, workload: agent.status === 'busy' ? 1 : 0, health: agent.status === 'error' ? 0 : 1, config: { autonomyLevel: 0.8, learningEnabled: false, adaptationEnabled: false, maxTasksPerHour: 60, maxConcurrentTasks: 1, timeoutThreshold: this.config.timeout, reportingInterval: 10000, heartbeatInterval: 5000, permissions: ['read', 'write', 'execute'], trustedAgents: [], expertise: {}, preferences: {}, }, environment: { runtime: 'claude', version: '1.0.0', workingDirectory: this.config.workingDirectory, tempDirectory: path.join(this.config.workingDirectory, 'temp', agent.id), logDirectory: path.join(this.config.workingDirectory, 'logs', agent.id), apiEndpoints: {}, credentials: {}, availableTools: agent.capabilities, toolConfigs: {}, }, endpoints: [], lastHeartbeat: agent.lastActivity, taskHistory: [], errorHistory: [], parentAgent: undefined, childAgents: [], collaborators: [], }; } private createAgentCapabilities(capabilities: string[]): AgentCapabilities { return { codeGeneration: capabilities.includes('coding') || capabilities.includes('codeGeneration'), codeReview: capabilities.includes('review') || capabilities.includes('codeReview'), testing: capabilities.includes('testing'), documentation: capabilities.includes('documentation'), research: capabilities.includes('research'), analysis: capabilities.includes('analysis'), webSearch: capabilities.includes('webSearch'), apiIntegration: capabilities.includes('apiIntegration'), fileSystem: capabilities.includes('fileSystem'), terminalAccess: capabilities.includes('terminal'), languages: capabilities.filter(c => ['javascript', 'typescript', 'python', 'java'].includes(c)), frameworks: capabilities.filter(c => ['react', 'node', 'express'].includes(c)), domains: capabilities.filter(c => ['web', 'api', 'database'].includes(c)), tools: capabilities.filter(c => ['bash', 'git', 'npm'].includes(c)), maxConcurrentTasks: 1, maxMemoryUsage: 512 * 1024 * 1024, maxExecutionTime: this.config.timeout, reliability: 0.9, speed: 1.0, quality: 0.8, }; } private async cancelAgentTask(agent: ClaudeAgent): Promise<void> { if (agent.process && !agent.process.killed) { // Send interrupt signal agent.process.kill('SIGINT'); // Wait briefly for graceful shutdown await new Promise(resolve => setTimeout(resolve, 1000)); // Force kill if still running if (!agent.process.killed) { agent.process.kill('SIGKILL'); } } agent.currentTask = undefined; agent.status = 'idle'; agent.lastActivity = new Date(); } private async terminateProcess(process: ChildProcess): Promise<void> { if (process.killed || process.exitCode !== null) { return; } // Send termination signal process.kill('SIGTERM'); // Wait for graceful shutdown await new Promise(resolve => setTimeout(resolve, 2000)); // Force kill if still running if (!process.killed && process.exitCode === null) { process.kill('SIGKILL'); } } private async terminateAllAgents(): Promise<void> { const terminationPromises = Array.from(this.agents.keys()) .map(agentId => this.terminateAgent(agentId, 'Interface shutdown')); await Promise.allSettled(terminationPromises); } private moveAgentToBusyPool(agent: ClaudeAgent): void { const idleIndex = this.processPool.idle.indexOf(agent); if (idleIndex !== -1) { this.processPool.idle.splice(idleIndex, 1); this.processPool.busy.push(agent); } } private returnAgentToIdlePool(agent: ClaudeAgent): void { agent.status = 'idle'; agent.currentTask = undefined; agent.lastActivity = new Date(); const busyIndex = this.processPool.busy.indexOf(agent); if (busyIndex !== -1) { this.processPool.busy.splice(busyIndex, 1); this.processPool.idle.push(agent); } } private moveAgentToFailedPool(agent: ClaudeAgent): void { // Remove from other pools this.removeAgentFromPools(agent); this.processPool.failed.push(agent); } private removeAgentFromPools(agent: ClaudeAgent): void { const idleIndex = this.processPool.idle.indexOf(agent); if (idleIndex !== -1) { this.processPool.idle.splice(idleIndex, 1); } const busyIndex = this.processPool.busy.indexOf(agent); if (busyIndex !== -1) { this.processPool.busy.splice(busyIndex, 1); } const failedIndex = this.processPool.failed.indexOf(agent); if (failedIndex !== -1) { this.processPool.failed.splice(failedIndex, 1); } } private updateAgentMetrics(agent: ClaudeAgent, execution: ClaudeTaskExecution): void { const metrics = agent.metrics; // Update averages const totalTasks = metrics.tasksCompleted + metrics.tasksFailed; if (execution.duration) { metrics.averageResponseTime = totalTasks > 0 ? ((metrics.averageResponseTime * (totalTasks - 1)) + execution.duration) / totalTasks : execution.duration; } // Update success rate metrics.successRate = totalTasks > 0 ? metrics.tasksCompleted / totalTasks : 0; // Update error rate metrics.errorRate = 1 - metrics.successRate; // Update token usage if available if (execution.tokensUsed) { metrics.totalTokensUsed += execution.tokensUsed; } } private getTotalActiveAgents(): number { return this.processPool.idle.length + this.processPool.busy.length; } private calculateThroughput(): number { const agents = Array.from(this.agents.values()); const totalTasks = agents.reduce((sum, a) => sum + a.totalTasks, 0); const totalTime = agents.reduce((sum, a) => sum + a.totalDuration, 0); return totalTime > 0 ? (totalTasks / totalTime) * 60000 : 0; // tasks per minute } private calculatePoolUtilization(): number { const total = this.getTotalActiveAgents(); const busy = this.processPool.busy.length; return total > 0 ? busy / total : 0; } private startHealthChecks(): void { this.healthCheckInterval = setInterval(() => { this.performHealthCheck(); }, this.config.healthCheckInterval); } private performHealthCheck(): void { const now = Date.now(); for (const agent of this.agents.values()) { // Check for stalled agents const inactiveTime = now - agent.lastActivity.getTime(); if (agent.status === 'busy' && inactiveTime > this.config.timeout * 2) { this.logger.warn('Agent appears stalled', { agentId: agent.id, inactiveTime, currentTask: agent.currentTask, }); // Try to recover the agent this.recoverStalledAgent(agent); } // Check for failed processes if (agent.process.killed || agent.process.exitCode !== null) { if (agent.status !== 'terminated') { this.logger.warn('Agent process died unexpectedly', { agentId: agent.id, exitCode: agent.process.exitCode, }); agent.status = 'error'; this.moveAgentToFailedPool(agent); } } } } private async recoverStalledAgent(agent: ClaudeAgent): Promise<void> { try { if (agent.currentTask) { await this.cancelExecution(agent.currentTask, 'Agent recovery'); } this.returnAgentToIdlePool(agent); this.logger.info('Agent recovered from stalled state', { agentId: agent.id, }); } catch (error) { this.logger.error('Failed to recover stalled agent', { agentId: agent.id, error: error instanceof Error ? error.message : String(error), }); // Terminate the problematic agent await this.terminateAgent(agent.id, 'Recovery failed'); } } private initializeProcessPool(): ProcessPool { return { idle: [], busy: [], failed: [], totalSpawned: 0, totalTerminated: 0, recyclingEnabled: this.config.processRecycling, maxAge: 3600000, // 1 hour maxTasks: 100, }; } private initializeAgentMetrics(): ClaudeAgentMetrics { return { tasksCompleted: 0, tasksFailed: 0, averageResponseTime: 0, totalTokensUsed: 0, memoryUsage: 0, cpuUsage: 0, errorRate: 0, successRate: 0, }; } private createDefaultConfig(config: Partial<ClaudeCodeConfig>): ClaudeCodeConfig { return { claudeExecutablePath: 'claude', defaultModel: 'claude-3-5-sonnet-20241022', maxTokens: 4096, temperature: 0.7, timeout: 300000, // 5 minutes maxConcurrentAgents: 10, enableStreaming: false, enableLogging: true, workingDirectory: process.cwd(), environmentVariables: {}, agentPoolSize: 0, processRecycling: true, healthCheckInterval: 30000, // 30 seconds ...config, }; } private setupEventHandlers(): void { this.on('agent:spawned', (data) => { this.logger.info('Agent spawned event', data); }); this.on('agent:terminated', (data) => { this.logger.info('Agent terminated event', data); }); this.on('task:completed', (data) => { this.logger.info('Task completed event', data); }); this.on('task:cancelled', (data) => { this.logger.warn('Task cancelled event', data); }); } } export default ClaudeCodeInterface;