Files
zclaw_openfang/desktop/src/domains/intelligence/store.ts
iven 32b9b41144 feat(domains): add Intelligence Domain with Valtio store and caching
- Create types.ts with frontend-friendly types
- Create cache.ts with LRU cache + TTL support
- Create store.ts wrapping intelligence-client with reactive state
- Create hooks.ts for React component integration
- Re-export backend types for compatibility

Intelligence Domain provides:
- Memory: store, search, delete with caching
- Heartbeat: init, start, stop, tick operations
- Compaction: threshold check and compact
- Reflection: conversation tracking and reflection
- Identity: load, build prompt, propose changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 20:01:47 +08:00

416 lines
12 KiB
TypeScript

/**
* Intelligence Domain Store
*
* Valtio-based state management for intelligence operations.
* Wraps intelligence-client with caching and reactive state.
*/
import { proxy } from 'valtio';
import { intelligenceClient } from '../../lib/intelligence-client';
import {
getIntelligenceCache,
memorySearchKey,
identityKey,
} from './cache';
import type {
IntelligenceStore,
IntelligenceState,
MemoryEntry,
MemoryType,
MemorySource,
MemorySearchOptions,
MemoryStats,
CacheStats,
} from './types';
// === Initial State ===
const initialState: IntelligenceState = {
// Memory
memories: [],
memoryStats: null,
isMemoryLoading: false,
// Heartbeat
heartbeatConfig: null,
heartbeatHistory: [],
isHeartbeatRunning: false,
// Compaction
lastCompaction: null,
compactionCheck: null,
// Reflection
reflectionState: null,
lastReflection: null,
// Identity
currentIdentity: null,
pendingProposals: [],
// Cache
cacheStats: {
entries: 0,
hits: 0,
misses: 0,
hitRate: 0,
},
// General
isLoading: false,
error: null,
};
// === Store Implementation ===
export const intelligenceStore = proxy<IntelligenceStore>({
...initialState,
// === Memory Actions ===
loadMemories: async (options: MemorySearchOptions): Promise<void> => {
const cache = getIntelligenceCache();
const key = memorySearchKey(options as Record<string, unknown>);
// Check cache first
const cached = cache.get<MemoryEntry[]>(key);
if (cached) {
intelligenceStore.memories = cached;
intelligenceStore.cacheStats = cache.getStats();
return;
}
intelligenceStore.isMemoryLoading = true;
intelligenceStore.error = null;
try {
const rawMemories = await intelligenceClient.memory.search({
agentId: options.agentId,
type: options.type,
tags: options.tags,
query: options.query,
limit: options.limit,
minImportance: options.minImportance,
});
// Convert to frontend format
const memories: MemoryEntry[] = rawMemories.map(m => ({
id: m.id,
agentId: m.agentId,
content: m.content,
type: m.type as MemoryType,
importance: m.importance,
source: m.source as MemorySource,
tags: m.tags,
createdAt: m.createdAt,
lastAccessedAt: m.lastAccessedAt,
accessCount: m.accessCount,
conversationId: m.conversationId,
}));
cache.set(key, memories);
intelligenceStore.memories = memories;
intelligenceStore.cacheStats = cache.getStats();
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to load memories';
} finally {
intelligenceStore.isMemoryLoading = false;
}
},
storeMemory: async (entry): Promise<string> => {
const cache = getIntelligenceCache();
try {
const id = await intelligenceClient.memory.store({
agent_id: entry.agentId,
memory_type: entry.type,
content: entry.content,
importance: entry.importance,
source: entry.source,
tags: entry.tags,
conversation_id: entry.conversationId,
});
// Invalidate relevant cache entries
cache.delete(memorySearchKey({ agentId: entry.agentId }));
return id;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to store memory';
throw err;
}
},
deleteMemory: async (id: string): Promise<void> => {
const cache = getIntelligenceCache();
try {
await intelligenceClient.memory.delete(id);
// Clear all memory search caches
cache.clear();
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to delete memory';
throw err;
}
},
loadMemoryStats: async (): Promise<void> => {
try {
const rawStats = await intelligenceClient.memory.stats();
const stats: MemoryStats = {
totalEntries: rawStats.totalEntries,
byType: rawStats.byType,
byAgent: rawStats.byAgent,
oldestEntry: rawStats.oldestEntry,
newestEntry: rawStats.newestEntry,
};
intelligenceStore.memoryStats = stats;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to load memory stats';
}
},
// === Heartbeat Actions ===
initHeartbeat: async (agentId: string, config?: import('../../lib/intelligence-backend').HeartbeatConfig): Promise<void> => {
try {
await intelligenceClient.heartbeat.init(agentId, config);
if (config) {
intelligenceStore.heartbeatConfig = config;
}
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to init heartbeat';
}
},
startHeartbeat: async (agentId: string): Promise<void> => {
try {
await intelligenceClient.heartbeat.start(agentId);
intelligenceStore.isHeartbeatRunning = true;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to start heartbeat';
}
},
stopHeartbeat: async (agentId: string): Promise<void> => {
try {
await intelligenceClient.heartbeat.stop(agentId);
intelligenceStore.isHeartbeatRunning = false;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to stop heartbeat';
}
},
tickHeartbeat: async (agentId: string): Promise<import('../../lib/intelligence-backend').HeartbeatResult> => {
try {
const result = await intelligenceClient.heartbeat.tick(agentId);
intelligenceStore.heartbeatHistory = [
result,
...intelligenceStore.heartbeatHistory.slice(0, 99),
];
return result;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Heartbeat tick failed';
throw err;
}
},
// === Compaction Actions ===
checkCompaction: async (messages: Array<{ id?: string; role: string; content: string; timestamp?: string }>): Promise<import('../../lib/intelligence-backend').CompactionCheck> => {
try {
const compactableMessages = messages.map(m => ({
id: m.id || `msg_${Date.now()}`,
role: m.role,
content: m.content,
timestamp: m.timestamp,
}));
const check = await intelligenceClient.compactor.checkThreshold(compactableMessages);
intelligenceStore.compactionCheck = check;
return check;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Compaction check failed';
throw err;
}
},
compact: async (
messages: Array<{ id?: string; role: string; content: string; timestamp?: string }>,
agentId: string,
conversationId?: string
): Promise<import('../../lib/intelligence-backend').CompactionResult> => {
try {
const compactableMessages = messages.map(m => ({
id: m.id || `msg_${Date.now()}`,
role: m.role,
content: m.content,
timestamp: m.timestamp,
}));
const result = await intelligenceClient.compactor.compact(
compactableMessages,
agentId,
conversationId
);
intelligenceStore.lastCompaction = result;
return result;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Compaction failed';
throw err;
}
},
// === Reflection Actions ===
recordConversation: async (): Promise<void> => {
try {
await intelligenceClient.reflection.recordConversation();
} catch (err) {
console.warn('[IntelligenceStore] Failed to record conversation:', err);
}
},
shouldReflect: async (): Promise<boolean> => {
try {
return intelligenceClient.reflection.shouldReflect();
} catch {
return false;
}
},
reflect: async (agentId: string): Promise<import('../../lib/intelligence-backend').ReflectionResult> => {
try {
// Get memories for reflection
const memories = await intelligenceClient.memory.search({
agentId,
limit: 50,
minImportance: 3,
});
const analysisMemories = memories.map(m => ({
id: m.id,
memory_type: m.type,
content: m.content,
importance: m.importance,
created_at: m.createdAt,
access_count: m.accessCount,
tags: m.tags,
}));
const result = await intelligenceClient.reflection.reflect(agentId, analysisMemories);
intelligenceStore.lastReflection = result;
// Invalidate caches
getIntelligenceCache().clear();
return result;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Reflection failed';
throw err;
}
},
// === Identity Actions ===
loadIdentity: async (agentId: string): Promise<void> => {
const cache = getIntelligenceCache();
const key = identityKey(agentId);
// Check cache
const cached = cache.get<import('../../lib/intelligence-backend').IdentityFiles>(key);
if (cached) {
intelligenceStore.currentIdentity = cached;
intelligenceStore.cacheStats = cache.getStats();
return;
}
try {
const identity = await intelligenceClient.identity.get(agentId);
cache.set(key, identity, 10 * 60 * 1000); // 10 minute TTL
intelligenceStore.currentIdentity = identity;
intelligenceStore.cacheStats = cache.getStats();
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to load identity';
}
},
buildPrompt: async (agentId: string, memoryContext?: string): Promise<string> => {
try {
return intelligenceClient.identity.buildPrompt(agentId, memoryContext);
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to build prompt';
throw err;
}
},
proposeIdentityChange: async (
agentId: string,
file: 'soul' | 'instructions',
content: string,
reason: string
): Promise<import('../../lib/intelligence-backend').IdentityChangeProposal> => {
try {
const proposal = await intelligenceClient.identity.proposeChange(
agentId,
file,
content,
reason
);
intelligenceStore.pendingProposals.push(proposal);
return proposal;
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to propose change';
throw err;
}
},
approveProposal: async (proposalId: string): Promise<void> => {
try {
const identity = await intelligenceClient.identity.approveProposal(proposalId);
intelligenceStore.pendingProposals = intelligenceStore.pendingProposals.filter(
p => p.id !== proposalId
);
intelligenceStore.currentIdentity = identity;
getIntelligenceCache().clear();
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to approve proposal';
throw err;
}
},
rejectProposal: async (proposalId: string): Promise<void> => {
try {
await intelligenceClient.identity.rejectProposal(proposalId);
intelligenceStore.pendingProposals = intelligenceStore.pendingProposals.filter(
p => p.id !== proposalId
);
} catch (err) {
intelligenceStore.error = err instanceof Error ? err.message : 'Failed to reject proposal';
throw err;
}
},
// === Cache Actions ===
clearCache: (): void => {
getIntelligenceCache().clear();
intelligenceStore.cacheStats = getIntelligenceCache().getStats();
},
getCacheStats: (): CacheStats => {
return getIntelligenceCache().getStats();
},
// === General Actions ===
clearError: (): void => {
intelligenceStore.error = null;
},
reset: (): void => {
Object.assign(intelligenceStore, initialState);
getIntelligenceCache().clear();
},
});
export type { IntelligenceStore };