agentmux_srv\backend\wcore/
workspace.rs

1// Copyright 2025-2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Workspace CRUD operations.
5
6use std::collections::HashMap;
7use uuid::Uuid;
8
9use crate::backend::storage::wstore::WaveStore;
10use crate::backend::storage::StoreError;
11use crate::backend::obj::*;
12
13/// Create a new workspace with the given name.
14pub fn create_workspace(store: &WaveStore, name: &str) -> Result<Workspace, StoreError> {
15    let mut ws = Workspace {
16        oid: Uuid::new_v4().to_string(),
17        name: name.to_string(),
18        tabids: vec![],
19        pinnedtabids: vec![],
20        activetabid: String::new(),
21        meta: MetaMapType::new(),
22        ..Default::default()
23    };
24    store.insert(&mut ws)?;
25    Ok(ws)
26}
27
28/// Delete a workspace and all its tabs/blocks.
29///
30/// Cascades through BOTH `tabids` and `pinnedtabids` — pinned tabs
31/// were silently leaked to disk before this fix (codex P1 #614).
32pub fn delete_workspace(store: &WaveStore, ws_id: &str) -> Result<(), StoreError> {
33    let ws = store.must_get::<Workspace>(ws_id)?;
34
35    // Delete all tabs in the workspace (regular + pinned).
36    for tab_id in ws.tabids.iter().chain(ws.pinnedtabids.iter()) {
37        super::tab::delete_tab_inner(store, tab_id)?;
38    }
39
40    store.delete::<Workspace>(ws_id)?;
41    Ok(())
42}
43
44/// Get a workspace by ID.
45pub fn get_workspace(store: &WaveStore, ws_id: &str) -> Result<Workspace, StoreError> {
46    store.must_get::<Workspace>(ws_id)
47}
48
49/// List all workspaces as WorkspaceListEntry (matching Go's behavior).
50/// Returns [{workspaceid, windowid}] — filters out unnamed workspaces.
51pub fn list_workspaces(store: &WaveStore) -> Result<Vec<WorkspaceListEntry>, StoreError> {
52    let workspaces = store.get_all::<Workspace>()?;
53    let windows = store.get_all::<Window>()?;
54
55    // Build workspace -> window mapping
56    let mut ws_to_window: HashMap<String, String> = HashMap::new();
57    for win in &windows {
58        ws_to_window.entry(win.workspaceid.clone()).or_insert_with(|| win.oid.clone());
59    }
60
61    let mut entries = Vec::new();
62    for ws in &workspaces {
63        if ws.name.is_empty() {
64            continue;
65        }
66        entries.push(WorkspaceListEntry {
67            workspaceid: ws.oid.clone(),
68            windowid: ws_to_window.get(&ws.oid).cloned().unwrap_or_default(),
69        });
70    }
71    Ok(entries)
72}