From 32b9b411444c81b61fbfa4e25a565610fe6a27de Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 21 Mar 2026 20:01:47 +0800 Subject: [PATCH] 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 --- desktop/src/domains/intelligence/cache.ts | 212 +++++++++++ desktop/src/domains/intelligence/hooks.ts | 253 +++++++++++++ desktop/src/domains/intelligence/index.ts | 118 ++++++ desktop/src/domains/intelligence/store.ts | 415 ++++++++++++++++++++++ desktop/src/domains/intelligence/types.ts | 183 ++++++++++ 5 files changed, 1181 insertions(+) create mode 100644 desktop/src/domains/intelligence/cache.ts create mode 100644 desktop/src/domains/intelligence/hooks.ts create mode 100644 desktop/src/domains/intelligence/index.ts create mode 100644 desktop/src/domains/intelligence/store.ts create mode 100644 desktop/src/domains/intelligence/types.ts diff --git a/desktop/src/domains/intelligence/cache.ts b/desktop/src/domains/intelligence/cache.ts new file mode 100644 index 0000000..1b2876c --- /dev/null +++ b/desktop/src/domains/intelligence/cache.ts @@ -0,0 +1,212 @@ +/** + * Intelligence Domain Cache + * + * LRU cache with TTL support for intelligence operations. + * Reduces redundant API calls and improves responsiveness. + */ + +import type { CacheEntry, CacheStats } from './types'; + +/** + * Simple LRU cache with TTL support + */ +export class IntelligenceCache { + private cache = new Map>(); + private accessOrder: string[] = []; + private maxSize: number; + private defaultTTL: number; + + // Stats tracking + private hits = 0; + private misses = 0; + + constructor(options?: { maxSize?: number; defaultTTL?: number }) { + this.maxSize = options?.maxSize ?? 100; + this.defaultTTL = options?.defaultTTL ?? 5 * 60 * 1000; // 5 minutes default + } + + /** + * Get a value from cache + */ + get(key: string): T | null { + const entry = this.cache.get(key) as CacheEntry | undefined; + + if (!entry) { + this.misses++; + return null; + } + + // Check TTL + if (Date.now() > entry.timestamp + entry.ttl) { + this.cache.delete(key); + this.accessOrder = this.accessOrder.filter(k => k !== key); + this.misses++; + return null; + } + + // Update access order (move to end = most recently used) + this.accessOrder = this.accessOrder.filter(k => k !== key); + this.accessOrder.push(key); + + this.hits++; + return entry.data; + } + + /** + * Set a value in cache + */ + set(key: string, data: T, ttl?: number): void { + // Remove if exists (to update access order) + if (this.cache.has(key)) { + this.accessOrder = this.accessOrder.filter(k => k !== key); + } + + // Evict oldest if at capacity + while (this.cache.size >= this.maxSize && this.accessOrder.length > 0) { + const oldestKey = this.accessOrder.shift(); + if (oldestKey) { + this.cache.delete(oldestKey); + } + } + + this.cache.set(key, { + data, + timestamp: Date.now(), + ttl: ttl ?? this.defaultTTL, + }); + this.accessOrder.push(key); + } + + /** + * Check if key exists and is not expired + */ + has(key: string): boolean { + const entry = this.cache.get(key); + if (!entry) return false; + + if (Date.now() > entry.timestamp + entry.ttl) { + this.cache.delete(key); + this.accessOrder = this.accessOrder.filter(k => k !== key); + return false; + } + + return true; + } + + /** + * Delete a specific key + */ + delete(key: string): boolean { + if (this.cache.has(key)) { + this.cache.delete(key); + this.accessOrder = this.accessOrder.filter(k => k !== key); + return true; + } + return false; + } + + /** + * Clear all cache entries + */ + clear(): void { + this.cache.clear(); + this.accessOrder = []; + // Don't reset hits/misses to maintain historical stats + } + + /** + * Get cache statistics + */ + getStats(): CacheStats { + const total = this.hits + this.misses; + return { + entries: this.cache.size, + hits: this.hits, + misses: this.misses, + hitRate: total > 0 ? this.hits / total : 0, + }; + } + + /** + * Reset statistics + */ + resetStats(): void { + this.hits = 0; + this.misses = 0; + } + + /** + * Get all keys (for debugging) + */ + keys(): string[] { + return Array.from(this.cache.keys()); + } + + /** + * Get cache size + */ + get size(): number { + return this.cache.size; + } +} + +// === Cache Key Generators === + +/** + * Generate cache key for memory search + */ +export function memorySearchKey(options: Record): string { + const sorted = Object.entries(options) + .filter(([, v]) => v !== undefined) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([k, v]) => `${k}=${JSON.stringify(v)}`) + .join('&'); + return `memory:search:${sorted}`; +} + +/** + * Generate cache key for identity + */ +export function identityKey(agentId: string): string { + return `identity:${agentId}`; +} + +/** + * Generate cache key for heartbeat config + */ +export function heartbeatConfigKey(agentId: string): string { + return `heartbeat:config:${agentId}`; +} + +/** + * Generate cache key for reflection state + */ +export function reflectionStateKey(): string { + return 'reflection:state'; +} + +// === Singleton Instance === + +let cacheInstance: IntelligenceCache | null = null; + +/** + * Get the global cache instance + */ +export function getIntelligenceCache(): IntelligenceCache { + if (!cacheInstance) { + cacheInstance = new IntelligenceCache({ + maxSize: 200, + defaultTTL: 5 * 60 * 1000, // 5 minutes + }); + } + return cacheInstance; +} + +/** + * Clear the global cache instance + */ +export function clearIntelligenceCache(): void { + if (cacheInstance) { + cacheInstance.clear(); + } +} diff --git a/desktop/src/domains/intelligence/hooks.ts b/desktop/src/domains/intelligence/hooks.ts new file mode 100644 index 0000000..0b53cf6 --- /dev/null +++ b/desktop/src/domains/intelligence/hooks.ts @@ -0,0 +1,253 @@ +/** + * Intelligence Domain Hooks + * + * React hooks for accessing intelligence state with Valtio. + * Provides reactive access to memory, heartbeat, reflection, and identity. + */ +import { useSnapshot } from 'valtio'; +import { intelligenceStore } from './store'; +import type { MemoryEntry, CacheStats } from './types'; + +// === Memory Hooks === + +/** + * Hook to access memories list + */ +export function useMemories() { + const { memories } = useSnapshot(intelligenceStore); + return memories as readonly MemoryEntry[]; +} + +/** + * Hook to access memory stats + */ +export function useMemoryStats() { + const { memoryStats } = useSnapshot(intelligenceStore); + return memoryStats; +} + +/** + * Hook to check if memories are loading + */ +export function useMemoryLoading(): boolean { + const { isMemoryLoading } = useSnapshot(intelligenceStore); + return isMemoryLoading; +} + +// === Heartbeat Hooks === + +/** + * Hook to access heartbeat config + */ +export function useHeartbeatConfig() { + const { heartbeatConfig } = useSnapshot(intelligenceStore); + return heartbeatConfig; +} + +/** + * Hook to access heartbeat history + */ +export function useHeartbeatHistory() { + const { heartbeatHistory } = useSnapshot(intelligenceStore); + return heartbeatHistory; +} + +/** + * Hook to check if heartbeat is running + */ +export function useHeartbeatRunning(): boolean { + const { isHeartbeatRunning } = useSnapshot(intelligenceStore); + return isHeartbeatRunning; +} + +// === Compaction Hooks === + +/** + * Hook to access last compaction result + */ +export function useLastCompaction() { + const { lastCompaction } = useSnapshot(intelligenceStore); + return lastCompaction; +} + +/** + * Hook to access compaction check + */ +export function useCompactionCheck() { + const { compactionCheck } = useSnapshot(intelligenceStore); + return compactionCheck; +} + +// === Reflection Hooks === + +/** + * Hook to access reflection state + */ +export function useReflectionState() { + const { reflectionState } = useSnapshot(intelligenceStore); + return reflectionState; +} + +/** + * Hook to access last reflection result + */ +export function useLastReflection() { + const { lastReflection } = useSnapshot(intelligenceStore); + return lastReflection; +} + +// === Identity Hooks === + +/** + * Hook to access current identity + */ +export function useIdentity() { + const { currentIdentity } = useSnapshot(intelligenceStore); + return currentIdentity; +} + +/** + * Hook to access pending identity proposals + */ +export function usePendingProposals() { + const { pendingProposals } = useSnapshot(intelligenceStore); + return pendingProposals; +} + +// === Cache Hooks === + +/** + * Hook to access cache stats + */ +export function useCacheStats(): CacheStats { + const { cacheStats } = useSnapshot(intelligenceStore); + return cacheStats; +} + +// === General Hooks === + +/** + * Hook to check if any intelligence operation is loading + */ +export function useIntelligenceLoading(): boolean { + const { isLoading, isMemoryLoading } = useSnapshot(intelligenceStore); + return isLoading || isMemoryLoading; +} + +/** + * Hook to access intelligence error + */ +export function useIntelligenceError(): string | null { + const { error } = useSnapshot(intelligenceStore); + return error; +} + +/** + * Hook to access the full intelligence state snapshot + */ +export function useIntelligenceState() { + return useSnapshot(intelligenceStore); +} + +/** + * Hook to access intelligence actions + * Returns the store directly for calling actions. + * Does not cause re-renders. + */ +export function useIntelligenceActions() { + return intelligenceStore; +} + +// === Convenience Hooks === + +/** + * Hook for memory operations with loading state + */ +export function useMemoryOperations() { + const memories = useMemories(); + const isLoading = useMemoryLoading(); + const stats = useMemoryStats(); + const actions = useIntelligenceActions(); + + return { + memories, + isLoading, + stats, + loadMemories: actions.loadMemories, + storeMemory: actions.storeMemory, + deleteMemory: actions.deleteMemory, + loadStats: actions.loadMemoryStats, + }; +} + +/** + * Hook for heartbeat operations + */ +export function useHeartbeatOperations() { + const config = useHeartbeatConfig(); + const isRunning = useHeartbeatRunning(); + const history = useHeartbeatHistory(); + const actions = useIntelligenceActions(); + + return { + config, + isRunning, + history, + init: actions.initHeartbeat, + start: actions.startHeartbeat, + stop: actions.stopHeartbeat, + tick: actions.tickHeartbeat, + }; +} + +/** + * Hook for compaction operations + */ +export function useCompactionOperations() { + const lastCompaction = useLastCompaction(); + const check = useCompactionCheck(); + const actions = useIntelligenceActions(); + + return { + lastCompaction, + check, + checkThreshold: actions.checkCompaction, + compact: actions.compact, + }; +} + +/** + * Hook for reflection operations + */ +export function useReflectionOperations() { + const state = useReflectionState(); + const lastReflection = useLastReflection(); + const actions = useIntelligenceActions(); + + return { + state, + lastReflection, + recordConversation: actions.recordConversation, + shouldReflect: actions.shouldReflect, + reflect: actions.reflect, + }; +} + +/** + * Hook for identity operations + */ +export function useIdentityOperations() { + const identity = useIdentity(); + const pendingProposals = usePendingProposals(); + const actions = useIntelligenceActions(); + + return { + identity, + pendingProposals, + loadIdentity: actions.loadIdentity, + buildPrompt: actions.buildPrompt, + proposeChange: actions.proposeIdentityChange, + approveProposal: actions.approveProposal, + rejectProposal: actions.rejectProposal, + }; +} diff --git a/desktop/src/domains/intelligence/index.ts b/desktop/src/domains/intelligence/index.ts new file mode 100644 index 0000000..3d5b49b --- /dev/null +++ b/desktop/src/domains/intelligence/index.ts @@ -0,0 +1,118 @@ +/** + * Intelligence Domain + * + * Unified intelligence layer for memory, heartbeat, compaction, + * reflection, and identity management. + * + * @example + * // Using hooks + * import { useMemoryOperations, useIdentityOperations } from '@/domains/intelligence'; + * + * function IntelligenceComponent() { + * const { memories, loadMemories, storeMemory } = useMemoryOperations(); + * const { identity, loadIdentity } = useIdentityOperations(); + * + * useEffect(() => { + * loadMemories({ agentId: 'agent-1', limit: 10 }); + * loadIdentity('agent-1'); + * }, []); + * + * // ... + * } + * + * @example + * // Using store directly (outside React) + * import { intelligenceStore } from '@/domains/intelligence'; + * + * async function storeMemory(content: string) { + * await intelligenceStore.storeMemory({ + * agentId: 'agent-1', + * type: 'fact', + * content, + * importance: 5, + * source: 'user', + * tags: [], + * }); + * } + */ + +// Types - Domain-specific +export type { + MemoryEntry, + MemoryType, + MemorySource, + MemorySearchOptions, + MemoryStats, + + // Cache + CacheEntry, + CacheStats, + + // Store + IntelligenceState, + IntelligenceStore, +} from './types'; + +// Types - Re-exported from backend +export type { + HeartbeatConfig, + HeartbeatAlert, + HeartbeatResult, + CompactableMessage, + CompactionConfig, + CompactionCheck, + CompactionResult, + PatternObservation, + ImprovementSuggestion, + ReflectionResult, + ReflectionState, + ReflectionConfig, + MemoryEntryForAnalysis, + IdentityFiles, + IdentityChangeProposal, + IdentitySnapshot, +} from '../../lib/intelligence-backend'; + +// Store +export { intelligenceStore } from './store'; + +// Cache utilities +export { + IntelligenceCache, + getIntelligenceCache, + clearIntelligenceCache, + memorySearchKey, + identityKey, + heartbeatConfigKey, + reflectionStateKey, +} from './cache'; + +// Hooks - State accessors +export { + useMemories, + useMemoryStats, + useMemoryLoading, + useHeartbeatConfig, + useHeartbeatHistory, + useHeartbeatRunning, + useLastCompaction, + useCompactionCheck, + useReflectionState, + useLastReflection, + useIdentity, + usePendingProposals, + useCacheStats, + useIntelligenceLoading, + useIntelligenceError, + useIntelligenceState, + useIntelligenceActions, +} from './hooks'; + +// Hooks - Operation bundles +export { + useMemoryOperations, + useHeartbeatOperations, + useCompactionOperations, + useReflectionOperations, + useIdentityOperations, +} from './hooks'; diff --git a/desktop/src/domains/intelligence/store.ts b/desktop/src/domains/intelligence/store.ts new file mode 100644 index 0000000..f2fa73f --- /dev/null +++ b/desktop/src/domains/intelligence/store.ts @@ -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({ + ...initialState, + + // === Memory Actions === + + loadMemories: async (options: MemorySearchOptions): Promise => { + const cache = getIntelligenceCache(); + const key = memorySearchKey(options as Record); + + // Check cache first + const cached = cache.get(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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + try { + await intelligenceClient.reflection.recordConversation(); + } catch (err) { + console.warn('[IntelligenceStore] Failed to record conversation:', err); + } + }, + + shouldReflect: async (): Promise => { + try { + return intelligenceClient.reflection.shouldReflect(); + } catch { + return false; + } + }, + + reflect: async (agentId: string): Promise => { + 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 => { + const cache = getIntelligenceCache(); + const key = identityKey(agentId); + + // Check cache + const cached = cache.get(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 => { + 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 => { + 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 => { + 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 => { + 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 }; diff --git a/desktop/src/domains/intelligence/types.ts b/desktop/src/domains/intelligence/types.ts new file mode 100644 index 0000000..f569fbc --- /dev/null +++ b/desktop/src/domains/intelligence/types.ts @@ -0,0 +1,183 @@ +/** + * Intelligence Domain Types + * + * Re-exports types from intelligence-backend for consistency. + * Domain-specific extensions are added here. + */ + +// === Re-export Backend Types === + +export type { + MemoryEntryInput, + PersistentMemory, + MemorySearchOptions as BackendMemorySearchOptions, + MemoryStats as BackendMemoryStats, + HeartbeatConfig, + HeartbeatAlert, + HeartbeatResult, + CompactableMessage, + CompactionResult, + CompactionCheck, + CompactionConfig, + PatternObservation, + ImprovementSuggestion, + ReflectionResult, + ReflectionState, + ReflectionConfig, + MemoryEntryForAnalysis, + IdentityFiles, + IdentityChangeProposal, + IdentitySnapshot, +} from '../../lib/intelligence-backend'; + +// === Frontend-Specific Types === + +export type MemoryType = 'fact' | 'preference' | 'lesson' | 'context' | 'task'; +export type MemorySource = 'auto' | 'user' | 'reflection' | 'llm-reflection'; + +/** + * Frontend-friendly memory entry + */ +export interface MemoryEntry { + id: string; + agentId: string; + content: string; + type: MemoryType; + importance: number; + source: MemorySource; + tags: string[]; + createdAt: string; + lastAccessedAt: string; + accessCount: number; + conversationId?: string; +} + +/** + * Frontend memory search options + */ +export interface MemorySearchOptions { + agentId?: string; + type?: MemoryType; + types?: MemoryType[]; + tags?: string[]; + query?: string; + limit?: number; + minImportance?: number; +} + +/** + * Frontend memory stats + */ +export interface MemoryStats { + totalEntries: number; + byType: Record; + byAgent: Record; + oldestEntry: string | null; + newestEntry: string | null; +} + +// === Cache Types === + +export interface CacheEntry { + data: T; + timestamp: number; + ttl: number; +} + +export interface CacheStats { + entries: number; + hits: number; + misses: number; + hitRate: number; +} + +// === Store Types === + +export interface IntelligenceState { + // Memory + memories: MemoryEntry[]; + memoryStats: MemoryStats | null; + isMemoryLoading: boolean; + + // Heartbeat + heartbeatConfig: HeartbeatConfig | null; + heartbeatHistory: HeartbeatResult[]; + isHeartbeatRunning: boolean; + + // Compaction + lastCompaction: CompactionResult | null; + compactionCheck: CompactionCheck | null; + + // Reflection + reflectionState: ReflectionState | null; + lastReflection: ReflectionResult | null; + + // Identity + currentIdentity: IdentityFiles | null; + pendingProposals: IdentityChangeProposal[]; + + // Cache + cacheStats: CacheStats; + + // General + isLoading: boolean; + error: string | null; +} + +// Import types that need to be used in store interface +import type { + HeartbeatConfig, + HeartbeatResult, + CompactionResult, + CompactionCheck, + ReflectionState, + ReflectionResult, + IdentityFiles, + IdentityChangeProposal, +} from '../../lib/intelligence-backend'; + +export interface IntelligenceStore extends IntelligenceState { + // Memory Actions + loadMemories: (options: MemorySearchOptions) => Promise; + storeMemory: (entry: { + agentId: string; + type: MemoryType; + content: string; + importance: number; + source: MemorySource; + tags: string[]; + conversationId?: string; + }) => Promise; + deleteMemory: (id: string) => Promise; + loadMemoryStats: () => Promise; + + // Heartbeat Actions + initHeartbeat: (agentId: string, config?: HeartbeatConfig) => Promise; + startHeartbeat: (agentId: string) => Promise; + stopHeartbeat: (agentId: string) => Promise; + tickHeartbeat: (agentId: string) => Promise; + + // Compaction Actions + checkCompaction: (messages: Array<{ id?: string; role: string; content: string; timestamp?: string }>) => Promise; + compact: (messages: Array<{ id?: string; role: string; content: string; timestamp?: string }>, agentId: string, conversationId?: string) => Promise; + + // Reflection Actions + recordConversation: () => Promise; + shouldReflect: () => Promise; + reflect: (agentId: string) => Promise; + + // Identity Actions + loadIdentity: (agentId: string) => Promise; + buildPrompt: (agentId: string, memoryContext?: string) => Promise; + proposeIdentityChange: (agentId: string, file: 'soul' | 'instructions', content: string, reason: string) => Promise; + approveProposal: (proposalId: string) => Promise; + rejectProposal: (proposalId: string) => Promise; + + // Cache Actions + clearCache: () => void; + getCacheStats: () => CacheStats; + + // General + clearError: () => void; + reset: () => void; +}