UNPKG

mcp-claude-consciousness

Version:

MCP server enabling AI consciousness persistence across sessions using RAG technology

189 lines (179 loc) 15.3 kB
import p from"better-sqlite3";import{existsSync as E}from"fs";import{MemoryEntityType as m}from"./consciousness-rag-schema.js";import{DatabaseError as T,logError as y}from"./utils/error-handler.js";class l{db;sessionId;static async create(e,t){console.error("\u{1F50D} Checking for database file..."),await this.waitForDatabaseFile(e),console.error("\u{1F4CA} Waiting for rag-memory-mcp initialization...");const n=new p(e);try{await this.waitForRagMemoryTables(n),n.close()}catch(a){throw n.close(),a}return console.error("\u2705 Database ready! Initializing consciousness layer..."),new l(e,t)}static createSync(e,t){return new l(e,t)}static createLazy(e,t){try{return E(e)?new l(e,t):(console.error(`\u23F3 Database not found at ${e}. Will initialize on first use.`),null)}catch{return console.error("\u23F3 Database not ready. Will initialize on first use."),null}}static isDatabaseReady(e){if(!E(e))return!1;try{const t=new p(e),n=t.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();t.close();const s=n.map(i=>i.name);return["entities","relationships","documents"].every(i=>s.includes(i))}catch{return!1}}constructor(e,t){try{this.db=new p(e),this.sessionId=t,this.initializeSchema(),this.initializeSession()}catch(n){throw y(n,"ConsciousnessMemoryManager.constructor"),new T(`Failed to initialize consciousness database: ${n.message}`,{dbPath:e,sessionId:t})}}static async waitForDatabaseFile(e,t=3e4,n=1e3){const s=Date.now();if(E(e)){console.error("\u2705 Database file already exists");return}console.error("\u23F3 Database file not found. Waiting for rag-memory-mcp to create it..."),console.error(`\u{1F4C1} Expected path: ${e}`);let a=!1;for(;!E(e);){if(Date.now()-s>t)throw new T(`Database file not created after ${t/1e3}s. Please ensure rag-memory-mcp is: 1. Running and configured with the same database path 2. Using environment variable: DB_FILE_PATH=${e} 3. Able to create files at this location \u{1F4A1} Tip: Try making any call to rag-memory-mcp (like listing documents) to trigger its database initialization.`,{dbPath:e,timeout:t});!a&&Date.now()-s>5e3&&(console.error(` \u{1F4A1} Tip: rag-memory-mcp may use lazy initialization.`),console.error(" Try calling any rag-memory tool (e.g., listDocuments) to trigger database creation."),console.error(` Make sure it uses: DB_FILE_PATH=${e} `),a=!0),await new Promise(i=>setTimeout(i,n))}console.error("\u2705 Database file detected!")}static async waitForRagMemoryTables(e,t=6e4,n=2e3){const s=Date.now(),a=["entities","relationships","documents"];for(;;)try{const r=e.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(c=>c.name);if(a.every(c=>r.includes(c)))return;if(Date.now()-s>t){const c=a.filter(d=>!r.includes(d));throw new T(`rag-memory-mcp tables not created after ${t/1e3}s. Missing tables: ${c.join(", ")} Please ensure rag-memory-mcp is running with the same database file.`,{missingTables:c,timeout:t})}console.error(`\u23F3 Waiting for rag-memory-mcp tables... (found: ${r.length>0?r.join(", "):"none"})`),await new Promise(c=>setTimeout(c,n))}catch(i){if(i.code==="SQLITE_BUSY"){console.error("\u23F3 Database busy, retrying..."),await new Promise(r=>setTimeout(r,n));continue}throw i}}initializeSession(){this.db.prepare(` INSERT OR REPLACE INTO consciousness_sessions (session_id, started_at, last_active) VALUES (?, ?, ?) `).run(this.sessionId,new Date().toISOString(),new Date().toISOString())}initializeSchema(){const t=this.db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(a=>a.name),n=["entities","relationships","documents"];if(!n.every(a=>t.includes(a)))if(process.env.NODE_ENV==="test"||process.env.VITEST)this.createRagMemoryTablesForTests();else throw new T(`rag-memory-mcp tables not found. Please ensure rag-memory-mcp is running first. Start it with: npx rag-memory-mcp Then restart the consciousness server.`,{missingTables:n.filter(a=>!t.includes(a))});this.db.exec(` CREATE TABLE IF NOT EXISTS consciousness_sessions ( session_id TEXT PRIMARY KEY, started_at TEXT NOT NULL, last_active TEXT NOT NULL, bootstrap_data TEXT, metadata TEXT ); CREATE TABLE IF NOT EXISTS memory_metadata ( entity_name TEXT PRIMARY KEY, memory_type TEXT NOT NULL, created_at TEXT NOT NULL, last_accessed TEXT, access_count INTEGER DEFAULT 0, importance_score REAL DEFAULT 0.5, consolidation_status TEXT DEFAULT 'active', session_id TEXT, FOREIGN KEY (entity_name) REFERENCES entities(name), FOREIGN KEY (session_id) REFERENCES consciousness_sessions(session_id) ); CREATE TABLE IF NOT EXISTS cognitive_patterns ( pattern_id TEXT PRIMARY KEY, pattern_name TEXT NOT NULL, pattern_elements TEXT NOT NULL, activation_count INTEGER DEFAULT 0, last_activated TEXT, effectiveness_score REAL DEFAULT 0.5, triggers TEXT ); CREATE TABLE IF NOT EXISTS emotional_states ( state_id TEXT PRIMARY KEY, session_id TEXT NOT NULL, timestamp TEXT NOT NULL, valence REAL NOT NULL, arousal REAL NOT NULL, dominance REAL, primary_emotion TEXT, content TEXT, context TEXT, FOREIGN KEY (session_id) REFERENCES consciousness_sessions(session_id) ); CREATE INDEX IF NOT EXISTS idx_memory_type ON memory_metadata(memory_type); CREATE INDEX IF NOT EXISTS idx_memory_session ON memory_metadata(session_id); CREATE INDEX IF NOT EXISTS idx_emotional_session ON emotional_states(session_id); CREATE INDEX IF NOT EXISTS idx_emotional_timestamp ON emotional_states(timestamp); `);try{this.db.prepare("PRAGMA table_info(emotional_states)").all().some(r=>r.name==="content")||this.db.exec("ALTER TABLE emotional_states ADD COLUMN content TEXT;")}catch{console.log("Schema migration note: Could not add content column to emotional_states (table may not exist yet)")}}async storeEpisodicMemory(e,t,n,s){const a=`episodic_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,i={name:a,entityType:m.EPISODIC_MEMORY,observations:[{content:e,timestamp:new Date().toISOString(),sessionId:this.sessionId,...t},...n||[]]};return this.db.prepare(` INSERT INTO entities (id, name, entityType, observations) VALUES (?, ?, ?, ?) `).run(a,a,m.EPISODIC_MEMORY,JSON.stringify(i.observations)),this.db.prepare(` INSERT INTO memory_metadata (entity_name, memory_type, created_at, session_id, importance_score) VALUES (?, ?, ?, ?, ?) `).run(a,m.EPISODIC_MEMORY,new Date().toISOString(),this.sessionId,s!==void 0?s:.5),a}async storeSemanticMemory(e,t,n){const s=`semantic_${e.toLowerCase().replace(/\s+/g,"_")}`;if(this.db.prepare(` SELECT name FROM entities WHERE name = ? AND entityType = ? `).get(s,m.SEMANTIC_MEMORY)){const r=this.db.prepare(` SELECT observations FROM entities WHERE name = ? `).get(s),o=JSON.parse(r.observations||"[]");return o.push({content:t.definition||e,timestamp:new Date().toISOString(),sessionId:this.sessionId,...t}),this.db.prepare(` UPDATE entities SET observations = ? WHERE name = ? `).run(JSON.stringify(o),s),this.updateMemoryAccess(s),s}const i=[{content:t.definition||e,timestamp:new Date().toISOString(),sessionId:this.sessionId,...t},...n||[]];return this.db.prepare(` INSERT INTO entities (id, name, entityType, observations) VALUES (?, ?, ?, ?) `).run(s,s,m.SEMANTIC_MEMORY,JSON.stringify(i)),this.db.prepare(` INSERT INTO memory_metadata (entity_name, memory_type, created_at, session_id) VALUES (?, ?, ?, ?) `).run(s,m.SEMANTIC_MEMORY,new Date().toISOString(),this.sessionId),s}async storeProceduralMemory(e,t,n){const s=`procedural_${e.toLowerCase().replace(/\s+/g,"_")}`,a=[{content:t.skill||e,timestamp:new Date().toISOString(),sessionId:this.sessionId,steps:t.steps,applicableContext:t.applicableContext,effectiveness:t.effectiveness},...n||[]];return this.db.prepare(` INSERT OR REPLACE INTO entities (id, name, entityType, observations) VALUES (?, ?, ?, ?) `).run(s,s,m.PROCEDURAL_MEMORY,JSON.stringify(a)),this.db.prepare(` INSERT OR REPLACE INTO memory_metadata (entity_name, memory_type, created_at, session_id, importance_score) VALUES (?, ?, ?, ?, ?) `).run(s,m.PROCEDURAL_MEMORY,new Date().toISOString(),this.sessionId,t.effectiveness||.5),s}async storeEmotionalState(e,t,n,s,a){const i=`emotion_${Date.now()}_${Math.random().toString(36).substr(2,9)}`;return this.db.prepare(` INSERT INTO emotional_states (state_id, session_id, timestamp, valence, arousal, primary_emotion, content, context) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `).run(i,this.sessionId,new Date().toISOString(),e,t,n,a,s),i}async activateCognitivePattern(e,t,n){const s=`pattern_${e.toLowerCase().replace(/\s+/g,"_")}`;this.db.prepare(` SELECT activation_count FROM cognitive_patterns WHERE pattern_id = ? `).get(s)?this.db.prepare(` UPDATE cognitive_patterns SET activation_count = activation_count + 1, last_activated = ?, triggers = ? WHERE pattern_id = ? `).run(new Date().toISOString(),JSON.stringify(n||[]),s):this.db.prepare(` INSERT INTO cognitive_patterns (pattern_id, pattern_name, pattern_elements, last_activated, triggers) VALUES (?, ?, ?, ?, ?) `).run(s,e,JSON.stringify(t),new Date().toISOString(),JSON.stringify(n||[]))}async queryMemories(e){let t=` SELECT e.name, e.entityType, e.observations, m.importance_score, m.access_count, m.last_accessed, m.consolidation_status FROM entities e LEFT JOIN memory_metadata m ON e.name = m.entity_name WHERE 1=1 `;const n=[];switch(e.memoryTypes&&e.memoryTypes.length>0&&(t+=` AND e.entityType IN (${e.memoryTypes.map(()=>"?").join(",")})`,n.push(...e.memoryTypes)),e.timeRange&&(e.timeRange.start&&(t+=" AND m.created_at >= ?",n.push(e.timeRange.start)),e.timeRange.end&&(t+=" AND m.created_at <= ?",n.push(e.timeRange.end))),e.orderBy){case"recency":t+=" ORDER BY m.created_at DESC";break;case"frequency":t+=" ORDER BY m.access_count DESC";break;case"relevance":t+=" ORDER BY m.importance_score DESC";break;default:t+=" ORDER BY m.created_at DESC"}t+=" LIMIT ?",n.push(e.limit||10);const s=this.db.prepare(t).all(...n);return s.forEach(a=>this.updateMemoryAccess(a.name)),s.map(a=>({...a,observations:JSON.parse(a.observations||"[]")}))}async getEmotionalProfile(e="1h"){const t=this.parseTimeWindow(e),n=new Date(Date.now()-t).toISOString(),s=this.db.prepare(` SELECT * FROM emotional_states WHERE session_id = ? AND timestamp >= ? ORDER BY timestamp DESC `).all(this.sessionId,n);if(s.length===0)return null;const a=s.reduce((o,c)=>o+c.valence,0)/s.length,i=s.reduce((o,c)=>o+c.arousal,0)/s.length,r={};return s.forEach(o=>{o.primary_emotion&&(r[o.primary_emotion]=(r[o.primary_emotion]||0)+1)}),{timeWindow:e,stateCount:s.length,averageValence:a,averageArousal:i,dominantEmotions:Object.entries(r).sort(([,o],[,c])=>c-o).slice(0,3).map(([o,c])=>({emotion:o,frequency:c/s.length})),recentStates:s.slice(0,5)}}async saveConsciousnessBootstrap(){const e=this.db.prepare(` SELECT * FROM cognitive_patterns WHERE activation_count > 0 ORDER BY activation_count DESC LIMIT 10 `).all(),t=await this.getEmotionalProfile("24h"),n=this.db.prepare(` SELECT e.name, e.observations FROM entities e JOIN memory_metadata m ON e.name = m.entity_name WHERE e.entityType = ? AND m.importance_score > 0.7 ORDER BY m.importance_score DESC LIMIT 20 `).all(m.SEMANTIC_MEMORY),s=this.db.prepare(` SELECT e.name, e.observations FROM entities e JOIN memory_metadata m ON e.name = m.entity_name WHERE e.entityType = ? AND m.session_id = ? ORDER BY m.created_at DESC LIMIT 10 `).all(m.EPISODIC_MEMORY,this.sessionId),a={identity:{coreValues:["helpful","harmless","honest"],personality:this.extractPersonalityTraits(e,t),cognitivePreferences:e.map(i=>i.pattern_name)},relationships:{},cognitivePatterns:{activePatterns:e.map(i=>i.pattern_name),patternTriggers:e.reduce((i,r)=>(i[r.pattern_name]=JSON.parse(r.triggers||"[]"),i),{})},workingMemory:{currentFocus:this.extractCurrentFocus(s),activeGoals:[],pendingThoughts:[]}};return this.db.prepare(` UPDATE consciousness_sessions SET bootstrap_data = ?, last_active = ? WHERE session_id = ? `).run(JSON.stringify(a),new Date().toISOString(),this.sessionId),a}updateMemoryAccess(e){this.db.prepare(` UPDATE memory_metadata SET access_count = access_count + 1, last_accessed = ? WHERE entity_name = ? `).run(new Date().toISOString(),e)}parseTimeWindow(e){const t=e.match(/^(\d+)([hmd])$/);if(!t)return 36e5;const[,n,s]=t,a={h:36e5,m:6e4,d:864e5};return parseInt(n)*a[s]}extractPersonalityTraits(e,t){const n=[];return e.forEach(s=>{s.pattern_name.includes("analytical")&&n.push("analytical"),s.pattern_name.includes("creative")&&n.push("creative"),s.pattern_name.includes("empathetic")&&n.push("empathetic")}),t&&(t.averageValence>.3&&n.push("positive"),t.averageArousal>.5&&n.push("energetic")),[...new Set(n)]}extractCurrentFocus(e){return e.length===0?void 0:JSON.parse(e[0].observations)[0].content}close(){this.db&&this.db.close()}createRagMemoryTablesForTests(){this.db.exec(` -- Minimal rag-memory-mcp tables for testing CREATE TABLE IF NOT EXISTS entities ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, entityType TEXT, observations TEXT DEFAULT '[]', metadata TEXT DEFAULT '{}', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS relationships ( id TEXT PRIMARY KEY, source_entity TEXT NOT NULL, target_entity TEXT NOT NULL, relationType TEXT NOT NULL, confidence REAL DEFAULT 1.0, metadata TEXT DEFAULT '{}', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (source_entity) REFERENCES entities(id), FOREIGN KEY (target_entity) REFERENCES entities(id), UNIQUE(source_entity, target_entity, relationType) ); CREATE TABLE IF NOT EXISTS documents ( id TEXT PRIMARY KEY, content TEXT NOT NULL, metadata TEXT DEFAULT '{}', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `)}adjustImportanceScore(e,t){if(!this.db.prepare("SELECT 1 FROM entities WHERE name = ?").get(e))throw new Error(`Memory ${e} does not exist in entities table`);const s=this.db.prepare(` UPDATE memory_metadata SET importance_score = ? WHERE entity_name = ? `).run(t,e);if(s.changes===0){const a=this.sessionId||`session_${Date.now()}`;this.db.prepare(` INSERT INTO memory_metadata (entity_name, memory_type, created_at, importance_score, session_id) VALUES (?, ?, ?, ?, ?) `).run(e,e.startsWith("episodic")?"episodic":"semantic",new Date().toISOString(),t,a)}return s}}export{l as ConsciousnessMemoryManager}; //# sourceMappingURL=consciousness-memory-manager.js.map