remem-mcp 0.7.3 → 0.7.6

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/dist/sdk.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  ingestDirectory,
7
7
  ingestFile,
8
8
  searchWiki
9
- } from "./chunk-ITMR6THY.js";
9
+ } from "./chunk-2RKMX6EE.js";
10
10
  import {
11
11
  SUPPORTED_LANGUAGES,
12
12
  detectLanguage,
@@ -17,11 +17,11 @@ import {
17
17
  indexFile,
18
18
  listSymbols,
19
19
  searchSymbols
20
- } from "./chunk-YU7WBJU5.js";
20
+ } from "./chunk-MPEN2X43.js";
21
21
  import {
22
22
  LocalEmbedder,
23
23
  SQLiteBackend
24
- } from "./chunk-4WCDAEPP.js";
24
+ } from "./chunk-L7YZBBIB.js";
25
25
  import "./chunk-XJGDYQ34.js";
26
26
  import "./chunk-ONQHLUGM.js";
27
27
  export {
@@ -2,7 +2,7 @@ import {
2
2
  LocalEmbedder,
3
3
  SQLiteBackend,
4
4
  init_local
5
- } from "./chunk-4WCDAEPP.js";
5
+ } from "./chunk-L7YZBBIB.js";
6
6
  import {
7
7
  generateId,
8
8
  init_ulid
@@ -111,6 +111,7 @@ async function injectMemory(messages, userMessage, storage, embedder, binding) {
111
111
  const allScenarios = await storage.listScenarios({
112
112
  teamId: binding.teamId,
113
113
  agentId: binding.agentId,
114
+ sessionKey: binding.sessionKey,
114
115
  limit: 2
115
116
  });
116
117
  scenarios = allScenarios.map((s) => s.summary);
@@ -120,7 +121,7 @@ async function injectMemory(messages, userMessage, storage, embedder, binding) {
120
121
  let persona = null;
121
122
  if (binding.teamId && binding.agentId && binding.userId) {
122
123
  try {
123
- const p = await storage.readPersona(binding.teamId, binding.agentId, binding.userId);
124
+ const p = await storage.readPersona(binding.teamId, binding.agentId, binding.userId, binding.sessionKey);
124
125
  persona = p?.content ?? null;
125
126
  } catch {
126
127
  }
@@ -514,4 +515,4 @@ export {
514
515
  loadProxyConfig,
515
516
  startProxyServer
516
517
  };
517
- //# sourceMappingURL=server-5PWQTRGZ.js.map
518
+ //# sourceMappingURL=server-MYSF7R5Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/proxy/server.ts","../src/proxy/capture.ts","../src/proxy/inject.ts","../src/proxy/session.ts"],"sourcesContent":["/**\n * Memory Proxy — HTTP proxy that injects memory into LLM requests.\n *\n * Adapted from TencentDB Agent Memory's Memory Proxy concept (MIT, Tencent 2026).\n * https://github.com/TencentCloud/TencentDB-Agent-Memory\n *\n * Instead of requiring MCP hooks, the agent points its base URL to this proxy.\n * The proxy intercepts /v1/chat/completions (OpenAI) and /v1/messages (Anthropic),\n * injects memory into the system prompt, and forwards to the upstream LLM.\n *\n * This enables zero-code integration with any agent that calls OpenAI/Anthropic APIs.\n */\n\nimport {\n createServer,\n request as httpRequestHttp,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport { request as httpRequest } from \"node:https\";\nimport { URL } from \"node:url\";\nimport { LocalEmbedder } from \"../embedding/local.js\";\nimport { SQLiteBackend } from \"../storage/sqlite.js\";\nimport { captureConversation, captureConversationAnthropic } from \"./capture.js\";\nimport { injectMemory } from \"./inject.js\";\nimport { SessionStore } from \"./session.js\";\n\nexport interface ProxyConfig {\n port: number;\n upstreamUrl: string;\n upstreamApiKey?: string;\n dbPath: string;\n /** Whether to auto-capture conversations. Default: true. */\n autoCapture: boolean;\n /** Whether to inject memory into system prompt. Default: true. */\n injectMemory: boolean;\n}\n\n/** Default proxy config from environment variables. */\nexport function loadProxyConfig(): ProxyConfig {\n return {\n port: parseInt(process.env.REMEM_PROXY_PORT ?? \"8765\", 10),\n upstreamUrl: process.env.REMEM_UPSTREAM_URL ?? \"https://api.openai.com\",\n upstreamApiKey: process.env.REMEM_UPSTREAM_API_KEY ?? process.env.OPENAI_API_KEY,\n dbPath: process.env.REMEM_DB_PATH ?? \"\",\n autoCapture: process.env.REMEM_PROXY_CAPTURE !== \"false\",\n injectMemory: process.env.REMEM_PROXY_INJECT !== \"false\",\n };\n}\n\n/** Start the Memory Proxy server. */\nexport async function startProxyServer(config: ProxyConfig): Promise<void> {\n if (!config.dbPath) {\n throw new Error(\"REMEM_DB_PATH is required for proxy mode\");\n }\n\n const storage = new SQLiteBackend(config.dbPath);\n const embedder = new LocalEmbedder();\n const sessions = new SessionStore();\n\n const server = createServer(async (req, res) => {\n try {\n const url = new URL(req.url ?? \"/\", `http://localhost:${config.port}`);\n\n // Health check\n if (url.pathname === \"/health\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"ok\", proxy: true, upstream: config.upstreamUrl }));\n return;\n }\n\n // Session init — user selects team/agent/task\n if (url.pathname === \"/session/init\" && req.method === \"POST\") {\n const body = await readBody(req);\n const { teamId, agentId, taskId, userId } = JSON.parse(body);\n const sessionId = sessions.create({ teamId, agentId, taskId, userId });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ sessionId }));\n return;\n }\n\n // OpenAI protocol: POST /v1/chat/completions\n if (url.pathname === \"/v1/chat/completions\" && req.method === \"POST\") {\n await handleOpenAIRequest(req, res, config, storage, embedder, sessions);\n return;\n }\n\n // Anthropic protocol: POST /v1/messages\n if (url.pathname === \"/v1/messages\" && req.method === \"POST\") {\n await handleAnthropicRequest(req, res, config, storage, embedder, sessions);\n return;\n }\n\n // Pass-through for other endpoints (e.g., /v1/models)\n await passthrough(req, res, config);\n } catch (err) {\n console.error(\"[remem-mcp proxy] Error:\", err);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Proxy internal error\", detail: String(err) }));\n }\n });\n\n server.listen(config.port, () => {\n console.log(`[remem-mcp proxy] Listening on http://localhost:${config.port}`);\n console.log(`[remem-mcp proxy] Upstream: ${config.upstreamUrl}`);\n console.log(`[remem-mcp proxy] Endpoints:`);\n console.log(` POST /v1/chat/completions (OpenAI protocol)`);\n console.log(` POST /v1/messages (Anthropic protocol)`);\n console.log(` POST /session/init (bind team/agent/task)`);\n console.log(` GET /health (health check)`);\n });\n}\n\n/** Handle OpenAI /v1/chat/completions request. */\nasync function handleOpenAIRequest(\n req: IncomingMessage,\n res: ServerResponse,\n config: ProxyConfig,\n storage: SQLiteBackend,\n embedder: LocalEmbedder,\n sessions: SessionStore,\n): Promise<void> {\n const body = await readBody(req);\n const parsed = JSON.parse(body);\n\n // Inject memory into system prompt\n if (config.injectMemory) {\n const lastUserMessage = getLastUserMessage(parsed.messages ?? []);\n const sessionBinding = sessions.getFromHeaders(req.headers);\n console.error(\n `[proxy] inject: userMsg=\"${lastUserMessage?.slice(0, 50)}\" binding=${JSON.stringify(sessionBinding)}`,\n );\n if (lastUserMessage) {\n try {\n const injected = await injectMemory(\n parsed.messages ?? [],\n lastUserMessage,\n storage,\n embedder,\n sessionBinding,\n );\n const hasMemory = injected.some((m: { content: string }) => m.content.includes(\"<remem-mcp>\"));\n console.error(`[proxy] inject result: hasMemory=${hasMemory}, messages=${injected.length}`);\n parsed.messages = injected;\n } catch (e) {\n console.error(`[proxy] inject error:`, e);\n }\n }\n }\n\n // Forward to upstream\n const upstreamResp = await forwardRequest(\n config.upstreamUrl,\n \"/v1/chat/completions\",\n \"POST\",\n JSON.stringify(parsed),\n config.upstreamApiKey,\n req.headers,\n );\n\n // Auto-capture the conversation\n if (config.autoCapture) {\n const responseText = extractOpenAIResponse(upstreamResp.body);\n if (responseText) {\n captureConversation(\n parsed.messages ?? [],\n responseText,\n storage,\n sessions.getFromHeaders(req.headers),\n ).catch((e: unknown) => console.error(\"[remem-mcp proxy] Capture error:\", e));\n }\n }\n\n // Return response to client\n res.writeHead(upstreamResp.statusCode, upstreamResp.headers);\n res.end(upstreamResp.body);\n}\n\n/** Handle Anthropic /v1/messages request. */\nasync function handleAnthropicRequest(\n req: IncomingMessage,\n res: ServerResponse,\n config: ProxyConfig,\n storage: SQLiteBackend,\n embedder: LocalEmbedder,\n sessions: SessionStore,\n): Promise<void> {\n const body = await readBody(req);\n const parsed = JSON.parse(body);\n\n // Inject memory into system prompt\n if (config.injectMemory) {\n const lastUserMessage = getLastUserMessageAnthropic(parsed.messages ?? []);\n const sessionBinding = sessions.getFromHeaders(req.headers);\n if (lastUserMessage) {\n const systemPrompt = typeof parsed.system === \"string\" ? parsed.system : \"\";\n const injected = await injectMemoryAnthropic(\n systemPrompt,\n lastUserMessage,\n storage,\n embedder,\n sessionBinding,\n );\n parsed.system = injected;\n }\n }\n\n // Forward to upstream\n const upstreamResp = await forwardRequest(\n config.upstreamUrl,\n \"/v1/messages\",\n \"POST\",\n JSON.stringify(parsed),\n config.upstreamApiKey,\n req.headers,\n );\n\n // Auto-capture\n if (config.autoCapture) {\n const responseText = extractAnthropicResponse(upstreamResp.body);\n if (responseText) {\n captureConversationAnthropic(\n parsed.messages ?? [],\n responseText,\n storage,\n sessions.getFromHeaders(req.headers),\n ).catch((e: unknown) => console.error(\"[remem-mcp proxy] Capture error:\", e));\n }\n }\n\n res.writeHead(upstreamResp.statusCode, upstreamResp.headers);\n res.end(upstreamResp.body);\n}\n\n/** Pass-through for unsupported endpoints. */\nasync function passthrough(\n req: IncomingMessage,\n res: ServerResponse,\n config: ProxyConfig,\n): Promise<void> {\n const url = new URL(req.url ?? \"/\", `http://localhost:${config.port}`);\n const upstreamResp = await forwardRequest(\n config.upstreamUrl,\n url.pathname + url.search,\n req.method ?? \"GET\",\n await readBody(req),\n config.upstreamApiKey,\n req.headers,\n );\n res.writeHead(upstreamResp.statusCode, upstreamResp.headers);\n res.end(upstreamResp.body);\n}\n\n/** Read the full request body as a string. */\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (c) => chunks.push(c));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n req.on(\"error\", reject);\n });\n}\n\n/** Get the last user message from OpenAI-format messages. */\nfunction getLastUserMessage(messages: Array<{ role: string; content: string }>): string | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") {\n return typeof messages[i].content === \"string\"\n ? messages[i].content\n : JSON.stringify(messages[i].content);\n }\n }\n return null;\n}\n\n/** Get the last user message from Anthropic-format messages. */\nfunction getLastUserMessageAnthropic(\n messages: Array<{ role: string; content: unknown }>,\n): string | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") {\n const content = messages[i].content;\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const textBlock = content.find((b: { type: string; text?: string }) => b.type === \"text\");\n if (textBlock?.text) return textBlock.text;\n }\n }\n }\n return null;\n}\n\n/** Extract text from OpenAI chat completion response. */\nfunction extractOpenAIResponse(body: string): string | null {\n try {\n const parsed = JSON.parse(body);\n return parsed.choices?.[0]?.message?.content ?? null;\n } catch {\n return null;\n }\n}\n\n/** Extract text from Anthropic messages response. */\nfunction extractAnthropicResponse(body: string): string | null {\n try {\n const parsed = JSON.parse(body);\n const textBlocks = parsed.content?.filter((b: { type: string }) => b.type === \"text\") ?? [];\n return textBlocks.map((b: { text: string }) => b.text).join(\"\\n\") || null;\n } catch {\n return null;\n }\n}\n\n/** Forward a request to the upstream server. */\nasync function forwardRequest(\n upstreamUrl: string,\n path: string,\n method: string,\n body: string,\n apiKey?: string,\n headers?: Record<string, string | string[] | undefined>,\n): Promise<{ statusCode: number; headers: Record<string, string>; body: string }> {\n const url = new URL(path, upstreamUrl);\n const isHttps = url.protocol === \"https:\";\n const requestFn = isHttps ? httpRequest : httpRequestHttp;\n\n const forwardHeaders: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body).toString(),\n };\n\n // Copy relevant headers from original request\n if (headers) {\n for (const [key, value] of Object.entries(headers)) {\n const lower = key.toLowerCase();\n if (\n lower === \"authorization\" ||\n lower === \"x-api-key\" ||\n lower === \"anthropic-version\" ||\n lower === \"openai-organization\" ||\n lower === \"accept\" ||\n lower === \"accept-encoding\"\n ) {\n if (typeof value === \"string\") {\n forwardHeaders[key] = value;\n }\n }\n }\n }\n\n // Override with configured API key if provided\n if (apiKey) {\n if (path.startsWith(\"/v1/messages\")) {\n forwardHeaders[\"x-api-key\"] = apiKey;\n } else {\n forwardHeaders[\"Authorization\"] = `Bearer ${apiKey}`;\n }\n }\n\n return new Promise((resolve, reject) => {\n const proxyReq = requestFn(url, { method, headers: forwardHeaders }, (proxyRes) => {\n const chunks: Buffer[] = [];\n proxyRes.on(\"data\", (c) => chunks.push(c));\n proxyRes.on(\"end\", () => {\n const respHeaders: Record<string, string> = {};\n for (const [k, v] of Object.entries(proxyRes.headers)) {\n if (typeof v === \"string\") respHeaders[k] = v;\n }\n resolve({\n statusCode: proxyRes.statusCode ?? 200,\n headers: respHeaders,\n body: Buffer.concat(chunks).toString(\"utf-8\"),\n });\n });\n proxyRes.on(\"error\", reject);\n });\n proxyReq.on(\"error\", reject);\n proxyReq.write(body);\n proxyReq.end();\n });\n}\n\n/** Inject memory into Anthropic-format system prompt. */\nasync function injectMemoryAnthropic(\n systemPrompt: string,\n userMessage: string,\n storage: SQLiteBackend,\n embedder: LocalEmbedder,\n sessionBinding: { teamId?: string; agentId?: string; userId?: string; taskId?: string },\n): Promise<string> {\n const memoryBlock = await injectMemory([], userMessage, storage, embedder, sessionBinding);\n // For Anthropic, we append the memory to the system prompt\n const memoryText = extractMemoryBlock(memoryBlock);\n if (!memoryText) return systemPrompt;\n return `${systemPrompt}\\n\\n${memoryText}`;\n}\n\n/** Extract the memory block text from injected messages. */\nfunction extractMemoryBlock(messages: Array<{ role: string; content: string }>): string | null {\n for (const msg of messages) {\n if (msg.role === \"system\" && msg.content.includes(\"<remem-mcp>\")) {\n return msg.content;\n }\n }\n return null;\n}\n","/**\n * Auto-capture — captures conversations after the upstream LLM responds.\n *\n * Reuses the same capture logic as the session transcript capture in hook-handlers.ts.\n */\n\nimport type { SQLiteBackend } from \"../storage/sqlite.js\";\nimport { generateId } from \"../utils/ulid.js\";\n\ninterface SessionBinding {\n teamId?: string;\n agentId?: string;\n userId?: string;\n taskId?: string;\n}\n\ninterface Message {\n role: string;\n content: string;\n}\n\n/**\n * Capture an OpenAI-format conversation (user messages + assistant response).\n */\nexport async function captureConversation(\n messages: Message[],\n assistantResponse: string,\n storage: SQLiteBackend,\n binding: SessionBinding,\n): Promise<void> {\n // Build a conversation summary\n const userMessages = messages.filter((m) => m.role === \"user\");\n if (userMessages.length === 0) return;\n\n const lastUser = userMessages[userMessages.length - 1];\n const content = `User: ${lastUser.content.slice(0, 500)}\\n\\nAssistant: ${assistantResponse.slice(0, 2000)}`;\n\n const id = generateId();\n const now = Date.now();\n\n await storage.put({\n id,\n sessionKey: `proxy-${binding.teamId ?? \"default\"}`,\n agentId: binding.agentId ?? \"proxy\",\n type: \"conversation\",\n content,\n tags: [\"proxy\", \"conversation\"],\n createdAt: now,\n metadata: {\n source: \"proxy\",\n teamId: binding.teamId,\n agentId: binding.agentId,\n userId: binding.userId,\n taskId: binding.taskId,\n },\n teamId: binding.teamId,\n userId: binding.userId,\n taskId: binding.taskId,\n });\n}\n\n/**\n * Capture an Anthropic-format conversation.\n */\nexport async function captureConversationAnthropic(\n messages: Array<{ role: string; content: unknown }>,\n assistantResponse: string,\n storage: SQLiteBackend,\n binding: SessionBinding,\n): Promise<void> {\n // Convert Anthropic format to simple messages\n const simpleMessages: Message[] = messages.map((m) => ({\n role: m.role,\n content:\n typeof m.content === \"string\"\n ? m.content\n : Array.isArray(m.content)\n ? m.content\n .filter((b: { type: string; text?: string }) => b.type === \"text\" && b.text)\n .map((b: { text: string }) => b.text)\n .join(\"\\n\")\n : \"\",\n }));\n\n await captureConversation(simpleMessages, assistantResponse, storage, binding);\n}\n","/**\n * Memory injection — queries recall + scenarios + persona + skills\n * and injects them into the system prompt.\n *\n * Reuses the same recall logic as the hook-recall handler.\n */\n\nimport type { LocalEmbedder } from \"../embedding/local.js\";\nimport type { SQLiteBackend } from \"../storage/sqlite.js\";\n\ninterface SessionBinding {\n teamId?: string;\n agentId?: string;\n userId?: string;\n taskId?: string;\n sessionKey?: string;\n}\n\ninterface Message {\n role: string;\n content: string;\n}\n\n/**\n * Inject memory into OpenAI-format messages.\n * - Queries recall with the last user message\n * - Prepends a system message with the memory block\n */\nexport async function injectMemory(\n messages: Message[],\n userMessage: string,\n storage: SQLiteBackend,\n embedder: LocalEmbedder,\n binding: SessionBinding,\n): Promise<Message[]> {\n // Generate embedding for the query\n let queryEmbedding: number[] | null = null;\n try {\n queryEmbedding = await embedder.embed(userMessage);\n console.error(`[proxy inject] embedding: ${queryEmbedding?.length ?? 0} dims`);\n } catch (e) {\n console.error(`[proxy inject] embed error: ${e}`);\n }\n\n // Query recall (hybrid search)\n let results: Array<{\n id: string;\n content: string;\n type: string;\n tags: string[];\n score: number;\n }> = [];\n try {\n const searchResults = await storage.search(userMessage, queryEmbedding, {\n sessionKey: \"\",\n limit: 5,\n offset: 0,\n mode: \"hybrid\",\n filters: {\n teamId: binding.teamId,\n agentId: binding.agentId,\n userId: binding.userId,\n },\n });\n results = searchResults.map((r) => ({\n id: r.entry.id,\n content: r.entry.content,\n type: r.entry.type,\n tags: r.entry.tags,\n score: r.score,\n }));\n console.error(`[proxy inject] search: ${results.length} results`);\n } catch (e) {\n console.error(`[proxy inject] search error: ${e}`);\n }\n\n // Query L1 atoms for the top captures\n const atoms: string[] = [];\n for (const result of results.slice(0, 3)) {\n try {\n const captureAtoms = await storage.listAtoms({ captureId: result.id, limit: 3 });\n for (const atom of captureAtoms) {\n atoms.push(atom.fact);\n }\n } catch {\n // best-effort\n }\n }\n\n // Query L2 scenarios\n let scenarios: string[] = [];\n if (binding.teamId) {\n try {\n const allScenarios = await storage.listScenarios({\n teamId: binding.teamId,\n agentId: binding.agentId,\n sessionKey: binding.sessionKey,\n limit: 2,\n });\n scenarios = allScenarios.map((s) => s.summary);\n } catch {\n // best-effort\n }\n }\n\n // Query L3 persona\n let persona: string | null = null;\n if (binding.teamId && binding.agentId && binding.userId) {\n try {\n const p = await storage.readPersona(binding.teamId, binding.agentId, binding.userId, binding.sessionKey);\n persona = p?.content ?? null;\n } catch {\n // best-effort\n }\n }\n\n // Query skills\n let skills: string[] = [];\n if (binding.teamId && binding.agentId) {\n try {\n const matchedSkills = await storage.searchSkills(\n binding.teamId,\n binding.agentId,\n userMessage,\n 2,\n );\n skills = matchedSkills.map((s) => `**${s.name}**: ${s.description ?? \"\"}`);\n } catch {\n // best-effort\n }\n }\n\n // [F1: Canvas injection] Query canvas for this session if offload is enabled\n let canvasBlock: string | null = null;\n const offloadOn =\n process.env.REMEM_OFFLOAD_ENABLED === \"true\" ||\n process.env.REMEM_OFFLOAD_ENABLED === \"1\" ||\n process.env.REMEM_FLOW === \"full\";\n if (offloadOn) {\n try {\n // For proxy mode, use team_id as session key (no cwd available)\n const sessionKey = `proxy-${binding.teamId ?? \"default\"}`;\n const canvasRow = (storage as any).db\n .prepare(\n \"SELECT mermaid_text, node_count FROM canvases WHERE session_key = ? ORDER BY updated_at DESC LIMIT 1\",\n )\n .get(sessionKey) as { mermaid_text: string | null; node_count: number } | undefined;\n if (canvasRow && canvasRow.mermaid_text && canvasRow.node_count > 0) {\n canvasBlock = `### Task Canvas (${canvasRow.node_count} steps)\\n\\`\\`\\`mermaid\\n${canvasRow.mermaid_text}\\n\\`\\`\\``;\n }\n } catch {\n // best-effort\n }\n }\n\n // Build the memory block\n const memoryBlock = buildMemoryBlock(results, atoms, scenarios, persona, skills, canvasBlock);\n if (!memoryBlock) return messages;\n\n // Find the system message and append, or prepend a new one\n const systemIdx = messages.findIndex((m) => m.role === \"system\");\n if (systemIdx >= 0) {\n const newMessages = [...messages];\n newMessages[systemIdx] = {\n ...messages[systemIdx],\n content: `${messages[systemIdx].content}\\n\\n${memoryBlock}`,\n };\n return newMessages;\n }\n\n return [{ role: \"system\", content: memoryBlock }, ...messages];\n}\n\n/** Build the <remem-mcp> memory block for injection. */\nfunction buildMemoryBlock(\n results: Array<{ content: string; type: string; tags: string[] }>,\n atoms: string[],\n scenarios: string[],\n persona: string | null,\n skills: string[],\n canvasBlock: string | null,\n): string | null {\n const sections: string[] = [];\n\n if (atoms.length > 0) {\n sections.push(`### Relevant Facts\\n${atoms.map((a) => `- ${a}`).join(\"\\n\")}`);\n }\n\n if (results.length > 0) {\n const captures = results.slice(0, 3).map((r) => `- [${r.type}] ${r.content.slice(0, 200)}`);\n sections.push(`### Past Memories\\n${captures.join(\"\\n\")}`);\n }\n\n if (scenarios.length > 0) {\n sections.push(`### Scenarios\\n${scenarios.map((s) => `- ${s}`).join(\"\\n\")}`);\n }\n\n if (persona) {\n sections.push(`### User Profile\\n${persona}`);\n }\n\n if (skills.length > 0) {\n sections.push(`### Relevant Skills\\n${skills.map((s) => `- ${s}`).join(\"\\n\")}`);\n }\n\n if (canvasBlock) {\n sections.push(canvasBlock);\n }\n\n if (sections.length === 0) return null;\n\n return `<remem-mcp>\\n${sections.join(\"\\n\\n\")}\\n</remem-mcp>`;\n}\n","/**\n * Session store — tracks team/agent/task bindings for proxy sessions.\n *\n * Adapted from TencentDB Agent Memory's sessionInit concept (MIT, Tencent 2026).\n * The proxy binds each session to a team/agent/task so memory queries are scoped.\n */\n\nimport { randomUUID } from \"node:crypto\";\n\ninterface SessionBinding {\n teamId?: string;\n agentId?: string;\n taskId?: string;\n userId?: string;\n}\n\n/**\n * In-memory session store.\n * For production, this should be persisted to Redis or SQLite.\n */\nexport class SessionStore {\n private sessions = new Map<string, SessionBinding>();\n\n /** Create a new session with the given binding. */\n create(binding: SessionBinding): string {\n const sessionId = randomUUID();\n this.sessions.set(sessionId, binding);\n return sessionId;\n }\n\n /** Get the binding for a session. */\n get(sessionId: string): SessionBinding | null {\n return this.sessions.get(sessionId) ?? null;\n }\n\n /** Get the binding from request headers (x-remem-session or x-tdai-user-key). */\n getFromHeaders(headers: Record<string, string | string[] | undefined>): SessionBinding {\n const sessionId = headers[\"x-remem-session\"] as string | undefined;\n if (sessionId) {\n const binding = this.sessions.get(sessionId);\n if (binding) return binding;\n }\n\n // Fall back to header-based binding\n return {\n teamId: headers[\"x-remem-team\"] as string | undefined,\n agentId: headers[\"x-remem-agent\"] as string | undefined,\n taskId: headers[\"x-remem-task\"] as string | undefined,\n userId: headers[\"x-remem-user\"] as string | undefined,\n };\n }\n\n /** Delete a session. */\n delete(sessionId: string): void {\n this.sessions.delete(sessionId);\n }\n\n /** Clear all sessions. */\n clear(): void {\n this.sessions.clear();\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
1
  -- Schema for remem-mcp
2
- -- Version: 13
2
+ -- Version: 17
3
3
  --
4
4
  -- This file runs on the first start. It creates all tables, triggers, and indexes.
5
5
  -- It uses CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS.
@@ -81,7 +81,13 @@ CREATE TABLE IF NOT EXISTS captures (
81
81
  salience REAL NOT NULL DEFAULT 1.0, -- decay score, updated by forget sweep
82
82
  -- v12: TTL for time-sensitive captures + feedback-derived salience floor
83
83
  expires_at INTEGER, -- timestamp; forgetSweep hard-deletes past this
84
- feedback_salience REAL NOT NULL DEFAULT 1.0 -- multiplier from feedback signals (0.1–2.0)
84
+ feedback_salience REAL NOT NULL DEFAULT 1.0, -- multiplier from feedback signals (0.1–2.0)
85
+ -- v17: Temporal validity (SodaMem-inspired). valid_from = when the fact became true,
86
+ -- valid_until = when it stopped being true (set by supersede). NULL = still valid.
87
+ valid_from INTEGER, -- timestamp when the fact became true
88
+ valid_until INTEGER, -- timestamp when the fact stopped being true (set by supersede)
89
+ -- v17: Provenance tracking. Links a capture to its originating tool call or conversation turn.
90
+ source_ref TEXT -- provenance: e.g. "bash:git status", "tool:codegraph_search"
85
91
  );
86
92
 
87
93
  -- L0 messages: role-based conversation messages linked to a capture.
@@ -117,7 +123,8 @@ CREATE TABLE IF NOT EXISTS scenarios (
117
123
  created_at INTEGER NOT NULL,
118
124
  team_id TEXT,
119
125
  agent_id TEXT,
120
- user_id TEXT
126
+ user_id TEXT,
127
+ session_key TEXT
121
128
  );
122
129
 
123
130
  -- v10: Entity index for entity-assisted recall.
@@ -175,15 +182,17 @@ CREATE INDEX IF NOT EXISTS idx_mutation_action ON mutation_log (action);
175
182
  CREATE INDEX IF NOT EXISTS idx_mutation_capture ON mutation_log (capture_id);
176
183
  CREATE INDEX IF NOT EXISTS idx_mutation_created ON mutation_log (created_at);
177
184
 
178
- -- L3: Persona (long-term user profile, one per team/agent/user)
185
+ -- L3: Persona (long-term user profile, one per team/agent/user/session)
179
186
  CREATE TABLE IF NOT EXISTS persona (
180
187
  team_id TEXT NOT NULL,
181
188
  agent_id TEXT NOT NULL,
182
189
  user_id TEXT NOT NULL,
183
190
  content TEXT NOT NULL,
184
191
  updated_at INTEGER NOT NULL,
185
- PRIMARY KEY (team_id, agent_id, user_id)
192
+ session_key TEXT,
193
+ PRIMARY KEY (team_id, agent_id, user_id, session_key)
186
194
  );
195
+ CREATE INDEX IF NOT EXISTS idx_persona_session ON persona (session_key, team_id, user_id);
187
196
 
188
197
  -- Knowledge assets (wiki, code-graph) registered by the team
189
198
  CREATE TABLE IF NOT EXISTS knowledge (
@@ -382,6 +391,7 @@ CREATE INDEX IF NOT EXISTS idx_atoms_capture ON atoms (capture_id);
382
391
  CREATE INDEX IF NOT EXISTS idx_atoms_team ON atoms (team_id, created_at DESC);
383
392
  CREATE INDEX IF NOT EXISTS idx_messages_capture ON messages (capture_id, seq);
384
393
  CREATE INDEX IF NOT EXISTS idx_scenarios_team ON scenarios (team_id, created_at DESC);
394
+ CREATE INDEX IF NOT EXISTS idx_scenarios_session ON scenarios (session_key, created_at DESC);
385
395
  CREATE INDEX IF NOT EXISTS idx_audit_ts ON audit_log (ts DESC);
386
396
  CREATE INDEX IF NOT EXISTS idx_knowledge_team ON knowledge (team_id, created_at DESC);
387
397
  CREATE INDEX IF NOT EXISTS idx_skills_team ON skills (team_id, updated_at DESC);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "remem-mcp",
3
3
  "mcpName": "io.github.tinhien11/remem-mcp",
4
- "version": "0.7.3",
4
+ "version": "0.7.6",
5
5
  "description": "Long-term memory for AI coding agents. Memory + CodeGraph + Wiki in one SQLite file. Auto-recall, auto-capture, no API key, no cloud.",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -101,7 +101,9 @@ Use grep only for: string literals, config values, file names.
101
101
 
102
102
  ## Global + project memory
103
103
 
104
- Set `REMEM_GLOBAL_SESSION_KEY=global` to enable cross-project memory. `recall`/`search` search both. Pass `auto_global=true` to `capture` for auto-classification (generic → global, file paths → project).
104
+ Set `REMEM_GLOBAL_SESSION_KEY=global` to enable cross-project **reading**. `recall`/`search` and hooks can read global memory automatically.
105
+
106
+ Writing is different: `capture` is project-local by default. Use `session_key: "global"` **only after the user explicitly asks** to remember something globally or across projects. Do not use `auto_global: true` during ordinary work.
105
107
 
106
108
  ## Unified Flow (F1 + F2 + F3)
107
109