"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BashExecutionService = exports.DisposableProcess = void 0;
const child_process_1 = require("child_process");
const log_1 = require("./log");
/**
 * Wraps a ChildProcess to make it disposable for use with `using` statements.
 * Always kills the entire process group with SIGKILL to prevent zombie processes.
 * SIGKILL cannot be caught or ignored, guaranteeing immediate cleanup.
 */
class DisposableProcess {
    process;
    disposed = false;
    constructor(process) {
        this.process = process;
    }
    [Symbol.dispose]() {
        // Prevent double-signalling if dispose is called multiple times
        if (this.disposed || this.process.pid === undefined) {
            return;
        }
        this.disposed = true;
        try {
            // Kill entire process group with SIGKILL - cannot be caught/ignored
            process.kill(-this.process.pid, "SIGKILL");
        }
        catch {
            // Fallback: try killing just the main process
            try {
                this.process.kill("SIGKILL");
            }
            catch {
                // Process already dead - ignore
            }
        }
    }
    get child() {
        return this.process;
    }
}
exports.DisposableProcess = DisposableProcess;
/**
 * Centralized bash execution service.
 *
 * All workspace command execution goes through this service to:
 * - Maintain consistent environment setup across all bash execution
 * - Provide single abstraction point for future host migration (containers, remote, etc.)
 * - Eliminate duplication between init hooks and bash tool
 *
 * Provides two execution modes:
 * - Streaming: Line-by-line output callbacks (for init hooks, real-time feedback)
 * - Buffered: Collect all output, return at end (for bash tool, LLM consumption)
 */
class BashExecutionService {
    /**
     * Create standardized bash environment.
     * Prevents interactive prompts that would block execution.
     */
    createBashEnvironment(secrets) {
        return {
            ...process.env,
            // Inject secrets as environment variables
            ...(secrets ?? {}),
            // Prevent interactive editors from blocking bash execution
            // Critical for git operations like rebase/commit that try to open editors
            GIT_EDITOR: "true", // Git-specific editor (highest priority)
            GIT_SEQUENCE_EDITOR: "true", // For interactive rebase sequences
            EDITOR: "true", // General fallback for non-git commands
            VISUAL: "true", // Another common editor environment variable
            // Prevent git from prompting for credentials
            // Critical for operations like fetch/pull that might try to authenticate
            // Without this, git can hang waiting for user input if credentials aren't configured
            GIT_TERMINAL_PROMPT: "0", // Disables git credential prompts
        };
    }
    /**
     * Execute bash command with streaming output.
     *
     * Output is emitted line-by-line through callbacks as it arrives.
     * Used by init hooks for real-time progress feedback.
     *
     * @param script Bash script to execute
     * @param config Execution configuration
     * @param callbacks Output and exit callbacks
     * @returns DisposableProcess that can be killed with `using` statement
     */
    executeStreaming(script, config, callbacks) {
        log_1.log.debug(`BashExecutionService: Executing streaming command in ${config.cwd}`);
        log_1.log.debug(`BashExecutionService: Script: ${script.substring(0, 100)}${script.length > 100 ? "..." : ""}`);
        const spawnCommand = config.niceness !== undefined ? "nice" : "bash";
        const spawnArgs = config.niceness !== undefined
            ? ["-n", config.niceness.toString(), "bash", "-c", script]
            : ["-c", script];
        const child = (0, child_process_1.spawn)(spawnCommand, spawnArgs, {
            cwd: config.cwd,
            env: this.createBashEnvironment(config.secrets),
            stdio: ["ignore", "pipe", "pipe"],
            // Spawn as detached process group leader to prevent zombie processes
            // When bash spawns background processes, detached:true allows killing
            // the entire group via process.kill(-pid)
            detached: config.detached ?? true,
        });
        log_1.log.debug(`BashExecutionService: Spawned process with PID ${child.pid ?? "unknown"}`);
        // Line-by-line streaming with incremental buffers
        let outBuf = "";
        let errBuf = "";
        const flushLines = (buf, isStderr) => {
            const lines = buf.split(/\r?\n/);
            // Keep the last partial line in buffer; emit full lines
            const partial = lines.pop() ?? "";
            for (const line of lines) {
                if (line.length === 0)
                    continue;
                if (isStderr) {
                    callbacks.onStderr(line);
                }
                else {
                    callbacks.onStdout(line);
                }
            }
            return partial;
        };
        child.stdout?.on("data", (chunk) => {
            outBuf += chunk.toString("utf8");
            outBuf = flushLines(outBuf, false);
        });
        child.stderr?.on("data", (chunk) => {
            errBuf += chunk.toString("utf8");
            errBuf = flushLines(errBuf, true);
        });
        child.on("close", (code) => {
            log_1.log.debug(`BashExecutionService: Process exited with code ${code ?? "unknown"}`);
            // Flush any remaining partial lines
            if (outBuf.trim().length > 0) {
                callbacks.onStdout(outBuf);
            }
            if (errBuf.trim().length > 0) {
                callbacks.onStderr(errBuf);
            }
            callbacks.onExit(code ?? 0);
        });
        child.on("error", (error) => {
            log_1.log.error(`BashExecutionService: Process error:`, error);
        });
        return new DisposableProcess(child);
    }
}
exports.BashExecutionService = BashExecutionService;
//# sourceMappingURL=bashExecutionService.js.map