- 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>
416 lines
12 KiB
TypeScript
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 };
|