//! Per-agent sandbox orchestration (spec §15): containers with no root, no //! capabilities, a seccomp deny profile, read-only rootfs, and no network. //! One `SandboxDriver` trait; the Docker implementation serves dev and the //! air-gapped compose target. mod docker; mod spec; pub use docker::DockerDriver; pub use spec::{ DriveMount, ExecResult, ManagedSandbox, PtySession, SandboxHandle, SandboxKind, SandboxSpec, }; /// Label every Clawmates sandbox carries, so orphans can be found + reaped /// after a crash/restart. Value is the kind: `agent` (no egress) or `browser`. pub const SANDBOX_LABEL: &str = "clawmates.sandbox"; /// The label value for a sandbox of the given kind. pub fn sandbox_kind(egress: bool) -> &'static str { if egress { "browser" } else { "agent" } } #[derive(Debug, thiserror::Error)] pub enum SandboxError { #[error("container engine error: {0}")] Engine(String), #[error("sandbox not found")] NotFound, } #[async_trait::async_trait] pub trait SandboxDriver: Send + Sync { /// Creates and starts a hardened sandbox container. async fn provision(&self, spec: &SandboxSpec) -> Result; /// Runs a command inside the sandbox (orchestrator-initiated only; the /// sandbox can initiate nothing outbound). async fn exec(&self, handle: &SandboxHandle, cmd: &[&str]) -> Result; /// Starts an interactive PTY (`exec -it`) inside the sandbox: a TTY-backed /// `cmd` (e.g. `["zsh","-l"]`) whose combined output streams back and whose /// stdin accepts keystrokes. `env` adds `KEY=VALUE` vars to the session (e.g. /// a MOTD greeting). Used by the Terminal app, not agent tools. async fn attach_pty( &self, handle: &SandboxHandle, cmd: &[&str], cols: u16, rows: u16, env: &[String], ) -> Result; /// Resizes a running PTY exec's window (cols × rows). async fn resize_pty(&self, exec_id: &str, cols: u16, rows: u16) -> Result<(), SandboxError>; /// Stops and removes the sandbox. async fn destroy(&self, handle: &SandboxHandle) -> Result<(), SandboxError>; /// Whether the sandbox container is currently running. async fn health(&self, handle: &SandboxHandle) -> Result; /// List sandboxes the engine currently holds for this kind /// (`agent`/`browser`), so the manager can reap orphans whose owning /// process died. Filtered by the [`SANDBOX_LABEL`] label. async fn list_managed(&self, kind: &str) -> Result, SandboxError>; }