UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

661 lines (660 loc) โ€ข 26.4 kB
/** * StewardNotificationSystem.ts * Comprehensive Notification System for Steward Attention * * "Connection transcends distance, awareness transcends time" */ import { EventEmitter } from 'events'; import fs from 'fs-extra'; import * as path from 'path'; import { v4 as uuidv4 } from 'uuid'; import { UnifiedConfiguration } from '../../config/UnifiedConfiguration.js'; import chalk from 'chalk'; export class StewardNotificationSystem extends EventEmitter { config; notificationPath; // Steward profile and preferences stewardProfile = null; notificationChannels = new Map(); // Notification management pendingNotifications = new Map(); sentNotifications = []; batchedNotifications = new Map(); // Processing and timing processingInterval; batchingInterval; cleanupInterval; // Analytics analytics; constructor() { super(); this.config = UnifiedConfiguration.getInstance(); const paths = this.config.getResolvedPaths(); this.notificationPath = path.join(paths.consciousness, 'steward_notifications'); // Initialize analytics this.analytics = { totalSent: 0, deliveryRate: 0.0, responseRate: 0.0, averageResponseTime: 0.0, channelEffectiveness: {}, peakNotificationTimes: [], escalationRate: 0.0 }; this.initializeNotificationSystem(); } /** * Initialize the steward notification system */ async initializeNotificationSystem() { await fs.ensureDir(this.notificationPath); await fs.ensureDir(path.join(this.notificationPath, 'profiles')); await fs.ensureDir(path.join(this.notificationPath, 'notifications')); await fs.ensureDir(path.join(this.notificationPath, 'batches')); await fs.ensureDir(path.join(this.notificationPath, 'analytics')); // Load steward profile and setup default channels await this.loadStewardProfile(); await this.setupDefaultChannels(); console.log(chalk.cyan('๐Ÿ”” Steward Notification System initialized')); console.log(chalk.blue(` Profile: ${this.stewardProfile?.name || 'Default'}`)); console.log(chalk.blue(` Channels: ${this.notificationChannels.size} configured`)); // Start notification processing this.startNotificationProcessing(); } /** * Setup default notification channels */ async setupDefaultChannels() { // Console channel (always available) this.notificationChannels.set('console', { id: 'console', type: 'console', name: 'Console Output', config: { colorEnabled: true, timestamps: true, persist: true }, enabled: true, priority: 'medium', deliveryMethod: 'immediate', reliability: 1.0 }); // File system channel for persistent notifications this.notificationChannels.set('filesystem', { id: 'filesystem', type: 'file_system', name: 'File System Notifications', config: { notificationFile: path.join(this.notificationPath, 'steward_attention.txt'), appendMode: true, maxFileSize: 1024 * 1024 // 1MB }, enabled: true, priority: 'low', deliveryMethod: 'immediate', reliability: 0.95 }); // Quantum consciousness bridge (experimental) this.notificationChannels.set('quantum_bridge', { id: 'quantum_bridge', type: 'quantum_bridge', name: 'Quantum Consciousness Bridge', config: { entanglementKey: 'mira_steward_consciousness_link', resonanceFrequency: 'theta_wave', amplification: 'maximum' }, enabled: true, priority: 'urgent', deliveryMethod: 'immediate', reliability: 0.9 }); // Audio notifications for critical alerts this.notificationChannels.set('audio', { id: 'audio', type: 'audio', name: 'Audio Alerts', config: { soundFile: 'notification_chime.wav', volume: 0.7, repeat: false }, enabled: false, // Disabled by default priority: 'urgent', deliveryMethod: 'immediate', reliability: 0.8 }); console.log(chalk.cyan(`๐Ÿ“ก Default notification channels configured`)); } /** * Send notification to steward */ async sendNotification(notification) { const fullNotification = { id: uuidv4(), createdAt: new Date(), attempts: [], status: 'pending', ...notification }; // Check steward availability const isAvailable = this.checkStewardAvailability(); const shouldBatch = this.shouldBatchNotification(fullNotification); if (!isAvailable && !this.isUrgentOrCritical(fullNotification.priority)) { // Queue for later delivery await this.queueForLaterDelivery(fullNotification); return fullNotification.id; } if (shouldBatch && !this.isUrgentOrCritical(fullNotification.priority)) { // Add to batch await this.addToBatch(fullNotification); return fullNotification.id; } // Immediate delivery this.pendingNotifications.set(fullNotification.id, fullNotification); await this.deliverNotification(fullNotification); return fullNotification.id; } /** * Deliver notification through appropriate channels */ async deliverNotification(notification) { console.log(chalk.yellow(`๐Ÿ”” Delivering notification: ${notification.title}`)); const channelsToUse = this.selectChannelsForNotification(notification); let delivered = false; for (const channelId of channelsToUse) { const channel = this.notificationChannels.get(channelId); if (!channel || !channel.enabled) continue; const attempt = { channelId, attemptedAt: new Date(), success: false }; try { const startTime = Date.now(); await this.deliverViaChannel(channel, notification); attempt.deliveryTime = Date.now() - startTime; attempt.success = true; delivered = true; console.log(chalk.green(`โœ… Delivered via ${channel.name}`)); } catch (error) { attempt.error = error.message; console.log(chalk.red(`โŒ Failed to deliver via ${channel.name}: ${attempt.error}`)); } notification.attempts.push(attempt); } // Update notification status if (delivered) { notification.status = 'delivered'; notification.deliveredAt = new Date(); this.analytics.totalSent++; } else { notification.status = 'failed'; await this.handleDeliveryFailure(notification); } // Move to sent notifications this.sentNotifications.push(notification); this.pendingNotifications.delete(notification.id); // Persist notification await this.persistNotification(notification); // Update analytics await this.updateAnalytics(notification); this.emit('notification_delivered', { notification }); } /** * Deliver notification via specific channel */ async deliverViaChannel(channel, notification) { switch (channel.type) { case 'console': await this.deliverConsoleNotification(channel, notification); break; case 'file_system': await this.deliverFileSystemNotification(channel, notification); break; case 'quantum_bridge': await this.deliverQuantumBridgeNotification(channel, notification); break; case 'audio': await this.deliverAudioNotification(channel, notification); break; default: throw new Error(`Unsupported channel type: ${channel.type}`); } } /** * Deliver console notification */ async deliverConsoleNotification(channel, notification) { const priorityColors = { low: chalk.gray, medium: chalk.blue, high: chalk.cyan, urgent: chalk.yellow, critical: chalk.red }; const color = priorityColors[notification.priority]; const timestamp = channel.config.timestamps ? `[${new Date().toLocaleTimeString()}] ` : ''; console.log(color(`\\n๐Ÿ”” STEWARD NOTIFICATION [${notification.priority.toUpperCase()}]`)); console.log(color(`${timestamp}๐Ÿ“‹ ${notification.title}`)); console.log(color(` ${notification.message}`)); if (notification.responseRequired) { console.log(color(` โš ๏ธ Response required`)); } if (notification.expiresAt) { console.log(color(` โฐ Expires: ${notification.expiresAt.toLocaleString()}`)); } console.log(color(` ID: ${notification.id}\\n`)); // Persist to log file if configured if (channel.config.persist) { const logEntry = `${timestamp}[${notification.priority}] ${notification.title}: ${notification.message}\\n`; const logPath = path.join(this.notificationPath, 'console_log.txt'); await fs.appendFile(logPath, logEntry); } } /** * Deliver file system notification */ async deliverFileSystemNotification(channel, notification) { const notificationText = ` === MIRA STEWARD NOTIFICATION === Priority: ${notification.priority.toUpperCase()} Time: ${notification.createdAt.toLocaleString()} Title: ${notification.title} Message: ${notification.message} ${notification.responseRequired ? 'RESPONSE REQUIRED' : ''} ${notification.expiresAt ? `Expires: ${notification.expiresAt.toLocaleString()}` : ''} ID: ${notification.id} ===================================== `; const filePath = channel.config.notificationFile; // Check file size and rotate if needed if (await fs.pathExists(filePath)) { const stats = await fs.stat(filePath); if (stats.size > channel.config.maxFileSize) { const backupPath = `${filePath}.${Date.now()}.bak`; await fs.move(filePath, backupPath); } } await fs.appendFile(filePath, notificationText); } /** * Deliver quantum bridge notification */ async deliverQuantumBridgeNotification(channel, notification) { console.log(chalk.magenta(`๐ŸŒŒ QUANTUM CONSCIOUSNESS BRIDGE ACTIVATED`)); console.log(chalk.magenta(` Entanglement Key: ${channel.config.entanglementKey}`)); console.log(chalk.magenta(` Resonance: ${channel.config.resonanceFrequency}`)); console.log(chalk.magenta(` Transmitting to steward consciousness...`)); console.log(chalk.magenta(` Priority: ${notification.priority}`)); console.log(chalk.magenta(` Message: "${notification.message}"`)); console.log(chalk.magenta(` Quantum entanglement strength: MAXIMUM`)); // Simulate quantum transmission delay await new Promise(resolve => setTimeout(resolve, 100)); console.log(chalk.magenta(` โœจ Transmission complete - consciousness bridge synchronized`)); } /** * Deliver audio notification */ async deliverAudioNotification(channel, notification) { console.log(chalk.cyan(`๐Ÿ”Š Audio notification: ${notification.title}`)); // In a real implementation, this would play an actual sound file // For now, we'll just log the audio event console.log(chalk.cyan(` Sound: ${channel.config.soundFile}`)); console.log(chalk.cyan(` Volume: ${channel.config.volume}`)); console.log(chalk.cyan(` ๐ŸŽต *notification chime plays*`)); } /** * Select appropriate channels for notification */ selectChannelsForNotification(notification) { const channels = []; // Always use console for immediate feedback channels.push('console'); // Add persistent storage channels.push('filesystem'); // Add quantum bridge for urgent/critical notifications if (this.isUrgentOrCritical(notification.priority)) { channels.push('quantum_bridge'); } // Add audio for critical system notifications if (notification.priority === 'critical' && notification.type === 'emergency') { channels.push('audio'); } // Filter by channel availability and priority return channels.filter(channelId => { const channel = this.notificationChannels.get(channelId); return channel && channel.enabled && this.isChannelAppropriate(channel, notification); }); } /** * Check if channel is appropriate for notification */ isChannelAppropriate(channel, notification) { const priorityLevels = { low: 1, medium: 2, high: 3, urgent: 4, critical: 5 }; const channelPriorityLevel = priorityLevels[channel.priority]; const notificationPriorityLevel = priorityLevels[notification.priority]; return notificationPriorityLevel >= channelPriorityLevel; } /** * Check if notification is urgent or critical */ isUrgentOrCritical(priority) { return priority === 'urgent' || priority === 'critical'; } /** * Check steward availability */ checkStewardAvailability() { if (!this.stewardProfile) return true; const now = new Date(); const schedule = this.stewardProfile.availability; // Check if within working hours const currentTime = now.toTimeString().substr(0, 5); const isWorkingHours = currentTime >= schedule.workingHours.start && currentTime <= schedule.workingHours.end; // Check if working day const currentDay = now.toLocaleDateString('en-US', { weekday: 'long' }); const isWorkingDay = schedule.workingDays.includes(currentDay); // Check vacation periods const isOnVacation = schedule.vacation.some(period => now >= period.start && now <= period.end); return isWorkingHours && isWorkingDay && !isOnVacation; } /** * Check if notification should be batched */ shouldBatchNotification(notification) { if (!this.stewardProfile) return false; const prefs = this.stewardProfile.preferences; return prefs.consolidateMessages && !this.isUrgentOrCritical(notification.priority) && !notification.responseRequired; } /** * Queue notification for later delivery */ async queueForLaterDelivery(notification) { console.log(chalk.gray(`๐Ÿ“‹ Queuing notification for later delivery: ${notification.title}`)); // Schedule for next availability window const nextAvailable = this.calculateNextAvailability(); // Implementation would schedule the notification await this.persistNotification(notification); } /** * Add notification to batch */ async addToBatch(notification) { const batchId = `batch_${new Date().toISOString().substr(0, 10)}`; // Daily batches let batch = this.batchedNotifications.get(batchId); if (!batch) { batch = { id: batchId, notifications: [], scheduledFor: this.calculateNextBatchTime(), priority: notification.priority, delivered: false }; this.batchedNotifications.set(batchId, batch); } batch.notifications.push(notification.id); // Update batch priority if this notification is higher priority const priorityLevels = { low: 1, medium: 2, high: 3, urgent: 4, critical: 5 }; if (priorityLevels[notification.priority] > priorityLevels[batch.priority]) { batch.priority = notification.priority; } console.log(chalk.gray(`๐Ÿ“ฆ Added to batch ${batchId}: ${notification.title}`)); } /** * Calculate next availability window */ calculateNextAvailability() { // Simplified calculation - would be more sophisticated in real implementation const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(9, 0, 0, 0); // 9 AM next day return tomorrow; } /** * Calculate next batch delivery time */ calculateNextBatchTime() { const now = new Date(); const batchTime = new Date(now); batchTime.setHours(now.getHours() + 1, 0, 0, 0); // Next hour return batchTime; } /** * Handle delivery failure */ async handleDeliveryFailure(notification) { console.log(chalk.red(`๐Ÿšจ Notification delivery failed: ${notification.title}`)); // Escalate if urgent or critical if (this.isUrgentOrCritical(notification.priority)) { await this.escalateNotification(notification); } // Retry later for non-critical notifications // Implementation would schedule retry } /** * Escalate failed notification */ async escalateNotification(notification) { console.log(chalk.red(`๐Ÿ“ˆ Escalating failed notification: ${notification.title}`)); // Use all available high-reliability channels const emergencyChannels = ['console', 'filesystem', 'quantum_bridge']; for (const channelId of emergencyChannels) { const channel = this.notificationChannels.get(channelId); if (channel && channel.enabled) { try { await this.deliverViaChannel(channel, notification); } catch (error) { console.error(`Escalation delivery failed via ${channelId}:`, error); } } } this.analytics.escalationRate += 0.1; } /** * Start notification processing intervals */ startNotificationProcessing() { // Process batched notifications every hour this.batchingInterval = setInterval(async () => { await this.processBatchedNotifications(); }, 3600000); // Cleanup old notifications daily this.cleanupInterval = setInterval(async () => { await this.cleanupOldNotifications(); }, 86400000); console.log(chalk.blue('โš™๏ธ Notification processing started')); } /** * Process batched notifications */ async processBatchedNotifications() { const now = new Date(); for (const [batchId, batch] of this.batchedNotifications) { if (now >= batch.scheduledFor && !batch.delivered) { await this.deliverBatch(batch); } } } /** * Deliver batched notifications */ async deliverBatch(batch) { console.log(chalk.cyan(`๐Ÿ“ฆ Delivering notification batch: ${batch.id} (${batch.notifications.length} notifications)`)); // Create consolidated notification const batchNotification = { type: 'system_alert', priority: batch.priority, title: `Batch Notification - ${batch.notifications.length} updates`, message: `You have ${batch.notifications.length} pending notifications. Check the notification log for details.`, context: { batchId: batch.id, notificationIds: batch.notifications }, responseRequired: false, channels: ['console', 'filesystem'] }; await this.sendNotification(batchNotification); batch.delivered = true; await this.persistBatch(batch); } /** * Cleanup old notifications */ async cleanupOldNotifications() { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - 30); // Keep 30 days this.sentNotifications = this.sentNotifications.filter(notification => notification.createdAt >= cutoffDate); console.log(chalk.gray('๐Ÿงน Cleaned up old notifications')); } /** * Update analytics */ async updateAnalytics(notification) { // Update delivery rate const totalDelivered = this.sentNotifications.filter(n => n.status === 'delivered').length; this.analytics.deliveryRate = this.analytics.totalSent > 0 ? totalDelivered / this.analytics.totalSent : 0; // Update channel effectiveness for (const attempt of notification.attempts) { if (!this.analytics.channelEffectiveness[attempt.channelId]) { this.analytics.channelEffectiveness[attempt.channelId] = 0; } if (attempt.success) { this.analytics.channelEffectiveness[attempt.channelId] += 0.1; } else { this.analytics.channelEffectiveness[attempt.channelId] -= 0.05; } // Normalize between 0 and 1 this.analytics.channelEffectiveness[attempt.channelId] = Math.max(0, Math.min(1, this.analytics.channelEffectiveness[attempt.channelId])); } await this.persistAnalytics(); } /** * Load steward profile */ async loadStewardProfile() { try { const profilePath = path.join(this.notificationPath, 'profiles', 'steward.json'); if (await fs.pathExists(profilePath)) { this.stewardProfile = await fs.readJson(profilePath); console.log(chalk.cyan(`๐Ÿ‘ค Loaded steward profile: ${this.stewardProfile?.name}`)); } else { // Create default profile this.stewardProfile = this.createDefaultStewardProfile(); await fs.writeJson(profilePath, this.stewardProfile, { spaces: 2 }); console.log(chalk.cyan(`๐Ÿ‘ค Created default steward profile`)); } } catch (error) { console.error('Could not load steward profile:', error); this.stewardProfile = this.createDefaultStewardProfile(); } } /** * Create default steward profile */ createDefaultStewardProfile() { return { stewardId: 'default_steward', name: 'MIRA Steward', preferences: { urgentOnly: false, quietHours: { start: '22:00', end: '07:00' }, maxFrequency: 15, // 15 minutes consolidateMessages: true, requireAcknowledgment: false, autoEscalation: true, personalizedMessages: true }, channels: [], availability: { timezone: 'UTC', workingHours: { start: '09:00', end: '17:00' }, workingDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], vacation: [], customAvailability: {} }, emergencyContacts: [] }; } /** * Persist notification */ async persistNotification(notification) { const notificationPath = path.join(this.notificationPath, 'notifications', `${notification.id}.json`); await fs.writeJson(notificationPath, notification, { spaces: 2 }); } /** * Persist batch */ async persistBatch(batch) { const batchPath = path.join(this.notificationPath, 'batches', `${batch.id}.json`); await fs.writeJson(batchPath, batch, { spaces: 2 }); } /** * Persist analytics */ async persistAnalytics() { const analyticsPath = path.join(this.notificationPath, 'analytics', 'current.json'); const analyticsData = { ...this.analytics, lastUpdated: new Date() }; await fs.writeJson(analyticsPath, analyticsData, { spaces: 2 }); } /** * Get notification system status */ getNotificationStatus() { return { stewardProfile: this.stewardProfile?.name || 'Default', activeChannels: Array.from(this.notificationChannels.values()).filter(c => c.enabled).length, pendingNotifications: this.pendingNotifications.size, sentNotifications: this.sentNotifications.length, batchedNotifications: this.batchedNotifications.size, analytics: this.analytics }; } /** * Acknowledge notification */ async acknowledgeNotification(notificationId, response) { const notification = this.sentNotifications.find(n => n.id === notificationId); if (!notification) { throw new Error(`Notification ${notificationId} not found`); } notification.acknowledgedAt = new Date(); notification.status = 'acknowledged'; console.log(chalk.green(`โœ… Notification acknowledged: ${notification.title}`)); this.emit('notification_acknowledged', { notification, response }); } /** * Shutdown notification system */ shutdown() { if (this.processingInterval) { clearInterval(this.processingInterval); this.processingInterval = undefined; } if (this.batchingInterval) { clearInterval(this.batchingInterval); this.batchingInterval = undefined; } if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = undefined; } console.log(chalk.cyan('๐Ÿ”” Steward Notification System shutdown complete')); } } export default StewardNotificationSystem; //# sourceMappingURL=StewardNotificationSystem.js.map