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>
This commit is contained in:
415
desktop/src/domains/intelligence/store.ts
Normal file
415
desktop/src/domains/intelligence/store.ts
Normal file
@@ -0,0 +1,415 @@
|
||||
/**
|
||||
* 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 };
|
||||
Reference in New Issue
Block a user