agentmux_srv\backend\storage/
memory_bundles.rs1use rusqlite::params;
15use serde::{Deserialize, Serialize};
16
17use super::error::StoreError;
18use super::store::Store;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Memory {
26 pub id: String,
27 pub name: String,
28 #[serde(default)]
29 pub description: String,
30 #[serde(default)]
31 pub is_blank: bool,
32 #[serde(default)]
36 pub is_global: bool,
37 #[serde(default)]
39 pub provider: String,
40 #[serde(default)]
41 pub model: String,
42 #[serde(default)]
43 pub instructions: String,
44 #[serde(default = "default_json_array_string")]
46 pub context_files: String,
47 #[serde(default = "default_json_array_string")]
49 pub mcp_servers: String,
50 #[serde(default = "default_json_array_string")]
52 pub skills: String,
53 #[serde(default)]
59 pub sort_order: i64,
60 #[serde(default)]
65 pub created_at: i64,
66 #[serde(default)]
67 pub updated_at: i64,
68}
69
70fn default_json_array_string() -> String {
71 "[]".to_string()
72}
73
74pub fn format_global_brain_block(bundles: &[Memory]) -> String {
81 bundles
82 .iter()
83 .filter(|b| !b.instructions.trim().is_empty())
84 .map(|b| format!("# [Workspace] {}\n\n{}", b.name, b.instructions))
85 .collect::<Vec<_>>()
86 .join("\n\n---\n\n")
87}
88
89impl Store {
90 pub fn bundle_memory_list(&self) -> Result<Vec<Memory>, StoreError> {
91 let conn = self.conn.lock().unwrap();
92 let mut stmt = conn.prepare(
93 "SELECT id, name, description, is_blank, is_global, provider, model, instructions,
94 context_files, mcp_servers, skills, sort_order, created_at, updated_at
95 FROM db_memory_bundles
96 ORDER BY is_blank ASC, is_global DESC, updated_at DESC",
97 )?;
98 let iter = stmt.query_map([], map_memory_row)?;
99 let mut out = Vec::new();
100 for r in iter {
101 out.push(r?);
102 }
103 Ok(out)
104 }
105
106 pub fn bundle_memory_list_global(&self) -> Result<Vec<Memory>, StoreError> {
111 let conn = self.conn.lock().unwrap();
112 let mut stmt = conn.prepare(
113 "SELECT id, name, description, is_blank, is_global, provider, model, instructions,
114 context_files, mcp_servers, skills, sort_order, created_at, updated_at
115 FROM db_memory_bundles
116 WHERE is_global = 1
117 ORDER BY sort_order ASC, name ASC",
118 )?;
119 let iter = stmt.query_map([], map_memory_row)?;
120 let mut out = Vec::new();
121 for r in iter {
122 out.push(r?);
123 }
124 Ok(out)
125 }
126
127 pub fn bundle_memory_get(&self, id: &str) -> Result<Option<Memory>, StoreError> {
128 let conn = self.conn.lock().unwrap();
129 let mut stmt = conn.prepare(
130 "SELECT id, name, description, is_blank, is_global, provider, model, instructions,
131 context_files, mcp_servers, skills, sort_order, created_at, updated_at
132 FROM db_memory_bundles WHERE id = ?1",
133 )?;
134 let result = stmt.query_row(params![id], map_memory_row);
135 match result {
136 Ok(m) => Ok(Some(m)),
137 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
138 Err(e) => Err(e.into()),
139 }
140 }
141
142 pub fn bundle_memory_upsert(&self, memory: &Memory) -> Result<(), StoreError> {
143 let conn = self.conn.lock().unwrap();
144 conn.execute(
145 "INSERT INTO db_memory_bundles
150 (id, name, description, is_blank, is_global, provider, model, instructions,
151 context_files, mcp_servers, skills, sort_order, created_at, updated_at)
152 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)
153 ON CONFLICT(id) DO UPDATE SET
154 name = excluded.name,
155 description = excluded.description,
156 is_global = excluded.is_global,
157 provider = excluded.provider,
158 model = excluded.model,
159 instructions = excluded.instructions,
160 context_files = excluded.context_files,
161 mcp_servers = excluded.mcp_servers,
162 skills = excluded.skills,
163 updated_at = excluded.updated_at",
164 params![
165 memory.id,
166 memory.name,
167 memory.description,
168 memory.is_blank as i64,
169 memory.is_global as i64,
170 memory.provider,
171 memory.model,
172 memory.instructions,
173 memory.context_files,
174 memory.mcp_servers,
175 memory.skills,
176 memory.sort_order,
177 memory.created_at,
178 memory.updated_at,
179 ],
180 )?;
181 Ok(())
182 }
183
184 pub fn bundle_memory_delete(&self, id: &str) -> Result<bool, StoreError> {
186 if id == "blank" {
187 return Err(StoreError::Other(
188 "cannot delete the blank Memory singleton".to_string(),
189 ));
190 }
191 if id.starts_with("seed-") {
195 return Err(StoreError::Other(
196 "cannot delete a seeded Memory bundle; toggle is_global or clear its instructions instead".to_string(),
197 ));
198 }
199 let conn = self.conn.lock().unwrap();
200 let rows = conn.execute("DELETE FROM db_memory_bundles WHERE id = ?1", params![id])?;
201 Ok(rows > 0)
202 }
203
204 pub fn bundle_memory_reorder(&self, ordered_ids: &[String]) -> Result<usize, StoreError> {
211 let mut conn = self.conn.lock().unwrap();
212 let tx = conn.transaction()?;
213 let mut updated = 0usize;
214 {
215 let mut stmt =
216 tx.prepare("UPDATE db_memory_bundles SET sort_order = ?1 WHERE id = ?2")?;
217 for (idx, id) in ordered_ids.iter().enumerate() {
218 updated += stmt.execute(params![idx as i64, id])?;
219 }
220 }
221 tx.commit()?;
222 Ok(updated)
223 }
224}
225
226fn map_memory_row(row: &rusqlite::Row) -> rusqlite::Result<Memory> {
227 Ok(Memory {
228 id: row.get(0)?,
229 name: row.get(1)?,
230 description: row.get(2)?,
231 is_blank: row.get::<_, i64>(3)? != 0,
232 is_global: row.get::<_, i64>(4)? != 0,
233 provider: row.get(5)?,
234 model: row.get(6)?,
235 instructions: row.get(7)?,
236 context_files: row.get(8)?,
237 mcp_servers: row.get(9)?,
238 skills: row.get(10)?,
239 sort_order: row.get(11)?,
240 created_at: row.get(12)?,
241 updated_at: row.get(13)?,
242 })
243}