Skip to main content

astrid_workspace/
config.rs

1//! Workspace configuration types.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Operating mode for the workspace.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum WorkspaceMode {
10    /// Always ask before operations outside workspace.
11    #[default]
12    Safe,
13    /// Smart defaults with selective approval.
14    Guided,
15    /// No restrictions (agent machine mode).
16    Autonomous,
17}
18
19/// Policy for handling escape requests.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
21#[serde(rename_all = "snake_case")]
22pub enum EscapePolicy {
23    /// Always ask the user.
24    #[default]
25    Ask,
26    /// Always deny escape requests.
27    Deny,
28    /// Always allow escape requests.
29    Allow,
30}
31
32/// Paths that are automatically allowed.
33#[derive(Debug, Clone, Default, Serialize, Deserialize)]
34pub struct AutoAllowPaths {
35    /// Paths that are always allowed for reading.
36    #[serde(default)]
37    pub read: Vec<PathBuf>,
38    /// Paths that are always allowed for writing.
39    #[serde(default)]
40    pub write: Vec<PathBuf>,
41    /// Glob patterns for auto-allowed paths.
42    #[serde(default)]
43    pub patterns: Vec<String>,
44}
45
46/// Workspace configuration.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct WorkspaceConfig {
49    /// Root directory of the workspace.
50    pub root: PathBuf,
51    /// Operating mode.
52    #[serde(default)]
53    pub mode: WorkspaceMode,
54    /// Policy for escape requests.
55    #[serde(default)]
56    pub escape_policy: EscapePolicy,
57    /// Paths that are automatically allowed.
58    #[serde(default)]
59    pub auto_allow: AutoAllowPaths,
60    /// Paths that are never allowed (even with approval).
61    #[serde(default)]
62    pub never_allow: Vec<PathBuf>,
63    /// Whether to allow creating files outside workspace.
64    #[serde(default)]
65    pub allow_create_outside: bool,
66    /// Whether to allow deleting files outside workspace.
67    #[serde(default)]
68    pub allow_delete_outside: bool,
69}
70
71impl WorkspaceConfig {
72    /// Create a new workspace configuration.
73    #[must_use]
74    pub fn new(root: impl Into<PathBuf>) -> Self {
75        Self {
76            root: root.into(),
77            mode: WorkspaceMode::Safe,
78            escape_policy: EscapePolicy::Ask,
79            auto_allow: AutoAllowPaths::default(),
80            never_allow: vec![
81                PathBuf::from("/etc"),
82                PathBuf::from("/var"),
83                PathBuf::from("/usr"),
84                PathBuf::from("/bin"),
85                PathBuf::from("/sbin"),
86                PathBuf::from("/boot"),
87                PathBuf::from("/root"),
88            ],
89            allow_create_outside: false,
90            allow_delete_outside: false,
91        }
92    }
93
94    /// Set the operating mode.
95    #[must_use]
96    pub fn with_mode(mut self, mode: WorkspaceMode) -> Self {
97        self.mode = mode;
98        self
99    }
100
101    /// Set the escape policy.
102    #[must_use]
103    pub fn with_escape_policy(mut self, policy: EscapePolicy) -> Self {
104        self.escape_policy = policy;
105        self
106    }
107
108    /// Add an auto-allowed read path.
109    #[must_use]
110    pub fn allow_read(mut self, path: impl Into<PathBuf>) -> Self {
111        self.auto_allow.read.push(path.into());
112        self
113    }
114
115    /// Add an auto-allowed write path.
116    #[must_use]
117    pub fn allow_write(mut self, path: impl Into<PathBuf>) -> Self {
118        self.auto_allow.write.push(path.into());
119        self
120    }
121
122    /// Add a never-allowed path.
123    #[must_use]
124    pub fn never_allow(mut self, path: impl Into<PathBuf>) -> Self {
125        self.never_allow.push(path.into());
126        self
127    }
128
129    /// Check if a path is in the workspace.
130    #[must_use]
131    pub fn is_in_workspace(&self, path: &std::path::Path) -> bool {
132        path.starts_with(&self.root)
133    }
134}
135
136impl Default for WorkspaceConfig {
137    fn default() -> Self {
138        Self::new(".")
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_workspace_config_creation() {
148        let config = WorkspaceConfig::new("/home/user/project");
149        assert_eq!(config.root, PathBuf::from("/home/user/project"));
150        assert_eq!(config.mode, WorkspaceMode::Safe);
151    }
152
153    #[test]
154    fn test_workspace_mode() {
155        let config = WorkspaceConfig::new("/test").with_mode(WorkspaceMode::Autonomous);
156        assert_eq!(config.mode, WorkspaceMode::Autonomous);
157    }
158
159    #[test]
160    fn test_is_in_workspace() {
161        let config = WorkspaceConfig::new("/home/user/project");
162        assert!(config.is_in_workspace(std::path::Path::new("/home/user/project/src/main.rs")));
163        assert!(!config.is_in_workspace(std::path::Path::new("/home/user/other")));
164    }
165}