@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
@@ -2,16 +2,24 @@ import '@melandlabs/ai/memory';
2
2
  import { ChromaVectorStore } from '@melandlabs/rag';
3
3
  import { mkdirSync } from 'fs';
4
4
  import { homedir } from 'os';
5
- import { join, dirname } from 'path';
5
+ import { dirname, join } from 'path';
6
6
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
7
7
 
8
- // src/storage/chroma-memory-index.ts
9
- var DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
10
- var RAW_VECTOR_BACKEND_ENV_KEYS = [
11
- "RAW_MESSAGE_VECTOR_STORE_BACKEND",
12
- "MEMORY_VECTOR_STORE_BACKEND",
13
- "VECTOR_STORE_BACKEND"
14
- ];
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __esm = (fn, res) => function __init() {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+
18
+ // src/contracts.ts
19
+ var init_contracts = __esm({
20
+ "src/contracts.ts"() {
21
+ }
22
+ });
15
23
  function getBackend(keys) {
16
24
  for (const key of keys) {
17
25
  const value = process.env[key]?.trim().toLowerCase();
@@ -62,21 +70,51 @@ async function searchRawMessagesWithChroma(input) {
62
70
  metadata: result.metadata ?? {}
63
71
  })).slice(0, input.limit);
64
72
  }
73
+ var DEFAULT_RAW_MESSAGES_COLLECTION, RAW_VECTOR_BACKEND_ENV_KEYS;
74
+ var init_chroma_memory_index = __esm({
75
+ "src/storage/chroma-memory-index.ts"() {
76
+ init_contracts();
77
+ DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
78
+ RAW_VECTOR_BACKEND_ENV_KEYS = [
79
+ "RAW_MESSAGE_VECTOR_STORE_BACKEND",
80
+ "MEMORY_VECTOR_STORE_BACKEND",
81
+ "VECTOR_STORE_BACKEND"
82
+ ];
83
+ }
84
+ });
65
85
 
66
86
  // src/storage/postgres-raw-message-factory.ts
67
- var factory = null;
68
87
  async function resolvePostgresFactory(env) {
69
- return null;
88
+ if (!factory) return null;
89
+ return await factory(env);
70
90
  }
71
91
  function hasPostgresFactory() {
72
92
  return factory !== null;
73
93
  }
74
- var manager = null;
75
- var DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
94
+ var factory;
95
+ var init_postgres_raw_message_factory = __esm({
96
+ "src/storage/postgres-raw-message-factory.ts"() {
97
+ factory = null;
98
+ }
99
+ });
100
+
101
+ // src/storage/sqlite-raw-message-store.ts
102
+ var sqlite_raw_message_store_exports = {};
103
+ __export(sqlite_raw_message_store_exports, {
104
+ __resetSQLiteRawMessageManagerForTests: () => __resetSQLiteRawMessageManagerForTests,
105
+ closeSQLiteRawMessageManager: () => closeSQLiteRawMessageManager,
106
+ getSQLiteRawMessageManager: () => getSQLiteRawMessageManager,
107
+ isSQLiteRawMessageStorageAvailable: () => isSQLiteRawMessageStorageAvailable,
108
+ lexicalSearchRawMessages: () => lexicalSearchRawMessages,
109
+ resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
110
+ });
76
111
  function resolveSQLiteRawMessageDbPath() {
77
112
  const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
78
113
  return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
79
114
  }
115
+ function isSQLiteRawMessageStorageAvailable(_env) {
116
+ return true;
117
+ }
80
118
  async function getSQLiteRawMessageManager(_env) {
81
119
  if (!manager) {
82
120
  const dbPath = resolveSQLiteRawMessageDbPath();
@@ -96,8 +134,35 @@ async function closeSQLiteRawMessageManager() {
96
134
  await manager.close();
97
135
  manager = null;
98
136
  }
137
+ function __resetSQLiteRawMessageManagerForTests() {
138
+ manager = null;
139
+ }
140
+ async function lexicalSearchRawMessages(input) {
141
+ const mgr = await getSQLiteRawMessageManager();
142
+ if (typeof mgr.lexicalSearchMessages !== "function") {
143
+ return [];
144
+ }
145
+ return mgr.lexicalSearchMessages(input);
146
+ }
147
+ var manager, DEFAULT_SQLITE_PATH;
148
+ var init_sqlite_raw_message_store = __esm({
149
+ "src/storage/sqlite-raw-message-store.ts"() {
150
+ init_chroma_memory_index();
151
+ manager = null;
152
+ DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
153
+ }
154
+ });
99
155
 
100
156
  // src/storage/raw-message-store.ts
157
+ var raw_message_store_exports = {};
158
+ __export(raw_message_store_exports, {
159
+ closeRawMessageStore: () => closeRawMessageStore,
160
+ configureRawMessageStore: () => configureRawMessageStore,
161
+ createRawMessageStore: () => createRawMessageStore,
162
+ getRawMessageManager: () => getRawMessageManager,
163
+ getRawMessageStorageBackend: () => getRawMessageStorageBackend,
164
+ isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
165
+ });
101
166
  function resolveBackend(env) {
102
167
  const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
103
168
  if (explicit === "postgres") return "postgres";
@@ -107,7 +172,7 @@ function resolveEnv(_env) {
107
172
  return {};
108
173
  }
109
174
  function createRawMessageStore(options = {}) {
110
- resolveEnv(options.env);
175
+ const env = resolveEnv(options.env);
111
176
  return {
112
177
  getBackend() {
113
178
  return resolveBackend();
@@ -117,7 +182,7 @@ function createRawMessageStore(options = {}) {
117
182
  },
118
183
  async getManager() {
119
184
  if (resolveBackend() === "postgres") {
120
- const pg = await resolvePostgresFactory();
185
+ const pg = await resolvePostgresFactory(env);
121
186
  if (!pg) {
122
187
  throw new Error(
123
188
  "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."
@@ -132,14 +197,47 @@ function createRawMessageStore(options = {}) {
132
197
  }
133
198
  };
134
199
  }
135
- var moduleStore = null;
136
- var moduleConfig = null;
200
+ function configureRawMessageStore(config) {
201
+ moduleConfig = config;
202
+ moduleStore = createRawMessageStore({ env: config.env });
203
+ return moduleStore;
204
+ }
205
+ function getRawMessageStorageBackend() {
206
+ if (!moduleStore) {
207
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
208
+ }
209
+ return moduleStore.getBackend();
210
+ }
137
211
  function isRawMessageStorageAvailable() {
138
212
  if (!moduleStore) {
139
213
  moduleStore = createRawMessageStore({ env: moduleConfig?.env });
140
214
  }
141
215
  return moduleStore.isAvailable();
142
216
  }
217
+ async function getRawMessageManager() {
218
+ if (!moduleStore) {
219
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
220
+ }
221
+ return moduleStore.getManager();
222
+ }
223
+ async function closeRawMessageStore() {
224
+ if (!moduleStore) return;
225
+ await moduleStore.close();
226
+ moduleStore = null;
227
+ }
228
+ var moduleStore, moduleConfig;
229
+ var init_raw_message_store = __esm({
230
+ "src/storage/raw-message-store.ts"() {
231
+ init_postgres_raw_message_factory();
232
+ init_sqlite_raw_message_store();
233
+ moduleStore = null;
234
+ moduleConfig = null;
235
+ }
236
+ });
237
+
238
+ // src/search/unified-search.ts
239
+ init_chroma_memory_index();
240
+ init_raw_message_store();
143
241
 
144
242
  // src/search/utilities.ts
145
243
  var DEFAULT_LIMIT = 10;
@@ -169,7 +267,14 @@ function clampUnifiedMemorySearchThreshold(threshold) {
169
267
  }
170
268
  return Math.min(1, Math.max(-1, parsed));
171
269
  }
172
- function mergeUnifiedMemorySearchResults(results, limit) {
270
+ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
271
+ const strategy = normalizeUnifiedMemoryMergeStrategy(options.strategy);
272
+ if (strategy === "rrf") {
273
+ const lists = options.rankedLists ?? [
274
+ { name: "memory-semantic", hits: results }
275
+ ];
276
+ return mergeUnifiedMemorySearchResultsRrf(lists, limit);
277
+ }
173
278
  return [...results].sort((a, b) => {
174
279
  const scoreDelta = b.similarity - a.similarity;
175
280
  if (scoreDelta !== 0) {
@@ -178,6 +283,42 @@ function mergeUnifiedMemorySearchResults(results, limit) {
178
283
  return a.type.localeCompare(b.type) || a.id.localeCompare(b.id);
179
284
  }).slice(0, limit);
180
285
  }
286
+ function normalizeUnifiedMemoryMergeStrategy(value) {
287
+ return value === "rrf" ? "rrf" : "similarity";
288
+ }
289
+ var DEFAULT_RRF_K = 60;
290
+ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
291
+ if (!Array.isArray(lists) || lists.length === 0) {
292
+ return [];
293
+ }
294
+ const safeK = Number.isFinite(k) && k > 0 ? k : DEFAULT_RRF_K;
295
+ const scores = /* @__PURE__ */ new Map();
296
+ const order = [];
297
+ for (const list of lists) {
298
+ for (let index = 0; index < list.hits.length; index += 1) {
299
+ const hit = list.hits[index];
300
+ const key = `${hit.type}::${hit.id}`;
301
+ const contribution = 1 / (safeK + index + 1);
302
+ const existing = scores.get(key);
303
+ if (existing) {
304
+ existing.rrf += contribution;
305
+ } else {
306
+ scores.set(key, { hit, rrf: contribution });
307
+ order.push(key);
308
+ }
309
+ }
310
+ }
311
+ const ranked = order.map((key) => scores.get(key)).sort((a, b) => {
312
+ if (b.rrf !== a.rrf) {
313
+ return b.rrf - a.rrf;
314
+ }
315
+ return a.hit.type.localeCompare(b.hit.type) || a.hit.id.localeCompare(b.hit.id);
316
+ }).slice(0, limit);
317
+ return ranked.map(({ hit, rrf }) => ({
318
+ ...hit,
319
+ metadata: { ...hit.metadata, rrfScore: rrf }
320
+ }));
321
+ }
181
322
  function toKnowledgeResult(result) {
182
323
  return {
183
324
  type: "knowledge",
@@ -209,6 +350,9 @@ function isRawMemorySemanticResult(result) {
209
350
  }
210
351
 
211
352
  // src/search/unified-search.ts
353
+ function deriveLexicalKeywords(query) {
354
+ return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
355
+ }
212
356
  function createUnifiedSearch(deps = {}) {
213
357
  const logger = deps.logger ?? console;
214
358
  async function searchUnifiedMemory(input) {
@@ -216,8 +360,8 @@ function createUnifiedSearch(deps = {}) {
216
360
  const sources = normalizeUnifiedMemorySearchSources(input.sources);
217
361
  const limit = clampUnifiedMemorySearchLimit(input.limit);
218
362
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
363
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
219
364
  const warnings = [];
220
- const results = [];
221
365
  if (!query) {
222
366
  return {
223
367
  query,
@@ -227,17 +371,17 @@ function createUnifiedSearch(deps = {}) {
227
371
  warnings
228
372
  };
229
373
  }
374
+ let memorySubs;
230
375
  if (sources.includes("memory")) {
231
376
  if (isRawMessageStorageAvailable()) {
232
377
  try {
233
- const memoryHits = await runMemorySource(input, limit, threshold);
234
- results.push(...memoryHits);
378
+ memorySubs = await runMemorySource(input, limit, threshold, warnings);
235
379
  } catch (error) {
236
380
  logger.warn?.("[memory-store] memory source failed:", error);
237
381
  warnings.push({
238
382
  source: "memory",
239
383
  code: "memory_search_failed",
240
- message: error.message ?? "memory_search_failed"
384
+ message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
241
385
  });
242
386
  }
243
387
  } else {
@@ -248,9 +392,10 @@ function createUnifiedSearch(deps = {}) {
248
392
  });
249
393
  }
250
394
  }
395
+ const insightHits = [];
251
396
  if (sources.includes("insights") && typeof deps.searchInsights === "function") {
252
397
  try {
253
- const insightHits = await deps.searchInsights({
398
+ const hits = await deps.searchInsights({
254
399
  userId: input.userId,
255
400
  query: input.query,
256
401
  limit,
@@ -259,8 +404,8 @@ function createUnifiedSearch(deps = {}) {
259
404
  includeArchived: input.includeArchivedInsights,
260
405
  authToken: input.authToken
261
406
  });
262
- for (const hit of insightHits) {
263
- results.push({
407
+ for (const hit of hits) {
408
+ insightHits.push({
264
409
  type: "insight",
265
410
  id: hit.id,
266
411
  content: hit.content,
@@ -276,16 +421,11 @@ function createUnifiedSearch(deps = {}) {
276
421
  message: error.message ?? "insights_search_failed"
277
422
  });
278
423
  }
279
- } else if (sources.includes("insights")) {
280
- warnings.push({
281
- source: "insights",
282
- code: "insights_search_not_configured",
283
- message: "Insights search is not configured for this host."
284
- });
285
- }
424
+ } else if (sources.includes("insights")) ;
425
+ const knowledgeHits = [];
286
426
  if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
287
427
  try {
288
- const knowledgeHits = await deps.searchKnowledge({
428
+ const hits = await deps.searchKnowledge({
289
429
  userId: input.userId,
290
430
  query: input.query,
291
431
  options: {
@@ -295,8 +435,8 @@ function createUnifiedSearch(deps = {}) {
295
435
  },
296
436
  authToken: input.authToken
297
437
  });
298
- for (const hit of knowledgeHits) {
299
- results.push(toKnowledgeResult(hit));
438
+ for (const hit of hits) {
439
+ knowledgeHits.push(toKnowledgeResult(hit));
300
440
  }
301
441
  } catch (error) {
302
442
  logger.warn?.("[memory-store] knowledge source failed:", error);
@@ -306,14 +446,14 @@ function createUnifiedSearch(deps = {}) {
306
446
  message: error.message ?? "knowledge_search_failed"
307
447
  });
308
448
  }
309
- } else if (sources.includes("knowledge")) {
310
- warnings.push({
311
- source: "knowledge",
312
- code: "knowledge_search_not_configured",
313
- message: "Knowledge search is not configured for this host."
314
- });
315
- }
316
- const merged = mergeUnifiedMemorySearchResults(results, limit);
449
+ } else if (sources.includes("knowledge")) ;
450
+ const merged = mergeAcrossSources({
451
+ memorySubs,
452
+ insightHits,
453
+ knowledgeHits,
454
+ limit,
455
+ strategy: mergeStrategy
456
+ });
317
457
  return {
318
458
  query: input.query,
319
459
  sources,
@@ -322,9 +462,35 @@ function createUnifiedSearch(deps = {}) {
322
462
  warnings
323
463
  };
324
464
  }
325
- async function runMemorySource(input, limit, threshold) {
465
+ async function runMemorySource(input, limit, threshold, warnings) {
326
466
  if (typeof deps.embedQuery !== "function") {
327
- throw new Error("embedQuery is not configured");
467
+ warnings.push({
468
+ source: "memory",
469
+ code: "memory_lexical_search_fallback",
470
+ message: "Semantic search not configured, using keyword search as fallback"
471
+ });
472
+ const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
473
+ const keywords = deriveLexicalKeywords(input.query);
474
+ let lexical2 = [];
475
+ if (keywords.length > 0) {
476
+ try {
477
+ const filters2 = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
478
+ const results = await Promise.all(
479
+ filters2.map(
480
+ (botId) => lexicalSearchRawMessages2({
481
+ userId: input.userId,
482
+ keywords,
483
+ limit: Math.ceil(limit / filters2.length),
484
+ botId
485
+ })
486
+ )
487
+ );
488
+ lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
489
+ } catch (error) {
490
+ logger.warn?.("[memory-store] Default lexical search failed:", error);
491
+ }
492
+ }
493
+ return { semantic: [], lexical: lexical2 };
328
494
  }
329
495
  const queryEmbedding = await deps.embedQuery({
330
496
  userId: input.userId,
@@ -332,9 +498,10 @@ function createUnifiedSearch(deps = {}) {
332
498
  authToken: input.authToken
333
499
  });
334
500
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
501
+ let semantic = [];
335
502
  if (isRawMessageChromaEnabled()) {
336
503
  try {
337
- const chromaHits = (await Promise.all(
504
+ semantic = (await Promise.all(
338
505
  filters.map((filter) => {
339
506
  const botId = "botId" in filter ? filter.botId : void 0;
340
507
  return searchRawMessagesWithChroma({
@@ -349,18 +516,16 @@ function createUnifiedSearch(deps = {}) {
349
516
  logger.log?.("[memory-store] Raw message semantic search completed", {
350
517
  backend: "chroma",
351
518
  dimensions: queryEmbedding.length,
352
- count: chromaHits.length
519
+ count: semantic.length
353
520
  });
354
- return chromaHits;
355
521
  } catch (error) {
356
522
  logger.warn?.(
357
523
  "[memory-store] Chroma raw message search failed; falling back to database search:",
358
524
  error
359
525
  );
360
526
  }
361
- }
362
- if (typeof deps.searchRawMessagesAnn === "function") {
363
- const rows = (await Promise.all(
527
+ } else if (typeof deps.searchRawMessagesAnn === "function") {
528
+ semantic = (await Promise.all(
364
529
  filters.map(
365
530
  (filter) => deps.searchRawMessagesAnn({
366
531
  userId: input.userId,
@@ -371,18 +536,108 @@ function createUnifiedSearch(deps = {}) {
371
536
  })
372
537
  )
373
538
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
374
- return rows;
375
539
  }
376
- return [];
540
+ if (semantic.length === 0) {
541
+ try {
542
+ const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
543
+ const manager2 = await getRawMessageManager2();
544
+ if (typeof manager2.searchMessagesSemantically === "function") {
545
+ const results = await manager2.searchMessagesSemantically({
546
+ userId: input.userId,
547
+ queryEmbedding,
548
+ limit
549
+ });
550
+ semantic = results.map((r) => ({
551
+ type: "memory",
552
+ id: r.id,
553
+ content: r.content,
554
+ similarity: r.similarity,
555
+ metadata: r.metadata ?? {}
556
+ }));
557
+ }
558
+ } catch (error) {
559
+ logger.warn?.("[memory-store] SQLite semantic search failed:", error);
560
+ }
561
+ }
562
+ let lexical = [];
563
+ if (typeof deps.searchRawMessagesLexical === "function") {
564
+ const keywords = deriveLexicalKeywords(input.query);
565
+ if (keywords.length > 0) {
566
+ try {
567
+ const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
568
+ lexical = (await Promise.all(
569
+ lexFilters.map(
570
+ (botId) => deps.searchRawMessagesLexical({
571
+ userId: input.userId,
572
+ keywords,
573
+ limit,
574
+ botId
575
+ })
576
+ )
577
+ )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
578
+ ...toMemoryResult(hit),
579
+ metadata: { ...hit.metadata, scoring: "bm25" }
580
+ }));
581
+ } catch (error) {
582
+ logger.warn?.("[memory-store] lexical memory search failed:", error);
583
+ warnings.push({
584
+ source: "memory",
585
+ code: "memory_lexical_search_failed",
586
+ message: error.message ?? "memory_lexical_search_failed"
587
+ });
588
+ }
589
+ }
590
+ } else if (input.mergeStrategy === "rrf") {
591
+ warnings.push({
592
+ source: "memory",
593
+ code: "memory_lexical_search_not_configured",
594
+ message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
595
+ });
596
+ }
597
+ return { semantic, lexical };
377
598
  }
378
599
  async function searchRawMemorySemantically(input) {
379
600
  const limit = clampUnifiedMemorySearchLimit(input.limit);
380
601
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
381
- return runMemorySource(input, limit, threshold);
602
+ const sub = await runMemorySource(input, limit, threshold, []);
603
+ return mergeAcrossSources({
604
+ memorySubs: sub,
605
+ insightHits: [],
606
+ knowledgeHits: [],
607
+ limit,
608
+ strategy: normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy)
609
+ });
382
610
  }
383
611
  return { searchUnifiedMemory, searchRawMemorySemantically };
384
612
  }
613
+ function mergeAcrossSources(input) {
614
+ const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
615
+ const all = [
616
+ ...memorySubs.semantic,
617
+ ...memorySubs.lexical,
618
+ ...input.insightHits,
619
+ ...input.knowledgeHits
620
+ ];
621
+ if (input.strategy !== "rrf") {
622
+ return mergeUnifiedMemorySearchResults(all, input.limit);
623
+ }
624
+ const lists = [];
625
+ if (memorySubs.semantic.length > 0) {
626
+ lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
627
+ }
628
+ if (memorySubs.lexical.length > 0) {
629
+ lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
630
+ }
631
+ if (input.insightHits.length > 0) {
632
+ lists.push({ name: "insights", hits: input.insightHits });
633
+ }
634
+ if (input.knowledgeHits.length > 0) {
635
+ lists.push({ name: "knowledge", hits: input.knowledgeHits });
636
+ }
637
+ if (lists.length <= 1) {
638
+ return mergeUnifiedMemorySearchResults(all, input.limit);
639
+ }
640
+ return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
641
+ }
385
642
 
386
- export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };
387
- //# sourceMappingURL=unified-search.js.map
388
- //# sourceMappingURL=unified-search.js.map
643
+ export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryMergeStrategy, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };