@melandlabs/memory-store 0.1.6 → 0.2.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.
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 +144 -23
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +144 -23
  8. package/dist/mcp.d.ts +1 -1
  9. package/dist/mcp.js +144 -23
  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 +145 -24
  14. package/dist/server/cli-http.js +144 -23
  15. package/dist/server/cli-mcp.js +144 -23
  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 +8 -8
  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
@@ -247,7 +247,14 @@ function clampUnifiedMemorySearchThreshold(threshold) {
247
247
  }
248
248
  return Math.min(1, Math.max(-1, parsed));
249
249
  }
250
- function mergeUnifiedMemorySearchResults(results, limit) {
250
+ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
251
+ const strategy = normalizeUnifiedMemoryMergeStrategy(options.strategy);
252
+ if (strategy === "rrf") {
253
+ const lists = options.rankedLists ?? [
254
+ { name: "memory-semantic", hits: results }
255
+ ];
256
+ return mergeUnifiedMemorySearchResultsRrf(lists, limit);
257
+ }
251
258
  return [...results].sort((a, b) => {
252
259
  const scoreDelta = b.similarity - a.similarity;
253
260
  if (scoreDelta !== 0) {
@@ -256,6 +263,42 @@ function mergeUnifiedMemorySearchResults(results, limit) {
256
263
  return a.type.localeCompare(b.type) || a.id.localeCompare(b.id);
257
264
  }).slice(0, limit);
258
265
  }
266
+ function normalizeUnifiedMemoryMergeStrategy(value) {
267
+ return value === "rrf" ? "rrf" : "similarity";
268
+ }
269
+ var DEFAULT_RRF_K = 60;
270
+ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
271
+ if (!Array.isArray(lists) || lists.length === 0) {
272
+ return [];
273
+ }
274
+ const safeK = Number.isFinite(k) && k > 0 ? k : DEFAULT_RRF_K;
275
+ const scores = /* @__PURE__ */ new Map();
276
+ const order = [];
277
+ for (const list of lists) {
278
+ for (let index = 0; index < list.hits.length; index += 1) {
279
+ const hit = list.hits[index];
280
+ const key = `${hit.type}::${hit.id}`;
281
+ const contribution = 1 / (safeK + index + 1);
282
+ const existing = scores.get(key);
283
+ if (existing) {
284
+ existing.rrf += contribution;
285
+ } else {
286
+ scores.set(key, { hit, rrf: contribution });
287
+ order.push(key);
288
+ }
289
+ }
290
+ }
291
+ const ranked = order.map((key) => scores.get(key)).sort((a, b) => {
292
+ if (b.rrf !== a.rrf) {
293
+ return b.rrf - a.rrf;
294
+ }
295
+ return a.hit.type.localeCompare(b.hit.type) || a.hit.id.localeCompare(b.hit.id);
296
+ }).slice(0, limit);
297
+ return ranked.map(({ hit, rrf }) => ({
298
+ ...hit,
299
+ metadata: { ...hit.metadata, rrfScore: rrf }
300
+ }));
301
+ }
259
302
  function toKnowledgeResult(result) {
260
303
  return {
261
304
  type: "knowledge",
@@ -287,6 +330,9 @@ function isRawMemorySemanticResult(result) {
287
330
  }
288
331
 
289
332
  // src/search/unified-search.ts
333
+ function deriveLexicalKeywords(query) {
334
+ return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
335
+ }
290
336
  function createUnifiedSearch(deps = {}) {
291
337
  const logger = deps.logger ?? console;
292
338
  async function searchUnifiedMemory(input) {
@@ -294,8 +340,8 @@ function createUnifiedSearch(deps = {}) {
294
340
  const sources = normalizeUnifiedMemorySearchSources(input.sources);
295
341
  const limit = clampUnifiedMemorySearchLimit(input.limit);
296
342
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
343
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
297
344
  const warnings = [];
298
- const results = [];
299
345
  if (!query) {
300
346
  return {
301
347
  query,
@@ -305,11 +351,11 @@ function createUnifiedSearch(deps = {}) {
305
351
  warnings
306
352
  };
307
353
  }
354
+ let memorySubs;
308
355
  if (sources.includes("memory")) {
309
356
  if (isRawMessageStorageAvailable()) {
310
357
  try {
311
- const memoryHits = await runMemorySource(input, limit, threshold);
312
- results.push(...memoryHits);
358
+ memorySubs = await runMemorySource(input, limit, threshold, warnings);
313
359
  } catch (error) {
314
360
  logger.warn?.("[memory-store] memory source failed:", error);
315
361
  warnings.push({
@@ -326,9 +372,10 @@ function createUnifiedSearch(deps = {}) {
326
372
  });
327
373
  }
328
374
  }
375
+ const insightHits = [];
329
376
  if (sources.includes("insights") && typeof deps.searchInsights === "function") {
330
377
  try {
331
- const insightHits = await deps.searchInsights({
378
+ const hits = await deps.searchInsights({
332
379
  userId: input.userId,
333
380
  query: input.query,
334
381
  limit,
@@ -337,8 +384,8 @@ function createUnifiedSearch(deps = {}) {
337
384
  includeArchived: input.includeArchivedInsights,
338
385
  authToken: input.authToken
339
386
  });
340
- for (const hit of insightHits) {
341
- results.push({
387
+ for (const hit of hits) {
388
+ insightHits.push({
342
389
  type: "insight",
343
390
  id: hit.id,
344
391
  content: hit.content,
@@ -361,9 +408,10 @@ function createUnifiedSearch(deps = {}) {
361
408
  message: "Insights search is not configured for this host."
362
409
  });
363
410
  }
411
+ const knowledgeHits = [];
364
412
  if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
365
413
  try {
366
- const knowledgeHits = await deps.searchKnowledge({
414
+ const hits = await deps.searchKnowledge({
367
415
  userId: input.userId,
368
416
  query: input.query,
369
417
  options: {
@@ -373,8 +421,8 @@ function createUnifiedSearch(deps = {}) {
373
421
  },
374
422
  authToken: input.authToken
375
423
  });
376
- for (const hit of knowledgeHits) {
377
- results.push(toKnowledgeResult(hit));
424
+ for (const hit of hits) {
425
+ knowledgeHits.push(toKnowledgeResult(hit));
378
426
  }
379
427
  } catch (error) {
380
428
  logger.warn?.("[memory-store] knowledge source failed:", error);
@@ -391,7 +439,13 @@ function createUnifiedSearch(deps = {}) {
391
439
  message: "Knowledge search is not configured for this host."
392
440
  });
393
441
  }
394
- const merged = mergeUnifiedMemorySearchResults(results, limit);
442
+ const merged = mergeAcrossSources({
443
+ memorySubs,
444
+ insightHits,
445
+ knowledgeHits,
446
+ limit,
447
+ strategy: mergeStrategy
448
+ });
395
449
  return {
396
450
  query: input.query,
397
451
  sources,
@@ -400,7 +454,7 @@ function createUnifiedSearch(deps = {}) {
400
454
  warnings
401
455
  };
402
456
  }
403
- async function runMemorySource(input, limit, threshold) {
457
+ async function runMemorySource(input, limit, threshold, warnings) {
404
458
  if (typeof deps.embedQuery !== "function") {
405
459
  throw new Error("embedQuery is not configured");
406
460
  }
@@ -410,9 +464,10 @@ function createUnifiedSearch(deps = {}) {
410
464
  authToken: input.authToken
411
465
  });
412
466
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
467
+ let semantic = [];
413
468
  if (isRawMessageChromaEnabled()) {
414
469
  try {
415
- const chromaHits = (await Promise.all(
470
+ semantic = (await Promise.all(
416
471
  filters.map((filter) => {
417
472
  const botId = "botId" in filter ? filter.botId : void 0;
418
473
  return searchRawMessagesWithChroma({
@@ -427,18 +482,16 @@ function createUnifiedSearch(deps = {}) {
427
482
  logger.log?.("[memory-store] Raw message semantic search completed", {
428
483
  backend: "chroma",
429
484
  dimensions: queryEmbedding.length,
430
- count: chromaHits.length
485
+ count: semantic.length
431
486
  });
432
- return chromaHits;
433
487
  } catch (error) {
434
488
  logger.warn?.(
435
489
  "[memory-store] Chroma raw message search failed; falling back to database search:",
436
490
  error
437
491
  );
438
492
  }
439
- }
440
- if (typeof deps.searchRawMessagesAnn === "function") {
441
- const rows = (await Promise.all(
493
+ } else if (typeof deps.searchRawMessagesAnn === "function") {
494
+ semantic = (await Promise.all(
442
495
  filters.map(
443
496
  (filter) => deps.searchRawMessagesAnn({
444
497
  userId: input.userId,
@@ -449,17 +502,87 @@ function createUnifiedSearch(deps = {}) {
449
502
  })
450
503
  )
451
504
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
452
- return rows;
453
505
  }
454
- return [];
506
+ let lexical = [];
507
+ if (typeof deps.searchRawMessagesLexical === "function") {
508
+ const keywords = deriveLexicalKeywords(input.query);
509
+ if (keywords.length > 0) {
510
+ try {
511
+ const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
512
+ lexical = (await Promise.all(
513
+ lexFilters.map(
514
+ (botId) => deps.searchRawMessagesLexical({
515
+ userId: input.userId,
516
+ keywords,
517
+ limit,
518
+ botId
519
+ })
520
+ )
521
+ )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
522
+ ...toMemoryResult(hit),
523
+ metadata: { ...hit.metadata, scoring: "bm25" }
524
+ }));
525
+ } catch (error) {
526
+ logger.warn?.("[memory-store] lexical memory search failed:", error);
527
+ warnings.push({
528
+ source: "memory",
529
+ code: "memory_lexical_search_failed",
530
+ message: error.message ?? "memory_lexical_search_failed"
531
+ });
532
+ }
533
+ }
534
+ } else if (input.mergeStrategy === "rrf") {
535
+ warnings.push({
536
+ source: "memory",
537
+ code: "memory_lexical_search_not_configured",
538
+ message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
539
+ });
540
+ }
541
+ return { semantic, lexical };
455
542
  }
456
543
  async function searchRawMemorySemantically(input) {
457
544
  const limit = clampUnifiedMemorySearchLimit(input.limit);
458
545
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
459
- return runMemorySource(input, limit, threshold);
546
+ const sub = await runMemorySource(input, limit, threshold, []);
547
+ return mergeAcrossSources({
548
+ memorySubs: sub,
549
+ insightHits: [],
550
+ knowledgeHits: [],
551
+ limit,
552
+ strategy: normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy)
553
+ });
460
554
  }
461
555
  return { searchUnifiedMemory, searchRawMemorySemantically };
462
556
  }
557
+ function mergeAcrossSources(input) {
558
+ const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
559
+ const all = [
560
+ ...memorySubs.semantic,
561
+ ...memorySubs.lexical,
562
+ ...input.insightHits,
563
+ ...input.knowledgeHits
564
+ ];
565
+ if (input.strategy !== "rrf") {
566
+ return mergeUnifiedMemorySearchResults(all, input.limit);
567
+ }
568
+ const lists = [];
569
+ if (memorySubs.semantic.length > 0) {
570
+ lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
571
+ }
572
+ if (memorySubs.lexical.length > 0) {
573
+ lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
574
+ }
575
+ if (input.insightHits.length > 0) {
576
+ lists.push({ name: "insights", hits: input.insightHits });
577
+ }
578
+ if (input.knowledgeHits.length > 0) {
579
+ lists.push({ name: "knowledge", hits: input.knowledgeHits });
580
+ }
581
+ if (lists.length <= 1) {
582
+ return mergeUnifiedMemorySearchResults(all, input.limit);
583
+ }
584
+ return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
585
+ }
463
586
 
464
587
  // src/http.ts
465
588
  async function embedMissingMessages(messages, deps) {
@@ -934,5 +1057,3 @@ main().catch((error) => {
934
1057
  console.error("[memory-store/http] fatal:", error);
935
1058
  process.exit(1);
936
1059
  });
937
- //# sourceMappingURL=cli-http.js.map
938
- //# sourceMappingURL=cli-http.js.map
@@ -248,7 +248,14 @@ function clampUnifiedMemorySearchThreshold(threshold) {
248
248
  }
249
249
  return Math.min(1, Math.max(-1, parsed));
250
250
  }
251
- function mergeUnifiedMemorySearchResults(results, limit) {
251
+ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
252
+ const strategy = normalizeUnifiedMemoryMergeStrategy(options.strategy);
253
+ if (strategy === "rrf") {
254
+ const lists = options.rankedLists ?? [
255
+ { name: "memory-semantic", hits: results }
256
+ ];
257
+ return mergeUnifiedMemorySearchResultsRrf(lists, limit);
258
+ }
252
259
  return [...results].sort((a, b) => {
253
260
  const scoreDelta = b.similarity - a.similarity;
254
261
  if (scoreDelta !== 0) {
@@ -257,6 +264,42 @@ function mergeUnifiedMemorySearchResults(results, limit) {
257
264
  return a.type.localeCompare(b.type) || a.id.localeCompare(b.id);
258
265
  }).slice(0, limit);
259
266
  }
267
+ function normalizeUnifiedMemoryMergeStrategy(value) {
268
+ return value === "rrf" ? "rrf" : "similarity";
269
+ }
270
+ var DEFAULT_RRF_K = 60;
271
+ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
272
+ if (!Array.isArray(lists) || lists.length === 0) {
273
+ return [];
274
+ }
275
+ const safeK = Number.isFinite(k) && k > 0 ? k : DEFAULT_RRF_K;
276
+ const scores = /* @__PURE__ */ new Map();
277
+ const order = [];
278
+ for (const list of lists) {
279
+ for (let index = 0; index < list.hits.length; index += 1) {
280
+ const hit = list.hits[index];
281
+ const key = `${hit.type}::${hit.id}`;
282
+ const contribution = 1 / (safeK + index + 1);
283
+ const existing = scores.get(key);
284
+ if (existing) {
285
+ existing.rrf += contribution;
286
+ } else {
287
+ scores.set(key, { hit, rrf: contribution });
288
+ order.push(key);
289
+ }
290
+ }
291
+ }
292
+ const ranked = order.map((key) => scores.get(key)).sort((a, b) => {
293
+ if (b.rrf !== a.rrf) {
294
+ return b.rrf - a.rrf;
295
+ }
296
+ return a.hit.type.localeCompare(b.hit.type) || a.hit.id.localeCompare(b.hit.id);
297
+ }).slice(0, limit);
298
+ return ranked.map(({ hit, rrf }) => ({
299
+ ...hit,
300
+ metadata: { ...hit.metadata, rrfScore: rrf }
301
+ }));
302
+ }
260
303
  function toKnowledgeResult(result) {
261
304
  return {
262
305
  type: "knowledge",
@@ -288,6 +331,9 @@ function isRawMemorySemanticResult(result) {
288
331
  }
289
332
 
290
333
  // src/search/unified-search.ts
334
+ function deriveLexicalKeywords(query) {
335
+ return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
336
+ }
291
337
  function createUnifiedSearch(deps = {}) {
292
338
  const logger = deps.logger ?? console;
293
339
  async function searchUnifiedMemory(input) {
@@ -295,8 +341,8 @@ function createUnifiedSearch(deps = {}) {
295
341
  const sources = normalizeUnifiedMemorySearchSources(input.sources);
296
342
  const limit = clampUnifiedMemorySearchLimit(input.limit);
297
343
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
344
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
298
345
  const warnings = [];
299
- const results = [];
300
346
  if (!query) {
301
347
  return {
302
348
  query,
@@ -306,11 +352,11 @@ function createUnifiedSearch(deps = {}) {
306
352
  warnings
307
353
  };
308
354
  }
355
+ let memorySubs;
309
356
  if (sources.includes("memory")) {
310
357
  if (isRawMessageStorageAvailable()) {
311
358
  try {
312
- const memoryHits = await runMemorySource(input, limit, threshold);
313
- results.push(...memoryHits);
359
+ memorySubs = await runMemorySource(input, limit, threshold, warnings);
314
360
  } catch (error) {
315
361
  logger.warn?.("[memory-store] memory source failed:", error);
316
362
  warnings.push({
@@ -327,9 +373,10 @@ function createUnifiedSearch(deps = {}) {
327
373
  });
328
374
  }
329
375
  }
376
+ const insightHits = [];
330
377
  if (sources.includes("insights") && typeof deps.searchInsights === "function") {
331
378
  try {
332
- const insightHits = await deps.searchInsights({
379
+ const hits = await deps.searchInsights({
333
380
  userId: input.userId,
334
381
  query: input.query,
335
382
  limit,
@@ -338,8 +385,8 @@ function createUnifiedSearch(deps = {}) {
338
385
  includeArchived: input.includeArchivedInsights,
339
386
  authToken: input.authToken
340
387
  });
341
- for (const hit of insightHits) {
342
- results.push({
388
+ for (const hit of hits) {
389
+ insightHits.push({
343
390
  type: "insight",
344
391
  id: hit.id,
345
392
  content: hit.content,
@@ -362,9 +409,10 @@ function createUnifiedSearch(deps = {}) {
362
409
  message: "Insights search is not configured for this host."
363
410
  });
364
411
  }
412
+ const knowledgeHits = [];
365
413
  if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
366
414
  try {
367
- const knowledgeHits = await deps.searchKnowledge({
415
+ const hits = await deps.searchKnowledge({
368
416
  userId: input.userId,
369
417
  query: input.query,
370
418
  options: {
@@ -374,8 +422,8 @@ function createUnifiedSearch(deps = {}) {
374
422
  },
375
423
  authToken: input.authToken
376
424
  });
377
- for (const hit of knowledgeHits) {
378
- results.push(toKnowledgeResult(hit));
425
+ for (const hit of hits) {
426
+ knowledgeHits.push(toKnowledgeResult(hit));
379
427
  }
380
428
  } catch (error) {
381
429
  logger.warn?.("[memory-store] knowledge source failed:", error);
@@ -392,7 +440,13 @@ function createUnifiedSearch(deps = {}) {
392
440
  message: "Knowledge search is not configured for this host."
393
441
  });
394
442
  }
395
- const merged = mergeUnifiedMemorySearchResults(results, limit);
443
+ const merged = mergeAcrossSources({
444
+ memorySubs,
445
+ insightHits,
446
+ knowledgeHits,
447
+ limit,
448
+ strategy: mergeStrategy
449
+ });
396
450
  return {
397
451
  query: input.query,
398
452
  sources,
@@ -401,7 +455,7 @@ function createUnifiedSearch(deps = {}) {
401
455
  warnings
402
456
  };
403
457
  }
404
- async function runMemorySource(input, limit, threshold) {
458
+ async function runMemorySource(input, limit, threshold, warnings) {
405
459
  if (typeof deps.embedQuery !== "function") {
406
460
  throw new Error("embedQuery is not configured");
407
461
  }
@@ -411,9 +465,10 @@ function createUnifiedSearch(deps = {}) {
411
465
  authToken: input.authToken
412
466
  });
413
467
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
468
+ let semantic = [];
414
469
  if (isRawMessageChromaEnabled()) {
415
470
  try {
416
- const chromaHits = (await Promise.all(
471
+ semantic = (await Promise.all(
417
472
  filters.map((filter) => {
418
473
  const botId = "botId" in filter ? filter.botId : void 0;
419
474
  return searchRawMessagesWithChroma({
@@ -428,18 +483,16 @@ function createUnifiedSearch(deps = {}) {
428
483
  logger.log?.("[memory-store] Raw message semantic search completed", {
429
484
  backend: "chroma",
430
485
  dimensions: queryEmbedding.length,
431
- count: chromaHits.length
486
+ count: semantic.length
432
487
  });
433
- return chromaHits;
434
488
  } catch (error) {
435
489
  logger.warn?.(
436
490
  "[memory-store] Chroma raw message search failed; falling back to database search:",
437
491
  error
438
492
  );
439
493
  }
440
- }
441
- if (typeof deps.searchRawMessagesAnn === "function") {
442
- const rows = (await Promise.all(
494
+ } else if (typeof deps.searchRawMessagesAnn === "function") {
495
+ semantic = (await Promise.all(
443
496
  filters.map(
444
497
  (filter) => deps.searchRawMessagesAnn({
445
498
  userId: input.userId,
@@ -450,17 +503,87 @@ function createUnifiedSearch(deps = {}) {
450
503
  })
451
504
  )
452
505
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
453
- return rows;
454
506
  }
455
- return [];
507
+ let lexical = [];
508
+ if (typeof deps.searchRawMessagesLexical === "function") {
509
+ const keywords = deriveLexicalKeywords(input.query);
510
+ if (keywords.length > 0) {
511
+ try {
512
+ const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
513
+ lexical = (await Promise.all(
514
+ lexFilters.map(
515
+ (botId) => deps.searchRawMessagesLexical({
516
+ userId: input.userId,
517
+ keywords,
518
+ limit,
519
+ botId
520
+ })
521
+ )
522
+ )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
523
+ ...toMemoryResult(hit),
524
+ metadata: { ...hit.metadata, scoring: "bm25" }
525
+ }));
526
+ } catch (error) {
527
+ logger.warn?.("[memory-store] lexical memory search failed:", error);
528
+ warnings.push({
529
+ source: "memory",
530
+ code: "memory_lexical_search_failed",
531
+ message: error.message ?? "memory_lexical_search_failed"
532
+ });
533
+ }
534
+ }
535
+ } else if (input.mergeStrategy === "rrf") {
536
+ warnings.push({
537
+ source: "memory",
538
+ code: "memory_lexical_search_not_configured",
539
+ message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
540
+ });
541
+ }
542
+ return { semantic, lexical };
456
543
  }
457
544
  async function searchRawMemorySemantically(input) {
458
545
  const limit = clampUnifiedMemorySearchLimit(input.limit);
459
546
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
460
- return runMemorySource(input, limit, threshold);
547
+ const sub = await runMemorySource(input, limit, threshold, []);
548
+ return mergeAcrossSources({
549
+ memorySubs: sub,
550
+ insightHits: [],
551
+ knowledgeHits: [],
552
+ limit,
553
+ strategy: normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy)
554
+ });
461
555
  }
462
556
  return { searchUnifiedMemory, searchRawMemorySemantically };
463
557
  }
558
+ function mergeAcrossSources(input) {
559
+ const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
560
+ const all = [
561
+ ...memorySubs.semantic,
562
+ ...memorySubs.lexical,
563
+ ...input.insightHits,
564
+ ...input.knowledgeHits
565
+ ];
566
+ if (input.strategy !== "rrf") {
567
+ return mergeUnifiedMemorySearchResults(all, input.limit);
568
+ }
569
+ const lists = [];
570
+ if (memorySubs.semantic.length > 0) {
571
+ lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
572
+ }
573
+ if (memorySubs.lexical.length > 0) {
574
+ lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
575
+ }
576
+ if (input.insightHits.length > 0) {
577
+ lists.push({ name: "insights", hits: input.insightHits });
578
+ }
579
+ if (input.knowledgeHits.length > 0) {
580
+ lists.push({ name: "knowledge", hits: input.knowledgeHits });
581
+ }
582
+ if (lists.length <= 1) {
583
+ return mergeUnifiedMemorySearchResults(all, input.limit);
584
+ }
585
+ return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
586
+ }
464
587
 
465
588
  // src/mcp.ts
466
589
  var DEFAULT_NAME = "@melandlabs/memory-store";
@@ -976,5 +1099,3 @@ main().catch((error) => {
976
1099
  console.error("[memory-store/mcp] fatal:", error);
977
1100
  process.exit(1);
978
1101
  });
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 };
@@ -56,7 +56,12 @@ async function closeSQLiteRawMessageManager() {
56
56
  function __resetSQLiteRawMessageManagerForTests() {
57
57
  manager = null;
58
58
  }
59
+ async function lexicalSearchRawMessages(input) {
60
+ const mgr = await getSQLiteRawMessageManager();
61
+ if (typeof mgr.lexicalSearchMessages !== "function") {
62
+ return [];
63
+ }
64
+ return mgr.lexicalSearchMessages(input);
65
+ }
59
66
 
60
- export { __resetSQLiteRawMessageManagerForTests, closeSQLiteRawMessageManager, getSQLiteRawMessageManager, isSQLiteRawMessageStorageAvailable, resolveSQLiteRawMessageDbPath };
61
- //# sourceMappingURL=sqlite-raw-message-store.js.map
62
- //# sourceMappingURL=sqlite-raw-message-store.js.map
67
+ export { __resetSQLiteRawMessageManagerForTests, closeSQLiteRawMessageManager, getSQLiteRawMessageManager, isSQLiteRawMessageStorageAvailable, lexicalSearchRawMessages, resolveSQLiteRawMessageDbPath };