@melandlabs/memory-store 0.2.7 → 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 +367 -98
- package/dist/index.d.ts +3 -3
- package/dist/index.js +724 -99
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +370 -98
- package/dist/search/unified-search.d.ts +3 -101
- package/dist/search/unified-search.js +368 -99
- package/dist/server/cli-http.js +376 -98
- package/dist/server/cli-mcp.js +379 -98
- package/dist/server/cli-shared.d.ts +1 -1
- package/dist/server/cli-shared.js +10 -1
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/raw-message-store.d.ts +1 -1
- package/dist/storage/raw-message-store.js +1 -1
- package/dist/storage/sqlite-raw-message-store.d.ts +1 -1
- package/dist/storage/sqlite-vector-index.d.ts +1 -1
- package/package.json +10 -9
- package/dist/config-Ll6ktm4D.d.ts +0 -183
package/dist/server/cli-mcp.js
CHANGED
|
@@ -241,7 +241,7 @@ __export(raw_message_store_exports, {
|
|
|
241
241
|
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
242
242
|
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
243
243
|
});
|
|
244
|
-
function resolveBackend(
|
|
244
|
+
function resolveBackend(_env) {
|
|
245
245
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
246
246
|
if (explicit === "postgres") return "postgres";
|
|
247
247
|
return "sqlite";
|
|
@@ -374,6 +374,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
|
374
374
|
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
375
375
|
return value === "rrf" ? "rrf" : "similarity";
|
|
376
376
|
}
|
|
377
|
+
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
378
|
+
if (value === "rewrite" || value === "iterative") {
|
|
379
|
+
return value;
|
|
380
|
+
}
|
|
381
|
+
return defaultStrategy;
|
|
382
|
+
}
|
|
377
383
|
var DEFAULT_RRF_K = 60;
|
|
378
384
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
379
385
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
@@ -396,7 +402,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
|
396
402
|
}
|
|
397
403
|
}
|
|
398
404
|
}
|
|
399
|
-
const ranked = order.map((key) =>
|
|
405
|
+
const ranked = order.map((key) => {
|
|
406
|
+
const entry = scores.get(key);
|
|
407
|
+
if (!entry) {
|
|
408
|
+
throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
|
|
409
|
+
}
|
|
410
|
+
return entry;
|
|
411
|
+
}).sort((a, b) => {
|
|
400
412
|
if (b.rrf !== a.rrf) {
|
|
401
413
|
return b.rrf - a.rrf;
|
|
402
414
|
}
|
|
@@ -441,14 +453,222 @@ function isRawMemorySemanticResult(result) {
|
|
|
441
453
|
function deriveLexicalKeywords(query) {
|
|
442
454
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
443
455
|
}
|
|
456
|
+
function parseDateBoundary(value, role) {
|
|
457
|
+
const trimmed = value.trim();
|
|
458
|
+
if (!trimmed) {
|
|
459
|
+
return void 0;
|
|
460
|
+
}
|
|
461
|
+
const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
462
|
+
if (dateOnlyMatch) {
|
|
463
|
+
const year = Number.parseInt(dateOnlyMatch[1], 10);
|
|
464
|
+
const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
|
|
465
|
+
const day = Number.parseInt(dateOnlyMatch[3], 10);
|
|
466
|
+
if (role === "from") {
|
|
467
|
+
return Date.UTC(year, month, day);
|
|
468
|
+
}
|
|
469
|
+
return Date.UTC(year, month, day + 1) - 1;
|
|
470
|
+
}
|
|
471
|
+
const parsed = Date.parse(trimmed);
|
|
472
|
+
if (Number.isNaN(parsed)) {
|
|
473
|
+
return void 0;
|
|
474
|
+
}
|
|
475
|
+
return parsed;
|
|
476
|
+
}
|
|
477
|
+
function getCandidateTimestamp(metadata) {
|
|
478
|
+
for (const key of ["timestamp", "createdAt", "time"]) {
|
|
479
|
+
const value = metadata[key];
|
|
480
|
+
if (typeof value === "number") {
|
|
481
|
+
return value;
|
|
482
|
+
}
|
|
483
|
+
if (typeof value === "string") {
|
|
484
|
+
const parsed = Date.parse(value);
|
|
485
|
+
if (!Number.isNaN(parsed)) {
|
|
486
|
+
return parsed;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
function filterByDateRange(items, dateFrom, dateTo) {
|
|
493
|
+
if (!dateFrom && !dateTo) {
|
|
494
|
+
return items;
|
|
495
|
+
}
|
|
496
|
+
const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
|
|
497
|
+
const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
|
|
498
|
+
if (fromMs === void 0 && toMs === void 0) {
|
|
499
|
+
return items;
|
|
500
|
+
}
|
|
501
|
+
return items.filter((item) => {
|
|
502
|
+
const ts = getCandidateTimestamp(item.metadata);
|
|
503
|
+
if (ts === void 0) {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
if (fromMs !== void 0 && ts < fromMs) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
if (toMs !== void 0 && ts > toMs) {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
return true;
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
async function embedQueryVariant(embedQuery, input, query) {
|
|
516
|
+
return embedQuery({
|
|
517
|
+
userId: input.userId,
|
|
518
|
+
query,
|
|
519
|
+
authToken: input.authToken
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
523
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
524
|
+
let semantic = [];
|
|
525
|
+
if (isRawMessageChromaEnabled()) {
|
|
526
|
+
try {
|
|
527
|
+
semantic = (await Promise.all(
|
|
528
|
+
filters.map((filter) => {
|
|
529
|
+
const botId = "botId" in filter ? filter.botId : void 0;
|
|
530
|
+
return searchRawMessagesWithChroma({
|
|
531
|
+
userId: input.userId,
|
|
532
|
+
queryEmbedding,
|
|
533
|
+
limit,
|
|
534
|
+
threshold,
|
|
535
|
+
botId
|
|
536
|
+
});
|
|
537
|
+
})
|
|
538
|
+
)).flat().map(toMemoryResult);
|
|
539
|
+
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
540
|
+
backend: "chroma",
|
|
541
|
+
dimensions: queryEmbedding.length,
|
|
542
|
+
count: semantic.length
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
logger.warn?.(
|
|
546
|
+
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
547
|
+
error
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
551
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
552
|
+
semantic = (await Promise.all(
|
|
553
|
+
filters.map(
|
|
554
|
+
(filter) => searchRawMessagesAnn({
|
|
555
|
+
userId: input.userId,
|
|
556
|
+
queryEmbedding,
|
|
557
|
+
limit,
|
|
558
|
+
threshold,
|
|
559
|
+
botId: "botId" in filter ? filter.botId : void 0
|
|
560
|
+
})
|
|
561
|
+
)
|
|
562
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
563
|
+
}
|
|
564
|
+
if (semantic.length === 0) {
|
|
565
|
+
try {
|
|
566
|
+
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
567
|
+
const manager2 = await getRawMessageManager2();
|
|
568
|
+
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
569
|
+
const results = await manager2.searchMessagesSemantically({
|
|
570
|
+
userId: input.userId,
|
|
571
|
+
queryEmbedding,
|
|
572
|
+
limit,
|
|
573
|
+
threshold
|
|
574
|
+
});
|
|
575
|
+
semantic = results.map((r) => ({
|
|
576
|
+
type: "memory",
|
|
577
|
+
id: r.id,
|
|
578
|
+
content: r.content,
|
|
579
|
+
similarity: r.similarity,
|
|
580
|
+
metadata: r.metadata ?? {}
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
} catch (error) {
|
|
584
|
+
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return semantic;
|
|
588
|
+
}
|
|
589
|
+
function mergeByMaxScore(lists) {
|
|
590
|
+
const best = /* @__PURE__ */ new Map();
|
|
591
|
+
for (const list of lists) {
|
|
592
|
+
for (const hit of list) {
|
|
593
|
+
const existing = best.get(hit.id);
|
|
594
|
+
if (!existing || hit.similarity > existing.similarity) {
|
|
595
|
+
best.set(hit.id, hit);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
600
|
+
}
|
|
601
|
+
function toIterativeRecallCandidate(hit) {
|
|
602
|
+
return {
|
|
603
|
+
id: hit.id,
|
|
604
|
+
content: hit.content,
|
|
605
|
+
similarity: hit.similarity,
|
|
606
|
+
metadata: hit.metadata
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
610
|
+
if (keywords.length === 0) {
|
|
611
|
+
return [];
|
|
612
|
+
}
|
|
613
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
614
|
+
try {
|
|
615
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
616
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
617
|
+
return (await Promise.all(
|
|
618
|
+
filters.map(
|
|
619
|
+
(botId) => searchRawMessagesLexical({
|
|
620
|
+
userId: input.userId,
|
|
621
|
+
keywords,
|
|
622
|
+
limit: Math.ceil(limit / filters.length),
|
|
623
|
+
botId
|
|
624
|
+
})
|
|
625
|
+
)
|
|
626
|
+
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
627
|
+
...toMemoryResult(hit),
|
|
628
|
+
metadata: { ...hit.metadata, scoring: "bm25" }
|
|
629
|
+
}));
|
|
630
|
+
} catch (error) {
|
|
631
|
+
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
636
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
637
|
+
return (await Promise.all(
|
|
638
|
+
filters.map(
|
|
639
|
+
(botId) => lexicalSearchRawMessages2({
|
|
640
|
+
userId: input.userId,
|
|
641
|
+
keywords,
|
|
642
|
+
limit: Math.ceil(limit / filters.length),
|
|
643
|
+
botId
|
|
644
|
+
})
|
|
645
|
+
)
|
|
646
|
+
)).flat().filter(Boolean).map(
|
|
647
|
+
(r) => toMemoryResult(
|
|
648
|
+
r
|
|
649
|
+
)
|
|
650
|
+
);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
653
|
+
}
|
|
654
|
+
return [];
|
|
655
|
+
}
|
|
444
656
|
function createUnifiedSearch(deps = {}) {
|
|
445
657
|
const logger = deps.logger ?? console;
|
|
658
|
+
function resolveReasoningStrategy(input) {
|
|
659
|
+
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
660
|
+
if (requested !== "none") {
|
|
661
|
+
return requested;
|
|
662
|
+
}
|
|
663
|
+
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
664
|
+
}
|
|
446
665
|
async function searchUnifiedMemory(input) {
|
|
447
666
|
const query = input.query.trim();
|
|
448
667
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
449
668
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
450
669
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
451
670
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
671
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
452
672
|
const warnings = [];
|
|
453
673
|
if (!query) {
|
|
454
674
|
return {
|
|
@@ -460,10 +680,21 @@ function createUnifiedSearch(deps = {}) {
|
|
|
460
680
|
};
|
|
461
681
|
}
|
|
462
682
|
let memorySubs;
|
|
683
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
684
|
+
if (input.dateFrom || input.dateTo) {
|
|
685
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
686
|
+
}
|
|
463
687
|
if (sources.includes("memory")) {
|
|
464
688
|
if (isRawMessageStorageAvailable()) {
|
|
465
689
|
try {
|
|
466
|
-
memorySubs = await runMemorySource(
|
|
690
|
+
memorySubs = await runMemorySource(
|
|
691
|
+
input,
|
|
692
|
+
limit,
|
|
693
|
+
threshold,
|
|
694
|
+
warnings,
|
|
695
|
+
reasoningStrategy,
|
|
696
|
+
reasoningInfo
|
|
697
|
+
);
|
|
467
698
|
} catch (error) {
|
|
468
699
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
469
700
|
warnings.push({
|
|
@@ -542,121 +773,155 @@ function createUnifiedSearch(deps = {}) {
|
|
|
542
773
|
limit,
|
|
543
774
|
strategy: mergeStrategy
|
|
544
775
|
});
|
|
545
|
-
|
|
776
|
+
const output = {
|
|
546
777
|
query: input.query,
|
|
547
778
|
sources,
|
|
548
779
|
results: merged,
|
|
549
780
|
count: merged.length,
|
|
550
781
|
warnings
|
|
551
782
|
};
|
|
783
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
784
|
+
output.reasoning = reasoningInfo;
|
|
785
|
+
}
|
|
786
|
+
return output;
|
|
552
787
|
}
|
|
553
|
-
async function runMemorySource(input, limit, threshold, warnings) {
|
|
788
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
789
|
+
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
790
|
+
warnings.push({
|
|
791
|
+
source: "memory",
|
|
792
|
+
code: "memory_iterative_planner_not_configured",
|
|
793
|
+
message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
|
|
794
|
+
});
|
|
795
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
796
|
+
}
|
|
797
|
+
if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
|
|
798
|
+
warnings.push({
|
|
799
|
+
source: "memory",
|
|
800
|
+
code: "memory_query_rewrite_not_configured",
|
|
801
|
+
message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
|
|
802
|
+
});
|
|
803
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
804
|
+
}
|
|
805
|
+
if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
|
|
806
|
+
const planner = deps.reasoning.iterativePlanner;
|
|
807
|
+
const result = await planner.plan({
|
|
808
|
+
query: input.query,
|
|
809
|
+
dateFrom: input.dateFrom,
|
|
810
|
+
dateTo: input.dateTo,
|
|
811
|
+
executor: {
|
|
812
|
+
search: async (request) => {
|
|
813
|
+
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
814
|
+
const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
|
|
815
|
+
let semanticHits = [];
|
|
816
|
+
if (typeof deps.embedQuery === "function") {
|
|
817
|
+
try {
|
|
818
|
+
const semanticQuery = keywords2.join(" ");
|
|
819
|
+
const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
|
|
820
|
+
semanticHits = await runSemanticSearchForEmbedding(
|
|
821
|
+
deps,
|
|
822
|
+
input,
|
|
823
|
+
queryEmbedding,
|
|
824
|
+
limit,
|
|
825
|
+
threshold,
|
|
826
|
+
logger
|
|
827
|
+
);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
logger.warn?.(
|
|
830
|
+
"[memory-store] Iterative semantic search failed; using lexical results only:",
|
|
831
|
+
error
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const candidates = filterByDateRange(
|
|
836
|
+
mergeByMaxScore([
|
|
837
|
+
lexicalHits.map(toIterativeRecallCandidate),
|
|
838
|
+
semanticHits.map(toIterativeRecallCandidate)
|
|
839
|
+
]),
|
|
840
|
+
request.dateFrom,
|
|
841
|
+
request.dateTo
|
|
842
|
+
);
|
|
843
|
+
return { candidates };
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
if (reasoningInfo) {
|
|
848
|
+
reasoningInfo.iterations = result.stats.iterations;
|
|
849
|
+
reasoningInfo.evidenceCount = result.evidence.length;
|
|
850
|
+
if (planner.lastDegraded?.()) {
|
|
851
|
+
reasoningInfo.degraded = true;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const semantic2 = result.evidence.map((candidate) => ({
|
|
855
|
+
type: "memory",
|
|
856
|
+
id: candidate.id,
|
|
857
|
+
content: candidate.content,
|
|
858
|
+
similarity: candidate.similarity,
|
|
859
|
+
metadata: { ...candidate.metadata, reasoning: "iterative" }
|
|
860
|
+
}));
|
|
861
|
+
return { semantic: semantic2, lexical: [] };
|
|
862
|
+
}
|
|
554
863
|
if (typeof deps.embedQuery !== "function") {
|
|
555
864
|
warnings.push({
|
|
556
865
|
source: "memory",
|
|
557
866
|
code: "memory_lexical_search_fallback",
|
|
558
867
|
message: "Semantic search not configured, using keyword search as fallback"
|
|
559
868
|
});
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const results = await Promise.all(
|
|
567
|
-
filters2.map(
|
|
568
|
-
(botId) => lexicalSearchRawMessages2({
|
|
569
|
-
userId: input.userId,
|
|
570
|
-
keywords,
|
|
571
|
-
limit: Math.ceil(limit / filters2.length),
|
|
572
|
-
botId
|
|
573
|
-
})
|
|
574
|
-
)
|
|
575
|
-
);
|
|
576
|
-
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
577
|
-
} catch (error) {
|
|
578
|
-
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
869
|
+
const keywords2 = deriveLexicalKeywords(input.query);
|
|
870
|
+
const lexical2 = filterByDateRange(
|
|
871
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
872
|
+
input.dateFrom,
|
|
873
|
+
input.dateTo
|
|
874
|
+
);
|
|
581
875
|
return { semantic: [], lexical: lexical2 };
|
|
582
876
|
}
|
|
583
|
-
const queryEmbedding = await deps.embedQuery({
|
|
584
|
-
userId: input.userId,
|
|
585
|
-
query: input.query,
|
|
586
|
-
authToken: input.authToken
|
|
587
|
-
});
|
|
588
|
-
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
589
877
|
let semantic = [];
|
|
590
|
-
|
|
878
|
+
const embedQuery = deps.embedQuery;
|
|
879
|
+
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
591
880
|
try {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
userId: input.userId,
|
|
597
|
-
queryEmbedding,
|
|
598
|
-
limit,
|
|
599
|
-
threshold,
|
|
600
|
-
botId
|
|
601
|
-
});
|
|
602
|
-
})
|
|
603
|
-
)).flat().map(toMemoryResult);
|
|
604
|
-
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
605
|
-
backend: "chroma",
|
|
606
|
-
dimensions: queryEmbedding.length,
|
|
607
|
-
count: semantic.length
|
|
881
|
+
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
882
|
+
query: input.query,
|
|
883
|
+
userId: input.userId,
|
|
884
|
+
authToken: input.authToken
|
|
608
885
|
});
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
886
|
+
if (reasoningInfo) {
|
|
887
|
+
reasoningInfo.rewrittenQueries = variants;
|
|
888
|
+
if (deps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
889
|
+
reasoningInfo.degraded = true;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
const embeddings = await Promise.all(
|
|
893
|
+
variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
|
|
894
|
+
);
|
|
895
|
+
const lists = await Promise.all(
|
|
896
|
+
embeddings.map(
|
|
897
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
898
|
+
)
|
|
613
899
|
);
|
|
900
|
+
semantic = mergeByMaxScore(lists);
|
|
901
|
+
} catch (error) {
|
|
902
|
+
logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
|
|
903
|
+
warnings.push({
|
|
904
|
+
source: "memory",
|
|
905
|
+
code: "memory_query_rewrite_failed",
|
|
906
|
+
message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
|
|
907
|
+
});
|
|
908
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
614
909
|
}
|
|
615
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
616
|
-
semantic = (await Promise.all(
|
|
617
|
-
filters.map(
|
|
618
|
-
(filter) => deps.searchRawMessagesAnn({
|
|
619
|
-
userId: input.userId,
|
|
620
|
-
queryEmbedding,
|
|
621
|
-
limit,
|
|
622
|
-
threshold,
|
|
623
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
624
|
-
})
|
|
625
|
-
)
|
|
626
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
627
910
|
}
|
|
628
911
|
if (semantic.length === 0) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const manager2 = await getRawMessageManager2();
|
|
632
|
-
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
633
|
-
const results = await manager2.searchMessagesSemantically({
|
|
634
|
-
userId: input.userId,
|
|
635
|
-
queryEmbedding,
|
|
636
|
-
limit,
|
|
637
|
-
threshold
|
|
638
|
-
});
|
|
639
|
-
semantic = results.map((r) => ({
|
|
640
|
-
type: "memory",
|
|
641
|
-
id: r.id,
|
|
642
|
-
content: r.content,
|
|
643
|
-
similarity: r.similarity,
|
|
644
|
-
metadata: r.metadata ?? {}
|
|
645
|
-
}));
|
|
646
|
-
}
|
|
647
|
-
} catch (error) {
|
|
648
|
-
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
649
|
-
}
|
|
912
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
913
|
+
semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
|
|
650
914
|
}
|
|
651
915
|
let lexical = [];
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (
|
|
916
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
917
|
+
if (keywords.length > 0) {
|
|
918
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
655
919
|
try {
|
|
656
920
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
921
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
657
922
|
lexical = (await Promise.all(
|
|
658
923
|
lexFilters.map(
|
|
659
|
-
(botId) =>
|
|
924
|
+
(botId) => searchRawMessagesLexical({
|
|
660
925
|
userId: input.userId,
|
|
661
926
|
keywords,
|
|
662
927
|
limit,
|
|
@@ -675,20 +940,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
675
940
|
message: error.message ?? "memory_lexical_search_failed"
|
|
676
941
|
});
|
|
677
942
|
}
|
|
943
|
+
} else if (input.mergeStrategy === "rrf") {
|
|
944
|
+
warnings.push({
|
|
945
|
+
source: "memory",
|
|
946
|
+
code: "memory_lexical_search_not_configured",
|
|
947
|
+
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
948
|
+
});
|
|
678
949
|
}
|
|
679
|
-
} else if (input.mergeStrategy === "rrf") {
|
|
680
|
-
warnings.push({
|
|
681
|
-
source: "memory",
|
|
682
|
-
code: "memory_lexical_search_not_configured",
|
|
683
|
-
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
684
|
-
});
|
|
685
950
|
}
|
|
686
|
-
return {
|
|
951
|
+
return {
|
|
952
|
+
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
953
|
+
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
954
|
+
};
|
|
687
955
|
}
|
|
688
956
|
async function searchRawMemorySemantically(input) {
|
|
689
957
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
690
958
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
691
|
-
const
|
|
959
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
960
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
692
961
|
return mergeAcrossSources({
|
|
693
962
|
memorySubs: sub,
|
|
694
963
|
insightHits: [],
|
|
@@ -815,6 +1084,7 @@ async function startMcpServer(options = {}) {
|
|
|
815
1084
|
{
|
|
816
1085
|
title: "Search Unified Memory",
|
|
817
1086
|
description: "Semantic search across raw messages (always), and optionally insights and uploaded knowledge.",
|
|
1087
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
818
1088
|
inputSchema: searchSchema
|
|
819
1089
|
},
|
|
820
1090
|
async (args) => {
|
|
@@ -830,6 +1100,7 @@ async function startMcpServer(options = {}) {
|
|
|
830
1100
|
{
|
|
831
1101
|
title: "Write Raw Message",
|
|
832
1102
|
description: "Persist a single raw message to the user's memory store.",
|
|
1103
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
833
1104
|
inputSchema: writeSchema
|
|
834
1105
|
},
|
|
835
1106
|
async (args) => {
|
|
@@ -869,6 +1140,7 @@ async function startMcpServer(options = {}) {
|
|
|
869
1140
|
{
|
|
870
1141
|
title: "Get Raw Message",
|
|
871
1142
|
description: "Fetch a single raw message by its message id.",
|
|
1143
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
872
1144
|
inputSchema: getSchema
|
|
873
1145
|
},
|
|
874
1146
|
async (args) => {
|
|
@@ -1077,6 +1349,9 @@ async function buildUnified(args) {
|
|
|
1077
1349
|
} else if (args.memoryBackend === "chroma") {
|
|
1078
1350
|
if (!args.chromaUrl)
|
|
1079
1351
|
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1352
|
+
if (!aiRag) {
|
|
1353
|
+
throw new Error("ai-rag modules not loaded");
|
|
1354
|
+
}
|
|
1080
1355
|
const store = new aiRag.ChromaVectorStore({
|
|
1081
1356
|
url: args.chromaUrl,
|
|
1082
1357
|
collectionName: "opencontext_raw_messages"
|
|
@@ -1115,6 +1390,9 @@ async function buildUnified(args) {
|
|
|
1115
1390
|
throw new Error("--insights-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1116
1391
|
if (!unified.embedQuery)
|
|
1117
1392
|
throw new Error("--insights-backend=chroma requires --embedding-provider local|openrouter");
|
|
1393
|
+
if (!aiRag) {
|
|
1394
|
+
throw new Error("ai-rag modules not loaded");
|
|
1395
|
+
}
|
|
1118
1396
|
const store = new aiRag.ChromaVectorStore({
|
|
1119
1397
|
url: args.chromaUrl,
|
|
1120
1398
|
collectionName: args.insightsCollection
|
|
@@ -1132,6 +1410,9 @@ async function buildUnified(args) {
|
|
|
1132
1410
|
throw new Error("--knowledge-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1133
1411
|
if (!unified.embedQuery)
|
|
1134
1412
|
throw new Error("--knowledge-backend=chroma requires --embedding-provider local|openrouter");
|
|
1413
|
+
if (!aiRag) {
|
|
1414
|
+
throw new Error("ai-rag modules not loaded");
|
|
1415
|
+
}
|
|
1135
1416
|
const store = new aiRag.ChromaVectorStore({
|
|
1136
1417
|
url: args.chromaUrl,
|
|
1137
1418
|
collectionName: args.knowledgeCollection
|
|
@@ -61,7 +61,7 @@ async function closeSQLiteRawMessageManager() {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// src/storage/raw-message-store.ts
|
|
64
|
-
function resolveBackend(
|
|
64
|
+
function resolveBackend(_env) {
|
|
65
65
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
66
66
|
if (explicit === "postgres") return "postgres";
|
|
67
67
|
return "sqlite";
|
|
@@ -281,6 +281,9 @@ async function buildUnified(args) {
|
|
|
281
281
|
} else if (args.memoryBackend === "chroma") {
|
|
282
282
|
if (!args.chromaUrl)
|
|
283
283
|
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
284
|
+
if (!aiRag) {
|
|
285
|
+
throw new Error("ai-rag modules not loaded");
|
|
286
|
+
}
|
|
284
287
|
const store = new aiRag.ChromaVectorStore({
|
|
285
288
|
url: args.chromaUrl,
|
|
286
289
|
collectionName: "opencontext_raw_messages"
|
|
@@ -319,6 +322,9 @@ async function buildUnified(args) {
|
|
|
319
322
|
throw new Error("--insights-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
320
323
|
if (!unified.embedQuery)
|
|
321
324
|
throw new Error("--insights-backend=chroma requires --embedding-provider local|openrouter");
|
|
325
|
+
if (!aiRag) {
|
|
326
|
+
throw new Error("ai-rag modules not loaded");
|
|
327
|
+
}
|
|
322
328
|
const store = new aiRag.ChromaVectorStore({
|
|
323
329
|
url: args.chromaUrl,
|
|
324
330
|
collectionName: args.insightsCollection
|
|
@@ -336,6 +342,9 @@ async function buildUnified(args) {
|
|
|
336
342
|
throw new Error("--knowledge-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
337
343
|
if (!unified.embedQuery)
|
|
338
344
|
throw new Error("--knowledge-backend=chroma requires --embedding-provider local|openrouter");
|
|
345
|
+
if (!aiRag) {
|
|
346
|
+
throw new Error("ai-rag modules not loaded");
|
|
347
|
+
}
|
|
339
348
|
const store = new aiRag.ChromaVectorStore({
|
|
340
349
|
url: args.chromaUrl,
|
|
341
350
|
collectionName: args.knowledgeCollection
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RawMessageStorageManager } from '@melandlabs/indexeddb/storage';
|
|
2
|
-
import {
|
|
2
|
+
import { i as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-2Q092MUz.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Top-level raw-message store facade.
|
|
@@ -60,7 +60,7 @@ async function closeSQLiteRawMessageManager() {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// src/storage/raw-message-store.ts
|
|
63
|
-
function resolveBackend(
|
|
63
|
+
function resolveBackend(_env) {
|
|
64
64
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
65
65
|
if (explicit === "postgres") return "postgres";
|
|
66
66
|
return "sqlite";
|