"use strict";
// =============================================================================
// Pure Agent - Terminal Tool
// Ported from Hermes tools/terminal_tool.py (local execution only)
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanupAllBackground = cleanupAllBackground;
const child_process_1 = require("child_process");
const tool_registry_1 = require("../tool-registry");
const result_types_1 = require("../result-types");
const logger_1 = require("../logger");
const utils_1 = require("../utils");
const constants_1 = require("../constants");
const _backgroundProcesses = new Map();
let _bgCounter = 0;
// ---------------------------------------------------------------------------
// Execute foreground command
// ---------------------------------------------------------------------------
async function executeForeground(command, timeout, cwd, signal) {
    return new Promise((resolve, reject) => {
        const shell = (0, constants_1.getDefaultShell)();
        const shellArgs = (0, constants_1.isWindows)() ? ['/c', command] : ['-c', command];
        const proc = (0, child_process_1.spawn)(shell, shellArgs, {
            cwd,
            env: { ...process.env },
            timeout,
            windowsHide: true,
            stdio: ['pipe', 'pipe', 'pipe'],
        });
        let stdout = '';
        let stderr = '';
        proc.stdout?.on('data', (data) => {
            stdout += data.toString('utf-8');
            // Truncate if too large
            if (stdout.length > constants_1.MAX_OUTPUT_CHARS) {
                stdout = (0, utils_1.truncate)(stdout, constants_1.MAX_OUTPUT_CHARS).text;
                proc.kill();
            }
        });
        proc.stderr?.on('data', (data) => {
            stderr += data.toString('utf-8');
            if (stderr.length > constants_1.MAX_OUTPUT_CHARS) {
                stderr = (0, utils_1.truncate)(stderr, constants_1.MAX_OUTPUT_CHARS).text;
            }
        });
        // Handle abort signal
        const onAbort = () => {
            proc.kill((0, constants_1.isWindows)() ? 'SIGTERM' : 'SIGTERM');
            setTimeout(() => {
                if (!proc.killed)
                    proc.kill('SIGKILL');
            }, 3000);
        };
        signal.addEventListener('abort', onAbort, { once: true });
        proc.on('close', (code) => {
            signal.removeEventListener('abort', onAbort);
            resolve({
                stdout: stdout.trim(),
                stderr: stderr.trim(),
                exitCode: code ?? -1,
            });
        });
        proc.on('error', (err) => {
            signal.removeEventListener('abort', onAbort);
            if (signal.aborted) {
                resolve({ stdout: stdout.trim(), stderr: 'Command interrupted by user', exitCode: -1 });
            }
            else {
                reject(err);
            }
        });
    });
}
// ---------------------------------------------------------------------------
// Execute background command
// ---------------------------------------------------------------------------
function executeBackground(command, cwd) {
    _bgCounter++;
    const jobId = `bg_${_bgCounter}`;
    const shell = (0, constants_1.getDefaultShell)();
    const shellArgs = (0, constants_1.isWindows)() ? ['/c', command] : ['-c', command];
    const proc = (0, child_process_1.spawn)(shell, shellArgs, {
        cwd,
        env: { ...process.env },
        detached: true,
        windowsHide: true,
        stdio: ['ignore', 'pipe', 'pipe'],
    });
    const bgProc = {
        process: proc,
        stdout: '',
        stderr: '',
        exitCode: null,
        finished: false,
        startTime: Date.now(),
    };
    proc.stdout?.on('data', (data) => {
        bgProc.stdout += data.toString('utf-8');
        if (bgProc.stdout.length > constants_1.MAX_OUTPUT_CHARS) {
            bgProc.stdout = (0, utils_1.truncate)(bgProc.stdout, constants_1.MAX_OUTPUT_CHARS).text;
        }
    });
    proc.stderr?.on('data', (data) => {
        bgProc.stderr += data.toString('utf-8');
        if (bgProc.stderr.length > constants_1.MAX_OUTPUT_CHARS) {
            bgProc.stderr = (0, utils_1.truncate)(bgProc.stderr, constants_1.MAX_OUTPUT_CHARS).text;
        }
    });
    proc.on('close', (code) => {
        bgProc.exitCode = code ?? -1;
        bgProc.finished = true;
        (0, logger_1.getLogger)().info(`Background process ${jobId} finished (exit ${code})`);
    });
    proc.on('error', (err) => {
        bgProc.finished = true;
        bgProc.exitCode = -1;
        bgProc.stderr += `\nProcess error: ${err.message}`;
    });
    _backgroundProcesses.set(jobId, bgProc);
    // Unref so it doesn't keep the Node.js process alive
    proc.unref();
    return {
        jobId,
        message: `Background process started: ${jobId}\nCommand: ${command}\nPID: ${proc.pid}`,
    };
}
// ---------------------------------------------------------------------------
// Check background job status
// ---------------------------------------------------------------------------
function checkBackgroundJob(jobId) {
    const bg = _backgroundProcesses.get(jobId);
    if (!bg)
        return null;
    return {
        jobId,
        finished: bg.finished,
        exitCode: bg.exitCode,
        stdout: bg.stdout,
        stderr: bg.stderr,
        elapsedMs: Date.now() - bg.startTime,
    };
}
// ---------------------------------------------------------------------------
// Terminal tool handler
// ---------------------------------------------------------------------------
async function terminalHandler(args, context) {
    const { command, background = false, timeout, job_id, } = args;
    // Check job status
    if (job_id) {
        const status = checkBackgroundJob(job_id);
        if (!status)
            return (0, result_types_1.toolError)(`Unknown background job: ${job_id}`);
        return (0, result_types_1.toolResult)(status);
    }
    if (!command)
        return (0, result_types_1.toolError)('Missing required parameter: command');
    const effectiveCwd = args.workdir || context.workingDir;
    const effectiveTimeout = Math.min(timeout ? timeout * 1000 : constants_1.FOREGROUND_MAX_TIMEOUT_MS, constants_1.FOREGROUND_MAX_TIMEOUT_MS);
    (0, logger_1.getLogger)().info(`Terminal: ${command} (cwd: ${effectiveCwd}, bg: ${background})`);
    // Background execution
    if (background) {
        const result = executeBackground(command, effectiveCwd);
        return (0, result_types_1.toolResult)(result);
    }
    // Foreground execution
    try {
        const result = await executeForeground(command, effectiveTimeout, effectiveCwd, context.signal);
        const output = {
            stdout: result.stdout,
            stderr: result.stderr,
            exit_code: result.exitCode,
        };
        // Add human-readable exit code hints
        if (result.exitCode !== 0) {
            output.hint = getExitCodeHint(command, result.exitCode);
        }
        // Limit output size
        const outputStr = JSON.stringify(output);
        if (outputStr.length > constants_1.MAX_OUTPUT_CHARS) {
            const { stdout, stderr, exit_code } = output;
            return (0, result_types_1.toolResult)({
                stdout: (0, utils_1.truncate)(stdout, Math.floor(constants_1.MAX_OUTPUT_CHARS * 0.7)).text,
                stderr: (0, utils_1.truncate)(stderr, Math.floor(constants_1.MAX_OUTPUT_CHARS * 0.2)).text,
                exit_code,
                _truncated: true,
            });
        }
        return (0, result_types_1.toolResult)(output);
    }
    catch (err) {
        if (context.signal.aborted) {
            return (0, result_types_1.toolResult)({
                stdout: '',
                stderr: 'Command interrupted by user',
                exit_code: -1,
                interrupted: true,
            });
        }
        return (0, result_types_1.toolError)(`Terminal execution failed: ${err.message}`);
    }
}
// ---------------------------------------------------------------------------
// Exit code hints
// ---------------------------------------------------------------------------
function getExitCodeHint(command, exitCode) {
    const cmdBase = command.trim().split(/\s+/)[0];
    if (exitCode === 127)
        return `Command not found: ${cmdBase}`;
    if (exitCode === 126)
        return `Permission denied or not executable: ${cmdBase}`;
    if (exitCode === 130)
        return 'Process terminated by SIGINT (Ctrl+C)';
    if (exitCode === 137)
        return 'Process killed (SIGKILL, possibly OOM)';
    if (exitCode === 139)
        return 'Segmentation fault';
    // Common commands
    if (cmdBase === 'grep' && exitCode === 1)
        return 'grep: no matches found';
    if (cmdBase === 'diff' && exitCode === 1)
        return 'diff: files differ';
    if (cmdBase === 'git' && exitCode === 128)
        return 'git: not in a git repository or other fatal error';
    if ((cmdBase === 'find' || cmdBase === 'rg') && exitCode === 1)
        return `${cmdBase}: some errors occurred during search`;
    return `Process exited with code ${exitCode}`;
}
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
function cleanupAllBackground() {
    let killed = 0;
    for (const [jobId, bg] of _backgroundProcesses) {
        if (!bg.finished) {
            bg.process.kill('SIGTERM');
            killed++;
        }
        _backgroundProcesses.delete(jobId);
    }
    return killed;
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------
tool_registry_1.registry.register({
    name: 'terminal',
    toolset: 'terminal',
    schema: {
        description: 'Execute shell commands in the terminal. Supports foreground (blocking) and background (non-blocking) execution.',
        parameters: {
            type: 'object',
            properties: {
                command: {
                    type: 'string',
                    description: 'The shell command to execute',
                },
                background: {
                    type: 'boolean',
                    description: 'Run in background (non-blocking). Default: false',
                    default: false,
                },
                timeout: {
                    type: 'number',
                    description: 'Timeout in seconds (max 300). Default: 300',
                },
                workdir: {
                    type: 'string',
                    description: 'Working directory for command execution',
                },
                job_id: {
                    type: 'string',
                    description: 'Check status of a background job by its ID',
                },
            },
            required: [],
        },
    },
    handler: terminalHandler,
    isAsync: true,
    description: 'Execute shell commands',
    emoji: '*',
    maxResultSizeChars: constants_1.MAX_OUTPUT_CHARS,
});
//# sourceMappingURL=terminal.js.map