@melandlabs/memory-store 0.1.6 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/{config-D6MzksfT.d.ts → config-BYfTzG3h.d.ts} +17 -0
  2. package/dist/contracts.d.ts +7 -0
  3. package/dist/contracts.js +0 -2
  4. package/dist/http.d.ts +1 -1
  5. package/dist/http.js +355 -67
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +287 -52
  8. package/dist/mcp.d.ts +1 -1
  9. package/dist/mcp.js +313 -60
  10. package/dist/policies/memory-graph-correction-policy.js +0 -2
  11. package/dist/policies/memory-graph-write-policy.js +0 -2
  12. package/dist/search/unified-search.d.ts +39 -3
  13. package/dist/search/unified-search.js +311 -56
  14. package/dist/server/cli-http.js +360 -66
  15. package/dist/server/cli-mcp.js +318 -59
  16. package/dist/server/cli-shared.d.ts +1 -1
  17. package/dist/server/cli-shared.js +0 -2
  18. package/dist/storage/chroma-memory-index.js +0 -2
  19. package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
  20. package/dist/storage/postgres-raw-message-factory.js +0 -2
  21. package/dist/storage/raw-message-store.d.ts +9 -1
  22. package/dist/storage/raw-message-store.js +0 -2
  23. package/dist/storage/sqlite-raw-message-store.d.ts +15 -2
  24. package/dist/storage/sqlite-raw-message-store.js +8 -3
  25. package/dist/storage/sqlite-vector-index.d.ts +1 -1
  26. package/dist/storage/sqlite-vector-index.js +0 -2
  27. package/package.json +81 -93
  28. package/dist/contracts.js.map +0 -1
  29. package/dist/http.js.map +0 -1
  30. package/dist/index.js.map +0 -1
  31. package/dist/mcp.js.map +0 -1
  32. package/dist/policies/memory-graph-correction-policy.js.map +0 -1
  33. package/dist/policies/memory-graph-write-policy.js.map +0 -1
  34. package/dist/search/unified-search.js.map +0 -1
  35. package/dist/server/cli-http.js.map +0 -1
  36. package/dist/server/cli-mcp.js.map +0 -1
  37. package/dist/server/cli-shared.js.map +0 -1
  38. package/dist/storage/chroma-memory-index.js.map +0 -1
  39. package/dist/storage/postgres-raw-message-factory.js.map +0 -1
  40. package/dist/storage/raw-message-store.js.map +0 -1
  41. package/dist/storage/sqlite-raw-message-store.js.map +0 -1
  42. package/dist/storage/sqlite-vector-index.js.map +0 -1
@@ -1,13 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { z } from 'zod';
5
2
  import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
6
3
  import { ChromaVectorStore } from '@melandlabs/rag';
7
4
  import { mkdirSync } from 'fs';
8
5
  import { homedir } from 'os';
9
- import { join, dirname } from 'path';
6
+ import { dirname, join } from 'path';
10
7
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { z } from 'zod';
11
+
12
+ var __defProp = Object.defineProperty;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __esm = (fn, res) => function __init() {
15
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
+ };
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
+ };
11
21
 
12
22
  // src/contracts.ts
13
23
  function normalizeTimestampToMs(value) {
@@ -31,14 +41,10 @@ function rawMessageToMemoryRecord(message) {
31
41
  embeddingContentHash: message.embeddingContentHash
32
42
  };
33
43
  }
34
-
35
- // src/storage/chroma-memory-index.ts
36
- var DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
37
- var RAW_VECTOR_BACKEND_ENV_KEYS = [
38
- "RAW_MESSAGE_VECTOR_STORE_BACKEND",
39
- "MEMORY_VECTOR_STORE_BACKEND",
40
- "VECTOR_STORE_BACKEND"
41
- ];
44
+ var init_contracts = __esm({
45
+ "src/contracts.ts"() {
46
+ }
47
+ });
42
48
  function getBackend(keys) {
43
49
  for (const key of keys) {
44
50
  const value = process.env[key]?.trim().toLowerCase();
@@ -141,21 +147,51 @@ async function searchRawMessagesWithChroma(input) {
141
147
  metadata: result.metadata ?? {}
142
148
  })).slice(0, input.limit);
143
149
  }
150
+ var DEFAULT_RAW_MESSAGES_COLLECTION, RAW_VECTOR_BACKEND_ENV_KEYS;
151
+ var init_chroma_memory_index = __esm({
152
+ "src/storage/chroma-memory-index.ts"() {
153
+ init_contracts();
154
+ DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
155
+ RAW_VECTOR_BACKEND_ENV_KEYS = [
156
+ "RAW_MESSAGE_VECTOR_STORE_BACKEND",
157
+ "MEMORY_VECTOR_STORE_BACKEND",
158
+ "VECTOR_STORE_BACKEND"
159
+ ];
160
+ }
161
+ });
144
162
 
145
163
  // src/storage/postgres-raw-message-factory.ts
146
- var factory = null;
147
164
  async function resolvePostgresFactory(env) {
148
- return null;
165
+ if (!factory) return null;
166
+ return await factory(env);
149
167
  }
150
168
  function hasPostgresFactory() {
151
169
  return factory !== null;
152
170
  }
153
- var manager = null;
154
- var DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
171
+ var factory;
172
+ var init_postgres_raw_message_factory = __esm({
173
+ "src/storage/postgres-raw-message-factory.ts"() {
174
+ factory = null;
175
+ }
176
+ });
177
+
178
+ // src/storage/sqlite-raw-message-store.ts
179
+ var sqlite_raw_message_store_exports = {};
180
+ __export(sqlite_raw_message_store_exports, {
181
+ __resetSQLiteRawMessageManagerForTests: () => __resetSQLiteRawMessageManagerForTests,
182
+ closeSQLiteRawMessageManager: () => closeSQLiteRawMessageManager,
183
+ getSQLiteRawMessageManager: () => getSQLiteRawMessageManager,
184
+ isSQLiteRawMessageStorageAvailable: () => isSQLiteRawMessageStorageAvailable,
185
+ lexicalSearchRawMessages: () => lexicalSearchRawMessages,
186
+ resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
187
+ });
155
188
  function resolveSQLiteRawMessageDbPath() {
156
189
  const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
157
190
  return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
158
191
  }
192
+ function isSQLiteRawMessageStorageAvailable(_env) {
193
+ return true;
194
+ }
159
195
  async function getSQLiteRawMessageManager(_env) {
160
196
  if (!manager) {
161
197
  const dbPath = resolveSQLiteRawMessageDbPath();
@@ -175,8 +211,35 @@ async function closeSQLiteRawMessageManager() {
175
211
  await manager.close();
176
212
  manager = null;
177
213
  }
214
+ function __resetSQLiteRawMessageManagerForTests() {
215
+ manager = null;
216
+ }
217
+ async function lexicalSearchRawMessages(input) {
218
+ const mgr = await getSQLiteRawMessageManager();
219
+ if (typeof mgr.lexicalSearchMessages !== "function") {
220
+ return [];
221
+ }
222
+ return mgr.lexicalSearchMessages(input);
223
+ }
224
+ var manager, DEFAULT_SQLITE_PATH;
225
+ var init_sqlite_raw_message_store = __esm({
226
+ "src/storage/sqlite-raw-message-store.ts"() {
227
+ init_chroma_memory_index();
228
+ manager = null;
229
+ DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
230
+ }
231
+ });
178
232
 
179
233
  // src/storage/raw-message-store.ts
234
+ var raw_message_store_exports = {};
235
+ __export(raw_message_store_exports, {
236
+ closeRawMessageStore: () => closeRawMessageStore,
237
+ configureRawMessageStore: () => configureRawMessageStore,
238
+ createRawMessageStore: () => createRawMessageStore,
239
+ getRawMessageManager: () => getRawMessageManager,
240
+ getRawMessageStorageBackend: () => getRawMessageStorageBackend,
241
+ isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
242
+ });
180
243
  function resolveBackend(env) {
181
244
  const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
182
245
  if (explicit === "postgres") return "postgres";
@@ -186,7 +249,7 @@ function resolveEnv(_env) {
186
249
  return {};
187
250
  }
188
251
  function createRawMessageStore(options = {}) {
189
- resolveEnv(options.env);
252
+ const env = resolveEnv(options.env);
190
253
  return {
191
254
  getBackend() {
192
255
  return resolveBackend();
@@ -196,7 +259,7 @@ function createRawMessageStore(options = {}) {
196
259
  },
197
260
  async getManager() {
198
261
  if (resolveBackend() === "postgres") {
199
- const pg = await resolvePostgresFactory();
262
+ const pg = await resolvePostgresFactory(env);
200
263
  if (!pg) {
201
264
  throw new Error(
202
265
  "OPENCONTEXT_MEMORY_STORE_BACKEND=postgres but no Postgres factory is registered. Call registerPostgresFactory() at startup, or unset the env var to use the default SQLite backend."
@@ -211,14 +274,47 @@ function createRawMessageStore(options = {}) {
211
274
  }
212
275
  };
213
276
  }
214
- var moduleStore = null;
215
- var moduleConfig = null;
277
+ function configureRawMessageStore(config) {
278
+ moduleConfig = config;
279
+ moduleStore = createRawMessageStore({ env: config.env });
280
+ return moduleStore;
281
+ }
282
+ function getRawMessageStorageBackend() {
283
+ if (!moduleStore) {
284
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
285
+ }
286
+ return moduleStore.getBackend();
287
+ }
216
288
  function isRawMessageStorageAvailable() {
217
289
  if (!moduleStore) {
218
290
  moduleStore = createRawMessageStore({ env: moduleConfig?.env });
219
291
  }
220
292
  return moduleStore.isAvailable();
221
293
  }
294
+ async function getRawMessageManager() {
295
+ if (!moduleStore) {
296
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
297
+ }
298
+ return moduleStore.getManager();
299
+ }
300
+ async function closeRawMessageStore() {
301
+ if (!moduleStore) return;
302
+ await moduleStore.close();
303
+ moduleStore = null;
304
+ }
305
+ var moduleStore, moduleConfig;
306
+ var init_raw_message_store = __esm({
307
+ "src/storage/raw-message-store.ts"() {
308
+ init_postgres_raw_message_factory();
309
+ init_sqlite_raw_message_store();
310
+ moduleStore = null;
311
+ moduleConfig = null;
312
+ }
313
+ });
314
+
315
+ // src/search/unified-search.ts
316
+ init_chroma_memory_index();
317
+ init_raw_message_store();
222
318
 
223
319
  // src/search/utilities.ts
224
320
  var DEFAULT_LIMIT = 10;
@@ -248,7 +344,14 @@ function clampUnifiedMemorySearchThreshold(threshold) {
248
344
  }
249
345
  return Math.min(1, Math.max(-1, parsed));
250
346
  }
251
- function mergeUnifiedMemorySearchResults(results, limit) {
347
+ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
348
+ const strategy = normalizeUnifiedMemoryMergeStrategy(options.strategy);
349
+ if (strategy === "rrf") {
350
+ const lists = options.rankedLists ?? [
351
+ { name: "memory-semantic", hits: results }
352
+ ];
353
+ return mergeUnifiedMemorySearchResultsRrf(lists, limit);
354
+ }
252
355
  return [...results].sort((a, b) => {
253
356
  const scoreDelta = b.similarity - a.similarity;
254
357
  if (scoreDelta !== 0) {
@@ -257,6 +360,42 @@ function mergeUnifiedMemorySearchResults(results, limit) {
257
360
  return a.type.localeCompare(b.type) || a.id.localeCompare(b.id);
258
361
  }).slice(0, limit);
259
362
  }
363
+ function normalizeUnifiedMemoryMergeStrategy(value) {
364
+ return value === "rrf" ? "rrf" : "similarity";
365
+ }
366
+ var DEFAULT_RRF_K = 60;
367
+ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
368
+ if (!Array.isArray(lists) || lists.length === 0) {
369
+ return [];
370
+ }
371
+ const safeK = Number.isFinite(k) && k > 0 ? k : DEFAULT_RRF_K;
372
+ const scores = /* @__PURE__ */ new Map();
373
+ const order = [];
374
+ for (const list of lists) {
375
+ for (let index = 0; index < list.hits.length; index += 1) {
376
+ const hit = list.hits[index];
377
+ const key = `${hit.type}::${hit.id}`;
378
+ const contribution = 1 / (safeK + index + 1);
379
+ const existing = scores.get(key);
380
+ if (existing) {
381
+ existing.rrf += contribution;
382
+ } else {
383
+ scores.set(key, { hit, rrf: contribution });
384
+ order.push(key);
385
+ }
386
+ }
387
+ }
388
+ const ranked = order.map((key) => scores.get(key)).sort((a, b) => {
389
+ if (b.rrf !== a.rrf) {
390
+ return b.rrf - a.rrf;
391
+ }
392
+ return a.hit.type.localeCompare(b.hit.type) || a.hit.id.localeCompare(b.hit.id);
393
+ }).slice(0, limit);
394
+ return ranked.map(({ hit, rrf }) => ({
395
+ ...hit,
396
+ metadata: { ...hit.metadata, rrfScore: rrf }
397
+ }));
398
+ }
260
399
  function toKnowledgeResult(result) {
261
400
  return {
262
401
  type: "knowledge",
@@ -288,6 +427,9 @@ function isRawMemorySemanticResult(result) {
288
427
  }
289
428
 
290
429
  // src/search/unified-search.ts
430
+ function deriveLexicalKeywords(query) {
431
+ return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
432
+ }
291
433
  function createUnifiedSearch(deps = {}) {
292
434
  const logger = deps.logger ?? console;
293
435
  async function searchUnifiedMemory(input) {
@@ -295,8 +437,8 @@ function createUnifiedSearch(deps = {}) {
295
437
  const sources = normalizeUnifiedMemorySearchSources(input.sources);
296
438
  const limit = clampUnifiedMemorySearchLimit(input.limit);
297
439
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
440
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
298
441
  const warnings = [];
299
- const results = [];
300
442
  if (!query) {
301
443
  return {
302
444
  query,
@@ -306,17 +448,17 @@ function createUnifiedSearch(deps = {}) {
306
448
  warnings
307
449
  };
308
450
  }
451
+ let memorySubs;
309
452
  if (sources.includes("memory")) {
310
453
  if (isRawMessageStorageAvailable()) {
311
454
  try {
312
- const memoryHits = await runMemorySource(input, limit, threshold);
313
- results.push(...memoryHits);
455
+ memorySubs = await runMemorySource(input, limit, threshold, warnings);
314
456
  } catch (error) {
315
457
  logger.warn?.("[memory-store] memory source failed:", error);
316
458
  warnings.push({
317
459
  source: "memory",
318
460
  code: "memory_search_failed",
319
- message: error.message ?? "memory_search_failed"
461
+ message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
320
462
  });
321
463
  }
322
464
  } else {
@@ -327,9 +469,10 @@ function createUnifiedSearch(deps = {}) {
327
469
  });
328
470
  }
329
471
  }
472
+ const insightHits = [];
330
473
  if (sources.includes("insights") && typeof deps.searchInsights === "function") {
331
474
  try {
332
- const insightHits = await deps.searchInsights({
475
+ const hits = await deps.searchInsights({
333
476
  userId: input.userId,
334
477
  query: input.query,
335
478
  limit,
@@ -338,8 +481,8 @@ function createUnifiedSearch(deps = {}) {
338
481
  includeArchived: input.includeArchivedInsights,
339
482
  authToken: input.authToken
340
483
  });
341
- for (const hit of insightHits) {
342
- results.push({
484
+ for (const hit of hits) {
485
+ insightHits.push({
343
486
  type: "insight",
344
487
  id: hit.id,
345
488
  content: hit.content,
@@ -355,16 +498,11 @@ function createUnifiedSearch(deps = {}) {
355
498
  message: error.message ?? "insights_search_failed"
356
499
  });
357
500
  }
358
- } else if (sources.includes("insights")) {
359
- warnings.push({
360
- source: "insights",
361
- code: "insights_search_not_configured",
362
- message: "Insights search is not configured for this host."
363
- });
364
- }
501
+ } else if (sources.includes("insights")) ;
502
+ const knowledgeHits = [];
365
503
  if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
366
504
  try {
367
- const knowledgeHits = await deps.searchKnowledge({
505
+ const hits = await deps.searchKnowledge({
368
506
  userId: input.userId,
369
507
  query: input.query,
370
508
  options: {
@@ -374,8 +512,8 @@ function createUnifiedSearch(deps = {}) {
374
512
  },
375
513
  authToken: input.authToken
376
514
  });
377
- for (const hit of knowledgeHits) {
378
- results.push(toKnowledgeResult(hit));
515
+ for (const hit of hits) {
516
+ knowledgeHits.push(toKnowledgeResult(hit));
379
517
  }
380
518
  } catch (error) {
381
519
  logger.warn?.("[memory-store] knowledge source failed:", error);
@@ -385,14 +523,14 @@ function createUnifiedSearch(deps = {}) {
385
523
  message: error.message ?? "knowledge_search_failed"
386
524
  });
387
525
  }
388
- } else if (sources.includes("knowledge")) {
389
- warnings.push({
390
- source: "knowledge",
391
- code: "knowledge_search_not_configured",
392
- message: "Knowledge search is not configured for this host."
393
- });
394
- }
395
- const merged = mergeUnifiedMemorySearchResults(results, limit);
526
+ } else if (sources.includes("knowledge")) ;
527
+ const merged = mergeAcrossSources({
528
+ memorySubs,
529
+ insightHits,
530
+ knowledgeHits,
531
+ limit,
532
+ strategy: mergeStrategy
533
+ });
396
534
  return {
397
535
  query: input.query,
398
536
  sources,
@@ -401,9 +539,35 @@ function createUnifiedSearch(deps = {}) {
401
539
  warnings
402
540
  };
403
541
  }
404
- async function runMemorySource(input, limit, threshold) {
542
+ async function runMemorySource(input, limit, threshold, warnings) {
405
543
  if (typeof deps.embedQuery !== "function") {
406
- throw new Error("embedQuery is not configured");
544
+ warnings.push({
545
+ source: "memory",
546
+ code: "memory_lexical_search_fallback",
547
+ message: "Semantic search not configured, using keyword search as fallback"
548
+ });
549
+ const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
550
+ const keywords = deriveLexicalKeywords(input.query);
551
+ let lexical2 = [];
552
+ if (keywords.length > 0) {
553
+ try {
554
+ const filters2 = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
555
+ const results = await Promise.all(
556
+ filters2.map(
557
+ (botId) => lexicalSearchRawMessages2({
558
+ userId: input.userId,
559
+ keywords,
560
+ limit: Math.ceil(limit / filters2.length),
561
+ botId
562
+ })
563
+ )
564
+ );
565
+ lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
566
+ } catch (error) {
567
+ logger.warn?.("[memory-store] Default lexical search failed:", error);
568
+ }
569
+ }
570
+ return { semantic: [], lexical: lexical2 };
407
571
  }
408
572
  const queryEmbedding = await deps.embedQuery({
409
573
  userId: input.userId,
@@ -411,9 +575,10 @@ function createUnifiedSearch(deps = {}) {
411
575
  authToken: input.authToken
412
576
  });
413
577
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
578
+ let semantic = [];
414
579
  if (isRawMessageChromaEnabled()) {
415
580
  try {
416
- const chromaHits = (await Promise.all(
581
+ semantic = (await Promise.all(
417
582
  filters.map((filter) => {
418
583
  const botId = "botId" in filter ? filter.botId : void 0;
419
584
  return searchRawMessagesWithChroma({
@@ -428,18 +593,16 @@ function createUnifiedSearch(deps = {}) {
428
593
  logger.log?.("[memory-store] Raw message semantic search completed", {
429
594
  backend: "chroma",
430
595
  dimensions: queryEmbedding.length,
431
- count: chromaHits.length
596
+ count: semantic.length
432
597
  });
433
- return chromaHits;
434
598
  } catch (error) {
435
599
  logger.warn?.(
436
600
  "[memory-store] Chroma raw message search failed; falling back to database search:",
437
601
  error
438
602
  );
439
603
  }
440
- }
441
- if (typeof deps.searchRawMessagesAnn === "function") {
442
- const rows = (await Promise.all(
604
+ } else if (typeof deps.searchRawMessagesAnn === "function") {
605
+ semantic = (await Promise.all(
443
606
  filters.map(
444
607
  (filter) => deps.searchRawMessagesAnn({
445
608
  userId: input.userId,
@@ -450,19 +613,113 @@ function createUnifiedSearch(deps = {}) {
450
613
  })
451
614
  )
452
615
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
453
- return rows;
454
616
  }
455
- return [];
617
+ if (semantic.length === 0) {
618
+ try {
619
+ const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
620
+ const manager2 = await getRawMessageManager2();
621
+ if (typeof manager2.searchMessagesSemantically === "function") {
622
+ const results = await manager2.searchMessagesSemantically({
623
+ userId: input.userId,
624
+ queryEmbedding,
625
+ limit
626
+ });
627
+ semantic = results.map((r) => ({
628
+ type: "memory",
629
+ id: r.id,
630
+ content: r.content,
631
+ similarity: r.similarity,
632
+ metadata: r.metadata ?? {}
633
+ }));
634
+ }
635
+ } catch (error) {
636
+ logger.warn?.("[memory-store] SQLite semantic search failed:", error);
637
+ }
638
+ }
639
+ let lexical = [];
640
+ if (typeof deps.searchRawMessagesLexical === "function") {
641
+ const keywords = deriveLexicalKeywords(input.query);
642
+ if (keywords.length > 0) {
643
+ try {
644
+ const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
645
+ lexical = (await Promise.all(
646
+ lexFilters.map(
647
+ (botId) => deps.searchRawMessagesLexical({
648
+ userId: input.userId,
649
+ keywords,
650
+ limit,
651
+ botId
652
+ })
653
+ )
654
+ )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
655
+ ...toMemoryResult(hit),
656
+ metadata: { ...hit.metadata, scoring: "bm25" }
657
+ }));
658
+ } catch (error) {
659
+ logger.warn?.("[memory-store] lexical memory search failed:", error);
660
+ warnings.push({
661
+ source: "memory",
662
+ code: "memory_lexical_search_failed",
663
+ message: error.message ?? "memory_lexical_search_failed"
664
+ });
665
+ }
666
+ }
667
+ } else if (input.mergeStrategy === "rrf") {
668
+ warnings.push({
669
+ source: "memory",
670
+ code: "memory_lexical_search_not_configured",
671
+ message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
672
+ });
673
+ }
674
+ return { semantic, lexical };
456
675
  }
457
676
  async function searchRawMemorySemantically(input) {
458
677
  const limit = clampUnifiedMemorySearchLimit(input.limit);
459
678
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
460
- return runMemorySource(input, limit, threshold);
679
+ const sub = await runMemorySource(input, limit, threshold, []);
680
+ return mergeAcrossSources({
681
+ memorySubs: sub,
682
+ insightHits: [],
683
+ knowledgeHits: [],
684
+ limit,
685
+ strategy: normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy)
686
+ });
461
687
  }
462
688
  return { searchUnifiedMemory, searchRawMemorySemantically };
463
689
  }
690
+ function mergeAcrossSources(input) {
691
+ const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
692
+ const all = [
693
+ ...memorySubs.semantic,
694
+ ...memorySubs.lexical,
695
+ ...input.insightHits,
696
+ ...input.knowledgeHits
697
+ ];
698
+ if (input.strategy !== "rrf") {
699
+ return mergeUnifiedMemorySearchResults(all, input.limit);
700
+ }
701
+ const lists = [];
702
+ if (memorySubs.semantic.length > 0) {
703
+ lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
704
+ }
705
+ if (memorySubs.lexical.length > 0) {
706
+ lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
707
+ }
708
+ if (input.insightHits.length > 0) {
709
+ lists.push({ name: "insights", hits: input.insightHits });
710
+ }
711
+ if (input.knowledgeHits.length > 0) {
712
+ lists.push({ name: "knowledge", hits: input.knowledgeHits });
713
+ }
714
+ if (lists.length <= 1) {
715
+ return mergeUnifiedMemorySearchResults(all, input.limit);
716
+ }
717
+ return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
718
+ }
464
719
 
465
720
  // src/mcp.ts
721
+ init_chroma_memory_index();
722
+ init_raw_message_store();
466
723
  var DEFAULT_NAME = "@melandlabs/memory-store";
467
724
  var DEFAULT_VERSION = "0.1.0";
468
725
  async function embedMissingMessages(messages, deps) {
@@ -621,7 +878,11 @@ async function startMcpServer(options = {}) {
621
878
  return server;
622
879
  }
623
880
 
881
+ // src/server/cli-shared.ts
882
+ init_raw_message_store();
883
+
624
884
  // src/storage/sqlite-vector-index.ts
885
+ init_sqlite_raw_message_store();
625
886
  function getMemoryVectorStoreBackend() {
626
887
  const backend = (process.env.INSIGHT_VECTOR_STORE_BACKEND || process.env.MEMORY_VECTOR_STORE_BACKEND || process.env.VECTOR_STORE_BACKEND || "sqlite-vec").toLowerCase();
627
888
  return backend === "chroma" ? "chroma" : "sqlite-vec";
@@ -976,5 +1237,3 @@ main().catch((error) => {
976
1237
  console.error("[memory-store/mcp] fatal:", error);
977
1238
  process.exit(1);
978
1239
  });
979
- //# sourceMappingURL=cli-mcp.js.map
980
- //# sourceMappingURL=cli-mcp.js.map
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { d as UnifiedSearchDeps } from '../config-D6MzksfT.js';
2
+ import { d as UnifiedSearchDeps } from '../config-BYfTzG3h.js';
3
3
 
4
4
  /**
5
5
  * Shared argv parsing + `unified.*` wiring for the memory-store CLI bins.
@@ -369,5 +369,3 @@ Cross-source search (wires unified.searchKnowledge / searchInsights / searchRawM
369
369
  }
370
370
 
371
371
  export { buildUnified, parseUnifiedArgs, printUnifiedHelp };
372
- //# sourceMappingURL=cli-shared.js.map
373
- //# sourceMappingURL=cli-shared.js.map
@@ -245,5 +245,3 @@ async function searchInsightsWithChroma(input) {
245
245
  }
246
246
 
247
247
  export { isInsightChromaEnabled, isRawMessageChromaEnabled, searchInsightsWithChroma, searchRawMessagesWithChroma, upsertInsightsToChroma, upsertRawMessagesToChroma };
248
- //# sourceMappingURL=chroma-memory-index.js.map
249
- //# sourceMappingURL=chroma-memory-index.js.map
@@ -1,4 +1,4 @@
1
- import { b as MemoryStoreEnv } from '../config-D6MzksfT.js';
1
+ import { b as MemoryStoreEnv } from '../config-BYfTzG3h.js';
2
2
 
3
3
  /**
4
4
  * Lazy registration slot for the host's Postgres-backed raw message
@@ -15,5 +15,3 @@ function hasPostgresFactory() {
15
15
  }
16
16
 
17
17
  export { clearPostgresFactory, hasPostgresFactory, registerPostgresFactory, resolvePostgresFactory };
18
- //# sourceMappingURL=postgres-raw-message-factory.js.map
19
- //# sourceMappingURL=postgres-raw-message-factory.js.map
@@ -1,5 +1,5 @@
1
1
  import { RawMessageStorageManager } from '@melandlabs/indexeddb/storage';
2
- import { b as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-D6MzksfT.js';
2
+ import { b as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-BYfTzG3h.js';
3
3
 
4
4
  /**
5
5
  * Top-level raw-message store facade.
@@ -34,6 +34,14 @@ type RawMessageStorageManagerWithSearch = RawMessageStorageManager & {
34
34
  startTime?: number;
35
35
  endTime?: number;
36
36
  }) => Promise<unknown[]>;
37
+ lexicalSearchMessages?: (input: {
38
+ userId: string;
39
+ keywords: string[];
40
+ limit?: number;
41
+ includeArchived?: boolean;
42
+ platform?: string;
43
+ botId?: string;
44
+ }) => Promise<unknown[]>;
37
45
  };
38
46
  interface CreateRawMessageStoreOptions {
39
47
  env?: MemoryStoreEnv;
@@ -125,5 +125,3 @@ async function closeRawMessageStore() {
125
125
  }
126
126
 
127
127
  export { closeRawMessageStore, configureRawMessageStore, createRawMessageStore, getRawMessageManager, getRawMessageStorageBackend, isRawMessageStorageAvailable };
128
- //# sourceMappingURL=raw-message-store.js.map
129
- //# sourceMappingURL=raw-message-store.js.map
@@ -1,5 +1,5 @@
1
1
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
2
- import { b as MemoryStoreEnv } from '../config-D6MzksfT.js';
2
+ import { b as MemoryStoreEnv } from '../config-BYfTzG3h.js';
3
3
 
4
4
  /**
5
5
  * SQLite-backed raw message store.
@@ -30,5 +30,18 @@ declare function closeSQLiteRawMessageManager(): Promise<void>;
30
30
  * Test-only: reset the singleton. Used by vitest between test cases.
31
31
  */
32
32
  declare function __resetSQLiteRawMessageManagerForTests(): void;
33
+ /**
34
+ * BM25 lexical proxy over the SQLite FTS5 index. Mirrors
35
+ * `SQLiteRawMessageManager.lexicalSearchMessages` so callers downstream of the
36
+ * raw-message facade can opt in to a second ranking signal.
37
+ */
38
+ declare function lexicalSearchRawMessages(input: {
39
+ userId: string;
40
+ keywords: string[];
41
+ limit?: number;
42
+ includeArchived?: boolean;
43
+ platform?: string;
44
+ botId?: string;
45
+ }): Promise<unknown[]>;
33
46
 
34
- export { __resetSQLiteRawMessageManagerForTests, closeSQLiteRawMessageManager, getSQLiteRawMessageManager, isSQLiteRawMessageStorageAvailable, resolveSQLiteRawMessageDbPath };
47
+ export { __resetSQLiteRawMessageManagerForTests, closeSQLiteRawMessageManager, getSQLiteRawMessageManager, isSQLiteRawMessageStorageAvailable, lexicalSearchRawMessages, resolveSQLiteRawMessageDbPath };