"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentMessageBus = void 0;
exports.createAgentMessageBus = createAgentMessageBus;
/**
 * SmartPerfetto Agent Message Bus
 *
 * Phase 4.1: Inter-agent communication system
 *
 * The message bus enables:
 * 1. Task dispatch from Master to domain agents
 * 2. Response collection from agents
 * 3. Inter-agent queries (e.g., FrameAgent asks CPUAgent for scheduling data)
 * 4. Broadcast messages (hypothesis updates, evidence found, etc.)
 *
 * Message Flow:
 * Master → dispatch task → Agent → execute → response → Master
 *                              ↓
 *                         query other agent
 *                              ↓
 *                         receive answer
 */
const events_1 = require("events");
const agentProtocol_1 = require("../types/agentProtocol");
const DEFAULT_CONFIG = {
    maxPendingMessages: 100,
    messageTimeoutMs: 180000,
    enableLogging: true,
    maxConcurrentTasks: 3,
};
class TaskTimeoutError extends Error {
    constructor(taskId, timeoutMs) {
        super(`Task ${taskId} timed out after ${timeoutMs}ms`);
        this.name = 'TaskTimeoutError';
        this.timeoutMs = timeoutMs;
    }
}
/**
 * Simple semaphore for limiting concurrent tasks
 */
class Semaphore {
    constructor(permits) {
        this.waitQueue = [];
        this.permits = permits;
    }
    async acquire() {
        if (this.permits > 0) {
            this.permits--;
            return Promise.resolve();
        }
        return new Promise((resolve) => {
            this.waitQueue.push(resolve);
        });
    }
    release() {
        this.permits++;
        if (this.waitQueue.length > 0 && this.permits > 0) {
            this.permits--;
            const next = this.waitQueue.shift();
            if (next)
                next();
        }
    }
    reset(permits) {
        this.permits = permits;
        // Clear the wait queue by rejecting or resolving
        while (this.waitQueue.length > 0) {
            const next = this.waitQueue.shift();
            if (next)
                next();
        }
    }
}
// =============================================================================
// Agent Message Bus Implementation
// =============================================================================
/**
 * Agent Message Bus
 *
 * Central communication hub for all agents in a session.
 * Handles task dispatch, response collection, and inter-agent queries.
 */
class AgentMessageBus extends events_1.EventEmitter {
    constructor(config) {
        super();
        this.agents = new Map();
        this.pendingMessages = new Map();
        this.messageQueue = [];
        this.sharedContext = null;
        this.isProcessing = false;
        this.perAgentSemaphore = new Map();
        this.config = { ...DEFAULT_CONFIG, ...config };
        this.taskSemaphore = new Semaphore(this.config.maxConcurrentTasks);
    }
    // ==========================================================================
    // Agent Registration
    // ==========================================================================
    /**
     * Register an agent with the message bus
     */
    registerAgent(agent) {
        this.agents.set(agent.config.id, agent);
        this.perAgentSemaphore.set(agent.config.id, new Semaphore(1));
        this.log(`Registered agent: ${agent.config.id}`);
        // Listen to agent events
        agent.on('task_completed', (data) => {
            this.emit('agent_response', data);
        });
        agent.on('task_failed', (data) => {
            this.emit('agent_error', data);
        });
    }
    /**
     * Unregister an agent
     */
    unregisterAgent(agentId) {
        const agent = this.agents.get(agentId);
        if (agent) {
            agent.removeAllListeners();
            this.agents.delete(agentId);
            this.perAgentSemaphore.delete(agentId);
            this.log(`Unregistered agent: ${agentId}`);
        }
    }
    /**
     * Get registered agent
     */
    getAgent(agentId) {
        return this.agents.get(agentId);
    }
    /**
     * Get all registered agents
     */
    getAllAgents() {
        return Array.from(this.agents.values());
    }
    // ==========================================================================
    // Shared Context
    // ==========================================================================
    /**
     * Set shared context for all agents
     */
    setSharedContext(context) {
        this.sharedContext = context;
        // Update context in all agents
        for (const agent of this.agents.values()) {
            agent.setSharedContext(context);
        }
    }
    /**
     * Get shared context
     */
    getSharedContext() {
        return this.sharedContext;
    }
    /**
     * Update hypotheses in shared context
     */
    updateHypothesis(hypothesis) {
        if (this.sharedContext) {
            this.sharedContext.hypotheses.set(hypothesis.id, hypothesis);
            this.broadcast({
                type: 'hypothesis_proposed',
                payload: hypothesis,
                from: 'message_bus',
                timestamp: Date.now(),
            });
        }
    }
    /**
     * Add confirmed finding to shared context
     */
    addConfirmedFinding(finding) {
        if (this.sharedContext) {
            this.sharedContext.confirmedFindings.push(finding);
            this.broadcast({
                type: 'evidence_found',
                payload: finding,
                from: 'message_bus',
                timestamp: Date.now(),
            });
        }
    }
    // ==========================================================================
    // Message Sending
    // ==========================================================================
    /**
     * Send a task to an agent
     */
    async dispatchTask(task) {
        const agent = this.agents.get(task.targetAgentId);
        if (!agent) {
            throw new Error(`Agent not found: ${task.targetAgentId}`);
        }
        // Ensure shared context exists
        if (!this.sharedContext) {
            throw new Error('Shared context not initialized. Call createSharedContext() first.');
        }
        if (this.pendingMessages.size >= this.config.maxPendingMessages) {
            const reason = `Task queue limit exceeded (${this.pendingMessages.size}/${this.config.maxPendingMessages})`;
            this.log(`[Backpressure] Rejected task ${task.id} for ${task.targetAgentId}: ${reason}`);
            this.emit('task_rejected', {
                taskId: task.id,
                agentId: task.targetAgentId,
                reason,
            });
            return this.buildRejectedResponse(task, reason);
        }
        this.pendingMessages.set(task.id, {
            task,
            enqueuedAt: Date.now(),
        });
        this.messageQueue.push(task.id);
        this.isProcessing = true;
        let globalPermit = false;
        let agentPermit = false;
        const agentSemaphore = this.perAgentSemaphore.get(task.targetAgentId) || new Semaphore(1);
        this.perAgentSemaphore.set(task.targetAgentId, agentSemaphore);
        try {
            // Wait for semaphore (proper async concurrency limiting)
            await this.taskSemaphore.acquire();
            globalPermit = true;
            await agentSemaphore.acquire();
            agentPermit = true;
            this.log(`Dispatching task ${task.id} to ${task.targetAgentId}`);
            const dispatchStartedAt = Date.now();
            this.emit('task_dispatched', { taskId: task.id, agentId: task.targetAgentId });
            // Ensure shared context is set on agent
            agent.setSharedContext(this.sharedContext);
            let response;
            try {
                response = await this.executeTaskWithTimeout(agent, task);
            }
            catch (error) {
                response = this.buildFailedResponse(task, error, dispatchStartedAt);
                this.emit('task_failed', {
                    taskId: task.id,
                    agentId: task.targetAgentId,
                    error: response.toolResults?.[0]?.error,
                    timeout: error instanceof TaskTimeoutError,
                });
            }
            // Process response
            this.processAgentResponse(response);
            this.emit('task_completed', { taskId: task.id, agentId: task.targetAgentId, response });
            return response;
        }
        finally {
            if (agentPermit) {
                agentSemaphore.release();
            }
            if (globalPermit) {
                this.taskSemaphore.release();
            }
            this.pendingMessages.delete(task.id);
            this.messageQueue = this.messageQueue.filter((id) => id !== task.id);
            this.isProcessing = this.pendingMessages.size > 0;
        }
    }
    /**
     * Dispatch multiple tasks in parallel
     */
    async dispatchTasksParallel(tasks) {
        const promises = tasks.map(task => this.dispatchTask(task));
        return Promise.all(promises);
    }
    /**
     * Send a query from one agent to another
     */
    async query(from, to, question) {
        const toAgent = this.agents.get(to);
        if (!toAgent) {
            throw new Error(`Target agent not found: ${to}`);
        }
        const message = {
            id: (0, agentProtocol_1.createMessageId)(),
            type: 'question',
            from,
            to,
            payload: question,
            correlationId: (0, agentProtocol_1.createMessageId)(),
            priority: question.priority,
            timestamp: Date.now(),
        };
        this.log(`Query from ${from} to ${to}: ${question.question}`);
        // For now, we create a simple task for the query
        const queryTask = {
            id: (0, agentProtocol_1.createTaskId)(),
            description: question.question,
            targetAgentId: to,
            priority: question.priority,
            context: {
                query: question.question,
                additionalData: question.context,
            },
            dependencies: [],
            timeout: this.config.messageTimeoutMs,
            createdAt: Date.now(),
        };
        const response = await this.dispatchTask(queryTask);
        return response;
    }
    /**
     * Broadcast a message to all agents
     */
    broadcast(message) {
        this.log(`Broadcasting: ${message.type}`);
        const agentMessage = {
            id: (0, agentProtocol_1.createMessageId)(),
            type: 'broadcast',
            from: message.from,
            to: 'all',
            payload: message,
            priority: 5,
            timestamp: message.timestamp,
        };
        // Emit to all listeners
        this.emit('broadcast', message);
        this.emit(`broadcast:${message.type}`, message.payload);
    }
    // ==========================================================================
    // Response Processing
    // ==========================================================================
    /**
     * Process agent response and update shared context
     */
    processAgentResponse(response) {
        if (!this.sharedContext)
            return;
        // Add findings to shared context
        for (const finding of response.findings) {
            // Check if finding is critical or high-confidence
            // Use optional chaining and nullish coalescing for safety
            const confidence = finding.confidence ?? 0;
            if (finding.severity === 'critical' || confidence >= 0.8) {
                this.addConfirmedFinding(finding);
            }
        }
        // Process hypothesis updates
        if (response.hypothesisUpdates) {
            for (const update of response.hypothesisUpdates) {
                const hypothesis = this.sharedContext.hypotheses.get(update.hypothesisId);
                if (hypothesis) {
                    // Update hypothesis based on action
                    switch (update.action) {
                        case 'support':
                            hypothesis.confidence = Math.min(1, hypothesis.confidence + 0.1);
                            if (update.evidence) {
                                hypothesis.supportingEvidence.push(update.evidence);
                            }
                            break;
                        case 'contradict':
                            hypothesis.confidence = Math.max(0, hypothesis.confidence - 0.2);
                            if (update.evidence) {
                                hypothesis.contradictingEvidence.push(update.evidence);
                            }
                            break;
                        case 'confirm':
                            hypothesis.status = 'confirmed';
                            hypothesis.confidence = update.newConfidence || 0.9;
                            break;
                        case 'reject':
                            hypothesis.status = 'rejected';
                            hypothesis.confidence = 0;
                            break;
                        case 'update_confidence':
                            if (update.newConfidence !== undefined) {
                                hypothesis.confidence = update.newConfidence;
                            }
                            break;
                    }
                    hypothesis.updatedAt = Date.now();
                    this.sharedContext.hypotheses.set(hypothesis.id, hypothesis);
                }
            }
        }
        // Add to investigation path
        this.sharedContext.investigationPath.push({
            stepNumber: this.sharedContext.investigationPath.length + 1,
            agentId: response.agentId,
            action: 'task_completed',
            timestamp: Date.now(),
            result: response.success ? 'success' : 'failed',
            summary: `Found ${response.findings.length} issues`,
        });
        // Process questions for other agents
        if (response.questionsForAgents && response.questionsForAgents.length > 0) {
            for (const question of response.questionsForAgents) {
                // Queue query for later processing
                this.emit('agent_question', question);
            }
        }
    }
    // ==========================================================================
    // Utilities
    // ==========================================================================
    /**
     * Create a shared context
     */
    createSharedContext(sessionId, traceId) {
        const context = {
            sessionId,
            traceId,
            hypotheses: new Map(),
            confirmedFindings: [],
            investigationPath: [],
        };
        this.setSharedContext(context);
        return context;
    }
    /**
     * Get investigation summary
     */
    getInvestigationSummary() {
        if (!this.sharedContext) {
            return 'No investigation in progress';
        }
        const steps = this.sharedContext.investigationPath
            .map(s => `${s.stepNumber}. [${s.agentId}] ${s.action} - ${s.result}`)
            .join('\n');
        const hypotheses = Array.from(this.sharedContext.hypotheses.values())
            .map(h => `- ${h.description} (${h.status}, confidence: ${h.confidence.toFixed(2)})`)
            .join('\n');
        const findings = this.sharedContext.confirmedFindings
            .map(f => `- [${f.severity}] ${f.title}`)
            .join('\n');
        return `## Investigation Path\n${steps}\n\n## Hypotheses\n${hypotheses}\n\n## Confirmed Findings\n${findings}`;
    }
    /**
     * Reset message bus state
     */
    reset() {
        // Clear pending messages
        this.pendingMessages.clear();
        // Clear queue
        this.messageQueue = [];
        // Clear shared context
        this.sharedContext = null;
        // Reset semaphore
        this.taskSemaphore.reset(this.config.maxConcurrentTasks);
        for (const sem of this.perAgentSemaphore.values()) {
            sem.reset(1);
        }
        this.isProcessing = false;
        this.log('Message bus reset');
    }
    /**
     * Log message (if enabled)
     */
    log(message) {
        if (this.config.enableLogging) {
            console.log(`[AgentMessageBus] ${message}`);
        }
    }
    resolveTaskTimeout(task) {
        const timeout = Number(task.timeout);
        if (Number.isFinite(timeout) && timeout > 0) {
            return Math.floor(timeout);
        }
        return this.config.messageTimeoutMs;
    }
    async executeTaskWithTimeout(agent, task) {
        const timeoutMs = this.resolveTaskTimeout(task);
        if (timeoutMs <= 0) {
            return agent.executeTask(task, this.sharedContext);
        }
        let timer;
        const timeoutPromise = new Promise((_resolve, reject) => {
            timer = setTimeout(() => reject(new TaskTimeoutError(task.id, timeoutMs)), timeoutMs);
        });
        try {
            return await Promise.race([
                agent.executeTask(task, this.sharedContext),
                timeoutPromise,
            ]);
        }
        finally {
            if (timer) {
                clearTimeout(timer);
            }
        }
    }
    buildFailedResponse(task, error, startedAt) {
        const errorMessage = error instanceof TaskTimeoutError
            ? error.message
            : (error?.message || 'Task execution failed');
        const elapsedMs = Date.now() - startedAt;
        return {
            agentId: task.targetAgentId,
            taskId: task.id,
            success: false,
            findings: [],
            confidence: 0,
            executionTimeMs: elapsedMs,
            suggestions: [errorMessage],
            toolResults: [
                {
                    success: false,
                    error: errorMessage,
                    executionTimeMs: elapsedMs,
                    metadata: {
                        timeoutMs: error instanceof TaskTimeoutError ? error.timeoutMs : undefined,
                    },
                },
            ],
        };
    }
    buildRejectedResponse(task, reason) {
        return {
            agentId: task.targetAgentId,
            taskId: task.id,
            success: false,
            findings: [],
            confidence: 0,
            executionTimeMs: 0,
            suggestions: [reason],
            toolResults: [
                {
                    success: false,
                    error: reason,
                    executionTimeMs: 0,
                    metadata: {
                        rejected: true,
                        maxPendingMessages: this.config.maxPendingMessages,
                    },
                },
            ],
        };
    }
}
exports.AgentMessageBus = AgentMessageBus;
/**
 * Create an agent message bus
 */
function createAgentMessageBus(config) {
    return new AgentMessageBus(config);
}
exports.default = AgentMessageBus;
//# sourceMappingURL=agentMessageBus.js.map