use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use crate::ids::{AgentId, UserId, WorkspaceId}; use crate::role::Role; /// A tenant team (spec §14 Workspace/Team). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Workspace { pub id: WorkspaceId, pub name: String, pub plan: String, } /// A human member of a workspace (spec §14 User). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct User { pub id: UserId, pub workspace_id: WorkspaceId, pub email: String, pub role: Role, pub display_name: String, #[serde(with = "time::serde::rfc3339")] pub created_at: OffsetDateTime, } /// Lifecycle of an agent; `Online` renders the green roster dot (§4). #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AgentStatus { Provisioning, Online, Offline, } impl AgentStatus { pub fn as_str(&self) -> &'static str { match self { AgentStatus::Provisioning => "provisioning", AgentStatus::Online => "online", AgentStatus::Offline => "offline", } } } impl std::str::FromStr for AgentStatus { type Err = String; fn from_str(s: &str) -> Result { match s { "provisioning" => Ok(AgentStatus::Provisioning), "online" => Ok(AgentStatus::Online), "offline" => Ok(AgentStatus::Offline), other => Err(format!("unknown agent status: {other}")), } } } /// The three file drives (spec §7.4): per-agent documents and received /// files, plus the team-wide shared ClawDrive. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum FileDrive { Documents, Received, Shared, /// The agent's Obsidian-style markdown vault — a "second brain" the agent /// writes notes/references into (per-agent scoped). Vault, } impl FileDrive { pub fn as_str(&self) -> &'static str { match self { FileDrive::Documents => "documents", FileDrive::Received => "received", FileDrive::Shared => "shared", FileDrive::Vault => "vault", } } /// Whether nodes on this drive belong to one agent or the whole team. pub fn is_agent_scoped(&self) -> bool { !matches!(self, FileDrive::Shared) } } impl std::str::FromStr for FileDrive { type Err = String; fn from_str(s: &str) -> Result { match s { "documents" => Ok(FileDrive::Documents), "received" => Ok(FileDrive::Received), "shared" => Ok(FileDrive::Shared), "vault" => Ok(FileDrive::Vault), other => Err(format!("unknown drive: {other}")), } } } /// One entry in a drive (spec §14 FileNode). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct FileNode { pub id: uuid::Uuid, pub workspace_id: WorkspaceId, /// `None` on the shared drive. pub agent_id: Option, pub drive: FileDrive, pub path: String, pub size: i64, pub blob_ref: String, } /// An AI coworker (spec §14 Agent). The `system_prompt` is the Settings /// "Job Description" textarea verbatim (§7.7). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Agent { pub id: AgentId, pub workspace_id: WorkspaceId, pub name: String, pub job_title: String, pub system_prompt: String, pub avatar: String, pub accent: String, pub wallpaper: String, pub managed_by: UserId, pub status: AgentStatus, }