"use strict";
// docker_exec — run code inside a sandboxed Docker container.
//
// This tool gives the agent a way to execute untrusted code without exposing
// the host filesystem. The container runs with:
//   - --rm                 : removed after the run
//   - --network=none       : no network access
//   - --read-only          : root filesystem is read-only (use /tmp/work for writes)
//   - --memory + --cpus    : hard caps
//   - timeout              : the host kills the container if it overruns
//
// The tool is disabled by default. Enable it from settings or the registry
// configuration; the runtime additionally requires `docker` on the PATH.
//
// Tests mock the spawn function, so this module is fully covered without
// requiring a real Docker installation.
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.createDockerExecTool = createDockerExecTool;
const node_child_process_1 = require("node:child_process");
const fs = __importStar(require("fs/promises"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const DEFAULT_IMAGES = {
    python: 'python:3.12-slim',
    node: 'node:20-slim',
    bash: 'alpine:3.19',
    ruby: 'ruby:3.3-slim',
};
const COMMAND_BUILDERS = {
    python: (scriptPath) => ['python', scriptPath],
    node: (scriptPath) => ['node', scriptPath],
    bash: (scriptPath) => ['sh', scriptPath],
    ruby: (scriptPath) => ['ruby', scriptPath],
};
const DEFAULT_TIMEOUT_MS = 30_000;
const DEFAULT_MEMORY = '256m';
const DEFAULT_CPUS = 0.5;
function createDockerExecTool(options = {}) {
    const images = { ...DEFAULT_IMAGES, ...(options.images ?? {}) };
    const memoryLimit = options.memoryLimit ?? DEFAULT_MEMORY;
    const cpuLimit = options.cpuLimit ?? DEFAULT_CPUS;
    const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
    const spawnFn = options.spawn ?? defaultSpawnFn;
    return {
        name: 'docker_exec',
        description: 'Execute a snippet of code inside a sandboxed Docker container. Supports python, node, bash, ruby. The container has no network, a read-only root filesystem, hard memory and cpu caps, and is removed after the run.',
        parameters: {
            type: 'object',
            properties: {
                language: { type: 'string', description: 'One of: python, node, bash, ruby' },
                code: { type: 'string', description: 'Source code to run' },
                timeout_ms: { type: 'number', description: 'Optional timeout override in milliseconds (max 5 minutes)' },
            },
            required: ['language', 'code'],
        },
        isReadOnly: false,
        riskLevel: 'high',
        permissionCategory: 'shell',
        async execute(input) {
            const language = typeof input.language === 'string' ? input.language.toLowerCase() : '';
            const code = typeof input.code === 'string' ? input.code : '';
            const explicitTimeout = typeof input.timeout_ms === 'number' ? input.timeout_ms : undefined;
            if (!language || !images[language]) {
                const supported = Object.keys(images).join(', ');
                return fail(`Unknown language "${language}". Supported: ${supported}.`);
            }
            if (!code.trim())
                return fail('code is required');
            const effectiveTimeout = clamp(explicitTimeout ?? timeoutMs, 100, 5 * 60_000);
            const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-docker-'));
            const scriptName = scriptFileName(language);
            const scriptPath = path.join(tempDir, scriptName);
            await fs.writeFile(scriptPath, code, 'utf-8');
            try {
                const command = (COMMAND_BUILDERS[language] ?? COMMAND_BUILDERS.bash)(`/work/${scriptName}`);
                const args = [
                    'run', '--rm',
                    '--network=none',
                    '--read-only',
                    '--tmpfs', '/tmp:rw,size=64m',
                    '--memory', memoryLimit,
                    '--cpus', String(cpuLimit),
                    '-v', `${tempDir}:/work:ro`,
                    '-w', '/work',
                    images[language],
                    ...command,
                ];
                const result = await spawnFn('docker', args, { timeoutMs: effectiveTimeout });
                if (result.timedOut) {
                    return fail(`docker_exec timed out after ${effectiveTimeout}ms`);
                }
                const success = result.exitCode === 0;
                const output = formatOutput(result);
                return { success, output, error: success ? undefined : `Exited with code ${result.exitCode ?? 'null'}` };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return fail(`docker_exec failed: ${message}`);
            }
            finally {
                await fs.rm(tempDir, { recursive: true, force: true }).catch(() => { });
            }
        },
    };
}
function fail(message) {
    return { success: false, output: message, error: message };
}
function clamp(value, min, max) {
    if (!Number.isFinite(value))
        return min;
    return Math.max(min, Math.min(max, Math.floor(value)));
}
function scriptFileName(language) {
    switch (language) {
        case 'python': return 'main.py';
        case 'node': return 'main.js';
        case 'ruby': return 'main.rb';
        case 'bash': return 'main.sh';
        default: return 'main.txt';
    }
}
function formatOutput({ exitCode, stdout, stderr }) {
    const parts = [];
    parts.push(`exit_code: ${exitCode ?? 'null'}`);
    if (stdout.trim())
        parts.push(`stdout:\n${stdout.trimEnd()}`);
    if (stderr.trim())
        parts.push(`stderr:\n${stderr.trimEnd()}`);
    return parts.join('\n');
}
const defaultSpawnFn = (command, args, options) => new Promise((resolve) => {
    const child = (0, node_child_process_1.spawn)(command, args, { shell: false });
    let stdout = '';
    let stderr = '';
    let timedOut = false;
    const timer = setTimeout(() => {
        timedOut = true;
        try {
            child.kill('SIGKILL');
        }
        catch { /* best-effort */ }
    }, options.timeoutMs);
    child.stdout?.on('data', (chunk) => { stdout += chunk.toString('utf-8'); });
    child.stderr?.on('data', (chunk) => { stderr += chunk.toString('utf-8'); });
    child.on('error', (error) => {
        clearTimeout(timer);
        stderr += '\n' + (error instanceof Error ? error.message : String(error));
        resolve({ exitCode: 1, stdout, stderr, timedOut });
    });
    child.on('close', (code) => {
        clearTimeout(timer);
        resolve({ exitCode: code, stdout, stderr, timedOut });
    });
});
//# sourceMappingURL=dockerExecTool.js.map