#!/usr/bin/env node
"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.parseArgs = parseArgs;
exports.loadHeadlessRuntimeSettings = loadHeadlessRuntimeSettings;
exports.main = main;
exports.formatSetupHealth = formatSetupHealth;
exports.buildSystemPrompt = buildSystemPrompt;
exports.buildConsoleToolOnlyResponse = buildConsoleToolOnlyResponse;
const readline = __importStar(require("readline"));
const path = __importStar(require("path"));
const promises_1 = require("fs/promises");
const chatClientFactory_1 = require("../core/chatClientFactory");
const queryLoop_1 = require("../core/queryLoop");
const tools_1 = require("../tools");
const engine_1 = require("../permissions/engine");
const sessionStorage_1 = require("../persistence/sessionStorage");
const assembly_1 = require("../context/assembly");
const health_1 = require("../setup/health");
const doctorFix_1 = require("../setup/doctorFix");
const nodemailer = __importStar(require("nodemailer"));
const eventStore_1 = require("../persistence/eventStore");
const promiseLedger_1 = require("../services/promiseLedger");
const outputValidation_1 = require("../core/outputValidation");
const commands_1 = require("./commands");
const cli_1 = require("../mycelium/cli");
const webSearchTool_1 = require("../tools/webSearchTool");
const pathResolution_1 = require("../tools/pathResolution");
const CLI_STORED_ENV_KEYS = new Set([
    'OPENAI_API_KEY',
    'CEREBRAS_API_KEY',
    'CLOUDFLARE_ACCOUNT_ID',
    'CLOUDFLARE_API_TOKEN',
    'DEEPINFRA_API_KEY',
    'FIREWORKS_API_KEY',
    'GEMINI_API_KEY',
    'GOOGLE_API_KEY',
    'GROQ_API_KEY',
    'GITHUB_TOKEN',
    'GITHUB_MODELS_TOKEN',
    'HF_TOKEN',
    'HUGGINGFACE_API_KEY',
    'MISTRAL_API_KEY',
    'OPENROUTER_API_KEY',
    'REPLICATE_API_TOKEN',
    'SAMBANOVA_API_KEY',
    'TOGETHER_API_KEY',
    'ANTHROPIC_API_KEY',
    'HARNESS_SMTP_HOST',
    'HARNESS_SMTP_PORT',
    'HARNESS_SMTP_USER',
    'HARNESS_SMTP_PASS',
    'HARNESS_SMTP_FROM',
    'HARNESS_DISCORD_BOT_TOKEN',
    'HARNESS_SLACK_WEBHOOK_URL',
    'HARNESS_WHATSAPP_ACCESS_TOKEN',
    'HARNESS_WHATSAPP_PHONE_NUMBER_ID',
    'HARNESS_WHATSAPP_ALLOWED_RECIPIENTS',
]);
function parseArgs(args = process.argv.slice(2)) {
    const options = {
        model: 'qwen2.5-coder:7b',
        host: 'http://localhost:11434',
        permissionMode: 'default',
        maxTurns: 50,
        modelRouting: {},
        visionModel: process.env.HARNESS_VISION_MODEL ?? '',
        audioTranscribeCommand: process.env.HARNESS_AUDIO_TRANSCRIBE_COMMAND ?? '',
        audioSamplePath: '',
    };
    const command = (0, commands_1.resolveCliCommand)(args[0]);
    if (command?.name === 'doctor') {
        options.command = command.name;
        args = args.slice(1);
    }
    else if (command?.name === 'mycelium') {
        options.command = 'mycelium';
        // Everything after 'mycelium' is forwarded to the subcommand handler.
        options.myceliumArgs = args.slice(1);
        args = [];
    }
    else if (command?.name === 'tui') {
        options.command = 'tui';
        args = args.slice(1);
    }
    else if (command?.name === 'simulate') {
        options.command = 'simulate';
        args = args.slice(1);
    }
    for (let i = 0; i < args.length; i++) {
        switch (args[i]) {
            case '--model':
            case '-m':
                options.model = args[++i];
                break;
            case '--host':
                options.host = args[++i];
                break;
            case '--mode':
                options.permissionMode = args[++i];
                break;
            case '--max-turns':
                options.maxTurns = parseInt(args[++i], 10);
                break;
            case '--summarizer-model':
                options.summarizerModel = args[++i];
                break;
            case '--small-helper-model':
                options.modelRouting.smallModel = args[++i];
                break;
            case '--default-helper-model':
                options.modelRouting.defaultModel = args[++i];
                break;
            case '--strong-helper-model':
                options.modelRouting.strongModel = args[++i];
                break;
            case '--vision-model':
                options.visionModel = args[++i];
                break;
            case '--audio-command':
                options.audioTranscribeCommand = args[++i];
                break;
            case '--audio-sample':
                options.audioSamplePath = args[++i];
                break;
            case '--validate-output':
                options.outputValidation = (0, outputValidation_1.parseOutputValidationProfile)(args[++i]);
                break;
            case '--unproductive-turn-limit':
                options.unproductiveTurnLimit = parseInt(args[++i], 10);
                break;
            case '--backend':
                options.backend = args[++i];
                break;
            case '--compact-remote-smoke':
                options.compactRemoteSmoke = true;
                break;
            case '--watch': {
                // Optional numeric arg: --watch 10 or just --watch (defaults to 5s).
                // Min 1s to avoid hammering Ollama; max 3600s (1h) as a sanity cap.
                const next = args[i + 1];
                let seconds = 5;
                if (next && /^\d+$/.test(next)) {
                    seconds = parseInt(next, 10);
                    i++;
                }
                seconds = Math.max(1, Math.min(3600, seconds));
                options.watchIntervalMs = seconds * 1000;
                break;
            }
            case '--fix':
                options.doctorFix = true;
                break;
            case '--yes':
            case '-y':
                options.doctorYes = true;
                break;
            case '--smtp-test':
                options.smtpTest = true;
                break;
            case '--helper-confidence-threshold':
                options.modelRouting.confidenceEscalationThreshold = parseFloat(args[++i]);
                break;
            case '-p':
            case '--prompt':
                options.prompt = args[++i];
                break;
            case '--prompt-file':
                options.promptFile = args[++i];
                break;
            case '--base-url':
                options.tuiBaseUrl = args[++i];
                break;
            case '--probe':
                options.simulateProbeIds = options.simulateProbeIds ?? [];
                options.simulateProbeIds.push(args[++i]);
                break;
            case '--category':
                options.simulateCategories = options.simulateCategories ?? [];
                options.simulateCategories.push(args[++i]);
                break;
            case '--probe-timeout': {
                const ms = parseInt(args[++i], 10);
                if (Number.isFinite(ms) && ms > 0)
                    options.simulateProbeTimeoutMs = ms;
                break;
            }
            case '--persist':
                options.simulatePersist = true;
                break;
            case '--help':
            case '-h':
                printHelp();
                process.exit(0);
        }
    }
    return options;
}
function printHelp() {
    console.log((0, commands_1.formatCliHelp)(outputValidation_1.OUTPUT_VALIDATION_PROFILES.map((profile) => profile.profile)));
}
/**
 * Interactive Y/n prompt for `harness doctor --fix` so users on a TTY
 * can approve a vision-model pull without a second invocation. Returns
 * `false` immediately when stdin is not a TTY so CI/scripts stay
 * deterministic — they should pass `--yes` instead.
 */
async function promptForVisionPull(model) {
    if (!process.stdin.isTTY || !process.stdout.isTTY)
        return false;
    const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
    try {
        const answer = await new Promise((resolve) => {
            rl.question(`Pull vision model "${model}" (~4GB)? [y/N] `, (a) => resolve(a));
        });
        return /^y(es)?$/i.test(answer.trim());
    }
    finally {
        rl.close();
    }
}
async function loadHeadlessRuntimeSettings(projectDir) {
    const settingsPath = path.join(projectDir, '.harness', 'settings.json');
    try {
        const raw = await (0, promises_1.readFile)(settingsPath, 'utf-8');
        const settings = JSON.parse(raw);
        let allowedExternalPaths = Array.isArray(settings.allowedExternalPaths)
            ? settings.allowedExternalPaths.map((value) => String(value).slice(0, 500))
            : [];
        if (Array.isArray(settings.allowedExternalPaths)) {
            (0, pathResolution_1.setAllowedExternalPaths)(allowedExternalPaths);
        }
        if (settings.agentOutputDir !== undefined) {
            const agentOutputDir = String(settings.agentOutputDir).trim().slice(0, 500);
            if (agentOutputDir) {
                process.env.HARNESS_AGENT_OUTPUT_DIR = agentOutputDir;
                if (!allowedExternalPaths.includes(agentOutputDir)) {
                    allowedExternalPaths = [...allowedExternalPaths, agentOutputDir];
                    (0, pathResolution_1.setAllowedExternalPaths)(allowedExternalPaths);
                }
            }
            else
                delete process.env.HARNESS_AGENT_OUTPUT_DIR;
        }
        if (settings.webReadMaxChars !== undefined) {
            (0, webSearchTool_1.configureWebReadTool)({ maxChars: (0, webSearchTool_1.sanitizeWebReadMaxChars)(settings.webReadMaxChars, webSearchTool_1.DEFAULT_WEB_READ_MAX_CHARS) });
        }
    }
    catch {
        // Headless CLI should still work when the web UI settings file is absent or malformed.
    }
    const apiKeysPath = path.join(projectDir, '.harness', 'api-keys.json');
    try {
        const raw = await (0, promises_1.readFile)(apiKeysPath, 'utf-8');
        const parsed = JSON.parse(raw);
        for (const [key, value] of Object.entries(parsed)) {
            if (!CLI_STORED_ENV_KEYS.has(key))
                continue;
            if (process.env[key]?.trim())
                continue;
            if (typeof value !== 'string' || !value.trim())
                continue;
            process.env[key] = value.trim();
        }
    }
    catch {
        // Missing credentials file is expected for local-only setups.
    }
}
async function main() {
    const options = parseArgs();
    if (options.command === 'doctor') {
        const runOnce = async () => {
            const result = await (0, health_1.checkSetupHealth)({
                host: options.host,
                visionModel: options.visionModel,
                audioTranscribeCommand: options.audioTranscribeCommand,
                audioSamplePath: options.audioSamplePath || undefined,
                pdfOcrCommand: process.env.HARNESS_PDF_OCR_COMMAND,
            });
            console.log(formatSetupHealth(result));
            if (options.smtpTest) {
                await runLiveSmtpTest();
            }
            // Event store + promise health (async addons)
            try {
                const projectDir = process.cwd();
                const [eventSummary, obligations] = await Promise.all([
                    (0, eventStore_1.summarizeEventStore)(projectDir).catch(() => null),
                    (0, promiseLedger_1.checkObligations)(projectDir).catch(() => null),
                ]);
                if (eventSummary && eventSummary.total_events > 0) {
                    const cats = Object.entries(eventSummary.categories).map(([k, v]) => `${k}:${v}`).join(', ');
                    console.log(formatHealthLine('Events', true, `${eventSummary.total_events} events (${cats}), ${eventSummary.snapshot_count} snapshots.`));
                }
                if (obligations) {
                    const breachCount = obligations.breaches.length;
                    console.log(formatHealthLine('Promises', breachCount === 0, `${obligations.total} total, ${obligations.pending} pending, ${obligations.fulfilled} fulfilled, ${breachCount} breach(es).`));
                }
            }
            catch { /* optional */ }
            const failedRequired = !result.ollama.ok
                || (options.visionModel ? !result.vision.ok : false)
                || (options.audioTranscribeCommand ? !result.audio.ok : false);
            return failedRequired;
        };
        if (options.watchIntervalMs !== undefined) {
            // Watch mode: redraw on every tick. Useful when toggling API keys
            // in the UI to confirm doctor reflects them, or when bringing
            // Ollama up/down. Ctrl+C stops the loop. Exit code stays 0 in
            // watch mode — it's a monitoring view, not a one-shot check.
            const seconds = Math.round(options.watchIntervalMs / 1000);
            // eslint-disable-next-line no-constant-condition
            while (true) {
                // Best-effort screen clear. process.stdout.write avoids the extra
                // blank line console.clear() sometimes leaves on Windows.
                if (process.stdout.isTTY)
                    process.stdout.write('\x1B[2J\x1B[0f');
                console.log(`harness doctor --watch (every ${seconds}s, Ctrl+C to stop) — ${new Date().toISOString()}`);
                try {
                    await runOnce();
                }
                catch (error) {
                    console.error(`doctor failed: ${error instanceof Error ? error.message : String(error)}`);
                }
                await new Promise((resolve) => setTimeout(resolve, options.watchIntervalMs));
            }
        }
        const failedRequired = await runOnce();
        if (options.doctorFix) {
            try {
                const fixResult = await (0, doctorFix_1.runDoctorFix)({
                    projectDir: process.cwd(),
                    ollamaHost: options.host,
                    visionModel: options.visionModel,
                    contextMaxTokens: 0, // doctorFix reads .harness/settings.json directly; this arg is reserved for future overrides.
                    yes: Boolean(options.doctorYes),
                    confirmVisionPull: options.doctorYes ? undefined : promptForVisionPull,
                });
                console.log('');
                console.log((0, doctorFix_1.formatDoctorFixSummary)(fixResult));
            }
            catch (error) {
                console.error(`doctor --fix failed: ${error instanceof Error ? error.message : String(error)}`);
                process.exitCode = 1;
            }
        }
        if (failedRequired)
            process.exitCode = 1;
        return;
    }
    if (options.command === 'mycelium') {
        const result = await (0, cli_1.runMyceliumCli)({ projectDir: process.cwd(), args: options.myceliumArgs ?? [] });
        console.log(result.output);
        process.exitCode = result.exitCode;
        return;
    }
    if (options.command === 'tui') {
        const { runTui } = await Promise.resolve().then(() => __importStar(require('../tui')));
        await runTui({
            baseUrl: options.tuiBaseUrl,
            // Only forward the model when the user explicitly customised it;
            // empty string lets the daemon pick its current selection.
            model: options.model && options.model !== 'qwen2.5-coder:7b' ? options.model : undefined,
        });
        return;
    }
    if (options.command === 'simulate') {
        const { runSimulation, formatSimulationSummary } = await Promise.resolve().then(() => __importStar(require('../eval/simulator')));
        const run = await runSimulation({
            baseUrl: options.tuiBaseUrl,
            model: options.model && options.model !== 'qwen2.5-coder:7b' ? options.model : undefined,
            filterIds: options.simulateProbeIds,
            filterCategories: options.simulateCategories,
            perProbeTimeoutMs: options.simulateProbeTimeoutMs,
            persistEvalRunProjectDir: options.simulatePersist ? process.cwd() : undefined,
        });
        console.log(formatSimulationSummary(run));
        if (options.simulatePersist) {
            console.log(`\n  Persisted run ${run.id} → .harness/evals/trace-runs.jsonl (promotion gate will count it)`);
        }
        if (run.failed > 0 || run.errored > 0) {
            process.exitCode = 1;
        }
        return;
    }
    const projectDir = process.cwd();
    await loadHeadlessRuntimeSettings(projectDir);
    // Initialize client (Ollama by default; OpenAI-compatible providers via
    // --backend or HARNESS_BACKEND env var).
    const client = (0, chatClientFactory_1.createChatClient)({
        backend: options.backend,
        model: options.model,
        host: options.host,
        autoFallback: true,
    });
    // Health check
    const health = await client.healthCheck();
    if (!health.ok) {
        console.error(`❌ ${health.error}`);
        process.exit(1);
    }
    let headlessPrompt = options.prompt;
    if (!headlessPrompt && options.promptFile) {
        const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
        headlessPrompt = await fs.readFile(options.promptFile, 'utf-8');
    }
    if (options.compactRemoteSmoke && headlessPrompt) {
        await runCompactRemoteSmoke(client, headlessPrompt);
        return;
    }
    // Set up components
    const tools = options.compactRemoteSmoke ? [] : (0, tools_1.getBuiltinTools)();
    const permissionEngine = new engine_1.PermissionEngine([], options.permissionMode);
    const session = new sessionStorage_1.SessionStorage(projectDir, options.model);
    await session.initialize();
    const systemPrompt = await (0, assembly_1.assembleSystemContext)({
        systemPrompt: options.compactRemoteSmoke ? 'Reply briefly in plain text.' : buildSystemPrompt(options.modelRouting),
        projectDir,
    });
    const config = {
        model: options.model,
        systemPrompt,
        maxTurns: options.maxTurns,
        unproductiveTurnLimit: options.unproductiveTurnLimit,
        outputValidation: options.outputValidation ? { enabled: true, profile: options.outputValidation } : undefined,
    };
    const deps = {
        client,
        tools,
        permissionCheck: (call) => permissionEngine.evaluateAsync(call),
        session,
        summarizerClient: options.summarizerModel
            ? (0, chatClientFactory_1.createChatClient)({ backend: options.backend, model: options.summarizerModel, host: options.host, autoFallback: true })
            : undefined,
    };
    // Headless mode — accept either an inline prompt or a path to read from.
    // --prompt-file lets callers pass large prompts (e.g. inline file contents)
    // that would otherwise overflow shell command-line size limits.
    if (headlessPrompt) {
        await runHeadless(config, deps, session, headlessPrompt);
        return;
    }
    // Interactive mode
    await runInteractive(config, deps, session);
}
function formatSetupHealth(result) {
    const lines = [
        'Setup doctor',
        formatHealthLine('Ollama', result.ollama.ok, result.ollama.message),
        formatHealthLine('Vision', result.vision.ok, result.vision.message),
        formatHealthLine('Audio', result.audio.ok, result.audio.message),
    ];
    if (result.pdfOcr)
        lines.push(formatHealthLine('PDF OCR', result.pdfOcr.ok, result.pdfOcr.message));
    lines.push(formatHealthLine('SMTP', result.smtp.ok, result.smtp.message));
    lines.push(formatHealthLine('Node', result.local.node.ok, result.local.node.message));
    lines.push(formatHealthLine('Package', result.local.package.ok, result.local.package.message));
    lines.push(formatHealthLine('Sessions', result.local.sessions.ok, result.local.sessions.message));
    lines.push(formatHealthLine('Tools', result.local.tools.ok, result.local.tools.message));
    lines.push(formatHealthLine('Automations', result.local.automations.ok, result.local.automations.message));
    lines.push(formatHealthLine('Mycelium', result.local.mycelium.ok, result.local.mycelium.message));
    if (result.backends && result.backends.length > 0) {
        lines.push('Backends (OpenAI-compatible):');
        for (const backend of result.backends) {
            lines.push('  ' + formatHealthLine(backend.label, backend.ok, backend.message));
        }
        // Discoverability: when at least one backend is configured, point
        // users at the smoke that exercises end-to-end CLI round-trips for
        // every configured backend. Skipped backends are clearly skipped,
        // so this is safe to surface even with a single key set.
        if (result.backends.some((b) => b.ok)) {
            lines.push('  Tip: run `npm run smoke:remote-backends` to round-trip every configured backend through the CLI.');
        }
    }
    if (result.fallback) {
        const f = result.fallback;
        const status = f.enabled
            ? `enabled, ${f.configuredCount} backend(s) with keys, cooldown ${Math.round(f.cooldownMs / 1000)}s, order: ${f.order}`
            : 'disabled (set HARNESS_REMOTE_AUTO_FALLBACK=1 to enable)';
        lines.push(formatHealthLine('Fallback', f.enabled && f.configuredCount > 1, `Provider fallback ${status}.`));
    }
    if (result.synthesisStats && Object.keys(result.synthesisStats).length > 0) {
        lines.push('Synthesis turn stats:');
        for (const [model, record] of Object.entries(result.synthesisStats)) {
            const ratio = record.total > 0 ? Math.round((record.fired / record.total) * 100) : 0;
            const adaptive = record.adaptiveMaxTurns !== 25 ? ` (adaptive: ${record.adaptiveMaxTurns} turns)` : '';
            lines.push(`  ${model}: ${record.fired}/${record.total} sessions (${ratio}%)${adaptive}`);
        }
    }
    return lines.join('\n');
}
function formatHealthLine(label, ok, message) {
    return `${ok ? 'OK' : 'WARN'} ${label}: ${message}`;
}
async function runLiveSmtpTest() {
    const host = process.env.HARNESS_SMTP_HOST?.trim();
    const port = parseInt(process.env.HARNESS_SMTP_PORT ?? '587', 10);
    const user = process.env.HARNESS_SMTP_USER?.trim();
    // Strip internal spaces — Google App Passwords use "xxxx xxxx xxxx xxxx" format.
    const pass = process.env.HARNESS_SMTP_PASS?.trim().replace(/\s+/g, '');
    if (!host || !user || !pass) {
        console.log(formatHealthLine('SMTP Test', false, 'Cannot test — SMTP credentials are not configured.'));
        return;
    }
    try {
        const transporter = nodemailer.createTransport({
            host,
            port,
            secure: port === 465,
            auth: { user, pass },
            connectionTimeout: 10_000,
            greetingTimeout: 10_000,
        });
        await transporter.verify();
        transporter.close();
        console.log(formatHealthLine('SMTP Test', true, `Live connection to ${host}:${port} succeeded.`));
    }
    catch (error) {
        const msg = error instanceof Error ? error.message : String(error);
        console.log(formatHealthLine('SMTP Test', false, `Live connection failed: ${msg}`));
    }
}
function buildSystemPrompt(modelRouting) {
    const routingLines = Object.entries(modelRouting)
        .filter(([, value]) => value !== undefined && value !== '')
        .map(([key, value]) => `${key}: ${value}`);
    const routingText = routingLines.length
        ? `\n\nHelper model routing policy:\n${routingLines.join('\n')}`
        : '';
    const externalPaths = (0, pathResolution_1.getAllowedExternalPaths)();
    const outputDir = (0, pathResolution_1.getAgentOutputDir)().trim();
    const externalText = externalPaths.length > 0
        ? ` File tools can read and write inside the project and these allowed external folders: ${externalPaths.join(', ')}.${outputDir ? ` Put new scratch artifacts and generated reports in ${outputDir} unless the user names a different destination.` : ''}`
        : '';
    const buildDiscipline = '\n\nBuild discipline:\n'
        + '- Before scaffolding a new top-level module or directory, check the Available Skills list above and prefer invoking a relevant skill (e.g. planner) over writing files directly.\n'
        + '- When the user is asking a feasibility or "can we?" question, answer with analysis first. Confirm intent before generating more than ~200 lines of new code.\n'
        + '- After writing or editing source files, run the project\'s validator (e.g. `npx tsc --noEmit` for TypeScript, `pytest` for Python) before declaring the work complete.';
    return 'You are a helpful coding assistant. Use the available tools to help the user with their task. Read files, write code, research the web, create documents, draft or send configured email, and execute commands as needed. When the user asks about current events, news, weather, prices, scores, scientific research, market research, or anything that changes over time, call web_search first and then summarize the results. Do not answer recent-information requests from training data alone.' + externalText + buildDiscipline + routingText;
}
function summarizeConsoleToolResult(name, success, output) {
    if (['skill', 'list_files', 'file_read', 'recall'].includes(name))
        return null;
    const text = output.replace(/\s+/g, ' ').trim();
    if (!text)
        return success ? null : `${name} failed`;
    const taskMatch = text.match(/\+ Task added:\s*([^\r\n]+)/i);
    if (taskMatch)
        return `Added task: ${taskMatch[1].trim()}`;
    if (/telegram message sent successfully/i.test(text))
        return null;
    return `${name}: ${text.slice(0, 160)}`;
}
function buildConsoleToolOnlyResponse(input) {
    if (input.errors.length > 0)
        return `Harness reported an error:\n${input.errors.slice(-2).join('\n')}`;
    if (input.toolSummaries.length > 0)
        return `Done.\n${input.toolSummaries.slice(-4).join('\n')}`;
    if (input.doneReason === 'max_turns_synthesized')
        return 'Done (synthesis turn produced no visible text).';
    if (input.toolCalls > 0)
        return 'Done. The model used tools, but did not return a readable final message.';
    return 'No response from the model.';
}
async function runHeadless(config, deps, session, prompt) {
    const messages = [{ role: 'user', content: prompt }];
    let assistantText = '';
    let toolCalls = 0;
    const toolSummaries = [];
    const errors = [];
    for await (const event of (0, queryLoop_1.queryLoop)(config, deps, messages)) {
        switch (event.type) {
            case 'text':
                assistantText += event.content;
                console.log(event.content);
                break;
            case 'tool_call':
                console.error(`🔧 ${event.call.name}(${JSON.stringify(event.call.input).slice(0, 100)})`);
                break;
            case 'tool_result':
                toolCalls++;
                const summary = summarizeConsoleToolResult(event.call.name, event.result.success, event.result.output);
                if (summary)
                    toolSummaries.push(summary);
                const icon = event.result.success ? '✅' : '❌';
                console.error(`  ${icon} ${event.result.output.slice(0, 200)}`);
                break;
            case 'context':
                console.error(`🧠 context ${event.strategy}: freed ~${event.tokensFreed} tokens, pressure ${Math.round(event.pressure * 100)}%${event.autosaved ? ', autosaved' : ''}`);
                break;
            case 'output_validation':
                console.error(`🧪 output validation ${event.validation.status}: score ${event.validation.score}`);
                for (const finding of event.validation.findings.slice(0, 5)) {
                    console.error(`  ${finding.severity.toUpperCase()} ${finding.message}`);
                }
                break;
            case 'error':
                errors.push(event.message);
                console.error(`⚠️ ${event.message}`);
                break;
            case 'synthesis_fired':
                console.error(`🔄 synthesis turn: model exhausted ${event.maxTurns} tool turns (${event.toolCallsTotal} calls)`);
                break;
            case 'auto_continue':
                console.error(`🔁 auto-continue #${event.continuationCount}: ${event.reason}`);
                break;
            case 'done':
                if (!assistantText.trim())
                    console.log(buildConsoleToolOnlyResponse({ toolCalls, toolSummaries, errors, doneReason: event.reason }));
                console.error(`\n--- ${event.reason} (${event.turns} turns) ---`);
                break;
        }
    }
}
async function runCompactRemoteSmoke(client, prompt) {
    const result = await client.chat([{ role: 'user', content: prompt }]);
    const content = typeof result.message.content === 'string'
        ? result.message.content
        : JSON.stringify(result.message.content);
    console.log(content.trim());
}
async function runInteractive(config, deps, session) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });
    console.log(`🤖 Ollama Agent Harness (${config.model})`);
    console.log('Type your message, or /quit to exit.\n');
    const prompt = () => new Promise((resolve) => rl.question('> ', resolve));
    while (true) {
        const input = await prompt();
        const trimmed = input.trim();
        if (!trimmed)
            continue;
        if (trimmed === '/quit' || trimmed === '/exit') {
            console.log('Goodbye!');
            rl.close();
            return;
        }
        const messages = [{ role: 'user', content: trimmed }];
        let assistantText = '';
        let toolCalls = 0;
        const toolSummaries = [];
        const errors = [];
        for await (const event of (0, queryLoop_1.queryLoop)(config, deps, messages)) {
            switch (event.type) {
                case 'text':
                    assistantText += event.content;
                    console.log(`\n${event.content}\n`);
                    break;
                case 'tool_call':
                    console.log(`  🔧 ${event.call.name}(${JSON.stringify(event.call.input).slice(0, 100)})`);
                    break;
                case 'tool_result':
                    toolCalls++;
                    const summary = summarizeConsoleToolResult(event.call.name, event.result.success, event.result.output);
                    if (summary)
                        toolSummaries.push(summary);
                    const icon = event.result.success ? '✅' : '❌';
                    console.log(`  ${icon} ${event.result.output.slice(0, 200)}`);
                    break;
                case 'error':
                    errors.push(event.message);
                    console.log(`  ⚠️ ${event.message}`);
                    break;
                case 'synthesis_fired':
                    console.log(`  🔄 synthesis turn: model exhausted ${event.maxTurns} tool turns (${event.toolCallsTotal} calls)`);
                    break;
                case 'context':
                    console.log(`  🧠 context ${event.strategy}: freed ~${event.tokensFreed} tokens, pressure ${Math.round(event.pressure * 100)}%${event.autosaved ? ', autosaved' : ''}`);
                    break;
                case 'output_validation':
                    console.log(`  🧪 output validation ${event.validation.status}: score ${event.validation.score}`);
                    for (const finding of event.validation.findings.slice(0, 3)) {
                        console.log(`     ${finding.severity.toUpperCase()} ${finding.message}`);
                    }
                    break;
                case 'done':
                    if (!assistantText.trim())
                        console.log(`\n${buildConsoleToolOnlyResponse({ toolCalls, toolSummaries, errors, doneReason: event.reason })}\n`);
                    break;
            }
        }
    }
}
if (require.main === module) {
    main().catch((error) => {
        console.error('Fatal error:', error);
        process.exit(1);
    });
}
//# sourceMappingURL=index.js.map