UNPKG

mcp-product-manager

Version:

MCP Orchestrator for task and project management with web interface

317 lines 11.2 kB
import { getDb } from '../utils/database.js'; class ErrorPatternTracker { constructor() { this.db = null; this.detectionRules = new Map(); this.initialize(); } async initialize() { this.db = await getDb(); await this.loadDetectionRules(); } async loadDetectionRules() { try { const rules = await this.db.all('SELECT * FROM error_detection_rules WHERE is_active = 1'); rules.forEach(rule => { this.detectionRules.set(rule.id, { ...rule, error_codes: rule.error_codes ? rule.error_codes.split(',').map(c => c.trim()) : ['*'], action_config: rule.action_config ? JSON.parse(rule.action_config) : {} }); }); } catch (error) { console.error('Failed to load detection rules:', error); } } /** * Track an error occurrence and check for patterns */ async trackError(error, context, response) { try { const errorCode = error.code || 'UNKNOWN_ERROR'; const errorId = response.metadata.errorId; // Find or create pattern const patternId = await this.findOrCreatePattern(errorCode, context, error.message); // Record the occurrence await this.recordOccurrence(patternId, errorId, error, context, response); // Check detection rules const alerts = await this.checkDetectionRules(patternId, errorCode); // Handle triggered alerts if (alerts.length > 0) { await this.handleAlerts(alerts, patternId); } return { patternId, alerts, patternDetected: alerts.length > 0 }; } catch (trackingError) { console.error('Error tracking failed:', trackingError); return null; } } async findOrCreatePattern(errorCode, context, errorMessage) { const { agentName, agentType, project, operation } = context; // Look for existing pattern const existing = await this.db.get(` SELECT id, frequency FROM error_patterns WHERE error_code = ? AND agent_type = ? AND project = ? AND resolution_status = 'unresolved' ORDER BY last_occurred DESC LIMIT 1 `, [errorCode, agentType || 'unknown', project || 'unknown']); if (existing) { // Update existing pattern await this.db.run(` UPDATE error_patterns SET frequency = frequency + 1, last_occurred = CURRENT_TIMESTAMP, retry_count = ? WHERE id = ? `, [context.retryCount || 0, existing.id]); return existing.id; } else { // Create new pattern const result = await this.db.run(` INSERT INTO error_patterns ( error_code, error_category, error_message, agent_name, agent_type, project, operation, http_status, retry_count ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ errorCode, this.categorizeError(errorCode), errorMessage, agentName || 'unknown', agentType || 'unknown', project || 'unknown', operation || 'unknown', context.httpStatus || 500, context.retryCount || 0 ]); return result.lastID; } } async recordOccurrence(patternId, errorId, error, context, response) { await this.db.run(` INSERT INTO error_occurrences ( pattern_id, error_id, error_code, error_message, agent_name, agent_type, project, operation, context_json, recovery_json, retry_count, request_id, stack_trace ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ patternId, errorId, error.code || 'UNKNOWN_ERROR', error.message, context.agentName || 'unknown', context.agentType || 'unknown', context.project || 'unknown', context.operation || 'unknown', JSON.stringify(context), JSON.stringify(response.recovery), context.retryCount || 0, context.requestId || null, error.stack || null ]); } async checkDetectionRules(patternId, errorCode) { const alerts = []; for (const [ruleId, rule] of this.detectionRules) { // Check if rule applies to this error code if (!rule.error_codes.includes('*') && !rule.error_codes.includes(errorCode)) { continue; } // Count occurrences within timeframe const occurrences = await this.db.get(` SELECT COUNT(*) as count FROM error_occurrences WHERE pattern_id = ? AND occurred_at >= datetime('now', '-${rule.threshold_timeframe} seconds') `, [patternId]); if (occurrences.count >= rule.threshold_count) { alerts.push({ ruleId, ruleName: rule.rule_name, occurrenceCount: occurrences.count, threshold: rule.threshold_count, timeframe: rule.threshold_timeframe, actionType: rule.action_type, actionConfig: rule.action_config }); } } return alerts; } async handleAlerts(alerts, patternId) { for (const alert of alerts) { // Record the alert await this.db.run(` INSERT INTO error_pattern_alerts ( pattern_id, rule_id, alert_type, alert_message, occurrence_count, timeframe_seconds, action_taken ) VALUES (?, ?, ?, ?, ?, ?, ?) `, [ patternId, alert.ruleId, this.getAlertLevel(alert), `${alert.ruleName}: ${alert.occurrenceCount} occurrences in ${alert.timeframe}s`, alert.occurrenceCount, alert.timeframe, alert.actionType ]); // Take action based on alert type await this.executeAlertAction(alert, patternId); } } async executeAlertAction(alert, patternId) { switch (alert.actionType) { case 'create_task': await this.createInterventionTask(alert, patternId); break; case 'alert': await this.sendAlert(alert, patternId); break; case 'block_operation': await this.blockOperation(alert, patternId); break; } } async createInterventionTask(alert, patternId) { // Get pattern details const pattern = await this.db.get('SELECT * FROM error_patterns WHERE id = ?', [patternId]); if (!pattern) return; const { priority = 'high', category = 'intervention' } = alert.actionConfig; // This would normally call the task creation API // For now, log the intention console.log(`[ERROR PATTERN] Creating intervention task:`, { description: `[INTERVENTION] Investigate repeated ${pattern.error_code} errors`, priority, category, project: pattern.project, details: { errorCode: pattern.error_code, frequency: pattern.frequency, agent: pattern.agent_name, alertRule: alert.ruleName } }); } async sendAlert(alert, patternId) { const { alert_level = 'warning' } = alert.actionConfig; console.log(`[ERROR PATTERN ALERT - ${alert_level.toUpperCase()}]`, { rule: alert.ruleName, occurrences: alert.occurrenceCount, timeframe: `${alert.timeframe}s`, patternId }); } async blockOperation(alert, patternId) { // This could set a flag that prevents further operations console.log(`[ERROR PATTERN] Blocking operations due to:`, alert.ruleName); } /** * Get error pattern statistics */ async getPatternStats(project = null, timeframe = '24 hours') { const projectFilter = project ? 'AND project = ?' : ''; const params = project ? [project] : []; const stats = await this.db.all(` SELECT error_code, error_category, COUNT(*) as occurrence_count, COUNT(DISTINCT agent_name) as agents_affected, AVG(retry_count) as avg_retry_count, MAX(last_occurred) as last_occurred FROM error_patterns WHERE last_occurred >= datetime('now', '-${timeframe}') ${projectFilter} GROUP BY error_code, error_category ORDER BY occurrence_count DESC `, params); return stats; } /** * Get recent alerts */ async getRecentAlerts(limit = 10) { return await this.db.all(` SELECT a.*, p.error_code, p.agent_name, p.project FROM error_pattern_alerts a JOIN error_patterns p ON a.pattern_id = p.id WHERE a.acknowledged = 0 ORDER BY a.created_at DESC LIMIT ? `, [limit]); } /** * Mark pattern as resolved */ async resolvePattern(patternId, resolvedBy, notes) { await this.db.run(` UPDATE error_patterns SET resolution_status = 'resolved', resolution_notes = ?, resolved_by = ?, resolved_at = CURRENT_TIMESTAMP WHERE id = ? `, [notes, resolvedBy, patternId]); } /** * Acknowledge an alert */ async acknowledgeAlert(alertId, acknowledgedBy) { await this.db.run(` UPDATE error_pattern_alerts SET acknowledged = 1, acknowledged_by = ?, acknowledged_at = CURRENT_TIMESTAMP WHERE id = ? `, [acknowledgedBy, alertId]); } categorizeError(errorCode) { const categories = { 'PERMISSION_DENIED': 'permissions', 'DATABASE_ERROR': 'infrastructure', 'TASK_NOT_FOUND': 'resource', 'PROJECT_NOT_FOUND': 'resource', 'VALIDATION_ERROR': 'input', 'CONCURRENT_CLAIM': 'concurrency', 'BUNDLE_CLAIM_FAILED': 'bundle' }; return categories[errorCode] || 'unknown'; } getAlertLevel(alert) { if (alert.occurrenceCount >= alert.threshold * 2) { return 'critical'; } else if (alert.occurrenceCount >= alert.threshold * 1.5) { return 'warning'; } return 'info'; } } // Singleton instance let trackerInstance = null; export async function getErrorPatternTracker() { if (!trackerInstance) { trackerInstance = new ErrorPatternTracker(); await trackerInstance.initialize(); } return trackerInstance; } export default ErrorPatternTracker; //# sourceMappingURL=errorPatternTracker.js.map