Phase 1 - Security: - Add AES-GCM encryption for localStorage fallback - Enforce WSS protocol for non-localhost WebSocket connections - Add URL sanitization to prevent XSS in markdown links Phase 2 - Domain Reorganization: - Create Intelligence Domain with Valtio store and caching - Add unified intelligence-client for Rust backend integration - Migrate from legacy agent-memory, heartbeat, reflection modules Phase 3 - Core Optimization: - Add virtual scrolling for ChatArea with react-window - Implement LRU cache with TTL for intelligence operations - Add message virtualization utilities Additional: - Add OpenFang compatibility test suite - Update E2E test fixtures - Add audit logging infrastructure - Update project documentation and plans Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
216 lines
5.7 KiB
Rust
216 lines
5.7 KiB
Rust
//! 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?;
|
|
Ok(())
|
|
}
|
|
|
|
/// 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_by_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())
|
|
}
|