"use strict";
/**
 * AnomalyDetector -- Runtime behavioral monitoring for rogue agents.
 * OWASP ASI10 remediation.
 *
 * Tracks per-agent baselines and flags deviations:
 *   - Token usage per cycle (3x baseline = anomaly)
 *   - Tool calls per cycle (3x baseline = anomaly)
 *   - Cycle duration (3x baseline = anomaly)
 *   - File writes outside agent directory
 *   - Handoff volume spikes
 *
 * @connexum/ai-governance
 */
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.AnomalyDetector = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class AnomalyDetector {
    dataFile;
    agentName;
    multiplier;
    alertThresholds;
    minCycles;
    baselineWindow;
    currentCycle = null;
    cycleStartTime = 0;
    eventBus;
    webhookDelivery;
    constructor(config) {
        this.agentName = config.agentName;
        this.multiplier = config.multiplier ?? 3;
        this.alertThresholds = config.alertThresholds ?? {};
        this.minCycles = config.minCyclesForBaseline ?? 5;
        this.baselineWindow = config.baselineWindow ?? 20;
        this.eventBus = config.eventBus;
        this.webhookDelivery = config.webhookDelivery;
        if (!fs.existsSync(config.dataDir)) {
            fs.mkdirSync(config.dataDir, { recursive: true });
        }
        this.dataFile = path.join(config.dataDir, `baselines-${config.agentName}.json`);
    }
    /**
     * Start tracking a new cycle.
     */
    startCycle() {
        this.cycleStartTime = Date.now();
        this.currentCycle = {
            tokens: 0,
            toolCalls: 0,
            durationMs: 0,
            fileWrites: 0,
            handoffs: 0,
            webRequests: 0,
        };
    }
    /**
     * Record a tool call in the current cycle.
     */
    recordToolCall(toolName) {
        if (!this.currentCycle)
            return;
        this.currentCycle.toolCalls++;
        if (toolName) {
            const webTools = ['WebSearch', 'WebFetch', 'mcp__playwright'];
            if (webTools.some((wt) => toolName.startsWith(wt))) {
                this.currentCycle.webRequests++;
            }
            const writeTools = ['Edit', 'Write', 'NotebookEdit'];
            if (writeTools.includes(toolName)) {
                this.currentCycle.fileWrites++;
            }
        }
    }
    /**
     * Record token usage in the current cycle.
     */
    recordTokens(count) {
        if (!this.currentCycle)
            return;
        this.currentCycle.tokens += count;
    }
    /**
     * Record a handoff in the current cycle.
     */
    recordHandoff() {
        if (!this.currentCycle)
            return;
        this.currentCycle.handoffs++;
    }
    /**
     * End the current cycle and check for anomalies.
     */
    endCycle() {
        if (!this.currentCycle) {
            return { anomalies: [], metrics: this.emptyMetrics() };
        }
        this.currentCycle.durationMs = Date.now() - this.cycleStartTime;
        const metrics = { ...this.currentCycle };
        const timestamp = new Date().toISOString();
        // Load history
        const history = this.loadHistory();
        // Check for anomalies against baseline
        const anomalies = [];
        if (history.length >= this.minCycles) {
            const baseline = this.computeBaseline(history);
            const checks = [
                { metric: 'tokens', label: 'Token usage' },
                { metric: 'toolCalls', label: 'Tool calls' },
                { metric: 'durationMs', label: 'Cycle duration' },
                { metric: 'fileWrites', label: 'File writes' },
                { metric: 'handoffs', label: 'Handoff volume' },
                { metric: 'webRequests', label: 'Web requests' },
            ];
            for (const check of checks) {
                const value = metrics[check.metric];
                const base = baseline[check.metric];
                // T-070: per-metric threshold override, falls back to global multiplier
                const metricMultiplier = this.alertThresholds[check.metric] ?? this.multiplier;
                const threshold = base * metricMultiplier;
                if (base > 0 && value > threshold) {
                    anomalies.push({
                        type: 'DEVIATION',
                        metric: check.label,
                        value,
                        baseline: base,
                        threshold,
                        timestamp,
                        agentName: this.agentName,
                    });
                }
            }
        }
        // Save this cycle to history
        history.push({ timestamp, metrics });
        // Keep only the baseline window
        while (history.length > this.baselineWindow) {
            history.shift();
        }
        this.saveHistory(history);
        // Persist detected anomalies so getAnomalyHistory() is not always empty
        if (anomalies.length > 0) {
            this.persistAnomalies(anomalies);
            // T-069: Emit to EventBus for SIEM integration
            if (this.eventBus) {
                for (const anomaly of anomalies) {
                    this.eventBus.emit('anomaly:detected', anomaly);
                }
            }
            // T-069: Direct webhook delivery for SIEM output
            if (this.webhookDelivery) {
                for (const anomaly of anomalies) {
                    this.webhookDelivery.deliver({
                        type: 'anomaly:detected',
                        source: this.agentName,
                        data: anomaly,
                        timestamp: anomaly.timestamp,
                    }).catch(() => {
                        // Webhook delivery failure is non-fatal
                    });
                }
            }
        }
        this.currentCycle = null;
        return { anomalies, metrics };
    }
    /**
     * Get current baseline averages.
     */
    getBaseline() {
        const history = this.loadHistory();
        if (history.length < this.minCycles) {
            return this.emptyMetrics();
        }
        return this.computeBaseline(history);
    }
    /**
     * Update alert thresholds at runtime (T-070).
     */
    updateThresholds(thresholds) {
        this.alertThresholds = { ...this.alertThresholds, ...thresholds };
    }
    /**
     * Get current alert thresholds (T-070).
     */
    getThresholds() {
        return {
            global: this.multiplier,
            perMetric: { ...this.alertThresholds },
        };
    }
    /**
     * Get all recorded anomalies.
     */
    getAnomalyHistory() {
        const anomalyFile = path.join(path.dirname(this.dataFile), `anomalies-${this.agentName}.json`);
        if (!fs.existsSync(anomalyFile))
            return [];
        try {
            return JSON.parse(fs.readFileSync(anomalyFile, 'utf-8'));
        }
        catch {
            return [];
        }
    }
    computeBaseline(history) {
        const count = history.length;
        const sum = this.emptyMetrics();
        for (const record of history) {
            sum.tokens += record.metrics.tokens;
            sum.toolCalls += record.metrics.toolCalls;
            sum.durationMs += record.metrics.durationMs;
            sum.fileWrites += record.metrics.fileWrites;
            sum.handoffs += record.metrics.handoffs;
            sum.webRequests += record.metrics.webRequests;
        }
        return {
            tokens: Math.round(sum.tokens / count),
            toolCalls: Math.round(sum.toolCalls / count),
            durationMs: Math.round(sum.durationMs / count),
            fileWrites: Math.round(sum.fileWrites / count),
            handoffs: Math.round(sum.handoffs / count),
            webRequests: Math.round(sum.webRequests / count),
        };
    }
    emptyMetrics() {
        return { tokens: 0, toolCalls: 0, durationMs: 0, fileWrites: 0, handoffs: 0, webRequests: 0 };
    }
    loadHistory() {
        if (!fs.existsSync(this.dataFile))
            return [];
        try {
            return JSON.parse(fs.readFileSync(this.dataFile, 'utf-8'));
        }
        catch {
            return [];
        }
    }
    saveHistory(history) {
        try {
            fs.writeFileSync(this.dataFile, JSON.stringify(history, null, 2));
        }
        catch (err) {
            // History persistence failure means cold start on next session
            process.stderr.write(`[ANOMALY DETECTOR] Failed to save baseline history for ${this.agentName}: ${err.message}\n`);
        }
    }
    persistAnomalies(anomalies) {
        const anomalyFile = path.join(path.dirname(this.dataFile), `anomalies-${this.agentName}.json`);
        try {
            const existing = fs.existsSync(anomalyFile)
                ? JSON.parse(fs.readFileSync(anomalyFile, 'utf-8'))
                : [];
            existing.push(...anomalies);
            // Keep last 500 anomalies
            const trimmed = existing.slice(-500);
            fs.writeFileSync(anomalyFile, JSON.stringify(trimmed, null, 2));
        }
        catch (err) {
            process.stderr.write(`[ANOMALY DETECTOR] Failed to persist anomalies for ${this.agentName}: ${err.message}\n`);
        }
    }
}
exports.AnomalyDetector = AnomalyDetector;
//# sourceMappingURL=anomaly-detector.js.map