Skip to main content

post_cortex_mcp/
workspace.rs

1//! Workspace CRUD and session-to-workspace membership.
2
3use crate::{MCPToolResult, get_memory_system};
4use anyhow::Result;
5use post_cortex_memory::ConversationMemorySystem;
6use tracing::{error, info, instrument};
7use uuid::Uuid;
8
9/// Reload the current session list for `workspace_id` and re-persist the
10/// workspace metadata row. Called after add/remove session mutations so
11/// storage stays in sync with the in-memory workspace_manager.
12async fn persist_workspace_after_mutation(
13    system: &ConversationMemorySystem,
14    workspace_id: Uuid,
15) -> std::result::Result<(), String> {
16    let workspace = system.workspace_manager.get_workspace(&workspace_id);
17    let (ws_name, ws_description, sessions) = match &workspace {
18        Some(ws) => (
19            ws.name.clone(),
20            ws.description.clone(),
21            ws.get_all_sessions(),
22        ),
23        None => (String::new(), String::new(), Vec::new()),
24    };
25    let session_uuids: Vec<Uuid> = sessions.into_iter().map(|(id, _)| id).collect();
26
27    system
28        .storage_actor
29        .save_workspace_metadata(workspace_id, &ws_name, &ws_description, &session_uuids)
30        .await
31}
32
33/// Create a new workspace with the given name and description.
34#[instrument(skip_all, fields(workspace_name = %name))]
35pub async fn create_workspace(name: String, description: String) -> Result<MCPToolResult> {
36    info!("MCP-TOOLS: create_workspace() called with name: '{}'", name);
37    let system = get_memory_system().await?;
38
39    let workspace_id = system
40        .workspace_manager
41        .create_workspace(name.clone(), description.clone());
42
43    if let Err(e) = system
44        .storage_actor
45        .save_workspace_metadata(workspace_id, &name, &description, &Vec::new())
46        .await
47    {
48        error!("Failed to persist workspace: {}", e);
49        return Ok(MCPToolResult::error(format!(
50            "Failed to persist workspace: {}",
51            e
52        )));
53    }
54
55    info!("Created workspace {} with ID {}", name, workspace_id);
56
57    Ok(MCPToolResult::success(
58        format!("Created workspace '{}' with ID: {}", name, workspace_id),
59        Some(serde_json::json!({
60            "workspace_id": workspace_id.to_string(),
61            "name": name,
62            "description": description,
63            "session_count": 0
64        })),
65    ))
66}
67
68/// Retrieve workspace details including all associated sessions.
69#[instrument(skip_all, fields(workspace_id = %workspace_id))]
70pub async fn get_workspace(workspace_id: Uuid) -> Result<MCPToolResult> {
71    info!("MCP-TOOLS: get_workspace() called for ID: {}", workspace_id);
72    let system = get_memory_system().await?;
73
74    match system.workspace_manager.get_workspace(&workspace_id) {
75        Some(workspace) => {
76            let sessions = workspace.get_all_sessions();
77            let session_details: Vec<serde_json::Value> = sessions
78                .into_iter()
79                .map(|(id, role)| {
80                    serde_json::json!({
81                        "session_id": id.to_string(),
82                        "role": format!("{:?}", role)
83                    })
84                })
85                .collect();
86
87            Ok(MCPToolResult::success(
88                format!("Workspace '{}' retrieved", workspace.name),
89                Some(serde_json::json!({
90                    "workspace_id": workspace.id.to_string(),
91                    "name": workspace.name,
92                    "description": workspace.description,
93                    "created_at": workspace.created_at,
94                    "sessions": session_details,
95                    "session_count": workspace.session_ids.len()
96                })),
97            ))
98        }
99        None => Ok(MCPToolResult::error(format!(
100            "Workspace {} not found",
101            workspace_id
102        ))),
103    }
104}
105
106/// List all workspaces with session counts.
107pub async fn list_workspaces() -> Result<MCPToolResult> {
108    info!("MCP-TOOLS: list_workspaces() called");
109    let system = get_memory_system().await?;
110
111    let workspaces = system.workspace_manager.list_workspaces();
112
113    let workspace_list: Vec<serde_json::Value> = workspaces
114        .into_iter()
115        .map(|ws| {
116            serde_json::json!({
117                "workspace_id": ws.id.to_string(),
118                "name": ws.name,
119                "description": ws.description,
120                "session_count": ws.session_ids.len(),
121                "created_at": ws.created_at
122            })
123        })
124        .collect();
125
126    Ok(MCPToolResult::success(
127        format!("Found {} workspaces", workspace_list.len()),
128        Some(serde_json::json!({
129            "workspaces": workspace_list
130        })),
131    ))
132}
133
134/// Delete a workspace (sessions are not removed).
135pub async fn delete_workspace(workspace_id: Uuid) -> Result<MCPToolResult> {
136    info!(
137        "MCP-TOOLS: delete_workspace() called for ID: {}",
138        workspace_id
139    );
140    let system = get_memory_system().await?;
141
142    match system.workspace_manager.delete_workspace(&workspace_id) {
143        Some(workspace) => {
144            if let Err(e) = system.storage_actor.delete_workspace(workspace_id).await {
145                error!("Failed to delete workspace from storage: {}", e);
146                return Ok(MCPToolResult::error(format!(
147                    "Failed to delete workspace from storage: {}",
148                    e
149                )));
150            }
151
152            info!("Deleted workspace '{}' ({})", workspace.name, workspace_id);
153
154            Ok(MCPToolResult::success(
155                format!("Deleted workspace '{}'", workspace.name),
156                Some(serde_json::json!({
157                    "workspace_id": workspace_id.to_string(),
158                    "name": workspace.name
159                })),
160            ))
161        }
162        None => Ok(MCPToolResult::error(format!(
163            "Workspace {} not found",
164            workspace_id
165        ))),
166    }
167}
168
169/// Add a session to a workspace with a specific role.
170pub async fn add_session_to_workspace(
171    workspace_id: Uuid,
172    session_id: Uuid,
173    role: String,
174) -> Result<MCPToolResult> {
175    info!(
176        "MCP-TOOLS: add_session_to_workspace() called: session {} -> workspace {} (role: {})",
177        session_id, workspace_id, role
178    );
179    let system = get_memory_system().await?;
180
181    let role_enum = match role.to_lowercase().as_str() {
182        "primary" => post_cortex_core::workspace::SessionRole::Primary,
183        "related" => post_cortex_core::workspace::SessionRole::Related,
184        "dependency" => post_cortex_core::workspace::SessionRole::Dependency,
185        "shared" => post_cortex_core::workspace::SessionRole::Shared,
186        _ => post_cortex_core::workspace::SessionRole::Related,
187    };
188
189    match system
190        .workspace_manager
191        .add_session_to_workspace(&workspace_id, session_id, role_enum)
192    {
193        Ok(()) => {
194            if let Err(e) = persist_workspace_after_mutation(&system, workspace_id).await {
195                error!("Failed to update workspace in storage: {}", e);
196                return Ok(MCPToolResult::error(format!(
197                    "Failed to update workspace in storage: {}",
198                    e
199                )));
200            }
201
202            info!(
203                "Added session {} to workspace {} with role {:?}",
204                session_id, workspace_id, role_enum
205            );
206
207            Ok(MCPToolResult::success(
208                "Added session to workspace successfully".to_string(),
209                Some(serde_json::json!({
210                    "workspace_id": workspace_id.to_string(),
211                    "session_id": session_id.to_string(),
212                    "role": format!("{:?}", role_enum)
213                })),
214            ))
215        }
216        Err(e) => Ok(MCPToolResult::error(format!(
217            "Failed to add session to workspace: {}",
218            e
219        ))),
220    }
221}
222
223/// Remove a session from a workspace.
224pub async fn remove_session_from_workspace(
225    workspace_id: Uuid,
226    session_id: Uuid,
227) -> Result<MCPToolResult> {
228    info!(
229        "MCP-TOOLS: remove_session_from_workspace() called: session {} from workspace {}",
230        session_id, workspace_id
231    );
232    let system = get_memory_system().await?;
233
234    match system
235        .workspace_manager
236        .remove_session_from_workspace(&workspace_id, &session_id)
237    {
238        Ok(Some(role)) => {
239            if let Err(e) = persist_workspace_after_mutation(&system, workspace_id).await {
240                error!("Failed to remove session from workspace in storage: {}", e);
241                return Ok(MCPToolResult::error(format!(
242                    "Failed to remove session from workspace in storage: {}",
243                    e
244                )));
245            }
246
247            info!(
248                "Removed session {} from workspace {} (was role {:?})",
249                session_id, workspace_id, role
250            );
251
252            Ok(MCPToolResult::success(
253                "Removed session from workspace successfully".to_string(),
254                Some(serde_json::json!({
255                    "workspace_id": workspace_id.to_string(),
256                    "session_id": session_id.to_string(),
257                    "previous_role": format!("{:?}", role)
258                })),
259            ))
260        }
261        Ok(None) => Ok(MCPToolResult::error(format!(
262            "Session {} not found in workspace {}",
263            session_id, workspace_id
264        ))),
265        Err(e) => Ok(MCPToolResult::error(e)),
266    }
267}