mcp-product-manager
Version:
MCP Orchestrator for task and project management with web interface
317 lines • 11.2 kB
JavaScript
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