"use strict";
/**
 * Shell Executor Utilities
 *
 * Cross-platform shell execution utilities for CCS.
 */
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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __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.execClaude = exports.getWindowsEscapedCommandShell = exports.escapeShellArg = exports.getClaudeLaunchEnvOverrides = exports.stripCodexSessionEnv = exports.stripClaudeCodeEnv = exports.stripBrowserEnv = exports.stripAnthropicRoutingEnv = exports.stripAnthropicEnv = void 0;
const child_process_1 = require("child_process");
const path = __importStar(require("path"));
const error_manager_1 = require("./error-manager");
const websearch_manager_1 = require("./websearch-manager");
const signal_forwarder_1 = require("./signal-forwarder");
const claude_subcommand_detector_1 = require("./claude-subcommand-detector");
const shared_manager_1 = __importDefault(require("../management/shared-manager"));
const config_loader_facade_1 = require("../config/config-loader-facade");
/**
 * Strip ANTHROPIC_* env vars from an environment object.
 * Used for account/default profiles to prevent stale proxy config from
 * interfering with native Claude API routing.
 */
function stripAnthropicEnv(env) {
    const result = {};
    for (const key of Object.keys(env)) {
        if (!key.startsWith('ANTHROPIC_')) {
            result[key] = env[key];
        }
    }
    return result;
}
exports.stripAnthropicEnv = stripAnthropicEnv;
const ANTHROPIC_ROUTING_ENV_KEYS = [
    'ANTHROPIC_BASE_URL',
    'ANTHROPIC_AUTH_TOKEN',
    'ANTHROPIC_API_KEY',
];
const ANTHROPIC_ROUTING_ENV_KEY_SET = new Set(ANTHROPIC_ROUTING_ENV_KEYS);
const ANTHROPIC_MODEL_ENV_KEYS = [
    'ANTHROPIC_MODEL',
    'ANTHROPIC_DEFAULT_OPUS_MODEL',
    'ANTHROPIC_DEFAULT_SONNET_MODEL',
    'ANTHROPIC_DEFAULT_HAIKU_MODEL',
    'ANTHROPIC_SMALL_FAST_MODEL',
];
const TMUX_SYNC_ENV_KEYS = [
    'CLAUDE_CONFIG_DIR',
    'CCS_PROFILE_TYPE',
    'CCS_WEBSEARCH_SKIP',
    'CCS_STRIP_INHERITED_ANTHROPIC_ENV',
    'CLAUDE_CODE_MAX_OUTPUT_TOKENS',
    ...ANTHROPIC_MODEL_ENV_KEYS,
    ...ANTHROPIC_ROUTING_ENV_KEYS,
];
const DEFAULT_WINDOWS_CMD_SHELL = 'C:\\Windows\\System32\\cmd.exe';
/**
 * Strip inherited Anthropic routing/auth env while preserving model intent.
 * Used for nested settings-profile Claude launches where `--settings` already
 * defines the provider transport and the parent process should only lend model
 * defaults or effort hints.
 *
 * `preserveFrom`: if provided, routing keys present in this source survive the
 * strip (with their values from `preserveFrom`). Settings-type profiles use
 * this to keep routing/auth supplied by their own `settings.env` while
 * dropping any routing leaked from the parent shell or `global.env`.
 */
function stripAnthropicRoutingEnv(env, preserveFrom) {
    const result = {};
    for (const key of Object.keys(env)) {
        if (!ANTHROPIC_ROUTING_ENV_KEY_SET.has(key.toUpperCase())) {
            result[key] = env[key];
        }
    }
    if (preserveFrom) {
        for (const key of ANTHROPIC_ROUTING_ENV_KEYS) {
            if (Object.prototype.hasOwnProperty.call(preserveFrom, key) &&
                preserveFrom[key] !== undefined) {
                result[key] = preserveFrom[key];
            }
        }
    }
    return result;
}
exports.stripAnthropicRoutingEnv = stripAnthropicRoutingEnv;
function syncTmuxNestedSessionEnv(env, profileType) {
    if (!process.env.TMUX) {
        return;
    }
    const nestedSessionEnv = profileType === 'account' || profileType === 'default'
        ? stripAnthropicEnv(env)
        : profileType === 'settings'
            ? stripAnthropicRoutingEnv(env)
            : env;
    for (const key of TMUX_SYNC_ENV_KEYS) {
        try {
            const value = nestedSessionEnv[key];
            if (value !== undefined) {
                (0, child_process_1.spawnSync)('tmux', ['setenv', key, value], { stdio: 'ignore' });
            }
            else {
                (0, child_process_1.spawnSync)('tmux', ['setenv', '-u', key], { stdio: 'ignore' });
            }
        }
        catch {
            // tmux setenv can fail if not in a tmux session; safe to ignore
        }
    }
}
/**
 * Strip inherited browser attach/runtime env vars from a process environment.
 *
 * Browser capability is opt-in and launch-scoped. Stale CCS_BROWSER_* values
 * from the parent process must never bleed into a browser-off child launch.
 */
function stripBrowserEnv(env) {
    const result = {};
    for (const key of Object.keys(env)) {
        if (!key.toUpperCase().startsWith('CCS_BROWSER_')) {
            result[key] = env[key];
        }
    }
    return result;
}
exports.stripBrowserEnv = stripBrowserEnv;
/**
 * Strip Claude Code nested-session guard env var from a process environment.
 *
 * Note: Windows env keys are case-insensitive, so remove case-insensitively
 * to avoid missing variants like `claudecode`.
 */
function stripClaudeCodeEnv(env) {
    const result = {};
    for (const key of Object.keys(env)) {
        if (key.toUpperCase() !== 'CLAUDECODE') {
            result[key] = env[key];
        }
    }
    return result;
}
exports.stripClaudeCodeEnv = stripClaudeCodeEnv;
/**
 * Strip Codex session-scoped env vars before launching a nested Codex process.
 *
 * Keep real user config such as CODEX_HOME intact. Only remove the known
 * session/runtime metadata exported by the current Codex host process.
 */
function stripCodexSessionEnv(env) {
    const sessionKeys = new Set(['CODEX_CI', 'CODEX_MANAGED_BY_BUN', 'CODEX_THREAD_ID']);
    const result = {};
    for (const key of Object.keys(env)) {
        const upperKey = key.toUpperCase();
        if (sessionKeys.has(upperKey)) {
            continue;
        }
        result[key] = env[key];
    }
    return result;
}
exports.stripCodexSessionEnv = stripCodexSessionEnv;
/**
 * Resolve CCS-managed environment overrides for Claude launch.
 * - preferences.auto_update: false -> DISABLE_AUTOUPDATER=1
 */
function getClaudeLaunchEnvOverrides() {
    try {
        const config = (0, config_loader_facade_1.loadOrCreateUnifiedConfig)();
        if (config.preferences?.auto_update === false) {
            return { DISABLE_AUTOUPDATER: '1' };
        }
    }
    catch {
        // Config read errors should never block Claude launch.
    }
    return {};
}
exports.getClaudeLaunchEnvOverrides = getClaudeLaunchEnvOverrides;
/**
 * Escape arguments for shell execution (cross-platform)
 *
 * IMPORTANT: On Windows, spawn({ shell: true }) uses cmd.exe by default,
 * NOT PowerShell. cmd.exe does NOT recognize single quotes as string delimiters.
 * We must use double quotes for cmd.exe compatibility.
 */
function escapeShellArg(arg) {
    const isWindows = process.platform === 'win32';
    if (isWindows) {
        // cmd.exe: Use double quotes, escape inner double quotes by doubling them
        // cmd.exe interprets "" as escaped double quote inside quoted string
        // Strip newlines/tabs that can break cmd.exe parsing
        return ('"' +
            String(arg)
                .replace(/[\r\n\t]/g, ' ') // Replace newlines/tabs with space
                .replace(/%/g, '%%') // Escape percent signs
                .replace(/\^/g, '^^') // Escape carets
                .replace(/!/g, '^^!') // Escape exclamation marks (delayed expansion)
                .replace(/"/g, '""') + // Escape quotes
            '"');
    }
    else {
        // Unix/macOS: Double quotes with escaped inner quotes
        return '"' + String(arg).replace(/"/g, '\\"') + '"';
    }
}
exports.escapeShellArg = escapeShellArg;
/**
 * Return the shell that matches escapeShellArg() quoting semantics.
 *
 * On Windows, use an absolute, trusted system cmd.exe path instead of a bare
 * executable name so wrapper launches cannot be hijacked through the current
 * directory or PATH. ComSpec is accepted only when it resolves to that same
 * system shell.
 */
function getWindowsEscapedCommandShell() {
    if (process.platform !== 'win32') {
        return true;
    }
    const systemRoot = [process.env.SystemRoot, process.env.SYSTEMROOT, process.env.windir].find((candidate) => Boolean(candidate && path.win32.isAbsolute(candidate)));
    const trustedSystemCmd = systemRoot
        ? path.win32.normalize(path.win32.join(systemRoot, 'System32', 'cmd.exe'))
        : DEFAULT_WINDOWS_CMD_SHELL;
    const trustedSystemCmdLower = trustedSystemCmd.toLowerCase();
    for (const candidate of [process.env.ComSpec, process.env.COMSPEC]) {
        if (!candidate || !path.win32.isAbsolute(candidate)) {
            continue;
        }
        const normalizedCandidate = path.win32.normalize(candidate);
        if (normalizedCandidate.toLowerCase() === trustedSystemCmdLower) {
            return normalizedCandidate;
        }
    }
    return trustedSystemCmd;
}
exports.getWindowsEscapedCommandShell = getWindowsEscapedCommandShell;
/**
 * Execute Claude CLI with unified spawn logic
 */
function execClaude(claudeCli, args, envVars = null, onExitCleanup) {
    const isWindows = process.platform === 'win32';
    const isPowerShellScript = isWindows && /\.ps1$/i.test(claudeCli);
    const needsShell = isWindows && /\.(cmd|bat)$/i.test(claudeCli);
    // Get WebSearch hook config env vars
    const webSearchEnv = (0, websearch_manager_1.getWebSearchHookEnv)();
    const claudeLaunchEnv = getClaudeLaunchEnvOverrides();
    // Strip inherited ANTHROPIC_* when the launch should not reuse parent routing.
    // Account/default profiles need full isolation from prior proxy sessions.
    // Settings profiles can selectively strip only routing/auth when `--settings`
    // already carries the provider source of truth but the parent model intent
    // should still flow into nested Team/subagent launches.
    const profileType = envVars?.CCS_PROFILE_TYPE;
    const stripInheritedAnthropicEnv = profileType === 'account' || profileType === 'default';
    const stripInheritedAnthropicRoutingEnv = envVars?.CCS_STRIP_INHERITED_ANTHROPIC_ENV === '1';
    const inheritedEnv = stripInheritedAnthropicEnv
        ? stripAnthropicEnv(process.env)
        : stripInheritedAnthropicRoutingEnv
            ? stripAnthropicRoutingEnv(process.env)
            : process.env;
    const baseEnv = stripBrowserEnv(inheritedEnv);
    // Prepare environment (merge with base env if envVars provided)
    const mergedEnv = envVars
        ? { ...baseEnv, ...claudeLaunchEnv, ...envVars, ...webSearchEnv }
        : { ...baseEnv, ...claudeLaunchEnv, ...webSearchEnv };
    const effectiveMergedEnv = stripInheritedAnthropicRoutingEnv
        ? stripAnthropicRoutingEnv(mergedEnv, envVars ?? undefined)
        : mergedEnv;
    const effectiveArgs = (0, claude_subcommand_detector_1.isClaudeSubcommandInvocation)(args)
        ? (0, claude_subcommand_detector_1.stripClaudeSubcommandSessionArgs)(args)
        : args;
    // Strip Claude Code nested session guard env var to allow CCS delegation
    // (Claude Code v2.1.39+ sets CLAUDECODE to detect nested sessions)
    let env = (0, claude_subcommand_detector_1.stripClaudeCodeFeatureBlockingEnv)(stripClaudeCodeEnv(effectiveMergedEnv));
    // For Claude subcommand invocations (`agents`, `mcp`, `doctor`, ...) strip
    // telemetry-disable env vars that cause upstream Claude Code to fall back
    // to non-interactive list mode instead of opening the subcommand TUI.
    // Issue #1218.
    if ((0, claude_subcommand_detector_1.isClaudeSubcommandInvocation)(effectiveArgs)) {
        env = (0, claude_subcommand_detector_1.stripSubcommandBlockingEnv)(env);
    }
    if (profileType !== 'account') {
        try {
            new shared_manager_1.default().normalizeSharedPluginMetadataPathsLocked(env.CLAUDE_CONFIG_DIR);
        }
        catch {
            // Best-effort normalization should never block Claude launch.
        }
    }
    // Keep tmux teammate panes aligned with the nested-safe Claude runtime env
    // rather than the tmux server's original shell environment.
    syncTmuxNestedSessionEnv(env, profileType);
    let child;
    if (isPowerShellScript) {
        child = (0, child_process_1.spawn)('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', claudeCli, ...effectiveArgs], {
            stdio: 'inherit',
            windowsHide: true,
            env,
        });
    }
    else if (needsShell) {
        // When shell needed: concatenate into string to avoid DEP0190 warning
        const cmdString = [claudeCli, ...effectiveArgs].map(escapeShellArg).join(' ');
        child = (0, child_process_1.spawn)(cmdString, {
            stdio: 'inherit',
            windowsHide: true,
            shell: getWindowsEscapedCommandShell(),
            env,
        });
    }
    else {
        // When no shell needed: use array form (faster, no shell overhead)
        child = (0, child_process_1.spawn)(claudeCli, effectiveArgs, {
            stdio: 'inherit',
            windowsHide: true,
            env,
        });
    }
    let cleanedUp = false;
    const runExitCleanup = () => {
        if (cleanedUp) {
            return;
        }
        cleanedUp = true;
        onExitCleanup?.();
    };
    child.once('exit', runExitCleanup);
    child.once('error', runExitCleanup);
    (0, signal_forwarder_1.wireChildProcessSignals)(child, async (err) => {
        if (err.code === 'EACCES') {
            console.error(`[X] Claude CLI is not executable: ${claudeCli}`);
            console.error('    Check file permissions and executable bit.');
        }
        else if (err.code === 'ENOENT') {
            if (isPowerShellScript) {
                console.error('[X] PowerShell executable not found (required for .ps1 wrapper launch).');
                console.error('    Ensure powershell.exe is available in PATH.');
            }
            else if (needsShell) {
                console.error('[X] Windows command shell not found for Claude wrapper launch.');
                console.error('    Ensure cmd.exe is available and accessible.');
            }
            else {
                await error_manager_1.ErrorManager.showClaudeNotFound();
            }
        }
        else {
            console.error(`[X] Failed to start Claude CLI (${claudeCli}): ${err.message}`);
        }
        process.exit(1);
    });
}
exports.execClaude = execClaude;
//# sourceMappingURL=shell-executor.js.map