Files
zclaw_openfang/desktop/src-tauri/src/memory_commands.rs
iven ce562e8bfc feat: complete Phase 1-3 architecture optimization
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>
2026-03-21 22:11:50 +08:00

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