"""Sandboxed command executors for Codebase archives.""" from __future__ import annotations import os import shlex import subprocess import sys import tempfile from pathlib import Path from typing import Any, Callable from uuid import uuid4 from deepagents.backends import LocalShellBackend from .codebase_archive import ( CodebaseArchiveStore, LocalCodebaseArchiveStore, create_codebase_archive_store, ) from .codebase_handoff import ( CodebaseHandoff, DEFAULT_CODEBASE_HANDOFF_ROOT, local_snapshot_workspace, pointer_error, ) from .sandbox_lease import SandboxLeaseManager DEFAULT_SNAPSHOT_ROOT = DEFAULT_CODEBASE_HANDOFF_ROOT DEFAULT_VALIDATOR_SANDBOX_ROOT = Path(tempfile.gettempdir()) / "agent-swarm-workbench-validator" class LocalCodebaseExecutor: """Runs validation checks against local Codebase archive materializations.""" def __init__(self, *, root_dir: Path | None = None, timeout_seconds: int = 20) -> None: self.root_dir = root_dir or DEFAULT_SNAPSHOT_ROOT self.timeout_seconds = timeout_seconds def run(self, archive_pointer: str, command: str) -> tuple[bool, str]: workspace = local_snapshot_workspace(self.root_dir, archive_pointer) if workspace is None: return False, pointer_error(archive_pointer) if not workspace.exists(): return False, f"snapshot workspace not found: {archive_pointer}" try: result = subprocess.run( ["/bin/sh", "-lc", command], cwd=workspace, capture_output=True, text=True, timeout=self.timeout_seconds, env={"PATH": _executor_path()}, check=False, ) except subprocess.TimeoutExpired as exc: output = (exc.stdout or "") + (exc.stderr or "") return False, f"{output}\n[Command timed out after {self.timeout_seconds}s]" output = result.stdout if result.stderr: output = f"{output}[stderr] {result.stderr}" status = "succeeded" if result.returncode == 0 else "failed" return result.returncode == 0, f"{output}\n[Command {status} with exit code {result.returncode}]" class CodebaseSandboxExecutor: """Restores Codebase archives into a sandbox backend before running checks.""" def __init__( self, *, backend_factory: Callable[[], Any], root_dir: Path | None = None, archive_store: CodebaseArchiveStore | None = None, timeout_seconds: int = 20, sandbox_ttl_seconds: float | None = None, sandbox_leases: SandboxLeaseManager | None = None, ) -> None: self.root_dir = root_dir or DEFAULT_SNAPSHOT_ROOT self.archive_store = archive_store or LocalCodebaseArchiveStore(root_dir=self.root_dir) self.handoff = CodebaseHandoff(archive_store=self.archive_store, root_dir=self.root_dir) self.backend_factory = backend_factory self.backend = None self.timeout_seconds = timeout_seconds self.workspace_dir = "workspace" self._restored: set[str] = set() self._lease_id = f"validator-{uuid4().hex}" self._sandbox_leases = sandbox_leases or SandboxLeaseManager(ttl_seconds=sandbox_ttl_seconds) def run(self, archive_pointer: str, command: str) -> tuple[bool, str]: self._ensure_backend() self._sandbox_leases.touch(self._lease_id) try: self._restore(archive_pointer) except (OSError, ValueError) as exc: return False, f"could not restore snapshot: {exc}" result = self.backend.execute(f"cd {shlex.quote(self.workspace_dir)} && {command}", timeout=self.timeout_seconds) output = getattr(result, "output", "") exit_code = getattr(result, "exit_code", None) if exit_code is None: return True, output status = "succeeded" if exit_code == 0 else "failed" return exit_code == 0, f"{output}\n[Command {status} with exit code {exit_code}]" def close(self) -> None: self._sandbox_leases.close(self._lease_id) def _open_backend(self) -> None: self.backend = self.backend_factory() self.workspace_dir = getattr(self.backend, "_validator_workspace", "workspace") self._restored = set() self._sandbox_leases.start(self._lease_id, self._close_backend) def _ensure_backend(self) -> None: if self.backend is None: self._open_backend() def _close_backend(self) -> None: if self.backend is None: return sandbox = getattr(self.backend, "_daytona_sandbox", None) cleanup = None if sandbox is not None: cleanup = getattr(sandbox, "delete", None) or getattr(sandbox, "stop", None) if cleanup is None: cleanup = getattr(self.backend, "delete", None) or getattr(self.backend, "stop", None) if callable(cleanup): cleanup() self.backend = None self._restored = set() def _restore(self, archive_pointer: str) -> None: if archive_pointer in self._restored: return self.handoff.restore_to_backend( archive_pointer, self.backend, workspace_dir=self.workspace_dir, ) self._restored.add(archive_pointer) def create_codebase_executor( archive_store: CodebaseArchiveStore | None = None, provider: str | None = None, ) -> CodebaseSandboxExecutor: provider = (provider or os.getenv("VALIDATOR_SANDBOX_PROVIDER", "local")).lower() archive_store = archive_store or create_codebase_archive_store() if provider == "local": return CodebaseSandboxExecutor(backend_factory=_local_validator_backend, archive_store=archive_store) if provider == "daytona": return CodebaseSandboxExecutor(backend_factory=_daytona_validator_backend, archive_store=archive_store) raise ValueError(f"unsupported validator sandbox provider: {provider}") def _executor_path() -> str: executable_dir = str(Path(sys.executable).parent) return os.pathsep.join([executable_dir, "/usr/local/bin", "/usr/bin", "/bin"]) def _local_validator_backend() -> LocalShellBackend: root = DEFAULT_VALIDATOR_SANDBOX_ROOT / uuid4().hex root.mkdir(parents=True, exist_ok=True) backend = LocalShellBackend(root_dir=root, virtual_mode=True, inherit_env=True) backend._validator_workspace = "workspace" return backend def _daytona_validator_backend() -> Any: from daytona import Daytona from langchain_daytona import DaytonaSandbox client = Daytona() sandbox = client.create() backend = DaytonaSandbox(sandbox=sandbox) backend._daytona_client = client backend._daytona_sandbox = sandbox backend._validator_workspace = "/home/daytona/workspace" return backend