import fs from 'fs'; import path from 'path'; import { EventEmitter } from 'events'; // DEFINITION: Path to the Single Source of Truth // Assuming running from apps/backend/src or similar structure in Docker const HANDOVER_LOG_PATH = path.join(process.cwd(), '..', '..', 'docs', 'HANDOVER_LOG.md'); export class HandoverWatchdog extends EventEmitter { private static instance: HandoverWatchdog; private isWatching: boolean = false; private lastProcessedTime: number = 0; private constructor() { super(); } public static getInstance(): HandoverWatchdog { if (!HandoverWatchdog.instance) { HandoverWatchdog.instance = new HandoverWatchdog(); } return HandoverWatchdog.instance; } /** * Starts the surveillance of the Blackboard Protocol. */ public startWatch(): void { if (this.isWatching) { console.log('👁️ [WATCHDOG] Already active.'); return; } console.log(`👁️ [WATCHDOG] Initializing Blackboard Protocol surveillance...`); console.log(`📍 [WATCHDOG] Target: ${HANDOVER_LOG_PATH}`); // Verify existence of the Truth Document if (!fs.existsSync(HANDOVER_LOG_PATH)) { console.warn(`⚠️ [WATCHDOG] Critical Failure: Handover log not found at ${HANDOVER_LOG_PATH}`); // Retry logic could be added here, but for now we warn return; } try { // We use watchFile for better Docker volume compatibility than fs.watch fs.watchFile(HANDOVER_LOG_PATH, { interval: 2000 }, (curr, prev) => { // Only trigger if modified time changed and it's newer than last process if (curr.mtimeMs !== prev.mtimeMs) { console.log('⚡ [WATCHDOG] Detect: Blackboard updated. Analyzing...'); this.analyzeBlackboard(); } }); this.isWatching = true; console.log('🟢 [WATCHDOG] Active and Waiting for Signals.'); } catch (error) { console.error('❌ [WATCHDOG] Failed to initialize:', error); } } /** * Reads the Blackboard and looks for "READY FOR [AGENT]" signals. */ private analyzeBlackboard(): void { try { const content = fs.readFileSync(HANDOVER_LOG_PATH, 'utf-8'); // REGEX LOGIC: // We look for: **Status:** [BOLD/EMOJI?] READY FOR [AGENT_NAME] // Captures the agent name. Case insensitive. const statusRegex = /\*\*Status:\*\*\s*(?:✅|🔴|🟢|\*\*)?\s*READY FOR\s*\[?(.*?)\]?/i; // We only scan the last 2000 characters to be efficient const recentContent = content.slice(-2000); const match = statusRegex.exec(recentContent); if (match && match[1]) { const targetAgent = match[1].trim().replace(/\*|\]|\[/g, ''); // Cleanup cleanup console.log(`🚀 [WATCHDOG] SIGNAL VERIFIED: Handover to Agent [${targetAgent}]`); // Emit the signal for the Orchestrator to pick up this.emit('handover_signal', { target: targetAgent, timestamp: new Date(), source: 'Blackboard' }); } else { // Debug log to confirm scan happened (can be removed later for silence) // console.log('🔍 [WATCHDOG] Scanned. No active handover signal found.'); } } catch (error) { console.error('❌ [WATCHDOG] Read error:', error); } } } export const handoverWatchdog = HandoverWatchdog.getInstance();