@jeelidev/personal-neo4j-memory-server
Version:
Personal MCP Memory Server with Neo4j backend - Enhanced Cloudflare Access support with robust tunnel management and remove functionality
377 lines (353 loc) • 53.5 kB
JavaScript
import{a as n,c as y,d as l,e as p,f as u}from"./chunk-ULWSXDW6.mjs";import Q from"neo4j-driver";function D(){let m=["NEO4J_URI","NEO4J_USERNAME","NEO4J_PASSWORD"];for(let e of m)if(!process.env[e])throw new y(`Required environment variable ${e} is not set`,n.INVALID_ENVIRONMENT_CONFIG);return{neo4j:{uri:process.env.NEO4J_URI,username:process.env.NEO4J_USERNAME,password:process.env.NEO4J_PASSWORD,database:process.env.NEO4J_DATABASE||process.env.DEFAULT_DATABASE||"neo4j"},logging:{level:process.env.LOG_LEVEL||"info"},vector:{modelName:process.env.VECTOR_MODEL||"sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",dimensions:process.env.VECTOR_DIMENSIONS?process.env.VECTOR_DIMENSIONS==="auto"?"auto":parseInt(process.env.VECTOR_DIMENSIONS,10):"auto",idleTimeout:(()=>{let e=parseInt(process.env.VECTOR_IDLE_TIMEOUT||"600000",10);return isNaN(e)?6e5:e})(),preload:process.env.VECTOR_PRELOAD!=="false"},limits:{maxMemoriesPerOperation:(()=>{let e=parseInt(process.env.MAX_MEMORIES_PER_OP||"50",10);return isNaN(e)?50:e})(),maxRelationsPerOperation:(()=>{let e=parseInt(process.env.MAX_RELATIONS_PER_OP||"200",10);return isNaN(e)?200:e})(),maxTraversalDepth:(()=>{let e=parseInt(process.env.MAX_TRAVERSAL_DEPTH||"5",10);return isNaN(e)?5:e})()}}}function O(){return D().neo4j}function T(){return D().vector}function ae(){return D().limits}var S=class{constructor(){this.driver=null;this.isConnected=!1;let e=O();this.currentDatabase=e.database}getDriver(){if(!this.driver){let e=O();console.error(`[Neo4jDriverManager] Initializing driver with URI: ${e.uri}`),console.error(`[Neo4jDriverManager] Username: ${e.username}`);let t=e.uri,r={maxConnectionLifetime:30*60*1e3,maxConnectionPoolSize:50,connectionAcquisitionTimeout:6e4,userAgent:"Neo4j-Memory-Server/3.2.0-Fixed"};e.uri.startsWith("bolt+s://")&&e.uri.includes("jeelidev.uk")?(console.error("[Neo4jDriverManager] FIX: Converting bolt+s:// to bolt:// for tunnel compatibility"),t=e.uri.replace("bolt+s://","bolt://"),r.encrypted=!0,r.trust="TRUST_ALL_CERTIFICATES",console.error(`[Neo4jDriverManager] Fixed URI: ${t}`)):e.uri.startsWith("bolt://")&&!e.uri.includes("localhost")&&!e.uri.includes("127.0.0.1")?(r.encrypted=!0,r.trust="TRUST_ALL_CERTIFICATES"):e.uri.startsWith("bolt://")&&(e.uri.includes("localhost")||e.uri.includes("127.0.0.1"))&&(r.encrypted=!1),console.error("[Neo4jDriverManager] Driver config:",JSON.stringify(r,null,2));try{this.driver=Q.driver(t,Q.auth.basic(e.username,e.password),r),console.error("[Neo4jDriverManager] Driver created successfully with fixed configuration")}catch(a){throw console.error("[Neo4jDriverManager] Failed to create driver:",a),a}}return this.driver}async verifyConnectivity(){let e=this.getDriver();console.error("[Neo4jDriverManager] Verifying connectivity...");let t={database:"system",defaultAccessMode:Q.session.READ,connectionTimeout:6e4,maxTransactionRetryTime:3e4},r=e.session(t);try{console.error("[Neo4jDriverManager] Running connectivity test query..."),await r.run("RETURN 1"),this.isConnected=!0,console.error("[Neo4jDriverManager] Connectivity verified successfully")}catch(a){throw console.error("[Neo4jDriverManager] Connectivity verification failed:",a),a.message&&a.message.includes("HTTP")&&(console.error("[Neo4jDriverManager] HTTP ERROR DETECTED - This suggests the tunnel is not properly configured for BOLT protocol"),console.error("[Neo4jDriverManager] Suggestion: Check Cloudflare Tunnel configuration for TCP routing")),a}finally{await r.close()}}isDriverConnected(){return this.isConnected}async close(){this.driver&&(await this.driver.close(),this.driver=null,this.isConnected=!1)}getCurrentDatabase(){return{database:this.currentDatabase}}switchDatabase(e){this.currentDatabase=e}};var v=class{constructor(e){this.driverManager=e}createSession(e){let t=this.driverManager.getDriver(),r=e||this.driverManager.getCurrentDatabase().database;return t.session({database:r})}createSystemSession(){return this.driverManager.getDriver().session({database:"system"})}async withSession(e,t){let r=this.createSession(t);try{return await e(r)}finally{await r.close()}}};var f=class{constructor(e,t){this.session=e;this.vectorDimensions=t}async validateUserDatabase(){try{let t=(await this.session.run("CALL db.info() YIELD name")).records[0]?.get("name");if(t==="system")throw new Error(`Cannot create constraints on system database. Context: ${t}`);if(!t)throw new Error("Database name could not be determined")}catch(e){throw e instanceof Error?e:new Error(`Database validation failed: ${String(e)}`)}}async ensureConstraints(){await this.validateUserDatabase();let e=["CREATE CONSTRAINT memory_id_unique IF NOT EXISTS FOR (m:Memory) REQUIRE m.id IS UNIQUE","CREATE CONSTRAINT observation_id_unique IF NOT EXISTS FOR (o:Observation) REQUIRE o.id IS UNIQUE"];for(let t of e)try{await this.session.run(t)}catch(r){throw r}}async ensureActiveIndexes(){await this.validateUserDatabase();let e=["CREATE INDEX memory_type_idx IF NOT EXISTS FOR (m:Memory) ON (m.memoryType)","CREATE INDEX memory_created_idx IF NOT EXISTS FOR (m:Memory) ON (m.createdAt)","CREATE INDEX relation_strength_idx IF NOT EXISTS FOR ()-[r:RELATES_TO]-() ON (r.strength)","CREATE INDEX relation_source_idx IF NOT EXISTS FOR ()-[r:RELATES_TO]-() ON (r.source)","CREATE INDEX relation_created_idx IF NOT EXISTS FOR ()-[r:RELATES_TO]-() ON (r.createdAt)"];for(let t of e)try{await this.session.run(t)}catch(r){throw r}}async ensureFulltextIndexes(){await this.validateUserDatabase();let e=["CREATE FULLTEXT INDEX memory_metadata_idx IF NOT EXISTS FOR (m:Memory) ON EACH [m.metadata]","CREATE FULLTEXT INDEX observation_content_idx IF NOT EXISTS FOR (o:Observation) ON EACH [o.content]"];for(let t of e)try{await this.session.run(t)}catch(r){throw new Error(`Fulltext index failed: ${t} - ${r instanceof Error?r.message:String(r)}`)}}async ensureVectorIndexes(){if(await this.validateUserDatabase(),!!this.vectorDimensions)try{let e=this.vectorDimensions;if(!e||e<=0)throw new Error(`INVALID VECTOR DIMENSIONS: ${e}. Model may not be loaded properly. Check embedding service configuration.`);let t=`
CREATE VECTOR INDEX memory_name_vector_idx IF NOT EXISTS
FOR (m:Memory) ON (m.nameEmbedding)
OPTIONS {indexConfig: {
\`vector.dimensions\`: ${e},
\`vector.similarity_function\`: 'cosine'
}}
`;await this.session.run(t);let r=`
CREATE VECTOR INDEX observation_embedding_vector_idx IF NOT EXISTS
FOR (o:Observation) ON (o.embedding)
OPTIONS {indexConfig: {
\`vector.dimensions\`: ${e},
\`vector.similarity_function\`: 'cosine'
}}
`;await this.session.run(r)}catch{}}async removeDeadIndexes(){let e=["memory_accessed_idx","memory_name_idx","relation_type_idx"];for(let t of e)try{await this.session.run(`DROP INDEX ${t} IF EXISTS`)}catch{}}async hasRequiredSchema(){try{let e=await this.session.run(`
SHOW CONSTRAINTS
WHERE entityType = "NODE"
AND labelsOrTypes = ["Memory"]
AND properties = ["id"]
AND type = "UNIQUENESS"
`),t=await this.session.run(`
SHOW CONSTRAINTS
WHERE entityType = "NODE"
AND labelsOrTypes = ["Observation"]
AND properties = ["id"]
AND type = "UNIQUENESS"
`),r=await this.session.run(`
SHOW INDEXES
WHERE name = "memory_type_idx"
`),a=await this.session.run(`
SHOW INDEXES
WHERE name = "memory_created_idx"
`);return e.records.length>0&&t.records.length>0&&r.records.length>0&&a.records.length>0}catch{return!1}}async initializeSchema(){await this.validateUserDatabase();try{await this.removeDeadIndexes(),await this.ensureConstraints(),await this.ensureActiveIndexes(),await this.ensureFulltextIndexes(),await this.ensureVectorIndexes()}catch(e){throw e}}};var R=class{constructor(e,t){this.driverManager=e,this.sessionFactory=t}async switchDatabase(e){try{let t=this.normalizeDatabaseName(e);if(!this.isValidDatabaseName(t))throw new y(`Invalid database name after normalization: ${e} -> ${t}`,n.INVALID_DATABASE_NAME);let r=this.driverManager.getCurrentDatabase().database;if(r===t)return await this.ensureSchemaExists(),{previousDatabase:r,currentDatabase:t,created:!1};let a=await this.databaseExists(t);return a||await this.createDatabase(t),this.driverManager.switchDatabase(t),await this.initializeDatabase(),{previousDatabase:r,currentDatabase:t,created:!a}}catch(t){throw new l(`Failed to switch to database '${e}': ${t instanceof Error?t.message:String(t)}`,n.DATABASE_OPERATION_FAILED,{databaseName:e,originalError:t instanceof Error?t.message:String(t)})}}getCurrentDatabase(){return this.driverManager.getCurrentDatabase()}async initializeDatabase(){let e=this.sessionFactory.createSession();await new f(e,void 0).initializeSchema(),await e.close()}async ensureSchemaExists(){let e=this.sessionFactory.createSession(),t=new f(e,void 0);try{await t.hasRequiredSchema()||await t.initializeSchema()}finally{await e.close()}}async databaseExists(e){let t=this.sessionFactory.createSystemSession();try{return(await t.run("SHOW DATABASES YIELD name WHERE name = $name",{name:e})).records.length>0}catch{return!0}finally{await t.close()}}async createDatabase(e){let t=this.sessionFactory.createSystemSession();try{await t.run("CREATE DATABASE $name IF NOT EXISTS",{name:e}),await new Promise(r=>setTimeout(r,1e3))}catch{}finally{await t.close()}}normalizeDatabaseName(e){if(!e||typeof e!="string")throw new y("Database name must be a non-empty string",n.INVALID_DATABASE_NAME);let t=e.toLowerCase().replace(/\s+/g,"-");if(t=t.replace(/[^a-z0-9-]/g,""),t=t.replace(/^-+/,""),t&&!/^[a-z0-9]/.test(t)&&(t="0"+t),t=t.replace(/-+$/,""),t.length>63&&(t=t.substring(0,63)),!t)throw new y(`Cannot normalize database name: ${e}`,n.INVALID_DATABASE_NAME);return t}isValidDatabaseName(e){return!e||typeof e!="string"||e.length===0||e.length>63?!1:/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(e)}async close(){await this.driverManager.close()}};var M=class{async createMemoryNode(e,t){let r=`
CREATE (m:Memory {
id: $id,
name: $name,
memoryType: $memoryType,
metadata: $metadata,
createdAt: $createdAt,
modifiedAt: $modifiedAt,
lastAccessed: $lastAccessed,
nameEmbedding: $nameEmbedding
})
RETURN m`;try{if((await e.run(r,{id:t.id,name:t.name,memoryType:t.memoryType,metadata:JSON.stringify(t.metadata||{}),createdAt:this.toISOString(t.createdAt),modifiedAt:this.toISOString(t.modifiedAt),lastAccessed:this.toISOString(t.lastAccessed),nameEmbedding:t.nameEmbedding||null})).records.length===0)throw new l(`Failed to create memory node: ${t.id}`,n.DATABASE_OPERATION_FAILED,{memoryId:t.id,operation:"create"});return t}catch(a){if(a instanceof Error){if(a.message.includes("ConstraintValidationFailed"))throw new y(`Memory with ID ${t.id} already exists`,n.DUPLICATE_ID,{memoryId:t.id});if(a.message.includes("ServiceUnavailable"))throw new l("Database service unavailable",n.DATABASE_UNAVAILABLE)}throw a}}async getCoreMemoryData(e,t){return!t||t.length===0?[]:(await e.run("MATCH (m:Memory) WHERE m.id IN $ids SET m.lastAccessed = $timestamp",{ids:t,timestamp:new Date().toISOString()}),(await e.run(`
MATCH (m:Memory)
WHERE m.id IN $ids
RETURN m.id as id,
m.name as name,
m.memoryType as memoryType,
m.metadata as metadata,
m.createdAt as createdAt,
m.modifiedAt as modifiedAt,
m.lastAccessed as lastAccessed
ORDER BY m.name
`,{ids:t})).records.map(s=>({id:s.get("id"),name:s.get("name"),memoryType:s.get("memoryType"),metadata:this.parseMetadata(s.get("metadata")),createdAt:s.get("createdAt"),modifiedAt:s.get("modifiedAt"),lastAccessed:s.get("lastAccessed")})))}async getMemoriesByType(e,t){return(await e.run(`
MATCH (m:Memory {memoryType: $memoryType})
RETURN m.id as id,
m.name as name,
m.memoryType as memoryType,
m.metadata as metadata,
m.createdAt as createdAt,
m.modifiedAt as modifiedAt,
m.lastAccessed as lastAccessed
ORDER BY m.createdAt DESC
`,{memoryType:t})).records.map(s=>({id:s.get("id"),name:s.get("name"),memoryType:s.get("memoryType"),metadata:this.parseMetadata(s.get("metadata")),createdAt:s.get("createdAt"),modifiedAt:s.get("modifiedAt"),lastAccessed:s.get("lastAccessed")}))}async updateMemory(e,t){let r=`
MATCH (m:Memory {id: $id})
SET m.name = $name,
m.memoryType = $memoryType,
m.metadata = $metadata,
m.modifiedAt = $modifiedAt
RETURN m`;try{let a=await e.run(r,{id:t.id,name:t.name,memoryType:t.memoryType,metadata:JSON.stringify(t.metadata||{}),modifiedAt:this.toISOString(t.modifiedAt)});if(a.records.length===0)throw new l(`Memory with id ${t.id} not found`,n.RESOURCE_NOT_FOUND,{memoryId:t.id});let s=a.records[0].get("m");return{id:s.properties.id,name:s.properties.name,memoryType:s.properties.memoryType,metadata:this.parseMetadata(s.properties.metadata),createdAt:new Date(s.properties.createdAt),modifiedAt:new Date(s.properties.modifiedAt),lastAccessed:new Date(s.properties.lastAccessed)}}catch(a){throw a instanceof l?a:a instanceof Error&&a.message.includes("ServiceUnavailable")?new l("Database service unavailable",n.DATABASE_UNAVAILABLE):new l(`Failed to update memory: ${a instanceof Error?a.message:String(a)}`,n.DATABASE_OPERATION_FAILED)}}async deleteMemory(e,t){return(await e.run(`
MATCH (m:Memory {id: $memoryId})
// Delete observations
OPTIONAL MATCH (m)-[:HAS_OBSERVATION]->(o:Observation)
DETACH DELETE o
WITH m
// Delete memory relationships
OPTIONAL MATCH (m)-[r:RELATES_TO]-()
DELETE r
WITH m
OPTIONAL MATCH ()-[r:RELATES_TO]->(m)
DELETE r
// Delete memory
DELETE m
RETURN count(m) > 0 as deleted`,{memoryId:t})).records[0]?.get("deleted")||!1}async memoryExists(e,t){return!!(await e.run("MATCH (m:Memory {id: $id}) RETURN count(m) > 0 as exists",{id:t})).records[0]?.get("exists")}async getFilteredMemories(e,t){let r=[],a={limit:t.limit||100,offset:t.offset||0};t.memoryTypes&&t.memoryTypes.length>0&&(r.push("m.memoryType IN $memoryTypes"),a.memoryTypes=t.memoryTypes);let i=`
MATCH (m:Memory)
${r.length>0?`WHERE ${r.join(" AND ")}`:""}
RETURN m.id as id,
m.name as name,
m.memoryType as memoryType,
m.metadata as metadata,
m.createdAt as createdAt,
m.modifiedAt as modifiedAt,
m.lastAccessed as lastAccessed
ORDER BY m.createdAt DESC
SKIP $offset
LIMIT $limit
`;return(await e.run(i,a)).records.map(o=>({id:o.get("id"),name:o.get("name"),memoryType:o.get("memoryType"),metadata:this.parseMetadata(o.get("metadata")),createdAt:o.get("createdAt"),modifiedAt:o.get("modifiedAt"),lastAccessed:o.get("lastAccessed")}))}parseMetadata(e){if(!e)return{};try{return JSON.parse(e)}catch{return{}}}toISOString(e){return e?typeof e=="string"?e:e.toISOString():new Date().toISOString()}};var b=class{async getMemoryContext(e,t){let a=await e.run(`
MATCH (m:Memory {id: $memoryId})
// Graph context - 2 levels deep with exact relation types
OPTIONAL MATCH path1 = (ancestor:Memory)-[rel1*1..2]->(m)
WHERE ancestor <> m AND ancestor.id IS NOT NULL
WITH m, collect(DISTINCT {
id: ancestor.id,
name: ancestor.name,
type: ancestor.memoryType,
relation: rel1[0].relationType,
distance: length(path1),
strength: rel1[0].strength,
source: rel1[0].source,
createdAt: rel1[0].createdAt
})[0..3] as ancestors
OPTIONAL MATCH path2 = (m)-[rel2*1..2]->(descendant:Memory)
WHERE descendant <> m AND descendant.id IS NOT NULL
WITH m, ancestors, collect(DISTINCT {
id: descendant.id,
name: descendant.name,
type: descendant.memoryType,
relation: rel2[0].relationType,
distance: length(path2),
strength: rel2[0].strength,
source: rel2[0].source,
createdAt: rel2[0].createdAt
})[0..3] as descendants
RETURN ancestors, descendants
`,{memoryId:t});if(a.records.length===0)return{ancestors:[],descendants:[]};let s=a.records[0];return{ancestors:this.processRelatedMemories(s.get("ancestors")||[]),descendants:this.processRelatedMemories(s.get("descendants")||[])}}async getBatchContext(e,t){if(t.length===0)return new Map;let a=await e.run(`
MATCH (m:Memory)
WHERE m.id IN $memoryIds
// Graph context - 2 levels deep with exact relation types
OPTIONAL MATCH path1 = (ancestor:Memory)-[rel1*1..2]->(m)
WHERE ancestor <> m AND ancestor.id IS NOT NULL
WITH m, collect(DISTINCT {
id: ancestor.id,
name: ancestor.name,
type: ancestor.memoryType,
relation: rel1[0].relationType,
distance: length(path1),
strength: rel1[0].strength,
source: rel1[0].source,
createdAt: rel1[0].createdAt
})[0..3] as ancestors
OPTIONAL MATCH path2 = (m)-[rel2*1..2]->(descendant:Memory)
WHERE descendant <> m AND descendant.id IS NOT NULL
WITH m, ancestors, collect(DISTINCT {
id: descendant.id,
name: descendant.name,
type: descendant.memoryType,
relation: rel2[0].relationType,
distance: length(path2),
strength: rel2[0].strength,
source: rel2[0].source,
createdAt: rel2[0].createdAt
})[0..3] as descendants
RETURN m.id as memoryId, ancestors, descendants
`,{memoryIds:t}),s=new Map;for(let i of a.records){let c=i.get("memoryId");s.set(c,{ancestors:this.processRelatedMemories(i.get("ancestors")||[]),descendants:this.processRelatedMemories(i.get("descendants")||[])})}return s}processRelatedMemories(e){return e.filter(t=>t.id!==null).map(t=>({...t,distance:t.distance?this.convertNeo4jInteger(t.distance):0}))}convertNeo4jInteger(e){return typeof e=="number"?e:e&&typeof e.toNumber=="function"?e.toNumber():0}};import{ulid as se}from"ulid";var K="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+,-./:;=?@_{}~`";function x(){let m=se();return ne(m)}function ne(m){let e="0123456789ABCDEFGHJKMNPQRSTVWXYZ",t=0n;for(let a of m){let s=e.indexOf(a);if(s===-1)throw new Error(`Invalid ULID character: ${a}`);t=t*32n+BigInt(s)}if(t===0n)return K[0];let r="";for(;t>0n;)r=K[Number(t%85n)]+r,t=t/85n;return r.substring(0,18)}import{pipeline as oe}from"@xenova/transformers";var A=class{constructor(){this.model=null;this.lastUsed=0;this.modelDimensions=0;this.cleanupTimer=null;this.loadingPromise=null;this.config=T()}async preloadModel(){await this.ensureModel()}async calculateEmbedding(e){if(!e||e.trim()==="")throw new y("Cannot calculate embedding for empty text",n.VALIDATION_FAILED);let t=await this.ensureModel();this.resetIdleTimer();try{let r=await t(e,{pooling:"mean",normalize:!0});return Array.from(r.data)}catch(r){throw new p(`Embedding calculation failed: ${r instanceof Error?r.message:String(r)}`,n.EMBEDDING_SERVICE_ERROR,{originalError:r instanceof Error?r.message:String(r)})}}async getModelDimensions(){return this.modelDimensions===0&&await this.ensureModel(),this.modelDimensions}calculateSimilarity(e,t){if(!e||!t||!Array.isArray(e)||!Array.isArray(t)||e.length!==t.length)return 0;try{let r=0,a=0,s=0;for(let i=0;i<e.length;i++)typeof e[i]!="number"||typeof t[i]!="number"||(r+=e[i]*t[i],a+=e[i]*e[i],s+=t[i]*t[i]);return a=Math.sqrt(a),s=Math.sqrt(s),a===0||s===0?0:r/(a*s)}catch(r){throw new p(`Similarity calculation failed: ${r instanceof Error?r.message:String(r)}`,n.EMBEDDING_SERVICE_ERROR,{operation:"calculateSimilarity"})}}async ensureModel(){return this.model?(this.lastUsed=Date.now(),this.model):this.loadingPromise?(await this.loadingPromise,this.model):(this.loadingPromise=this.loadModel(),await this.loadingPromise,this.loadingPromise=null,this.model)}async loadModel(){try{let e=this.config.modelName.startsWith("Xenova/")?this.config.modelName:`Xenova/${this.config.modelName.replace("sentence-transformers/","")}`;if(this.model=await oe("feature-extraction",e),this.config.dimensions==="auto"){let t=await this.model("test");this.modelDimensions=t.data.length}else this.modelDimensions=this.config.dimensions;this.lastUsed=Date.now()}catch(e){throw this.model=null,this.modelDimensions=0,this.loadingPromise=null,new p(`Failed to load embedding model: ${e instanceof Error?e.message:String(e)}`,n.EMBEDDING_SERVICE_ERROR,{model:this.config.modelName,originalError:e instanceof Error?e.message:String(e)})}}resetIdleTimer(){this.cleanupTimer&&clearTimeout(this.cleanupTimer),this.cleanupTimer=setTimeout(()=>{Date.now()-this.lastUsed>=this.config.idleTimeout&&(this.model=null,this.modelDimensions=0,this.cleanupTimer=null)},this.config.idleTimeout)}async shutdown(){this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=null),this.model=null,this.modelDimensions=0}};var Y=null;function ie(){return Y||(Y=new A),Y}async function I(m){return ie().calculateEmbedding(m)}function q(m){return typeof m=="object"&&m!==null&&"message"in m&&typeof m.message=="string"}function ee(m){return q(m)?m.message:typeof m=="string"?m:"Unknown error occurred"}function E(m,e){return`${m}: ${ee(e)}`}var w=class{async createObservations(e,t,r){for(let a of r)await this.createSingleObservation(e,t,a)}async createSingleObservation(e,t,r){if(typeof r!="string"||!r.trim())throw new y("Observation content must be a non-empty string",n.INVALID_OBSERVATION_CONTENT,{receivedType:typeof r,memoryId:t});let a=x();try{let s=await I(r);await e.run(`
MATCH (m:Memory {id: $memoryId})
CREATE (o:Observation {
id: $obsId,
content: $content,
createdAt: $timestamp,
embedding: $embedding
})
CREATE (m)-[:HAS_OBSERVATION]->(o)`,{memoryId:t,obsId:a,content:r,timestamp:new Date().toISOString(),embedding:s})}catch(s){if(s instanceof y)throw s;if(s instanceof Error){if(s.message.includes("Cannot calculate embedding"))throw new p(`Embedding service failed: ${s.message}`,n.EMBEDDING_SERVICE_ERROR,{observationId:a,memoryId:t});if(s.message.includes("ServiceUnavailable"))throw new l("Database service unavailable",n.DATABASE_UNAVAILABLE)}throw new l(`Failed to create observation: ${s instanceof Error?s.message:String(s)}`,n.DATABASE_OPERATION_FAILED,{observationId:a,memoryId:t})}}async deleteObservations(e,t,r){await e.run(`
MATCH (m:Memory {id: $memoryId})-[:HAS_OBSERVATION]->(o:Observation)
WHERE o.id IN $observationIds
DETACH DELETE o`,{memoryId:t,observationIds:r})}async getObservationsForMemory(e,t){return(await e.run(`
MATCH (m:Memory {id: $memoryId})-[:HAS_OBSERVATION]->(o:Observation)
RETURN o.id as id, o.content as content, o.createdAt as createdAt
ORDER BY o.createdAt ASC
`,{memoryId:t})).records.map(s=>({id:s.get("id"),content:s.get("content"),createdAt:s.get("createdAt")}))}async getBatchObservations(e,t){if(t.length===0)return new Map;let a=await e.run(`
MATCH (m:Memory)-[:HAS_OBSERVATION]->(o:Observation)
WHERE m.id IN $memoryIds
WITH m, o ORDER BY o.createdAt ASC
RETURN m.id as memoryId,
collect({
id: o.id,
content: o.content,
createdAt: o.createdAt
}) as observations
`,{memoryIds:t}),s=new Map;for(let i of a.records){let c=i.get("memoryId"),o=i.get("observations")||[];s.set(c,o.filter(d=>d.content).map(d=>({id:d.id,content:d.content,createdAt:d.createdAt})))}return s}};var C=class{async createRelation(e,t,r,a){try{if((await e.run(`
MATCH (from:Memory {id: $fromId}), (to:Memory {id: $toId})
CREATE (from)-[:RELATES_TO {relationType: $relationType}]->(to)
RETURN from, to`,{fromId:t,toId:r,relationType:a})).records.length===0)throw new l("Failed to create relation: one or both memories do not exist",n.RESOURCE_NOT_FOUND,{fromId:t,toId:r,relationType:a})}catch(s){throw s instanceof l?s:s instanceof Error&&s.message.includes("ServiceUnavailable")?new l("Database service unavailable",n.DATABASE_UNAVAILABLE):new l(`Failed to create relation: ${s instanceof Error?s.message:String(s)}`,n.DATABASE_OPERATION_FAILED,{fromId:t,toId:r,relationType:a})}}async createEnhancedRelation(e,t){try{if((await e.run(`
MATCH (from:Memory {id: $fromId}), (to:Memory {id: $toId})
CREATE (from)-[:RELATES_TO {
relationType: $relationType,
strength: $strength,
source: $source,
createdAt: $createdAt
}]->(to)
RETURN from, to`,t)).records.length===0)throw new l("Failed to create enhanced relation: one or both memories do not exist",n.RESOURCE_NOT_FOUND,{fromId:t.fromId,toId:t.toId,relationType:t.relationType})}catch(r){if(r instanceof l)throw r;if(r instanceof Error){if(r.message.includes("ConstraintValidationFailed"))throw new y(`Relation already exists between ${t.fromId} and ${t.toId}`,n.DUPLICATE_RELATION,{fromId:t.fromId,toId:t.toId,relationType:t.relationType});if(r.message.includes("ServiceUnavailable"))throw new l("Database service unavailable",n.DATABASE_UNAVAILABLE)}throw new l(`Failed to create enhanced relation: ${r instanceof Error?r.message:String(r)}`,n.DATABASE_OPERATION_FAILED,t)}}async updateEnhancedRelation(e,t){try{let a=(await e.run(`
MATCH (from:Memory {id: $fromId})-[r:RELATES_TO {relationType: $relationType}]->(to:Memory {id: $toId})
SET r.strength = $strength,
r.source = $source
RETURN count(r) > 0 as updated`,{fromId:t.fromId,toId:t.toId,relationType:t.relationType,strength:t.strength,source:t.source})).records[0]?.get("updated")||!1;if(!a)throw new l(`Relation not found: ${t.fromId} \u2192 ${t.toId} (${t.relationType})`,n.RESOURCE_NOT_FOUND,{fromId:t.fromId,toId:t.toId,relationType:t.relationType});return a}catch(r){throw r instanceof l?r:r instanceof Error&&r.message.includes("ServiceUnavailable")?new l("Database service unavailable",n.DATABASE_UNAVAILABLE):new l(`Failed to update relation: ${r instanceof Error?r.message:String(r)}`,n.DATABASE_OPERATION_FAILED,t)}}async deleteRelation(e,t,r,a){try{if(!((await e.run(`
MATCH (from:Memory {id: $fromId})-[r:RELATES_TO {relationType: $relationType}]->(to:Memory {id: $toId})
DELETE r
RETURN count(r) > 0 as deleted`,{fromId:t,toId:r,relationType:a})).records[0]?.get("deleted")||!1))throw new l(`Relation not found: ${t} \u2192 ${r} (${a})`,n.RESOURCE_NOT_FOUND,{fromId:t,toId:r,relationType:a})}catch(s){throw s instanceof l?s:s instanceof Error&&s.message.includes("ServiceUnavailable")?new l("Database service unavailable",n.DATABASE_UNAVAILABLE):new l(`Failed to delete relation: ${s instanceof Error?s.message:String(s)}`,n.DATABASE_OPERATION_FAILED,{fromId:t,toId:r,relationType:a})}}};var N=class{constructor(e){this.sessionFactory=e;this.coreRepo=new M,this.graphRepo=new b,this.obsRepo=new w,this.relRepo=new C}async create(e){return await this.sessionFactory.withSession(async t=>{let r=await this.coreRepo.createMemoryNode(t,e);if(e.observations&&e.observations.length>0){let a=this.validateObservations(e.observations);await this.obsRepo.createObservations(t,e.id,a)}return r})}async findById(e){let t=await this.findByIds([e]);return t.length>0?t[0]:null}async findByIds(e){return!e||e.length===0?[]:await this.sessionFactory.withSession(async t=>{let r=await this.coreRepo.getCoreMemoryData(t,e);if(r.length===0)return[];let a=r.map(c=>c.id),s=await this.graphRepo.getBatchContext(t,a),i=await this.obsRepo.getBatchObservations(t,a);return r.map(c=>this.composeMemory(c,s.get(c.id)||{ancestors:[],descendants:[]},i.get(c.id)||[]))})}async findByType(e){return await this.sessionFactory.withSession(async t=>{let r=await this.coreRepo.getMemoriesByType(t,e);if(r.length===0)return[];let a=r.map(c=>c.id),[s,i]=await Promise.all([this.graphRepo.getBatchContext(t,a),this.obsRepo.getBatchObservations(t,a)]);return r.map(c=>this.composeMemory(c,s.get(c.id)||{ancestors:[],descendants:[]},i.get(c.id)||[]))})}async update(e){return await this.sessionFactory.withSession(async t=>await this.coreRepo.updateMemory(t,e))}async delete(e){return await this.sessionFactory.withSession(async t=>await this.coreRepo.deleteMemory(t,e))}async exists(e){return await this.sessionFactory.withSession(async t=>await this.coreRepo.memoryExists(t,e))}async findWithFilters(e){return await this.sessionFactory.withSession(async t=>{let r=await this.coreRepo.getFilteredMemories(t,e);if(r.length===0)return[];let a=r.map(c=>c.id),[s,i]=await Promise.all([this.graphRepo.getBatchContext(t,a),this.obsRepo.getBatchObservations(t,a)]);return r.map(c=>this.composeMemory(c,s.get(c.id)||{ancestors:[],descendants:[]},i.get(c.id)||[]))})}async addObservations(e,t){return await this.sessionFactory.withSession(async r=>{await this.obsRepo.createObservations(r,e,t)})}async deleteObservations(e,t){return await this.sessionFactory.withSession(async r=>{await this.obsRepo.deleteObservations(r,e,t)})}async createRelation(e,t,r){return await this.sessionFactory.withSession(async a=>{await this.relRepo.createRelation(a,e,t,r)})}async createEnhancedRelation(e){return await this.sessionFactory.withSession(async t=>{await this.relRepo.createEnhancedRelation(t,e)})}async deleteRelation(e,t,r){return await this.sessionFactory.withSession(async a=>{await this.relRepo.deleteRelation(a,e,t,r)})}composeMemory(e,t,r){return{id:e.id,name:e.name,memoryType:e.memoryType,metadata:e.metadata,createdAt:e.createdAt,modifiedAt:e.modifiedAt,lastAccessed:e.lastAccessed,observations:r.map(a=>({id:a.id,content:a.content,createdAt:a.createdAt})),related:{ancestors:t.ancestors.map(a=>({...a,distance:a.distance})),descendants:t.descendants.map(a=>({...a,distance:a.distance}))}}}validateObservations(e){return e.map(t=>{if(typeof t=="string")return t;if(t&&typeof t=="object"&&typeof t.content=="string")return t.content;throw new Error("Invalid observation format: must be string or {content: string}")})}};var P=class{classify(e){if(e==null||typeof e!="string")throw new y("Query must be a non-empty string",n.INVALID_SEARCH_QUERY);let t=e.trim();if(e.length>0&&t.length===0)throw new y("Query must be a non-empty string",n.INVALID_SEARCH_QUERY);let r=t.toLowerCase();return this.isWildcardQuery(t)?{type:"wildcard",confidence:1,preprocessing:{normalized:r}}:this.isExactSearchPattern(t)?{type:"exact_search",confidence:.9,preprocessing:{normalized:r}}:{type:"semantic_search",confidence:.8,preprocessing:{normalized:r}}}isWildcardQuery(e){return e==="*"||e.toLowerCase()==="all"}isExactSearchPattern(e){return/^[^a-zA-Z]*$/.test(e)&&e.length>0}};import k from"neo4j-driver";var _=class{constructor(e){this.session=e}async search(e,t,r){let a=await this.searchFulltext(e,t,r),s=await this.searchExactName(e,t,r),i=new Map;for(let o of s)i.set(o.id,o);for(let o of a)if(i.has(o.id)){let d=i.get(o.id);d.matchTypes.exactMetadata=d.matchTypes.exactMetadata||o.matchTypes.exactMetadata,d.matchTypes.exactContent=d.matchTypes.exactContent||o.matchTypes.exactContent}else i.set(o.id,o);let c=Array.from(i.values());return c.sort((o,d)=>{let h=(o.matchTypes.exactName?3:0)+(o.matchTypes.exactMetadata?2:0)+(o.matchTypes.exactContent?1:0);return(d.matchTypes.exactName?3:0)+(d.matchTypes.exactMetadata?2:0)+(d.matchTypes.exactContent?1:0)-h}),c.slice(0,t)}async searchFulltext(e,t,r){let a=[],s=this.sanitizeLuceneQuery(e);try{let i="";r&&r.length>0&&(i="WHERE node.memoryType IN $memoryTypes");let c=`
CALL db.index.fulltext.queryNodes('memory_metadata_idx', $query)
YIELD node, score
${i}
RETURN node.id as id,
node.name as name,
node.metadata as metadata,
score
LIMIT $limit
`,o=await this.session.run(c,{query:s,limit:k.int(t),memoryTypes:r});for(let g of o.records)a.push({id:g.get("id"),name:g.get("name"),metadata:this.parseMetadata(g.get("metadata")),matchTypes:{exactMetadata:!0,exactName:!1,exactContent:!1}});let d=`
CALL db.index.fulltext.queryNodes('observation_content_idx', $query)
YIELD node, score
MATCH (m:Memory)-[:HAS_OBSERVATION]->(node)
${r&&r.length>0?"WHERE m.memoryType IN $memoryTypes":""}
RETURN DISTINCT m.id as id,
m.name as name,
m.metadata as metadata,
score
LIMIT $limit
`,h=await this.session.run(d,{query:s,limit:k.int(t),memoryTypes:r});for(let g of h.records){let J=g.get("id"),Z=a.find(re=>re.id===J);Z?Z.matchTypes.exactContent=!0:a.push({id:J,name:g.get("name"),metadata:this.parseMetadata(g.get("metadata")),matchTypes:{exactMetadata:!1,exactName:!1,exactContent:!0}})}}catch(i){let c=i instanceof Error?i.message:String(i);throw c.includes("No such index")||c.includes("IndexNotFoundError")?new p("FULLTEXT indexes are missing. Check that memory_metadata_idx and observation_content_idx exist",n.SERVICE_MISCONFIGURED,{missingIndexes:["memory_metadata_idx","observation_content_idx"],createCommand:"CREATE FULLTEXT INDEX ... IF NOT EXISTS"}):new p(`FULLTEXT search failed: ${c}`,n.SERVICE_ERROR,{service:"fulltext-search"})}return a}async searchExactName(e,t,r){let a="WHERE toLower(m.name) CONTAINS $query";r&&r.length>0&&(a+=" AND m.memoryType IN $memoryTypes");let s=`
MATCH (m:Memory)
${a}
RETURN m.id as id,
m.name as name,
m.metadata as metadata,
toLower(m.name) = $query as exactNameMatch
ORDER BY exactNameMatch DESC, m.name
LIMIT $limit
`;return(await this.session.run(s,{query:e,memoryTypes:r,limit:k.int(t)})).records.map(c=>({id:c.get("id"),name:c.get("name"),metadata:this.parseMetadata(c.get("metadata")),matchTypes:{exactMetadata:!1,exactName:!0,exactContent:!1}}))}sanitizeLuceneQuery(e){if(!e||typeof e!="string")return"";let t=e.replace(/\bAND\b/g,"and").replace(/\bOR\b/g,"or").replace(/\bNOT\b/g,"not").replace(/\bTO\b/g,"to"),r="";for(let a=0;a<t.length;a++){let s=t[a],i=a>0?t[a-1]:"";/^[0-9A-Za-z\s\\]$/.test(s)?r+=s:i!=="\\"?r+="\\"+s:r+=s}return r}parseMetadata(e){if(!e)return{};try{return JSON.parse(e)}catch{return{}}}};import ce from"neo4j-driver";var U=class{constructor(e){this.session=e;this.gdsVerified=null}async search(e,t,r,a){await this.ensureGDSAvailable();let s=await I(e);return this.searchWithGDS(s,t,r,a)}async ensureGDSAvailable(){if(this.gdsVerified!==!0)try{let t=(await this.session.run("RETURN gds.similarity.cosine([1,2,3], [2,3,4]) AS similarity")).records[0]?.get("similarity");if(typeof t!="number")throw new p("GDS function returned invalid result",n.SERVICE_MISCONFIGURED,{service:"neo4j-gds",expectedType:"number",actualType:typeof t});this.gdsVerified=!0}catch(e){this.gdsVerified=!1;let t=e instanceof Error?e.message:String(e);throw new p("Neo4j Graph Data Science (GDS) plugin is required but not installed",n.REQUIRED_SERVICE_MISSING,{service:"neo4j-gds",installUrl:"https://dozerdb.org/",verifyCommand:"RETURN gds.similarity.cosine([1,2,3], [2,3,4])",originalError:t})}}async searchWithGDS(e,t,r,a){let s="";a&&a.length>0&&(s="WHERE m.memoryType IN $memoryTypes");let i=`
MATCH (m:Memory)
${s}
// Calculate name embedding score using GDS
WITH m,
CASE WHEN m.nameEmbedding IS NOT NULL
THEN gds.similarity.cosine(m.nameEmbedding, $queryVector)
ELSE 0.0 END AS nameScore
// Calculate best observation embedding score using GDS
OPTIONAL MATCH (m)-[:HAS_OBSERVATION]->(o:Observation)
WITH m, nameScore,
CASE WHEN o.embedding IS NOT NULL
THEN gds.similarity.cosine(o.embedding, $queryVector)
ELSE 0.0 END AS obsScore
// Get the maximum score between name and all observations
WITH m, nameScore, max(obsScore) AS maxObsScore
WITH m, CASE WHEN nameScore >= coalesce(maxObsScore, 0.0)
THEN nameScore
ELSE coalesce(maxObsScore, 0.0) END AS bestScore
WHERE bestScore >= $threshold
ORDER BY bestScore DESC
LIMIT $limit
RETURN m.id as id, bestScore as score
`;try{return(await this.session.run(i,{queryVector:e,threshold:r,limit:ce.int(t),memoryTypes:a})).records.map(o=>({id:o.get("id"),score:o.get("score")}))}catch(c){let o=c instanceof Error?c.message:String(c);throw o.includes("gds.similarity")||o.includes("Unknown function")?new p("GDS vector search failed. The plugin may have been disabled or removed",n.SERVICE_UNAVAILABLE,{service:"neo4j-gds",originalError:o,verifyCommand:"RETURN gds.similarity.cosine([1,2,3], [2,3,4])"}):new p(`Vector search query failed: ${o}`,n.SERVICE_ERROR,{service:"vector-search",query:"gds-similarity"})}}isGDSVerified(){return this.gdsVerified}};import me from"neo4j-driver";var L=class{constructor(e){this.session=e}async search(e,t,r,a,s){let i=[],c={limit:me.int(e)};r&&r.length>0&&(i.push("m.memoryType IN $memoryTypes"),c.memoryTypes=r),a&&(i.push(a),Object.assign(c,s));let o=i.length>0?" WHERE "+i.join(" AND "):"",d=t?this.buildWildcardWithContextQuery(o):this.buildBasicWildcardQuery(o);return(await this.session.run(d,c)).records.map(g=>this.mapRecordToResult(g,t))}buildBasicWildcardQuery(e){return`
MATCH (m:Memory)${e}
OPTIONAL MATCH (m)-[:HAS_OBSERVATION]->(o:Observation)
WITH m, o ORDER BY o.createdAt ASC
WITH m, collect(DISTINCT {id: o.id, content: o.content, createdAt: o.createdAt}) as observations
RETURN m.id as id,
m.name as name,
m.memoryType as type,
m.metadata as metadata,
m.createdAt as createdAt,
m.modifiedAt as modifiedAt,
m.lastAccessed as lastAccessed,
observations
ORDER BY m.createdAt DESC
LIMIT $limit
`}buildWildcardWithContextQuery(e){return`
MATCH (m:Memory)${e}
// Graph context collection
OPTIONAL MATCH path1 = (ancestor:Memory)-[rel1:RELATES_TO*1..2]->(m)
WHERE ancestor <> m AND ancestor.id IS NOT NULL
WITH m, collect(DISTINCT {
id: ancestor.id,
name: ancestor.name,
type: ancestor.memoryType,
relation: rel1[-1].relationType,
distance: length(path1),
strength: rel1[-1].strength,
source: rel1[-1].source,
createdAt: rel1[-1].createdAt
})[0..3] as ancestors
OPTIONAL MATCH path2 = (m)-[rel2:RELATES_TO*1..2]->(descendant:Memory)
WHERE descendant <> m AND descendant.id IS NOT NULL
WITH m, ancestors, collect(DISTINCT {
id: descendant.id,
name: descendant.name,
type: descendant.memoryType,
relation: rel2[0].relationType,
distance: length(path2),
strength: rel2[0].strength,
source: rel2[0].source,
createdAt: rel2[0].createdAt
})[0..3] as descendants
// Observations with chronological ordering
OPTIONAL MATCH (m)-[:HAS_OBSERVATION]->(o:Observation)
WITH m, ancestors, descendants, o ORDER BY o.createdAt ASC
WITH m, ancestors, descendants,
collect(DISTINCT {id: o.id, content: o.content, createdAt: o.createdAt}) as observations
RETURN m.id as id,
m.name as name,
m.memoryType as type,
m.metadata as metadata,
m.createdAt as createdAt,
m.modifiedAt as modifiedAt,
m.lastAccessed as lastAccessed,
observations,
[rel IN ancestors WHERE rel.id IS NOT NULL] as ancestors,
[rel IN descendants WHERE rel.id IS NOT NULL] as descendants
ORDER BY m.createdAt DESC
LIMIT $limit
`}mapRecordToResult(e,t){let r={id:e.get("id"),name:e.get("name"),type:e.get("type"),observations:this.processObservations(e.get("observations")||[]),metadata:this.parseMetadata(e.get("metadata")),createdAt:e.get("createdAt"),modifiedAt:e.get("modifiedAt"),lastAccessed:e.get("lastAccessed"),score:1};if(t){let a=e.get("ancestors")||[],s=e.get("descendants")||[];(a.length>0||s.length>0)&&(r.related={ancestors:a.length>0?a.map(this.convertNeo4jInteger):void 0,descendants:s.length>0?s.map(this.convertNeo4jInteger):void 0})}return r}processObservations(e){return Array.isArray(e)?e.filter(t=>t&&t.content).map(t=>({id:t.id,content:t.content,createdAt:t.createdAt||new Date().toISOString()})):[]}parseMetadata(e){if(!e)return{};try{return JSON.parse(e)}catch{return{}}}convertNeo4jInteger(e){return e&&typeof e.distance=="object"&&e.distance.toNumber?{...e,distance:e.distance.toNumber()}:e}};var $=class{constructor(e){this.session=e;this.queryClassifier=new P,this.exactChannel=new _(e),this.vectorChannel=new U(e),this.wildcardService=new L(e)}async search(e,t=10,r=!0,a,s=.1){if(!e||typeof e!="string")throw new y("Search query must be a non-empty string",n.INVALID_SEARCH_QUERY);if(t<=0)throw new y("Search limit must be positive",n.INVALID_PARAMETER,{parameter:"limit",value:t});let i=this.queryClassifier.classify(e);return i.type==="wildcard"?(await this.wildcardService.search(t,r,a)).map(o=>({...o,score:1,matchType:"exact"})):this.executeSearch(i,t,s,a)}async executeSearch(e,t,r,a){let s=await this.exactChannel.search(e.preprocessing.normalized,t*2,a),i=[];if(e.type==="semantic_search")try{i=await this.vectorChannel.search(e.preprocessing.normalized,t*2,r,a)}catch{}let c=new Map;for(let d of s)c.set(d.id,{id:d.id,hasExactMatch:!0});for(let d of i){let h=c.get(d.id);h?h.vectorScore=d.score:c.set(d.id,{id:d.id,hasExactMatch:!1,vectorScore:d.score})}let o=Array.from(c.values()).map(d=>this.calculateSimpleScore(d)).filter(d=>d.score>=r).sort((d,h)=>h.score-d.score).slice(0,t);return this.enrichWithMemoryData(o,a)}calculateSimpleScore(e){if(e.hasExactMatch){let r=e.vectorScore?Math.min(e.vectorScore*.15,.15):0;return{id:e.id,score:Math.min(.85+r,1),matchType:"exact"}}return e.vectorScore?{id:e.id,score:e.vectorScore,matchType:"semantic"}:{id:e.id,score:.1,matchType:"exact"}}async enrichWithMemoryData(e,t){if(e.length===0)return[];let r=e.map(o=>o.id),a="WHERE m.id IN $candidateIds";t&&t.length>0&&(a+=" AND m.memoryType IN $memoryTypes");let s=`
MATCH (m:Memory)
${a}
// Graph context - 2 levels deep with exact relation types
OPTIONAL MATCH path1 = (ancestor:Memory)-[rel1*1..2]->(m)
WHERE ancestor <> m AND ancestor.id IS NOT NULL
WITH m, collect(DISTINCT {
id: ancestor.id,
name: ancestor.name,
type: ancestor.memoryType,
relation: rel1[0].relationType,
distance: length(path1),
strength: rel1[0].strength,
source: rel1[0].source,
createdAt: rel1[0].createdAt
})[0..3] as ancestors
OPTIONAL MATCH path2 = (m)-[rel2*1..2]->(descendant:Memory)
WHERE descendant <> m AND descendant.id IS NOT NULL
WITH m, ancestors, collect(DISTINCT {
id: descendant.id,
name: descendant.name,
type: descendant.memoryType,
relation: rel2[0].relationType,
distance: length(path2),
strength: rel2[0].strength,
source: rel2[0].source,
createdAt: rel2[0].createdAt
})[0..3] as descendants
// Core content with ordered observations
OPTIONAL MATCH (m)-[:HAS_OBSERVATION]->(o:Observation)
WITH m, ancestors, descendants, o
ORDER BY o.createdAt ASC
WITH m, ancestors, descendants,
collect(DISTINCT {id: o.id, content: o.content, createdAt: o.createdAt}) as observations
RETURN m.id as id,
m.name as name,
m.memoryType as type,
m.metadata as metadata,
m.createdAt as createdAt,
m.modifiedAt as modifiedAt,
m.lastAccessed as lastAccessed,
observations,
ancestors,
descendants
ORDER BY m.name
`,i=await this.session.run(s,{candidateIds:r,memoryTypes:t}),c=new Map;for(let o of i.records){let d=o.get("ancestors")||[],h=o.get("descendants")||[],g={id:o.get("id"),name:o.get("name"),type:o.get("type"),observations:this.processObservations(o.get("observations")||[]),metadata:this.parseMetadata(o.get("metadata")),createdAt:o.get("createdAt"),modifiedAt:o.get("modifiedAt"),lastAccessed:o.get("lastAccessed"),related:this.processGraphContext(d,h)};c.set(g.id,g)}return e.map(o=>{let d=c.get(o.id);return d?{...d,score:o.score,matchType:o.matchType}:{id:o.id,name:`Unknown Memory ${o.id}`,type:"unknown",observations:[],metadata:{},score:o.score,matchType:o.matchType}}).filter(o=>o.id)}processObservations(e){return Array.isArray(e)?e.filter(t=>t&&t.content).map(t=>({id:t.id,content:t.content,createdAt:t.createdAt||new Date().toISOString()})):[]}processGraphContext(e,t){let r=e.filter(s=>s.id!==null).map(s=>({...s,distance:s.distance?this.convertNeo4jInteger(s.distance):0})),a=t.filter(s=>s.id!==null).map(s=>({...s,distance:s.distance?this.convertNeo4jInteger(s.distance):0}));if(r.length>0||a.length>0)return{...r.length>0&&{ancestors:r},...a.length>0&&{descendants:a}}}convertNeo4jInteger(e){return typeof e=="number"?e:e&&typeof e.toNumber=="function"?e.toNumber():0}parseMetadata(e){if(!e)return{};try{return JSON.parse(e)}catch{return{}}}};var F=class{constructor(e){this.sessionFactory=e}async search(e){let t=this.sessionFactory.createSession();try{let a=await new $(t).search(e.query,e.limit||10,e.includeGraphContext!==!1,e.memoryTypes,e.threshold||.1);return this.convertToSearchResults(a)}finally{await t.close()}}convertToSearchResults(e){return e.map(t=>({memory:{id:t.id,name:t.name,memoryType:t.type,observations:t.observations,metadata:t.metadata,createdAt:t.createdAt?new Date(t.createdAt):new Date,modifiedAt:t.modifiedAt?new Date(t.modifiedAt):new Date,lastAccessed:t.lastAccessed?new Date(t.lastAccessed):new Date,related:t.related},score:t.score||0,matchType:this.determineMatchType(t.matchType)}))}determineMatchType(e){switch(e){case"semantic":return"vector";case"exact":return"metadata";default:return"vector"}}};var H=class{static validate(e){if(!e.id||e.id.length!==18)throw new Error("Memory ID must be exactly 18 characters");if(!e.name||e.name.trim().length===0)throw new Error("Memory name is required");if(!e.memoryType||e.memoryType.trim().length===0)throw new Error("Memory type is required")}static markAsAccessed(e){return{...e,lastAccessed:new Date}}static withUpdatedMetadata(e,t){return{...e,metadata:{...e.metadata,...t},modifiedAt:new Date}}};var V=class{constructor(e,t){this.memoryRepository=e;this.embeddingService=t}async execute(e){let t=x(),r=await this.embeddingService.calculateEmbedding(e.name),a={id:t,name:e.name,memoryType:e.memoryType,metadata:e.metadata||{},createdAt:new Date,modifiedAt:new Date,lastAccessed:new Date};H.validate(a),a.nameEmbedding=r;let s=await this.memoryRepository.create(a);return e.observations&&e.observations.length>0&&await this.memoryRepository.addObservations(t,e.observations),s}};var B=class{constructor(e){this.searchRepository=e}async execute(e){if(!e.query||e.query.trim().length===0)throw new y("Search query is required",n.EMPTY_QUERY);if(e.limit!==void 0&&e.limit<=0)throw new y("Search limit must be positive",n.INVALID_LIMIT);if(e.threshold&&(e.threshold<0||e.threshold>1))throw new y("Search threshold must be between 0 and 1",n.INVALID_THRESHOLD);return await this.searchRepository.search(e)}};var W=class{constructor(e){this.memoryRepository=e}async execute(e){let t=await this.memoryRepository.findById(e.id);if(!t)throw new u("Memory",e.id,n.MEMORY_NOT_FOUND);let r={...t,...e.name!==void 0&&{name:e.name},...e.memoryType!==void 0&&{memoryType:e.memoryType},...e.metadata!==void 0&&{metadata:e.metadata},modifiedAt:new Date};return await this.memoryRepository.update(r)}};var j=class{constructor(e){this.memoryRepository=e}async execute(e){if(!await this.memoryRepository.findById(e))throw new u("Memory",e,n.MEMORY_NOT_FOUND);return await this.memoryRepository.delete(e)}async executeMany(e){let t={deleted:0,errors:[]};for(let r of e)try{await this.execute(r),t.deleted++}catch(a){t.errors.push(E(`Failed to delete ${r}`,a))}return t}};var z=class{constructor(e){this.memoryRepository=e}async addObservations(e){if(!await this.memoryRepository.findById(e.memoryId))throw new u("Memory",e.memoryId,n.MEMORY_NOT_FOUND);await this.memoryRepository.addObservations(e.memoryId,e.contents)}async deleteObservations(e){if(!await this.memoryRepository.findById(e.memoryId))throw new u("Memory",e.memoryId,n.MEMORY_NOT_FOUND);return this.validateObservationIds(e.contents),await this.memoryRepository.deleteObservations(e.memoryId,e.contents)}async executeMany(e,t){let r={processed:0,errors:[]};for(let a of t)try{e==="add"?(await this.addObservations(a),r.processed+=a.contents.length):(await this.deleteObservations(a),r.processed+=a.contents.length)}catch(s){r.errors.push(E(`Failed to ${e} observations for memory ${a.memoryId}`,s))}return r}validateObservationIds(e){for(let t of e)if(!this.isValidObservationId(t))throw new y(`DELETE operation requires observation IDs, not content strings. Received: "${t}". Expected: 18-character BASE85 identifier (e.g., "dZD1/D-Ljt*!I?R)\`-"). To delete observations: 1) First search/retrieve memory to get observation IDs, 2) Then use those IDs for deletion.`,n.INVALID_OBSERVATION_CONTENT)}isValidObservationId(e){return e.length!==18?!1:/^[0-9A-Za-z!#$%&()*+,\-./:;=?@_{}~`]+$/.test(e)}};var X=class{constructor(e){this.memoryRepository=e}async createRelation(e){if(!await this.memoryRepository.findById(e.fromId))throw new u("Source memory",e.fromId,n.MEMORY_NOT_FOUND);if(!await this.memoryRepository.findById(e.toId))throw new u("Target memory",e.toId,n.MEMORY_NOT_FOUND);let a={fromId:e.fromId,toId:e.toId,relationType:e.relationType,strength:e.strength||.5,source:e.source||"agen