UNPKG

@lanonasis/cli

Version:

Professional CLI for LanOnasis Memory as a Service (MaaS) with MCP support, seamless inline editing, and enterprise-grade security

302 lines (301 loc) 11.1 kB
/** * Memory State Management System * Implements comprehensive memory lifecycle management inspired by mem0's state system */ import { CLIConfig } from '../utils/config.js'; import { logger } from './logger.js'; export var MemoryState; (function (MemoryState) { MemoryState["ACTIVE"] = "active"; MemoryState["PAUSED"] = "paused"; MemoryState["ARCHIVED"] = "archived"; MemoryState["DELETED"] = "deleted"; })(MemoryState || (MemoryState = {})); export class MemoryStateManager { config; stateTransitions = []; constructor() { this.config = new CLIConfig(); } async initialize() { logger.info('Memory State Manager initialized'); } /** * Update memory state with validation and history tracking */ async updateMemoryState(memoryId, newState, reason, metadata) { try { // Get current memory state const memory = await this.getMemory(memoryId); if (!memory) { throw new Error(`Memory ${memoryId} not found`); } const currentState = memory.state; // Validate state transition if (!this.isValidTransition(currentState, newState)) { throw new Error(`Invalid state transition from ${currentState} to ${newState}`); } // Create state transition record const transition = { id: this.generateTransitionId(), memory_id: memoryId, from_state: currentState, to_state: newState, reason, metadata, timestamp: new Date().toISOString(), user_id: await this.getCurrentUserId() }; // Update memory state via API await this.updateMemoryViaAPI(memoryId, { state: newState, updated_at: transition.timestamp, archived_at: newState === MemoryState.ARCHIVED ? transition.timestamp : null, deleted_at: newState === MemoryState.DELETED ? transition.timestamp : null }); // Record transition this.stateTransitions.push(transition); logger.info('Memory state updated', { memoryId, fromState: currentState, toState: newState, reason }); return transition; } catch (error) { logger.error('Failed to update memory state', { error, memoryId, newState }); throw error; } } /** * Bulk state update operations */ async bulkUpdateState(memoryIds, operation) { const results = []; const targetState = this.operationToState(operation); for (const memoryId of memoryIds) { try { const memory = await this.getMemory(memoryId); if (!memory) { results.push({ memory_id: memoryId, success: false, previous_state: MemoryState.ACTIVE, new_state: targetState, error: 'Memory not found' }); continue; } const previousState = memory.state; // Skip if already in target state if (previousState === targetState) { results.push({ memory_id: memoryId, success: true, previous_state: previousState, new_state: targetState }); continue; } // Perform state transition await this.updateMemoryState(memoryId, targetState, `Bulk ${operation} operation`); results.push({ memory_id: memoryId, success: true, previous_state: previousState, new_state: targetState }); } catch (error) { results.push({ memory_id: memoryId, success: false, previous_state: MemoryState.ACTIVE, new_state: targetState, error: error instanceof Error ? error.message : 'Unknown error' }); } } logger.info('Bulk state update completed', { operation, totalMemories: memoryIds.length, successful: results.filter(r => r.success).length, failed: results.filter(r => !r.success).length }); return results; } /** * Get memory state history */ getMemoryStateHistory(memoryId) { return this.stateTransitions .filter(t => t.memory_id === memoryId) .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); } /** * Get memories by state */ async getMemoriesByState(state, userId, appId, limit = 100) { try { const params = new URLSearchParams({ state, limit: limit.toString(), ...(userId && { user_id: userId }), ...(appId && { app_id: appId }) }); const memories = await this.callMemoryAPI('GET', `/memory?${params}`); return memories.memories || []; } catch (error) { logger.error('Failed to get memories by state', { error, state, userId, appId }); return []; } } /** * Archive old memories based on policy */ async archiveOldMemories(beforeDate, userId, appId) { try { // Get memories created before the specified date const params = new URLSearchParams({ before: beforeDate, state: MemoryState.ACTIVE, ...(userId && { user_id: userId }), ...(appId && { app_id: appId }) }); const memories = await this.callMemoryAPI('GET', `/memory?${params}`); const memoryIds = memories.memories?.map((m) => m.id) || []; if (memoryIds.length === 0) { return []; } return await this.bulkUpdateState(memoryIds, 'archive'); } catch (error) { logger.error('Failed to archive old memories', { error, beforeDate, userId, appId }); return []; } } /** * Restore memories from archived/paused state */ async restoreMemories(memoryIds) { const results = []; for (const memoryId of memoryIds) { try { const memory = await this.getMemory(memoryId); if (!memory) { results.push({ memory_id: memoryId, success: false, previous_state: MemoryState.ARCHIVED, new_state: MemoryState.ACTIVE, error: 'Memory not found' }); continue; } const previousState = memory.state; // Only restore from paused or archived states if (previousState !== MemoryState.PAUSED && previousState !== MemoryState.ARCHIVED) { results.push({ memory_id: memoryId, success: false, previous_state: previousState, new_state: MemoryState.ACTIVE, error: `Cannot restore from ${previousState} state` }); continue; } await this.updateMemoryState(memoryId, MemoryState.ACTIVE, 'Memory restoration'); results.push({ memory_id: memoryId, success: true, previous_state: previousState, new_state: MemoryState.ACTIVE }); } catch (error) { results.push({ memory_id: memoryId, success: false, previous_state: MemoryState.ARCHIVED, new_state: MemoryState.ACTIVE, error: error instanceof Error ? error.message : 'Unknown error' }); } } return results; } /** * Private helper methods */ isValidTransition(fromState, toState) { const validTransitions = { [MemoryState.ACTIVE]: [MemoryState.PAUSED, MemoryState.ARCHIVED, MemoryState.DELETED], [MemoryState.PAUSED]: [MemoryState.ACTIVE, MemoryState.ARCHIVED, MemoryState.DELETED], [MemoryState.ARCHIVED]: [MemoryState.ACTIVE, MemoryState.DELETED], [MemoryState.DELETED]: [] // No transitions from deleted state }; return validTransitions[fromState]?.includes(toState) || false; } operationToState(operation) { switch (operation) { case 'pause': return MemoryState.PAUSED; case 'archive': return MemoryState.ARCHIVED; case 'delete': return MemoryState.DELETED; default: throw new Error(`Unknown operation: ${operation}`); } } async getMemory(memoryId) { try { const response = await this.callMemoryAPI('GET', `/memory/${memoryId}`); return response; } catch (error) { logger.error('Failed to get memory', { error, memoryId }); return null; } } async updateMemoryViaAPI(memoryId, updates) { await this.callMemoryAPI('PUT', `/memory/${memoryId}`, updates); } async callMemoryAPI(method, endpoint, data) { const apiUrl = this.config.get('apiUrl') || 'https://api.lanonasis.com'; const token = this.config.get('token'); const axios = (await import('axios')).default; const response = await axios({ method, url: `${apiUrl}/api/v1${endpoint}`, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, data }); return response.data; } async getCurrentUserId() { const token = this.config.get('token'); if (token) { try { const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()); return payload.sub || payload.user_id || 'anonymous'; } catch { return 'anonymous'; } } return 'anonymous'; } generateTransitionId() { return `transition_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; } }