"use strict";
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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Brain = exports.CodexTimeoutError = void 0;
const child_process = __importStar(require("child_process"));
const util_1 = require("util");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const openai_1 = __importDefault(require("openai"));
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
const generative_ai_1 = require("@google/generative-ai");
const ModelManager_1 = require("./ModelManager");
const ToolsetGuard_1 = require("./toolset/ToolsetGuard");
const execFile = (0, util_1.promisify)(child_process.execFile);
const DEFAULT_MODEL_TIMEOUT_MS = Number(process.env.OPENLIFE_MODEL_TIMEOUT_MS || 60000);
const REASONING_MODE = String(process.env.OPENLIFE_REASONING_MODE || 'errors').toLowerCase(); // off | errors | always
// Conversational fast path must fail fast so the Gateway can surface a
// timeout-aware reply to the user. Deep engineering missions keep the full
// 60s budget. Both are env-overridable.
function getCodexFastTimeoutMs() {
    return Number(process.env.OPENLIFE_CODEX_FAST_TIMEOUT_MS || 30000);
}
function getCodexDeepTimeoutMs() {
    return Number(process.env.OPENLIFE_CODEX_DEEP_TIMEOUT_MS || DEFAULT_MODEL_TIMEOUT_MS);
}
class CodexTimeoutError extends Error {
    depth;
    timeoutMs;
    model;
    constructor(depth, timeoutMs, model) {
        super(`CODEX_TIMEOUT_${depth.toUpperCase()}_${timeoutMs}MS_${model || 'default'}`);
        this.name = 'CodexTimeoutError';
        this.depth = depth;
        this.timeoutMs = timeoutMs;
        this.model = model || 'default';
    }
}
exports.CodexTimeoutError = CodexTimeoutError;
class Brain {
    static codexQueue = Promise.resolve();
    openai = null;
    anthropic = null;
    geminiApi = null;
    modelManager;
    constructor() {
        this.modelManager = new ModelManager_1.ModelManager();
        const openAIKey = process.env.OPENAI_API_KEY;
        if (openAIKey && openAIKey.trim() !== '') {
            const opts = { apiKey: openAIKey };
            const openAIBaseUrl = (process.env.OPENAI_BASE_URL || '').trim();
            if (openAIBaseUrl)
                opts.baseURL = openAIBaseUrl;
            this.openai = new openai_1.default(opts);
        }
        const anthropicKey = process.env.ANTHROPIC_API_KEY;
        if (anthropicKey && anthropicKey.trim() !== '')
            this.anthropic = new sdk_1.default({ apiKey: anthropicKey });
        const geminiKey = process.env.GEMINI_API_KEY;
        if (geminiKey && geminiKey.trim() !== '')
            this.geminiApi = new generative_ai_1.GoogleGenerativeAI(geminiKey);
    }
    /**
     * Story 8.1 — public probe for v1.4 creators.
     *
     * Returns true iff at least one API-key-backed provider is configured
     * (OpenAI, Anthropic, Gemini, OpenRouter, or Ollama URL). Used by
     * SquadCreator.design() / SkillCreator.design() to decide between
     * LLM-driven proposal and heuristic fallback without forcing the
     * caller to know about the provider table.
     */
    isAnyProviderAvailable() {
        if (this.openai || this.anthropic || this.geminiApi)
            return true;
        if ((process.env.OPENROUTER_API_KEY || '').trim() !== '')
            return true;
        if ((process.env.OLLAMA_URL || '').trim() !== '')
            return true;
        return false;
    }
    async think(systemPrompt, userMessage, opts) {
        const config = this.modelManager.getModelConfig();
        const modelsToTry = [config.primary, ...config.fallbacks].filter((model, index, arr) => arr.findIndex(m => m.raw === model.raw) === index);
        return this.runModelChain(systemPrompt, userMessage, modelsToTry, 'deep', opts);
    }
    /** Fast conversational path. Keeps the configured model chain order intact. */
    async thinkFast(systemPrompt, userMessage, opts) {
        const config = this.modelManager.getModelConfig();
        const models = [config.primary, ...config.fallbacks].filter((model, index, arr) => arr.findIndex(m => m.raw === model.raw) === index);
        return this.runModelChain(systemPrompt, userMessage, models, 'fast', opts);
    }
    async runModelChain(systemPrompt, userMessage, modelsToTry, depth = 'deep', opts) {
        const emitReasoning = (chunk) => {
            try {
                opts?.onReasoning?.(chunk);
            }
            catch { /* never let a callback break the chain */ }
        };
        emitReasoning(`🧭 chain: ${modelsToTry.map(m => m.raw).join(' → ')}`);
        const failures = [];
        for (let i = 0; i < modelsToTry.length; i++) {
            const modelIdent = modelsToTry[i];
            try {
                console.log(`[BRAIN] Tentando modelo ${i === 0 ? 'PRIMÁRIO' : 'FALLBACK ' + i}: ${modelIdent.raw}...`);
                emitReasoning(`🔎 trying ${i === 0 ? 'primary' : `fallback ${i}`}: ${modelIdent.raw}`);
                let out = '';
                switch (modelIdent.provider) {
                    case 'openai-api':
                        out = await this.thinkWithOpenAIAPI(systemPrompt, userMessage, modelIdent.name);
                        break;
                    case 'openai-cli':
                        out = await this.thinkWithOpenAICLI(systemPrompt, userMessage, modelIdent.name, depth);
                        break;
                    case 'anthropic':
                        out = await this.thinkWithAnthropic(systemPrompt, userMessage, modelIdent.name);
                        break;
                    case 'gemini-api':
                        out = await this.thinkWithGeminiAPI(systemPrompt, userMessage, modelIdent.name);
                        break;
                    case 'gemini-cli':
                        out = await this.thinkWithGeminiCLI(systemPrompt, userMessage, modelIdent.name);
                        break;
                    case 'ollama':
                        out = await this.thinkWithOllama(systemPrompt, userMessage, modelIdent.name);
                        break;
                    case 'openrouter':
                        out = await this.thinkWithOpenRouter(systemPrompt, userMessage, modelIdent.name);
                        break;
                    default: throw new Error(`Provedor desconhecido: ${modelIdent.provider}`);
                }
                if (REASONING_MODE === 'always') {
                    const trace = [
                        `🔎 model_try: "${modelIdent.raw}"`,
                        `🧭 fallback_chain: "${modelsToTry.length} candidatos"`,
                        `✅ model_selected: "${modelIdent.raw}"`
                    ].join('\n');
                    return `${trace}\n\n${out}`;
                }
                return out;
            }
            catch (error) {
                const reason = error instanceof Error ? error.message : String(error);
                failures.push({ model: modelIdent.raw, reason });
                console.error(`[BRAIN ERROR - ${modelIdent.raw}]`, reason);
                emitReasoning(`✗ ${modelIdent.raw} failed: ${reason.slice(0, 120)}`);
                if (i !== modelsToTry.length - 1) {
                    console.log(`[BRAIN] Rotacionando para o próximo fallback da cadeia...`);
                    emitReasoning('↻ rotating to next fallback');
                }
            }
        }
        const concise = failures.slice(-3).map(f => `- ${f.model}: ${f.reason}`).join('\n');
        const strictGpt55Only = modelsToTry.length === 1 && modelsToTry[0]?.raw === 'openai-cli/gpt-5.5';
        if (strictGpt55Only) {
            return [
                'Executor GPT-5.5 indisponível no momento.',
                '',
                'Falhas recentes:',
                concise,
                '',
                'Ação recomendada:',
                '1) Reautentique o Codex OAuth no mesmo runtime onde o OpenLife está rodando.',
                '2) Valide com: codex login status',
                '3) Valide execução real com: codex exec --model gpt-5.5 "responda apenas OK"'
            ].join('\n');
        }
        const guidance = [
            '1) Valide as credenciais/binários dos provedores configurados.',
            '2) Ajuste models.json apenas se a política do produto permitir fallbacks.',
            '3) Rode: openlife doctor e openlife models status.'
        ].join('\n');
        const summary = REASONING_MODE !== 'off'
            ? `\n\nResumo operacional:\n- Objetivo: responder com robustez por fallback.\n- Estratégia: rotação sequencial de provedores.\n- Resultado: todos os provedores tentados falharam.`
            : '';
        return `CRITICAL ERROR: cadeia de modelos indisponível no momento.\n\nFalhas recentes:\n${concise}\n\nAção recomendada:\n${guidance}${summary}`;
    }
    formatProviderError(provider, model, error, opts) {
        const e = (error && typeof error === 'object' ? error : {});
        const response = (e.response && typeof e.response === 'object' ? e.response : {});
        const responseData = (response.data && typeof response.data === 'object' ? response.data : {});
        const responseDataError = (responseData.error && typeof responseData.error === 'object' ? responseData.error : {});
        const errorObj = (e.error && typeof e.error === 'object' ? e.error : {});
        const parts = [];
        const status = e.status ?? response.status ?? e.code;
        if (status !== undefined && status !== null)
            parts.push(`HTTP ${String(status)}`);
        const apiMessage = (typeof errorObj.message === 'string' ? errorObj.message : undefined) ||
            (typeof responseDataError.message === 'string' ? responseDataError.message : undefined) ||
            (typeof e.message === 'string' ? e.message : undefined) ||
            String(error);
        parts.push(String(apiMessage || 'erro_desconhecido').slice(0, 240));
        const stderr = typeof e.stderr === 'string' ? e.stderr.trim().slice(-200) : '';
        if (stderr)
            parts.push(`stderr: ${stderr}`);
        if (opts?.keyEnvVar && opts?.expectedKeyPrefix) {
            const rawKey = process.env[opts.keyEnvVar] || '';
            if (rawKey && !rawKey.startsWith(opts.expectedKeyPrefix)) {
                parts.push(`key prefix: ${rawKey.slice(0, 8)} (esperado começar com "${opts.expectedKeyPrefix}")`);
            }
        }
        const wrapped = new Error(`[${provider}/${model}] ${parts.join(' | ')}`);
        wrapped.cause = error;
        wrapped.providerStatus = status;
        return wrapped;
    }
    async thinkWithOpenAIAPI(systemPrompt, userMessage, model) {
        if (!this.openai)
            throw new Error('OPENAI_API_KEY não configurada no .env.');
        try {
            const response = await this.openai.chat.completions.create({
                model,
                messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userMessage }],
                temperature: 0.7,
            });
            return response.choices[0].message.content || 'Sem resposta.';
        }
        catch (err) {
            throw this.formatProviderError('openai-api', model, err, { keyEnvVar: 'OPENAI_API_KEY', expectedKeyPrefix: 'sk-' });
        }
    }
    async thinkWithOpenAICLI(systemPrompt, userMessage, model, depth = 'deep') {
        (0, ToolsetGuard_1.assertToolsetAllowed)('delegation', 'Brain.thinkWithOpenAICLI');
        const outputFile = path.join(process.cwd(), '.openlife', `codex-last-message-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`);
        fs.mkdirSync(path.dirname(outputFile), { recursive: true });
        const args = ['exec', '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check', '--color', 'never', '--output-last-message', outputFile];
        if (String(process.env.OPENLIFE_CODEX_EPHEMERAL || 'true').toLowerCase() !== 'false')
            args.push('--ephemeral');
        if (model && model !== 'default')
            args.push('--model', model);
        const prompt = depth === 'fast'
            ? [
                'Você é Lara, agente conversacional do OpenLife.',
                'Responda diretamente em português natural e conciso.',
                'Não use ferramentas, não leia arquivos e não execute comandos para responder esta mensagem.',
                'Se não souber algo específico, seja honesta e diga o que falta.',
                '',
                `Mensagem: ${userMessage}`
            ].join('\n')
            : `${systemPrompt}\n\nMensagem: ${userMessage}`;
        args.push(prompt);
        const timeoutMs = depth === 'fast' ? getCodexFastTimeoutMs() : getCodexDeepTimeoutMs();
        // Deep missions remain serialized through the static queue to avoid
        // contending Codex CLI invocations. Fast conversational calls bypass
        // the queue so a long-running deep mission can not block a greeting.
        let release = () => { };
        if (depth === 'deep') {
            const previous = Brain.codexQueue;
            Brain.codexQueue = new Promise((resolve) => { release = resolve; });
            await previous;
        }
        try {
            const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));
            const command = process.platform === 'win32' ? 'codex' : 'timeout';
            const commandArgs = process.platform === 'win32'
                ? args
                : ['-k', '2s', `${timeoutSeconds}s`, 'codex', ...args];
            // Codex CLI reads stdin when invoked without a TTY. The promisified
            // execFile doesn't expose stdio, so it keeps the child's stdin
            // attached to our (open) pipe — codex then waits forever for input
            // and our outer timeout fires. Use spawn with `stdio: ['ignore', ...]`
            // to close stdin explicitly. Fixes ~30s phantom timeouts when the
            // daemon (non-TTY) calls codex.
            const stdout = await new Promise((resolve, reject) => {
                const child = child_process.spawn(command, commandArgs, {
                    stdio: ['ignore', 'pipe', 'pipe'],
                });
                let stdoutBuf = '';
                let stderrBuf = '';
                let killed = false;
                const watchdog = setTimeout(() => {
                    killed = true;
                    try {
                        child.kill('SIGKILL');
                    }
                    catch { /* ignore */ }
                }, timeoutMs + 3000);
                child.stdout.on('data', (d) => { stdoutBuf += String(d); });
                child.stderr.on('data', (d) => { stderrBuf += String(d); });
                child.on('error', (err) => { clearTimeout(watchdog); reject(err); });
                child.on('close', (code, signal) => {
                    clearTimeout(watchdog);
                    if (killed || code === 124 || (signal && killed)) {
                        const e = new Error('ETIMEDOUT');
                        e.code = 'ETIMEDOUT';
                        reject(e);
                        return;
                    }
                    if (code !== 0) {
                        const e = new Error(`codex exited with code ${code}: ${stderrBuf.slice(-300)}`);
                        e.code = String(code);
                        reject(e);
                        return;
                    }
                    resolve(stdoutBuf);
                });
            });
            const lastMessage = fs.existsSync(outputFile) ? fs.readFileSync(outputFile, 'utf-8').trim() : '';
            try {
                fs.unlinkSync(outputFile);
            }
            catch { /* ignore cleanup */ }
            const finalText = lastMessage || stdout.trim();
            if (!finalText)
                throw new Error(`EMPTY_CODEX_RESPONSE_${model || 'default'}`);
            return finalText;
        }
        catch (err) {
            try {
                if (fs.existsSync(outputFile))
                    fs.unlinkSync(outputFile);
            }
            catch { /* ignore cleanup */ }
            // execFile signals a timeout via either ETIMEDOUT or by killing the
            // child (signal !== null). Surface a typed error so callers can
            // present a depth-aware message.
            const e = err;
            const errCode = e.code;
            const isTimeout = errCode === 'ETIMEDOUT' || errCode === 124 || errCode === '124' || (e?.killed === true && !!e?.signal);
            if (isTimeout) {
                throw new CodexTimeoutError(depth, timeoutMs, model);
            }
            throw this.formatProviderError('openai-cli', model, err);
        }
        finally {
            release();
        }
    }
    async thinkWithAnthropic(systemPrompt, userMessage, model) {
        if (!this.anthropic)
            throw new Error('ANTHROPIC_API_KEY não configurada no .env.');
        try {
            const response = await this.anthropic.messages.create({ model, max_tokens: 4096, system: systemPrompt, messages: [{ role: 'user', content: userMessage }] });
            const first = response.content[0];
            if (first && first.type === 'text')
                return first.text || 'Sem resposta.';
            return 'Sem resposta.';
        }
        catch (err) {
            throw this.formatProviderError('anthropic', model, err, { keyEnvVar: 'ANTHROPIC_API_KEY', expectedKeyPrefix: 'sk-ant-' });
        }
    }
    async thinkWithGeminiAPI(systemPrompt, userMessage, model) {
        if (!this.geminiApi)
            throw new Error('GEMINI_API_KEY não configurada no .env.');
        try {
            const generativeModel = this.geminiApi.getGenerativeModel({ model, systemInstruction: systemPrompt });
            const result = await generativeModel.generateContent(userMessage);
            const response = await result.response;
            return response.text();
        }
        catch (err) {
            throw this.formatProviderError('gemini-api', model, err);
        }
    }
    async thinkWithOpenRouter(systemPrompt, userMessage, model) {
        const apiKey = process.env.OPENROUTER_API_KEY;
        if (!apiKey)
            throw new Error('OPENROUTER_API_KEY não configurada no .env.');
        const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
            body: JSON.stringify({ model, messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userMessage }] })
        });
        if (!response.ok) {
            const bodyText = await response.text().catch(() => '');
            throw this.formatProviderError('openrouter', model, { status: response.status, message: response.statusText, error: { message: bodyText.slice(0, 200) } });
        }
        try {
            const data = (await response.json());
            return data.choices?.[0]?.message?.content || 'Sem resposta.';
        }
        catch (err) {
            throw this.formatProviderError('openrouter', model, err);
        }
    }
    async thinkWithOllama(systemPrompt, userMessage, model) {
        const url = process.env.OLLAMA_URL || 'http://localhost:11434/api/chat';
        let response;
        try {
            response = await fetch(url, {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ model, messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userMessage }], stream: false })
            });
        }
        catch (err) {
            throw this.formatProviderError('ollama', model, err);
        }
        if (!response.ok) {
            const bodyText = await response.text().catch(() => '');
            throw this.formatProviderError('ollama', model, { status: response.status, message: response.statusText, error: { message: bodyText.slice(0, 200) } });
        }
        try {
            const data = (await response.json());
            return data.message?.content || 'Sem resposta do Ollama.';
        }
        catch (err) {
            throw this.formatProviderError('ollama', model, err);
        }
    }
    async thinkWithGeminiCLI(systemPrompt, userMessage, model) {
        (0, ToolsetGuard_1.assertToolsetAllowed)('delegation', 'Brain.thinkWithGeminiCLI');
        const args = ['-o', 'json', '-m', model && model !== 'default' ? model : 'gemini-3.1-pro-preview', '-p', `${systemPrompt}\n\nMensagem: ${userMessage}`, '-y'];
        try {
            const { stdout } = await execFile('gemini', args, { maxBuffer: 1024 * 1024 * 10, timeout: DEFAULT_MODEL_TIMEOUT_MS });
            const idx = stdout.indexOf('{');
            if (idx < 0)
                throw new Error('Resposta Gemini inválida.');
            const parsed = JSON.parse(stdout.substring(idx));
            return parsed.response || parsed.text || 'Sem resposta no JSON.';
        }
        catch (err) {
            throw this.formatProviderError('gemini-cli', model, err);
        }
    }
}
exports.Brain = Brain;
