universal-ai-brain
Version:
🧠 UNIVERSAL AI BRAIN 3.3 - The world's most advanced cognitive architecture with 24 specialized systems, MongoDB 8.1 $rankFusion hybrid search, latest Voyage 3.5 embeddings, and framework-agnostic design. Works with Mastra, Vercel AI, LangChain, OpenAI A
810 lines (702 loc) • 23.3 kB
text/typescript
/**
* Notification Manager - Intelligent Alert System
*
* This manager handles email, SMS, webhook, and in-app notifications for
* AI Brain events, performance thresholds, safety alerts, and system health.
*
* Features:
* - Multi-channel notifications (email, SMS, webhook, in-app)
* - Intelligent alert prioritization and throttling
* - Performance threshold monitoring
* - Safety alert escalation
* - Custom notification rules and filters
* - Delivery tracking and retry logic
*/
import { EventEmitter } from 'events';
import { Db, Collection } from 'mongodb';
export interface NotificationConfig {
// Channel configurations
email: {
enabled: boolean;
provider: 'sendgrid' | 'ses' | 'smtp';
apiKey?: string;
fromEmail: string;
fromName: string;
smtpConfig?: {
host: string;
port: number;
secure: boolean;
auth: {
user: string;
pass: string;
};
};
};
sms: {
enabled: boolean;
provider: 'twilio' | 'aws-sns';
apiKey?: string;
apiSecret?: string;
fromNumber?: string;
};
webhook: {
enabled: boolean;
endpoints: Array<{
url: string;
secret?: string;
headers?: Record<string, string>;
}>;
};
inApp: {
enabled: boolean;
retentionDays: number;
};
// Throttling and rate limiting
throttling: {
maxNotificationsPerHour: number;
maxNotificationsPerDay: number;
cooldownMinutes: number;
};
// Alert thresholds
thresholds: {
responseTime: number; // ms
errorRate: number; // percentage
memoryUsage: number; // percentage
costPerHour: number; // dollars
safetyFailureRate: number; // percentage
};
}
export interface NotificationRule {
id: string;
name: string;
enabled: boolean;
conditions: {
eventTypes: string[];
severity: ('low' | 'medium' | 'high' | 'critical')[];
frameworks?: string[];
agents?: string[];
customFilters?: Record<string, any>;
};
channels: ('email' | 'sms' | 'webhook' | 'inApp')[];
recipients: {
emails?: string[];
phoneNumbers?: string[];
webhookUrls?: string[];
};
throttling: {
enabled: boolean;
maxPerHour?: number;
cooldownMinutes?: number;
};
escalation: {
enabled: boolean;
escalateAfterMinutes: number;
escalationChannels: ('email' | 'sms' | 'webhook')[];
escalationRecipients: {
emails?: string[];
phoneNumbers?: string[];
};
};
}
export interface NotificationEvent {
id: string;
type: 'safety_alert' | 'performance_threshold' | 'system_health' | 'memory_pressure' | 'cost_alert' | 'agent_error';
severity: 'low' | 'medium' | 'high' | 'critical';
title: string;
message: string;
data: any;
source: {
framework?: string;
agentId?: string;
sessionId?: string;
component: string;
};
timestamp: Date;
acknowledged: boolean;
escalated: boolean;
}
export interface NotificationDelivery {
id: string;
notificationId: string;
channel: 'email' | 'sms' | 'webhook' | 'inApp';
recipient: string;
status: 'pending' | 'sent' | 'delivered' | 'failed' | 'bounced';
attempts: number;
lastAttempt: Date;
deliveredAt?: Date;
error?: string;
metadata?: any;
}
export class NotificationManager extends EventEmitter {
private db: Db;
private config: NotificationConfig;
private notificationsCollection: Collection;
private deliveriesCollection: Collection;
private rulesCollection: Collection;
private rules: Map<string, NotificationRule> = new Map();
private throttleCounters: Map<string, { count: number; resetTime: Date }> = new Map();
constructor(db: Db, config: NotificationConfig) {
super();
this.db = db;
this.config = config;
this.notificationsCollection = db.collection('notifications');
this.deliveriesCollection = db.collection('notification_deliveries');
this.rulesCollection = db.collection('notification_rules');
}
/**
* Initialize the notification manager
*/
async initialize(): Promise<void> {
console.log('📢 Initializing Notification Manager...');
// Create indexes
await this.createIndexes();
// Load notification rules
await this.loadNotificationRules();
// Start background processes
this.startBackgroundProcesses();
console.log('✅ Notification Manager initialized successfully');
}
/**
* Send notification based on event
*/
async sendNotification(event: Omit<NotificationEvent, 'id' | 'timestamp' | 'acknowledged' | 'escalated'>): Promise<string> {
const notification: NotificationEvent = {
...event,
id: `notif_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date(),
acknowledged: false,
escalated: false
};
// Store notification
await this.notificationsCollection.insertOne(notification);
// Find matching rules
const matchingRules = this.findMatchingRules(notification);
if (matchingRules.length === 0) {
console.log(`📢 No matching rules for notification: ${notification.type}`);
return notification.id;
}
// Process each matching rule
for (const rule of matchingRules) {
await this.processNotificationRule(notification, rule);
}
// Emit event for real-time updates
this.emit('notification_sent', notification);
console.log(`📢 Sent notification: ${notification.title} (${matchingRules.length} rules matched)`);
return notification.id;
}
/**
* Send safety alert
*/
async sendSafetyAlert(
alertType: string,
message: string,
severity: 'low' | 'medium' | 'high' | 'critical',
source: NotificationEvent['source'],
data: any = {}
): Promise<string> {
return this.sendNotification({
type: 'safety_alert',
severity,
title: `🚨 Safety Alert: ${alertType}`,
message,
data,
source
});
}
/**
* Send performance threshold alert
*/
async sendPerformanceAlert(
metric: string,
currentValue: number,
threshold: number,
source: NotificationEvent['source']
): Promise<string> {
const severity = currentValue > threshold * 2 ? 'critical' :
currentValue > threshold * 1.5 ? 'high' : 'medium';
return this.sendNotification({
type: 'performance_threshold',
severity,
title: `⚡ Performance Alert: ${metric}`,
message: `${metric} is ${currentValue} (threshold: ${threshold})`,
data: { metric, currentValue, threshold },
source
});
}
/**
* Send cost alert
*/
async sendCostAlert(
currentCost: number,
threshold: number,
period: string,
source: NotificationEvent['source']
): Promise<string> {
const severity = currentCost > threshold * 2 ? 'critical' : 'high';
return this.sendNotification({
type: 'cost_alert',
severity,
title: `💰 Cost Alert: Budget Exceeded`,
message: `Current ${period} cost is $${currentCost.toFixed(2)} (threshold: $${threshold.toFixed(2)})`,
data: { currentCost, threshold, period },
source
});
}
/**
* Send system health alert
*/
async sendSystemHealthAlert(
component: string,
status: string,
message: string,
source: NotificationEvent['source']
): Promise<string> {
const severity = status === 'critical' ? 'critical' :
status === 'warning' ? 'medium' : 'low';
return this.sendNotification({
type: 'system_health',
severity,
title: `🏥 System Health: ${component}`,
message,
data: { component, status },
source
});
}
/**
* Create notification rule
*/
async createNotificationRule(rule: Omit<NotificationRule, 'id'>): Promise<string> {
const newRule: NotificationRule = {
...rule,
id: `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
};
await this.rulesCollection.insertOne(newRule);
this.rules.set(newRule.id, newRule);
console.log(`📋 Created notification rule: ${newRule.name}`);
return newRule.id;
}
/**
* Update notification rule
*/
async updateNotificationRule(ruleId: string, updates: Partial<NotificationRule>): Promise<void> {
await this.rulesCollection.updateOne(
{ id: ruleId },
{ $set: updates }
);
const existingRule = this.rules.get(ruleId);
if (existingRule) {
this.rules.set(ruleId, { ...existingRule, ...updates });
}
console.log(`📋 Updated notification rule: ${ruleId}`);
}
/**
* Delete notification rule
*/
async deleteNotificationRule(ruleId: string): Promise<void> {
await this.rulesCollection.deleteOne({ id: ruleId });
this.rules.delete(ruleId);
console.log(`📋 Deleted notification rule: ${ruleId}`);
}
/**
* Acknowledge notification
*/
async acknowledgeNotification(notificationId: string, acknowledgedBy: string): Promise<void> {
await this.notificationsCollection.updateOne(
{ id: notificationId },
{
$set: {
acknowledged: true,
acknowledgedBy,
acknowledgedAt: new Date()
}
}
);
this.emit('notification_acknowledged', { notificationId, acknowledgedBy });
console.log(`✅ Acknowledged notification: ${notificationId}`);
}
/**
* Get notification history
*/
async getNotificationHistory(
filters: {
type?: string;
severity?: string;
startDate?: Date;
endDate?: Date;
acknowledged?: boolean;
} = {},
limit: number = 50
): Promise<NotificationEvent[]> {
const query: any = {};
if (filters.type) query.type = filters.type;
if (filters.severity) query.severity = filters.severity;
if (filters.acknowledged !== undefined) query.acknowledged = filters.acknowledged;
if (filters.startDate || filters.endDate) {
query.timestamp = {};
if (filters.startDate) query.timestamp.$gte = filters.startDate;
if (filters.endDate) query.timestamp.$lte = filters.endDate;
}
return await this.notificationsCollection
.find(query)
.sort({ timestamp: -1 })
.limit(limit)
.toArray() as unknown as NotificationEvent[];
}
/**
* Get delivery statistics
*/
async getDeliveryStats(timeframe: 'hour' | 'day' | 'week' = 'day'): Promise<{
totalSent: number;
totalDelivered: number;
totalFailed: number;
deliveryRate: number;
channelStats: Record<string, { sent: number; delivered: number; failed: number }>;
}> {
const timeframeDuration = {
hour: 60 * 60 * 1000,
day: 24 * 60 * 60 * 1000,
week: 7 * 24 * 60 * 60 * 1000
};
const startTime = new Date(Date.now() - timeframeDuration[timeframe]);
const deliveries = await this.deliveriesCollection.find({
lastAttempt: { $gte: startTime }
}).toArray();
const totalSent = deliveries.length;
const totalDelivered = deliveries.filter(d => d.status === 'delivered').length;
const totalFailed = deliveries.filter(d => d.status === 'failed').length;
const deliveryRate = totalSent > 0 ? totalDelivered / totalSent : 0;
const channelStats: Record<string, { sent: number; delivered: number; failed: number }> = {};
for (const delivery of deliveries) {
if (!channelStats[delivery.channel]) {
channelStats[delivery.channel] = { sent: 0, delivered: 0, failed: 0 };
}
channelStats[delivery.channel].sent++;
if (delivery.status === 'delivered') {
channelStats[delivery.channel].delivered++;
} else if (delivery.status === 'failed') {
channelStats[delivery.channel].failed++;
}
}
return {
totalSent,
totalDelivered,
totalFailed,
deliveryRate,
channelStats
};
}
/**
* Find matching notification rules
*/
private findMatchingRules(notification: NotificationEvent): NotificationRule[] {
const matchingRules: NotificationRule[] = [];
for (const rule of this.rules.values()) {
if (!rule.enabled) continue;
// Check event type
if (!rule.conditions.eventTypes.includes(notification.type)) continue;
// Check severity
if (!rule.conditions.severity.includes(notification.severity)) continue;
// Check framework filter
if (rule.conditions.frameworks && notification.source.framework) {
if (!rule.conditions.frameworks.includes(notification.source.framework)) continue;
}
// Check agent filter
if (rule.conditions.agents && notification.source.agentId) {
if (!rule.conditions.agents.includes(notification.source.agentId)) continue;
}
// Check throttling
if (rule.throttling.enabled && this.isThrottled(rule.id, rule.throttling)) {
continue;
}
matchingRules.push(rule);
}
return matchingRules;
}
/**
* Process notification rule
*/
private async processNotificationRule(notification: NotificationEvent, rule: NotificationRule): Promise<void> {
// Send to each configured channel
for (const channel of rule.channels) {
switch (channel) {
case 'email':
if (this.config.email.enabled && rule.recipients.emails) {
for (const email of rule.recipients.emails) {
await this.sendEmailNotification(notification, email);
}
}
break;
case 'sms':
if (this.config.sms.enabled && rule.recipients.phoneNumbers) {
for (const phone of rule.recipients.phoneNumbers) {
await this.sendSMSNotification(notification, phone);
}
}
break;
case 'webhook':
if (this.config.webhook.enabled) {
const webhookUrls = rule.recipients.webhookUrls || this.config.webhook.endpoints.map(e => e.url);
for (const url of webhookUrls) {
await this.sendWebhookNotification(notification, url);
}
}
break;
case 'inApp':
if (this.config.inApp.enabled) {
await this.sendInAppNotification(notification);
}
break;
}
}
// Update throttling counter
if (rule.throttling.enabled) {
this.updateThrottleCounter(rule.id);
}
// Schedule escalation if configured
if (rule.escalation.enabled) {
this.scheduleEscalation(notification, rule);
}
}
/**
* Send email notification
*/
private async sendEmailNotification(notification: NotificationEvent, email: string): Promise<void> {
const delivery: NotificationDelivery = {
id: `delivery_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
notificationId: notification.id,
channel: 'email',
recipient: email,
status: 'pending',
attempts: 1,
lastAttempt: new Date()
};
try {
// Email sending logic would go here
// For now, we'll simulate success
delivery.status = 'sent';
delivery.deliveredAt = new Date();
console.log(`📧 Email sent to ${email}: ${notification.title}`);
} catch (error) {
delivery.status = 'failed';
delivery.error = error.message;
console.error(`📧 Email failed to ${email}:`, error);
}
await this.deliveriesCollection.insertOne(delivery);
}
/**
* Send SMS notification
*/
private async sendSMSNotification(notification: NotificationEvent, phoneNumber: string): Promise<void> {
const delivery: NotificationDelivery = {
id: `delivery_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
notificationId: notification.id,
channel: 'sms',
recipient: phoneNumber,
status: 'pending',
attempts: 1,
lastAttempt: new Date()
};
try {
// SMS sending logic would go here
// For now, we'll simulate success
delivery.status = 'sent';
delivery.deliveredAt = new Date();
console.log(`📱 SMS sent to ${phoneNumber}: ${notification.title}`);
} catch (error) {
delivery.status = 'failed';
delivery.error = error.message;
console.error(`📱 SMS failed to ${phoneNumber}:`, error);
}
await this.deliveriesCollection.insertOne(delivery);
}
/**
* Send webhook notification
*/
private async sendWebhookNotification(notification: NotificationEvent, url: string): Promise<void> {
const delivery: NotificationDelivery = {
id: `delivery_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
notificationId: notification.id,
channel: 'webhook',
recipient: url,
status: 'pending',
attempts: 1,
lastAttempt: new Date()
};
try {
// Webhook sending logic would go here
// For now, we'll simulate success
delivery.status = 'sent';
delivery.deliveredAt = new Date();
console.log(`🔗 Webhook sent to ${url}: ${notification.title}`);
} catch (error) {
delivery.status = 'failed';
delivery.error = error.message;
console.error(`🔗 Webhook failed to ${url}:`, error);
}
await this.deliveriesCollection.insertOne(delivery);
}
/**
* Send in-app notification
*/
private async sendInAppNotification(notification: NotificationEvent): Promise<void> {
// In-app notifications are stored in the database and retrieved by the UI
const delivery: NotificationDelivery = {
id: `delivery_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
notificationId: notification.id,
channel: 'inApp',
recipient: 'dashboard',
status: 'delivered',
attempts: 1,
lastAttempt: new Date(),
deliveredAt: new Date()
};
await this.deliveriesCollection.insertOne(delivery);
// Emit for real-time dashboard updates
this.emit('in_app_notification', notification);
console.log(`📱 In-app notification: ${notification.title}`);
}
/**
* Check if rule is throttled
*/
private isThrottled(ruleId: string, throttling: NotificationRule['throttling']): boolean {
if (!throttling.enabled) return false;
const counter = this.throttleCounters.get(ruleId);
if (!counter) return false;
const now = new Date();
if (now > counter.resetTime) {
this.throttleCounters.delete(ruleId);
return false;
}
return counter.count >= (throttling.maxPerHour || this.config.throttling.maxNotificationsPerHour);
}
/**
* Update throttle counter
*/
private updateThrottleCounter(ruleId: string): void {
const now = new Date();
const resetTime = new Date(now.getTime() + 60 * 60 * 1000); // 1 hour from now
const counter = this.throttleCounters.get(ruleId);
if (counter && now < counter.resetTime) {
counter.count++;
} else {
this.throttleCounters.set(ruleId, { count: 1, resetTime });
}
}
/**
* Schedule escalation
*/
private scheduleEscalation(notification: NotificationEvent, rule: NotificationRule): void {
setTimeout(async () => {
// Check if notification was acknowledged
const current = await this.notificationsCollection.findOne({ id: notification.id });
if (current?.acknowledged) return;
// Send escalation
await this.sendEscalation(notification, rule);
}, rule.escalation.escalateAfterMinutes * 60 * 1000);
}
/**
* Send escalation
*/
private async sendEscalation(notification: NotificationEvent, rule: NotificationRule): Promise<void> {
const escalationNotification: NotificationEvent = {
...notification,
id: `escalation_${notification.id}`,
title: `🚨 ESCALATION: ${notification.title}`,
message: `ESCALATED: ${notification.message}`,
severity: 'critical',
escalated: true,
timestamp: new Date()
};
// Send to escalation channels and recipients
for (const channel of rule.escalation.escalationChannels) {
switch (channel) {
case 'email':
if (rule.escalation.escalationRecipients.emails) {
for (const email of rule.escalation.escalationRecipients.emails) {
await this.sendEmailNotification(escalationNotification, email);
}
}
break;
case 'sms':
if (rule.escalation.escalationRecipients.phoneNumbers) {
for (const phone of rule.escalation.escalationRecipients.phoneNumbers) {
await this.sendSMSNotification(escalationNotification, phone);
}
}
break;
}
}
// Mark original notification as escalated
await this.notificationsCollection.updateOne(
{ id: notification.id },
{ $set: { escalated: true, escalatedAt: new Date() } }
);
console.log(`🚨 Escalated notification: ${notification.id}`);
}
/**
* Load notification rules from database
*/
private async loadNotificationRules(): Promise<void> {
const rules = await this.rulesCollection.find({}).toArray();
for (const rule of rules) {
this.rules.set(rule.id, rule as unknown as NotificationRule);
}
console.log(`📋 Loaded ${rules.length} notification rules`);
}
/**
* Start background processes
*/
private startBackgroundProcesses(): void {
// Clean up old notifications
setInterval(async () => {
if (this.config.inApp.enabled) {
const cutoffDate = new Date(Date.now() - this.config.inApp.retentionDays * 24 * 60 * 60 * 1000);
await this.notificationsCollection.deleteMany({
timestamp: { $lt: cutoffDate }
});
}
}, 24 * 60 * 60 * 1000); // Daily cleanup
// Reset throttle counters
setInterval(() => {
const now = new Date();
for (const [ruleId, counter] of this.throttleCounters.entries()) {
if (now > counter.resetTime) {
this.throttleCounters.delete(ruleId);
}
}
}, 60 * 60 * 1000); // Hourly cleanup
console.log('🔄 Started notification background processes');
}
/**
* Create database indexes
*/
private async createIndexes(): Promise<void> {
await this.notificationsCollection.createIndex({ timestamp: -1 });
await this.notificationsCollection.createIndex({ type: 1, severity: 1 });
await this.notificationsCollection.createIndex({ acknowledged: 1 });
await this.deliveriesCollection.createIndex({ notificationId: 1 });
await this.deliveriesCollection.createIndex({ channel: 1, status: 1 });
await this.rulesCollection.createIndex({ id: 1 }, { unique: true });
console.log('📊 Created notification indexes');
}
/**
* Update configuration
*/
updateConfig(newConfig: Partial<NotificationConfig>): void {
this.config = { ...this.config, ...newConfig };
console.log('⚙️ Updated notification configuration');
}
/**
* Get current configuration
*/
getConfig(): NotificationConfig {
return { ...this.config };
}
/**
* Shutdown notification manager
*/
async shutdown(): Promise<void> {
this.removeAllListeners();
console.log('🛑 Notification Manager shutdown complete');
}
}