feat: implement ZCLAW Agent Intelligence Evolution Phase 1-3

Phase 1: Persistent Memory + Identity Dynamic Evolution
- agent-memory.ts: MemoryManager with localStorage persistence, keyword search, deduplication, importance scoring, pruning, markdown export
- agent-identity.ts: AgentIdentityManager with per-agent SOUL/AGENTS/USER.md, change proposals with approval workflow, snapshot rollback
- memory-extractor.ts: Rule-based conversation memory extraction (Phase 1), LLM extraction prompt ready for Phase 2
- MemoryPanel.tsx: Memory browsing UI with search, type filter, delete, export (integrated as 4th tab in RightPanel)

Phase 2: Context Governance
- context-compactor.ts: Token estimation, threshold monitoring (soft/hard), memory flush before compaction, rule-based summarization
- chatStore integration: auto-compact when approaching token limits

Phase 3: Proactive Intelligence + Self-Reflection
- heartbeat-engine.ts: Periodic checks (pending tasks, memory health, idle greeting), quiet hours, proactivity levels (silent/light/standard/autonomous)
- reflection-engine.ts: Pattern analysis from memory corpus, improvement suggestions, identity change proposals, meta-memory creation

Chat Flow Integration (chatStore.ts):
- Pre-send: context compaction check -> memory search -> identity system prompt injection
- Post-complete: async memory extraction -> reflection conversation tracking -> auto-trigger reflection

Tests: 274 passing across 12 test files
- agent-memory.test.ts: 42 tests
- context-compactor.test.ts: 23 tests
- heartbeat-reflection.test.ts: 28 tests
- chatStore.test.ts: 11 tests (no regressions)

Refs: ZCLAW_AGENT_INTELLIGENCE_EVOLUTION.md updated with implementation progress
This commit is contained in:
iven
2026-03-15 22:24:57 +08:00
parent 4862e79b2b
commit 04ddf94123
13 changed files with 3949 additions and 26 deletions

View File

@@ -1,6 +1,11 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { getGatewayClient, AgentStreamDelta } from '../lib/gateway-client';
import { getMemoryManager } from '../lib/agent-memory';
import { getAgentIdentityManager } from '../lib/agent-identity';
import { getMemoryExtractor } from '../lib/memory-extractor';
import { getContextCompactor } from '../lib/context-compactor';
import { getReflectionEngine } from '../lib/reflection-engine';
export interface MessageFile {
name: string;
@@ -269,8 +274,54 @@ export const useChatStore = create<ChatState>()(
const { addMessage, currentAgent, sessionKey } = get();
const effectiveSessionKey = sessionKey || `session_${Date.now()}`;
const effectiveAgentId = resolveGatewayAgentId(currentAgent);
const agentId = currentAgent?.id || 'zclaw-main';
// Add user message
// Check context compaction threshold before adding new message
try {
const compactor = getContextCompactor();
const check = compactor.checkThreshold(get().messages.map(m => ({ role: m.role, content: m.content })));
if (check.shouldCompact) {
console.log(`[Chat] Context compaction triggered (${check.urgency}): ${check.currentTokens} tokens`);
const result = await compactor.compact(
get().messages.map(m => ({ role: m.role, content: m.content, id: m.id, timestamp: m.timestamp })),
agentId,
get().currentConversationId ?? undefined
);
// Replace messages with compacted version
const compactedMsgs: Message[] = result.compactedMessages.map((m, i) => ({
id: m.id || `compacted_${i}_${Date.now()}`,
role: m.role as Message['role'],
content: m.content,
timestamp: m.timestamp || new Date(),
}));
set({ messages: compactedMsgs });
}
} catch (err) {
console.warn('[Chat] Context compaction check failed:', err);
}
// Build memory-enhanced content
let enhancedContent = content;
try {
const memoryMgr = getMemoryManager();
const identityMgr = getAgentIdentityManager();
const relevantMemories = await memoryMgr.search(content, {
agentId,
limit: 8,
minImportance: 3,
});
const memoryContext = relevantMemories.length > 0
? `\n\n## 相关记忆\n${relevantMemories.map(m => `- [${m.type}] ${m.content}`).join('\n')}`
: '';
const systemPrompt = identityMgr.buildSystemPrompt(agentId, memoryContext);
if (systemPrompt) {
enhancedContent = `<context>\n${systemPrompt}\n</context>\n\n${content}`;
}
} catch (err) {
console.warn('[Chat] Memory enhancement failed, proceeding without:', err);
}
// Add user message (original content for display)
const userMsg: Message = {
id: `user_${Date.now()}`,
role: 'user',
@@ -297,7 +348,7 @@ export const useChatStore = create<ChatState>()(
// Try streaming first (OpenFang WebSocket)
if (client.getState() === 'connected') {
const { runId } = await client.chatStream(
content,
enhancedContent,
{
onDelta: (delta: string) => {
set((state) => ({
@@ -343,6 +394,21 @@ export const useChatStore = create<ChatState>()(
m.id === assistantId ? { ...m, streaming: false } : m
),
}));
// Async memory extraction after stream completes
const msgs = get().messages
.filter(m => m.role === 'user' || m.role === 'assistant')
.map(m => ({ role: m.role, content: m.content }));
getMemoryExtractor().extractFromConversation(msgs, agentId, get().currentConversationId ?? undefined).catch(err =>
console.warn('[Chat] Memory extraction failed:', err)
);
// Track conversation for reflection trigger
const reflectionEngine = getReflectionEngine();
reflectionEngine.recordConversation();
if (reflectionEngine.shouldReflect()) {
reflectionEngine.reflect(agentId).catch(err =>
console.warn('[Chat] Reflection failed:', err)
);
}
},
onError: (error: string) => {
set((state) => ({
@@ -375,7 +441,7 @@ export const useChatStore = create<ChatState>()(
}
// Fallback to REST API (non-streaming)
const result = await client.chat(content, {
const result = await client.chat(enhancedContent, {
sessionKey: effectiveSessionKey,
agentId: effectiveAgentId,
});