"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.execAsync = void 0;
exports.execCommand = execCommand;
const child_process_1 = require("child_process");
const util_1 = require("util");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const services_1 = require("../../services");
exports.execAsync = (0, util_1.promisify)(child_process_1.exec);
const PROCESSES_DIR = path.join(process.cwd(), ".knowhow", "processes");
fs.mkdirSync(PROCESSES_DIR, { recursive: true });
const STARTED_PIDS = new Set();
setupProcessCleanup();
function stripTrailingAmp(cmd) {
    const t = cmd.trim();
    return t.endsWith("&") ? t.replace(/&\s*$/, "").trim() : t;
}
function commandNameFrom(cmd) {
    const cleaned = stripTrailingAmp(cmd);
    const first = cleaned.split(/\s+/)[0] || "command";
    return first.replace(/[^\w.-]+/g, "_");
}
function makeLogPath(cmd, customFileName) {
    let baseName = customFileName
        ? customFileName.replace(/[^\w.-]+/g, "_")
        : commandNameFrom(cmd);
    let logPath = path.join(PROCESSES_DIR, `${baseName}.txt`);
    if (fs.existsSync(logPath)) {
        const epochSeconds = Math.floor(Date.now() / 1000);
        baseName = `${baseName}_${epochSeconds}`;
        logPath = path.join(PROCESSES_DIR, `${baseName}.txt`);
    }
    return logPath;
}
function setupProcessCleanup() {
    const killAll = () => {
        for (const pid of STARTED_PIDS) {
            try {
                if (os.platform() === "win32") {
                    (0, child_process_1.spawn)("taskkill", ["/PID", String(pid), "/T", "/F"], {
                        stdio: "ignore",
                        detached: true,
                    }).unref();
                }
                else {
                    try {
                        process.kill(-pid, "SIGTERM");
                    }
                    catch {
                        try {
                            process.kill(pid, "SIGTERM");
                        }
                        catch { }
                    }
                }
            }
            catch { }
        }
    };
    process.once("exit", killAll);
    process.once("SIGINT", () => {
        killAll();
        process.exit(130);
    });
    process.once("SIGTERM", () => {
        killAll();
        process.exit(143);
    });
    process.once("uncaughtException", (e) => {
        console.error(e);
        killAll();
        process.exit(1);
    });
    process.once("unhandledRejection", (e) => {
        console.error(e);
        killAll();
        process.exit(1);
    });
}
const execWithTimeout = async (command, opts = {}) => {
    const { timeout = 5000, continueInBackground = false, maxBuffer = 1024 * 1024 * 16, } = opts;
    const cleaned = stripTrailingAmp(command);
    const shouldBg = continueInBackground || command.trim().endsWith("&");
    if (timeout === -1 && !shouldBg) {
        return new Promise((resolve) => {
            let out = "";
            let err = "";
            const child = (0, child_process_1.spawn)(cleaned, { shell: true });
            child.stdout?.on("data", (d) => (out += d.toString()));
            child.stderr?.on("data", (d) => (err += d.toString()));
            child.once("error", (e) => resolve({
                stdout: out,
                stderr: err || String(e),
                timedOut: false,
                killed: false,
                pid: child.pid ?? undefined,
            }));
            child.once("exit", () => resolve({
                stdout: out,
                stderr: err,
                timedOut: false,
                killed: false,
                pid: child.pid ?? undefined,
            }));
        });
    }
    if (shouldBg) {
        const logPath = makeLogPath(cleaned, opts.logFileName);
        const fd = fs.openSync(logPath, "w");
        const child = (0, child_process_1.spawn)(cleaned, {
            shell: true,
            detached: true,
            stdio: ["ignore", fd, fd],
        });
        const pid = child.pid;
        const header = `PID: ${pid}\n` +
            `CMD: ${cleaned}\n` +
            `START: ${new Date().toISOString()}\n` +
            `---\n`;
        fs.writeSync(fd, header);
        fs.fsyncSync(fd);
        try {
            fs.closeSync(fd);
        }
        catch { }
        return await new Promise((resolve) => {
            let settled = false;
            const done = (res) => {
                if (settled)
                    return;
                settled = true;
                resolve({ ...res, pid, logPath });
            };
            child.once("error", (e) => done({
                stdout: "",
                stderr: `Failed to start command: ${String(e)}`,
                timedOut: false,
                killed: false,
            }));
            const tid = setTimeout(() => {
                try {
                    child.unref();
                }
                catch { }
                STARTED_PIDS.add(pid);
                done({
                    stdout: "",
                    stderr: `Command timed out after ${timeout}ms but continues in background\n` +
                        `Logs: ${logPath}\n` +
                        `Tip: read first line for PID; kill by PID for cleanup.\n`,
                    timedOut: true,
                    killed: false,
                });
            }, timeout);
            child.once("exit", () => {
                clearTimeout(tid);
                done({
                    stdout: "",
                    stderr: `Process finished before timeout. Logs: ${logPath}\n`,
                    timedOut: false,
                    killed: false,
                });
            });
        });
    }
    return new Promise((resolve) => {
        let out = "";
        let err = "";
        const child = (0, child_process_1.exec)(cleaned, { timeout, maxBuffer }, (error, stdout, stderr) => {
            out = out || stdout;
            err = err || stderr;
            if (error) {
                resolve({
                    stdout: out,
                    stderr: err || error.message,
                    timedOut: error.killed ?? false,
                    killed: !!error.killed,
                    pid: child.pid ?? undefined,
                });
            }
            else {
                resolve({
                    stdout: out,
                    stderr: err,
                    timedOut: false,
                    killed: false,
                    pid: child.pid ?? undefined,
                });
            }
        });
        child.stdout?.on("data", (d) => (out += d.toString()));
        child.stderr?.on("data", (d) => (err += d.toString()));
    });
};
async function execCommand(command, timeout, continueInBackground, logFileName) {
    if (!command || typeof command !== "string") {
        throw new Error("Invalid command. We received a non-string value. Please ensure you are sending strings of 4k tokens or less.");
    }
    const toolService = (this instanceof services_1.ToolsService ? this : (0, services_1.services)().Tools);
    const context = toolService.getContext();
    if (context.Events) {
        await context.Events.emitBlocking("exec:pre-run", {
            command,
            timeout,
            continueInBackground,
            logFileName,
        });
    }
    const { stdout, stderr, timedOut, killed, pid, logPath } = await execWithTimeout(command, {
        timeout,
        continueInBackground,
        logFileName,
    });
    let output = "";
    if (stderr)
        output += stderr + "\n";
    if (stdout)
        output += stdout;
    const statusMsg = timedOut
        ? killed
            ? " (killed due to timeout)"
            : ` (timed out, still running${pid ? `, pid=${pid}` : ""}${logPath ? `, logs=${logPath}` : ""})`
        : "";
    const lines = output.split("\n");
    const result = `$ ${command}${statusMsg}\n${output}`;
    let eventResults = [];
    if (context.Events) {
        eventResults = await context.Events.emitBlocking("exec:post-run", {
            command,
            timeout,
            continueInBackground,
            logFileName,
            stdout,
            stderr,
            timedOut,
            killed,
            pid,
            logPath,
            output,
        });
    }
    let eventResultsText = "";
    if (eventResults && eventResults.length > 0) {
        eventResultsText =
            "\n\nAdditional Information:\n" + JSON.stringify(eventResults, null, 2);
    }
    return result + eventResultsText;
}
;
//# sourceMappingURL=execCommand.js.map