"use strict";
// =============================================================================
// Pure Agent - Terminal Environments
// Ported from Hermes tools/environments/
// Docker and SSH backends for terminal tool
// =============================================================================
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.SSHEnvironment = exports.DockerEnvironment = void 0;
exports.createEnvironment = createEnvironment;
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const crypto = __importStar(require("crypto"));
const logger_1 = require("../logger");
class DockerEnvironment {
    type = 'docker';
    _containerId = null;
    _cwd;
    _timeout;
    _image;
    _options;
    constructor(options) {
        this._options = options;
        this._image = options.image;
        this._cwd = options.cwd || '/root';
        this._timeout = options.timeout || 60;
    }
    get cwd() { return this._cwd; }
    async init() {
        // Check Docker availability
        try {
            (0, child_process_1.execSync)('docker --version', { timeout: 5000, windowsHide: true });
        }
        catch {
            throw new Error('Docker not found. Install Docker or ensure it is in PATH.');
        }
        // Build docker run args
        const args = ['run', '-d', '--init'];
        // Security
        args.push('--cap-drop', 'ALL');
        args.push('--security-opt', 'no-new-privileges');
        args.push('--pids-limit', '256');
        // Resource limits
        if (this._options.cpu)
            args.push('--cpus', String(this._options.cpu));
        if (this._options.memory)
            args.push('--memory', this._options.memory);
        // Network
        if (this._options.network === false)
            args.push('--network', 'none');
        // Volumes
        if (this._options.volumes) {
            for (const vol of this._options.volumes) {
                args.push('-v', vol);
            }
        }
        // Auto-mount CWD
        if (this._options.autoMountCwd) {
            args.push('-v', `${process.cwd()}:/workspace`);
            this._cwd = '/workspace';
        }
        // Environment variables
        if (this._options.env) {
            for (const [k, v] of Object.entries(this._options.env)) {
                args.push('-e', `${k}=${v}`);
            }
        }
        // Labels for cleanup
        args.push('--label', 'pure-agent=1');
        if (this._options.taskId) {
            args.push('--label', `pure-agent-task=${this._options.taskId}`);
        }
        // Image and default command
        args.push(this._image, 'sleep', 'infinity');
        const result = (0, child_process_1.execSync)(`docker ${args.join(' ')}`, {
            encoding: 'utf-8',
            timeout: 30000,
            windowsHide: true,
        }).trim();
        this._containerId = result;
        (0, logger_1.getLogger)().info(`Docker container started: ${this._containerId.slice(0, 12)}`);
    }
    async execute(command, options) {
        if (!this._containerId)
            throw new Error('Docker environment not initialized');
        const timeout = (options?.timeout || this._timeout) * 1000;
        const cwd = options?.cwd || this._cwd;
        return new Promise((resolve) => {
            const proc = (0, child_process_1.spawn)('docker', [
                'exec',
                '-i',
                '--workdir', cwd,
                this._containerId,
                'bash', '-c', command,
            ], {
                timeout,
                windowsHide: true,
                stdio: ['ignore', 'pipe', 'pipe'],
            });
            let stdout = '';
            let stderr = '';
            proc.stdout?.on('data', (data) => {
                stdout += data.toString('utf-8');
                if (stdout.length > 200000)
                    proc.kill();
            });
            proc.stderr?.on('data', (data) => {
                stderr += data.toString('utf-8');
                if (stderr.length > 50000)
                    proc.kill();
            });
            proc.on('close', (code) => {
                resolve({
                    output: (stdout + (stderr ? '\n[stderr] ' + stderr : '')).trim(),
                    exitCode: code ?? -1,
                });
            });
            proc.on('error', (err) => {
                resolve({ output: `Docker exec error: ${err.message}`, exitCode: -1 });
            });
        });
    }
    cleanup() {
        if (this._containerId) {
            try {
                (0, child_process_1.execSync)(`docker stop ${this._containerId} && docker rm ${this._containerId}`, {
                    timeout: 15000,
                    windowsHide: true,
                    stdio: 'ignore',
                });
                (0, logger_1.getLogger)().info(`Docker container removed: ${this._containerId.slice(0, 12)}`);
            }
            catch {
                // Best effort cleanup
            }
            this._containerId = null;
        }
    }
}
exports.DockerEnvironment = DockerEnvironment;
class SSHEnvironment {
    type = 'ssh';
    _host;
    _user;
    _port;
    _keyPath;
    _cwd;
    _timeout;
    _controlPath;
    constructor(options) {
        this._host = options.host;
        this._user = options.user;
        this._port = options.port || 22;
        this._keyPath = options.keyPath || '';
        this._cwd = options.cwd || '~';
        this._timeout = options.timeout || 60;
        // Control socket path for connection reuse
        const hash = crypto.createHash('sha256')
            .update(`${this._user}@${this._host}:${this._port}`)
            .digest('hex')
            .slice(0, 16);
        this._controlPath = path.join(os.tmpdir(), `pa-ssh-${hash}`);
    }
    get cwd() { return this._cwd; }
    async init() {
        // Check SSH availability
        try {
            (0, child_process_1.execSync)('ssh -V', { timeout: 5000, windowsHide: true, stdio: 'ignore' });
        }
        catch {
            throw new Error('SSH not found. Install OpenSSH client.');
        }
        // Test connection
        try {
            const result = this._sshExec('echo OK', { timeout: 10 });
            if (!result.includes('OK'))
                throw new Error('Unexpected response');
        }
        catch (err) {
            throw new Error(`SSH connection failed: ${err.message}`);
        }
        // Detect remote home
        try {
            this._cwd = this._sshExec('echo $HOME').trim() || '~';
        }
        catch { /* keep default */ }
        (0, logger_1.getLogger)().info(`SSH connected: ${this._user}@${this._host}:${this._port}`);
    }
    async execute(command, options) {
        const timeout = options?.timeout || this._timeout;
        const cwd = options?.cwd || this._cwd;
        const wrappedCmd = `cd ${this._quoteShell(cwd)} && ${command}`;
        return new Promise((resolve) => {
            const sshArgs = this._buildSSHArgs(wrappedCmd);
            const proc = (0, child_process_1.spawn)('ssh', sshArgs, {
                timeout: timeout * 1000,
                windowsHide: true,
                stdio: ['ignore', 'pipe', 'pipe'],
            });
            let stdout = '';
            let stderr = '';
            proc.stdout?.on('data', (data) => {
                stdout += data.toString('utf-8');
                if (stdout.length > 200000)
                    proc.kill();
            });
            proc.stderr?.on('data', (data) => {
                stderr += data.toString('utf-8');
            });
            proc.on('close', (code) => {
                resolve({
                    output: (stdout + (stderr ? '\n[stderr] ' + stderr : '')).trim(),
                    exitCode: code ?? -1,
                });
            });
            proc.on('error', (err) => {
                resolve({ output: `SSH error: ${err.message}`, exitCode: -1 });
            });
        });
    }
    cleanup() {
        // Close control master
        try {
            (0, child_process_1.execSync)(`ssh -O exit -o ControlPath="${this._controlPath}" ${this._host}`, {
                timeout: 5000,
                windowsHide: true,
                stdio: 'ignore',
            });
        }
        catch { /* ignore */ }
        // Remove socket file
        try {
            fs.unlinkSync(this._controlPath);
        }
        catch { /* ignore */ }
        (0, logger_1.getLogger)().info(`SSH disconnected: ${this._user}@${this._host}`);
    }
    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------
    _buildSSHArgs(command) {
        const args = [
            '-o', 'ControlMaster=auto',
            '-o', `ControlPath="${this._controlPath}"`,
            '-o', 'ControlPersist=300',
            '-o', 'BatchMode=yes',
            '-o', 'StrictHostKeyChecking=accept-new',
            '-o', 'ConnectTimeout=10',
        ];
        if (this._port !== 22)
            args.push('-p', String(this._port));
        if (this._keyPath)
            args.push('-i', this._keyPath);
        args.push(`${this._user}@${this._host}`, command);
        return args;
    }
    _sshExec(command, options) {
        const args = this._buildSSHArgs(command);
        try {
            return (0, child_process_1.execSync)(`ssh ${args.join(' ')}`, {
                encoding: 'utf-8',
                timeout: (options?.timeout || 30) * 1000,
                windowsHide: true,
            });
        }
        catch (err) {
            throw new Error(`SSH exec failed: ${err.stderr || err.message}`);
        }
    }
    _quoteShell(s) {
        return `'${s.replace(/'/g, "'\\''")}'`;
    }
}
exports.SSHEnvironment = SSHEnvironment;
// ---------------------------------------------------------------------------
// Environment factory
// ---------------------------------------------------------------------------
function createEnvironment(type, options) {
    switch (type) {
        case 'docker':
            return new DockerEnvironment(options);
        case 'ssh':
            return new SSHEnvironment(options);
        case 'local':
            // Local uses the existing terminal tool directly
            return {
                type: 'local',
                cwd: options.cwd || process.cwd(),
                async execute(command, opts) {
                    // Delegate to the existing terminal tool
                    return { output: 'Use terminal tool directly for local execution', exitCode: 0 };
                },
                cleanup() { },
            };
        default:
            throw new Error(`Unknown environment type: ${type}`);
    }
}
//# sourceMappingURL=environments.js.map