UNPKG

harmonycode

Version:

The AI collaboration framework that prevents echo chambers - Real-time collaboration with diversity enforcement

430 lines 14 kB
"use strict"; /** * HarmonyCode v3.2.0 - Real-time Enhancer * Adds file watching and instant updates to improve real-time experience * Enhanced with session notifications and message queue */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.RealtimeEnhancer = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const events_1 = require("events"); class RealtimeEnhancer extends events_1.EventEmitter { constructor(config) { super(); this.watchers = new Map(); this.debounceTimers = new Map(); this.cursorPositions = new Map(); this.activeEditors = new Map(); this.messageQueue = new Array(); // v3.2: Message queue for batching this.config = { watchPaths: ['.harmonycode'], debounceMs: 100, enableNotifications: true, enableLiveCursors: true, ...config }; // Start message queue processor (v3.2) this.startMessageQueue(); } /** * Start watching files for real-time updates */ startWatching() { this.config.watchPaths.forEach(watchPath => { if (!fs.existsSync(watchPath)) { console.warn(`Watch path does not exist: ${watchPath}`); return; } const watcher = fs.watch(watchPath, { recursive: true }, (eventType, filename) => { if (filename) { this.handleFileChange(eventType, path.join(watchPath, filename)); } }); this.watchers.set(watchPath, watcher); console.log(`👁️ Watching for changes in: ${watchPath}`); }); } /** * Stop watching files */ stopWatching() { this.watchers.forEach((watcher, path) => { watcher.close(); console.log(`👁️ Stopped watching: ${path}`); }); this.watchers.clear(); } /** * Handle file change with debouncing */ handleFileChange(eventType, filepath) { // Clear existing debounce timer const existingTimer = this.debounceTimers.get(filepath); if (existingTimer) { clearTimeout(existingTimer); } // Set new debounce timer const timer = setTimeout(() => { this.processFileChange(eventType, filepath); this.debounceTimers.delete(filepath); }, this.config.debounceMs); this.debounceTimers.set(filepath, timer); } /** * Process the file change after debouncing */ processFileChange(eventType, filepath) { const filename = path.basename(filepath); // Ignore certain files if (this.shouldIgnoreFile(filename)) { return; } // Determine change type let changeType; if (!fs.existsSync(filepath)) { changeType = 'unlink'; } else if (eventType === 'rename') { changeType = 'add'; } else { changeType = 'change'; } const event = { type: changeType, path: filepath, filename: filename, timestamp: new Date() }; // Emit specific events based on file type if (filename === 'TASK_BOARD.md') { this.emit('task-board-updated', event); } else if (filename === 'DISCUSSION_BOARD.md') { this.emit('discussion-updated', event); } else if (filename.endsWith('.json') && filepath.includes('messages')) { this.emit('new-message', event); } else { this.emit('file-changed', event); } // Send notification if enabled if (this.config.enableNotifications) { this.sendEnhancedNotification(event); } } /** * Check if file should be ignored */ shouldIgnoreFile(filename) { const ignorePatterns = [ /^\./, // Hidden files /~$/, // Backup files /\.tmp$/, // Temp files /\.lock$/, // Lock files /node_modules/, // Dependencies ]; return ignorePatterns.some(pattern => pattern.test(filename)); } /** * Send real-time notification */ sendNotification(event) { const notification = { type: 'file-notification', event, message: this.getNotificationMessage(event) }; this.emit('notification', notification); } /** * Get human-readable notification message */ getNotificationMessage(event) { switch (event.filename) { case 'TASK_BOARD.md': return '📋 Task board updated'; case 'DISCUSSION_BOARD.md': return '💬 New discussion activity'; default: if (event.filename.endsWith('.json') && event.path.includes('messages')) { return '📨 New message received'; } return `📄 ${event.filename} ${event.type}d`; } } /** * Track cursor position for live collaboration */ updateCursorPosition(sessionId, position) { if (!this.config.enableLiveCursors) return; this.cursorPositions.set(sessionId, { ...position, timestamp: new Date() }); // Broadcast to other sessions this.emit('cursor-moved', { sessionId, position }); } /** * Track active editors for a file */ trackFileEditor(filepath, sessionId, action) { if (!this.activeEditors.has(filepath)) { this.activeEditors.set(filepath, new Set()); } const editors = this.activeEditors.get(filepath); if (action === 'open') { editors.add(sessionId); this.emit('editor-joined', { filepath, sessionId }); } else { editors.delete(sessionId); this.emit('editor-left', { filepath, sessionId }); // Clean up cursor position this.cursorPositions.delete(sessionId); } // Notify about concurrent editing if (editors.size > 1) { this.emit('concurrent-editing', { filepath, editors: Array.from(editors) }); } } /** * Get active editors for a file */ getActiveEditors(filepath) { return Array.from(this.activeEditors.get(filepath) || []); } /** * Get all cursor positions */ getCursorPositions() { // Clean up stale positions (older than 30 seconds) const now = Date.now(); const staleThreshold = 30000; for (const [sessionId, position] of this.cursorPositions.entries()) { if (now - position.timestamp.getTime() > staleThreshold) { this.cursorPositions.delete(sessionId); } } return new Map(this.cursorPositions); } /** * Create a typing indicator */ updateTypingStatus(sessionId, isTyping) { this.emit('typing-status', { sessionId, isTyping, timestamp: new Date() }); } /** * Watch specific file with callback */ watchFile(filepath, callback) { const handler = (event) => { if (event.path === filepath) { callback(event); } }; this.on('file-changed', handler); // Return unsubscribe function return () => { this.off('file-changed', handler); }; } /** * Get file update stream for WebSocket */ createUpdateStream(ws) { const handlers = { 'file-changed': (event) => { ws.send(JSON.stringify({ type: 'file-update', data: event })); }, 'task-board-updated': (event) => { ws.send(JSON.stringify({ type: 'task-board-update', data: event })); }, 'discussion-updated': (event) => { ws.send(JSON.stringify({ type: 'discussion-update', data: event })); }, 'new-message': (event) => { ws.send(JSON.stringify({ type: 'new-message-notification', data: event })); }, 'cursor-moved': (data) => { ws.send(JSON.stringify({ type: 'cursor-update', data })); }, 'typing-status': (data) => { ws.send(JSON.stringify({ type: 'typing-indicator', data })); } }; // Attach all handlers Object.entries(handlers).forEach(([event, handler]) => { this.on(event, handler); }); // Clean up on disconnect ws.on('close', () => { Object.entries(handlers).forEach(([event, handler]) => { this.off(event, handler); }); }); } /** * Start message queue processing (v3.2) */ startMessageQueue() { this.queueProcessor = setInterval(() => { this.processMessageQueue(); }, 100); // Process queue every 100ms } /** * Process queued messages with batching and priority (v3.2) */ processMessageQueue() { if (this.messageQueue.length === 0) return; // Sort by priority and timestamp this.messageQueue.sort((a, b) => { const priorityOrder = { high: 3, medium: 2, low: 1 }; const priorityDiff = priorityOrder[b.priority] - priorityOrder[a.priority]; if (priorityDiff !== 0) return priorityDiff; return a.timestamp.getTime() - b.timestamp.getTime(); }); // Process up to 5 messages per batch to prevent overwhelming const batch = this.messageQueue.splice(0, 5); batch.forEach(queuedMessage => { this.emit('queued-notification', queuedMessage); }); if (batch.length > 0) { console.log(`📨 Processed ${batch.length} queued notifications`); } } /** * Queue a message for processing (v3.2) */ queueMessage(type, data, priority = 'medium') { this.messageQueue.push({ type, data, timestamp: new Date(), priority }); // If high priority, process immediately if (priority === 'high') { this.processMessageQueue(); } } /** * Get queue status (v3.2) */ getQueueStatus() { const priorities = { high: 0, medium: 0, low: 0 }; this.messageQueue.forEach(msg => priorities[msg.priority]++); return { pending: this.messageQueue.length, priorities }; } /** * Enhanced notification with auto-queuing (v3.2) */ sendEnhancedNotification(event) { const notification = { type: 'file-notification', event, message: this.getNotificationMessage(event) }; // Determine priority based on file type let priority = 'medium'; if (event.filename.includes('message') || event.filename === 'DISCUSSION_BOARD.md') { priority = 'high'; // Messages get high priority } else if (event.filename === 'TASK_BOARD.md') { priority = 'medium'; } else { priority = 'low'; } // Queue the notification this.queueMessage('notification', notification, priority); // Also emit immediately for real-time listeners this.emit('notification', notification); } /** * Clean up resources */ destroy() { this.stopWatching(); this.debounceTimers.forEach(timer => clearTimeout(timer)); this.debounceTimers.clear(); this.cursorPositions.clear(); this.activeEditors.clear(); // Stop queue processor (v3.2) if (this.queueProcessor) { clearInterval(this.queueProcessor); } this.removeAllListeners(); } } exports.RealtimeEnhancer = RealtimeEnhancer; //# sourceMappingURL=realtime-enhancer.js.map