import { evaluateExecPolicy } from "../sandbox/exec-policy.js";
import { spawn } from "child_process";
import { Type } from "@sinclair/typebox";
//#region src/agent/tools/shell.ts
const MAX_SHELL_TIMEOUT = 300;
const DEFAULT_MAX_BYTES = 50 * 1024;
const DEFAULT_MAX_LINES = 2e3;
const ShellSchema = Type.Object({ command: Type.String({ description: "Shell command to execute" }) });
function formatSize(bytes) {
	if (bytes < 1024) return `${bytes}B`;
	if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
	return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
}
function truncateTail(content, maxLines = DEFAULT_MAX_LINES, maxBytes = DEFAULT_MAX_BYTES) {
	const totalBytes = Buffer.byteLength(content, "utf-8");
	const lines = content.split("\n");
	const totalLines = lines.length;
	if (totalLines <= maxLines && totalBytes <= maxBytes) return {
		content,
		truncated: false,
		truncatedBy: null,
		totalLines,
		totalBytes,
		outputLines: totalLines,
		outputBytes: totalBytes
	};
	const outputLinesArr = [];
	let outputBytesCount = 0;
	let truncatedBy = "lines";
	for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
		const line = lines[i];
		const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0);
		if (outputBytesCount + lineBytes > maxBytes) {
			truncatedBy = "bytes";
			break;
		}
		outputLinesArr.unshift(line);
		outputBytesCount += lineBytes;
	}
	return {
		content: outputLinesArr.join("\n"),
		truncated: true,
		truncatedBy,
		totalLines,
		totalBytes,
		outputLines: outputLinesArr.length,
		outputBytes: outputBytesCount
	};
}
function createShellTool(cwd, options) {
	return {
		name: "shell",
		description: "Execute shell command.",
		parameters: ShellSchema,
		label: "💻 Shell",
		async execute(toolCallId, params, _signal) {
			const p = params;
			const passthroughNames = options?.getSkillPassthroughEnvVarNames?.() ?? [];
			const policy = evaluateExecPolicy({
				command: p.command,
				cwd,
				allowedEnvVars: passthroughNames
			});
			if (!policy.allowed) return {
				content: [{
					type: "text",
					text: `🚫 Sandbox: ${policy.reason}`
				}],
				details: {
					exitCode: null,
					timedOut: false,
					truncated: false
				}
			};
			return new Promise((resolve) => {
				let output = "";
				let errorOutput = "";
				let timedOut = false;
				let tempStream = null;
				const effectiveTimeoutSec = Math.min(MAX_SHELL_TIMEOUT, Math.ceil(policy.timeoutMs / 1e3));
				const timeout = setTimeout(() => {
					timedOut = true;
					proc.kill("SIGKILL");
				}, effectiveTimeoutSec * 1e3);
				const proc = spawn(p.command, [], {
					shell: true,
					cwd: policy.effectiveCwd,
					env: {
						...policy.sanitizedEnv,
						COLUMNS: "200"
					}
				});
				proc.stdout?.on("data", (data) => {
					const text = data.toString();
					output += text;
				});
				proc.stderr?.on("data", (data) => {
					const text = data.toString();
					errorOutput += text;
				});
				proc.on("close", (code) => {
					clearTimeout(timeout);
					tempStream?.end();
					const truncation = truncateTail(errorOutput + output);
					let resultText = truncation.content;
					if (timedOut) resultText = `⏱️ Command timed out after ${MAX_SHELL_TIMEOUT}s\n` + resultText;
					if (truncation.truncated) resultText += `\n\n[Output truncated: ${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}]`;
					resolve({
						content: [{
							type: "text",
							text: resultText
						}],
						details: {
							exitCode: code,
							timedOut,
							truncated: truncation.truncated,
							truncatedBy: truncation.truncatedBy,
							outputBytes: truncation.outputBytes
						}
					});
				});
				proc.on("error", (err) => {
					clearTimeout(timeout);
					resolve({
						content: [{
							type: "text",
							text: `Error: ${err.message}`
						}],
						details: {
							exitCode: null,
							timedOut: false,
							truncated: false
						}
					});
				});
			});
		}
	};
}
//#endregion
export { createShellTool };

//# sourceMappingURL=shell.js.map