stellar-cyber-mcp-agents
Version:
Model Context Protocol (MCP) server for Stellar Cyber security operations with specialized multi-agent analysis capabilities
337 lines • 12.3 kB
JavaScript
import { EventEmitter } from 'eventemitter3';
import { AgentState, AgentHealth, AgentEventType, RequestPriority } from '../types/agent.js';
export class BaseAgent extends EventEmitter {
metadata;
registry;
channel;
logger;
metrics;
state = AgentState.INITIALIZING;
health = AgentHealth.HEALTHY;
startTime = new Date();
activeRequests = 0;
totalRequests = 0;
errors = 0;
heartbeatInterval;
healthCheckInterval;
constructor(metadata, registry, channel, logger, metrics) {
super();
this.metadata = metadata;
this.registry = registry;
this.channel = channel;
this.logger = logger;
this.metrics = metrics;
}
async initialize() {
this.logger.info('Initializing agent', { agentId: this.metadata.id });
try {
this.setState(AgentState.INITIALIZING);
// Register with registry
await this.registry.registerAgent(this.metadata);
// Set up request handling
this.channel.subscribeToRequests(this.metadata.id, this.handleIncomingRequest.bind(this));
// Set up event handling
this.channel.subscribeToEvents(this.handleAgentEvent.bind(this));
// Perform agent-specific initialization
await this.onInitialize();
this.logger.info('Agent initialized successfully', { agentId: this.metadata.id });
}
catch (error) {
this.logger.error('Failed to initialize agent', { agentId: this.metadata.id, error });
this.setState(AgentState.ERROR);
this.setHealth(AgentHealth.CRITICAL);
throw error;
}
}
async start() {
this.logger.info('Starting agent', { agentId: this.metadata.id });
try {
if (this.state !== AgentState.INITIALIZING) {
throw new Error(`Cannot start agent in state ${this.state}`);
}
this.setState(AgentState.READY);
this.startTime = new Date();
// Start heartbeat
this.startHeartbeat();
// Start health checks
this.startHealthChecks();
// Perform agent-specific startup
await this.onStart();
this.emitEvent(AgentEventType.AGENT_STARTED, {
metadata: this.metadata,
startTime: this.startTime
});
this.logger.info('Agent started successfully', { agentId: this.metadata.id });
}
catch (error) {
this.logger.error('Failed to start agent', { agentId: this.metadata.id, error });
this.setState(AgentState.ERROR);
this.setHealth(AgentHealth.CRITICAL);
throw error;
}
}
async stop() {
this.logger.info('Stopping agent', { agentId: this.metadata.id });
try {
this.setState(AgentState.STOPPING);
// Stop intervals
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
// Wait for active requests to complete (with timeout)
const timeout = 30000; // 30 seconds
const start = Date.now();
while (this.activeRequests > 0 && (Date.now() - start) < timeout) {
await new Promise(resolve => setTimeout(resolve, 100));
}
// Perform agent-specific cleanup
await this.onStop();
// Unsubscribe from channels
this.channel.unsubscribeFromRequests(this.metadata.id);
this.setState(AgentState.STOPPED);
this.emitEvent(AgentEventType.AGENT_STOPPED, {
metadata: this.metadata,
uptime: Date.now() - this.startTime.getTime(),
totalRequests: this.totalRequests,
errors: this.errors
});
this.logger.info('Agent stopped successfully', { agentId: this.metadata.id });
}
catch (error) {
this.logger.error('Failed to stop agent', { agentId: this.metadata.id, error });
this.setState(AgentState.ERROR);
throw error;
}
}
async destroy() {
this.logger.info('Destroying agent', { agentId: this.metadata.id });
try {
if (this.state !== AgentState.STOPPED) {
await this.stop();
}
// Unregister from registry
await this.registry.unregisterAgent(this.metadata.id);
// Perform agent-specific cleanup
await this.onDestroy();
// Remove all listeners
this.removeAllListeners();
this.logger.info('Agent destroyed successfully', { agentId: this.metadata.id });
}
catch (error) {
this.logger.error('Failed to destroy agent', { agentId: this.metadata.id, error });
throw error;
}
}
async healthCheck() {
try {
// Perform agent-specific health check
const health = await this.onHealthCheck();
this.setHealth(health);
return health;
}
catch (error) {
this.logger.error('Health check failed', { agentId: this.metadata.id, error });
this.setHealth(AgentHealth.CRITICAL);
return AgentHealth.CRITICAL;
}
}
// Getters
getId() {
return this.metadata.id;
}
getMetadata() {
return this.metadata;
}
getState() {
return this.state;
}
getHealth() {
return this.health;
}
getUptime() {
return Date.now() - this.startTime.getTime();
}
getStatus() {
return {
id: this.metadata.id,
state: this.state,
health: this.health,
lastHeartbeat: new Date(),
uptime: this.getUptime(),
activeRequests: this.activeRequests,
totalRequests: this.totalRequests,
errors: this.errors
};
}
// Protected utility methods
setState(state) {
const previousState = this.state;
this.state = state;
this.logger.debug('Agent state changed', {
agentId: this.metadata.id,
previousState,
currentState: state
});
this.emit('state_changed', { previousState, currentState: state });
this.metrics.recordGauge('agent.state', this.getStateValue(state));
}
setHealth(health) {
const previousHealth = this.health;
this.health = health;
this.logger.debug('Agent health changed', {
agentId: this.metadata.id,
previousHealth,
currentHealth: health
});
this.emit('health_changed', { previousHealth, currentHealth: health });
this.metrics.recordGauge('agent.health', this.getHealthValue(health));
}
emitEvent(type, data) {
const event = {
id: crypto.randomUUID(),
sourceAgentId: this.metadata.id,
type,
data,
timestamp: new Date()
};
this.channel.broadcastEvent(event).catch(error => {
this.logger.error('Failed to broadcast event', { event, error });
});
}
async sendRequest(targetAgentId, capability, payload, priority = RequestPriority.MEDIUM, timeout = 30000) {
const request = {
id: crypto.randomUUID(),
sourceAgentId: this.metadata.id,
targetAgentId,
capability,
payload,
priority,
timeout,
timestamp: new Date()
};
this.metrics.incrementCounter('agent.requests.sent');
try {
const response = await this.channel.sendRequest(request);
if (response.success) {
this.metrics.incrementCounter('agent.requests.success');
}
else {
this.metrics.incrementCounter('agent.requests.failed');
}
return response;
}
catch (error) {
this.metrics.incrementCounter('agent.requests.error');
throw error;
}
}
async handleIncomingRequest(request) {
const startTime = Date.now();
this.activeRequests++;
this.totalRequests++;
this.metrics.incrementCounter('agent.requests.received');
this.metrics.recordGauge('agent.requests.active', this.activeRequests);
try {
this.logger.debug('Handling incoming request', {
agentId: this.metadata.id,
requestId: request.id,
capability: request.capability,
sourceAgent: request.sourceAgentId
});
const context = {
requestId: request.id,
sourceAgentId: request.sourceAgentId,
correlationId: request.correlationId,
timestamp: request.timestamp,
metadata: {}
};
const result = await this.handleRequest(request, context);
const response = {
requestId: request.id,
sourceAgentId: this.metadata.id,
success: true,
result,
timestamp: new Date(),
processingTime: Date.now() - startTime
};
this.metrics.recordTimer('agent.request.processing_time', response.processingTime);
this.metrics.incrementCounter('agent.requests.processed');
return response;
}
catch (error) {
this.errors++;
this.metrics.incrementCounter('agent.requests.error');
this.logger.error('Error handling request', {
agentId: this.metadata.id,
requestId: request.id,
error
});
const response = {
requestId: request.id,
sourceAgentId: this.metadata.id,
success: false,
error: {
code: 'PROCESSING_ERROR',
message: error instanceof Error ? error.message : 'Unknown error',
details: error
},
timestamp: new Date(),
processingTime: Date.now() - startTime
};
return response;
}
finally {
this.activeRequests--;
this.metrics.recordGauge('agent.requests.active', this.activeRequests);
}
}
handleAgentEvent(event) {
// Override in subclasses if needed
this.emit('agent_event', event);
}
startHeartbeat() {
this.heartbeatInterval = setInterval(async () => {
try {
await this.registry.updateAgentStatus(this.getStatus());
this.emitEvent(AgentEventType.AGENT_HEARTBEAT, this.getStatus());
}
catch (error) {
this.logger.error('Failed to send heartbeat', { agentId: this.metadata.id, error });
}
}, 10000); // 10 seconds
}
startHealthChecks() {
this.healthCheckInterval = setInterval(async () => {
try {
await this.healthCheck();
}
catch (error) {
this.logger.error('Health check failed', { agentId: this.metadata.id, error });
}
}, 30000); // 30 seconds
}
getStateValue(state) {
switch (state) {
case AgentState.INITIALIZING: return 0;
case AgentState.READY: return 1;
case AgentState.BUSY: return 2;
case AgentState.ERROR: return 3;
case AgentState.STOPPING: return 4;
case AgentState.STOPPED: return 5;
default: return -1;
}
}
getHealthValue(health) {
switch (health) {
case AgentHealth.HEALTHY: return 1;
case AgentHealth.DEGRADED: return 2;
case AgentHealth.UNHEALTHY: return 3;
case AgentHealth.CRITICAL: return 4;
default: return -1;
}
}
}
//# sourceMappingURL=base-agent.js.map