Files
zclaw_openfang/desktop/src-tauri/src/intelligence_hooks.rs
iven b7bc9ddcb1
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix(audit): 修复深度审计 P1/P2 问题 — 记忆统一、持久化、前端适配
H3: 重写 memory_commands.rs 统一到 VikingStorage 单一存储,移除双写
H4: 心跳引擎 record_interaction() 持久化到 VikingStorage,启动时恢复
M4: 反思结果/状态持久化到 VikingStorage metadata,重启后自动恢复
- HandApprovalModal import 修正 (handStore 替代 gatewayStore)
- kernel-client.ts 幽灵调用替换为 kernel_status
- PersistentMemoryStore dead_code warnings 清理
- 审计报告和 README 更新至 v0.6.3,完成度 58%→62%
2026-03-27 09:59:55 +08:00

207 lines
7.0 KiB
Rust

//! Intelligence Hooks - Pre/Post conversation integration
//!
//! Bridges the intelligence layer modules (identity, memory, heartbeat, reflection)
//! into the kernel's chat flow at the Tauri command boundary.
//!
//! Architecture: kernel_commands.rs → intelligence_hooks → intelligence modules → Viking/Kernel
use tracing::debug;
use crate::intelligence::identity::IdentityManagerState;
use crate::intelligence::heartbeat::HeartbeatEngineState;
use crate::intelligence::reflection::{MemoryEntryForAnalysis, ReflectionEngineState};
/// Run pre-conversation intelligence hooks
///
/// 1. Build memory context from VikingStorage (FTS5 + TF-IDF + Embedding)
/// 2. Build identity-enhanced system prompt (SOUL.md + instructions)
///
/// Returns the enhanced system prompt that should be passed to the kernel.
pub async fn pre_conversation_hook(
agent_id: &str,
user_message: &str,
identity_state: &IdentityManagerState,
) -> Result<String, String> {
// Step 1: Build memory context from Viking storage
let memory_context = build_memory_context(agent_id, user_message).await
.unwrap_or_default();
// Step 2: Build identity-enhanced system prompt
let enhanced_prompt = build_identity_prompt(agent_id, &memory_context, identity_state)
.await
.unwrap_or_default();
Ok(enhanced_prompt)
}
/// Run post-conversation intelligence hooks
///
/// 1. Record interaction for heartbeat engine
/// 2. Record conversation for reflection engine, trigger reflection if needed
pub async fn post_conversation_hook(
agent_id: &str,
_user_message: &str,
_heartbeat_state: &HeartbeatEngineState,
reflection_state: &ReflectionEngineState,
) {
// Step 1: Record interaction for heartbeat
crate::intelligence::heartbeat::record_interaction(agent_id);
debug!("[intelligence_hooks] Recorded interaction for agent: {}", agent_id);
// Step 2: Record conversation for reflection
let mut engine = reflection_state.lock().await;
// Apply restored state on first call (one-shot after app restart)
if let Some(restored_state) = crate::intelligence::reflection::pop_restored_state(agent_id) {
engine.apply_restored_state(restored_state);
}
if let Some(restored_result) = crate::intelligence::reflection::pop_restored_result(agent_id) {
engine.apply_restored_result(restored_result);
}
engine.record_conversation();
debug!(
"[intelligence_hooks] Conversation count updated for agent: {}",
agent_id
);
if engine.should_reflect() {
debug!(
"[intelligence_hooks] Reflection threshold reached for agent: {}",
agent_id
);
// Query actual memories from VikingStorage for reflection analysis
let memories = query_memories_for_reflection(agent_id).await
.unwrap_or_default();
debug!(
"[intelligence_hooks] Fetched {} memories for reflection",
memories.len()
);
let reflection_result = engine.reflect(agent_id, &memories);
debug!(
"[intelligence_hooks] Reflection completed: {} patterns, {} suggestions",
reflection_result.patterns.len(),
reflection_result.improvements.len()
);
}
}
/// Build memory context by searching VikingStorage for relevant memories
async fn build_memory_context(
agent_id: &str,
user_message: &str,
) -> Result<String, String> {
// Try Viking storage (has FTS5 + TF-IDF + Embedding)
let storage = crate::viking_commands::get_storage().await?;
// FindOptions from zclaw_growth
let options = zclaw_growth::FindOptions {
scope: Some(format!("agent://{}", agent_id)),
limit: Some(8),
min_similarity: Some(0.2),
};
// find is on the VikingStorage trait — call via trait to dispatch correctly
let results: Vec<zclaw_growth::MemoryEntry> =
zclaw_growth::VikingStorage::find(storage.as_ref(), user_message, options)
.await
.map_err(|e| format!("Memory search failed: {}", e))?;
if results.is_empty() {
return Ok(String::new());
}
// Format memories into context string
let mut context = String::from("## 相关记忆\n\n");
let mut token_estimate: usize = 0;
let max_tokens: usize = 500;
for entry in &results {
// Prefer overview (L1 summary) over full content
// overview is Option<String> — use as_deref to get Option<&str>
let overview_str = entry.overview.as_deref().unwrap_or("");
let text = if !overview_str.is_empty() {
overview_str
} else {
&entry.content
};
// Truncate long entries
let truncated = if text.len() > 100 {
format!("{}...", &text[..100])
} else {
text.to_string()
};
// Simple token estimate (~1.5 tokens per CJK char, ~0.25 per other)
let tokens: usize = truncated.chars()
.map(|c: char| if c.is_ascii() { 1 } else { 2 })
.sum();
if token_estimate + tokens > max_tokens {
break;
}
context.push_str(&format!("- [{}] {}\n", entry.memory_type, truncated));
token_estimate += tokens;
}
Ok(context)
}
/// Build identity-enhanced system prompt
async fn build_identity_prompt(
agent_id: &str,
memory_context: &str,
identity_state: &IdentityManagerState,
) -> Result<String, String> {
// IdentityManagerState is Arc<tokio::sync::Mutex<AgentIdentityManager>>
// tokio::sync::Mutex::lock() returns MutexGuard directly
let mut manager = identity_state.lock().await;
let prompt = manager.build_system_prompt(
agent_id,
if memory_context.is_empty() { None } else { Some(memory_context) },
);
Ok(prompt)
}
/// Query agent memories from VikingStorage and convert to MemoryEntryForAnalysis
/// for the reflection engine.
///
/// Fetches up to 50 recent memories scoped to the given agent, without token
/// truncation (unlike build_memory_context which is size-limited for prompts).
async fn query_memories_for_reflection(
agent_id: &str,
) -> Result<Vec<MemoryEntryForAnalysis>, String> {
let storage = crate::viking_commands::get_storage().await?;
let options = zclaw_growth::FindOptions {
scope: Some(format!("agent://{}", agent_id)),
limit: Some(50),
min_similarity: Some(0.0), // Fetch all, no similarity filter
};
let results: Vec<zclaw_growth::MemoryEntry> =
zclaw_growth::VikingStorage::find(storage.as_ref(), "", options)
.await
.map_err(|e| format!("Memory query for reflection failed: {}", e))?;
let memories: Vec<MemoryEntryForAnalysis> = results
.into_iter()
.map(|entry| MemoryEntryForAnalysis {
memory_type: entry.memory_type.to_string(),
content: entry.content,
importance: entry.importance as usize,
access_count: entry.access_count as usize,
tags: entry.keywords,
})
.collect();
Ok(memories)
}