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/README.md +20 -59
- package/dist/{chunk-ITMR6THY.js → chunk-2RKMX6EE.js} +5 -4
- package/dist/chunk-2RKMX6EE.js.map +1 -0
- package/dist/{chunk-4WCDAEPP.js → chunk-L7YZBBIB.js} +306 -32
- package/dist/chunk-L7YZBBIB.js.map +1 -0
- package/dist/{chunk-YU7WBJU5.js → chunk-MPEN2X43.js} +106 -2
- package/dist/{chunk-YU7WBJU5.js.map → chunk-MPEN2X43.js.map} +1 -1
- package/dist/{engine-S47HHGAO.js → engine-LAY25SCA.js} +4 -2
- package/dist/index.js +440 -261
- package/dist/index.js.map +1 -1
- package/dist/sdk.js +3 -3
- package/dist/{server-5PWQTRGZ.js → server-MYSF7R5Y.js} +4 -3
- package/dist/server-MYSF7R5Y.js.map +1 -0
- package/dist/storage/schema.sql +15 -5
- package/package.json +1 -1
- package/skills/remem-mcp/SKILL.md +3 -1
- package/dist/chunk-4WCDAEPP.js.map +0 -1
- package/dist/chunk-ITMR6THY.js.map +0 -1
- package/dist/server-5PWQTRGZ.js.map +0 -1
- /package/dist/{engine-S47HHGAO.js.map → engine-LAY25SCA.js.map} +0 -0
package/dist/sdk.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ingestDirectory,
|
|
7
7
|
ingestFile,
|
|
8
8
|
searchWiki
|
|
9
|
-
} from "./chunk-
|
|
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-
|
|
20
|
+
} from "./chunk-MPEN2X43.js";
|
|
21
21
|
import {
|
|
22
22
|
LocalEmbedder,
|
|
23
23
|
SQLiteBackend
|
|
24
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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":[]}
|
package/dist/storage/schema.sql
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
-- Schema for remem-mcp
|
|
2
|
-
-- Version:
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|