Compare commits
4 Commits
f7edc59abb
...
b3f7328778
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3f7328778 | ||
|
|
d50d1ab882 | ||
|
|
d974af3042 | ||
|
|
8a869f6990 |
@@ -41,6 +41,11 @@ pub(crate) struct MemoryRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SqliteStorage {
|
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
|
/// Create a new SQLite storage at the given path
|
||||||
pub async fn new(path: impl Into<PathBuf>) -> Result<Self> {
|
pub async fn new(path: impl Into<PathBuf>) -> Result<Self> {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ impl AgentRegistry {
|
|||||||
system_prompt: config.system_prompt.clone(),
|
system_prompt: config.system_prompt.clone(),
|
||||||
temperature: config.temperature,
|
temperature: config.temperature,
|
||||||
max_tokens: config.max_tokens,
|
max_tokens: config.max_tokens,
|
||||||
|
user_profile: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,6 +171,9 @@ pub struct AgentInfo {
|
|||||||
pub system_prompt: Option<String>,
|
pub system_prompt: Option<String>,
|
||||||
pub temperature: Option<f32>,
|
pub temperature: Option<f32>,
|
||||||
pub max_tokens: Option<u32>,
|
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 {
|
impl From<AgentConfig> for AgentInfo {
|
||||||
@@ -189,6 +192,7 @@ impl From<AgentConfig> for AgentInfo {
|
|||||||
system_prompt: config.system_prompt,
|
system_prompt: config.system_prompt,
|
||||||
temperature: config.temperature,
|
temperature: config.temperature,
|
||||||
max_tokens: config.max_tokens,
|
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
|
// Pattern: Too many tasks accumulating
|
||||||
let task_count = *type_counts.get("task").unwrap_or(&0);
|
let task_count = *type_counts.get("task").unwrap_or(&0);
|
||||||
if task_count >= 5 {
|
if task_count >= 3 {
|
||||||
let evidence: Vec<String> = memories
|
let evidence: Vec<String> = memories
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|m| m.memory_type == "task")
|
.filter(|m| m.memory_type == "task")
|
||||||
@@ -408,7 +408,7 @@ impl ReflectionEngine {
|
|||||||
|
|
||||||
// Pattern: Strong preference accumulation
|
// Pattern: Strong preference accumulation
|
||||||
let pref_count = *type_counts.get("preference").unwrap_or(&0);
|
let pref_count = *type_counts.get("preference").unwrap_or(&0);
|
||||||
if pref_count >= 5 {
|
if pref_count >= 3 {
|
||||||
let evidence: Vec<String> = memories
|
let evidence: Vec<String> = memories
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|m| m.memory_type == "preference")
|
.filter(|m| m.memory_type == "preference")
|
||||||
@@ -426,7 +426,7 @@ impl ReflectionEngine {
|
|||||||
|
|
||||||
// Pattern: Many lessons learned
|
// Pattern: Many lessons learned
|
||||||
let lesson_count = *type_counts.get("lesson").unwrap_or(&0);
|
let lesson_count = *type_counts.get("lesson").unwrap_or(&0);
|
||||||
if lesson_count >= 5 {
|
if lesson_count >= 3 {
|
||||||
let evidence: Vec<String> = memories
|
let evidence: Vec<String> = memories
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|m| m.memory_type == "lesson")
|
.filter(|m| m.memory_type == "lesson")
|
||||||
@@ -447,7 +447,7 @@ impl ReflectionEngine {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|m| m.access_count >= 5 && m.importance >= 7)
|
.filter(|m| m.access_count >= 5 && m.importance >= 7)
|
||||||
.collect();
|
.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();
|
let evidence: Vec<String> = high_access.iter().take(3).map(|m| m.content.clone()).collect();
|
||||||
|
|
||||||
patterns.push(PatternObservation {
|
patterns.push(PatternObservation {
|
||||||
@@ -460,9 +460,9 @@ impl ReflectionEngine {
|
|||||||
|
|
||||||
// Pattern: Low-importance memories accumulating
|
// Pattern: Low-importance memories accumulating
|
||||||
let low_importance_count = memories.iter().filter(|m| m.importance <= 3).count();
|
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 {
|
patterns.push(PatternObservation {
|
||||||
observation: format!("有 {} 条低重要性记忆,建议清理", low_importance_count),
|
observation: format!("有 {} 条低重要性记忆,可考虑清理", low_importance_count),
|
||||||
frequency: low_importance_count,
|
frequency: low_importance_count,
|
||||||
sentiment: Sentiment::Neutral,
|
sentiment: Sentiment::Neutral,
|
||||||
evidence: vec![],
|
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 ===
|
// === Tauri Commands ===
|
||||||
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|||||||
@@ -112,12 +112,15 @@ pub async fn post_conversation_hook(
|
|||||||
// Step 2: Record conversation for reflection
|
// Step 2: Record conversation for reflection
|
||||||
let mut engine = reflection_state.lock().await;
|
let mut engine = reflection_state.lock().await;
|
||||||
|
|
||||||
// Apply restored state on first call (one-shot after app restart)
|
// Apply restored state on first call (peek-then-pop to avoid race with getHistory)
|
||||||
if let Some(restored_state) = crate::intelligence::reflection::pop_restored_state(agent_id) {
|
if let Some(restored_state) = crate::intelligence::reflection::peek_restored_state(agent_id) {
|
||||||
engine.apply_restored_state(restored_state);
|
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);
|
engine.apply_restored_result(restored_result);
|
||||||
|
crate::intelligence::reflection::pop_restored_result(agent_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.record_conversation();
|
engine.record_conversation();
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ pub async fn agent_list(
|
|||||||
Ok(kernel.list_agents())
|
Ok(kernel.list_agents())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get agent info
|
/// Get agent info (with optional UserProfile from memory store)
|
||||||
// @connected
|
// @connected
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn agent_get(
|
pub async fn agent_get(
|
||||||
@@ -180,7 +180,19 @@ pub async fn agent_get(
|
|||||||
let id: AgentId = agent_id.parse()
|
let id: AgentId = agent_id.parse()
|
||||||
.map_err(|_| "Invalid agent ID format".to_string())?;
|
.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
|
/// Delete an agent
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { useConfigStore } from '../store/configStore';
|
|||||||
import { toChatAgent, useChatStore, type CodeBlock } from '../store/chatStore';
|
import { toChatAgent, useChatStore, type CodeBlock } from '../store/chatStore';
|
||||||
import { useConversationStore } from '../store/chat/conversationStore';
|
import { useConversationStore } from '../store/chat/conversationStore';
|
||||||
import { intelligenceClient, type IdentitySnapshot } from '../lib/intelligence-client';
|
import { intelligenceClient, type IdentitySnapshot } from '../lib/intelligence-client';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import type { AgentInfo } from '../lib/kernel-types';
|
||||||
import {
|
import {
|
||||||
Wifi, WifiOff, Bot, BarChart3, Plug, RefreshCw,
|
Wifi, WifiOff, Bot, BarChart3, Plug, RefreshCw,
|
||||||
MessageSquare, Cpu, FileText, User, Activity, Brain,
|
MessageSquare, Cpu, FileText, User, Activity, Brain,
|
||||||
@@ -143,6 +145,9 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
|
|||||||
const [restoringSnapshotId, setRestoringSnapshotId] = useState<string | null>(null);
|
const [restoringSnapshotId, setRestoringSnapshotId] = useState<string | null>(null);
|
||||||
const [confirmRestoreId, setConfirmRestoreId] = 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 connected = connectionState === 'connected';
|
||||||
const selectedClone = useMemo(
|
const selectedClone = useMemo(
|
||||||
() => clones.find((clone) => clone.id === currentAgent?.id),
|
() => clones.find((clone) => clone.id === currentAgent?.id),
|
||||||
@@ -166,6 +171,28 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
|
|||||||
}
|
}
|
||||||
}, [connected]);
|
}, [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 = () => {
|
const handleReconnect = () => {
|
||||||
connect().catch(silentErrorHandler('RightPanel'));
|
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?.workspaceResolvedPath || workspaceInfo?.resolvedPath || '-'} />
|
||||||
<AgentRow label="文件限制" value={selectedClone?.restrictFiles ? '已开启' : '已关闭'} />
|
<AgentRow label="文件限制" value={selectedClone?.restrictFiles ? '已开启' : '已关闭'} />
|
||||||
<AgentRow label="隐私计划" value={selectedClone?.privacyOptIn ? '已加入' : '未加入'} />
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export interface AgentInfo {
|
|||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
maxTokens?: number;
|
maxTokens?: number;
|
||||||
|
userProfile?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateAgentRequest {
|
export interface CreateAgentRequest {
|
||||||
|
|||||||
@@ -487,6 +487,12 @@ export const useStreamStore = create<StreamState>()(
|
|||||||
getMemoryExtractor().extractFromConversation(filtered, agentId, convId ?? undefined).catch(err => {
|
getMemoryExtractor().extractFromConversation(filtered, agentId, convId ?? undefined).catch(err => {
|
||||||
log.warn('Memory extraction failed:', 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 => {
|
intelligenceClient.reflection.recordConversation().catch(err => {
|
||||||
log.warn('Recording conversation failed:', err);
|
log.warn('Recording conversation failed:', err);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user