Skip to main content

post_cortex_mcp/
workspace.rs

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