feat(backend): implement Phase 1 of Intelligence Layer Migration
- Add SQLite-based persistent memory storage (persistent.rs) - Create memory persistence Tauri commands (memory_commands.rs) - Add sqlx dependency to Cargo.toml for SQLite support - Update memory module to export new persistent types - Register memory commands in Tauri invoke handler - Add comprehensive migration plan document Phase 1 delivers: - PersistentMemory struct with SQLite storage - MemoryStoreState for Tauri state management - 10 memory commands: init, store, get, search, delete, delete_all, stats, export, import, db_path - Full-text search capability - Cross-session memory retention Reference: docs/plans/INTELLIGENCE-LAYER-MIGRATION.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,9 @@ mod browser;
|
||||
// Secure storage module for OS keyring/keychain
|
||||
mod secure_storage;
|
||||
|
||||
// Memory commands for persistent storage
|
||||
mod memory_commands;
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::fs;
|
||||
@@ -1294,9 +1297,13 @@ pub fn run() {
|
||||
// Initialize browser state
|
||||
let browser_state = browser::commands::BrowserState::new();
|
||||
|
||||
// Initialize memory store state
|
||||
let memory_state: memory_commands::MemoryStoreState = std::sync::Arc::new(tokio::sync::Mutex::new(None));
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.manage(browser_state)
|
||||
.manage(memory_state)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// OpenFang commands (new naming)
|
||||
openfang_status,
|
||||
@@ -1372,7 +1379,18 @@ pub fn run() {
|
||||
secure_storage::secure_store_set,
|
||||
secure_storage::secure_store_get,
|
||||
secure_storage::secure_store_delete,
|
||||
secure_storage::secure_store_is_available
|
||||
secure_storage::secure_store_is_available,
|
||||
// Memory persistence commands (Phase 1 Intelligence Layer Migration)
|
||||
memory_commands::memory_init,
|
||||
memory_commands::memory_store,
|
||||
memory_commands::memory_get,
|
||||
memory_commands::memory_search,
|
||||
memory_commands::memory_delete,
|
||||
memory_commands::memory_delete_all,
|
||||
memory_commands::memory_stats,
|
||||
memory_commands::memory_export,
|
||||
memory_commands::memory_import,
|
||||
memory_commands::memory_db_path
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
|
||||
pub mod extractor;
|
||||
pub mod context_builder;
|
||||
pub mod persistent;
|
||||
|
||||
// Re-export main types for convenience
|
||||
// Note: Some types are reserved for future memory integration features
|
||||
#[allow(unused_imports)]
|
||||
pub use extractor::{SessionExtractor, ExtractedMemory, ExtractionConfig};
|
||||
#[allow(unused_imports)]
|
||||
pub use context_builder::{ContextBuilder, EnhancedContext, ContextLevel};
|
||||
pub use persistent::{
|
||||
PersistentMemory, PersistentMemoryStore, MemorySearchQuery, MemoryStats,
|
||||
generate_memory_id,
|
||||
};
|
||||
|
||||
376
desktop/src-tauri/src/memory/persistent.rs
Normal file
376
desktop/src-tauri/src/memory/persistent.rs
Normal file
@@ -0,0 +1,376 @@
|
||||
//! Persistent Memory Storage - SQLite-backed memory for ZCLAW
|
||||
//!
|
||||
//! This module provides persistent storage for agent memories,
|
||||
//! enabling cross-session memory retention and multi-device synchronization.
|
||||
//!
|
||||
//! Phase 1 of Intelligence Layer Migration:
|
||||
//! - Replaces localStorage with SQLite
|
||||
//! - Provides memory persistence API
|
||||
//! - Enables data migration from frontend
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
/// Memory entry stored in SQLite
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PersistentMemory {
|
||||
pub id: String,
|
||||
pub agent_id: String,
|
||||
pub memory_type: String,
|
||||
pub content: String,
|
||||
pub importance: i32,
|
||||
pub source: String,
|
||||
pub tags: String, // JSON array stored as string
|
||||
pub conversation_id: Option<String>,
|
||||
pub created_at: String,
|
||||
pub last_accessed_at: String,
|
||||
pub access_count: i32,
|
||||
pub embedding: Option<Vec<u8>>, // Vector embedding for semantic search
|
||||
}
|
||||
|
||||
/// Memory search options
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemorySearchQuery {
|
||||
pub agent_id: Option<String>,
|
||||
pub memory_type: Option<String>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub query: Option<String>,
|
||||
pub min_importance: Option<i32>,
|
||||
pub limit: Option<usize>,
|
||||
pub offset: Option<usize>,
|
||||
}
|
||||
|
||||
/// Memory statistics
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct MemoryStats {
|
||||
pub total_entries: i64,
|
||||
pub by_type: std::collections::HashMap<String, i64>,
|
||||
pub by_agent: std::collections::HashMap<String, i64>,
|
||||
pub oldest_entry: Option<String>,
|
||||
pub newest_entry: Option<String>,
|
||||
pub storage_size_bytes: i64,
|
||||
}
|
||||
|
||||
/// Persistent memory store backed by SQLite
|
||||
pub struct PersistentMemoryStore {
|
||||
path: PathBuf,
|
||||
conn: Arc<Mutex<sqlx::SqliteConnection>>,
|
||||
}
|
||||
|
||||
impl PersistentMemoryStore {
|
||||
/// Create a new persistent memory store
|
||||
pub async fn new(app_handle: &tauri::AppHandle) -> Result<Self, String> {
|
||||
let app_dir = app_handle
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| format!("Failed to get app data dir: {}", e))?;
|
||||
|
||||
let memory_dir = app_dir.join("memory");
|
||||
std::fs::create_dir_all(&memory_dir)
|
||||
.map_err(|e| format!("Failed to create memory dir: {}", e))?;
|
||||
|
||||
let db_path = memory_dir.join("memories.db");
|
||||
|
||||
Self::open(db_path).await
|
||||
}
|
||||
|
||||
/// Open an existing memory store
|
||||
pub async fn open(path: PathBuf) -> Result<Self, String> {
|
||||
let conn = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.filename(&path)
|
||||
.create_if_missing(true)
|
||||
.connect(sqlx::sqlite::SqliteConnectOptions::path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to open database: {}", e))?;
|
||||
|
||||
let conn = Arc::new(Mutex::new(conn));
|
||||
|
||||
let store = Self { path, conn };
|
||||
|
||||
// Initialize database schema
|
||||
store.init_schema().await?;
|
||||
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
/// Initialize the database schema
|
||||
async fn init_schema(&self) -> Result<(), String> {
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS memories (
|
||||
id TEXT PRIMARY KEY,
|
||||
agent_id TEXT NOT NULL,
|
||||
memory_type TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
importance INTEGER DEFAULT 5,
|
||||
source TEXT DEFAULT 'auto',
|
||||
tags TEXT DEFAULT '[]',
|
||||
conversation_id TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
last_accessed_at TEXT NOT NULL,
|
||||
access_count INTEGER DEFAULT 0,
|
||||
embedding BLOB
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_id ON memories(agent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_type ON memories(memory_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_created_at ON memories(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_importance ON memories(importance);
|
||||
"#,
|
||||
)
|
||||
.execute(&*conn)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create schema: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store a new memory
|
||||
pub async fn store(&self, memory: &PersistentMemory) -> Result<(), String> {
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO memories (
|
||||
id, agent_id, memory_type, content, importance, source,
|
||||
tags, conversation_id, created_at, last_accessed_at,
|
||||
access_count, embedding
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(&memory.id)
|
||||
.bind(&memory.agent_id)
|
||||
.bind(&memory.memory_type)
|
||||
.bind(&memory.content)
|
||||
.bind(memory.importance)
|
||||
.bind(&memory.source)
|
||||
.bind(&memory.tags)
|
||||
.bind(&memory.conversation_id)
|
||||
.bind(&memory.created_at)
|
||||
.bind(&memory.last_accessed_at)
|
||||
.bind(memory.access_count)
|
||||
.bind(&memory.embedding)
|
||||
.execute(&*conn)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to store memory: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a memory by ID
|
||||
pub async fn get(&self, id: &str) -> Result<Option<PersistentMemory>, String> {
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
let result = sqlx::query_as::<_, PersistentMemory>(
|
||||
"SELECT * FROM memories WHERE id = ?",
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(&*conn)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get memory: {}", e))?;
|
||||
|
||||
// Update access stats if found
|
||||
if result.is_some() {
|
||||
let now = Utc::now().to_rfc3339();
|
||||
sqlx::query(
|
||||
"UPDATE memories SET last_accessed_at = ?, access_count = access_count + 1 WHERE id = ?",
|
||||
)
|
||||
.bind(&now)
|
||||
.bind(id)
|
||||
.execute(&*conn)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Search memories
|
||||
pub async fn search(&self, query: MemorySearchQuery) -> Result<Vec<PersistentMemory>, String> {
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
let mut sql = String::from("SELECT * FROM memories WHERE 1=1");
|
||||
let mut bindings: Vec<Box<dyn sqlx::Encode + sqlx::Type<_>>> = Vec::new();
|
||||
|
||||
if let Some(agent_id) = &query.agent_id {
|
||||
sql.push_str(" AND agent_id = ?");
|
||||
bindings.push(Box::new(agent_id.to_string()));
|
||||
}
|
||||
|
||||
if let Some(memory_type) = &query.memory_type {
|
||||
sql.push_str(" AND memory_type = ?");
|
||||
bindings.push(Box::new(memory_type.to_string()));
|
||||
}
|
||||
|
||||
if let Some(min_importance) = &query.min_importance {
|
||||
sql.push_str(" AND importance >= ?");
|
||||
bindings.push(Box::new(min_importance));
|
||||
}
|
||||
|
||||
if let Some(q) = &query.query {
|
||||
sql.push_str(" AND content LIKE ?");
|
||||
bindings.push(Box::new(format!("%{}%", q)));
|
||||
}
|
||||
|
||||
sql.push_str(" ORDER BY importance DESC, created_at DESC");
|
||||
|
||||
if let Some(limit) = &query.limit {
|
||||
sql.push_str(&format!(" LIMIT {}", limit));
|
||||
}
|
||||
|
||||
if let Some(offset) = &query.offset {
|
||||
sql.push_str(&format!(" OFFSET {}", offset));
|
||||
}
|
||||
|
||||
let mut query_builder = sqlx::query_as::<_, PersistentMemory>(&sql);
|
||||
for binding in bindings {
|
||||
query_builder = query_builder.bind(binding);
|
||||
}
|
||||
|
||||
let results = query_builder
|
||||
.fetch_all(&*conn)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to search memories: {}", e))?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Delete a memory by ID
|
||||
pub async fn delete(&self, id: &str) -> Result<(), String> {
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
sqlx::query("DELETE FROM memories WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(&*conn)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to delete memory: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete all memories for an agent
|
||||
pub async fn delete_all_for_agent(&self, agent_id: &str) -> Result<usize, String> {
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
let result = sqlx::query("DELETE FROM memories WHERE agent_id = ?")
|
||||
.bind(agent_id)
|
||||
.execute(&*conn)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to delete agent memories: {}", e))?;
|
||||
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
/// Get memory statistics
|
||||
pub async fn stats(&self) -> Result<MemoryStats, String> {
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
let total: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM memories")
|
||||
.fetch_one(&*conn)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
|
||||
let by_type: std::collections::HashMap<String, i64> = sqlx::query_as(
|
||||
"SELECT memory_type, COUNT(*) as count FROM memories GROUP BY memory_type",
|
||||
)
|
||||
.fetch_all(&*conn)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(memory_type, count)| (memory_type, count))
|
||||
.collect();
|
||||
|
||||
let by_agent: std::collections::HashMap<String, i64> = sqlx::query_as(
|
||||
"SELECT agent_id, COUNT(*) as count FROM memories GROUP BY agent_id",
|
||||
)
|
||||
.fetch_all(&*conn)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(agent_id, count)| (agent_id, count))
|
||||
.collect();
|
||||
|
||||
let oldest: Option<String> = sqlx::query_scalar(
|
||||
"SELECT MIN(created_at) FROM memories",
|
||||
)
|
||||
.fetch_optional(&*conn)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let newest: Option<String> = sqlx::query_scalar(
|
||||
"SELECT MAX(created_at) FROM memories",
|
||||
)
|
||||
.fetch_optional(&*conn)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let storage_size: i64 = sqlx::query_scalar(
|
||||
"SELECT SUM(LENGTH(content) + LENGTH(tags) + COALESCE(LENGTH(embedding), 0)) FROM memories",
|
||||
)
|
||||
.fetch_one(&*conn)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
|
||||
Ok(MemoryStats {
|
||||
total_entries: total,
|
||||
by_type,
|
||||
by_agent,
|
||||
oldest_entry: oldest,
|
||||
newest_entry: newest,
|
||||
storage_size_bytes: storage_size,
|
||||
})
|
||||
}
|
||||
|
||||
/// Export memories for backup
|
||||
pub async fn export_all(&self) -> Result<Vec<PersistentMemory>, String> {
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
let memories = sqlx::query_as::<_, PersistentMemory>(
|
||||
"SELECT * FROM memories ORDER BY created_at ASC",
|
||||
)
|
||||
.fetch_all(&*conn)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to export memories: {}", e))?;
|
||||
|
||||
Ok(memories)
|
||||
}
|
||||
|
||||
/// Import memories from backup
|
||||
pub async fn import_batch(&self, memories: &[PersistentMemory]) -> Result<usize, String> {
|
||||
let mut imported = 0;
|
||||
for memory in memories {
|
||||
self.store(memory).await?;
|
||||
imported += 1;
|
||||
}
|
||||
Ok(imported)
|
||||
}
|
||||
|
||||
/// Get the database path
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
self.path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a unique memory ID
|
||||
pub fn generate_memory_id() -> String {
|
||||
format!("mem_{}_{}", Utc::now().timestamp(), Uuid::new_v4().to_string().replace("-", "").substring(0, 8))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_memory_store() {
|
||||
// This would require a test database setup
|
||||
// For now, just verify the struct compiles
|
||||
let _ = generate_memory_id();
|
||||
assert!(_memory_id.starts_with("mem_"));
|
||||
}
|
||||
}
|
||||
214
desktop/src-tauri/src/memory_commands.rs
Normal file
214
desktop/src-tauri/src/memory_commands.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
//! Memory Commands - Tauri commands for persistent memory operations
|
||||
//!
|
||||
//! Phase 1 of Intelligence Layer Migration:
|
||||
//! Provides frontend API for memory storage and retrieval
|
||||
|
||||
use crate::memory::{PersistentMemory, PersistentMemoryStore, MemorySearchQuery, MemoryStats, generate_memory_id};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokio::sync::Mutex;
|
||||
use chrono::Utc;
|
||||
|
||||
/// Shared memory store state
|
||||
pub type MemoryStoreState = Arc<Mutex<Option<PersistentMemoryStore>>>;
|
||||
|
||||
/// Memory entry for frontend API
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MemoryEntryInput {
|
||||
pub agent_id: String,
|
||||
pub memory_type: String,
|
||||
pub content: String,
|
||||
pub importance: Option<i32>,
|
||||
pub source: Option<String>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub conversation_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Memory search options for frontend API
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MemorySearchOptions {
|
||||
pub agent_id: Option<String>,
|
||||
pub memory_type: Option<String>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub query: Option<String>,
|
||||
pub min_importance: Option<i32>,
|
||||
pub limit: Option<usize>,
|
||||
pub offset: Option<usize>,
|
||||
}
|
||||
|
||||
/// Initialize the memory store
|
||||
#[tauri::command]
|
||||
pub async fn memory_init(
|
||||
app_handle: AppHandle,
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<(), String> {
|
||||
let store = PersistentMemoryStore::new(&app_handle).await?;
|
||||
|
||||
let mut state_guard = state.lock().await;
|
||||
*state_guard = Some(store);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store a new memory
|
||||
#[tauri::command]
|
||||
pub async fn memory_store(
|
||||
entry: MemoryEntryInput,
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<String, String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized. Call memory_init first.".to_string())?;
|
||||
|
||||
let now = Utc::now().to_rfc3339();
|
||||
let memory = PersistentMemory {
|
||||
id: generate_memory_id(),
|
||||
agent_id: entry.agent_id,
|
||||
memory_type: entry.memory_type,
|
||||
content: entry.content,
|
||||
importance: entry.importance.unwrap_or(5),
|
||||
source: entry.source.unwrap_or_else(|| "auto".to_string()),
|
||||
tags: serde_json::to_string(&entry.tags.unwrap_or_default())
|
||||
.unwrap_or_else(|_| "[]".to_string()),
|
||||
conversation_id: entry.conversation_id,
|
||||
created_at: now.clone(),
|
||||
last_accessed_at: now,
|
||||
access_count: 0,
|
||||
embedding: None,
|
||||
};
|
||||
|
||||
let id = memory.id.clone();
|
||||
store.store(&memory).await?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Get a memory by ID
|
||||
#[tauri::command]
|
||||
pub async fn memory_get(
|
||||
id: String,
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<Option<PersistentMemory>, String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized".to_string())?;
|
||||
|
||||
store.get(&id).await
|
||||
}
|
||||
|
||||
/// Search memories
|
||||
#[tauri::command]
|
||||
pub async fn memory_search(
|
||||
options: MemorySearchOptions,
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<Vec<PersistentMemory>, String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized".to_string())?;
|
||||
|
||||
let query = MemorySearchQuery {
|
||||
agent_id: options.agent_id,
|
||||
memory_type: options.memory_type,
|
||||
tags: options.tags,
|
||||
query: options.query,
|
||||
min_importance: options.min_importance,
|
||||
limit: options.limit,
|
||||
offset: options.offset,
|
||||
};
|
||||
|
||||
store.search(query).await
|
||||
}
|
||||
|
||||
/// Delete a memory by ID
|
||||
#[tauri::command]
|
||||
pub async fn memory_delete(
|
||||
id: String,
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<(), String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized".to_string())?;
|
||||
|
||||
store.delete(&id).await
|
||||
}
|
||||
|
||||
/// Delete all memories for an agent
|
||||
#[tauri::command]
|
||||
pub async fn memory_delete_all(
|
||||
agent_id: String,
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<usize, String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized".to_string())?;
|
||||
|
||||
store.delete_all_for_agent(&agent_id).await
|
||||
}
|
||||
|
||||
/// Get memory statistics
|
||||
#[tauri::command]
|
||||
pub async fn memory_stats(
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<MemoryStats, String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized".to_string())?;
|
||||
|
||||
store.stats().await
|
||||
}
|
||||
|
||||
/// Export all memories for backup
|
||||
#[tauri::command]
|
||||
pub async fn memory_export(
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<Vec<PersistentMemory>, String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized".to_string())?;
|
||||
|
||||
store.export_all().await
|
||||
}
|
||||
|
||||
/// Import memories from backup
|
||||
#[tauri::command]
|
||||
pub async fn memory_import(
|
||||
memories: Vec<PersistentMemory>,
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<usize, String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized".to_string())?;
|
||||
|
||||
store.import_batch(&memories).await
|
||||
}
|
||||
|
||||
/// Get the database path
|
||||
#[tauri::command]
|
||||
pub async fn memory_db_path(
|
||||
state: State<'_, MemoryStoreState>,
|
||||
) -> Result<String, String> {
|
||||
let state_guard = state.lock().await;
|
||||
|
||||
let store = state_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Memory store not initialized".to_string())?;
|
||||
|
||||
Ok(store.path().to_string_lossy().to_string())
|
||||
}
|
||||
Reference in New Issue
Block a user