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:
212
desktop/src/domains/intelligence/cache.ts
Normal file
212
desktop/src/domains/intelligence/cache.ts
Normal file
@@ -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<string, CacheEntry<unknown>>();
|
||||
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<T>(key: string): T | null {
|
||||
const entry = this.cache.get(key) as CacheEntry<T> | 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<T>(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, unknown>): 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();
|
||||
}
|
||||
}
|
||||
253
desktop/src/domains/intelligence/hooks.ts
Normal file
253
desktop/src/domains/intelligence/hooks.ts
Normal file
@@ -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,
|
||||
};
|
||||
}
|
||||
118
desktop/src/domains/intelligence/index.ts
Normal file
118
desktop/src/domains/intelligence/index.ts
Normal file
@@ -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';
|
||||
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 };
|
||||
183
desktop/src/domains/intelligence/types.ts
Normal file
183
desktop/src/domains/intelligence/types.ts
Normal file
@@ -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<string, number>;
|
||||
byAgent: Record<string, number>;
|
||||
oldestEntry: string | null;
|
||||
newestEntry: string | null;
|
||||
}
|
||||
|
||||
// === Cache Types ===
|
||||
|
||||
export interface CacheEntry<T> {
|
||||
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<void>;
|
||||
storeMemory: (entry: {
|
||||
agentId: string;
|
||||
type: MemoryType;
|
||||
content: string;
|
||||
importance: number;
|
||||
source: MemorySource;
|
||||
tags: string[];
|
||||
conversationId?: string;
|
||||
}) => Promise<string>;
|
||||
deleteMemory: (id: string) => Promise<void>;
|
||||
loadMemoryStats: () => Promise<void>;
|
||||
|
||||
// Heartbeat Actions
|
||||
initHeartbeat: (agentId: string, config?: HeartbeatConfig) => Promise<void>;
|
||||
startHeartbeat: (agentId: string) => Promise<void>;
|
||||
stopHeartbeat: (agentId: string) => Promise<void>;
|
||||
tickHeartbeat: (agentId: string) => Promise<HeartbeatResult>;
|
||||
|
||||
// Compaction Actions
|
||||
checkCompaction: (messages: Array<{ id?: string; role: string; content: string; timestamp?: string }>) => Promise<CompactionCheck>;
|
||||
compact: (messages: Array<{ id?: string; role: string; content: string; timestamp?: string }>, agentId: string, conversationId?: string) => Promise<CompactionResult>;
|
||||
|
||||
// Reflection Actions
|
||||
recordConversation: () => Promise<void>;
|
||||
shouldReflect: () => Promise<boolean>;
|
||||
reflect: (agentId: string) => Promise<ReflectionResult>;
|
||||
|
||||
// Identity Actions
|
||||
loadIdentity: (agentId: string) => Promise<void>;
|
||||
buildPrompt: (agentId: string, memoryContext?: string) => Promise<string>;
|
||||
proposeIdentityChange: (agentId: string, file: 'soul' | 'instructions', content: string, reason: string) => Promise<IdentityChangeProposal>;
|
||||
approveProposal: (proposalId: string) => Promise<void>;
|
||||
rejectProposal: (proposalId: string) => Promise<void>;
|
||||
|
||||
// Cache Actions
|
||||
clearCache: () => void;
|
||||
getCacheStats: () => CacheStats;
|
||||
|
||||
// General
|
||||
clearError: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
Reference in New Issue
Block a user