[appimage] Phase 2.0: Proaktive Intelligenz
All checks were successful
Build AppImage / build (push) Successful in 8m19s

- MySQL Pool als Managed State (MysqlPoolState in lib.rs)
- Keyword-Extraktion aus User-Nachrichten (Stoppwort-Filter DE/EN)
- Proaktive KB-Abfrage bei SessionStart (proactive_session_hints)
- Auto-Fehler-Pattern: error_tracker Tabelle, bei 3+ Occurrences
  automatisch KB-Eintrag in Kategorie 'fehler' erstellen
- 6 neue Tauri-Commands für KB-Hints und Error-Tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eddy 2026-04-20 14:19:29 +02:00
parent d315f421ec
commit 2de88a2a22
6 changed files with 476 additions and 11 deletions

View file

@ -1346,11 +1346,22 @@ END;
## In Arbeit / Geplant
### Phase 2.0: Proaktive Intelligenz (geplant)
- [ ] MySQL Pool als Managed State (Effizienz-Fix für knowledge.rs)
- [ ] Proaktive KB-Abfrage bei SessionStart
- [ ] Themen-Erkennung aus User-Nachrichten für KB-Suche
- [ ] Auto-Fehler-Pattern-Speicherung (3x gleicher Fehler → Pattern)
### Phase 2.0: Proaktive Intelligenz ✅ ERLEDIGT (20.04.2026)
| Feature | Status | Datei(en) |
|---------|--------|-----------|
| MySQL Pool als Managed State | ✅ | `knowledge.rs`, `lib.rs` |
| Proaktive KB-Abfrage bei SessionStart | ✅ | `knowledge.rs`, `claude.rs`, `events.ts` |
| Themen-Erkennung (Keyword-Extraktion) | ✅ | `knowledge.rs` (`extract_keywords()`, Stoppwort-Filter) |
| Auto-Fehler-Pattern (3x → KB-Eintrag) | ✅ | `db.rs` (`error_tracker` Tabelle), `events.ts`, `knowledge.rs` |
#### Details
- **MySQL Pool**: `MysqlPoolState` als Tauri Managed State, einmal beim App-Start erstellt
- **Proaktive Hints**: `proactive_session_hints()` lädt bei Session-Start relevante KB-Einträge basierend auf Projekt-Kontext
- **Keyword-Extraktion**: Deutsche + englische Stoppwörter gefiltert, max 6 Keywords, dedupliziert
- **Error-Tracking**: `error_tracker` SQLite-Tabelle mit Hash-basiertem Grouping. Bei 3+ Occurrences wird automatisch ein `fehler`-Eintrag in der claude-db (MySQL) erstellt
- **Neue Tauri-Commands**: `extract_message_keywords`, `get_session_hints`, `auto_save_error_pattern`, `track_error`, `set_error_kb_pattern`, `get_error_stats`
### Phase 2.1: Desktop-Zugriff erweitern (geplant)
- [ ] Guard-Rails in Claude-Bridge einbauen

View file

@ -665,7 +665,38 @@ pub async fn init_sticky_context(app: AppHandle) -> Result<StickyContextInfo, St
}
}
if let Some(ref ctx) = context {
// Phase 2.0: Proaktive KB-Hints bei Session-Start laden
// Projekt-Name aus Sticky Context extrahieren falls vorhanden
let project_name = if let Some(db_state) = app.try_state::<Arc<Mutex<crate::db::Database>>>() {
let db = db_state.lock().unwrap();
db.get_setting("current_project_name").ok().flatten()
} else {
None
};
let proactive_hints = match knowledge::proactive_session_hints(project_name.as_deref()).await {
Ok(hints) if !hints.is_empty() => {
println!("📋 Proaktive Session-Hints: {} Bytes", hints.len());
Some(hints)
}
Ok(_) => None,
Err(e) => {
println!("⚠️ Proaktive Hints Fehler (ignoriert): {}", e);
None
}
};
// Context + proaktive Hints kombinieren
let mut full_context = context.clone();
if let Some(hints) = proactive_hints {
let ctx = full_context.get_or_insert_with(String::new);
if !ctx.is_empty() {
ctx.push_str("\n\n");
}
ctx.push_str(&hints);
}
if let Some(ref ctx) = full_context {
info.loaded = true;
info.estimated_tokens = ctx.len() / 4;
@ -685,7 +716,7 @@ pub async fn init_sticky_context(app: AppHandle) -> Result<StickyContextInfo, St
// Context an Bridge senden
let _ = send_to_bridge(&app, "set-context", ctx);
println!("✅ Sticky Context geladen: {} Einträge, ~{} Token", info.entries, info.estimated_tokens);
println!("✅ Sticky Context geladen: {} Einträge, ~{} Token (inkl. proaktive Hints)", info.entries, info.estimated_tokens);
} else {
println!(" Kein Sticky Context konfiguriert");
}

View file

@ -204,6 +204,19 @@ impl Database {
DELETE FROM monitor_events
WHERE timestamp < datetime('now', '-7 days');
END;
-- Phase 2.0: Fehler-Tracking für Auto-Pattern-Erkennung
CREATE TABLE IF NOT EXISTS error_tracker (
error_hash TEXT PRIMARY KEY,
error_message TEXT NOT NULL,
tool TEXT NOT NULL,
occurrence_count INTEGER DEFAULT 1,
first_seen TEXT NOT NULL,
last_seen TEXT NOT NULL,
kb_pattern_id INTEGER,
UNIQUE(error_hash)
);
CREATE INDEX IF NOT EXISTS idx_error_tracker_count ON error_tracker(occurrence_count DESC);
",
)?;
Ok(())
@ -839,6 +852,66 @@ impl Database {
Ok(counts)
}
// ============ Phase 2.0: Fehler-Tracking ============
/// Fehler-Occurrence zählen und zurückgeben
/// Gibt (neuer_count, error_message, tool, kb_pattern_id) zurück
pub fn track_error(&self, error_hash: &str, error_message: &str, tool: &str) -> SqlResult<(i32, Option<i64>)> {
let now = chrono::Local::now().to_rfc3339();
// Versuche zu aktualisieren
let updated = self.conn.execute(
"UPDATE error_tracker SET
occurrence_count = occurrence_count + 1,
last_seen = ?1,
error_message = ?2
WHERE error_hash = ?3",
params![now, error_message, error_hash],
)?;
if updated == 0 {
// Neuer Eintrag
self.conn.execute(
"INSERT INTO error_tracker (error_hash, error_message, tool, occurrence_count, first_seen, last_seen)
VALUES (?1, ?2, ?3, 1, ?4, ?4)",
params![error_hash, error_message, tool, now],
)?;
return Ok((1, None));
}
// Aktuellen Count und kb_pattern_id holen
let result: (i32, Option<i64>) = self.conn.query_row(
"SELECT occurrence_count, kb_pattern_id FROM error_tracker WHERE error_hash = ?1",
params![error_hash],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
Ok(result)
}
/// KB-Pattern-ID für einen Fehler speichern (nachdem Pattern in KB erstellt wurde)
pub fn set_error_kb_pattern(&self, error_hash: &str, kb_pattern_id: i64) -> SqlResult<()> {
self.conn.execute(
"UPDATE error_tracker SET kb_pattern_id = ?1 WHERE error_hash = ?2",
params![kb_pattern_id, error_hash],
)?;
Ok(())
}
/// Fehler-Statistiken laden (Top N häufigste Fehler)
pub fn get_error_stats(&self, limit: usize) -> SqlResult<Vec<(String, String, String, i32, Option<i64>)>> {
let mut stmt = self.conn.prepare(
"SELECT error_hash, error_message, tool, occurrence_count, kb_pattern_id
FROM error_tracker
ORDER BY occurrence_count DESC
LIMIT ?1"
)?;
let stats = stmt.query_map(params![limit as i64], |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?))
})?.collect::<SqlResult<Vec<_>>>()?;
Ok(stats)
}
// ============ Statistiken ============
/// DB-Statistiken
@ -1043,3 +1116,42 @@ pub async fn get_monitor_stats(app: AppHandle) -> Result<Vec<(String, usize)>, S
let db = state.lock().unwrap();
db.count_monitor_events_by_type().map_err(|e| e.to_string())
}
// ============ Phase 2.0: Fehler-Tracking Commands ============
/// Fehler tracken — gibt (count, kb_pattern_id) zurück
#[tauri::command]
pub async fn track_error(
app: AppHandle,
error_hash: String,
error_message: String,
tool: String,
) -> Result<(i32, Option<i64>), String> {
let state = app.state::<DbState>();
let db = state.lock().unwrap();
db.track_error(&error_hash, &error_message, &tool).map_err(|e| e.to_string())
}
/// KB-Pattern-ID für Fehler setzen
#[tauri::command]
pub async fn set_error_kb_pattern(
app: AppHandle,
error_hash: String,
kb_pattern_id: i64,
) -> Result<(), String> {
let state = app.state::<DbState>();
let db = state.lock().unwrap();
db.set_error_kb_pattern(&error_hash, kb_pattern_id).map_err(|e| e.to_string())
}
/// Fehler-Statistiken laden
#[tauri::command]
pub async fn get_error_stats(
app: AppHandle,
limit: Option<usize>,
) -> Result<Vec<(String, String, String, i32, Option<i64>)>, String> {
let limit = limit.unwrap_or(20);
let state = app.state::<DbState>();
let db = state.lock().unwrap();
db.get_error_stats(limit).map_err(|e| e.to_string())
}

View file

@ -1,9 +1,12 @@
// Claude Desktop — Wissensbasis (claude-db)
// Direkte MySQL-Anbindung zur zentralen Wissensdatenbank
// Phase 2.0: MySQL Pool als Managed State + Themen-Erkennung
use mysql_async::{Pool, prelude::*};
use serde::{Deserialize, Serialize};
use chrono::NaiveDateTime;
use std::sync::Arc;
use tauri::{AppHandle, Manager};
/// Verbindungskonfiguration — aus ENV-Variablen, Fallback auf Defaults
const MYSQL_PORT: u16 = 3306;
@ -13,6 +16,86 @@ fn mysql_user() -> String { std::env::var("CLAUDE_MYSQL_USER").unwrap_or_else(|_
fn mysql_pass() -> String { std::env::var("CLAUDE_MYSQL_PASS").unwrap_or_else(|_| "8715".to_string()) }
fn mysql_db() -> String { std::env::var("CLAUDE_MYSQL_DB").unwrap_or_else(|_| "claude".to_string()) }
/// Managed MySQL Pool — wird einmal beim App-Start erstellt
/// Alle Knowledge-Funktionen nutzen diesen Pool statt jedes Mal einen neuen zu erstellen
pub type MysqlPoolState = Arc<Option<Pool>>;
/// Erstellt den globalen MySQL Pool (einmal beim App-Start aufrufen)
pub fn create_managed_pool() -> MysqlPoolState {
let url = format!(
"mysql://{}:{}@{}:{}/{}",
mysql_user(), mysql_pass(), mysql_host(), MYSQL_PORT, mysql_db()
);
match Pool::new(url.as_str()) {
pool => {
println!("🗄️ MySQL Pool erstellt ({}:{})", mysql_host(), MYSQL_PORT);
Arc::new(Some(pool))
}
}
}
/// Pool aus AppHandle holen — Fallback auf neuen Pool wenn State nicht verfügbar
/// TODO: Bestehende Commands schrittweise auf get_pool(app) migrieren
#[allow(dead_code)]
fn get_pool(app: Option<&AppHandle>) -> Pool {
if let Some(app) = app {
if let Some(pool_state) = app.try_state::<MysqlPoolState>() {
if let Some(pool) = pool_state.as_ref() {
return pool.clone();
}
}
}
// Fallback: neuen Pool erstellen (für Aufrufe ohne AppHandle, z.B. search_knowledge_internal)
create_pool()
}
/// Deutsche Stoppwörter die bei der Themen-Erkennung gefiltert werden
const STOP_WORDS: &[&str] = &[
"der", "die", "das", "den", "dem", "des", "ein", "eine", "einer", "einem", "einen",
"und", "oder", "aber", "doch", "noch", "auch", "nur", "schon", "mal", "dann",
"ist", "sind", "war", "hat", "haben", "wird", "werden", "kann", "können", "soll",
"muss", "müssen", "darf", "will", "wollen", "möchte", "würde", "sollte",
"ich", "du", "er", "sie", "es", "wir", "ihr", "mein", "dein", "sein",
"mit", "für", "auf", "von", "aus", "bei", "nach", "über", "unter", "vor",
"wie", "was", "wer", "wo", "wann", "warum", "wieso", "weshalb",
"nicht", "kein", "keine", "keinen", "keinem",
"this", "that", "the", "and", "for", "with", "from", "into",
"bitte", "danke", "okay", "alles", "nächste", "mach", "zeig", "gib",
"mir", "dir", "uns", "hier", "dort", "jetzt", "gerade", "einfach",
"phase", "feature", "erstelle", "implementiere", "baue",
];
/// Extrahiert relevante Keywords aus einer User-Nachricht
/// Filtert Stoppwörter und kurze Wörter raus, gibt die besten Suchbegriffe zurück
pub fn extract_keywords(message: &str) -> Vec<String> {
let words: Vec<String> = message
.to_lowercase()
// Satzzeichen entfernen
.replace(|c: char| !c.is_alphanumeric() && c != '-' && c != '_' && c != '.', " ")
.split_whitespace()
.filter(|w| {
w.len() >= 3
&& !STOP_WORDS.contains(&w.as_ref())
// Zahlen alleine sind selten gute Suchbegriffe
&& !w.chars().all(|c| c.is_numeric())
})
.map(|w| w.to_string())
.collect();
// Deduplizieren und max 6 Keywords behalten
let mut unique: Vec<String> = Vec::new();
for w in words {
if !unique.contains(&w) {
unique.push(w);
}
if unique.len() >= 6 {
break;
}
}
unique
}
/// Wissenseintrag aus der knowledge-Tabelle
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeEntry {
@ -69,7 +152,23 @@ fn create_pool() -> Pool {
/// KB-Hints für eine Nachricht laden — fehlertolerant, gibt leeren String bei DB-Problemen
/// Wird von claude.rs aufgerufen bevor die Nachricht an die Bridge geht
/// Phase 2.0: Nutzt jetzt Keyword-Extraktion statt rohe Nachricht als Query
pub async fn search_knowledge_internal(query: &str, limit: i32) -> Result<String, String> {
// Phase 2.0: Keywords aus der Nachricht extrahieren für bessere Suche
let keywords = extract_keywords(query);
let search_query = if keywords.is_empty() {
// Fallback: erste 100 Zeichen der Nachricht
query[..query.len().min(100)].to_string()
} else {
keywords.join(" ")
};
search_knowledge_by_query(&search_query, limit).await
}
/// Sucht in der KB mit einem vorbereiteten Query-String
/// Zentrale Suchfunktion die von search_knowledge_internal und proactive_session_hints genutzt wird
pub async fn search_knowledge_by_query(search_query: &str, limit: i32) -> Result<String, String> {
let pool = create_pool();
// Verbindung mit Timeout — DB nicht erreichbar soll nicht blockieren
@ -102,14 +201,14 @@ pub async fn search_knowledge_internal(query: &str, limit: i32) -> Result<String
AND MATCH(title, content, tags) AGAINST(? IN NATURAL LANGUAGE MODE)
ORDER BY priority DESC, relevance DESC
LIMIT ?"#,
(query, query, limit),
(search_query, search_query, limit),
).await.map_err(|e| e.to_string())?;
drop(conn);
let _ = pool.disconnect().await;
if results.is_empty() {
println!("🔍 KB-Hints für '{}': keine Treffer", &query[..query.len().min(40)]);
println!("🔍 KB-Hints für '{}': keine Treffer", &search_query[..search_query.len().min(40)]);
return Ok(String::new());
}
@ -132,11 +231,102 @@ pub async fn search_knowledge_internal(query: &str, limit: i32) -> Result<String
let block = hints.join("\n");
println!("🔍 KB-Hints für '{}': {} Treffer, {} Bytes",
&query[..query.len().min(40)], results.len(), block.len());
&search_query[..search_query.len().min(40)], results.len(), block.len());
Ok(block)
}
/// Phase 2.0: Proaktive KB-Abfrage bei SessionStart
/// Lädt relevante Einträge basierend auf Projekt-Kontext und letzter Aktivität
pub async fn proactive_session_hints(project_name: Option<&str>) -> Result<String, String> {
let mut search_terms = Vec::new();
// Projekt-bezogene Begriffe
if let Some(name) = project_name {
search_terms.push(name.to_string());
// Projekt-spezifische Zusatzbegriffe
let lower = name.to_lowercase();
if lower.contains("dolibarr") { search_terms.push("dolibarr".to_string()); }
if lower.contains("claude") { search_terms.push("claude desktop tauri".to_string()); }
if lower.contains("bericht") { search_terms.push("bericht modul".to_string()); }
if lower.contains("kunden") { search_terms.push("kundenkarte schaltplan".to_string()); }
}
// Allgemeine Begriffe für Session-Start (aktive Fehler, wichtige Patterns)
search_terms.push("fehler workaround aktiv".to_string());
let query = search_terms.join(" ");
println!("📋 Proaktive KB-Abfrage: '{}'", &query[..query.len().min(60)]);
search_knowledge_by_query(&query, 5).await
}
/// Phase 2.0: Auto-Fehler-Pattern in KB speichern
/// Wird aufgerufen wenn ein Fehler 3x aufgetreten ist
pub async fn save_error_pattern_to_kb(
error_hash: &str,
error_message: &str,
tool: &str,
occurrence_count: i32,
) -> Result<i64, String> {
let pool = create_pool();
let mut conn = pool.get_conn().await.map_err(|e| e.to_string())?;
// Prüfen ob für diesen Hash schon ein KB-Eintrag existiert
let existing: Option<i64> = conn.exec_first(
r#"SELECT id FROM knowledge
WHERE category = 'fehler'
AND tags LIKE CONCAT('%', ?, '%')
AND status = 'active'
LIMIT 1"#,
(error_hash,),
).await.map_err(|e| e.to_string())?;
if let Some(id) = existing {
// Schon vorhanden — nur Occurrence aktualisieren
conn.exec_drop(
r#"UPDATE knowledge SET
content = CONCAT(content, '\n\n---\nWeiteres Auftreten: ', NOW(), ' (', ?, 'x gesamt)'),
updated_at = NOW()
WHERE id = ?"#,
(occurrence_count, id),
).await.map_err(|e| e.to_string())?;
drop(conn);
let _ = pool.disconnect().await;
println!("📝 Fehler-Pattern #{} aktualisiert ({}x)", id, occurrence_count);
return Ok(id);
}
// Neuen KB-Eintrag erstellen
let title = format!("Auto-Pattern: {} Fehler in {}", &error_message[..error_message.len().min(60)], tool);
let content = format!(
"## Automatisch erkanntes Fehler-Pattern\n\n\
**Tool:** {}\n\
**Häufigkeit:** {}x aufgetreten\n\
**Fehlermeldung:**\n```\n{}\n```\n\n\
**Hash:** `{}`\n\n\
> Dieses Pattern wurde automatisch erstellt nachdem der Fehler {}x aufgetreten ist.\n\
> Bitte Lösung/Workaround ergänzen.",
tool, occurrence_count, &error_message[..error_message.len().min(500)], error_hash, occurrence_count
);
let tags = format!("auto-pattern,fehler,{},{}", tool.to_lowercase(), error_hash);
conn.exec_drop(
r#"INSERT INTO knowledge (category, title, content, tags, priority, status, source, created_at, updated_at)
VALUES ('fehler', ?, ?, ?, 2, 'active', 'auto-pattern', NOW(), NOW())"#,
(&title, &content, &tags),
).await.map_err(|e| e.to_string())?;
let id: i64 = conn.last_insert_id().ok_or("Keine Insert-ID")? as i64;
drop(conn);
let _ = pool.disconnect().await;
println!("🆕 Neues Fehler-Pattern in KB gespeichert: #{} ({}x {})", id, occurrence_count, tool);
Ok(id)
}
// ============ Tauri Commands ============
/// Wissensbasis durchsuchen (Volltext)
@ -503,3 +693,28 @@ pub async fn test_knowledge_connection() -> Result<String, String> {
println!("✅ Wissensbasis verbunden: {} Einträge", count);
Ok(format!("Verbunden! {} Einträge in der Wissensbasis", count))
}
// ============ Phase 2.0: Neue Commands ============
/// Keywords aus einer Nachricht extrahieren (für Frontend-Debug/Anzeige)
#[tauri::command]
pub async fn extract_message_keywords(message: String) -> Result<Vec<String>, String> {
Ok(extract_keywords(&message))
}
/// Proaktive KB-Hints bei Session-Start laden
#[tauri::command]
pub async fn get_session_hints(project_name: Option<String>) -> Result<String, String> {
proactive_session_hints(project_name.as_deref()).await
}
/// Fehler-Pattern automatisch in KB speichern (aufgerufen von Frontend bei 3+ Occurrences)
#[tauri::command]
pub async fn auto_save_error_pattern(
error_hash: String,
error_message: String,
tool: String,
occurrence_count: i32,
) -> Result<i64, String> {
save_error_pattern_to_kb(&error_hash, &error_message, &tool, occurrence_count).await
}

View file

@ -33,6 +33,8 @@ pub fn run() {
.manage(guard::GuardState::new(Mutex::new(guard::GuardRails::new())))
.manage::<hooks::HookState>(Arc::new(Mutex::new(hooks::HookManager::default())))
.manage::<ide::IdeState>(Arc::new(Mutex::new(ide::IdeConnector::default())))
// Phase 2.0: MySQL Pool als Managed State — wird einmal erstellt, von allen Knowledge-Commands geteilt
.manage::<knowledge::MysqlPoolState>(knowledge::create_managed_pool())
.invoke_handler(tauri::generate_handler![
// Claude SDK
claude::send_message,
@ -86,6 +88,10 @@ pub fn run() {
db::load_monitor_events_by_type,
db::clear_all_monitor_events,
db::get_monitor_stats,
// Phase 2.0: Fehler-Tracking
db::track_error,
db::set_error_kb_pattern,
db::get_error_stats,
// Wissensbasis (claude-db)
knowledge::search_knowledge,
knowledge::get_knowledge,
@ -95,6 +101,10 @@ pub fn run() {
knowledge::test_knowledge_connection,
knowledge::get_tool_hints,
knowledge::format_tool_hints,
// Phase 2.0: Proaktive Intelligenz
knowledge::extract_message_keywords,
knowledge::get_session_hints,
knowledge::auto_save_error_pattern,
// Context-Management
context::get_sticky_context,
context::set_sticky_context,

View file

@ -118,7 +118,7 @@ export async function initEventListeners(): Promise<void> {
})
);
// Session erstellt — Hook feuern (fire-and-forget)
// Session erstellt — Hook feuern + proaktive KB-Hints laden (fire-and-forget)
listeners.push(
await listen<{ id: string }>('session-created', (event) => {
const { id } = event.payload;
@ -127,6 +127,9 @@ export async function initEventListeners(): Promise<void> {
event: 'SessionStart',
summary: JSON.stringify({ sessionId: id })
}).catch((err) => console.debug('Hook session-start fehlgeschlagen:', err));
// Phase 2.0: Proaktive KB-Abfrage bei Session-Start
loadProactiveSessionHints();
})
);
@ -290,6 +293,9 @@ export async function initEventListeners(): Promise<void> {
// Pattern-Detektion ist optional — Fehler nur loggen
console.debug('Pattern-Detektion fehlgeschlagen:', err);
});
// Phase 2.0: Auto-Fehler-Tracking — Fehler hashen und zählen
trackErrorOccurrence(output, tool || 'unknown');
}
})
);
@ -424,6 +430,86 @@ export async function cleanupEventListeners(): Promise<void> {
listeners = [];
}
// Phase 2.0: Fehler-Hash berechnen (einfacher Hash aus Fehlermeldung)
function hashError(errorMessage: string): string {
// Normalisierung: Zahlen, Pfade und UUIDs entfernen für besseres Grouping
const normalized = errorMessage
.substring(0, 200)
.replace(/\/[\w/.-]+/g, '<PATH>') // Pfade
.replace(/[0-9a-f]{8}-[0-9a-f]{4}/gi, '<UUID>') // UUIDs
.replace(/\d+/g, '<N>') // Zahlen
.replace(/\s+/g, ' ') // Whitespace
.trim()
.toLowerCase();
// Einfacher String-Hash
let hash = 0;
for (let i = 0; i < normalized.length; i++) {
const char = normalized.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 32-bit Integer
}
return 'err_' + Math.abs(hash).toString(36);
}
// Phase 2.0: Auto-Fehler-Tracking — Fehler zählen und bei 3+ automatisch Pattern in KB speichern
async function trackErrorOccurrence(errorMessage: string, tool: string) {
try {
const errorHash = hashError(errorMessage);
const [count, existingKbId] = await invoke<[number, number | null]>('track_error', {
errorHash,
errorMessage: errorMessage.substring(0, 1000),
tool,
});
console.log(`📊 Fehler-Tracking: ${errorHash}${count}x (KB: ${existingKbId || 'noch nicht'})`);
// Bei 3+ Occurrences und noch kein KB-Eintrag: automatisch speichern
if (count >= 3 && !existingKbId) {
console.log(`🆕 Auto-Pattern: Fehler ${count}x aufgetreten, speichere in KB...`);
const kbId = await invoke<number>('auto_save_error_pattern', {
errorHash,
errorMessage: errorMessage.substring(0, 1000),
tool,
occurrenceCount: count,
});
// KB-ID zurückschreiben
await invoke('set_error_kb_pattern', { errorHash, kbPatternId: kbId });
addMonitorEvent('hook', `Auto-Pattern erstellt: KB #${kbId} (${count}x ${tool})`, {
errorHash,
kbId,
occurrenceCount: count,
tool,
});
}
} catch (err) {
// Fehler-Tracking ist komplett optional — niemals die App blockieren
console.debug('Fehler-Tracking fehlgeschlagen:', err);
}
}
// Phase 2.0: Proaktive KB-Abfrage bei Session-Erstellung
export async function loadProactiveSessionHints(projectName?: string): Promise<void> {
try {
const hints = await invoke<string>('get_session_hints', {
projectName: projectName || null,
});
if (hints && hints.length > 0) {
console.log('📋 Proaktive Session-Hints geladen:', hints.length, 'Bytes');
addMonitorEvent('hook', `Proaktive KB-Hints geladen (~${Math.ceil(hints.length / 4)} Token)`, {
projectName,
hintSize: hints.length,
});
}
} catch (err) {
console.debug('Proaktive Session-Hints nicht verfügbar:', err);
}
}
// Agent-Typ mappen
function mapAgentType(type: string): Agent['type'] {
const typeMap: Record<string, Agent['type']> = {