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:
iven
2026-03-21 00:36:06 +08:00
parent 48a430fc97
commit 0db8a2822f
7 changed files with 1633 additions and 11 deletions

View 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())
}