@melandlabs/memory-store 0.2.6 → 0.3.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.
- package/dist/config-2Q092MUz.d.ts +470 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +386 -105
- package/dist/index.d.ts +3 -3
- package/dist/index.js +745 -107
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +389 -105
- package/dist/search/unified-search.d.ts +3 -101
- package/dist/search/unified-search.js +387 -106
- package/dist/server/cli-http.js +395 -105
- package/dist/server/cli-mcp.js +398 -105
- package/dist/server/cli-shared.d.ts +1 -1
- package/dist/server/cli-shared.js +17 -5
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/raw-message-store.d.ts +3 -1
- package/dist/storage/raw-message-store.js +20 -9
- package/dist/storage/sqlite-raw-message-store.d.ts +6 -3
- package/dist/storage/sqlite-raw-message-store.js +4 -3
- package/dist/storage/sqlite-vector-index.d.ts +1 -1
- package/dist/storage/sqlite-vector-index.js +1 -1
- package/package.json +10 -9
- package/dist/config-BYfTzG3h.d.ts +0 -170
package/dist/mcp.d.ts
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -184,16 +184,17 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
184
184
|
lexicalSearchRawMessages: () => lexicalSearchRawMessages,
|
|
185
185
|
resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
|
|
186
186
|
});
|
|
187
|
-
function resolveSQLiteRawMessageDbPath() {
|
|
187
|
+
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
188
|
+
if (dbPath && dbPath.length > 0) return dbPath;
|
|
188
189
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
189
190
|
return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
|
|
190
191
|
}
|
|
191
192
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
192
193
|
return true;
|
|
193
194
|
}
|
|
194
|
-
async function getSQLiteRawMessageManager(
|
|
195
|
+
async function getSQLiteRawMessageManager(options = {}) {
|
|
195
196
|
if (!manager) {
|
|
196
|
-
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
197
|
+
const dbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
|
|
197
198
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
198
199
|
manager = new SQLiteRawMessageManager({
|
|
199
200
|
dbPath,
|
|
@@ -239,7 +240,7 @@ __export(raw_message_store_exports, {
|
|
|
239
240
|
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
240
241
|
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
241
242
|
});
|
|
242
|
-
function resolveBackend(
|
|
243
|
+
function resolveBackend(_env) {
|
|
243
244
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
244
245
|
if (explicit === "postgres") return "postgres";
|
|
245
246
|
return "sqlite";
|
|
@@ -266,33 +267,43 @@ function createRawMessageStore(options = {}) {
|
|
|
266
267
|
}
|
|
267
268
|
return pg;
|
|
268
269
|
}
|
|
269
|
-
return await getSQLiteRawMessageManager(
|
|
270
|
+
return await getSQLiteRawMessageManager({
|
|
271
|
+
dbPath: options.dbPath
|
|
272
|
+
});
|
|
270
273
|
},
|
|
271
274
|
async close() {
|
|
272
275
|
await closeSQLiteRawMessageManager();
|
|
273
276
|
}
|
|
274
277
|
};
|
|
275
278
|
}
|
|
279
|
+
function resolveDbPath(config) {
|
|
280
|
+
if (!config) return void 0;
|
|
281
|
+
if (config.dbPath) return config.dbPath;
|
|
282
|
+
if (config.db && typeof config.db === "object" && "path" in config.db) {
|
|
283
|
+
return config.db.path;
|
|
284
|
+
}
|
|
285
|
+
return void 0;
|
|
286
|
+
}
|
|
276
287
|
function configureRawMessageStore(config) {
|
|
277
288
|
moduleConfig = config;
|
|
278
|
-
moduleStore = createRawMessageStore({ env: config.env });
|
|
289
|
+
moduleStore = createRawMessageStore({ env: config.env, dbPath: resolveDbPath(config) });
|
|
279
290
|
return moduleStore;
|
|
280
291
|
}
|
|
281
292
|
function getRawMessageStorageBackend() {
|
|
282
293
|
if (!moduleStore) {
|
|
283
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
294
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
284
295
|
}
|
|
285
296
|
return moduleStore.getBackend();
|
|
286
297
|
}
|
|
287
298
|
function isRawMessageStorageAvailable() {
|
|
288
299
|
if (!moduleStore) {
|
|
289
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
300
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
290
301
|
}
|
|
291
302
|
return moduleStore.isAvailable();
|
|
292
303
|
}
|
|
293
304
|
async function getRawMessageManager() {
|
|
294
305
|
if (!moduleStore) {
|
|
295
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
306
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
296
307
|
}
|
|
297
308
|
return moduleStore.getManager();
|
|
298
309
|
}
|
|
@@ -362,6 +373,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
|
362
373
|
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
363
374
|
return value === "rrf" ? "rrf" : "similarity";
|
|
364
375
|
}
|
|
376
|
+
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
377
|
+
if (value === "rewrite" || value === "iterative") {
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
return defaultStrategy;
|
|
381
|
+
}
|
|
365
382
|
var DEFAULT_RRF_K = 60;
|
|
366
383
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
367
384
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
@@ -384,7 +401,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
|
384
401
|
}
|
|
385
402
|
}
|
|
386
403
|
}
|
|
387
|
-
const ranked = order.map((key) =>
|
|
404
|
+
const ranked = order.map((key) => {
|
|
405
|
+
const entry = scores.get(key);
|
|
406
|
+
if (!entry) {
|
|
407
|
+
throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
|
|
408
|
+
}
|
|
409
|
+
return entry;
|
|
410
|
+
}).sort((a, b) => {
|
|
388
411
|
if (b.rrf !== a.rrf) {
|
|
389
412
|
return b.rrf - a.rrf;
|
|
390
413
|
}
|
|
@@ -429,14 +452,222 @@ function isRawMemorySemanticResult(result) {
|
|
|
429
452
|
function deriveLexicalKeywords(query) {
|
|
430
453
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
431
454
|
}
|
|
455
|
+
function parseDateBoundary(value, role) {
|
|
456
|
+
const trimmed = value.trim();
|
|
457
|
+
if (!trimmed) {
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
460
|
+
const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
461
|
+
if (dateOnlyMatch) {
|
|
462
|
+
const year = Number.parseInt(dateOnlyMatch[1], 10);
|
|
463
|
+
const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
|
|
464
|
+
const day = Number.parseInt(dateOnlyMatch[3], 10);
|
|
465
|
+
if (role === "from") {
|
|
466
|
+
return Date.UTC(year, month, day);
|
|
467
|
+
}
|
|
468
|
+
return Date.UTC(year, month, day + 1) - 1;
|
|
469
|
+
}
|
|
470
|
+
const parsed = Date.parse(trimmed);
|
|
471
|
+
if (Number.isNaN(parsed)) {
|
|
472
|
+
return void 0;
|
|
473
|
+
}
|
|
474
|
+
return parsed;
|
|
475
|
+
}
|
|
476
|
+
function getCandidateTimestamp(metadata) {
|
|
477
|
+
for (const key of ["timestamp", "createdAt", "time"]) {
|
|
478
|
+
const value = metadata[key];
|
|
479
|
+
if (typeof value === "number") {
|
|
480
|
+
return value;
|
|
481
|
+
}
|
|
482
|
+
if (typeof value === "string") {
|
|
483
|
+
const parsed = Date.parse(value);
|
|
484
|
+
if (!Number.isNaN(parsed)) {
|
|
485
|
+
return parsed;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return void 0;
|
|
490
|
+
}
|
|
491
|
+
function filterByDateRange(items, dateFrom, dateTo) {
|
|
492
|
+
if (!dateFrom && !dateTo) {
|
|
493
|
+
return items;
|
|
494
|
+
}
|
|
495
|
+
const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
|
|
496
|
+
const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
|
|
497
|
+
if (fromMs === void 0 && toMs === void 0) {
|
|
498
|
+
return items;
|
|
499
|
+
}
|
|
500
|
+
return items.filter((item) => {
|
|
501
|
+
const ts = getCandidateTimestamp(item.metadata);
|
|
502
|
+
if (ts === void 0) {
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
if (fromMs !== void 0 && ts < fromMs) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
if (toMs !== void 0 && ts > toMs) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
return true;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
async function embedQueryVariant(embedQuery, input, query) {
|
|
515
|
+
return embedQuery({
|
|
516
|
+
userId: input.userId,
|
|
517
|
+
query,
|
|
518
|
+
authToken: input.authToken
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
522
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
523
|
+
let semantic = [];
|
|
524
|
+
if (isRawMessageChromaEnabled()) {
|
|
525
|
+
try {
|
|
526
|
+
semantic = (await Promise.all(
|
|
527
|
+
filters.map((filter) => {
|
|
528
|
+
const botId = "botId" in filter ? filter.botId : void 0;
|
|
529
|
+
return searchRawMessagesWithChroma({
|
|
530
|
+
userId: input.userId,
|
|
531
|
+
queryEmbedding,
|
|
532
|
+
limit,
|
|
533
|
+
threshold,
|
|
534
|
+
botId
|
|
535
|
+
});
|
|
536
|
+
})
|
|
537
|
+
)).flat().map(toMemoryResult);
|
|
538
|
+
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
539
|
+
backend: "chroma",
|
|
540
|
+
dimensions: queryEmbedding.length,
|
|
541
|
+
count: semantic.length
|
|
542
|
+
});
|
|
543
|
+
} catch (error) {
|
|
544
|
+
logger.warn?.(
|
|
545
|
+
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
546
|
+
error
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
550
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
551
|
+
semantic = (await Promise.all(
|
|
552
|
+
filters.map(
|
|
553
|
+
(filter) => searchRawMessagesAnn({
|
|
554
|
+
userId: input.userId,
|
|
555
|
+
queryEmbedding,
|
|
556
|
+
limit,
|
|
557
|
+
threshold,
|
|
558
|
+
botId: "botId" in filter ? filter.botId : void 0
|
|
559
|
+
})
|
|
560
|
+
)
|
|
561
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
562
|
+
}
|
|
563
|
+
if (semantic.length === 0) {
|
|
564
|
+
try {
|
|
565
|
+
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
566
|
+
const manager2 = await getRawMessageManager2();
|
|
567
|
+
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
568
|
+
const results = await manager2.searchMessagesSemantically({
|
|
569
|
+
userId: input.userId,
|
|
570
|
+
queryEmbedding,
|
|
571
|
+
limit,
|
|
572
|
+
threshold
|
|
573
|
+
});
|
|
574
|
+
semantic = results.map((r) => ({
|
|
575
|
+
type: "memory",
|
|
576
|
+
id: r.id,
|
|
577
|
+
content: r.content,
|
|
578
|
+
similarity: r.similarity,
|
|
579
|
+
metadata: r.metadata ?? {}
|
|
580
|
+
}));
|
|
581
|
+
}
|
|
582
|
+
} catch (error) {
|
|
583
|
+
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return semantic;
|
|
587
|
+
}
|
|
588
|
+
function mergeByMaxScore(lists) {
|
|
589
|
+
const best = /* @__PURE__ */ new Map();
|
|
590
|
+
for (const list of lists) {
|
|
591
|
+
for (const hit of list) {
|
|
592
|
+
const existing = best.get(hit.id);
|
|
593
|
+
if (!existing || hit.similarity > existing.similarity) {
|
|
594
|
+
best.set(hit.id, hit);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
599
|
+
}
|
|
600
|
+
function toIterativeRecallCandidate(hit) {
|
|
601
|
+
return {
|
|
602
|
+
id: hit.id,
|
|
603
|
+
content: hit.content,
|
|
604
|
+
similarity: hit.similarity,
|
|
605
|
+
metadata: hit.metadata
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
609
|
+
if (keywords.length === 0) {
|
|
610
|
+
return [];
|
|
611
|
+
}
|
|
612
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
613
|
+
try {
|
|
614
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
615
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
616
|
+
return (await Promise.all(
|
|
617
|
+
filters.map(
|
|
618
|
+
(botId) => searchRawMessagesLexical({
|
|
619
|
+
userId: input.userId,
|
|
620
|
+
keywords,
|
|
621
|
+
limit: Math.ceil(limit / filters.length),
|
|
622
|
+
botId
|
|
623
|
+
})
|
|
624
|
+
)
|
|
625
|
+
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
626
|
+
...toMemoryResult(hit),
|
|
627
|
+
metadata: { ...hit.metadata, scoring: "bm25" }
|
|
628
|
+
}));
|
|
629
|
+
} catch (error) {
|
|
630
|
+
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
try {
|
|
634
|
+
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
635
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
636
|
+
return (await Promise.all(
|
|
637
|
+
filters.map(
|
|
638
|
+
(botId) => lexicalSearchRawMessages2({
|
|
639
|
+
userId: input.userId,
|
|
640
|
+
keywords,
|
|
641
|
+
limit: Math.ceil(limit / filters.length),
|
|
642
|
+
botId
|
|
643
|
+
})
|
|
644
|
+
)
|
|
645
|
+
)).flat().filter(Boolean).map(
|
|
646
|
+
(r) => toMemoryResult(
|
|
647
|
+
r
|
|
648
|
+
)
|
|
649
|
+
);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
652
|
+
}
|
|
653
|
+
return [];
|
|
654
|
+
}
|
|
432
655
|
function createUnifiedSearch(deps = {}) {
|
|
433
656
|
const logger = deps.logger ?? console;
|
|
657
|
+
function resolveReasoningStrategy(input) {
|
|
658
|
+
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
659
|
+
if (requested !== "none") {
|
|
660
|
+
return requested;
|
|
661
|
+
}
|
|
662
|
+
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
663
|
+
}
|
|
434
664
|
async function searchUnifiedMemory(input) {
|
|
435
665
|
const query = input.query.trim();
|
|
436
666
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
437
667
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
438
668
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
439
669
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
670
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
440
671
|
const warnings = [];
|
|
441
672
|
if (!query) {
|
|
442
673
|
return {
|
|
@@ -448,10 +679,21 @@ function createUnifiedSearch(deps = {}) {
|
|
|
448
679
|
};
|
|
449
680
|
}
|
|
450
681
|
let memorySubs;
|
|
682
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
683
|
+
if (input.dateFrom || input.dateTo) {
|
|
684
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
685
|
+
}
|
|
451
686
|
if (sources.includes("memory")) {
|
|
452
687
|
if (isRawMessageStorageAvailable()) {
|
|
453
688
|
try {
|
|
454
|
-
memorySubs = await runMemorySource(
|
|
689
|
+
memorySubs = await runMemorySource(
|
|
690
|
+
input,
|
|
691
|
+
limit,
|
|
692
|
+
threshold,
|
|
693
|
+
warnings,
|
|
694
|
+
reasoningStrategy,
|
|
695
|
+
reasoningInfo
|
|
696
|
+
);
|
|
455
697
|
} catch (error) {
|
|
456
698
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
457
699
|
warnings.push({
|
|
@@ -530,120 +772,155 @@ function createUnifiedSearch(deps = {}) {
|
|
|
530
772
|
limit,
|
|
531
773
|
strategy: mergeStrategy
|
|
532
774
|
});
|
|
533
|
-
|
|
775
|
+
const output = {
|
|
534
776
|
query: input.query,
|
|
535
777
|
sources,
|
|
536
778
|
results: merged,
|
|
537
779
|
count: merged.length,
|
|
538
780
|
warnings
|
|
539
781
|
};
|
|
782
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
783
|
+
output.reasoning = reasoningInfo;
|
|
784
|
+
}
|
|
785
|
+
return output;
|
|
540
786
|
}
|
|
541
|
-
async function runMemorySource(input, limit, threshold, warnings) {
|
|
787
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
788
|
+
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
789
|
+
warnings.push({
|
|
790
|
+
source: "memory",
|
|
791
|
+
code: "memory_iterative_planner_not_configured",
|
|
792
|
+
message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
|
|
793
|
+
});
|
|
794
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
795
|
+
}
|
|
796
|
+
if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
|
|
797
|
+
warnings.push({
|
|
798
|
+
source: "memory",
|
|
799
|
+
code: "memory_query_rewrite_not_configured",
|
|
800
|
+
message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
|
|
801
|
+
});
|
|
802
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
803
|
+
}
|
|
804
|
+
if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
|
|
805
|
+
const planner = deps.reasoning.iterativePlanner;
|
|
806
|
+
const result = await planner.plan({
|
|
807
|
+
query: input.query,
|
|
808
|
+
dateFrom: input.dateFrom,
|
|
809
|
+
dateTo: input.dateTo,
|
|
810
|
+
executor: {
|
|
811
|
+
search: async (request) => {
|
|
812
|
+
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
813
|
+
const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
|
|
814
|
+
let semanticHits = [];
|
|
815
|
+
if (typeof deps.embedQuery === "function") {
|
|
816
|
+
try {
|
|
817
|
+
const semanticQuery = keywords2.join(" ");
|
|
818
|
+
const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
|
|
819
|
+
semanticHits = await runSemanticSearchForEmbedding(
|
|
820
|
+
deps,
|
|
821
|
+
input,
|
|
822
|
+
queryEmbedding,
|
|
823
|
+
limit,
|
|
824
|
+
threshold,
|
|
825
|
+
logger
|
|
826
|
+
);
|
|
827
|
+
} catch (error) {
|
|
828
|
+
logger.warn?.(
|
|
829
|
+
"[memory-store] Iterative semantic search failed; using lexical results only:",
|
|
830
|
+
error
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const candidates = filterByDateRange(
|
|
835
|
+
mergeByMaxScore([
|
|
836
|
+
lexicalHits.map(toIterativeRecallCandidate),
|
|
837
|
+
semanticHits.map(toIterativeRecallCandidate)
|
|
838
|
+
]),
|
|
839
|
+
request.dateFrom,
|
|
840
|
+
request.dateTo
|
|
841
|
+
);
|
|
842
|
+
return { candidates };
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
if (reasoningInfo) {
|
|
847
|
+
reasoningInfo.iterations = result.stats.iterations;
|
|
848
|
+
reasoningInfo.evidenceCount = result.evidence.length;
|
|
849
|
+
if (planner.lastDegraded?.()) {
|
|
850
|
+
reasoningInfo.degraded = true;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const semantic2 = result.evidence.map((candidate) => ({
|
|
854
|
+
type: "memory",
|
|
855
|
+
id: candidate.id,
|
|
856
|
+
content: candidate.content,
|
|
857
|
+
similarity: candidate.similarity,
|
|
858
|
+
metadata: { ...candidate.metadata, reasoning: "iterative" }
|
|
859
|
+
}));
|
|
860
|
+
return { semantic: semantic2, lexical: [] };
|
|
861
|
+
}
|
|
542
862
|
if (typeof deps.embedQuery !== "function") {
|
|
543
863
|
warnings.push({
|
|
544
864
|
source: "memory",
|
|
545
865
|
code: "memory_lexical_search_fallback",
|
|
546
866
|
message: "Semantic search not configured, using keyword search as fallback"
|
|
547
867
|
});
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
const results = await Promise.all(
|
|
555
|
-
filters2.map(
|
|
556
|
-
(botId) => lexicalSearchRawMessages2({
|
|
557
|
-
userId: input.userId,
|
|
558
|
-
keywords,
|
|
559
|
-
limit: Math.ceil(limit / filters2.length),
|
|
560
|
-
botId
|
|
561
|
-
})
|
|
562
|
-
)
|
|
563
|
-
);
|
|
564
|
-
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
565
|
-
} catch (error) {
|
|
566
|
-
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
868
|
+
const keywords2 = deriveLexicalKeywords(input.query);
|
|
869
|
+
const lexical2 = filterByDateRange(
|
|
870
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
871
|
+
input.dateFrom,
|
|
872
|
+
input.dateTo
|
|
873
|
+
);
|
|
569
874
|
return { semantic: [], lexical: lexical2 };
|
|
570
875
|
}
|
|
571
|
-
const queryEmbedding = await deps.embedQuery({
|
|
572
|
-
userId: input.userId,
|
|
573
|
-
query: input.query,
|
|
574
|
-
authToken: input.authToken
|
|
575
|
-
});
|
|
576
|
-
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
577
876
|
let semantic = [];
|
|
578
|
-
|
|
877
|
+
const embedQuery = deps.embedQuery;
|
|
878
|
+
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
579
879
|
try {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
userId: input.userId,
|
|
585
|
-
queryEmbedding,
|
|
586
|
-
limit,
|
|
587
|
-
threshold,
|
|
588
|
-
botId
|
|
589
|
-
});
|
|
590
|
-
})
|
|
591
|
-
)).flat().map(toMemoryResult);
|
|
592
|
-
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
593
|
-
backend: "chroma",
|
|
594
|
-
dimensions: queryEmbedding.length,
|
|
595
|
-
count: semantic.length
|
|
880
|
+
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
881
|
+
query: input.query,
|
|
882
|
+
userId: input.userId,
|
|
883
|
+
authToken: input.authToken
|
|
596
884
|
});
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
885
|
+
if (reasoningInfo) {
|
|
886
|
+
reasoningInfo.rewrittenQueries = variants;
|
|
887
|
+
if (deps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
888
|
+
reasoningInfo.degraded = true;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
const embeddings = await Promise.all(
|
|
892
|
+
variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
|
|
601
893
|
);
|
|
894
|
+
const lists = await Promise.all(
|
|
895
|
+
embeddings.map(
|
|
896
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
897
|
+
)
|
|
898
|
+
);
|
|
899
|
+
semantic = mergeByMaxScore(lists);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
|
|
902
|
+
warnings.push({
|
|
903
|
+
source: "memory",
|
|
904
|
+
code: "memory_query_rewrite_failed",
|
|
905
|
+
message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
|
|
906
|
+
});
|
|
907
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
602
908
|
}
|
|
603
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
604
|
-
semantic = (await Promise.all(
|
|
605
|
-
filters.map(
|
|
606
|
-
(filter) => deps.searchRawMessagesAnn({
|
|
607
|
-
userId: input.userId,
|
|
608
|
-
queryEmbedding,
|
|
609
|
-
limit,
|
|
610
|
-
threshold,
|
|
611
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
612
|
-
})
|
|
613
|
-
)
|
|
614
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
615
909
|
}
|
|
616
910
|
if (semantic.length === 0) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const manager2 = await getRawMessageManager2();
|
|
620
|
-
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
621
|
-
const results = await manager2.searchMessagesSemantically({
|
|
622
|
-
userId: input.userId,
|
|
623
|
-
queryEmbedding,
|
|
624
|
-
limit
|
|
625
|
-
});
|
|
626
|
-
semantic = results.map((r) => ({
|
|
627
|
-
type: "memory",
|
|
628
|
-
id: r.id,
|
|
629
|
-
content: r.content,
|
|
630
|
-
similarity: r.similarity,
|
|
631
|
-
metadata: r.metadata ?? {}
|
|
632
|
-
}));
|
|
633
|
-
}
|
|
634
|
-
} catch (error) {
|
|
635
|
-
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
636
|
-
}
|
|
911
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
912
|
+
semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
|
|
637
913
|
}
|
|
638
914
|
let lexical = [];
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (
|
|
915
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
916
|
+
if (keywords.length > 0) {
|
|
917
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
642
918
|
try {
|
|
643
919
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
920
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
644
921
|
lexical = (await Promise.all(
|
|
645
922
|
lexFilters.map(
|
|
646
|
-
(botId) =>
|
|
923
|
+
(botId) => searchRawMessagesLexical({
|
|
647
924
|
userId: input.userId,
|
|
648
925
|
keywords,
|
|
649
926
|
limit,
|
|
@@ -662,20 +939,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
662
939
|
message: error.message ?? "memory_lexical_search_failed"
|
|
663
940
|
});
|
|
664
941
|
}
|
|
942
|
+
} else if (input.mergeStrategy === "rrf") {
|
|
943
|
+
warnings.push({
|
|
944
|
+
source: "memory",
|
|
945
|
+
code: "memory_lexical_search_not_configured",
|
|
946
|
+
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
947
|
+
});
|
|
665
948
|
}
|
|
666
|
-
} else if (input.mergeStrategy === "rrf") {
|
|
667
|
-
warnings.push({
|
|
668
|
-
source: "memory",
|
|
669
|
-
code: "memory_lexical_search_not_configured",
|
|
670
|
-
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
671
|
-
});
|
|
672
949
|
}
|
|
673
|
-
return {
|
|
950
|
+
return {
|
|
951
|
+
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
952
|
+
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
953
|
+
};
|
|
674
954
|
}
|
|
675
955
|
async function searchRawMemorySemantically(input) {
|
|
676
956
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
677
957
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
678
|
-
const
|
|
958
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
959
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
679
960
|
return mergeAcrossSources({
|
|
680
961
|
memorySubs: sub,
|
|
681
962
|
insightHits: [],
|
|
@@ -802,6 +1083,7 @@ async function startMcpServer(options = {}) {
|
|
|
802
1083
|
{
|
|
803
1084
|
title: "Search Unified Memory",
|
|
804
1085
|
description: "Semantic search across raw messages (always), and optionally insights and uploaded knowledge.",
|
|
1086
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
805
1087
|
inputSchema: searchSchema
|
|
806
1088
|
},
|
|
807
1089
|
async (args) => {
|
|
@@ -817,6 +1099,7 @@ async function startMcpServer(options = {}) {
|
|
|
817
1099
|
{
|
|
818
1100
|
title: "Write Raw Message",
|
|
819
1101
|
description: "Persist a single raw message to the user's memory store.",
|
|
1102
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
820
1103
|
inputSchema: writeSchema
|
|
821
1104
|
},
|
|
822
1105
|
async (args) => {
|
|
@@ -856,6 +1139,7 @@ async function startMcpServer(options = {}) {
|
|
|
856
1139
|
{
|
|
857
1140
|
title: "Get Raw Message",
|
|
858
1141
|
description: "Fetch a single raw message by its message id.",
|
|
1142
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
859
1143
|
inputSchema: getSchema
|
|
860
1144
|
},
|
|
861
1145
|
async (args) => {
|