Compare commits
4 Commits
f7edc59abb
...
b3f7328778
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3f7328778 | ||
|
|
d50d1ab882 | ||
|
|
d974af3042 | ||
|
|
8a869f6990 |
@@ -41,6 +41,11 @@ pub(crate) struct MemoryRow {
|
||||
}
|
||||
|
||||
impl SqliteStorage {
|
||||
/// Get a reference to the underlying connection pool
|
||||
pub fn pool(&self) -> &SqlitePool {
|
||||
&self.pool
|
||||
}
|
||||
|
||||
/// Create a new SQLite storage at the given path
|
||||
pub async fn new(path: impl Into<PathBuf>) -> Result<Self> {
|
||||
let path = path.into();
|
||||
|
||||
@@ -85,6 +85,7 @@ impl AgentRegistry {
|
||||
system_prompt: config.system_prompt.clone(),
|
||||
temperature: config.temperature,
|
||||
max_tokens: config.max_tokens,
|
||||
user_profile: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +171,9 @@ pub struct AgentInfo {
|
||||
pub system_prompt: Option<String>,
|
||||
pub temperature: Option<f32>,
|
||||
pub max_tokens: Option<u32>,
|
||||
/// UserProfile from zclaw-memory UserProfileStore (populated on-demand by agent_get)
|
||||
#[serde(default)]
|
||||
pub user_profile: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl From<AgentConfig> for AgentInfo {
|
||||
@@ -189,6 +192,7 @@ impl From<AgentConfig> for AgentInfo {
|
||||
system_prompt: config.system_prompt,
|
||||
temperature: config.temperature,
|
||||
max_tokens: config.max_tokens,
|
||||
user_profile: None, // Populated on-demand by agent_get command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@ impl ReflectionEngine {
|
||||
|
||||
// Pattern: Too many tasks accumulating
|
||||
let task_count = *type_counts.get("task").unwrap_or(&0);
|
||||
if task_count >= 5 {
|
||||
if task_count >= 3 {
|
||||
let evidence: Vec<String> = memories
|
||||
.iter()
|
||||
.filter(|m| m.memory_type == "task")
|
||||
@@ -408,7 +408,7 @@ impl ReflectionEngine {
|
||||
|
||||
// Pattern: Strong preference accumulation
|
||||
let pref_count = *type_counts.get("preference").unwrap_or(&0);
|
||||
if pref_count >= 5 {
|
||||
if pref_count >= 3 {
|
||||
let evidence: Vec<String> = memories
|
||||
.iter()
|
||||
.filter(|m| m.memory_type == "preference")
|
||||
@@ -426,7 +426,7 @@ impl ReflectionEngine {
|
||||
|
||||
// Pattern: Many lessons learned
|
||||
let lesson_count = *type_counts.get("lesson").unwrap_or(&0);
|
||||
if lesson_count >= 5 {
|
||||
if lesson_count >= 3 {
|
||||
let evidence: Vec<String> = memories
|
||||
.iter()
|
||||
.filter(|m| m.memory_type == "lesson")
|
||||
@@ -447,7 +447,7 @@ impl ReflectionEngine {
|
||||
.iter()
|
||||
.filter(|m| m.access_count >= 5 && m.importance >= 7)
|
||||
.collect();
|
||||
if high_access.len() >= 3 {
|
||||
if high_access.len() >= 2 {
|
||||
let evidence: Vec<String> = high_access.iter().take(3).map(|m| m.content.clone()).collect();
|
||||
|
||||
patterns.push(PatternObservation {
|
||||
@@ -460,9 +460,9 @@ impl ReflectionEngine {
|
||||
|
||||
// Pattern: Low-importance memories accumulating
|
||||
let low_importance_count = memories.iter().filter(|m| m.importance <= 3).count();
|
||||
if low_importance_count > 20 {
|
||||
if low_importance_count > 15 {
|
||||
patterns.push(PatternObservation {
|
||||
observation: format!("有 {} 条低重要性记忆,建议清理", low_importance_count),
|
||||
observation: format!("有 {} 条低重要性记忆,可考虑清理", low_importance_count),
|
||||
frequency: low_importance_count,
|
||||
sentiment: Sentiment::Neutral,
|
||||
evidence: vec![],
|
||||
@@ -721,6 +721,18 @@ pub fn pop_restored_result(agent_id: &str) -> Option<ReflectionResult> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Peek restored state from cache (non-destructive read)
|
||||
pub fn peek_restored_state(agent_id: &str) -> Option<ReflectionState> {
|
||||
let cache = get_state_cache();
|
||||
cache.read().ok()?.get(agent_id).cloned()
|
||||
}
|
||||
|
||||
/// Peek restored result from cache (non-destructive read)
|
||||
pub fn peek_restored_result(agent_id: &str) -> Option<ReflectionResult> {
|
||||
let cache = get_result_cache();
|
||||
cache.read().ok()?.get(agent_id).cloned()
|
||||
}
|
||||
|
||||
// === Tauri Commands ===
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -112,12 +112,15 @@ pub async fn post_conversation_hook(
|
||||
// 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) {
|
||||
// Apply restored state on first call (peek-then-pop to avoid race with getHistory)
|
||||
if let Some(restored_state) = crate::intelligence::reflection::peek_restored_state(agent_id) {
|
||||
engine.apply_restored_state(restored_state);
|
||||
// Pop after successful apply to prevent re-processing
|
||||
crate::intelligence::reflection::pop_restored_state(agent_id);
|
||||
}
|
||||
if let Some(restored_result) = crate::intelligence::reflection::pop_restored_result(agent_id) {
|
||||
if let Some(restored_result) = crate::intelligence::reflection::peek_restored_result(agent_id) {
|
||||
engine.apply_restored_result(restored_result);
|
||||
crate::intelligence::reflection::pop_restored_result(agent_id);
|
||||
}
|
||||
|
||||
engine.record_conversation();
|
||||
|
||||
@@ -163,7 +163,7 @@ pub async fn agent_list(
|
||||
Ok(kernel.list_agents())
|
||||
}
|
||||
|
||||
/// Get agent info
|
||||
/// Get agent info (with optional UserProfile from memory store)
|
||||
// @connected
|
||||
#[tauri::command]
|
||||
pub async fn agent_get(
|
||||
@@ -180,7 +180,19 @@ pub async fn agent_get(
|
||||
let id: AgentId = agent_id.parse()
|
||||
.map_err(|_| "Invalid agent ID format".to_string())?;
|
||||
|
||||
Ok(kernel.get_agent(&id))
|
||||
let mut info = kernel.get_agent(&id);
|
||||
|
||||
// Extend with UserProfile if available
|
||||
if let Some(ref mut agent_info) = info {
|
||||
if let Ok(storage) = crate::viking_commands::get_storage().await {
|
||||
let profile_store = zclaw_memory::UserProfileStore::new(storage.pool().clone());
|
||||
if let Ok(Some(profile)) = profile_store.get(&agent_id).await {
|
||||
agent_info.user_profile = Some(serde_json::to_value(profile).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// Delete an agent
|
||||
|
||||
@@ -8,6 +8,8 @@ import { useConfigStore } from '../store/configStore';
|
||||
import { toChatAgent, useChatStore, type CodeBlock } from '../store/chatStore';
|
||||
import { useConversationStore } from '../store/chat/conversationStore';
|
||||
import { intelligenceClient, type IdentitySnapshot } from '../lib/intelligence-client';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { AgentInfo } from '../lib/kernel-types';
|
||||
import {
|
||||
Wifi, WifiOff, Bot, BarChart3, Plug, RefreshCw,
|
||||
MessageSquare, Cpu, FileText, User, Activity, Brain,
|
||||
@@ -143,6 +145,9 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
|
||||
const [restoringSnapshotId, setRestoringSnapshotId] = useState<string | null>(null);
|
||||
const [confirmRestoreId, setConfirmRestoreId] = useState<string | null>(null);
|
||||
|
||||
// UserProfile from memory store (dynamic, learned from conversations)
|
||||
const [userProfile, setUserProfile] = useState<Record<string, unknown> | null>(null);
|
||||
|
||||
const connected = connectionState === 'connected';
|
||||
const selectedClone = useMemo(
|
||||
() => clones.find((clone) => clone.id === currentAgent?.id),
|
||||
@@ -166,6 +171,28 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
|
||||
}
|
||||
}, [connected]);
|
||||
|
||||
// Fetch UserProfile from agent data (includes memory-learned profile)
|
||||
useEffect(() => {
|
||||
if (!currentAgent?.id) return;
|
||||
invoke<AgentInfo | null>('agent_get', { agentId: currentAgent.id })
|
||||
.then(data => setUserProfile(data?.userProfile ?? null))
|
||||
.catch(() => setUserProfile(null));
|
||||
}, [currentAgent?.id]);
|
||||
|
||||
// Listen for profile updates after conversations
|
||||
useEffect(() => {
|
||||
const handler = (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
if (detail?.agentId === currentAgent?.id && currentAgent?.id) {
|
||||
invoke<AgentInfo | null>('agent_get', { agentId: currentAgent.id })
|
||||
.then(data => setUserProfile(data?.userProfile ?? null))
|
||||
.catch(() => {});
|
||||
}
|
||||
};
|
||||
window.addEventListener('zclaw:agent-profile-updated', handler);
|
||||
return () => window.removeEventListener('zclaw:agent-profile-updated', handler);
|
||||
}, [currentAgent?.id]);
|
||||
|
||||
const handleReconnect = () => {
|
||||
connect().catch(silentErrorHandler('RightPanel'));
|
||||
};
|
||||
@@ -552,6 +579,24 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
|
||||
<AgentRow label="已解析" value={selectedClone?.workspaceResolvedPath || workspaceInfo?.resolvedPath || '-'} />
|
||||
<AgentRow label="文件限制" value={selectedClone?.restrictFiles ? '已开启' : '已关闭'} />
|
||||
<AgentRow label="隐私计划" value={selectedClone?.privacyOptIn ? '已加入' : '未加入'} />
|
||||
{/* Dynamic: UserProfile data (from conversation learning) */}
|
||||
{userProfile && (
|
||||
<div className="mt-3 pt-3 border-t border-gray-100 dark:border-gray-800">
|
||||
<div className="text-xs text-gray-400 mb-2">对话中了解到的</div>
|
||||
{userProfile.industry ? (
|
||||
<AgentRow label="行业" value={String(userProfile.industry)} />
|
||||
) : null}
|
||||
{userProfile.role ? (
|
||||
<AgentRow label="角色" value={String(userProfile.role)} />
|
||||
) : null}
|
||||
{userProfile.communicationStyle ? (
|
||||
<AgentRow label="沟通偏好" value={String(userProfile.communicationStyle)} />
|
||||
) : null}
|
||||
{Array.isArray(userProfile.recentTopics) && (userProfile.recentTopics as string[]).length > 0 ? (
|
||||
<AgentRow label="近期话题" value={(userProfile.recentTopics as string[]).slice(0, 5).join(', ')} />
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface AgentInfo {
|
||||
systemPrompt?: string;
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
userProfile?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface CreateAgentRequest {
|
||||
|
||||
@@ -487,6 +487,12 @@ export const useStreamStore = create<StreamState>()(
|
||||
getMemoryExtractor().extractFromConversation(filtered, agentId, convId ?? undefined).catch(err => {
|
||||
log.warn('Memory extraction failed:', err);
|
||||
});
|
||||
// Notify RightPanel to refresh UserProfile after memory extraction
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('zclaw:agent-profile-updated', {
|
||||
detail: { agentId }
|
||||
}));
|
||||
}
|
||||
intelligenceClient.reflection.recordConversation().catch(err => {
|
||||
log.warn('Recording conversation failed:', err);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user