claude-desktop/src-tauri/src/memory.rs
Eddy f5ca5bca7c Phase 3: SQLite-Persistierung, Guard-Rails Integration + Claude Bridge
- db.rs: Vollständige SQLite-Schicht (Permissions, Audit, Memory, Patterns, Settings)
- guard.rs: Risiko-Klassifikation + Freigabe-Management in lib.rs integriert
- scripts/claude-bridge.js: Node.js Bridge für Claude CLI (stream-json, NDJSON)
- audit.rs + memory.rs: An SQLite angebunden statt In-Memory
- Frontend: MemoryPanel + AuditLog laden echte Daten via Tauri-Commands
- shell.nix: Rust-Toolchain aus nixpkgs statt rustup
- Build: cargo check + npm run build erfolgreich

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:28:35 +02:00

192 lines
5.9 KiB
Rust

// Claude Desktop — Autonomes Gedächtnis-System
// Lädt Zugänge, Patterns, Vorgehensweisen automatisch und behält sie im Kontext
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Manager};
use crate::db;
/// Kategorien für Sticky Context (werden nie vergessen)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ContextCategory {
Critical, // API-Keys, Server-URLs, Projekt-Pfade
Pattern, // Bekannte Fehler und Workarounds
Preference, // Benutzer-Präferenzen
GuardRail, // Freigabe-Regeln
Hook, // Aktive Hooks
Skill, // Verfügbare Skills
}
/// Ein Eintrag im Gedächtnis
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryEntry {
pub id: String,
pub category: ContextCategory,
pub key: String,
pub value: serde_json::Value,
pub sticky: bool, // Wird nie durch Compacting entfernt
pub auto_load: bool, // Wird beim Start automatisch geladen
pub last_used: Option<String>,
pub use_count: u32,
}
/// Das Gedächtnis-System
#[derive(Debug, Default)]
pub struct MemorySystem {
entries: HashMap<String, MemoryEntry>,
loaded_from_db: bool,
}
impl MemorySystem {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
loaded_from_db: false,
}
}
/// Fügt einen Eintrag hinzu
pub fn add(&mut self, entry: MemoryEntry) {
self.entries.insert(entry.id.clone(), entry);
}
/// Holt einen Eintrag
pub fn get(&self, id: &str) -> Option<&MemoryEntry> {
self.entries.get(id)
}
/// Holt alle Einträge einer Kategorie
pub fn get_by_category(&self, category: ContextCategory) -> Vec<&MemoryEntry> {
self.entries
.values()
.filter(|e| e.category == category)
.collect()
}
/// Holt alle Sticky-Einträge (für Kontext-Injection)
pub fn get_sticky_context(&self) -> Vec<&MemoryEntry> {
self.entries.values().filter(|e| e.sticky).collect()
}
/// Holt alle Auto-Load-Einträge
pub fn get_auto_load(&self) -> Vec<&MemoryEntry> {
self.entries.values().filter(|e| e.auto_load).collect()
}
/// Statistiken
pub fn stats(&self) -> MemoryStats {
MemoryStats {
total: self.entries.len(),
sticky: self.entries.values().filter(|e| e.sticky).count(),
by_category: self.count_by_category(),
}
}
fn count_by_category(&self) -> HashMap<String, usize> {
let mut counts = HashMap::new();
for entry in self.entries.values() {
let cat = format!("{:?}", entry.category);
*counts.entry(cat).or_insert(0) += 1;
}
counts
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MemoryStats {
pub total: usize,
pub sticky: usize,
pub by_category: HashMap<String, usize>,
}
/// Vorgehensweise / Pattern
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pattern {
pub id: String,
pub name: String,
pub description: String,
pub trigger: String, // Wann wird es angewendet
pub old_approach: String, // Was wurde vorher gemacht
pub new_approach: String, // Was ist die Korrektur
pub reason: String, // Warum wurde korrigiert
pub occurrence_count: u32, // Wie oft ist das Problem aufgetreten
pub auto_corrected: bool, // Wurde automatisch korrigiert
pub created_at: String,
pub updated_at: String,
}
// Tauri-Commands
/// Lädt das Gedächtnis beim Start
#[tauri::command]
pub async fn load_memory(app: AppHandle) -> Result<MemoryStats, String> {
println!("🧠 Lade Gedächtnis-System...");
let state = app.state::<Arc<Mutex<db::Database>>>();
let db_lock = state.lock().unwrap();
let entries = db_lock.load_memory_entries().map_err(|e| e.to_string())?;
let mut by_category: HashMap<String, usize> = HashMap::new();
let mut sticky_count = 0;
for entry in &entries {
let cat = format!("{:?}", entry.category);
*by_category.entry(cat).or_insert(0) += 1;
if entry.sticky {
sticky_count += 1;
}
}
println!("🧠 {} Einträge geladen ({} sticky)", entries.len(), sticky_count);
Ok(MemoryStats {
total: entries.len(),
sticky: sticky_count,
by_category,
})
}
/// Holt den Sticky-Kontext für Claude
#[tauri::command]
pub async fn get_sticky_context(app: AppHandle) -> Result<Vec<MemoryEntry>, String> {
let state = app.state::<Arc<Mutex<db::Database>>>();
let db_lock = state.lock().unwrap();
let entries = db_lock.load_memory_entries().map_err(|e| e.to_string())?;
Ok(entries.into_iter().filter(|e| e.sticky).collect())
}
/// Speichert eine neue Vorgehensweise
#[tauri::command]
pub async fn save_pattern(app: AppHandle, pattern: Pattern) -> Result<(), String> {
println!("📝 Speichere Vorgehensweise: {}", pattern.name);
let state = app.state::<Arc<Mutex<db::Database>>>();
let db_lock = state.lock().unwrap();
db_lock.save_pattern(&pattern).map_err(|e| e.to_string())
}
/// Erkennt ein Problem und schlägt Korrektur vor
#[tauri::command]
pub async fn detect_issue(
app: AppHandle,
error_message: String,
_context: String,
) -> Result<Option<Pattern>, String> {
let preview_len = error_message.len().min(50);
println!("🔍 Prüfe auf bekannte Probleme: {}", &error_message[..preview_len]);
let state = app.state::<Arc<Mutex<db::Database>>>();
let db_lock = state.lock().unwrap();
let patterns = db_lock.load_patterns().map_err(|e| e.to_string())?;
// Einfaches Pattern-Matching: Trigger im Fehlertext suchen
let error_lower = error_message.to_lowercase();
for pattern in patterns {
if error_lower.contains(&pattern.trigger.to_lowercase()) {
return Ok(Some(pattern));
}
}
Ok(None)
}