"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;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkSetupHealth = checkSetupHealth;
const ollama_1 = require("ollama");
const fs = __importStar(require("fs/promises"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const multimodalTools_1 = require("../tools/multimodalTools");
const visionModels_1 = require("../models/visionModels");
const pdfTool_1 = require("../tools/pdfTool");
const registry_1 = require("../tools/registry");
const chatClientFactory_1 = require("../core/chatClientFactory");
const fallbackChatClient_1 = require("../core/fallbackChatClient");
const synthesisStats_1 = require("../core/synthesisStats");
async function checkSetupHealth(input) {
    const audio = await checkAudioHealth(input.audioTranscribeCommand, input.audioSamplePath);
    const pdfOcr = await checkPdfOcrHealth(input.pdfOcrCommand);
    const local = await checkLocalHealth(input.projectDir ?? process.cwd());
    const backends = checkBackendAuth();
    const fallback = checkFallbackConfig(backends);
    const smtp = checkSmtpConfig();
    const rawStats = await (0, synthesisStats_1.loadSynthesisStats)(input.projectDir ?? process.cwd());
    const synthesisStats = {};
    for (const [model, record] of Object.entries(rawStats)) {
        synthesisStats[model] = { ...record, adaptiveMaxTurns: (0, synthesisStats_1.adaptiveMaxTurns)(rawStats, model, 25) };
    }
    try {
        const response = await new ollama_1.Ollama({ host: input.host }).list();
        const modelNames = response.models.map((model) => model.name);
        const matchingVisionModel = input.visionModel
            ? modelNames.some((name) => name === input.visionModel || name.startsWith(`${input.visionModel}:`))
            : false;
        const detectedVisionModel = input.visionModel ? '' : (0, visionModels_1.findInstalledVisionModel)(modelNames);
        return {
            ollama: {
                ok: true,
                message: modelNames.length > 0 ? `Connected to Ollama with ${modelNames.length} model(s).` : 'Connected to Ollama, but no models are installed.',
                modelCount: modelNames.length,
            },
            vision: input.visionModel
                ? {
                    ok: matchingVisionModel,
                    message: matchingVisionModel ? `Vision model '${input.visionModel}' is installed.` : `Vision model '${input.visionModel}' was not found in Ollama.`,
                }
                : detectedVisionModel
                    ? { ok: true, message: `Auto-detected vision model '${detectedVisionModel}'.` }
                    : { ok: false, message: 'No vision model configured and no installed vision model was auto-detected.' },
            audio,
            pdfOcr,
            local,
            backends,
            fallback,
            smtp,
            ...(Object.keys(synthesisStats).length > 0 ? { synthesisStats } : {}),
        };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return {
            ollama: { ok: false, message: `Cannot connect to Ollama: ${message}`, modelCount: 0 },
            vision: input.visionModel ? { ok: false, message: 'Vision model could not be checked because Ollama is unavailable.' } : { ok: false, message: 'No vision model configured.' },
            audio,
            pdfOcr,
            local,
            backends,
            fallback,
            smtp,
            ...(Object.keys(synthesisStats).length > 0 ? { synthesisStats } : {}),
        };
    }
}
/**
 * Inspect process.env to determine which remote backends have a
 * key configured. No network calls are made here — actual chat health is
 * exercised by the agent loop on first use, and we want `harness doctor`
 * to be fast and offline-safe.
 */
function checkBackendAuth() {
    const checks = [];
    for (const [id, preset] of [...Object.entries(chatClientFactory_1.OPENAI_COMPATIBLE_PRESETS), ['replicate', chatClientFactory_1.REPLICATE_PRESET]]) {
        const key = (0, chatClientFactory_1.readApiKey)(preset);
        if (key) {
            const sourceEnv = preset.apiKeyEnvVars.find((name) => process.env[name] && process.env[name].trim().length > 0);
            // Count credential-pool entries without ever surfacing the keys
            // themselves. A single key reports as 1, a comma-separated list
            // reports its parsed count.
            const keyCount = key.split(',').map((k) => k.trim()).filter(Boolean).length;
            const poolNote = keyCount > 1 ? ` (pool of ${keyCount} keys)` : '';
            checks.push({
                id,
                label: preset.label,
                ok: true,
                message: `API key configured (via ${sourceEnv})${poolNote}.`,
                apiKeyEnvVar: sourceEnv,
                keyCount,
                signupUrl: preset.signupUrl,
            });
        }
        else {
            const envVarList = preset.apiKeyEnvVars.join(' or ');
            checks.push({
                id,
                label: preset.label,
                ok: false,
                message: `No API key. Set ${envVarList}${preset.signupUrl ? ` (get one at ${preset.signupUrl})` : ''}.`,
                signupUrl: preset.signupUrl,
            });
        }
    }
    return checks;
}
function checkFallbackConfig(backends) {
    // Opt-in default since v0.5.0 (was opt-out). See chatClientFactory for rationale.
    const enabled = process.env.HARNESS_REMOTE_AUTO_FALLBACK === '1';
    const configuredOrder = (process.env.HARNESS_REMOTE_FALLBACK_ORDER || '').trim();
    return {
        enabled,
        cooldownMs: fallbackChatClient_1.FALLBACK_COOLDOWN_MS,
        order: configuredOrder || 'default',
        configuredCount: backends.filter((b) => b.ok).length,
    };
}
function checkSmtpConfig() {
    const host = process.env.HARNESS_SMTP_HOST?.trim();
    const user = process.env.HARNESS_SMTP_USER?.trim();
    const pass = process.env.HARNESS_SMTP_PASS?.trim();
    const from = process.env.HARNESS_SMTP_FROM?.trim();
    if (!host || !user || !pass) {
        const missing = [];
        if (!host)
            missing.push('HARNESS_SMTP_HOST');
        if (!user)
            missing.push('HARNESS_SMTP_USER');
        if (!pass)
            missing.push('HARNESS_SMTP_PASS');
        return { ok: false, message: `SMTP not configured. Missing: ${missing.join(', ')}.` };
    }
    const port = process.env.HARNESS_SMTP_PORT?.trim() || '587';
    return { ok: true, message: `SMTP configured: ${host}:${port} as ${from || user}.` };
}
async function checkLocalHealth(projectDir) {
    const packagePath = path.join(projectDir, 'package.json');
    const sessionsDir = path.join(projectDir, '.harness', 'sessions');
    const automationsDir = path.join(projectDir, '.harness', 'automations');
    const registry = (0, registry_1.createBuiltinToolRegistry)();
    return {
        node: checkNodeVersion(),
        package: await checkPackage(packagePath),
        sessions: await checkWritableDirectory(sessionsDir, 'Session storage is writable.'),
        tools: { ok: registry.listTools().length > 0, message: `${registry.listTools().length} built-in tool(s) across ${registry.listToolsets().length} toolset(s).` },
        automations: await checkWritableDirectory(automationsDir, 'Automation storage is writable.'),
        mycelium: await checkMyceliumHealth(projectDir),
    };
}
async function checkMyceliumHealth(projectDir) {
    // Lazy-load to avoid importing the mycelium module unless health checks run.
    try {
        const { loadMyceliumGraph } = await Promise.resolve().then(() => __importStar(require('../mycelium/graph')));
        const graph = await loadMyceliumGraph(projectDir);
        const stats = graph.stats();
        if (stats.nodes === 0) {
            return { ok: true, message: 'Mycelium graph is empty (run `harness mycelium seed` to populate).' };
        }
        const recent = graph.listEpisodes(20);
        const avgReward = recent.length > 0
            ? recent.reduce((sum, e) => sum + e.reward, 0) / recent.length
            : 0;
        return {
            ok: true,
            message: `${stats.nodes} nodes, ${stats.edges} edges (${stats.protectedEdges} protected, ${stats.archivedEdges} archived), avg reward ${avgReward.toFixed(2)} over last ${recent.length} episode(s).`,
        };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return { ok: false, message: `Cannot read mycelium graph: ${message}` };
    }
}
function checkNodeVersion() {
    const major = Number(process.versions.node.split('.')[0]);
    return Number.isFinite(major) && major >= 20
        ? { ok: true, message: `Node ${process.versions.node}` }
        : { ok: false, message: `Node ${process.versions.node}; Node 20+ is recommended.` };
}
async function checkPackage(packagePath) {
    try {
        const parsed = JSON.parse(await fs.readFile(packagePath, 'utf-8'));
        const hasValidation = Boolean(parsed.scripts?.test && (parsed.scripts.typecheck || parsed.scripts.lint));
        return {
            ok: hasValidation,
            message: hasValidation ? `${parsed.name ?? 'package'} has test and typecheck scripts.` : 'package.json is missing test or typecheck scripts.',
        };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return { ok: false, message: `Cannot read package.json: ${message}` };
    }
}
async function checkWritableDirectory(dirPath, okMessage) {
    const probe = path.join(dirPath, '.doctor-probe');
    try {
        await fs.mkdir(dirPath, { recursive: true });
        await fs.writeFile(probe, 'ok', 'utf-8');
        await fs.rm(probe, { force: true });
        return { ok: true, message: okMessage };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return { ok: false, message: `Directory is not writable: ${message}` };
    }
}
async function checkAudioHealth(audioCommand, audioSamplePath) {
    if (!audioCommand)
        return { ok: false, message: 'No audio transcription command configured.' };
    if (!audioSamplePath)
        return { ok: true, message: 'Audio transcription command is configured. Add a sample file path to run an end-to-end transcription check.' };
    const originalCommand = process.env.HARNESS_AUDIO_TRANSCRIBE_COMMAND;
    try {
        process.env.HARNESS_AUDIO_TRANSCRIBE_COMMAND = audioCommand;
        const result = await multimodalTools_1.AudioTranscribeTool.execute({ path: audioSamplePath });
        if (!result.success)
            return { ok: false, message: result.output || 'Audio transcription sample check failed.' };
        const preview = result.output.trim().replace(/\s+/g, ' ').slice(0, 120);
        return { ok: true, message: preview ? `Audio sample transcribed: ${preview}` : 'Audio sample transcribed successfully.' };
    }
    finally {
        if (originalCommand === undefined)
            delete process.env.HARNESS_AUDIO_TRANSCRIBE_COMMAND;
        else
            process.env.HARNESS_AUDIO_TRANSCRIBE_COMMAND = originalCommand;
    }
}
async function checkPdfOcrHealth(pdfOcrCommand) {
    if (!pdfOcrCommand)
        return { ok: false, message: 'No PDF OCR command configured.' };
    const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-pdf-doctor-'));
    const pdfPath = path.join(tmpDir, 'probe.pdf');
    const projectRel = path.relative(process.cwd(), pdfPath);
    const insideProject = !projectRel.startsWith('..') && !path.isAbsolute(projectRel);
    const originalCommand = process.env.HARNESS_PDF_OCR_COMMAND;
    try {
        await fs.writeFile(pdfPath, buildSyntheticPdf('Harness OCR probe'));
        if (!insideProject) {
            return { ok: true, message: 'PDF OCR command is configured. (Skipped end-to-end probe because the temp file is outside the project directory.)' };
        }
        process.env.HARNESS_PDF_OCR_COMMAND = pdfOcrCommand;
        const result = await pdfTool_1.PdfReadTool.execute({ path: projectRel, ocr: true });
        if (!result.success)
            return { ok: false, message: result.output || 'PDF OCR probe failed.' };
        return { ok: true, message: 'PDF OCR command executed successfully on a synthetic probe.' };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return { ok: false, message: `PDF OCR probe failed: ${message}` };
    }
    finally {
        if (originalCommand === undefined)
            delete process.env.HARNESS_PDF_OCR_COMMAND;
        else
            process.env.HARNESS_PDF_OCR_COMMAND = originalCommand;
        await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => { });
    }
}
function buildSyntheticPdf(text) {
    // Minimal single-page PDF with one text string. Sufficient as an OCR probe input.
    const safe = text.replace(/[\\()]/g, '');
    const stream = `BT /F1 24 Tf 72 720 Td (${safe}) Tj ET`;
    const objects = [
        '1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj',
        '2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj',
        '3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj',
        `4 0 obj << /Length ${stream.length} >> stream\n${stream}\nendstream endobj`,
        '5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj',
    ];
    let pdf = '%PDF-1.4\n';
    const offsets = [];
    for (const obj of objects) {
        offsets.push(Buffer.byteLength(pdf));
        pdf += obj + '\n';
    }
    const xrefOffset = Buffer.byteLength(pdf);
    pdf += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
    for (const off of offsets)
        pdf += String(off).padStart(10, '0') + ' 00000 n \n';
    pdf += `trailer << /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF`;
    return Buffer.from(pdf, 'binary');
}
//# sourceMappingURL=health.js.map