# ClawTex — Governed Agent with Genetic Memory
# Built by DeVere Cooley / AN2B LLC · an2b.com
# BB4C — Breath Before Code

"""
Exec tool — run shell commands inside a sandboxed subprocess.

action_type: exec  →  REVIEW by default policy

ClawTex agents should NEVER call this tool directly without
Warden approval.  The breathe() method in agent.py gates it.
"""

from __future__ import annotations

import asyncio
import logging
import os
import shlex
from typing import Any

from .registry import registry

logger = logging.getLogger(__name__)

_DEFAULT_TIMEOUT = int(os.environ.get("EXEC_TIMEOUT_SECONDS", "30"))
_WORKSPACE = os.environ.get("CLAWTEX_WORKSPACE", ".")


@registry.register(
    name="exec",
    action_type="exec",
    description=(
        "Execute a shell command in the workspace. "
        "Always subject to Warden REVIEW. "
        "Use sparingly — prefer higher-level tools."
    ),
    parameters={
        "type": "object",
        "properties": {
            "command": {
                "type": "string",
                "description": "Shell command to run.",
            },
            "timeout": {
                "type": "integer",
                "description": f"Timeout in seconds (default {_DEFAULT_TIMEOUT}).",
                "default": _DEFAULT_TIMEOUT,
            },
            "workdir": {
                "type": "string",
                "description": "Working directory (defaults to workspace root).",
            },
        },
        "required": ["command"],
    },
)
async def exec_command(
    command: str,
    timeout: int = _DEFAULT_TIMEOUT,
    workdir: str | None = None,
    **_: Any,
) -> dict[str, Any]:
    """
    Run *command* in a subprocess, capture stdout/stderr, return results.

    stdout and stderr are truncated to 10 000 characters each to avoid
    overwhelming context windows.
    """
    cwd = workdir or _WORKSPACE
    logger.info("exec: %r (cwd=%s, timeout=%ds)", command, cwd, timeout)

    try:
        proc = await asyncio.wait_for(
            asyncio.create_subprocess_shell(
                command,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=cwd,
            ),
            timeout=timeout,
        )
        stdout_b, stderr_b = await asyncio.wait_for(
            proc.communicate(), timeout=timeout
        )
    except asyncio.TimeoutError:
        logger.warning("exec timed out after %ds: %r", timeout, command)
        return {
            "command": command,
            "returncode": -1,
            "stdout": "",
            "stderr": f"Command timed out after {timeout}s",
            "timed_out": True,
        }
    except Exception as exc:  # noqa: BLE001
        logger.error("exec error: %s", exc)
        return {
            "command": command,
            "returncode": -1,
            "stdout": "",
            "stderr": str(exc),
            "error": True,
        }

    _TRUNC = 10_000
    stdout = stdout_b.decode("utf-8", errors="replace")[:_TRUNC]
    stderr = stderr_b.decode("utf-8", errors="replace")[:_TRUNC]

    logger.info("exec finished: returncode=%d", proc.returncode)
    return {
        "command": command,
        "returncode": proc.returncode,
        "stdout": stdout,
        "stderr": stderr,
    }
