Agent computer: terminal (tmux + drives + tabs), Obsidian vault, UI polish
Terminal app (xterm ⇄ WebSocket ⇄ per-agent themed container):
- zsh + oh-my-zsh + powerlevel10k image (agent-terminal), runs as uid 65532 to
share read-write ownership of the file-drive volume with the server.
- Interactive PTY in cm-sandbox (bollard exec tty/attach + resize) + a
TerminalManager; ticket-authed WS bridge routed straight to the backend via a
Traefik PathRegexp(/ws) rule. MOTD greets the user by name.
- tmux resumable sessions; multi-tab (one tmux session per tab, same container),
drag-to-reorder, rename, and a Save that persists named tabs to the server
(terminal_tabs, migration 0014) so they survive logout / a new device.
- Files drives mounted per-agent (subpath) at ~/drives/{documents,received,
shared}; a reconciler keeps the Files app's index in sync with terminal writes.
Storage moved to a shared `filedata` volume (CLAWMATES_STORAGE__DATA_DIR).
Obsidian vault (a markdown "second brain" per agent):
- New `vault` FileDrive (migration 0015) mounted into the terminal at ~/obsidian;
a file-content read route; a purple Obsidian tile + a vault viewer app.
Computer UI:
- Draggable computer-panel width (min = phone preset) keeping the size presets.
- Green Terminal glyph, "Claw Chat" → "Chat", colored gradient-outline app icons.
- Agent page: avatar↔activity-grid spacing + larger, uniform section fonts with
colored section-tinted tag chips.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
671df7c622
commit
e61724ff82
@@ -15,6 +15,73 @@ pub struct SandboxSpec {
|
||||
/// hold no credentials and have no broker route; their output is
|
||||
/// tainted `web`. Everything else runs with no network at all.
|
||||
pub egress: bool,
|
||||
/// What this sandbox is for — drives the reaper label and whether the
|
||||
/// rootfs/home is writable.
|
||||
pub kind: SandboxKind,
|
||||
/// Named-volume subpath mounts (Terminal drives). Empty for tool sandboxes.
|
||||
pub mounts: Vec<DriveMount>,
|
||||
}
|
||||
|
||||
/// A read-write mount of a per-agent subpath of a named Docker volume into the
|
||||
/// container — used to expose the Files drives inside the Terminal.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DriveMount {
|
||||
/// The engine's named volume (e.g. `clawmates_filedata`).
|
||||
pub volume: String,
|
||||
/// Subpath within the volume (per-agent isolation), e.g. `{ws}/documents/{agent}`.
|
||||
pub subpath: String,
|
||||
/// Mount target inside the container, e.g. `/home/agent/drives/documents`.
|
||||
pub target: String,
|
||||
pub read_only: bool,
|
||||
}
|
||||
|
||||
/// The flavour of a sandbox container. Agent + Browser are hardened tool
|
||||
/// sandboxes; Terminal is the interactive themed dev shell.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SandboxKind {
|
||||
/// Hardened, no-egress agent tool sandbox (read-only rootfs, tmpfs home).
|
||||
Agent,
|
||||
/// Egress-enabled browser sandbox (no credentials; output tainted `web`).
|
||||
Browser,
|
||||
/// Interactive themed terminal: a writable home so the baked zsh /
|
||||
/// oh-my-zsh / powerlevel10k config + history work. Still non-root,
|
||||
/// cap-drop ALL, seccomp, no-new-privileges and resource-limited.
|
||||
Terminal,
|
||||
}
|
||||
|
||||
impl SandboxKind {
|
||||
/// The [`crate::SANDBOX_LABEL`] value, so each manager reaps only its own.
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
SandboxKind::Agent => "agent",
|
||||
SandboxKind::Browser => "browser",
|
||||
SandboxKind::Terminal => "terminal",
|
||||
}
|
||||
}
|
||||
/// Terminal keeps a writable rootfs + home (baked dotfiles + history);
|
||||
/// the hardened tool sandboxes stay read-only with a tmpfs home.
|
||||
pub fn writable_home(self) -> bool {
|
||||
matches!(self, SandboxKind::Terminal)
|
||||
}
|
||||
|
||||
/// The uid:gid the container runs as. Terminal matches the server's nonroot
|
||||
/// uid (65532) so it shares read-write ownership of the file-drive volume;
|
||||
/// the hardened tool sandboxes run as 10001.
|
||||
pub fn run_user(self) -> &'static str {
|
||||
match self {
|
||||
SandboxKind::Terminal => "65532:65532",
|
||||
_ => "10001:10001",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An attached interactive PTY exec (a `docker exec -it` session): combined
|
||||
/// TTY output as byte chunks + a writer for keystrokes, plus the exec id so
|
||||
/// the window size can be resized.
|
||||
pub struct PtySession {
|
||||
pub exec_id: String,
|
||||
pub output: std::pin::Pin<Box<dyn futures::Stream<Item = Result<Vec<u8>, crate::SandboxError>> + Send>>,
|
||||
pub input: std::pin::Pin<Box<dyn tokio::io::AsyncWrite + Send>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
Reference in New Issue
Block a user