remem-mcp 0.6.7 → 0.7.0

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  __esm,
3
3
  init_esm_shims
4
- } from "./chunk-XK4Y53J2.js";
4
+ } from "./chunk-ONQHLUGM.js";
5
5
 
6
6
  // src/codegraph/resolver.ts
7
7
  function resolveCall(calleeName, callerFilePath, registry, importMap) {
@@ -43,9 +43,7 @@ function resolveCall(calleeName, callerFilePath, registry, importMap) {
43
43
  if (symSame) {
44
44
  return { calleeId: symSame.id, confidence: 0.9, strategy: "same-module" };
45
45
  }
46
- const sameModuleSimple = registry.reverseLookup(calleeName).find(
47
- (s) => registry.getModuleForFile(s.file_path) === callerModule
48
- );
46
+ const sameModuleSimple = registry.reverseLookup(calleeName).find((s) => registry.getModuleForFile(s.file_path) === callerModule);
49
47
  if (sameModuleSimple) {
50
48
  return { calleeId: sameModuleSimple.id, confidence: 0.9, strategy: "same-module" };
51
49
  }
@@ -102,7 +100,9 @@ function resolveAllCalls(db) {
102
100
  const registry = new FunctionRegistry(symbols);
103
101
  const imports = db.prepare("SELECT file_path, symbol_name, source_path FROM imports").all();
104
102
  const importMap = new ImportMap(imports);
105
- const calls = db.prepare("SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL").all();
103
+ const calls = db.prepare(
104
+ "SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL"
105
+ ).all();
106
106
  const stats = { total: calls.length, resolved: 0, byStrategy: {} };
107
107
  let skipped = 0;
108
108
  const callerFiles = /* @__PURE__ */ new Map();
@@ -117,9 +117,7 @@ function resolveAllCalls(db) {
117
117
  }
118
118
  }
119
119
  }
120
- const updateStmt = db.prepare(
121
- "UPDATE calls SET callee_id = ?, confidence = ? WHERE id = ?"
122
- );
120
+ const updateStmt = db.prepare("UPDATE calls SET callee_id = ?, confidence = ? WHERE id = ?");
123
121
  const batch = db.transaction(() => {
124
122
  for (const call of calls) {
125
123
  if (shouldSkipResolution(call.callee_name)) {
@@ -297,4 +295,4 @@ export {
297
295
  resolveAllCalls,
298
296
  resolveCall
299
297
  };
300
- //# sourceMappingURL=resolver-AVK7XKX7.js.map
298
+ //# sourceMappingURL=resolver-YIFWRNMS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codegraph/resolver.ts"],"sourcesContent":["/**\n * Call resolver: 6-strategy cascade for resolving callee names to symbol IDs.\n *\n * Adapted from Codebase-Memory (arXiv:2603.27277, MIT, DeusData 2026).\n * https://github.com/DeusData/codebase-memory-mcp\n *\n * Strategies (in priority order):\n * 1. Import map (0.95): Split callee into prefix.suffix; look up prefix in file's import map\n * 2. Import map suffix (0.85): Suffix-based matching against import-resolved module paths\n * 3. Same module (0.90): Prefix callee with enclosing file's module path\n * 4. Unique name (0.75): Simple name lookup, accept if exactly 1 candidate\n * 5. Suffix match (0.55): Multiple candidates, select by suffix + import-distance\n * 6. Fuzzy (0.30-0.40): String similarity — last resort\n */\n\nimport type { Database } from \"better-sqlite3\";\n\nexport interface SymbolRow {\n id: string;\n name: string;\n kind: string;\n file_path: string;\n module_path: string | null;\n language: string;\n parent_id: string | null;\n}\n\nexport interface CallRow {\n id: number;\n caller_id: string;\n callee_name: string;\n callee_id: string | null;\n line: number;\n call_type: string;\n}\n\nexport interface ImportRow {\n file_path: string;\n symbol_name: string;\n source_path: string | null;\n}\n\nexport interface ResolutionResult {\n calleeId: string | null;\n confidence: number;\n strategy: string;\n}\n\n/**\n * FunctionRegistry indexes all symbol definitions by:\n * - qualified name (exact map): \"module_path/SymbolName\" → SymbolRow\n * - simple name (reverse index): \"SymbolName\" → SymbolRow[]\n */\nexport class FunctionRegistry {\n private exactMap = new Map<string, SymbolRow>();\n private reverseIndex = new Map<string, SymbolRow[]>();\n private fileModules = new Map<string, string>(); // file_path → module_path\n\n constructor(symbols: SymbolRow[]) {\n for (const sym of symbols) {\n // Build module_path from file_path if not set\n const modulePath = sym.module_path ?? deriveModulePath(sym.file_path);\n this.fileModules.set(sym.file_path, modulePath);\n\n // Exact map: module_path/name\n const qualifiedName = `${modulePath}/${sym.name}`;\n this.exactMap.set(qualifiedName, sym);\n // Also index by file_path/name for same-file resolution\n this.exactMap.set(`${sym.file_path}/${sym.name}`, sym);\n\n // Reverse index: simple name → all matching symbols\n const existing = this.reverseIndex.get(sym.name);\n if (existing) {\n existing.push(sym);\n } else {\n this.reverseIndex.set(sym.name, [sym]);\n }\n }\n }\n\n /** Look up by qualified name (module_path/name). */\n exactLookup(qualifiedName: string): SymbolRow | undefined {\n return this.exactMap.get(qualifiedName);\n }\n\n /** Look up by simple name — returns all candidates. */\n reverseLookup(simpleName: string): SymbolRow[] {\n return this.reverseIndex.get(simpleName) ?? [];\n }\n\n /** Get all reverse index entries (for fuzzy lookup). */\n reverseLookupEntries(): IterableIterator<[string, SymbolRow[]]> {\n return this.reverseIndex.entries();\n }\n\n /** Get module_path for a file. */\n getModuleForFile(filePath: string): string {\n return this.fileModules.get(filePath) ?? deriveModulePath(filePath);\n }\n}\n\n/**\n * ImportMap tracks what each file imports: { prefix → module_path }.\n * Built from the imports table.\n */\nexport class ImportMap {\n private fileImports = new Map<string, Map<string, string>>();\n\n constructor(imports: ImportRow[]) {\n for (const imp of imports) {\n if (!imp.source_path) continue;\n let fileMap = this.fileImports.get(imp.file_path);\n if (!fileMap) {\n fileMap = new Map();\n this.fileImports.set(imp.file_path, fileMap);\n }\n // Map: symbol_name (or prefix) → source module_path\n const prefix = imp.symbol_name.split(\".\")[0] ?? imp.symbol_name;\n const modulePath = deriveModulePath(imp.source_path);\n fileMap.set(prefix, modulePath);\n fileMap.set(imp.symbol_name, modulePath);\n }\n }\n\n /** Look up module path for an import prefix in a given file. */\n lookup(filePath: string, prefix: string): string | undefined {\n return this.fileImports.get(filePath)?.get(prefix);\n }\n\n /** Get all imports for a file. */\n getImportsForFile(filePath: string): Map<string, string> | undefined {\n return this.fileImports.get(filePath);\n }\n}\n\n/**\n * Resolve a callee name to a symbol ID using the 6-strategy cascade.\n * Returns { calleeId, confidence, strategy } or { calleeId: null, confidence: 0, strategy: \"unresolved\" }.\n */\nexport function resolveCall(\n calleeName: string,\n callerFilePath: string,\n registry: FunctionRegistry,\n importMap: ImportMap,\n): ResolutionResult {\n // Strategy 1: Import map (0.95)\n // Split callee into prefix.suffix; look up prefix in file's import map\n const parts = calleeName.split(\".\");\n if (parts.length >= 2) {\n const prefix = parts[0];\n const suffix = parts.slice(1).join(\".\");\n const modulePath = importMap.lookup(callerFilePath, prefix);\n if (modulePath) {\n const qualifiedName = `${modulePath}/${suffix}`;\n const sym = registry.exactLookup(qualifiedName);\n if (sym) {\n return { calleeId: sym.id, confidence: 0.95, strategy: \"import-map\" };\n }\n // Also try full callee name in the resolved module\n const fullQualified = `${modulePath}/${calleeName}`;\n const symFull = registry.exactLookup(fullQualified);\n if (symFull) {\n return { calleeId: symFull.id, confidence: 0.95, strategy: \"import-map\" };\n }\n }\n }\n\n // Strategy 2: Import map suffix (0.85)\n // Try suffix-based matching against import-resolved module paths\n if (parts.length >= 2) {\n const prefix = parts[0];\n const suffix = parts[parts.length - 1];\n const modulePath = importMap.lookup(callerFilePath, prefix);\n if (modulePath) {\n const candidates = registry.reverseLookup(suffix);\n for (const c of candidates) {\n const cModule = registry.getModuleForFile(c.file_path);\n if (cModule === modulePath || cModule.endsWith(`/${modulePath}`)) {\n return { calleeId: c.id, confidence: 0.85, strategy: \"import-map-suffix\" };\n }\n }\n }\n }\n\n // Strategy 3: Same module (0.90)\n // Prefix callee with enclosing file's module path\n const callerModule = registry.getModuleForFile(callerFilePath);\n const sameModuleQualified = `${callerModule}/${calleeName}`;\n const symSame = registry.exactLookup(sameModuleQualified);\n if (symSame) {\n return { calleeId: symSame.id, confidence: 0.9, strategy: \"same-module\" };\n }\n // Also try just the simple name in same module\n const sameModuleSimple = registry\n .reverseLookup(calleeName)\n .find((s) => registry.getModuleForFile(s.file_path) === callerModule);\n if (sameModuleSimple) {\n return { calleeId: sameModuleSimple.id, confidence: 0.9, strategy: \"same-module\" };\n }\n\n // Strategy 4: Unique name (0.75)\n // Simple name lookup, accept if exactly 1 candidate project-wide\n const candidates = registry.reverseLookup(calleeName);\n if (candidates.length === 1) {\n return { calleeId: candidates[0].id, confidence: 0.75, strategy: \"unique-name\" };\n }\n\n // Strategy 5: Suffix match (0.55)\n // Multiple candidates, select by suffix match with import-distance scoring\n if (candidates.length > 1) {\n // Prefer candidates in modules reachable from this file's imports\n const fileImports = importMap.getImportsForFile(callerFilePath);\n if (fileImports) {\n for (const c of candidates) {\n const cModule = registry.getModuleForFile(c.file_path);\n for (const [, impModule] of fileImports) {\n if (cModule === impModule || cModule.startsWith(`${impModule}/`)) {\n return { calleeId: c.id, confidence: 0.55, strategy: \"suffix-match\" };\n }\n }\n }\n }\n // Fallback: pick first candidate (nearest by file path similarity)\n const callerDir = callerFilePath.slice(0, callerFilePath.lastIndexOf(\"/\"));\n let best = candidates[0];\n let bestDist = Infinity;\n for (const c of candidates) {\n const cDir = c.file_path.slice(0, c.file_path.lastIndexOf(\"/\"));\n const dist = pathDistance(callerDir, cDir);\n if (dist < bestDist) {\n bestDist = dist;\n best = c;\n }\n }\n return { calleeId: best.id, confidence: 0.55, strategy: \"suffix-match\" };\n }\n\n // Strategy 6: Fuzzy (0.30-0.40)\n // String similarity — last resort\n const fuzzyMatch = fuzzyLookup(calleeName, registry);\n if (fuzzyMatch) {\n return { calleeId: fuzzyMatch.id, confidence: 0.35, strategy: \"fuzzy\" };\n }\n\n return { calleeId: null, confidence: 0, strategy: \"unresolved\" };\n}\n\n/** Names too short or builtin to bother resolving. */\nconst SKIP_NAMES = new Set([\n \"T\",\n \"L\",\n \"R\",\n \"S\",\n \"P\",\n \"C\",\n \"D\",\n \"E\",\n \"F\",\n \"M\",\n \"N\",\n \"V\",\n \"X\",\n \"Y\",\n \"String\",\n \"Array\",\n \"Object\",\n \"Number\",\n \"Boolean\",\n \"Map\",\n \"Set\",\n \"Date\",\n \"Error\",\n \"RegExp\",\n \"Promise\",\n \"Symbol\",\n \"Buffer\",\n \"Vec\",\n \"Box\",\n \"Option\",\n \"Result\",\n \"Some\",\n \"None\",\n \"Ok\",\n \"Err\",\n \"List\",\n \"Dict\",\n \"Tuple\",\n]);\n\n/** Check if a callee name should be skipped (too short or builtin type). */\nfunction shouldSkipResolution(calleeName: string): boolean {\n if (calleeName.length <= 2) return true;\n if (SKIP_NAMES.has(calleeName)) return true;\n // Skip single-letter + dot patterns like \"T.foo\"\n const parts = calleeName.split(\".\");\n if (parts.length >= 2 && parts[0].length <= 1) return true;\n return false;\n}\n\n/**\n * Run call resolution on all unresolved calls in the database.\n * Called after indexDirectory() completes.\n */\nexport function resolveAllCalls(db: Database): {\n total: number;\n resolved: number;\n byStrategy: Record<string, number>;\n} {\n // Create index for faster lookups (idempotent)\n try {\n db.exec(\"CREATE INDEX IF NOT EXISTS idx_calls_callee_name ON calls(callee_name)\");\n db.exec(\"CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)\");\n db.exec(\"CREATE INDEX IF NOT EXISTS idx_symbols_module ON symbols(module_path)\");\n } catch {}\n\n // Load all symbols into FunctionRegistry\n const symbols = db\n .prepare(\"SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols\")\n .all() as SymbolRow[];\n const registry = new FunctionRegistry(symbols);\n\n // Load all imports into ImportMap\n const imports = db\n .prepare(\"SELECT file_path, symbol_name, source_path FROM imports\")\n .all() as ImportRow[];\n const importMap = new ImportMap(imports);\n\n // Get all unresolved calls — skip short/builtin names to save resolution time\n const calls = db\n .prepare(\n \"SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL\",\n )\n .all() as CallRow[];\n\n const stats = { total: calls.length, resolved: 0, byStrategy: {} as Record<string, number> };\n let skipped = 0;\n\n // Get caller file paths — batch query\n const callerFiles = new Map<string, string>();\n const callerIds = [...new Set(calls.map((c) => c.caller_id))];\n if (callerIds.length > 0) {\n // Batch in chunks of 500 to avoid SQLite param limit\n for (let i = 0; i < callerIds.length; i += 500) {\n const chunk = callerIds.slice(i, i + 500);\n const placeholders = chunk.map(() => \"?\").join(\",\");\n const rows = db\n .prepare(`SELECT id, file_path FROM symbols WHERE id IN (${placeholders})`)\n .all(...chunk) as { id: string; file_path: string }[];\n for (const r of rows) {\n callerFiles.set(r.id, r.file_path);\n }\n }\n }\n\n // Resolve each call — skip short/builtin names\n const updateStmt = db.prepare(\"UPDATE calls SET callee_id = ?, confidence = ? WHERE id = ?\");\n const batch = db.transaction(() => {\n for (const call of calls) {\n if (shouldSkipResolution(call.callee_name)) {\n skipped++;\n continue;\n }\n const callerFile = callerFiles.get(call.caller_id) ?? \"\";\n const result = resolveCall(call.callee_name, callerFile, registry, importMap);\n if (result.calleeId) {\n updateStmt.run(result.calleeId, result.confidence, call.id);\n stats.resolved++;\n stats.byStrategy[result.strategy] = (stats.byStrategy[result.strategy] ?? 0) + 1;\n }\n }\n });\n batch();\n\n if (skipped > 0) {\n console.error(`[remem-mcp] resolveAllCalls: skipped ${skipped} short/builtin names`);\n }\n\n return stats;\n}\n\n/** Derive a module path from a file path: \"src/storage/sqlite.ts\" → \"src/storage/sqlite\" */\nfunction deriveModulePath(filePath: string): string {\n return filePath.replace(/\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|c|cpp|cc|cxx|hpp|cs)$/, \"\");\n}\n\n/** Calculate directory path distance for suffix-match scoring. */\nfunction pathDistance(a: string, b: string): number {\n if (a === b) return 0;\n const aParts = a.split(\"/\");\n const bParts = b.split(\"/\");\n let common = 0;\n for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {\n if (aParts[i] === bParts[i]) common++;\n else break;\n }\n return aParts.length + bParts.length - 2 * common;\n}\n\n/** Fuzzy lookup: find symbol with most similar name. */\nfunction fuzzyLookup(name: string, registry: FunctionRegistry): SymbolRow | null {\n let best: SymbolRow | null = null;\n let bestScore = 0;\n for (const [symName, candidates] of registry.reverseLookupEntries()) {\n const score = similarity(name, symName);\n if (score > bestScore && score > 0.6) {\n bestScore = score;\n best = candidates[0];\n }\n }\n return best;\n}\n\n/** Simple string similarity (Jaccard on character bigrams). */\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1;\n const aBigrams = bigrams(a);\n const bBigrams = bigrams(b);\n const intersection = aBigrams.filter((bg) => bBigrams.includes(bg)).length;\n const union = aBigrams.length + bBigrams.length - intersection;\n return union > 0 ? intersection / union : 0;\n}\n\nfunction bigrams(s: string): string[] {\n const result: string[] = [];\n for (let i = 0; i < s.length - 1; i++) {\n result.push(s.slice(i, i + 2));\n }\n return result;\n}\n"],"mappings":"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","names":["candidates"]}
package/dist/sdk.js CHANGED
@@ -1,14 +1,12 @@
1
1
  import {
2
- LocalEmbedder,
3
2
  Memory,
4
- SQLiteBackend,
5
3
  classifyGlobalContent,
6
4
  findOutdatedPages,
7
5
  getWikiPage,
8
6
  ingestDirectory,
9
7
  ingestFile,
10
8
  searchWiki
11
- } from "./chunk-X4ECGLYH.js";
9
+ } from "./chunk-TGLOMFIE.js";
12
10
  import {
13
11
  SUPPORTED_LANGUAGES,
14
12
  detectLanguage,
@@ -19,8 +17,13 @@ import {
19
17
  indexFile,
20
18
  listSymbols,
21
19
  searchSymbols
22
- } from "./chunk-FA324IBN.js";
23
- import "./chunk-XK4Y53J2.js";
20
+ } from "./chunk-ZXI67YIV.js";
21
+ import {
22
+ LocalEmbedder,
23
+ SQLiteBackend
24
+ } from "./chunk-OYUNKUZK.js";
25
+ import "./chunk-XJGDYQ34.js";
26
+ import "./chunk-ONQHLUGM.js";
24
27
  export {
25
28
  LocalEmbedder,
26
29
  Memory,
@@ -0,0 +1,517 @@
1
+ import {
2
+ LocalEmbedder,
3
+ SQLiteBackend,
4
+ init_local
5
+ } from "./chunk-OYUNKUZK.js";
6
+ import {
7
+ generateId,
8
+ init_ulid
9
+ } from "./chunk-XJGDYQ34.js";
10
+ import {
11
+ init_esm_shims
12
+ } from "./chunk-ONQHLUGM.js";
13
+
14
+ // src/proxy/server.ts
15
+ init_esm_shims();
16
+ init_local();
17
+ import {
18
+ createServer,
19
+ request as httpRequestHttp
20
+ } from "http";
21
+ import { request as httpRequest } from "https";
22
+ import { URL } from "url";
23
+
24
+ // src/proxy/capture.ts
25
+ init_esm_shims();
26
+ init_ulid();
27
+ async function captureConversation(messages, assistantResponse, storage, binding) {
28
+ const userMessages = messages.filter((m) => m.role === "user");
29
+ if (userMessages.length === 0) return;
30
+ const lastUser = userMessages[userMessages.length - 1];
31
+ const content = `User: ${lastUser.content.slice(0, 500)}
32
+
33
+ Assistant: ${assistantResponse.slice(0, 2e3)}`;
34
+ const id = generateId();
35
+ const now = Date.now();
36
+ await storage.put({
37
+ id,
38
+ sessionKey: `proxy-${binding.teamId ?? "default"}`,
39
+ agentId: binding.agentId ?? "proxy",
40
+ type: "conversation",
41
+ content,
42
+ tags: ["proxy", "conversation"],
43
+ createdAt: now,
44
+ metadata: {
45
+ source: "proxy",
46
+ teamId: binding.teamId,
47
+ agentId: binding.agentId,
48
+ userId: binding.userId,
49
+ taskId: binding.taskId
50
+ },
51
+ teamId: binding.teamId,
52
+ userId: binding.userId,
53
+ taskId: binding.taskId
54
+ });
55
+ }
56
+ async function captureConversationAnthropic(messages, assistantResponse, storage, binding) {
57
+ const simpleMessages = messages.map((m) => ({
58
+ role: m.role,
59
+ content: typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n") : ""
60
+ }));
61
+ await captureConversation(simpleMessages, assistantResponse, storage, binding);
62
+ }
63
+
64
+ // src/proxy/inject.ts
65
+ init_esm_shims();
66
+ async function injectMemory(messages, userMessage, storage, embedder, binding) {
67
+ let queryEmbedding = null;
68
+ try {
69
+ queryEmbedding = await embedder.embed(userMessage);
70
+ console.error(`[proxy inject] embedding: ${queryEmbedding?.length ?? 0} dims`);
71
+ } catch (e) {
72
+ console.error(`[proxy inject] embed error: ${e}`);
73
+ }
74
+ let results = [];
75
+ try {
76
+ const searchResults = await storage.search(userMessage, queryEmbedding, {
77
+ sessionKey: "",
78
+ limit: 5,
79
+ offset: 0,
80
+ mode: "hybrid",
81
+ filters: {
82
+ teamId: binding.teamId,
83
+ agentId: binding.agentId,
84
+ userId: binding.userId
85
+ }
86
+ });
87
+ results = searchResults.map((r) => ({
88
+ id: r.entry.id,
89
+ content: r.entry.content,
90
+ type: r.entry.type,
91
+ tags: r.entry.tags,
92
+ score: r.score
93
+ }));
94
+ console.error(`[proxy inject] search: ${results.length} results`);
95
+ } catch (e) {
96
+ console.error(`[proxy inject] search error: ${e}`);
97
+ }
98
+ const atoms = [];
99
+ for (const result of results.slice(0, 3)) {
100
+ try {
101
+ const captureAtoms = await storage.listAtoms({ captureId: result.id, limit: 3 });
102
+ for (const atom of captureAtoms) {
103
+ atoms.push(atom.fact);
104
+ }
105
+ } catch {
106
+ }
107
+ }
108
+ let scenarios = [];
109
+ if (binding.teamId) {
110
+ try {
111
+ const allScenarios = await storage.listScenarios({
112
+ teamId: binding.teamId,
113
+ agentId: binding.agentId,
114
+ limit: 2
115
+ });
116
+ scenarios = allScenarios.map((s) => s.summary);
117
+ } catch {
118
+ }
119
+ }
120
+ let persona = null;
121
+ if (binding.teamId && binding.agentId && binding.userId) {
122
+ try {
123
+ const p = await storage.readPersona(binding.teamId, binding.agentId, binding.userId);
124
+ persona = p?.content ?? null;
125
+ } catch {
126
+ }
127
+ }
128
+ let skills = [];
129
+ if (binding.teamId && binding.agentId) {
130
+ try {
131
+ const matchedSkills = await storage.searchSkills(
132
+ binding.teamId,
133
+ binding.agentId,
134
+ userMessage,
135
+ 2
136
+ );
137
+ skills = matchedSkills.map((s) => `**${s.name}**: ${s.description ?? ""}`);
138
+ } catch {
139
+ }
140
+ }
141
+ let canvasBlock = null;
142
+ const offloadOn = process.env.REMEM_OFFLOAD_ENABLED === "true" || process.env.REMEM_OFFLOAD_ENABLED === "1" || process.env.REMEM_FLOW === "full";
143
+ if (offloadOn) {
144
+ try {
145
+ const sessionKey = `proxy-${binding.teamId ?? "default"}`;
146
+ const canvasRow = storage.db.prepare(
147
+ "SELECT mermaid_text, node_count FROM canvases WHERE session_key = ? ORDER BY updated_at DESC LIMIT 1"
148
+ ).get(sessionKey);
149
+ if (canvasRow && canvasRow.mermaid_text && canvasRow.node_count > 0) {
150
+ canvasBlock = `### Task Canvas (${canvasRow.node_count} steps)
151
+ \`\`\`mermaid
152
+ ${canvasRow.mermaid_text}
153
+ \`\`\``;
154
+ }
155
+ } catch {
156
+ }
157
+ }
158
+ const memoryBlock = buildMemoryBlock(results, atoms, scenarios, persona, skills, canvasBlock);
159
+ if (!memoryBlock) return messages;
160
+ const systemIdx = messages.findIndex((m) => m.role === "system");
161
+ if (systemIdx >= 0) {
162
+ const newMessages = [...messages];
163
+ newMessages[systemIdx] = {
164
+ ...messages[systemIdx],
165
+ content: `${messages[systemIdx].content}
166
+
167
+ ${memoryBlock}`
168
+ };
169
+ return newMessages;
170
+ }
171
+ return [{ role: "system", content: memoryBlock }, ...messages];
172
+ }
173
+ function buildMemoryBlock(results, atoms, scenarios, persona, skills, canvasBlock) {
174
+ const sections = [];
175
+ if (atoms.length > 0) {
176
+ sections.push(`### Relevant Facts
177
+ ${atoms.map((a) => `- ${a}`).join("\n")}`);
178
+ }
179
+ if (results.length > 0) {
180
+ const captures = results.slice(0, 3).map((r) => `- [${r.type}] ${r.content.slice(0, 200)}`);
181
+ sections.push(`### Past Memories
182
+ ${captures.join("\n")}`);
183
+ }
184
+ if (scenarios.length > 0) {
185
+ sections.push(`### Scenarios
186
+ ${scenarios.map((s) => `- ${s}`).join("\n")}`);
187
+ }
188
+ if (persona) {
189
+ sections.push(`### User Profile
190
+ ${persona}`);
191
+ }
192
+ if (skills.length > 0) {
193
+ sections.push(`### Relevant Skills
194
+ ${skills.map((s) => `- ${s}`).join("\n")}`);
195
+ }
196
+ if (canvasBlock) {
197
+ sections.push(canvasBlock);
198
+ }
199
+ if (sections.length === 0) return null;
200
+ return `<remem-mcp>
201
+ ${sections.join("\n\n")}
202
+ </remem-mcp>`;
203
+ }
204
+
205
+ // src/proxy/session.ts
206
+ init_esm_shims();
207
+ import { randomUUID } from "crypto";
208
+ var SessionStore = class {
209
+ sessions = /* @__PURE__ */ new Map();
210
+ /** Create a new session with the given binding. */
211
+ create(binding) {
212
+ const sessionId = randomUUID();
213
+ this.sessions.set(sessionId, binding);
214
+ return sessionId;
215
+ }
216
+ /** Get the binding for a session. */
217
+ get(sessionId) {
218
+ return this.sessions.get(sessionId) ?? null;
219
+ }
220
+ /** Get the binding from request headers (x-remem-session or x-tdai-user-key). */
221
+ getFromHeaders(headers) {
222
+ const sessionId = headers["x-remem-session"];
223
+ if (sessionId) {
224
+ const binding = this.sessions.get(sessionId);
225
+ if (binding) return binding;
226
+ }
227
+ return {
228
+ teamId: headers["x-remem-team"],
229
+ agentId: headers["x-remem-agent"],
230
+ taskId: headers["x-remem-task"],
231
+ userId: headers["x-remem-user"]
232
+ };
233
+ }
234
+ /** Delete a session. */
235
+ delete(sessionId) {
236
+ this.sessions.delete(sessionId);
237
+ }
238
+ /** Clear all sessions. */
239
+ clear() {
240
+ this.sessions.clear();
241
+ }
242
+ };
243
+
244
+ // src/proxy/server.ts
245
+ function loadProxyConfig() {
246
+ return {
247
+ port: parseInt(process.env.REMEM_PROXY_PORT ?? "8765", 10),
248
+ upstreamUrl: process.env.REMEM_UPSTREAM_URL ?? "https://api.openai.com",
249
+ upstreamApiKey: process.env.REMEM_UPSTREAM_API_KEY ?? process.env.OPENAI_API_KEY,
250
+ dbPath: process.env.REMEM_DB_PATH ?? "",
251
+ autoCapture: process.env.REMEM_PROXY_CAPTURE !== "false",
252
+ injectMemory: process.env.REMEM_PROXY_INJECT !== "false"
253
+ };
254
+ }
255
+ async function startProxyServer(config) {
256
+ if (!config.dbPath) {
257
+ throw new Error("REMEM_DB_PATH is required for proxy mode");
258
+ }
259
+ const storage = new SQLiteBackend(config.dbPath);
260
+ const embedder = new LocalEmbedder();
261
+ const sessions = new SessionStore();
262
+ const server = createServer(async (req, res) => {
263
+ try {
264
+ const url = new URL(req.url ?? "/", `http://localhost:${config.port}`);
265
+ if (url.pathname === "/health" && req.method === "GET") {
266
+ res.writeHead(200, { "Content-Type": "application/json" });
267
+ res.end(JSON.stringify({ status: "ok", proxy: true, upstream: config.upstreamUrl }));
268
+ return;
269
+ }
270
+ if (url.pathname === "/session/init" && req.method === "POST") {
271
+ const body = await readBody(req);
272
+ const { teamId, agentId, taskId, userId } = JSON.parse(body);
273
+ const sessionId = sessions.create({ teamId, agentId, taskId, userId });
274
+ res.writeHead(200, { "Content-Type": "application/json" });
275
+ res.end(JSON.stringify({ sessionId }));
276
+ return;
277
+ }
278
+ if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
279
+ await handleOpenAIRequest(req, res, config, storage, embedder, sessions);
280
+ return;
281
+ }
282
+ if (url.pathname === "/v1/messages" && req.method === "POST") {
283
+ await handleAnthropicRequest(req, res, config, storage, embedder, sessions);
284
+ return;
285
+ }
286
+ await passthrough(req, res, config);
287
+ } catch (err) {
288
+ console.error("[remem-mcp proxy] Error:", err);
289
+ res.writeHead(500, { "Content-Type": "application/json" });
290
+ res.end(JSON.stringify({ error: "Proxy internal error", detail: String(err) }));
291
+ }
292
+ });
293
+ server.listen(config.port, () => {
294
+ console.log(`[remem-mcp proxy] Listening on http://localhost:${config.port}`);
295
+ console.log(`[remem-mcp proxy] Upstream: ${config.upstreamUrl}`);
296
+ console.log(`[remem-mcp proxy] Endpoints:`);
297
+ console.log(` POST /v1/chat/completions (OpenAI protocol)`);
298
+ console.log(` POST /v1/messages (Anthropic protocol)`);
299
+ console.log(` POST /session/init (bind team/agent/task)`);
300
+ console.log(` GET /health (health check)`);
301
+ });
302
+ }
303
+ async function handleOpenAIRequest(req, res, config, storage, embedder, sessions) {
304
+ const body = await readBody(req);
305
+ const parsed = JSON.parse(body);
306
+ if (config.injectMemory) {
307
+ const lastUserMessage = getLastUserMessage(parsed.messages ?? []);
308
+ const sessionBinding = sessions.getFromHeaders(req.headers);
309
+ console.error(
310
+ `[proxy] inject: userMsg="${lastUserMessage?.slice(0, 50)}" binding=${JSON.stringify(sessionBinding)}`
311
+ );
312
+ if (lastUserMessage) {
313
+ try {
314
+ const injected = await injectMemory(
315
+ parsed.messages ?? [],
316
+ lastUserMessage,
317
+ storage,
318
+ embedder,
319
+ sessionBinding
320
+ );
321
+ const hasMemory = injected.some((m) => m.content.includes("<remem-mcp>"));
322
+ console.error(`[proxy] inject result: hasMemory=${hasMemory}, messages=${injected.length}`);
323
+ parsed.messages = injected;
324
+ } catch (e) {
325
+ console.error(`[proxy] inject error:`, e);
326
+ }
327
+ }
328
+ }
329
+ const upstreamResp = await forwardRequest(
330
+ config.upstreamUrl,
331
+ "/v1/chat/completions",
332
+ "POST",
333
+ JSON.stringify(parsed),
334
+ config.upstreamApiKey,
335
+ req.headers
336
+ );
337
+ if (config.autoCapture) {
338
+ const responseText = extractOpenAIResponse(upstreamResp.body);
339
+ if (responseText) {
340
+ captureConversation(
341
+ parsed.messages ?? [],
342
+ responseText,
343
+ storage,
344
+ sessions.getFromHeaders(req.headers)
345
+ ).catch((e) => console.error("[remem-mcp proxy] Capture error:", e));
346
+ }
347
+ }
348
+ res.writeHead(upstreamResp.statusCode, upstreamResp.headers);
349
+ res.end(upstreamResp.body);
350
+ }
351
+ async function handleAnthropicRequest(req, res, config, storage, embedder, sessions) {
352
+ const body = await readBody(req);
353
+ const parsed = JSON.parse(body);
354
+ if (config.injectMemory) {
355
+ const lastUserMessage = getLastUserMessageAnthropic(parsed.messages ?? []);
356
+ const sessionBinding = sessions.getFromHeaders(req.headers);
357
+ if (lastUserMessage) {
358
+ const systemPrompt = typeof parsed.system === "string" ? parsed.system : "";
359
+ const injected = await injectMemoryAnthropic(
360
+ systemPrompt,
361
+ lastUserMessage,
362
+ storage,
363
+ embedder,
364
+ sessionBinding
365
+ );
366
+ parsed.system = injected;
367
+ }
368
+ }
369
+ const upstreamResp = await forwardRequest(
370
+ config.upstreamUrl,
371
+ "/v1/messages",
372
+ "POST",
373
+ JSON.stringify(parsed),
374
+ config.upstreamApiKey,
375
+ req.headers
376
+ );
377
+ if (config.autoCapture) {
378
+ const responseText = extractAnthropicResponse(upstreamResp.body);
379
+ if (responseText) {
380
+ captureConversationAnthropic(
381
+ parsed.messages ?? [],
382
+ responseText,
383
+ storage,
384
+ sessions.getFromHeaders(req.headers)
385
+ ).catch((e) => console.error("[remem-mcp proxy] Capture error:", e));
386
+ }
387
+ }
388
+ res.writeHead(upstreamResp.statusCode, upstreamResp.headers);
389
+ res.end(upstreamResp.body);
390
+ }
391
+ async function passthrough(req, res, config) {
392
+ const url = new URL(req.url ?? "/", `http://localhost:${config.port}`);
393
+ const upstreamResp = await forwardRequest(
394
+ config.upstreamUrl,
395
+ url.pathname + url.search,
396
+ req.method ?? "GET",
397
+ await readBody(req),
398
+ config.upstreamApiKey,
399
+ req.headers
400
+ );
401
+ res.writeHead(upstreamResp.statusCode, upstreamResp.headers);
402
+ res.end(upstreamResp.body);
403
+ }
404
+ function readBody(req) {
405
+ return new Promise((resolve, reject) => {
406
+ const chunks = [];
407
+ req.on("data", (c) => chunks.push(c));
408
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
409
+ req.on("error", reject);
410
+ });
411
+ }
412
+ function getLastUserMessage(messages) {
413
+ for (let i = messages.length - 1; i >= 0; i--) {
414
+ if (messages[i].role === "user") {
415
+ return typeof messages[i].content === "string" ? messages[i].content : JSON.stringify(messages[i].content);
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+ function getLastUserMessageAnthropic(messages) {
421
+ for (let i = messages.length - 1; i >= 0; i--) {
422
+ if (messages[i].role === "user") {
423
+ const content = messages[i].content;
424
+ if (typeof content === "string") return content;
425
+ if (Array.isArray(content)) {
426
+ const textBlock = content.find((b) => b.type === "text");
427
+ if (textBlock?.text) return textBlock.text;
428
+ }
429
+ }
430
+ }
431
+ return null;
432
+ }
433
+ function extractOpenAIResponse(body) {
434
+ try {
435
+ const parsed = JSON.parse(body);
436
+ return parsed.choices?.[0]?.message?.content ?? null;
437
+ } catch {
438
+ return null;
439
+ }
440
+ }
441
+ function extractAnthropicResponse(body) {
442
+ try {
443
+ const parsed = JSON.parse(body);
444
+ const textBlocks = parsed.content?.filter((b) => b.type === "text") ?? [];
445
+ return textBlocks.map((b) => b.text).join("\n") || null;
446
+ } catch {
447
+ return null;
448
+ }
449
+ }
450
+ async function forwardRequest(upstreamUrl, path, method, body, apiKey, headers) {
451
+ const url = new URL(path, upstreamUrl);
452
+ const isHttps = url.protocol === "https:";
453
+ const requestFn = isHttps ? httpRequest : httpRequestHttp;
454
+ const forwardHeaders = {
455
+ "Content-Type": "application/json",
456
+ "Content-Length": Buffer.byteLength(body).toString()
457
+ };
458
+ if (headers) {
459
+ for (const [key, value] of Object.entries(headers)) {
460
+ const lower = key.toLowerCase();
461
+ if (lower === "authorization" || lower === "x-api-key" || lower === "anthropic-version" || lower === "openai-organization" || lower === "accept" || lower === "accept-encoding") {
462
+ if (typeof value === "string") {
463
+ forwardHeaders[key] = value;
464
+ }
465
+ }
466
+ }
467
+ }
468
+ if (apiKey) {
469
+ if (path.startsWith("/v1/messages")) {
470
+ forwardHeaders["x-api-key"] = apiKey;
471
+ } else {
472
+ forwardHeaders["Authorization"] = `Bearer ${apiKey}`;
473
+ }
474
+ }
475
+ return new Promise((resolve, reject) => {
476
+ const proxyReq = requestFn(url, { method, headers: forwardHeaders }, (proxyRes) => {
477
+ const chunks = [];
478
+ proxyRes.on("data", (c) => chunks.push(c));
479
+ proxyRes.on("end", () => {
480
+ const respHeaders = {};
481
+ for (const [k, v] of Object.entries(proxyRes.headers)) {
482
+ if (typeof v === "string") respHeaders[k] = v;
483
+ }
484
+ resolve({
485
+ statusCode: proxyRes.statusCode ?? 200,
486
+ headers: respHeaders,
487
+ body: Buffer.concat(chunks).toString("utf-8")
488
+ });
489
+ });
490
+ proxyRes.on("error", reject);
491
+ });
492
+ proxyReq.on("error", reject);
493
+ proxyReq.write(body);
494
+ proxyReq.end();
495
+ });
496
+ }
497
+ async function injectMemoryAnthropic(systemPrompt, userMessage, storage, embedder, sessionBinding) {
498
+ const memoryBlock = await injectMemory([], userMessage, storage, embedder, sessionBinding);
499
+ const memoryText = extractMemoryBlock(memoryBlock);
500
+ if (!memoryText) return systemPrompt;
501
+ return `${systemPrompt}
502
+
503
+ ${memoryText}`;
504
+ }
505
+ function extractMemoryBlock(messages) {
506
+ for (const msg of messages) {
507
+ if (msg.role === "system" && msg.content.includes("<remem-mcp>")) {
508
+ return msg.content;
509
+ }
510
+ }
511
+ return null;
512
+ }
513
+ export {
514
+ loadProxyConfig,
515
+ startProxyServer
516
+ };
517
+ //# sourceMappingURL=server-SUKPVQWL.js.map