"use strict";
/**
 * Codex CLI agent shim.
 * Per-session CODEX_HOME for parallel safety (see feedback_codex_home_per_account.md).
 * Transport-aware sandbox modes (see feedback_codex_sandbox_modes.md).
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.codexShim = void 0;
exports.buildHeadlessArgs = buildHeadlessArgs;
const quote_js_1 = require("./quote.js");
const preflight_js_1 = require("./preflight.js");
const headless_js_1 = require("../headless.js");
const index_js_1 = require("./parsers/index.js");
const codex_stderr_scan_js_1 = require("./parsers/codex-stderr-scan.js");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os"));
/**
 * Resolve CODEX_HOME for this spawn.
 *
 * - `CHORUS_CODEX_HOME` env var (when set) wins on the single-user path.
 *   Lets users point chorus at a non-rate-limited codex account
 *   (e.g. `~/.codex-cdx-2`) without forking the shim or wiring multi-account
 *   isolation. Ignored when `accountId` is explicit — that path already owns
 *   account selection.
 * - When `accountId` is undefined/empty AND no env override: reuse the user's
 *   existing `~/.codex/` (their primary login). v0.5 single-user default.
 * - When `accountId` is explicit: create/reuse `~/.codex-<accountId>/` for
 *   parallel multi-account isolation. Copies config.toml from the primary
 *   home; NEVER copies auth.json (each account must have its own login).
 */
function ensureCodexHome(accountId) {
    const homeDir = os_1.default.homedir();
    const primary = path_1.default.join(homeDir, '.codex');
    if (!accountId) {
        const override = process.env.CHORUS_CODEX_HOME?.trim();
        if (override && override.length > 0) {
            // Trust the user — if the dir doesn't exist or has no auth.json, codex
            // exec will fail loudly (which is now surfaced via quota_exhausted /
            // cli_error). Better than silently picking the rate-limited primary.
            return override;
        }
        // Single-user fast path — use the user's existing login.
        return primary;
    }
    const codexDir = path_1.default.join(homeDir, `.codex-${accountId}`);
    if (!fs_1.default.existsSync(codexDir)) {
        fs_1.default.mkdirSync(codexDir, { recursive: true });
        // config.toml is non-critical — codex falls back to its built-in
        // defaults when the file is absent. Wrap the copy so an EACCES /
        // ENOENT race (file unlinked between exists and read, or chmod 0)
        // doesn't crash ensureCodexHome with an unhandled exception. The
        // directory was already created; the caller can still attempt to
        // spawn codex with no overridden config.
        try {
            const defaultConfigPath = path_1.default.join(primary, 'config.toml');
            const targetConfigPath = path_1.default.join(codexDir, 'config.toml');
            if (fs_1.default.existsSync(defaultConfigPath) && !fs_1.default.existsSync(targetConfigPath)) {
                const content = fs_1.default.readFileSync(defaultConfigPath, 'utf-8');
                fs_1.default.writeFileSync(targetConfigPath, content, 'utf-8');
            }
        }
        catch (err) {
            console.warn(`[codex] config.toml copy to ${codexDir} failed (continuing with codex defaults):`, err instanceof Error ? err.message : err);
        }
        // NOTE: NEVER copy auth.json — each CODEX_HOME must have its own.
        // First launch will prompt for `codex login`.
    }
    return codexDir;
}
/**
 * Build `codex exec` argv for headless reviewer/doer runs.
 *
 * Pure function — no I/O, no env reads — so we can unit-test the exact
 * argv shape (especially `--ignore-user-config`, which is load-bearing
 * for issues #10 and #16).
 *
 * Why `--ignore-user-config` is here: the user's `~/.codex/config.toml`
 * may declare MCP servers, plugins, or notification hooks. In headless
 * `codex exec` mode those integrations have caused codex to hang or
 * cancel mid-call — see #10 (codex as our reviewer) and #16 (codex as
 * MCP client of chorus) for two independent reproductions of the same
 * class of failure. Skipping the user config gives us a clean,
 * deterministic codex run for review work; we still pass through the
 * sandbox/network flags chorus owns explicitly below.
 */
function buildHeadlessArgs(opts) {
    const args = ['exec'];
    // Chorus chat dirs aren't git repos. Without this flag codex exec
    // exits 1 with "Not inside a trusted directory".
    args.push('--skip-git-repo-check');
    // Strip user config — see function docstring for why.
    args.push('--ignore-user-config');
    if (opts.sandbox === 'full') {
        args.push('--dangerously-bypass-approvals-and-sandbox');
    }
    else if (opts.sandbox === 'strict') {
        args.push('-c', 'sandbox_mode="read-only"');
    }
    if (opts.networkAccess) {
        args.push('-c', 'sandbox_workspace_write.network_access=true');
    }
    if (opts.model) {
        args.push('--model', opts.model);
    }
    // `-` tells codex exec to read the prompt from stdin (avoids ARG_MAX).
    args.push('-');
    return args;
}
exports.codexShim = {
    lineage: 'openai',
    name: 'codex-cli',
    buildLaunchCommand(opts) {
        (0, quote_js_1.validateValue)('accountId', opts.accountId);
        (0, quote_js_1.validateValue)('model', opts.model);
        const codexHome = ensureCodexHome(opts.accountId);
        // Pre-trust the chat dir so Codex skips its first-launch trust prompt.
        (0, preflight_js_1.preTrustCodexWorkspace)(codexHome, opts.cwd);
        const cwd = (0, quote_js_1.quotePath)(opts.cwd);
        const flags = [];
        // Sandbox profile from user settings (may be overridden by transport).
        if (opts.unsandboxed || opts.sandbox === 'full') {
            // Full bypass — user explicitly opted into trust-everything.
            flags.push('--dangerously-bypass-approvals-and-sandbox');
        }
        else if (opts.sandbox === 'strict') {
            // Read-only — codex can't write files or shell-exec.
            flags.push('-c', 'sandbox_mode="read-only"');
        }
        // 'workspace' (default) leaves codex in its config.toml-defined
        // workspace-write mode. No flag override.
        // Network access — opt-in. github transport always needs network.
        if (opts.networkAccess || opts.transport === 'github') {
            flags.push('-c', 'sandbox_workspace_write.network_access=true');
        }
        if (opts.model) {
            flags.push('--model', (0, quote_js_1.quoteValue)(opts.model));
        }
        const flagsStr = flags.length > 0 ? ` ${flags.join(' ')}` : '';
        return `cd ${cwd} && CODEX_HOME=${(0, quote_js_1.quotePath)(codexHome)} codex${flagsStr}`;
    },
    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 (`codex exec -`).
     *
     * Codex `exec` accepts the prompt either as a positional arg or, when `-`
     * is passed (or no positional), on stdin. We always pipe via stdin so big
     * diff reviews (chorus self-review on a 100KB+ PR diff) don't hit the OS
     * argv ceiling — a single large argv string crosses ARG_MAX on some shells
     * and triggers truncation/silent failure modes upstream.
     *
     * Sandbox flags forwarded same as the tmux path. Codex's `exec` honors
     * `-c sandbox_mode=...` and `-c sandbox_workspace_write.network_access=...`
     * the same way as the interactive command.
     */
    runHeadless(opts) {
        (0, quote_js_1.validateValue)('accountId', opts.accountId);
        (0, quote_js_1.validateValue)('model', opts.model);
        const codexHome = ensureCodexHome(opts.accountId);
        (0, preflight_js_1.preTrustCodexWorkspace)(codexHome, opts.cwd);
        const args = buildHeadlessArgs(opts);
        const run = (0, headless_js_1.spawnHeadless)({
            command: 'codex',
            args,
            cwd: opts.cwd,
            env: { CODEX_HOME: codexHome },
            stdinPayload: opts.promptText,
            parseLine: index_js_1.parseCodex,
            onExit: (fullStdout, fullStderr, code) => (0, index_js_1.parseCodexExit)(fullStdout, fullStderr, code),
            cli: 'codex',
            timeoutMs: opts.timeoutMs,
            abortSignal: opts.abortSignal,
            heartbeat: true, // no streaming; heartbeat keeps UI alive
            // Cut codex's 8-minute internal-retry loop on auth failures by
            // matching the deterministic stderr signatures the moment they
            // surface. Without this, parseCodexExit only sees the error
            // after `codex` finally gives up — which is the multi-minute
            // wait we're trying to remove.
            earlyAbortStderrScan: codex_stderr_scan_js_1.scanCodexStderr,
        });
        return run.events;
    },
    estimateCostUsd() {
        // Codex uses subscription, not per-call API billing
        return 0;
    },
};
//# sourceMappingURL=codex.js.map