"use strict";
/**
 * Kimi CLI (MoonshotAI) agent shim.
 *
 * Uses `--afk` flag which auto-dismisses AskUserQuestion AND auto-approves
 * tool calls. Equivalent to claude's auto-accept and codex's
 * approval_policy=never. Without it kimi shows a one-time "Allow this tool?"
 * dialog that would block a chorus-spawned reviewer indefinitely.
 *
 * If a new kimi release ever drops `--afk`, the error-detector pattern in
 * src/daemon/error-detector.ts (kind='permission_prompt') is a defense-in-
 * depth fallback — runner can capture-pane and send-keys to dismiss it.
 *
 * MCP context: kimi spawned as a reviewer doesn't talk to chorus's own MCP
 * server (that would be circular). It just reads ask.md, writes answer.md.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.kimiShim = void 0;
const node_fs_1 = __importDefault(require("node:fs"));
const node_os_1 = __importDefault(require("node:os"));
const node_path_1 = __importDefault(require("node:path"));
const quote_js_1 = require("./quote.js");
const headless_js_1 = require("../headless.js");
const index_js_1 = require("./parsers/index.js");
const atomic_write_js_1 = require("../../lib/atomic-write.js");
const opencode_js_1 = require("./opencode.js");
const sandbox_guard_js_1 = require("./sandbox-guard.js");
let cachedTransport = null;
function detectKimiTransport() {
    if (cachedTransport)
        return cachedTransport;
    const override = process.env.CHORUS_KIMI_TRANSPORT;
    if (override === 'kimi-cli' || override === 'opencode') {
        cachedTransport = override;
        return override;
    }
    // Probe the standalone kimi config — non-empty `default_model` OR any
    // `[models.<name>]` table means the user has wired up a real model.
    const configPath = node_path_1.default.join(node_os_1.default.homedir(), '.kimi', 'config.toml');
    if (node_fs_1.default.existsSync(configPath)) {
        try {
            const body = node_fs_1.default.readFileSync(configPath, 'utf-8');
            const defaultModel = body.match(/^\s*default_model\s*=\s*["']([^"']+)["']/m);
            const hasDefault = defaultModel != null && defaultModel[1].length > 0;
            const hasModelsTable = /^\[models\.[A-Za-z0-9_.-]+\]/m.test(body);
            if (hasDefault || hasModelsTable) {
                cachedTransport = 'kimi-cli';
                return 'kimi-cli';
            }
        }
        catch {
            /* fall through */
        }
    }
    cachedTransport = 'opencode';
    return 'opencode';
}
/**
 * Drop a per-participant `_meta.json` sidecar so the cockpit can show
 * which binary + model actually ran. Without this the run page would
 * always say "kimi-cli · kimi-k2.6" even when the underlying transport
 * is opencode + opencode-go/kimi-k2.6. Best-effort — failures are silent
 * (the sidecar is purely informational).
 */
function writeTransportMeta(cwd, binary, model) {
    try {
        // Atomic temp+rename — cockpit polls this file; a crash mid-write would
        // otherwise leave a half-written JSON that the parser rejects. See
        // src/lib/atomic-write.ts for the rationale.
        const metaPath = node_path_1.default.join(cwd, '_meta.json');
        (0, atomic_write_js_1.atomicWriteJsonSync)(metaPath, { binary, model, ts: Date.now() });
    }
    catch {
        /* informational only */
    }
}
exports.kimiShim = {
    lineage: 'moonshot',
    name: 'kimi-cli',
    buildLaunchCommand(opts) {
        (0, quote_js_1.validateValue)('model', opts.model);
        // Same fail-closed gate as opencode — kimi-cli mirrors opencode's
        // codebase and inherits the same gap. Audit D1 BLOCKER.
        (0, sandbox_guard_js_1.assertSandboxSupported)(opts.sandbox, 'kimi');
        const cwd = (0, quote_js_1.quotePath)(opts.cwd);
        let cmd = `cd ${cwd} && kimi`;
        // --afk auto-dismisses prompts and auto-approves tool calls. Default ON
        // for headless reviewer spawns; user can flip via settings if they want
        // to be prompted in the kimi terminal session.
        if (opts.autoApprove !== false) {
            cmd += ` --afk`;
        }
        if (opts.model) {
            cmd += ` -m ${(0, quote_js_1.quoteValue)(opts.model)}`;
        }
        return cmd;
    },
    // Defense-in-depth recovery if --afk is dropped or a future kimi rev shows
    // a different prompt shape. The kimi standalone CLI mirrors opencode's
    // 2-step dialog UX (it's the same upstream codebase) so we send the same
    // 3-key sequence: `Right` highlights "Allow always", first `Enter` opens
    // the nested "Confirm/Cancel" dialog (default = Confirm), second `Enter`
    // confirms it. See opencode.ts for the full per-key rationale.
    recoverKeys: {
        permission_prompt: ['Right', 'Enter', 'Enter'],
    },
    formatPrompt(opts) {
        const sentinel = opts.expectDoneSentinel
            ? '\n\nWhen finished, end your response with: ## DONE'
            : '';
        return (`${opts.task}\n\n` +
            `Read the prompt at: ${opts.promptFile}\n\n` +
            `Write your full answer to: ${opts.answerFile}${sentinel}`);
    },
    /**
     * Headless mode — branches on detected transport (see top-of-file
     * comment for the kimi-cli vs opencode-go decision tree).
     *
     *   - kimi-cli: streaming `kimi --print --output-format stream-json`,
     *     parsed via parseKimi (Claude-Code-compatible JSON events).
     *
     *   - opencode: one-shot `opencode run --format json --model
     *     opencode-go/kimi-k2.6 "<prompt>"`, parsed via parseOpencode
     *     (JSON-Lines, one text event per LLM message).
     *
     * Model normalisation: templates pass `kimi-k2.6` (plain). For the
     * opencode path we prepend `opencode-go/` so the CLI routes through
     * the Go subscription gateway.
     */
    runHeadless(opts) {
        const sandboxError = (0, sandbox_guard_js_1.sandboxFailClosed)(opts.sandbox, 'kimi');
        if (sandboxError)
            return sandboxError;
        const transport = detectKimiTransport();
        if (transport === 'kimi-cli') {
            const model = opts.model ?? 'kimi-k2.6';
            writeTransportMeta(opts.cwd, 'kimi-cli', model);
            const args = ['--print', '--output-format', 'stream-json'];
            if (opts.model)
                args.push('-m', opts.model);
            const run = (0, headless_js_1.spawnHeadless)({
                command: 'kimi',
                args,
                cwd: opts.cwd,
                stdinPayload: opts.promptText,
                parseLine: index_js_1.parseKimi,
                // cli tag is the binary that actually ran — error messages quote
                // it as "{cli} exited N", so misattributing failures to the wrong
                // binary sends the user looking at the wrong PATH/auth/install.
                cli: 'kimi',
                timeoutMs: opts.timeoutMs,
                abortSignal: opts.abortSignal,
                heartbeat: false,
            });
            return run.events;
        }
        // opencode path — qualify the model with the opencode-go/ prefix.
        const rawModel = opts.model ?? 'kimi-k2.6';
        const model = rawModel.startsWith('opencode-go/')
            ? rawModel
            : `opencode-go/${rawModel}`;
        writeTransportMeta(opts.cwd, 'opencode-cli', model);
        // Argv-overflow guard: stash the prompt on disk and pass a tiny argv
        // directive instead of the full prompt. Mirrors opencodeShim — see its
        // runHeadless for why (opencode `run` only takes a positional argv).
        const promptPath = node_path_1.default.join(opts.cwd, 'prompt.md');
        node_fs_1.default.writeFileSync(promptPath, opts.promptText, 'utf-8');
        const directive = `Open the file at this absolute path using your read tool: ${promptPath} ` +
            `— follow the instructions inside exactly and respond with your full answer in this conversation, ending with ## DONE.`;
        const opencodeArgs = ['run', '--format', 'json', '--model', model, directive];
        // PTY-wrap the spawn — opencode 1.14.x's `run --format json` checks
        // isatty(stdout) and emits zero bytes when piped (model still runs to
        // completion, just no JSON output). The opencode shim's runHeadless
        // wraps for the same reason. Without this, every Kimi reviewer routed
        // through the opencode-go transport (the default when no standalone
        // kimi config exists) silently produces 0-byte answer.md — exactly the
        // failure mode the opencode PTY fix was written to prevent. Confirmed
        // launch-eve by both deepseek and gemini reviewing the shims; the bug
        // was a copy-paste oversight when the PTY fix landed in opencode.ts.
        const { command, args } = (0, opencode_js_1.wrapWithPty)('opencode', opencodeArgs);
        const run = (0, headless_js_1.spawnHeadless)({
            command,
            args,
            cwd: opts.cwd,
            parseLine: index_js_1.parseOpencode,
            onExit: (fullStdout) => (0, index_js_1.parseOpencodeExit)(fullStdout),
            // Tag the failure as 'opencode' — that's the binary that runs.
            // The previous 'kimi' tag emitted "kimi exited 127: opencode:
            // command not found" which sent the user hunting for a kimi-CLI
            // PATH issue when the actual missing binary is opencode.
            cli: 'opencode',
            timeoutMs: opts.timeoutMs,
            abortSignal: opts.abortSignal,
            heartbeat: true,
        });
        return run.events;
    },
    estimateCostUsd() {
        // Kimi CLI uses Moonshot subscription on the user's plan, not metered API.
        return 0;
    },
    // Same opencode-go gateway flake risk as the opencode shim — when the
    // requested model carries the opencode-go/ prefix the kimi shim
    // delegates to opencode internally (see runHeadless above). Inherit
    // the same retry posture so moonshot lineages don't silently fall
    // back without an attempt at the cheap recovery. Flagged by codex on
    // the PR #87 self-audit ("isOpenCodeFamily" idiom appears throughout
    // error-detector.ts; the original lineage dispatch only checked
    // 'opencode' and left this gap open).
    retryPolicy: {
        onNullKind: true,
        onNoOutput: true,
    },
};
//# sourceMappingURL=kimi.js.map