Agents created: 1. PlantLineageAgent - Monitors plant ancestry and lineage integrity 2. TransportTrackerAgent - Tracks transport events and carbon footprint 3. DemandForecastAgent - Predicts consumer demand and market trends 4. VerticalFarmAgent - Manages vertical farm operations and optimization 5. EnvironmentAnalysisAgent - Analyzes growing conditions and recommendations 6. MarketMatchingAgent - Connects grower supply with consumer demand 7. SustainabilityAgent - Monitors environmental impact and sustainability 8. NetworkDiscoveryAgent - Maps geographic distribution and network analysis 9. QualityAssuranceAgent - Verifies blockchain integrity and data quality 10. GrowerAdvisoryAgent - Provides personalized growing recommendations Also includes: - BaseAgent abstract class for common functionality - AgentOrchestrator for centralized agent management - Comprehensive type definitions - Full documentation in docs/AGENTS.md
301 lines
7.2 KiB
TypeScript
301 lines
7.2 KiB
TypeScript
/**
|
|
* 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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<AgentTask | null>;
|
|
|
|
/**
|
|
* 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<string, any>, 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<string, any>): 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<AgentPriority, number> = {
|
|
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
|
|
};
|
|
}
|
|
}
|