/** * BaseAgent - Abstract base class for all LocalGreenChain agents * Provides common functionality for autonomous task execution */ import { AgentConfig, AgentStatus, AgentMetrics, AgentTask, AgentAlert, AgentError, AgentPriority, BaseAgent as IBaseAgent } from './types'; export abstract class BaseAgent implements IBaseAgent { config: AgentConfig; status: AgentStatus = 'idle'; metrics: AgentMetrics; protected alerts: AgentAlert[] = []; protected taskQueue: AgentTask[] = []; protected runInterval: NodeJS.Timeout | null = null; protected startTime: number = 0; constructor(config: AgentConfig) { this.config = config; this.metrics = { agentId: config.id, tasksCompleted: 0, tasksFailed: 0, averageExecutionMs: 0, lastRunAt: null, lastSuccessAt: null, lastErrorAt: null, uptime: 0, errors: [] }; } /** * Start the agent's execution loop */ async start(): Promise { if (this.status === 'running') { console.log(`[${this.config.name}] Already running`); return; } this.status = 'running'; this.startTime = Date.now(); console.log(`[${this.config.name}] Starting agent...`); // Run immediately once await this.executeTask(); // Set up interval this.runInterval = setInterval(async () => { if (this.status === 'running') { await this.executeTask(); } }, this.config.intervalMs); this.emitEvent('agent_started', { config: this.config }); } /** * Stop the agent */ async stop(): Promise { if (this.runInterval) { clearInterval(this.runInterval); this.runInterval = null; } this.status = 'idle'; this.metrics.uptime += Date.now() - this.startTime; console.log(`[${this.config.name}] Stopped`); this.emitEvent('agent_stopped', { uptime: this.metrics.uptime }); } /** * Pause the agent */ pause(): void { if (this.status === 'running') { this.status = 'paused'; console.log(`[${this.config.name}] Paused`); } } /** * Resume the agent */ resume(): void { if (this.status === 'paused') { this.status = 'running'; console.log(`[${this.config.name}] Resumed`); } } /** * Execute a single task cycle */ protected async executeTask(): Promise { const startTime = Date.now(); this.metrics.lastRunAt = new Date().toISOString(); try { const task = await this.runOnce(); if (task) { const executionTime = Date.now() - startTime; this.updateAverageExecutionTime(executionTime); if (task.status === 'completed') { this.metrics.tasksCompleted++; this.metrics.lastSuccessAt = new Date().toISOString(); this.emitEvent('task_completed', { taskId: task.id, result: task.result }); } else if (task.status === 'failed') { this.metrics.tasksFailed++; this.metrics.lastErrorAt = new Date().toISOString(); this.emitEvent('task_failed', { taskId: task.id, error: task.error }); } } } catch (error: any) { this.handleError(error); } } /** * Run a single execution cycle - must be implemented by subclasses */ abstract runOnce(): Promise; /** * Get current metrics */ getMetrics(): AgentMetrics { return { ...this.metrics, uptime: this.status === 'running' ? this.metrics.uptime + (Date.now() - this.startTime) : this.metrics.uptime }; } /** * Get all alerts */ getAlerts(): AgentAlert[] { return this.alerts; } /** * Add a task to the queue */ protected addTask(type: string, payload: Record, priority: AgentPriority = 'medium'): AgentTask { const task: AgentTask = { id: `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, agentId: this.config.id, type, payload, priority, createdAt: new Date().toISOString(), status: 'pending', retryCount: 0 }; this.taskQueue.push(task); this.sortTaskQueue(); return task; } /** * Create an alert */ protected createAlert( severity: AgentAlert['severity'], title: string, message: string, options?: { actionRequired?: string; relatedEntityId?: string; relatedEntityType?: string; } ): AgentAlert { const alert: AgentAlert = { id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, agentId: this.config.id, severity, title, message, timestamp: new Date().toISOString(), acknowledged: false, ...options }; this.alerts.push(alert); // Keep only last 100 alerts if (this.alerts.length > 100) { this.alerts = this.alerts.slice(-100); } console.log(`[${this.config.name}] Alert (${severity}): ${title}`); return alert; } /** * Handle errors */ protected handleError(error: any, taskId?: string): void { const agentError: AgentError = { timestamp: new Date().toISOString(), message: error.message || String(error), taskId, stack: error.stack }; this.metrics.errors.push(agentError); this.metrics.lastErrorAt = agentError.timestamp; // Keep only last 50 errors if (this.metrics.errors.length > 50) { this.metrics.errors = this.metrics.errors.slice(-50); } console.error(`[${this.config.name}] Error: ${agentError.message}`); // Create critical alert if too many errors const recentErrors = this.metrics.errors.filter( e => Date.now() - new Date(e.timestamp).getTime() < 60000 ); if (recentErrors.length >= 5) { this.createAlert('critical', 'High Error Rate', `${recentErrors.length} errors in the last minute`, { actionRequired: 'Check agent configuration and dependencies' } ); } } /** * Emit an event */ protected emitEvent(eventType: string, data: Record): void { // Events can be picked up by the orchestrator console.log(`[${this.config.name}] Event: ${eventType}`); } /** * Update average execution time */ private updateAverageExecutionTime(newTime: number): void { const totalTasks = this.metrics.tasksCompleted + this.metrics.tasksFailed; if (totalTasks === 0) { this.metrics.averageExecutionMs = newTime; } else { this.metrics.averageExecutionMs = (this.metrics.averageExecutionMs * totalTasks + newTime) / (totalTasks + 1); } } /** * Sort task queue by priority */ private sortTaskQueue(): void { const priorityOrder: Record = { critical: 0, high: 1, medium: 2, low: 3 }; this.taskQueue.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]); } /** * Create a task result */ protected createTaskResult( type: string, status: 'completed' | 'failed', result?: any, error?: string ): AgentTask { return { id: `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, agentId: this.config.id, type, payload: {}, priority: 'medium', createdAt: new Date().toISOString(), status, result, error, retryCount: 0 }; } }