mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
661 lines (660 loc) โข 26.4 kB
JavaScript
/**
* 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