open-mem 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -3
- package/README.md +77 -27
- package/dist/adapters/http/server.d.ts +15 -1
- package/dist/adapters/http/server.d.ts.map +1 -1
- package/dist/adapters/http/sse.d.ts +16 -1
- package/dist/adapters/http/sse.d.ts.map +1 -1
- package/dist/adapters/mcp/server.d.ts +25 -1
- package/dist/adapters/mcp/server.d.ts.map +1 -1
- package/dist/adapters/opencode/tools.d.ts.map +1 -1
- package/dist/adapters/platform/bridge-client.d.ts +32 -0
- package/dist/adapters/platform/bridge-client.d.ts.map +1 -0
- package/dist/adapters/platform/builtin.d.ts +6 -0
- package/dist/adapters/platform/builtin.d.ts.map +1 -0
- package/dist/adapters/platform/claude-code.d.ts +7 -0
- package/dist/adapters/platform/claude-code.d.ts.map +1 -0
- package/dist/adapters/platform/cursor.d.ts +7 -0
- package/dist/adapters/platform/cursor.d.ts.map +1 -0
- package/dist/adapters/platform/index.d.ts +9 -0
- package/dist/adapters/platform/index.d.ts.map +1 -0
- package/dist/adapters/platform/normalize.d.ts +14 -0
- package/dist/adapters/platform/normalize.d.ts.map +1 -0
- package/dist/adapters/platform/opencode.d.ts +7 -0
- package/dist/adapters/platform/opencode.d.ts.map +1 -0
- package/dist/adapters/platform/runtime.d.ts +27 -0
- package/dist/adapters/platform/runtime.d.ts.map +1 -0
- package/dist/adapters/platform/types.d.ts +53 -0
- package/dist/adapters/platform/types.d.ts.map +1 -0
- package/dist/ai/compressor.d.ts.map +1 -1
- package/dist/ai/conflict-evaluator.d.ts.map +1 -1
- package/dist/ai/entity-extractor.d.ts +2 -2
- package/dist/ai/entity-extractor.d.ts.map +1 -1
- package/dist/ai/parser.d.ts.map +1 -1
- package/dist/ai/prompts.d.ts.map +1 -1
- package/dist/ai/summarizer.d.ts.map +1 -1
- package/dist/claude-code.d.ts +3 -0
- package/dist/claude-code.d.ts.map +1 -0
- package/dist/claude-code.js +551 -0
- package/dist/config/store.d.ts.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/context/builder.d.ts.map +1 -1
- package/dist/contracts/api.d.ts +129 -0
- package/dist/contracts/api.d.ts.map +1 -0
- package/dist/core/contracts.d.ts +67 -1
- package/dist/core/contracts.d.ts.map +1 -1
- package/dist/core/memory-engine.d.ts +33 -2
- package/dist/core/memory-engine.d.ts.map +1 -1
- package/dist/cursor.d.ts +3 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +551 -0
- package/dist/daemon.js +146 -122
- package/dist/dashboard/assets/index-BTEnO15N.js +63 -0
- package/dist/dashboard/assets/index-o3hCx7_v.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/db/config-audit.d.ts +10 -0
- package/dist/db/config-audit.d.ts.map +1 -0
- package/dist/db/entities.d.ts.map +1 -1
- package/dist/db/maintenance-history.d.ts +9 -0
- package/dist/db/maintenance-history.d.ts.map +1 -0
- package/dist/db/observations.d.ts +12 -0
- package/dist/db/observations.d.ts.map +1 -1
- package/dist/db/schema.d.ts +3 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/user-memory.d.ts.map +1 -1
- package/dist/hooks/chat-capture.d.ts +11 -0
- package/dist/hooks/chat-capture.d.ts.map +1 -1
- package/dist/hooks/context-inject.d.ts.map +1 -1
- package/dist/hooks/session-events.d.ts +10 -0
- package/dist/hooks/session-events.d.ts.map +1 -1
- package/dist/hooks/tool-capture.d.ts +12 -0
- package/dist/hooks/tool-capture.d.ts.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +223 -225
- package/dist/maintenance.js +130 -106
- package/dist/mcp.js +184 -187
- package/dist/platform-worker.d.ts +3 -0
- package/dist/platform-worker.d.ts.map +1 -0
- package/dist/queue/processor.d.ts +26 -1
- package/dist/queue/processor.d.ts.map +1 -1
- package/dist/runtime/metrics.d.ts +43 -0
- package/dist/runtime/metrics.d.ts.map +1 -0
- package/dist/search/graph.d.ts.map +1 -1
- package/dist/search/orchestrator.d.ts +1 -0
- package/dist/search/orchestrator.d.ts.map +1 -1
- package/dist/search/reranker.d.ts +1 -1
- package/dist/search/reranker.d.ts.map +1 -1
- package/dist/store/ports.d.ts +9 -0
- package/dist/store/ports.d.ts.map +1 -1
- package/dist/store/sqlite/adapters.d.ts.map +1 -1
- package/dist/tools/recall.d.ts.map +1 -1
- package/dist/tools/save.d.ts +1 -1
- package/dist/tools/save.d.ts.map +1 -1
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agents-md.d.ts.map +1 -1
- package/dist/utils/folder-context-maintenance.d.ts.map +1 -1
- package/package.json +27 -6
- package/dist/dashboard/assets/index-9JxqY10c.css +0 -1
- package/dist/dashboard/assets/index-DWbZtwHB.js +0 -60
- package/dist/dashboard/dist/assets/index-9JxqY10c.css +0 -1
- package/dist/dashboard/dist/assets/index-CGCNZcwT.js +0 -60
- package/dist/dashboard/dist/assets/index-CI60x_dC.css +0 -1
- package/dist/dashboard/dist/assets/index-CTwrdVhA.js +0 -60
- package/dist/dashboard/dist/assets/index-DWbZtwHB.js +0 -60
- package/dist/dashboard/dist/index.html +0 -19
- package/dist/servers/http-server.d.ts +0 -22
- package/dist/servers/http-server.d.ts.map +0 -1
- package/dist/servers/mcp-server.d.ts +0 -27
- package/dist/servers/mcp-server.d.ts.map +0 -1
- package/dist/servers/sse-broadcaster.d.ts +0 -27
- package/dist/servers/sse-broadcaster.d.ts.map +0 -1
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var D=import.meta.require;import{createInterface as n0}from"readline";import{parseArgs as d0}from"util";var Kf={name:"claude-code",version:"0.1",capabilities:{nativeSessionLifecycle:!0,nativeToolCapture:!0,nativeChatCapture:!0,emulatedIdleFlush:!0}},Jf={name:"cursor",version:"0.1",capabilities:{nativeSessionLifecycle:!1,nativeToolCapture:!0,nativeChatCapture:!0,emulatedIdleFlush:!0}};function g(){return new Date().toISOString()}function P(f){if(!f||typeof f!=="object"||Array.isArray(f))return null;return f}function H(f){return typeof f==="string"&&f.trim().length>0?f:null}function O(f,B){let E=P(B);if(!E)return null;let K=H(E.kind),J=H(E.sessionId);if(!K||!J)return null;if(K==="session.start"||K==="session.end"||K==="idle.flush")return{kind:K,platform:f,sessionId:J,occurredAt:H(E.occurredAt)??g(),metadata:P(E.metadata)??void 0};if(K==="chat.message"){let z=H(E.text);if(!z)return null;let A=H(E.role);return{kind:K,platform:f,sessionId:J,text:z,role:A==="assistant"||A==="system"?A:"user",occurredAt:H(E.occurredAt)??g(),metadata:P(E.metadata)??void 0}}if(K==="tool.execute"){let z=H(E.toolName),A=H(E.output);if(!z||!A)return null;return{kind:K,platform:f,sessionId:J,callId:H(E.callId)??`${f}-${Date.now()}`,toolName:z,output:A,occurredAt:H(E.occurredAt)??g(),metadata:P(E.metadata)??void 0}}return null}function nf(f){let B=f.type??f.event;if(!B)return null;if(B==="session.start")return"session.start";if(B==="session.end")return"session.end";if(B==="idle.flush")return"idle.flush";if(B==="tool.execute")return"tool.execute";if(B==="chat.message")return"chat.message";return null}class zf{descriptor=Kf;normalize(f){if(!f||typeof f!=="object"||Array.isArray(f))return null;let B=f,E=nf(B);if(!E||!B.sessionId)return null;return O("claude-code",{kind:E,sessionId:B.sessionId,callId:B.callId,toolName:B.toolName,output:B.output,role:B.role,text:B.text,occurredAt:B.occurredAt,metadata:B.metadata})}}function Af(){return new zf}function df(f){let B=f.eventName??f.event;if(!B)return null;if(B==="sessionStart")return"session.start";if(B==="sessionEnd")return"session.end";if(B==="idleFlush")return"idle.flush";if(B==="toolExecute")return"tool.execute";if(B==="chatMessage")return"chat.message";return null}class Nf{descriptor=Jf;normalize(f){if(!f||typeof f!=="object"||Array.isArray(f))return null;let B=f,E=df(B),K=B.sessionId??B.session;if(!E||!K)return null;return O("cursor",{kind:E,sessionId:K,callId:B.callId??B.invocationId,toolName:B.toolName??B.tool,output:B.output,role:B.role,text:B.text??B.message,occurredAt:B.occurredAt??B.timestamp,metadata:B.metadata??B.meta})}}function Rf(){return new Nf}function j(f,B=""){if(!f)return f;return f.replace(/<private>[\s\S]*?<\/private>/gi,B)}var $f=200,rf=/(\([\s\S]*[+*]\)\s*[+*?])|(\(\.\*\)\+)|(\(\.\+\)\+)/;function q(f,B,E="[REDACTED]"){if(!f||B.length===0)return f;let K=f;for(let J of B){if(J.length>$f){console.warn(`[open-mem] Skipping oversized redaction pattern (${J.length} chars, max ${$f})`);continue}if(rf.test(J)){console.warn("[open-mem] Skipping potentially dangerous redaction pattern (nested quantifiers detected)");continue}try{K=K.replace(new RegExp(J,"g"),E)}catch{}}return K}var sf=20,Wf=2000,Qf=60;function of(f){let B=f.toLowerCase().replace(/[^a-z0-9\s-]/g," ").split(/\s+/).filter((E)=>E.length>4);return[...new Set(B)].slice(0,5)}function uf(f){let{observations:B,sessions:E,projectPath:K,sessionId:J,text:z,agent:A,sensitivePatterns:R=[]}=f;if(A!==void 0&&A!=="user")return!1;let N=q(j(z),R);if(N.length<sf)return!1;E.getOrCreate(J,K);let W=`User request: ${N.length>Qf?`${N.slice(0,Qf)}...`:N}`,Q=N.length>Wf?`${N.slice(0,Wf)}...`:N;return B.create({sessionId:J,type:"discovery",title:W,subtitle:"",facts:[],narrative:Q,concepts:of(N),filesRead:[],filesModified:[],rawToolOutput:"",toolName:"chat.message",tokenCount:Math.ceil(Q.length/4),discoveryTokens:0,importance:3}),!0}import{existsSync as Uf}from"fs";import{mkdir as tf,readFile as ef,rename as af,unlink as f0,writeFile as B0}from"fs/promises";import{dirname as l,isAbsolute as Vf,join as T,normalize as E0,relative as Hf,resolve as w,sep as p}from"path";var h="<!-- open-mem-context -->",F="<!-- /open-mem-context -->",K0={bugfix:"\uD83D\uDD34",feature:"\uD83D\uDFE3",refactor:"\uD83D\uDD04",change:"\u2705",discovery:"\uD83D\uDD35",decision:"\u2696\uFE0F"},Zf=new Map,J0=new Set(["node_modules",".git","dist","coverage",".open-mem","build","__pycache__",".next",".nuxt"]);async function Yf(f,B,E=5){if(B.length===0)return;let K=[];for(let A of B){for(let R of A.filesModified)K.push(R);for(let R of A.filesRead)K.push(R)}let J=R0(K,f,E);if(J.size===0)return;let z=$0(B,J,f);for(let[A,R]of z)try{let N=z0(A,R,f);await A0(A,N)}catch(N){console.error(`[open-mem] Failed to update AGENTS.md in ${A}:`,N)}}function z0(f,B,E){let K=[...B].sort((N,$)=>$.createdAt.localeCompare(N.createdAt)).slice(0,10),J=Hf(E,f)||".",z=[];z.push(`## Recent Activity in \`${J}/\` (auto-generated by open-mem)`),z.push(""),z.push("| Type | Title | Date |"),z.push("|------|-------|------|");for(let N of K){let $=K0[N.type]||"\uD83D\uDCDD",W=N.createdAt.split("T")[0],Q=N.title.replace(/\|/g,"\\|");z.push(`| ${$} ${N.type} | ${Q} | ${W} |`)}let A=new Set;for(let N of K)for(let $ of N.concepts)A.add($);if(A.size>0){let N=[...A].slice(0,10).join(", ");z.push(""),z.push(`**Key concepts:** ${N}`)}let R=K.filter((N)=>N.type==="decision").map((N)=>N.title);if(R.length>0)z.push(""),z.push(`**Recent decisions:** ${R.slice(0,5).join("; ")}`);return z.join(`
|
|
4
|
+
`)}async function A0(f,B){if(!Uf(f))return;let K=(Zf.get(f)??Promise.resolve()).then(async()=>{let J=T(f,"AGENTS.md"),z=T(f,".AGENTS.md.tmp"),A="";try{A=await ef(J,"utf-8")}catch{}let R=N0(A,B);try{await tf(l(z),{recursive:!0}),await B0(z,R,"utf-8"),await af(z,J)}catch(N){try{await f0(z)}catch{}throw N}});return Zf.set(f,K.catch(()=>{})),K}function N0(f,B){if(!f)return`${h}
|
|
5
|
+
${B}
|
|
6
|
+
${F}
|
|
7
|
+
`;let E=f.indexOf(h),K=f.indexOf(F);if(E!==-1&&K!==-1&&K>E){let J=f.substring(0,E),z=f.substring(K+F.length);return`${J}${h}
|
|
8
|
+
${B}
|
|
9
|
+
${F}${z}`}return`${f}
|
|
10
|
+
|
|
11
|
+
${h}
|
|
12
|
+
${B}
|
|
13
|
+
${F}
|
|
14
|
+
`}function R0(f,B,E){let K=new Set,J=w(B);for(let z of f){if(!z||!z.trim())continue;if(z.startsWith("~")||z.startsWith("http"))continue;let A=Vf(z)?z:T(B,z),R=l(A),N=w(R);if(!N.startsWith(J+p)&&N!==J)continue;if(N===J)continue;let $=Hf(J,N);if($.split(p).length>E)continue;if(E0($).split(p).some((Z)=>J0.has(Z)))continue;if(!Uf(N))continue;K.add(N)}return K}function $0(f,B,E){let K=new Map;for(let J of f){let z=[...J.filesModified,...J.filesRead],A=new Set;for(let R of z){if(!R)continue;let N=Vf(R)?R:T(E,R),$=w(l(N));if(B.has($))A.add($)}for(let R of A){let N=K.get(R)??[];N.push(J),K.set(R,N)}}return K}function Xf(f,B,E){if(f.retentionDays===0)return;try{let K=B.deleteOlderThan(f.retentionDays),J=E.deleteCompletedOlderThan(f.retentionDays);if(K>0||J>0)console.log(`[open-mem] Retention: deleted ${K} observations, ${J} pending messages`)}catch(K){console.error("[open-mem] Retention enforcement error:",K)}}async function x(f,B,E){let{queue:K,sessions:J,projectPath:z,config:A,observations:R,pendingMessages:N}=f;switch(B){case"session.created":{if(E)J.getOrCreate(E,z);try{Xf(A,R,N)}catch($){console.error("[open-mem] Retention enforcement error:",$)}break}case"session.idle":{if(K.processBatch().catch(($)=>{console.error("[open-mem] Background processing error:",$)}),E)J.updateStatus(E,"idle"),Cf(E,z,A,R).catch(($)=>{console.error("[open-mem] Folder context error:",$)});break}case"session.completed":case"session.ended":{if(E)await K.processBatch(),await K.summarizeSession(E),J.markCompleted(E),await Cf(E,z,A,R);break}}}async function Cf(f,B,E,K){if(!E.folderContextEnabled)return;try{let J=K.getBySession(f);if(J.length>0)await Yf(B,J,E.folderContextMaxDepth)}catch(J){console.error("[open-mem] Folder context update error:",J)}}function Lf(f){let{config:B,queue:E,sessions:K,projectPath:J,tool:z,sessionId:A,callId:R,toolOutput:N}=f;if(B.ignoredTools.includes(z))return!1;if(!N||N.length<B.minOutputLength)return!1;let $=q(N,B.sensitivePatterns);return $=j($,"[PRIVATE]"),K.getOrCreate(A,J),E.enqueue(A,z,$,R),!0}class v{adapter;lifecycleDeps;queue;sessions;observations;projectPath;config;constructor(f){this.adapter=f.adapter,this.queue=f.queue,this.sessions=f.sessions,this.observations=f.observations,this.projectPath=f.projectPath,this.config=f.config,this.lifecycleDeps={queue:f.queue,sessions:f.sessions,projectPath:f.projectPath,config:f.config,observations:f.observations,pendingMessages:f.pendingMessages}}platform(){return this.adapter.descriptor.name}normalize(f){return this.adapter.normalize(f)}async ingestRaw(f){let B=this.normalize(f);if(!B)return!1;return await this.ingestNormalized(B),!0}async ingestNormalized(f){switch(f.kind){case"session.start":await x(this.lifecycleDeps,"session.created",f.sessionId);return;case"session.end":await x(this.lifecycleDeps,"session.ended",f.sessionId);return;case"idle.flush":await x(this.lifecycleDeps,"session.idle",f.sessionId);return;case"tool.execute":Lf({config:this.config,queue:this.queue,sessions:this.sessions,projectPath:this.projectPath,tool:f.toolName,sessionId:f.sessionId,callId:f.callId,toolOutput:f.output});return;case"chat.message":uf({observations:this.observations,sessions:this.sessions,projectPath:this.projectPath,sessionId:f.sessionId,text:f.text,agent:f.role==="user"?"user":f.role,sensitivePatterns:this.config.sensitivePatterns});return}}}import{generateText as Y0}from"ai";var W0=new Set(["decision","bugfix","feature","refactor","discovery","change"]);function u(f,B){let E=new RegExp(`<${B}[^>]*>([\\s\\S]*?)</${B}>`,"i"),K=f.match(E);return K?K[1].trim():""}function Y(f,B){let E=new RegExp(`<${B}[^>]*>([\\s\\S]*?)</${B}>`,"gi"),K=[];for(let J of f.matchAll(E)){let z=J[1].trim();if(z)K.push(z)}return K}function kf(f){let B=u(f,"observation");if(!B)return null;let E=u(B,"type").toLowerCase(),K=W0.has(E)?E:"discovery",J=u(B,"title")||"Untitled observation",z=u(B,"subtitle"),A=u(B,"narrative"),R=Y(u(B,"facts"),"fact"),N=Y(u(B,"concepts"),"concept"),$=Y(u(B,"files_read"),"file"),W=Y(u(B,"files_modified"),"file"),Q=u(B,"importance"),Z=Number.parseInt(Q,10),V=Number.isNaN(Z)?3:Math.max(1,Math.min(5,Z));return{type:K,title:J,subtitle:z,facts:R,narrative:A,concepts:N,filesRead:$,filesModified:W,importance:V}}function Sf(f){let B=u(f,"session_summary");if(!B)return null;let E=u(B,"summary")||"No summary available",K=Y(u(B,"key_decisions"),"decision"),J=Y(u(B,"files_modified"),"file"),z=Y(u(B,"concepts"),"concept"),A=u(B,"request")||void 0,R=u(B,"investigated")||void 0,N=u(B,"learned")||void 0,$=u(B,"completed")||void 0,W=u(B,"next_steps")||void 0;return{summary:E,keyDecisions:K,filesModified:J,concepts:z,request:A,investigated:R,learned:N,completed:$,nextSteps:W}}var Q0=new Set(["new_fact","update","duplicate"]);function Df(f){let B=u(f,"evaluation");if(!B)return null;let E=u(B,"outcome").toLowerCase().trim();if(!Q0.has(E))return null;let K=E,J=u(B,"reason");if(!J)return null;let z=u(B,"supersedes"),A={outcome:K,reason:J};if(K==="update"&&z)A.supersedesId=z;if(K==="update"&&!A.supersedesId)return null;return A}var u0=new Set(["technology","library","pattern","concept","file","person","project","other"]),Z0=new Set(["uses","depends_on","implements","extends","related_to","replaces","configures"]);function Mf(f){let B=u(f,"extraction");if(!B)return null;let E=u(B,"entities"),K=u(B,"relations"),J=Y(E,"entity"),z=[];for(let N of J){let $=u(N,"name");if(!$)continue;let W=u(N,"type").toLowerCase(),Q=u0.has(W)?W:"other";z.push({name:$,entityType:Q})}let A=Y(K,"relation"),R=[];for(let N of A){let $=u(N,"source"),W=u(N,"target"),Q=u(N,"relationship").toLowerCase();if(!$||!W||!Q)continue;if(!Z0.has(Q))continue;R.push({sourceName:$,targetName:W,relationship:Q})}return{entities:z,relations:R}}function M(f){return Math.ceil(f.length/4)}function _f(f,B,E){let K=E?`<session_context>
|
|
15
|
+
${E}
|
|
16
|
+
</session_context>
|
|
17
|
+
|
|
18
|
+
`:"";return`<task>
|
|
19
|
+
Analyze the following tool output and extract a structured observation.
|
|
20
|
+
</task>
|
|
21
|
+
|
|
22
|
+
<tool_name>${f}</tool_name>
|
|
23
|
+
|
|
24
|
+
<tool_output>
|
|
25
|
+
${B}
|
|
26
|
+
</tool_output>
|
|
27
|
+
|
|
28
|
+
${K}<instructions>
|
|
29
|
+
Extract a structured observation from the tool output. Determine the most appropriate type and provide a concise but informative summary.
|
|
30
|
+
|
|
31
|
+
When extracting concepts, prefer established vocabulary where appropriate:
|
|
32
|
+
- how-it-works: Technical mechanisms and behaviors
|
|
33
|
+
- why-it-exists: Rationale and motivations
|
|
34
|
+
- what-changed: Modifications and their effects
|
|
35
|
+
- problem-solution: Issues encountered and how they were resolved
|
|
36
|
+
- gotcha: Surprising behaviors, edge cases, or pitfalls
|
|
37
|
+
- pattern: Recurring design patterns or approaches
|
|
38
|
+
- trade-off: Deliberate compromises between competing concerns
|
|
39
|
+
You may also use any domain-specific concepts relevant to the observation.
|
|
40
|
+
|
|
41
|
+
Respond with EXACTLY this XML format:
|
|
42
|
+
<observation>
|
|
43
|
+
<type>decision|bugfix|feature|refactor|discovery|change</type>
|
|
44
|
+
<title>Brief descriptive title (max 80 chars)</title>
|
|
45
|
+
<subtitle>One-line elaboration</subtitle>
|
|
46
|
+
<importance>1-5 (1=trivial/routine, 2=low, 3=normal, 4=significant, 5=critical decision or discovery)</importance>
|
|
47
|
+
<facts>
|
|
48
|
+
<fact>Specific factual detail 1</fact>
|
|
49
|
+
<fact>Specific factual detail 2</fact>
|
|
50
|
+
</facts>
|
|
51
|
+
<narrative>2-3 sentence narrative explaining what happened and why it matters</narrative>
|
|
52
|
+
<concepts>
|
|
53
|
+
<concept>relevant-concept-1</concept>
|
|
54
|
+
<concept>relevant-concept-2</concept>
|
|
55
|
+
</concepts>
|
|
56
|
+
<files_read>
|
|
57
|
+
<file>path/to/file/read</file>
|
|
58
|
+
</files_read>
|
|
59
|
+
<files_modified>
|
|
60
|
+
<file>path/to/file/modified</file>
|
|
61
|
+
</files_modified>
|
|
62
|
+
</observation>
|
|
63
|
+
</instructions>`}function mf(f,B){let E=f.map((K,J)=>` <obs index="${J+1}" type="${K.type}">
|
|
64
|
+
<title>${K.title}</title>
|
|
65
|
+
<narrative>${K.narrative}</narrative>
|
|
66
|
+
</obs>`).join(`
|
|
67
|
+
`);return`<task>
|
|
68
|
+
Summarize the following coding session based on its observations.
|
|
69
|
+
</task>
|
|
70
|
+
|
|
71
|
+
<session_id>${B}</session_id>
|
|
72
|
+
|
|
73
|
+
<observations>
|
|
74
|
+
${E}
|
|
75
|
+
</observations>
|
|
76
|
+
|
|
77
|
+
<instructions>
|
|
78
|
+
Create a concise session summary. Focus on key decisions, outcomes, and patterns.
|
|
79
|
+
|
|
80
|
+
Respond with EXACTLY this XML format:
|
|
81
|
+
<session_summary>
|
|
82
|
+
<request>What the user asked for (1-2 sentences)</request>
|
|
83
|
+
<investigated>What was explored or researched</investigated>
|
|
84
|
+
<learned>Key discoveries and insights</learned>
|
|
85
|
+
<completed>What was accomplished</completed>
|
|
86
|
+
<next_steps>What to do next (if any)</next_steps>
|
|
87
|
+
<summary>2-4 sentence summary of the entire session</summary>
|
|
88
|
+
<key_decisions>
|
|
89
|
+
<decision>Important decision made during session</decision>
|
|
90
|
+
</key_decisions>
|
|
91
|
+
<files_modified>
|
|
92
|
+
<file>path/to/modified/file</file>
|
|
93
|
+
</files_modified>
|
|
94
|
+
<concepts>
|
|
95
|
+
<concept>key-concept</concept>
|
|
96
|
+
</concepts>
|
|
97
|
+
</session_summary>
|
|
98
|
+
</instructions>`}function Gf(f,B){let E=B.map((K)=>` <candidate id="${K.id}">
|
|
99
|
+
<title>${K.title}</title>
|
|
100
|
+
<narrative>${K.narrative}</narrative>
|
|
101
|
+
<concepts>${K.concepts.join(", ")}</concepts>
|
|
102
|
+
<type>${K.type}</type>
|
|
103
|
+
</candidate>`).join(`
|
|
104
|
+
`);return`<conflict_evaluation>
|
|
105
|
+
<new_observation>
|
|
106
|
+
<title>${f.title}</title>
|
|
107
|
+
<narrative>${f.narrative}</narrative>
|
|
108
|
+
<concepts>${f.concepts.join(", ")}</concepts>
|
|
109
|
+
<type>${f.type}</type>
|
|
110
|
+
</new_observation>
|
|
111
|
+
<existing_candidates>
|
|
112
|
+
${E}
|
|
113
|
+
</existing_candidates>
|
|
114
|
+
<instructions>
|
|
115
|
+
Evaluate whether the new observation represents:
|
|
116
|
+
1. new_fact \u2014 genuinely new information not covered by any candidate
|
|
117
|
+
2. update \u2014 supersedes/updates an existing candidate (newer version of same info)
|
|
118
|
+
3. duplicate \u2014 semantically identical to an existing candidate
|
|
119
|
+
|
|
120
|
+
Respond with EXACTLY this XML format:
|
|
121
|
+
<evaluation>
|
|
122
|
+
<outcome>new_fact|update|duplicate</outcome>
|
|
123
|
+
<supersedes>candidate-id (only if outcome is update)</supersedes>
|
|
124
|
+
<reason>Brief explanation</reason>
|
|
125
|
+
</evaluation>
|
|
126
|
+
</instructions>
|
|
127
|
+
</conflict_evaluation>`}function Ff(f){let B=[...f.filesRead,...f.filesModified];return`<entity_extraction>
|
|
128
|
+
<observation>
|
|
129
|
+
<title>${f.title}</title>
|
|
130
|
+
<type>${f.type}</type>
|
|
131
|
+
<narrative>${f.narrative}</narrative>
|
|
132
|
+
<facts>${f.facts.join(`
|
|
133
|
+
`)}</facts>
|
|
134
|
+
<files>${B.join(`
|
|
135
|
+
`)}</files>
|
|
136
|
+
<concepts>${f.concepts.join(", ")}</concepts>
|
|
137
|
+
</observation>
|
|
138
|
+
<instructions>
|
|
139
|
+
Extract entities and relationships from this observation.
|
|
140
|
+
|
|
141
|
+
Entity types: technology, library, pattern, concept, file, person, project, other
|
|
142
|
+
Relationship types: uses, depends_on, implements, extends, related_to, replaces, configures
|
|
143
|
+
|
|
144
|
+
Extract entities that are clearly mentioned or strongly implied. For example, "React hooks" implies a relationship between React and hooks. Do not extract speculative relationships.
|
|
145
|
+
Respond with EXACTLY this XML format:
|
|
146
|
+
<extraction>
|
|
147
|
+
<entities>
|
|
148
|
+
<entity><name>React</name><type>library</type></entity>
|
|
149
|
+
<entity><name>useState</name><type>pattern</type></entity>
|
|
150
|
+
</entities>
|
|
151
|
+
<relations>
|
|
152
|
+
<relation><source>React</source><relationship>uses</relationship><target>useState</target></relation>
|
|
153
|
+
</relations>
|
|
154
|
+
</extraction>
|
|
155
|
+
</instructions>
|
|
156
|
+
</entity_extraction>`}var U0={"claude-sonnet-4-20250514":"us.anthropic.claude-sonnet-4-20250514-v1:0","claude-opus-4-20250514":"us.anthropic.claude-opus-4-20250514-v1:0","claude-3-5-sonnet-20241022":"us.anthropic.claude-3-5-sonnet-20241022-v2:0","claude-3-5-haiku-20241022":"us.anthropic.claude-3-5-haiku-20241022-v1:0","claude-3-haiku-20240307":"anthropic.claude-3-haiku-20240307-v1:0"};function V0(f){if(f.includes("."))return f;return U0[f]||`us.anthropic.${f}-v1:0`}function C(f){switch(f.provider){case"anthropic":{let{createAnthropic:B}=D("@ai-sdk/anthropic");return B({apiKey:f.apiKey})(f.model)}case"bedrock":{let{createAmazonBedrock:B}=D("@ai-sdk/amazon-bedrock");return B()(V0(f.model))}case"openai":{let{createOpenAI:B}=D("@ai-sdk/openai");return B({apiKey:f.apiKey})(f.model)}case"google":{let{createGoogleGenerativeAI:B}=D("@ai-sdk/google");return B({apiKey:f.apiKey})(f.model)}default:throw Error(`Unknown provider: ${f.provider}. Supported: anthropic, bedrock, openai, google`)}}function yf(f){try{switch(f.provider){case"google":{let{createGoogleGenerativeAI:B}=D("@ai-sdk/google");return B({apiKey:f.apiKey}).embedding("text-embedding-004")}case"openai":{let{createOpenAI:B}=D("@ai-sdk/openai");return B({apiKey:f.apiKey}).embedding("text-embedding-3-small")}case"bedrock":{let{createAmazonBedrock:B}=D("@ai-sdk/amazon-bedrock");return B().embedding("amazon.titan-embed-text-v2:0")}case"anthropic":return null;default:return null}}catch{return null}}var H0={"gemini-2.5-flash-lite":10,"gemini-2.5-flash":10,"gemini-2.5-pro":5,"gemini-2.0-flash":15,"gemini-2.0-flash-lite":30,"gemini-3-flash":5},Pf=0;async function L(f,B){if(!B)return;let E=H0[f]||5,K=Math.ceil(60000/E)+100,z=Date.now()-Pf;if(z<K){let A=K-z;await new Promise((R)=>setTimeout(R,A))}Pf=Date.now()}class y{model;config;_generate=Y0;constructor(f){this.config=f,this.model=null;let B=f.provider!=="bedrock";if(f.compressionEnabled&&(!B||f.apiKey))try{this.model=C({provider:f.provider,model:f.model,apiKey:f.apiKey})}catch{}}static MAX_INPUT_LENGTH=50000;async compress(f,B,E){if(!this.config.compressionEnabled||!this.model)return null;if(B.length<this.config.minOutputLength)return null;let K=M(B),J=B.length>y.MAX_INPUT_LENGTH?`${B.substring(0,y.MAX_INPUT_LENGTH)}
|
|
157
|
+
|
|
158
|
+
[... truncated ...]`:B,z=_f(f,J,E),A=2;for(let R=0;R<=A;R++)try{if(this.config.provider==="google")await L(this.config.model,this.config.rateLimitingEnabled);let{text:N}=await this._generate({model:this.model,maxOutputTokens:this.config.maxTokensPerCompression,prompt:z}),$=kf(N);if($)$.discoveryTokens=K;return $}catch(N){if(k0(N)&&R<A){let $=2**R*1000;await Of($);continue}return null}return null}async compressBatch(f){let B=new Map;for(let E=0;E<f.length;E++){let K=f[E],J=await this.compress(K.toolName,K.toolOutput,K.sessionContext);if(B.set(K.callId,J),E<f.length-1)await Of(200)}return B}createFallbackObservation(f,B){let E=L0(B),K=X0[f]??"discovery";return{type:K,title:`${f} execution`,subtitle:B.substring(0,100).replace(/\n/g," "),facts:[],narrative:`Tool ${f} was executed. Output length: ${B.length} chars.`,concepts:[],filesRead:K==="discovery"?E:[],filesModified:K==="change"?E:[],discoveryTokens:M(B),importance:2}}async isAvailable(){if(!this.model)return!1;try{return await this._generate({model:this.model,maxOutputTokens:10,prompt:"ping"}),!0}catch{return!1}}}var X0={Read:"discovery",Write:"change",Edit:"change",Bash:"change",Glob:"discovery",Grep:"discovery"},C0=/(?:^|\s)((?:\.\/|\/|src\/|tests\/|lib\/)\S+\.\w+)/gm;function L0(f){let B=[];for(let E of f.matchAll(C0))B.push(E[1]);return[...new Set(B)]}function k0(f){if(typeof f!=="object"||f===null)return!1;let B=f,E=B.status;if(E===429||E===500||E===503)return!0;let K=B.error;if(typeof K==="object"&&K!==null&&K.type==="overloaded_error")return!0;return!1}function Of(f){return new Promise((B)=>setTimeout(B,f))}import{generateText as S0}from"ai";class c{model;config;_generate=S0;constructor(f){if(this.config=f,this.model=null,f.provider==="bedrock"||f.apiKey)try{this.model=C({provider:f.provider,model:f.model,apiKey:f.apiKey})}catch{}}async evaluate(f,B){if(!this.model||B.length===0)return null;let E=Gf(f,B),K=2;for(let J=0;J<=K;J++)try{if(this.config.provider==="google")await L(this.config.model,this.config.rateLimitingEnabled);let{text:z}=await this._generate({model:this.model,maxOutputTokens:512,prompt:E});return Df(z)}catch(z){if(D0(z)&&J<K){let A=2**J*1000;await M0(A);continue}return null}return null}}function D0(f){if(typeof f!=="object"||f===null)return!1;let B=f,E=B.status;if(E===429||E===500||E===503)return!0;let K=B.error;if(typeof K==="object"&&K!==null&&K.type==="overloaded_error")return!0;return!1}function M0(f){return new Promise((B)=>setTimeout(B,f))}import{generateText as _0}from"ai";class b{model;config;_generate=_0;constructor(f){if(this.config=f,this.model=null,f.provider==="bedrock"||f.apiKey)try{this.model=C({provider:f.provider,model:f.model,apiKey:f.apiKey})}catch{}}async extract(f){if(!this.model)return null;let B=Ff(f),E=2;for(let K=0;K<=E;K++)try{if(this.config.provider==="google")await L(this.config.model,this.config.rateLimitingEnabled);let{text:J}=await this._generate({model:this.model,maxOutputTokens:1024,prompt:B});return Mf(J)}catch(J){if(m0(J)&&K<E){let z=2**K*1000;await G0(z);continue}return null}return null}}function m0(f){if(typeof f!=="object"||f===null)return!1;let B=f,E=B.status;if(E===429||E===500||E===503)return!0;let K=B.error;if(typeof K==="object"&&K!==null&&K.type==="overloaded_error")return!0;return!1}function G0(f){return new Promise((B)=>setTimeout(B,f))}import{generateText as F0}from"ai";class n{model;config;_generate=F0;constructor(f){this.config=f,this.model=null;let B=f.provider!=="bedrock";if(f.compressionEnabled&&(!B||f.apiKey))try{this.model=C({provider:f.provider,model:f.model,apiKey:f.apiKey})}catch{}}async summarize(f,B){if(B.length===0)return null;if(!this.config.compressionEnabled||!this.model)return this.createFallbackSummary(B);let E=mf(B.map((K)=>({type:K.type,title:K.title,narrative:K.narrative})),f);try{if(this.config.provider==="google")await L(this.config.model,this.config.rateLimitingEnabled);let{text:K}=await this._generate({model:this.model,maxOutputTokens:this.config.maxTokensPerCompression,prompt:E}),J=Sf(K);if(!J)return this.createFallbackSummary(B);return J}catch{return this.createFallbackSummary(B)}}createFallbackSummary(f){let B=new Set,E=new Set,K=[];for(let R of f){for(let N of R.filesModified)B.add(N);for(let N of R.concepts)E.add(N);if(R.type==="decision")K.push(R.title)}let J=new Map;for(let R of f)J.set(R.type,(J.get(R.type)??0)+1);let z=Array.from(J.entries()).map(([R,N])=>`${N} ${R}${N>1?"s":""}`).join(", "),A=Array.from(E).slice(0,5).join(", ");return{summary:`Session with ${f.length} observations: ${z}. Files modified: ${B.size}. Key concepts: ${A}.`,keyDecisions:K.slice(0,5),filesModified:Array.from(B),concepts:Array.from(E)}}shouldSummarize(f){return f>=2}}import{existsSync as y0,readFileSync as P0}from"fs";var O0={dbPath:".open-mem/memory.db",provider:"google",apiKey:void 0,model:"gemini-2.5-flash-lite",maxTokensPerCompression:1024,compressionEnabled:!0,contextInjectionEnabled:!0,maxContextTokens:4000,batchSize:5,batchIntervalMs:30000,ignoredTools:[],minOutputLength:50,maxIndexEntries:20,sensitivePatterns:[],retentionDays:90,maxDatabaseSizeMb:500,logLevel:"warn",contextShowTokenCosts:!0,contextObservationTypes:"all",contextFullObservationCount:3,maxObservations:50,contextShowLastSummary:!0,rateLimitingEnabled:!0,folderContextEnabled:!0,folderContextMaxDepth:5,daemonEnabled:!1,dashboardEnabled:!1,dashboardPort:3737,platformOpenCodeEnabled:!0,platformClaudeCodeEnabled:!1,platformCursorEnabled:!1,mcpCompatibilityMode:"strict",mcpProtocolVersion:"2024-11-05",mcpSupportedProtocolVersions:["2024-11-05"],embeddingDimension:void 0,conflictResolutionEnabled:!1,conflictSimilarityBandLow:0.7,conflictSimilarityBandHigh:0.92,userMemoryEnabled:!1,userMemoryDbPath:"~/.config/open-mem/user-memory.db",userMemoryMaxContextTokens:1000,rerankingEnabled:!1,rerankingMaxCandidates:20,entityExtractionEnabled:!1};function j0(){let f={};if(process.env.OPEN_MEM_DB_PATH)f.dbPath=process.env.OPEN_MEM_DB_PATH;if(process.env.OPEN_MEM_PROVIDER)f.provider=process.env.OPEN_MEM_PROVIDER;if(process.env.OPEN_MEM_MODEL)f.model=process.env.OPEN_MEM_MODEL;if(process.env.OPEN_MEM_MAX_CONTEXT_TOKENS)f.maxContextTokens=Number.parseInt(process.env.OPEN_MEM_MAX_CONTEXT_TOKENS,10);if(process.env.OPEN_MEM_COMPRESSION==="false")f.compressionEnabled=!1;if(process.env.OPEN_MEM_CONTEXT_INJECTION==="false")f.contextInjectionEnabled=!1;if(process.env.OPEN_MEM_IGNORED_TOOLS)f.ignoredTools=process.env.OPEN_MEM_IGNORED_TOOLS.split(",").map((B)=>B.trim());if(process.env.OPEN_MEM_BATCH_SIZE)f.batchSize=Number.parseInt(process.env.OPEN_MEM_BATCH_SIZE,10);if(process.env.OPEN_MEM_RETENTION_DAYS)f.retentionDays=Number.parseInt(process.env.OPEN_MEM_RETENTION_DAYS,10);if(process.env.OPEN_MEM_LOG_LEVEL)f.logLevel=process.env.OPEN_MEM_LOG_LEVEL;if(process.env.OPEN_MEM_CONTEXT_SHOW_TOKEN_COSTS==="false")f.contextShowTokenCosts=!1;if(process.env.OPEN_MEM_CONTEXT_TYPES)f.contextObservationTypes=process.env.OPEN_MEM_CONTEXT_TYPES==="all"?"all":process.env.OPEN_MEM_CONTEXT_TYPES.split(",").map((B)=>B.trim());if(process.env.OPEN_MEM_CONTEXT_FULL_COUNT)f.contextFullObservationCount=Number.parseInt(process.env.OPEN_MEM_CONTEXT_FULL_COUNT,10);if(process.env.OPEN_MEM_MAX_OBSERVATIONS)f.maxObservations=Number.parseInt(process.env.OPEN_MEM_MAX_OBSERVATIONS,10);if(process.env.OPEN_MEM_CONTEXT_SHOW_LAST_SUMMARY==="false")f.contextShowLastSummary=!1;if(process.env.OPEN_MEM_RATE_LIMITING==="false")f.rateLimitingEnabled=!1;if(process.env.OPEN_MEM_FOLDER_CONTEXT==="false")f.folderContextEnabled=!1;if(process.env.OPEN_MEM_FOLDER_CONTEXT_MAX_DEPTH)f.folderContextMaxDepth=Number.parseInt(process.env.OPEN_MEM_FOLDER_CONTEXT_MAX_DEPTH,10);if(process.env.OPEN_MEM_DAEMON==="true")f.daemonEnabled=!0;if(process.env.OPEN_MEM_DASHBOARD==="true")f.dashboardEnabled=!0;if(process.env.OPEN_MEM_DASHBOARD_PORT)f.dashboardPort=Number.parseInt(process.env.OPEN_MEM_DASHBOARD_PORT,10);if(process.env.OPEN_MEM_PLATFORM_OPENCODE==="false")f.platformOpenCodeEnabled=!1;if(process.env.OPEN_MEM_PLATFORM_CLAUDE_CODE==="true")f.platformClaudeCodeEnabled=!0;if(process.env.OPEN_MEM_PLATFORM_CURSOR==="true")f.platformCursorEnabled=!0;if(process.env.OPEN_MEM_MCP_COMPAT_MODE)f.mcpCompatibilityMode=process.env.OPEN_MEM_MCP_COMPAT_MODE;if(process.env.OPEN_MEM_MCP_PROTOCOL_VERSION)f.mcpProtocolVersion=process.env.OPEN_MEM_MCP_PROTOCOL_VERSION;if(process.env.OPEN_MEM_MCP_SUPPORTED_PROTOCOLS)f.mcpSupportedProtocolVersions=process.env.OPEN_MEM_MCP_SUPPORTED_PROTOCOLS.split(",").map((B)=>B.trim()).filter(Boolean);if(process.env.OPEN_MEM_EMBEDDING_DIMENSION)f.embeddingDimension=Number.parseInt(process.env.OPEN_MEM_EMBEDDING_DIMENSION,10);if(process.env.OPEN_MEM_CONFLICT_RESOLUTION==="true")f.conflictResolutionEnabled=!0;if(process.env.OPEN_MEM_CONFLICT_BAND_LOW){let B=Number.parseFloat(process.env.OPEN_MEM_CONFLICT_BAND_LOW);if(!Number.isNaN(B))f.conflictSimilarityBandLow=B}if(process.env.OPEN_MEM_CONFLICT_BAND_HIGH){let B=Number.parseFloat(process.env.OPEN_MEM_CONFLICT_BAND_HIGH);if(!Number.isNaN(B))f.conflictSimilarityBandHigh=B}if(process.env.OPEN_MEM_USER_MEMORY==="true")f.userMemoryEnabled=!0;if(process.env.OPEN_MEM_USER_MEMORY_DB_PATH)f.userMemoryDbPath=process.env.OPEN_MEM_USER_MEMORY_DB_PATH;if(process.env.OPEN_MEM_USER_MEMORY_MAX_TOKENS)f.userMemoryMaxContextTokens=Number.parseInt(process.env.OPEN_MEM_USER_MEMORY_MAX_TOKENS,10);if(process.env.OPEN_MEM_RERANKING==="true")f.rerankingEnabled=!0;if(process.env.OPEN_MEM_RERANKING_MAX_CANDIDATES)f.rerankingMaxCandidates=Number.parseInt(process.env.OPEN_MEM_RERANKING_MAX_CANDIDATES,10);if(process.env.OPEN_MEM_ENTITY_EXTRACTION==="true")f.entityExtractionEnabled=!0;return f}function q0(f){let B=`${f}/.open-mem/config.json`;if(!y0(B))return{};try{let E=P0(B,"utf-8"),K=JSON.parse(E);if(!K||typeof K!=="object"||Array.isArray(K))return{};return K}catch{return{}}}function h0(f){switch(f){case"google":return 768;case"openai":return 1536;case"bedrock":return 1024;case"anthropic":return 0;default:return 768}}function jf(f,B){let E=q0(f),K=j0(),J={...O0,...E,...K,...B};if(!J.dbPath.startsWith("/"))J.dbPath=`${f}/${J.dbPath}`;if(!process.env.OPEN_MEM_PROVIDER&&!B?.provider){if(process.env.GOOGLE_GENERATIVE_AI_API_KEY||process.env.GEMINI_API_KEY)J.provider="google";else if(process.env.ANTHROPIC_API_KEY)J.provider="anthropic";else if(process.env.AWS_BEARER_TOKEN_BEDROCK||process.env.AWS_ACCESS_KEY_ID||process.env.AWS_PROFILE)J.provider="bedrock"}if(!J.apiKey)switch(J.provider){case"google":J.apiKey=process.env.GOOGLE_GENERATIVE_AI_API_KEY||process.env.GEMINI_API_KEY;break;case"anthropic":J.apiKey=process.env.ANTHROPIC_API_KEY;break;case"openai":J.apiKey=process.env.OPENAI_API_KEY;break;case"bedrock":break}if(J.embeddingDimension===void 0)J.embeddingDimension=h0(J.provider);return J}import{Database as qf}from"bun:sqlite";import{existsSync as d,mkdirSync as T0,unlinkSync as hf}from"fs";import*as Tf from"sqlite-vec";class I{db;dbPath;_hasVectorExtension=!1;static enableExtensionSupport(){let f=["/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib","/usr/local/opt/sqlite/lib/libsqlite3.dylib"];for(let B of f)try{if(d(B))return qf.setCustomSQLite(B),!0}catch{return!1}return!1}constructor(f){this.dbPath=f,this.db=this.open(f),this.configure()}open(f){let B=f.lastIndexOf("/");if(B>0){let E=f.substring(0,B);T0(E,{recursive:!0})}return new qf(f,{create:!0})}configure(){try{this.applyPragmas(),this.loadExtensions()}catch(f){console.warn("[open-mem] Database configure failed, attempting recovery by removing WAL/SHM files:",f.message);try{this.db.close()}catch{}this.deleteSidecarFiles();try{this.db=this.open(this.dbPath),this.applyPragmas(),this.loadExtensions(),console.warn("[open-mem] Recovery successful after removing WAL/SHM files");return}catch(B){console.warn("[open-mem] WAL/SHM cleanup insufficient, recreating database from scratch:",B.message);try{this.db.close()}catch{}this.deleteDatabaseFiles();try{this.db=this.open(this.dbPath),this.applyPragmas(),this.loadExtensions(),console.warn("[open-mem] Recovery successful after full database recreation");return}catch(E){throw console.warn("[open-mem] All recovery attempts failed, filesystem may be broken:",E.message),f}}}}applyPragmas(){this.db.exec("PRAGMA journal_mode = WAL"),this.db.exec("PRAGMA synchronous = NORMAL"),this.db.exec("PRAGMA foreign_keys = ON"),this.db.exec("PRAGMA busy_timeout = 5000")}loadExtensions(){try{Tf.load(this.db),this._hasVectorExtension=!0}catch{this._hasVectorExtension=!1}}get hasVectorExtension(){return this._hasVectorExtension}deleteSidecarFiles(){for(let f of["-wal","-shm"]){let B=this.dbPath+f;try{if(d(B))hf(B)}catch{}}}deleteDatabaseFiles(){this.deleteSidecarFiles();try{if(d(this.dbPath))hf(this.dbPath)}catch{}}ensureMigrationTable(){this.db.exec(`
|
|
159
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
160
|
+
version INTEGER PRIMARY KEY,
|
|
161
|
+
name TEXT NOT NULL,
|
|
162
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
163
|
+
)
|
|
164
|
+
`)}migrate(f){this.ensureMigrationTable();let B=this.db.query("SELECT version FROM _migrations ORDER BY version").all(),E=new Set(B.map((J)=>J.version)),K=f.filter((J)=>!E.has(J.version)).sort((J,z)=>J.version-z.version);for(let J of K)this.db.transaction(()=>{this.db.exec(J.up),this.db.query("INSERT INTO _migrations (version, name) VALUES ($version, $name)").run({$version:J.version,$name:J.name})})()}run(f,B){let E=this.db.query(f);if(B)E.run(...B);else E.run()}get(f,B){let E=this.db.query(f);return B?E.get(...B):E.get()}all(f,B){let E=this.db.query(f);return B?E.all(...B):E.all()}exec(f){this.db.exec(f)}transaction(f){return this.db.transaction(f)()}close(){this.db.close()}get isOpen(){try{return this.db.query("SELECT 1").get(),!0}catch{return!1}}get raw(){return this.db}}function xf(f){return new I(f)}import{randomUUID as If}from"crypto";class r{db;constructor(f){this.db=f}upsertEntity(f,B){let E=If(),K=new Date().toISOString();this.db.run(`INSERT INTO entities (id, name, entity_type, first_seen_at, last_seen_at, mention_count)
|
|
165
|
+
VALUES (?, ?, ?, ?, ?, 1)
|
|
166
|
+
ON CONFLICT(name, entity_type) DO UPDATE SET
|
|
167
|
+
mention_count = mention_count + 1,
|
|
168
|
+
last_seen_at = ?`,[E,f,B,K,K,K]);let J=this.db.get("SELECT * FROM entities WHERE name = ? AND entity_type = ?",[f,B]);if(!J)throw Error(`Failed to upsert entity: ${f} (${B})`);return this.mapEntityRow(J)}createRelation(f,B,E,K){let J=If(),z=new Date().toISOString();try{this.db.run(`INSERT OR IGNORE INTO entity_relations
|
|
169
|
+
(id, source_entity_id, target_entity_id, relationship, observation_id, created_at)
|
|
170
|
+
VALUES (?, ?, ?, ?, ?, ?)`,[J,f,B,E,K,z])}catch{return null}let A=this.db.get(`SELECT * FROM entity_relations
|
|
171
|
+
WHERE source_entity_id = ? AND target_entity_id = ? AND relationship = ?`,[f,B,E]);return A?this.mapRelationRow(A):null}linkObservation(f,B){this.db.run("INSERT OR IGNORE INTO entity_observations (entity_id, observation_id) VALUES (?, ?)",[f,B])}findByName(f){try{return this.db.all(`SELECT e.*
|
|
172
|
+
FROM entities e
|
|
173
|
+
JOIN entities_fts fts ON e._rowid = fts.rowid
|
|
174
|
+
WHERE entities_fts MATCH ?
|
|
175
|
+
ORDER BY rank`,[f]).map((E)=>this.mapEntityRow(E))}catch{return[]}}getRelationsFor(f){return this.db.all(`SELECT * FROM entity_relations
|
|
176
|
+
WHERE source_entity_id = ? OR target_entity_id = ?`,[f,f]).map((E)=>this.mapRelationRow(E))}traverseRelations(f,B=1){let E=Math.min(B,2),K=100,J=new Set,z=[{id:f,currentDepth:0}];J.add(f);while(z.length>0){if(J.size>=100)break;let A=z.shift();if(!A)continue;if(A.currentDepth>=E)continue;let R=this.getRelationsFor(A.id);for(let N of R){let $=N.sourceEntityId===A.id?N.targetEntityId:N.sourceEntityId;if(!J.has($))J.add($),z.push({id:$,currentDepth:A.currentDepth+1})}}return J}getObservationsForEntity(f){return this.db.all("SELECT observation_id FROM entity_observations WHERE entity_id = ?",[f]).map((E)=>E.observation_id)}getById(f){let B=this.db.get("SELECT * FROM entities WHERE id = ?",[f]);return B?this.mapEntityRow(B):null}mapEntityRow(f){return{id:f.id,name:f.name,entityType:f.entity_type,firstSeenAt:f.first_seen_at,lastSeenAt:f.last_seen_at,mentionCount:f.mention_count}}mapRelationRow(f){return{id:f.id,sourceEntityId:f.source_entity_id,targetEntityId:f.target_entity_id,relationship:f.relationship,observationId:f.observation_id,createdAt:f.created_at}}}import{randomUUID as I0}from"crypto";import{embed as x0}from"ai";async function i(f,B){try{let{embedding:E}=await x0({model:f,value:B});return E}catch{return null}}function gf(f,B){if(f.length!==B.length||f.length===0)return 0;let E=0,K=0,J=0;for(let A=0;A<f.length;A++)E+=f[A]*B[A],K+=f[A]*f[A],J+=B[A]*B[A];let z=Math.sqrt(K)*Math.sqrt(J);if(z===0)return 0;return E/z}function s(f){let B=[f.title,f.narrative];if(f.concepts.length>0)B.push(f.concepts.join(", "));return B.join(`
|
|
177
|
+
`)}function g0(f){return f.replace(/[%_\\]/g,"\\$&")}class o{db;constructor(f){this.db=f}create(f){let B=I0(),E=new Date().toISOString(),K=f.discoveryTokens??0,J=f.importance??3,z=f.scope??"project";return this.db.run(`INSERT INTO observations
|
|
178
|
+
(id, session_id, scope, type, title, subtitle, facts, narrative,
|
|
179
|
+
concepts, files_read, files_modified, raw_tool_output,
|
|
180
|
+
tool_name, created_at, token_count, discovery_tokens, importance, revision_of, deleted_at)
|
|
181
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[B,f.sessionId,z,f.type,f.title,f.subtitle,JSON.stringify(f.facts),f.narrative,JSON.stringify(f.concepts),JSON.stringify(f.filesRead),JSON.stringify(f.filesModified),f.rawToolOutput,f.toolName,E,f.tokenCount,K,J,null,null]),{...f,id:B,scope:z,createdAt:E,discoveryTokens:K,importance:J,revisionOf:null,deletedAt:null,supersededBy:null,supersededAt:null}}importObservation(f){this.db.run(`INSERT INTO observations
|
|
182
|
+
(id, session_id, scope, type, title, subtitle, facts, narrative,
|
|
183
|
+
concepts, files_read, files_modified, raw_tool_output,
|
|
184
|
+
tool_name, created_at, token_count, discovery_tokens, importance, revision_of, deleted_at)
|
|
185
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[f.id,f.sessionId,f.scope??"project",f.type,f.title,f.subtitle,JSON.stringify(f.facts),f.narrative,JSON.stringify(f.concepts),JSON.stringify(f.filesRead),JSON.stringify(f.filesModified),f.rawToolOutput,f.toolName,f.createdAt,f.tokenCount,f.discoveryTokens??0,f.importance??3,f.revisionOf??null,f.deletedAt??null])}getById(f){let B=this.db.get("SELECT * FROM observations WHERE id = ? AND superseded_by IS NULL AND deleted_at IS NULL",[f]);return B?this.mapRow(B):null}getByIdIncludingArchived(f){let B=this.db.get("SELECT * FROM observations WHERE id = ?",[f]);return B?this.mapRow(B):null}getBySession(f){return this.db.all("SELECT * FROM observations WHERE session_id = ? AND superseded_by IS NULL AND deleted_at IS NULL ORDER BY created_at ASC",[f]).map((B)=>this.mapRow(B))}getCount(f){if(f)return this.db.get("SELECT COUNT(*) as count FROM observations WHERE session_id = ?",[f])?.count??0;return this.db.get("SELECT COUNT(*) as count FROM observations")?.count??0}getIndex(f,B=20){return this.db.all(`SELECT o.id, o.session_id, o.type, o.title, o.token_count, o.discovery_tokens, o.created_at, o.importance
|
|
186
|
+
FROM observations o
|
|
187
|
+
JOIN sessions s ON o.session_id = s.id
|
|
188
|
+
WHERE s.project_path = ? AND o.superseded_by IS NULL AND o.deleted_at IS NULL
|
|
189
|
+
ORDER BY o.created_at DESC
|
|
190
|
+
LIMIT ?`,[f,B]).map((E)=>({id:E.id,sessionId:E.session_id,type:E.type,title:E.title,tokenCount:E.token_count,discoveryTokens:E.discovery_tokens??0,createdAt:E.created_at,importance:E.importance??3}))}listByProject(f,B={}){let{limit:E=50,offset:K=0,type:J,state:z,sessionId:A}=B,R=`SELECT o.*
|
|
191
|
+
FROM observations o
|
|
192
|
+
JOIN sessions s ON o.session_id = s.id
|
|
193
|
+
WHERE s.project_path = ?`,N=[f];if(A)R+=" AND o.session_id = ?",N.push(A);if(J)R+=" AND o.type = ?",N.push(J);if(z==="current")R+=" AND o.superseded_by IS NULL AND o.deleted_at IS NULL";else if(z==="superseded")R+=" AND o.superseded_by IS NOT NULL AND o.deleted_at IS NULL";else if(z==="tombstoned")R+=" AND o.deleted_at IS NOT NULL";else R+=" AND o.superseded_by IS NULL AND o.deleted_at IS NULL";return R+=" ORDER BY o.created_at DESC LIMIT ? OFFSET ?",N.push(E,K),this.db.all(R,N).map(($)=>this.mapRow($))}search(f){let B=!!f.projectPath,E=`
|
|
194
|
+
SELECT o.*, rank
|
|
195
|
+
FROM observations o
|
|
196
|
+
JOIN observations_fts fts ON o._rowid = fts.rowid
|
|
197
|
+
${B?"JOIN sessions s ON o.session_id = s.id":""}
|
|
198
|
+
WHERE observations_fts MATCH ? AND o.superseded_by IS NULL AND o.deleted_at IS NULL
|
|
199
|
+
`,K=[f.query];if(B&&f.projectPath)E+=" AND s.project_path = ?",K.push(f.projectPath);if(f.sessionId)E+=" AND o.session_id = ?",K.push(f.sessionId);if(f.type)E+=" AND o.type = ?",K.push(f.type);if(f.importanceMin!==void 0)E+=" AND o.importance >= ?",K.push(f.importanceMin);if(f.importanceMax!==void 0)E+=" AND o.importance <= ?",K.push(f.importanceMax);if(f.createdAfter)E+=" AND o.created_at >= ?",K.push(f.createdAfter);if(f.createdBefore)E+=" AND o.created_at <= ?",K.push(f.createdBefore);if(f.concepts&&f.concepts.length>0){let J=f.concepts.map(()=>"EXISTS (SELECT 1 FROM json_each(o.concepts) WHERE LOWER(value) = LOWER(?))");E+=` AND (${J.join(" OR ")})`;for(let z of f.concepts)K.push(z)}if(f.files&&f.files.length>0){let J=f.files.map(()=>`(EXISTS (SELECT 1 FROM json_each(o.files_read) WHERE LOWER(value) LIKE LOWER(?) ESCAPE '\\')
|
|
200
|
+
OR EXISTS (SELECT 1 FROM json_each(o.files_modified) WHERE LOWER(value) LIKE LOWER(?) ESCAPE '\\'))`);E+=` AND (${J.join(" OR ")})`;for(let z of f.files){let A=`%${g0(z)}%`;K.push(A,A)}}return E+=" ORDER BY rank LIMIT ? OFFSET ?",K.push(f.limit??10),K.push(f.offset??0),this.db.all(E,K).map((J)=>({observation:this.mapRow(J),rank:J.rank,snippet:J.title}))}searchByConcept(f,B=10,E){let K=!!E,J=`SELECT o.*
|
|
201
|
+
FROM observations o
|
|
202
|
+
JOIN observations_fts fts ON o._rowid = fts.rowid
|
|
203
|
+
${K?"JOIN sessions s ON o.session_id = s.id":""}
|
|
204
|
+
WHERE observations_fts MATCH ?
|
|
205
|
+
AND o.superseded_by IS NULL AND o.deleted_at IS NULL
|
|
206
|
+
${K?"AND s.project_path = ?":""}
|
|
207
|
+
ORDER BY rank
|
|
208
|
+
LIMIT ?`,z=[`concepts:${f}`];if(K&&E)z.push(E);return z.push(B),this.db.all(J,z).map((A)=>this.mapRow(A))}searchByFile(f,B=10,E){let K=!!E,J=`SELECT o.*
|
|
209
|
+
FROM observations o
|
|
210
|
+
JOIN observations_fts fts ON o._rowid = fts.rowid
|
|
211
|
+
${K?"JOIN sessions s ON o.session_id = s.id":""}
|
|
212
|
+
WHERE observations_fts MATCH ?
|
|
213
|
+
AND o.superseded_by IS NULL AND o.deleted_at IS NULL
|
|
214
|
+
${K?"AND s.project_path = ?":""}
|
|
215
|
+
ORDER BY rank
|
|
216
|
+
LIMIT ?`,z=[`files_read:"${f.replace(/"/g,'""')}" OR files_modified:"${f.replace(/"/g,'""')}"`];if(K&&E)z.push(E);return z.push(B),this.db.all(J,z).map((A)=>this.mapRow(A))}setEmbedding(f,B){this.db.run("UPDATE observations SET embedding = ? WHERE id = ?",[JSON.stringify(B),f])}getWithEmbeddings(f,B){return this.db.all(`SELECT o.id, o.embedding, o.title
|
|
217
|
+
FROM observations o
|
|
218
|
+
JOIN sessions s ON o.session_id = s.id
|
|
219
|
+
WHERE s.project_path = ? AND o.embedding IS NOT NULL AND o.superseded_by IS NULL AND o.deleted_at IS NULL
|
|
220
|
+
ORDER BY o.created_at DESC
|
|
221
|
+
LIMIT ?`,[f,B]).map((E)=>{try{return{id:E.id,embedding:JSON.parse(E.embedding),title:E.title}}catch{return null}}).filter((E)=>E!==null)}findSimilar(f,B,E,K){let J=this.db.all(`SELECT id, embedding FROM observations
|
|
222
|
+
WHERE embedding IS NOT NULL AND type = ? AND superseded_by IS NULL AND deleted_at IS NULL
|
|
223
|
+
ORDER BY created_at DESC
|
|
224
|
+
LIMIT 200`,[B]),z=[];for(let A of J)try{let R=JSON.parse(A.embedding);if(!Array.isArray(R)||R.length!==f.length)continue;let N=gf(f,R);if(N>=E)z.push({id:A.id,similarity:N})}catch{}return z.sort((A,R)=>R.similarity-A.similarity).slice(0,K)}insertVecEmbedding(f,B){let E=new Float32Array(B);this.db.run("BEGIN");try{this.db.run("DELETE FROM observation_embeddings WHERE observation_id = ?",[f]),this.db.run("INSERT INTO observation_embeddings (observation_id, embedding) VALUES (?, ?)",[f,E]),this.db.run("COMMIT")}catch(K){throw this.db.run("ROLLBACK"),K}}migrateExistingEmbeddings(f){let B=this.db.all("SELECT id, embedding FROM observations WHERE embedding IS NOT NULL"),E=0,K=0;for(let J of B)try{let z=JSON.parse(J.embedding);if(!Array.isArray(z)||z.length!==f){K++;continue}this.insertVecEmbedding(J.id,z),E++}catch{K++}return{migrated:E,skipped:K}}getVecEmbeddingMatches(f,B){try{let E=new Float32Array(f);return this.db.all(`SELECT observation_id, distance
|
|
225
|
+
FROM observation_embeddings
|
|
226
|
+
WHERE embedding MATCH ? AND k = ?`,[E,B]).map((K)=>({observationId:K.observation_id,distance:K.distance}))}catch{return[]}}searchVecSubset(f,B,E){if(B.length===0)return[];try{let K=new Float32Array(f),J=Math.max(E*5,B.length),z=this.db.all(`SELECT observation_id, distance
|
|
227
|
+
FROM observation_embeddings
|
|
228
|
+
WHERE embedding MATCH ? AND k = ?`,[K,J]),A=new Set(B);return z.filter((R)=>A.has(R.observation_id)).slice(0,E).map((R)=>({observationId:R.observation_id,distance:R.distance}))}catch{return[]}}update(f,B){let E=this.getById(f);if(!E)return null;if(Object.keys(B).length===0)return E;let K=this.create({sessionId:E.sessionId,scope:E.scope??"project",type:B.type??E.type,title:B.title??E.title,subtitle:B.subtitle??E.subtitle,facts:B.facts??E.facts,narrative:B.narrative??E.narrative,concepts:B.concepts??E.concepts,filesRead:B.filesRead??E.filesRead,filesModified:B.filesModified??E.filesModified,rawToolOutput:E.rawToolOutput,toolName:"memory.revise",tokenCount:E.tokenCount,discoveryTokens:E.discoveryTokens,importance:B.importance??E.importance});return this.db.run("UPDATE observations SET revision_of = ? WHERE id = ?",[f,K.id]),this.supersede(f,K.id),this.getById(K.id)}supersede(f,B){let E=new Date().toISOString();this.db.run("UPDATE observations SET superseded_by = ?, superseded_at = ? WHERE id = ?",[B,E,f])}delete(f){if(this.db.all("SELECT id FROM observations WHERE id = ?",[f]).length===0)return!1;let E=new Date().toISOString();return this.db.run("UPDATE observations SET deleted_at = ? WHERE id = ?",[E,f]),this.deleteEmbeddingsForObservations([f]),!0}getLineage(f){let B=this.getByIdIncludingArchived(f);if(!B)return[];let E=new Set([B.id]),K=[B];while(K[0].revisionOf){let J=this.getByIdIncludingArchived(K[0].revisionOf);if(!J||E.has(J.id))break;K.unshift(J),E.add(J.id)}while(K[K.length-1].supersededBy){let J=K[K.length-1].supersededBy;if(!J)break;let z=this.getByIdIncludingArchived(J);if(!z||E.has(z.id))break;K.push(z),E.add(z.id)}return K}deleteOlderThan(f){return this.db.all(`DELETE FROM observations
|
|
229
|
+
WHERE (created_at < datetime('now', '-' || ? || ' days') OR deleted_at IS NOT NULL)
|
|
230
|
+
AND session_id NOT IN (SELECT id FROM sessions WHERE status != 'completed')
|
|
231
|
+
RETURNING id`,[f]).length}deleteEmbeddingsForObservations(f){if(f.length===0)return;let B=f.map(()=>"?").join(",");try{this.db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${B})`,f)}catch{}this.db.run(`UPDATE observations SET embedding = NULL WHERE id IN (${B})`,f)}mapRow(f){return{id:f.id,sessionId:f.session_id,scope:f.scope??"project",type:f.type,title:f.title,subtitle:f.subtitle,facts:JSON.parse(f.facts),narrative:f.narrative,concepts:JSON.parse(f.concepts),filesRead:JSON.parse(f.files_read),filesModified:JSON.parse(f.files_modified),rawToolOutput:f.raw_tool_output,toolName:f.tool_name,createdAt:f.created_at,tokenCount:f.token_count,discoveryTokens:f.discovery_tokens??0,importance:f.importance??3,revisionOf:f.revision_of??null,deletedAt:f.deleted_at??null,supersededBy:f.superseded_by??null,supersededAt:f.superseded_at??null}}}import{randomUUID as p0}from"crypto";class t{db;constructor(f){this.db=f}create(f){let B=p0(),E=new Date().toISOString();return this.db.run(`INSERT INTO pending_messages
|
|
232
|
+
(id, session_id, tool_name, tool_output, call_id, created_at)
|
|
233
|
+
VALUES (?, ?, ?, ?, ?, ?)`,[B,f.sessionId,f.toolName,f.toolOutput,f.callId,E]),{...f,id:B,createdAt:E,status:"pending",retryCount:0,error:null}}getPending(f=10){return this.db.all("SELECT * FROM pending_messages WHERE status = 'pending' ORDER BY created_at ASC LIMIT ?",[f]).map((B)=>this.mapRow(B))}getByStatus(f){return this.db.all("SELECT * FROM pending_messages WHERE status = ? ORDER BY created_at ASC",[f]).map((B)=>this.mapRow(B))}markProcessing(f){this.db.run("UPDATE pending_messages SET status = 'processing' WHERE id = ?",[f])}markCompleted(f){this.db.run("UPDATE pending_messages SET status = 'completed' WHERE id = ?",[f])}markFailed(f,B){this.db.run("UPDATE pending_messages SET status = 'failed', error = ?, retry_count = retry_count + 1 WHERE id = ?",[B,f])}resetStale(f=5){return this.db.all(`UPDATE pending_messages SET status = 'pending'
|
|
234
|
+
WHERE status = 'processing'
|
|
235
|
+
AND created_at < datetime('now', ? || ' minutes')
|
|
236
|
+
RETURNING id`,[`-${f}`]).length}deleteCompletedOlderThan(f){return this.db.all(`DELETE FROM pending_messages
|
|
237
|
+
WHERE status = 'completed'
|
|
238
|
+
AND created_at < datetime('now', '-' || ? || ' days')
|
|
239
|
+
RETURNING id`,[f]).length}mapRow(f){return{id:f.id,sessionId:f.session_id,toolName:f.tool_name,toolOutput:f.tool_output,callId:f.call_id,createdAt:f.created_at,status:f.status,retryCount:f.retry_count,error:f.error??null}}}var w0=[{version:1,name:"create-schema",up:`
|
|
240
|
+
-- Sessions table
|
|
241
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
242
|
+
_rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
243
|
+
id TEXT UNIQUE NOT NULL,
|
|
244
|
+
project_path TEXT NOT NULL,
|
|
245
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
246
|
+
ended_at TEXT,
|
|
247
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
248
|
+
CHECK (status IN ('active', 'idle', 'completed')),
|
|
249
|
+
observation_count INTEGER NOT NULL DEFAULT 0,
|
|
250
|
+
summary_id TEXT
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
254
|
+
ON sessions(project_path);
|
|
255
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_status
|
|
256
|
+
ON sessions(status);
|
|
257
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_started
|
|
258
|
+
ON sessions(started_at DESC);
|
|
259
|
+
|
|
260
|
+
-- Observations table
|
|
261
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
262
|
+
_rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
263
|
+
id TEXT UNIQUE NOT NULL,
|
|
264
|
+
session_id TEXT NOT NULL,
|
|
265
|
+
type TEXT NOT NULL
|
|
266
|
+
CHECK (type IN ('decision','bugfix','feature','refactor','discovery','change')),
|
|
267
|
+
title TEXT NOT NULL,
|
|
268
|
+
subtitle TEXT NOT NULL DEFAULT '',
|
|
269
|
+
facts TEXT NOT NULL DEFAULT '[]',
|
|
270
|
+
narrative TEXT NOT NULL DEFAULT '',
|
|
271
|
+
concepts TEXT NOT NULL DEFAULT '[]',
|
|
272
|
+
files_read TEXT NOT NULL DEFAULT '[]',
|
|
273
|
+
files_modified TEXT NOT NULL DEFAULT '[]',
|
|
274
|
+
raw_tool_output TEXT NOT NULL,
|
|
275
|
+
tool_name TEXT NOT NULL,
|
|
276
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
277
|
+
token_count INTEGER NOT NULL DEFAULT 0,
|
|
278
|
+
discovery_tokens INTEGER NOT NULL DEFAULT 0,
|
|
279
|
+
embedding TEXT,
|
|
280
|
+
importance INTEGER NOT NULL DEFAULT 3,
|
|
281
|
+
superseded_by TEXT,
|
|
282
|
+
superseded_at TEXT,
|
|
283
|
+
scope TEXT NOT NULL DEFAULT 'project'
|
|
284
|
+
CHECK (scope IN ('project','user')),
|
|
285
|
+
revision_of TEXT,
|
|
286
|
+
deleted_at TEXT,
|
|
287
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
CREATE INDEX IF NOT EXISTS idx_observations_session
|
|
291
|
+
ON observations(session_id);
|
|
292
|
+
CREATE INDEX IF NOT EXISTS idx_observations_type
|
|
293
|
+
ON observations(type);
|
|
294
|
+
CREATE INDEX IF NOT EXISTS idx_observations_created
|
|
295
|
+
ON observations(created_at DESC);
|
|
296
|
+
CREATE INDEX IF NOT EXISTS idx_observations_superseded
|
|
297
|
+
ON observations(superseded_by);
|
|
298
|
+
CREATE INDEX IF NOT EXISTS idx_observations_scope
|
|
299
|
+
ON observations(scope);
|
|
300
|
+
CREATE INDEX IF NOT EXISTS idx_observations_revision_of
|
|
301
|
+
ON observations(revision_of);
|
|
302
|
+
CREATE INDEX IF NOT EXISTS idx_observations_deleted_at
|
|
303
|
+
ON observations(deleted_at);
|
|
304
|
+
|
|
305
|
+
-- Clean up superseded_by when the superseding observation is deleted
|
|
306
|
+
CREATE TRIGGER IF NOT EXISTS trg_clear_superseded_by
|
|
307
|
+
AFTER DELETE ON observations
|
|
308
|
+
BEGIN
|
|
309
|
+
UPDATE observations
|
|
310
|
+
SET superseded_by = NULL, superseded_at = NULL
|
|
311
|
+
WHERE superseded_by = OLD.id;
|
|
312
|
+
END;
|
|
313
|
+
|
|
314
|
+
-- Session summaries table
|
|
315
|
+
CREATE TABLE IF NOT EXISTS session_summaries (
|
|
316
|
+
_rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
317
|
+
id TEXT UNIQUE NOT NULL,
|
|
318
|
+
session_id TEXT NOT NULL UNIQUE,
|
|
319
|
+
summary TEXT NOT NULL,
|
|
320
|
+
key_decisions TEXT NOT NULL DEFAULT '[]',
|
|
321
|
+
files_modified TEXT NOT NULL DEFAULT '[]',
|
|
322
|
+
concepts TEXT NOT NULL DEFAULT '[]',
|
|
323
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
324
|
+
token_count INTEGER NOT NULL DEFAULT 0,
|
|
325
|
+
request TEXT NOT NULL DEFAULT '',
|
|
326
|
+
investigated TEXT NOT NULL DEFAULT '',
|
|
327
|
+
learned TEXT NOT NULL DEFAULT '',
|
|
328
|
+
completed TEXT NOT NULL DEFAULT '',
|
|
329
|
+
next_steps TEXT NOT NULL DEFAULT '',
|
|
330
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
-- Pending messages (queue persistence)
|
|
334
|
+
CREATE TABLE IF NOT EXISTS pending_messages (
|
|
335
|
+
_rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
336
|
+
id TEXT UNIQUE NOT NULL,
|
|
337
|
+
session_id TEXT NOT NULL,
|
|
338
|
+
tool_name TEXT NOT NULL,
|
|
339
|
+
tool_output TEXT NOT NULL,
|
|
340
|
+
call_id TEXT NOT NULL,
|
|
341
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
342
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
343
|
+
CHECK (status IN ('pending','processing','completed','failed')),
|
|
344
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
345
|
+
error TEXT,
|
|
346
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
CREATE INDEX IF NOT EXISTS idx_pending_status
|
|
350
|
+
ON pending_messages(status);
|
|
351
|
+
CREATE INDEX IF NOT EXISTS idx_pending_session
|
|
352
|
+
ON pending_messages(session_id);
|
|
353
|
+
|
|
354
|
+
-- Embedding metadata
|
|
355
|
+
CREATE TABLE IF NOT EXISTS _embedding_meta (
|
|
356
|
+
key TEXT PRIMARY KEY,
|
|
357
|
+
value TEXT NOT NULL
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
-- Config audit events
|
|
361
|
+
CREATE TABLE IF NOT EXISTS config_audit_events (
|
|
362
|
+
_rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
363
|
+
id TEXT UNIQUE NOT NULL,
|
|
364
|
+
timestamp TEXT NOT NULL,
|
|
365
|
+
patch TEXT NOT NULL,
|
|
366
|
+
previous_values TEXT NOT NULL,
|
|
367
|
+
source TEXT NOT NULL
|
|
368
|
+
CHECK (source IN ('api','mode','rollback','rollback-failed'))
|
|
369
|
+
);
|
|
370
|
+
CREATE INDEX IF NOT EXISTS idx_config_audit_timestamp
|
|
371
|
+
ON config_audit_events(timestamp DESC);
|
|
372
|
+
|
|
373
|
+
-- Maintenance history
|
|
374
|
+
CREATE TABLE IF NOT EXISTS maintenance_history (
|
|
375
|
+
_rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
376
|
+
id TEXT UNIQUE NOT NULL,
|
|
377
|
+
timestamp TEXT NOT NULL,
|
|
378
|
+
action TEXT NOT NULL,
|
|
379
|
+
dry_run INTEGER NOT NULL DEFAULT 0,
|
|
380
|
+
result TEXT NOT NULL
|
|
381
|
+
);
|
|
382
|
+
CREATE INDEX IF NOT EXISTS idx_maintenance_history_timestamp
|
|
383
|
+
ON maintenance_history(timestamp DESC);
|
|
384
|
+
|
|
385
|
+
-- Entities table
|
|
386
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
387
|
+
_rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
388
|
+
id TEXT UNIQUE NOT NULL,
|
|
389
|
+
name TEXT NOT NULL,
|
|
390
|
+
entity_type TEXT NOT NULL
|
|
391
|
+
CHECK (entity_type IN ('technology','library','pattern','concept','file','person','project','other')),
|
|
392
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
393
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
394
|
+
mention_count INTEGER NOT NULL DEFAULT 1,
|
|
395
|
+
UNIQUE(name, entity_type)
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
399
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(entity_type);
|
|
400
|
+
|
|
401
|
+
-- Entity relations table
|
|
402
|
+
CREATE TABLE IF NOT EXISTS entity_relations (
|
|
403
|
+
_rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
404
|
+
id TEXT UNIQUE NOT NULL,
|
|
405
|
+
source_entity_id TEXT NOT NULL,
|
|
406
|
+
target_entity_id TEXT NOT NULL,
|
|
407
|
+
relationship TEXT NOT NULL
|
|
408
|
+
CHECK (relationship IN ('uses','depends_on','implements','extends','related_to','replaces','configures')),
|
|
409
|
+
observation_id TEXT NOT NULL,
|
|
410
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
411
|
+
UNIQUE(source_entity_id, target_entity_id, relationship),
|
|
412
|
+
FOREIGN KEY (source_entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
|
413
|
+
FOREIGN KEY (target_entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
|
414
|
+
FOREIGN KEY (observation_id) REFERENCES observations(id) ON DELETE CASCADE
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
CREATE INDEX IF NOT EXISTS idx_entity_relations_source ON entity_relations(source_entity_id);
|
|
418
|
+
CREATE INDEX IF NOT EXISTS idx_entity_relations_target ON entity_relations(target_entity_id);
|
|
419
|
+
|
|
420
|
+
-- Entity-Observation junction table
|
|
421
|
+
CREATE TABLE IF NOT EXISTS entity_observations (
|
|
422
|
+
entity_id TEXT NOT NULL,
|
|
423
|
+
observation_id TEXT NOT NULL,
|
|
424
|
+
PRIMARY KEY (entity_id, observation_id),
|
|
425
|
+
FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
|
426
|
+
FOREIGN KEY (observation_id) REFERENCES observations(id) ON DELETE CASCADE
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
-- FTS5 for observations
|
|
430
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
|
|
431
|
+
title,
|
|
432
|
+
subtitle,
|
|
433
|
+
narrative,
|
|
434
|
+
facts,
|
|
435
|
+
concepts,
|
|
436
|
+
files_read,
|
|
437
|
+
files_modified,
|
|
438
|
+
content=observations,
|
|
439
|
+
content_rowid=_rowid,
|
|
440
|
+
tokenize='porter unicode61'
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
CREATE TRIGGER observations_ai AFTER INSERT ON observations BEGIN
|
|
444
|
+
INSERT INTO observations_fts(
|
|
445
|
+
rowid, title, subtitle, narrative, facts, concepts,
|
|
446
|
+
files_read, files_modified
|
|
447
|
+
)
|
|
448
|
+
VALUES (
|
|
449
|
+
new._rowid, new.title, new.subtitle, new.narrative,
|
|
450
|
+
new.facts, new.concepts, new.files_read, new.files_modified
|
|
451
|
+
);
|
|
452
|
+
END;
|
|
453
|
+
|
|
454
|
+
CREATE TRIGGER observations_ad AFTER DELETE ON observations BEGIN
|
|
455
|
+
INSERT INTO observations_fts(
|
|
456
|
+
observations_fts, rowid, title, subtitle, narrative,
|
|
457
|
+
facts, concepts, files_read, files_modified
|
|
458
|
+
)
|
|
459
|
+
VALUES (
|
|
460
|
+
'delete', old._rowid, old.title, old.subtitle, old.narrative,
|
|
461
|
+
old.facts, old.concepts, old.files_read, old.files_modified
|
|
462
|
+
);
|
|
463
|
+
END;
|
|
464
|
+
|
|
465
|
+
CREATE TRIGGER observations_au AFTER UPDATE ON observations BEGIN
|
|
466
|
+
INSERT INTO observations_fts(
|
|
467
|
+
observations_fts, rowid, title, subtitle, narrative,
|
|
468
|
+
facts, concepts, files_read, files_modified
|
|
469
|
+
)
|
|
470
|
+
VALUES (
|
|
471
|
+
'delete', old._rowid, old.title, old.subtitle, old.narrative,
|
|
472
|
+
old.facts, old.concepts, old.files_read, old.files_modified
|
|
473
|
+
);
|
|
474
|
+
INSERT INTO observations_fts(
|
|
475
|
+
rowid, title, subtitle, narrative, facts, concepts,
|
|
476
|
+
files_read, files_modified
|
|
477
|
+
)
|
|
478
|
+
VALUES (
|
|
479
|
+
new._rowid, new.title, new.subtitle, new.narrative,
|
|
480
|
+
new.facts, new.concepts, new.files_read, new.files_modified
|
|
481
|
+
);
|
|
482
|
+
END;
|
|
483
|
+
|
|
484
|
+
-- FTS5 for session summaries
|
|
485
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS summaries_fts USING fts5(
|
|
486
|
+
summary,
|
|
487
|
+
key_decisions,
|
|
488
|
+
concepts,
|
|
489
|
+
content=session_summaries,
|
|
490
|
+
content_rowid=_rowid,
|
|
491
|
+
tokenize='porter unicode61'
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
CREATE TRIGGER summaries_ai AFTER INSERT ON session_summaries BEGIN
|
|
495
|
+
INSERT INTO summaries_fts(rowid, summary, key_decisions, concepts)
|
|
496
|
+
VALUES (new._rowid, new.summary, new.key_decisions, new.concepts);
|
|
497
|
+
END;
|
|
498
|
+
|
|
499
|
+
CREATE TRIGGER summaries_ad AFTER DELETE ON session_summaries BEGIN
|
|
500
|
+
INSERT INTO summaries_fts(
|
|
501
|
+
summaries_fts, rowid, summary, key_decisions, concepts
|
|
502
|
+
)
|
|
503
|
+
VALUES (
|
|
504
|
+
'delete', old._rowid, old.summary, old.key_decisions, old.concepts
|
|
505
|
+
);
|
|
506
|
+
END;
|
|
507
|
+
|
|
508
|
+
-- FTS5 for entity search
|
|
509
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
510
|
+
name,
|
|
511
|
+
entity_type,
|
|
512
|
+
content=entities,
|
|
513
|
+
content_rowid=_rowid,
|
|
514
|
+
tokenize='porter unicode61'
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
CREATE TRIGGER entities_ai AFTER INSERT ON entities BEGIN
|
|
518
|
+
INSERT INTO entities_fts(rowid, name, entity_type)
|
|
519
|
+
VALUES (new._rowid, new.name, new.entity_type);
|
|
520
|
+
END;
|
|
521
|
+
|
|
522
|
+
CREATE TRIGGER entities_ad AFTER DELETE ON entities BEGIN
|
|
523
|
+
INSERT INTO entities_fts(entities_fts, rowid, name, entity_type)
|
|
524
|
+
VALUES ('delete', old._rowid, old.name, old.entity_type);
|
|
525
|
+
END;
|
|
526
|
+
|
|
527
|
+
CREATE TRIGGER entities_au AFTER UPDATE ON entities BEGIN
|
|
528
|
+
INSERT INTO entities_fts(entities_fts, rowid, name, entity_type)
|
|
529
|
+
VALUES ('delete', old._rowid, old.name, old.entity_type);
|
|
530
|
+
INSERT INTO entities_fts(rowid, name, entity_type)
|
|
531
|
+
VALUES (new._rowid, new.name, new.entity_type);
|
|
532
|
+
END;
|
|
533
|
+
`}];function pf(f,B){if(f.migrate(w0),B?.hasVectorExtension&&B?.embeddingDimension&&B.embeddingDimension>0)l0(f,B.embeddingDimension)}function l0(f,B){if(f.get("SELECT name FROM sqlite_master WHERE type='table' AND name='observation_embeddings'")){let K=f.get("SELECT value FROM _embedding_meta WHERE key = 'dimension'");if(K&&Number(K.value)!==B){console.warn(`[open-mem] vec0 table exists with dimension ${K.value}, but config specifies ${B}. Drop observation_embeddings to re-create with new dimension.`);return}}else f.exec(`CREATE VIRTUAL TABLE observation_embeddings USING vec0(
|
|
534
|
+
observation_id TEXT PRIMARY KEY,
|
|
535
|
+
embedding float[${B}] distance_metric=cosine
|
|
536
|
+
)`);f.run("INSERT OR REPLACE INTO _embedding_meta (key, value) VALUES (?, ?)",["dimension",String(B)])}class e{db;constructor(f){this.db=f}create(f,B){let E=new Date().toISOString();return this.db.run(`INSERT INTO sessions (id, project_path, started_at, status)
|
|
537
|
+
VALUES (?, ?, ?, 'active')`,[f,B,E]),this.getById(f)}getOrCreate(f,B){let E=this.getById(f);if(E)return E;return this.create(f,B)}getById(f){let B=this.db.get("SELECT * FROM sessions WHERE id = ?",[f]);return B?this.mapRow(B):null}getRecent(f,B=10){return this.db.all("SELECT * FROM sessions WHERE project_path = ? ORDER BY started_at DESC LIMIT ?",[f,B]).map((E)=>this.mapRow(E))}getAll(f){return this.db.all("SELECT * FROM sessions WHERE project_path = ? ORDER BY started_at DESC",[f]).map((B)=>this.mapRow(B))}getActive(){return this.db.all("SELECT * FROM sessions WHERE status = 'active' ORDER BY started_at DESC").map((f)=>this.mapRow(f))}updateStatus(f,B){this.db.run("UPDATE sessions SET status = ? WHERE id = ?",[B,f])}markCompleted(f){this.db.run("UPDATE sessions SET status = 'completed', ended_at = datetime('now') WHERE id = ?",[f])}incrementObservationCount(f){this.db.run("UPDATE sessions SET observation_count = observation_count + 1 WHERE id = ?",[f])}setSummary(f,B){this.db.run("UPDATE sessions SET summary_id = ? WHERE id = ?",[B,f])}mapRow(f){return{id:f.id,projectPath:f.project_path,startedAt:f.started_at,endedAt:f.ended_at??null,status:f.status,observationCount:f.observation_count,summaryId:f.summary_id??null}}}import{randomUUID as v0}from"crypto";class a{db;constructor(f){this.db=f}create(f){let B=v0(),E=new Date().toISOString();return this.db.run(`INSERT INTO session_summaries
|
|
538
|
+
(id, session_id, summary, key_decisions, files_modified,
|
|
539
|
+
concepts, created_at, token_count,
|
|
540
|
+
request, investigated, learned, completed, next_steps)
|
|
541
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[B,f.sessionId,f.summary,JSON.stringify(f.keyDecisions),JSON.stringify(f.filesModified),JSON.stringify(f.concepts),E,f.tokenCount,f.request??"",f.investigated??"",f.learned??"",f.completed??"",f.nextSteps??""]),{...f,id:B,createdAt:E}}importSummary(f){this.db.run(`INSERT INTO session_summaries
|
|
542
|
+
(id, session_id, summary, key_decisions, files_modified,
|
|
543
|
+
concepts, created_at, token_count,
|
|
544
|
+
request, investigated, learned, completed, next_steps)
|
|
545
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[f.id,f.sessionId,f.summary,JSON.stringify(f.keyDecisions),JSON.stringify(f.filesModified),JSON.stringify(f.concepts),f.createdAt,f.tokenCount,f.request??"",f.investigated??"",f.learned??"",f.completed??"",f.nextSteps??""])}getBySessionId(f){let B=this.db.get("SELECT * FROM session_summaries WHERE session_id = ?",[f]);return B?this.mapRow(B):null}getRecent(f=10){return this.db.all("SELECT * FROM session_summaries ORDER BY created_at DESC LIMIT ?",[f]).map((B)=>this.mapRow(B))}search(f,B=10){return this.db.all(`SELECT ss.*
|
|
546
|
+
FROM session_summaries ss
|
|
547
|
+
JOIN summaries_fts fts ON ss._rowid = fts.rowid
|
|
548
|
+
WHERE summaries_fts MATCH ?
|
|
549
|
+
ORDER BY rank
|
|
550
|
+
LIMIT ?`,[f,B]).map((E)=>this.mapRow(E))}mapRow(f){return{id:f.id,sessionId:f.session_id,summary:f.summary,keyDecisions:JSON.parse(f.key_decisions),filesModified:JSON.parse(f.files_modified),concepts:JSON.parse(f.concepts),createdAt:f.created_at,tokenCount:f.token_count,request:f.request||void 0,investigated:f.investigated||void 0,learned:f.learned||void 0,completed:f.completed||void 0,nextSteps:f.next_steps||void 0}}}class ff{config;compressor;summarizer;pendingRepo;observationRepo;sessionRepo;summaryRepo;embeddingModel;conflictEvaluator;entityExtractor;entityRepo;observer;processing=!1;timer=null;mode="in-process";onEnqueue=null;constructor(f,B,E,K,J,z,A,R=null,N=null,$=null,W=null,Q=null){this.config=f;this.compressor=B;this.summarizer=E;this.pendingRepo=K;this.observationRepo=J;this.sessionRepo=z;this.summaryRepo=A;this.embeddingModel=R;this.conflictEvaluator=N;this.entityExtractor=$;this.entityRepo=W;this.observer=Q}setMode(f){if(this.mode=f,f==="enqueue-only")this.stop()}getMode(){return this.mode}setOnEnqueue(f){this.onEnqueue=f}enqueue(f,B,E,K){if(this.pendingRepo.create({sessionId:f,toolName:B,toolOutput:E,callId:K}),this.observer?.onEnqueue?.({sessionId:f,toolName:B,createdAt:new Date().toISOString()}),this.mode==="enqueue-only")this.onEnqueue?.()}async processBatch(){if(this.mode==="enqueue-only")return 0;if(this.processing)return 0;this.processing=!0;let f=0,B=0,E=Date.now();try{this.pendingRepo.resetStale(5);let K=this.pendingRepo.getPending(this.config.batchSize);if(this.observer?.onBatchStart?.({pending:K.length,mode:this.mode,startedAt:new Date(E).toISOString()}),K.length===0)return 0;for(let J of K)try{this.pendingRepo.markProcessing(J.id);let A=await this.compressor.compress(J.toolName,J.toolOutput)??this.compressor.createFallbackObservation(J.toolName,J.toolOutput),R=!1,N=null;if(this.embeddingModel)try{let W=s({title:A.title,narrative:A.narrative,concepts:A.concepts}),Q=await i(this.embeddingModel,W);if(Q){let Z=this.config.conflictResolutionEnabled&&this.conflictEvaluator,V=this.config.conflictSimilarityBandLow??0.7,k=this.config.conflictSimilarityBandHigh??0.92;if(Z){let X=this.observationRepo.findSimilar(Q,A.type,V,5),_=X.find((m)=>m.similarity>k);if(_)console.log(`[open-mem] Dedup: skipping duplicate of ${_.id} (similarity: ${_.similarity.toFixed(3)})`),R=!0;else{let m=X.filter((S)=>S.similarity>=V&&S.similarity<=k);if(m.length>0)try{let S=m.map((U)=>{let G=this.observationRepo.getById(U.id);return G?{id:G.id,title:G.title,narrative:G.narrative,concepts:G.concepts,type:G.type}:null}).filter((U)=>U!==null);if(S.length>0&&this.conflictEvaluator){let U=await this.conflictEvaluator.evaluate({title:A.title,narrative:A.narrative,concepts:A.concepts,type:A.type},S);if(U&&U.outcome==="duplicate")console.log(`[open-mem] Conflict eval: duplicate (${U.reason})`),R=!0;else if(U&&U.outcome==="update"&&U.supersedesId)console.log(`[open-mem] Conflict eval: update supersedes ${U.supersedesId} (${U.reason})`),N=U.supersedesId}}catch{}}}else{let X=this.observationRepo.findSimilar(Q,A.type,0.92,1);if(X.length>0)console.log(`[open-mem] Dedup: skipping duplicate of ${X[0].id} (similarity: ${X[0].similarity.toFixed(3)})`),R=!0}}}catch{}if(R){this.pendingRepo.markCompleted(J.id);continue}let $=this.observationRepo.create({sessionId:J.sessionId,type:A.type,title:A.title,subtitle:A.subtitle,facts:A.facts,narrative:A.narrative,concepts:A.concepts,filesRead:A.filesRead,filesModified:A.filesModified,rawToolOutput:J.toolOutput,toolName:J.toolName,tokenCount:M(`${A.title} ${A.narrative} ${A.facts.join(" ")}`),discoveryTokens:A.discoveryTokens??M(J.toolOutput),importance:A.importance??3});if(this.embeddingModel)try{let W=s({title:$.title,narrative:$.narrative,concepts:$.concepts}),Q=await i(this.embeddingModel,W);if(Q)this.observationRepo.setEmbedding($.id,Q)}catch{}if(N)try{this.observationRepo.supersede(N,$.id),console.log(`[open-mem] Superseded observation ${N} with ${$.id}`)}catch(W){console.error(`[open-mem] Failed to supersede ${N}:`,W)}if(this.config.entityExtractionEnabled&&this.entityExtractor&&this.entityRepo)try{let W=await this.entityExtractor.extract({title:$.title,narrative:$.narrative,concepts:$.concepts,facts:$.facts,filesRead:$.filesRead,filesModified:$.filesModified,type:$.type});if(W){let Q=new Map;for(let Z of W.entities){let V=this.entityRepo.upsertEntity(Z.name,Z.entityType);Q.set(Z.name,V.id),this.entityRepo.linkObservation(V.id,$.id)}for(let Z of W.relations){let V=Q.get(Z.sourceName),k=Q.get(Z.targetName);if(V&&k)this.entityRepo.createRelation(V,k,Z.relationship,$.id)}}}catch{}this.sessionRepo.incrementObservationCount(J.sessionId),this.pendingRepo.markCompleted(J.id),f++}catch(z){this.pendingRepo.markFailed(J.id,String(z)),B++,this.observer?.onItemFailed?.({pendingId:J.id,error:String(z),failedAt:new Date().toISOString()})}return f}finally{this.observer?.onBatchEnd?.({processed:f,failed:B,durationMs:Date.now()-E,finishedAt:new Date().toISOString()}),this.processing=!1}}async summarizeSession(f){let B=this.observationRepo.getBySession(f);if(!this.summarizer.shouldSummarize(B.length))return;if(this.summaryRepo.getBySessionId(f))return;let K=await this.summarizer.summarize(f,B);if(!K)return;let J=this.summaryRepo.create({sessionId:f,summary:K.summary,keyDecisions:K.keyDecisions,filesModified:K.filesModified,concepts:K.concepts,tokenCount:M(K.summary)});this.sessionRepo.setSummary(f,J.id)}start(){if(this.mode==="enqueue-only")return;if(this.timer)return;this.timer=setInterval(async()=>{try{await this.processBatch()}catch{}},this.config.batchIntervalMs)}stop(){if(this.timer)clearInterval(this.timer),this.timer=null}get isRunning(){return this.timer!==null}get isProcessing(){return this.processing}getStats(){return{pending:this.pendingRepo.getPending(1000).length,processing:this.processing}}}import{spawnSync as wf}from"child_process";import{dirname as c0,resolve as lf}from"path";function b0(f){try{let B=wf("git",["rev-parse","--git-common-dir"],{cwd:f,encoding:"utf-8",timeout:5000});if(B.status!==0||!B.stdout)return null;let E=B.stdout.trim();if(E===".git")return null;let K=wf("git",["rev-parse","--git-dir"],{cwd:f,encoding:"utf-8",timeout:5000});if(K.status!==0||!K.stdout)return null;let J=K.stdout.trim(),z=lf(f,E),A=lf(f,J);if(z===A)return null;let R=c0(z);if(R===z||R==="/")return null;return R}catch{return null}}function vf(f){return b0(f)??f}function Bf(f){process.stdout.write(`${JSON.stringify(f)}
|
|
551
|
+
`)}function r0(){let{values:f}=d0({options:{project:{type:"string",short:"p"},"http-port":{type:"string"}},strict:!1}),B=typeof f.project==="string"?f.project:process.cwd(),E=f["http-port"],K=typeof E==="string"&&Number.parseInt(E,10)>0?Number.parseInt(E,10):void 0;return{projectDir:B,httpPort:K}}function i0(f,B){if(f==="claude-code"&&!B.platformClaudeCodeEnabled)throw Error("Claude Code adapter is disabled. Set OPEN_MEM_PLATFORM_CLAUDE_CODE=true.");if(f==="cursor"&&!B.platformCursorEnabled)throw Error("Cursor adapter is disabled. Set OPEN_MEM_PLATFORM_CURSOR=true.")}function s0(f,B){let E=vf(B),K=jf(E);i0(f,K),I.enableExtensionSupport();let J=xf(K.dbPath);pf(J,{hasVectorExtension:J.hasVectorExtension,embeddingDimension:K.embeddingDimension});let z=new e(J),A=new o(J),R=new a(J),N=new t(J),$=new y(K),W=new n(K),Q=K.provider!=="bedrock",Z=K.compressionEnabled&&(!Q||K.apiKey)?yf({provider:K.provider,model:K.model,apiKey:K.apiKey}):null,V=K.conflictResolutionEnabled&&(!Q||K.apiKey)?new c({provider:K.provider,apiKey:K.apiKey,model:K.model,rateLimitingEnabled:K.rateLimitingEnabled}):null,k=K.entityExtractionEnabled&&(!Q||K.apiKey)?new b({provider:K.provider,apiKey:K.apiKey,model:K.model,rateLimitingEnabled:K.rateLimitingEnabled}):null,X=new r(J),_=new ff(K,$,W,N,A,z,R,Z,V,k,X),m=f==="claude-code"?Af():Rf(),S=new v({adapter:m,queue:_,sessions:z,observations:A,pendingMessages:N,projectPath:E,config:K});return{db:J,queue:_,runtime:S,platform:f,projectPath:E}}function cf(f,B){let E=f.queue.getStats();return{id:B,ok:!0,code:"OK",status:{platform:f.platform,projectPath:f.projectPath,queue:{mode:f.queue.getMode(),running:f.queue.isRunning,processing:E.processing,pending:E.pending}}}}function Ef(f){if(!f||typeof f!=="object"||Array.isArray(f))return{command:"event",payload:f};let B=f,E=typeof B.command==="string"&&(B.command==="event"||B.command==="flush"||B.command==="health"||B.command==="shutdown")?B.command:void 0,K=typeof B.id==="string"||typeof B.id==="number"?B.id:void 0;if(!E)return{command:"event",payload:f,id:K};return{id:K,command:E,payload:"payload"in B?B.payload:void 0}}async function bf(f){let B=r0(),E=s0(f,B.projectDir);E.queue.start();let K=!1,J=async()=>{if(K)return;K=!0;try{await E.queue.processBatch()}catch{}E.queue.stop(),E.db.close(),process.exit(0)},z=async(N)=>{let $=N.command??"event";if($==="health")return cf(E,N.id);if($==="flush"){let Q=await E.queue.processBatch();return{id:N.id,ok:!0,code:"OK",processed:Q}}if($==="shutdown")return{id:N.id,ok:!0,code:"OK",message:"shutting down"};if(!await E.runtime.ingestRaw(N.payload))return{id:N.id,ok:!1,code:"UNSUPPORTED_EVENT",message:"Payload did not match adapter event schema"};return{id:N.id,ok:!0,code:"OK",ingested:!0}};if(B.httpPort)Bun.serve({port:B.httpPort,hostname:"127.0.0.1",idleTimeout:0,fetch:async(N)=>{if(N.method==="GET"&&new URL(N.url).pathname==="/v1/health")return Response.json(cf(E));if(N.method==="POST"&&new URL(N.url).pathname==="/v1/events"){let $;try{$=await N.json()}catch{return Response.json({ok:!1,code:"INVALID_JSON",message:"Invalid JSON payload"},{status:400})}try{let W=await z(Ef($));if((Ef($).command??"event")==="shutdown")setTimeout(()=>{J()},0);return Response.json(W,{status:W.ok?200:422})}catch(W){return Response.json({ok:!1,code:"INGESTION_FAILED",message:String(W)},{status:500})}}return Response.json({ok:!1,code:"NOT_FOUND"},{status:404})}});process.on("SIGINT",()=>{J()}),process.on("SIGTERM",()=>{J()});let A=Promise.resolve(),R=n0({input:process.stdin,terminal:!1});R.on("line",(N)=>{let $=N.trim();if(!$)return;A=A.then(async()=>{let W;try{W=JSON.parse($)}catch{Bf({ok:!1,code:"INVALID_JSON",message:"Invalid JSON payload"});return}try{let Q=Ef(W),Z=await z(Q);if(Bf(Z),(Q.command??"event")==="shutdown")await J()}catch(Q){Bf({ok:!1,code:"INGESTION_FAILED",message:String(Q)})}})}),R.on("close",()=>{A.finally(()=>J())})}bf("claude-code");
|