@melandlabs/memory-store 0.3.0 → 1.0.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/README.md +209 -56
- package/dist/{config-2Q092MUz.d.ts → config-CnbqSek5.d.ts} +215 -2
- package/dist/contracts.d.ts +1 -0
- package/dist/contracts.js +2 -0
- package/dist/http.d.ts +21 -5
- package/dist/http.js +1051 -155
- package/dist/index.d.ts +230 -15
- package/dist/index.js +1087 -152
- package/dist/mcp.d.ts +14 -5
- package/dist/mcp.js +1188 -174
- package/dist/policies/memory-graph-correction-policy.d.ts +1 -0
- package/dist/policies/memory-graph-correction-policy.js +4 -0
- package/dist/policies/memory-graph-write-policy.d.ts +1 -0
- package/dist/policies/memory-graph-write-policy.js +2 -0
- package/dist/search/unified-search.d.ts +6 -19
- package/dist/search/unified-search.js +749 -145
- package/dist/server/cli-http.js +1051 -155
- package/dist/server/cli-mcp.js +1185 -171
- package/dist/server/cli-shared.d.ts +5 -2
- package/dist/server/cli-shared.js +6 -4
- package/dist/storage/chroma-memory-index.d.ts +1 -0
- package/dist/storage/chroma-memory-index.js +1 -2
- package/dist/storage/postgres-raw-message-factory.d.ts +4 -1
- package/dist/storage/raw-message-store.d.ts +5 -2
- package/dist/storage/raw-message-store.js +6 -4
- package/dist/storage/sqlite-raw-message-store.d.ts +5 -1
- package/dist/storage/sqlite-raw-message-store.js +6 -4
- package/dist/storage/sqlite-vector-index.d.ts +4 -1
- package/dist/storage/sqlite-vector-index.js +4 -4
- package/dist/unified-search-Bft57TRH.d.ts +122 -0
- package/package.json +11 -7
package/dist/server/cli-mcp.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
|
|
2
3
|
import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
|
|
3
4
|
import { ChromaVectorStore } from '@melandlabs/rag';
|
|
4
5
|
import { mkdirSync } from 'fs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
import { getOpenContextPath } from '@melandlabs/env-config';
|
|
8
|
+
import { getSQLiteVsaStore, SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
8
9
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
11
|
import { z } from 'zod';
|
|
12
|
+
import { applyReflectedConsolidationPlan } from '@melandlabs/memory-consolidation';
|
|
13
|
+
import { superpose, bind, unbind, cleanup, cosineSimilarity } from '@melandlabs/vsa';
|
|
11
14
|
|
|
12
15
|
var __defProp = Object.defineProperty;
|
|
13
16
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -18,8 +21,6 @@ var __export = (target, all) => {
|
|
|
18
21
|
for (var name in all)
|
|
19
22
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
23
|
};
|
|
21
|
-
|
|
22
|
-
// src/contracts.ts
|
|
23
24
|
function normalizeTimestampToMs(value) {
|
|
24
25
|
if (!Number.isFinite(value)) {
|
|
25
26
|
return 0;
|
|
@@ -188,7 +189,7 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
188
189
|
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
189
190
|
if (dbPath && dbPath.length > 0) return dbPath;
|
|
190
191
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
191
|
-
return fromEnv && fromEnv.length > 0 ? fromEnv :
|
|
192
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : getOpenContextPath("memory", "store.db");
|
|
192
193
|
}
|
|
193
194
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
194
195
|
return true;
|
|
@@ -222,12 +223,11 @@ async function lexicalSearchRawMessages(input) {
|
|
|
222
223
|
}
|
|
223
224
|
return mgr.lexicalSearchMessages(input);
|
|
224
225
|
}
|
|
225
|
-
var manager
|
|
226
|
+
var manager;
|
|
226
227
|
var init_sqlite_raw_message_store = __esm({
|
|
227
228
|
"src/storage/sqlite-raw-message-store.ts"() {
|
|
228
229
|
init_chroma_memory_index();
|
|
229
230
|
manager = null;
|
|
230
|
-
DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
231
231
|
}
|
|
232
232
|
});
|
|
233
233
|
|
|
@@ -327,6 +327,41 @@ var init_raw_message_store = __esm({
|
|
|
327
327
|
init_chroma_memory_index();
|
|
328
328
|
init_raw_message_store();
|
|
329
329
|
|
|
330
|
+
// src/search/reranker.ts
|
|
331
|
+
async function applyReranker(reranker, query, results) {
|
|
332
|
+
if (!reranker || results.length === 0) {
|
|
333
|
+
return results;
|
|
334
|
+
}
|
|
335
|
+
const scores = await reranker.rerank({
|
|
336
|
+
query,
|
|
337
|
+
candidates: results.map((result) => ({
|
|
338
|
+
id: result.id,
|
|
339
|
+
content: result.content,
|
|
340
|
+
metadata: result.metadata
|
|
341
|
+
}))
|
|
342
|
+
});
|
|
343
|
+
if (scores.length === 0) {
|
|
344
|
+
return results;
|
|
345
|
+
}
|
|
346
|
+
const byId = new Map(results.map((result) => [result.id, result]));
|
|
347
|
+
const reordered = [];
|
|
348
|
+
const seen = /* @__PURE__ */ new Set();
|
|
349
|
+
for (const { id, score } of scores) {
|
|
350
|
+
const hit = byId.get(id);
|
|
351
|
+
if (!hit || seen.has(id)) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
seen.add(id);
|
|
355
|
+
reordered.push({ ...hit, metadata: { ...hit.metadata, rerankerScore: score } });
|
|
356
|
+
}
|
|
357
|
+
for (const hit of results) {
|
|
358
|
+
if (!seen.has(hit.id)) {
|
|
359
|
+
reordered.push(hit);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return reordered;
|
|
363
|
+
}
|
|
364
|
+
|
|
330
365
|
// src/search/utilities.ts
|
|
331
366
|
var DEFAULT_LIMIT = 10;
|
|
332
367
|
var DEFAULT_THRESHOLD = 0.7;
|
|
@@ -448,6 +483,264 @@ function isRawMemorySemanticResult(result) {
|
|
|
448
483
|
const item = result;
|
|
449
484
|
return typeof item.id === "string" && typeof item.content === "string" && typeof item.similarity === "number" && Boolean(item.metadata) && typeof item.metadata === "object";
|
|
450
485
|
}
|
|
486
|
+
async function resolveScopePeer(input) {
|
|
487
|
+
const peers = input.peerFilter ?? [];
|
|
488
|
+
if (peers.length === 0) {
|
|
489
|
+
return { peers: [], warnings: [] };
|
|
490
|
+
}
|
|
491
|
+
if (typeof input.scopeCheck !== "function") {
|
|
492
|
+
return { peers, warnings: [] };
|
|
493
|
+
}
|
|
494
|
+
const retained = [];
|
|
495
|
+
const dropped = [];
|
|
496
|
+
for (const peer of peers) {
|
|
497
|
+
const ok = await input.scopeCheck({ userId: input.userId, peers: [peer] });
|
|
498
|
+
if (ok) {
|
|
499
|
+
retained.push(peer);
|
|
500
|
+
} else {
|
|
501
|
+
dropped.push(peer);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (dropped.length === 0) {
|
|
505
|
+
return { peers, warnings: [] };
|
|
506
|
+
}
|
|
507
|
+
if (retained.length === 0) {
|
|
508
|
+
return {
|
|
509
|
+
peers: [],
|
|
510
|
+
warnings: [
|
|
511
|
+
{
|
|
512
|
+
source: "memory",
|
|
513
|
+
code: "peer_filter_outside_user_scope",
|
|
514
|
+
message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored; falling back to userId scope`
|
|
515
|
+
}
|
|
516
|
+
]
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
peers: retained,
|
|
521
|
+
warnings: [
|
|
522
|
+
{
|
|
523
|
+
source: "memory",
|
|
524
|
+
code: "peer_filter_outside_user_scope",
|
|
525
|
+
message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored because they fall outside userId ${input.userId}`
|
|
526
|
+
}
|
|
527
|
+
]
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/search/gather-evidence.ts
|
|
532
|
+
var DEFAULT_TIERS = ["summary", "raw", "insight", "knowledge"];
|
|
533
|
+
var MAX_EVIDENCE_CHARS = 600;
|
|
534
|
+
function toSummaryResult(row) {
|
|
535
|
+
const start = typeof row.startTimestamp === "number" ? row.startTimestamp : void 0;
|
|
536
|
+
const end = typeof row.endTimestamp === "number" ? row.endTimestamp : void 0;
|
|
537
|
+
return {
|
|
538
|
+
type: "memory",
|
|
539
|
+
id: row.summaryId,
|
|
540
|
+
content: row.summaryText,
|
|
541
|
+
similarity: 1,
|
|
542
|
+
metadata: {
|
|
543
|
+
tier: "summary",
|
|
544
|
+
summaryTier: row.summaryTier,
|
|
545
|
+
keywords: row.keywords,
|
|
546
|
+
startTimestamp: start,
|
|
547
|
+
endTimestamp: end
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers) {
|
|
552
|
+
if (typeof deps.searchSummaries !== "function") {
|
|
553
|
+
return { hits: [], warnings: [] };
|
|
554
|
+
}
|
|
555
|
+
const keywords = input.query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
556
|
+
try {
|
|
557
|
+
const rows = await deps.searchSummaries({
|
|
558
|
+
userId: input.userId,
|
|
559
|
+
query: input.query,
|
|
560
|
+
keywords,
|
|
561
|
+
limit,
|
|
562
|
+
threshold,
|
|
563
|
+
authToken: input.authToken,
|
|
564
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
565
|
+
});
|
|
566
|
+
const hits = rows.map(toSummaryResult);
|
|
567
|
+
return { hits, warnings: [] };
|
|
568
|
+
} catch (error) {
|
|
569
|
+
logger.warn?.("[memory-store] gather-evidence: summary search failed:", error);
|
|
570
|
+
return {
|
|
571
|
+
hits: [],
|
|
572
|
+
warnings: [
|
|
573
|
+
{
|
|
574
|
+
source: "memory",
|
|
575
|
+
code: "reflect_summaries_failed",
|
|
576
|
+
message: error.message ?? "reflect_summaries_failed"
|
|
577
|
+
}
|
|
578
|
+
]
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function truncate(input, max = MAX_EVIDENCE_CHARS) {
|
|
583
|
+
if (input.length <= max) {
|
|
584
|
+
return input;
|
|
585
|
+
}
|
|
586
|
+
return `${input.slice(0, max - 1)}\u2026`;
|
|
587
|
+
}
|
|
588
|
+
function buildSynthesisPrompt(input) {
|
|
589
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
590
|
+
for (const item of input.evidence) {
|
|
591
|
+
const bucket = grouped.get(item.source) ?? [];
|
|
592
|
+
bucket.push(item);
|
|
593
|
+
grouped.set(item.source, bucket);
|
|
594
|
+
}
|
|
595
|
+
const sections = [];
|
|
596
|
+
for (const tier of DEFAULT_TIERS) {
|
|
597
|
+
const items = grouped.get(tier) ?? [];
|
|
598
|
+
if (items.length === 0) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const lines = items.map(
|
|
602
|
+
(item, index) => ` [${index + 1}] (${item.id}, score=${item.score.toFixed(4)}) ${item.snippet}`
|
|
603
|
+
);
|
|
604
|
+
sections.push(`## ${tier}
|
|
605
|
+
${lines.join("\n")}`);
|
|
606
|
+
}
|
|
607
|
+
const schemaDirective = input.responseSchema ? `
|
|
608
|
+
Respond with a JSON object that conforms to the following schema:
|
|
609
|
+
${JSON.stringify(input.responseSchema, null, 2)}
|
|
610
|
+
Wrap your JSON in a single fenced \`\`\`json code block; do not add any prose outside the code block.` : "\nRespond in plain prose. Be concise; cite evidence by its [n] number.";
|
|
611
|
+
if (sections.length === 0) {
|
|
612
|
+
return `You are a reflection assistant.
|
|
613
|
+
|
|
614
|
+
Question: ${input.query}
|
|
615
|
+
|
|
616
|
+
No evidence was found across any tier. State that explicitly.${schemaDirective}`;
|
|
617
|
+
}
|
|
618
|
+
return `You are a reflection assistant. Synthesise the user's question using only the evidence below. Do not invent facts.
|
|
619
|
+
|
|
620
|
+
Question: ${input.query}
|
|
621
|
+
|
|
622
|
+
Evidence (grouped by tier):
|
|
623
|
+
${sections.join("\n\n")}${schemaDirective}`;
|
|
624
|
+
}
|
|
625
|
+
function extractJsonPayload(text) {
|
|
626
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
627
|
+
if (fenced?.[1]) {
|
|
628
|
+
return fenced[1];
|
|
629
|
+
}
|
|
630
|
+
const start = text.indexOf("{");
|
|
631
|
+
if (start === -1) {
|
|
632
|
+
return void 0;
|
|
633
|
+
}
|
|
634
|
+
let depth = 0;
|
|
635
|
+
let inString = false;
|
|
636
|
+
let escaped = false;
|
|
637
|
+
for (let i = start; i < text.length; i += 1) {
|
|
638
|
+
const ch = text[i];
|
|
639
|
+
if (escaped) {
|
|
640
|
+
escaped = false;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (inString) {
|
|
644
|
+
if (ch === "\\") {
|
|
645
|
+
escaped = true;
|
|
646
|
+
} else if (ch === '"') {
|
|
647
|
+
inString = false;
|
|
648
|
+
}
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (ch === '"') {
|
|
652
|
+
inString = true;
|
|
653
|
+
} else if (ch === "{") {
|
|
654
|
+
depth += 1;
|
|
655
|
+
} else if (ch === "}") {
|
|
656
|
+
depth -= 1;
|
|
657
|
+
if (depth === 0) {
|
|
658
|
+
return text.slice(start, i + 1);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return void 0;
|
|
663
|
+
}
|
|
664
|
+
function coerceAnswer(text, schema) {
|
|
665
|
+
if (!schema) {
|
|
666
|
+
return text;
|
|
667
|
+
}
|
|
668
|
+
const payload = extractJsonPayload(text);
|
|
669
|
+
if (!payload) {
|
|
670
|
+
return text;
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
const parsed = JSON.parse(payload);
|
|
674
|
+
if (parsed && typeof parsed === "object") {
|
|
675
|
+
const answer = parsed.answer;
|
|
676
|
+
if (typeof answer === "string") {
|
|
677
|
+
return answer;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
} catch {
|
|
681
|
+
}
|
|
682
|
+
return text;
|
|
683
|
+
}
|
|
684
|
+
async function synthesizeAnswer(input) {
|
|
685
|
+
const { query, hits, responseSchema, deps, logger } = input;
|
|
686
|
+
const evidence = hits.map((hit) => ({
|
|
687
|
+
id: hit.id,
|
|
688
|
+
source: mapHitToTier(hit),
|
|
689
|
+
snippet: truncate(hit.content),
|
|
690
|
+
score: hit.similarity
|
|
691
|
+
}));
|
|
692
|
+
const warnings = [];
|
|
693
|
+
const complete = deps.reasoning?.complete;
|
|
694
|
+
if (!complete) {
|
|
695
|
+
warnings.push({
|
|
696
|
+
source: "memory",
|
|
697
|
+
code: "reflect_llm_not_configured",
|
|
698
|
+
message: "No `reasoning.complete` callback is configured; search() returned evidence without an LLM synthesis."
|
|
699
|
+
});
|
|
700
|
+
return { answer: "", warnings };
|
|
701
|
+
}
|
|
702
|
+
const prompt = buildSynthesisPrompt({ query, evidence, responseSchema });
|
|
703
|
+
try {
|
|
704
|
+
const raw = await complete(prompt);
|
|
705
|
+
const answer = coerceAnswer(raw, responseSchema);
|
|
706
|
+
logger.log?.("[memory-store] search synthesis completed", {
|
|
707
|
+
evidenceCount: evidence.length,
|
|
708
|
+
answerChars: answer.length
|
|
709
|
+
});
|
|
710
|
+
return { answer, warnings };
|
|
711
|
+
} catch (error) {
|
|
712
|
+
logger.warn?.("[memory-store] search synthesis failed:", error);
|
|
713
|
+
warnings.push({
|
|
714
|
+
source: "memory",
|
|
715
|
+
code: "reflect_llm_failed",
|
|
716
|
+
message: error.message ?? "reflect_llm_failed"
|
|
717
|
+
});
|
|
718
|
+
return { answer: "", warnings };
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function mapHitToTier(hit) {
|
|
722
|
+
const tierMarker = hit.metadata?.tier;
|
|
723
|
+
if (tierMarker === "summary") {
|
|
724
|
+
return "summary";
|
|
725
|
+
}
|
|
726
|
+
if (hit.type === "memory") {
|
|
727
|
+
return "raw";
|
|
728
|
+
}
|
|
729
|
+
if (hit.type === "insight") {
|
|
730
|
+
return "insight";
|
|
731
|
+
}
|
|
732
|
+
if (hit.type === "knowledge") {
|
|
733
|
+
return "knowledge";
|
|
734
|
+
}
|
|
735
|
+
return hit.type;
|
|
736
|
+
}
|
|
737
|
+
async function resolveSearchScopePeers(input) {
|
|
738
|
+
return resolveScopePeer({
|
|
739
|
+
userId: input.userId,
|
|
740
|
+
peerFilter: input.peerFilter,
|
|
741
|
+
scopeCheck: input.deps.peerScopeCheck
|
|
742
|
+
});
|
|
743
|
+
}
|
|
451
744
|
|
|
452
745
|
// src/search/unified-search.ts
|
|
453
746
|
function deriveLexicalKeywords(query) {
|
|
@@ -519,7 +812,7 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
519
812
|
authToken: input.authToken
|
|
520
813
|
});
|
|
521
814
|
}
|
|
522
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
815
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers = []) {
|
|
523
816
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
524
817
|
let semantic = [];
|
|
525
818
|
if (isRawMessageChromaEnabled()) {
|
|
@@ -549,6 +842,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
549
842
|
}
|
|
550
843
|
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
551
844
|
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
845
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
552
846
|
semantic = (await Promise.all(
|
|
553
847
|
filters.map(
|
|
554
848
|
(filter) => searchRawMessagesAnn({
|
|
@@ -556,7 +850,9 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
556
850
|
queryEmbedding,
|
|
557
851
|
limit,
|
|
558
852
|
threshold,
|
|
559
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
853
|
+
botId: "botId" in filter ? filter.botId : void 0,
|
|
854
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
855
|
+
...factTypes ? { factTypes } : {}
|
|
560
856
|
})
|
|
561
857
|
)
|
|
562
858
|
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
@@ -566,11 +862,14 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
566
862
|
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
567
863
|
const manager2 = await getRawMessageManager2();
|
|
568
864
|
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
865
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
569
866
|
const results = await manager2.searchMessagesSemantically({
|
|
570
867
|
userId: input.userId,
|
|
571
868
|
queryEmbedding,
|
|
572
869
|
limit,
|
|
573
|
-
threshold
|
|
870
|
+
threshold,
|
|
871
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
872
|
+
...factTypes ? { factTypes } : {}
|
|
574
873
|
});
|
|
575
874
|
semantic = results.map((r) => ({
|
|
576
875
|
type: "memory",
|
|
@@ -606,7 +905,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
606
905
|
metadata: hit.metadata
|
|
607
906
|
};
|
|
608
907
|
}
|
|
609
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
908
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers = []) {
|
|
610
909
|
if (keywords.length === 0) {
|
|
611
910
|
return [];
|
|
612
911
|
}
|
|
@@ -614,13 +913,16 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
614
913
|
try {
|
|
615
914
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
616
915
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
916
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
617
917
|
return (await Promise.all(
|
|
618
918
|
filters.map(
|
|
619
919
|
(botId) => searchRawMessagesLexical({
|
|
620
920
|
userId: input.userId,
|
|
621
921
|
keywords,
|
|
622
922
|
limit: Math.ceil(limit / filters.length),
|
|
623
|
-
botId
|
|
923
|
+
botId,
|
|
924
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
925
|
+
...factTypes ? { factTypes } : {}
|
|
624
926
|
})
|
|
625
927
|
)
|
|
626
928
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -634,13 +936,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
634
936
|
try {
|
|
635
937
|
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
636
938
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
939
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
637
940
|
return (await Promise.all(
|
|
638
941
|
filters.map(
|
|
639
942
|
(botId) => lexicalSearchRawMessages2({
|
|
640
943
|
userId: input.userId,
|
|
641
944
|
keywords,
|
|
642
945
|
limit: Math.ceil(limit / filters.length),
|
|
643
|
-
botId
|
|
946
|
+
botId,
|
|
947
|
+
...factTypes ? { factTypes } : {}
|
|
644
948
|
})
|
|
645
949
|
)
|
|
646
950
|
)).flat().filter(Boolean).map(
|
|
@@ -653,6 +957,42 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
653
957
|
}
|
|
654
958
|
return [];
|
|
655
959
|
}
|
|
960
|
+
function searchInputToUnified(input) {
|
|
961
|
+
return {
|
|
962
|
+
userId: input.userId,
|
|
963
|
+
query: input.query,
|
|
964
|
+
sources: input.sources ? [...input.sources] : void 0,
|
|
965
|
+
limit: input.limit,
|
|
966
|
+
threshold: input.threshold,
|
|
967
|
+
authToken: input.authToken,
|
|
968
|
+
includeArchivedInsights: input.includeArchivedInsights,
|
|
969
|
+
botIds: input.botIds,
|
|
970
|
+
documentIds: input.documentIds,
|
|
971
|
+
asOf: input.asOf,
|
|
972
|
+
dateFrom: input.dateFrom,
|
|
973
|
+
dateTo: input.dateTo,
|
|
974
|
+
mergeStrategy: input.mergeStrategy,
|
|
975
|
+
peerFilter: input.peerFilter,
|
|
976
|
+
reasoningStrategy: input.reasoningStrategy,
|
|
977
|
+
factTypes: input.factTypes
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
function mapHitToEvidenceSource(hit) {
|
|
981
|
+
const tierMarker = hit.metadata?.tier;
|
|
982
|
+
if (tierMarker === "summary") {
|
|
983
|
+
return "summary";
|
|
984
|
+
}
|
|
985
|
+
if (hit.type === "memory") {
|
|
986
|
+
return "raw";
|
|
987
|
+
}
|
|
988
|
+
if (hit.type === "insight") {
|
|
989
|
+
return "insight";
|
|
990
|
+
}
|
|
991
|
+
if (hit.type === "knowledge") {
|
|
992
|
+
return "knowledge";
|
|
993
|
+
}
|
|
994
|
+
return hit.type;
|
|
995
|
+
}
|
|
656
996
|
function createUnifiedSearch(deps = {}) {
|
|
657
997
|
const logger = deps.logger ?? console;
|
|
658
998
|
function resolveReasoningStrategy(input) {
|
|
@@ -662,130 +1002,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
662
1002
|
}
|
|
663
1003
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
664
1004
|
}
|
|
665
|
-
async function
|
|
666
|
-
const query = input.query.trim();
|
|
667
|
-
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
668
|
-
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
669
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
670
|
-
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
671
|
-
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
672
|
-
const warnings = [];
|
|
673
|
-
if (!query) {
|
|
674
|
-
return {
|
|
675
|
-
query,
|
|
676
|
-
sources,
|
|
677
|
-
results: [],
|
|
678
|
-
count: 0,
|
|
679
|
-
warnings
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
let memorySubs;
|
|
683
|
-
const reasoningInfo = { strategy: reasoningStrategy };
|
|
684
|
-
if (input.dateFrom || input.dateTo) {
|
|
685
|
-
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
686
|
-
}
|
|
687
|
-
if (sources.includes("memory")) {
|
|
688
|
-
if (isRawMessageStorageAvailable()) {
|
|
689
|
-
try {
|
|
690
|
-
memorySubs = await runMemorySource(
|
|
691
|
-
input,
|
|
692
|
-
limit,
|
|
693
|
-
threshold,
|
|
694
|
-
warnings,
|
|
695
|
-
reasoningStrategy,
|
|
696
|
-
reasoningInfo
|
|
697
|
-
);
|
|
698
|
-
} catch (error) {
|
|
699
|
-
logger.warn?.("[memory-store] memory source failed:", error);
|
|
700
|
-
warnings.push({
|
|
701
|
-
source: "memory",
|
|
702
|
-
code: "memory_search_failed",
|
|
703
|
-
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
} else {
|
|
707
|
-
warnings.push({
|
|
708
|
-
source: "memory",
|
|
709
|
-
code: "raw_message_storage_unavailable",
|
|
710
|
-
message: "Raw memory storage is not available in this environment."
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
const insightHits = [];
|
|
715
|
-
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
716
|
-
try {
|
|
717
|
-
const hits = await deps.searchInsights({
|
|
718
|
-
userId: input.userId,
|
|
719
|
-
query: input.query,
|
|
720
|
-
limit,
|
|
721
|
-
threshold,
|
|
722
|
-
botIds: input.botIds,
|
|
723
|
-
includeArchived: input.includeArchivedInsights,
|
|
724
|
-
authToken: input.authToken
|
|
725
|
-
});
|
|
726
|
-
for (const hit of hits) {
|
|
727
|
-
insightHits.push({
|
|
728
|
-
type: "insight",
|
|
729
|
-
id: hit.id,
|
|
730
|
-
content: hit.content,
|
|
731
|
-
similarity: hit.similarity,
|
|
732
|
-
metadata: hit.metadata
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
} catch (error) {
|
|
736
|
-
logger.warn?.("[memory-store] insights source failed:", error);
|
|
737
|
-
warnings.push({
|
|
738
|
-
source: "insights",
|
|
739
|
-
code: "insights_search_failed",
|
|
740
|
-
message: error.message ?? "insights_search_failed"
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
} else if (sources.includes("insights")) ;
|
|
744
|
-
const knowledgeHits = [];
|
|
745
|
-
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
746
|
-
try {
|
|
747
|
-
const hits = await deps.searchKnowledge({
|
|
748
|
-
userId: input.userId,
|
|
749
|
-
query: input.query,
|
|
750
|
-
options: {
|
|
751
|
-
limit,
|
|
752
|
-
threshold,
|
|
753
|
-
documentIds: input.documentIds
|
|
754
|
-
},
|
|
755
|
-
authToken: input.authToken
|
|
756
|
-
});
|
|
757
|
-
for (const hit of hits) {
|
|
758
|
-
knowledgeHits.push(toKnowledgeResult(hit));
|
|
759
|
-
}
|
|
760
|
-
} catch (error) {
|
|
761
|
-
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
762
|
-
warnings.push({
|
|
763
|
-
source: "knowledge",
|
|
764
|
-
code: "knowledge_search_failed",
|
|
765
|
-
message: error.message ?? "knowledge_search_failed"
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
} else if (sources.includes("knowledge")) ;
|
|
769
|
-
const merged = mergeAcrossSources({
|
|
770
|
-
memorySubs,
|
|
771
|
-
insightHits,
|
|
772
|
-
knowledgeHits,
|
|
773
|
-
limit,
|
|
774
|
-
strategy: mergeStrategy
|
|
775
|
-
});
|
|
776
|
-
const output = {
|
|
777
|
-
query: input.query,
|
|
778
|
-
sources,
|
|
779
|
-
results: merged,
|
|
780
|
-
count: merged.length,
|
|
781
|
-
warnings
|
|
782
|
-
};
|
|
783
|
-
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
784
|
-
output.reasoning = reasoningInfo;
|
|
785
|
-
}
|
|
786
|
-
return output;
|
|
787
|
-
}
|
|
788
|
-
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
1005
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
|
|
789
1006
|
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
790
1007
|
warnings.push({
|
|
791
1008
|
source: "memory",
|
|
@@ -811,7 +1028,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
811
1028
|
executor: {
|
|
812
1029
|
search: async (request) => {
|
|
813
1030
|
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
814
|
-
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1031
|
+
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1032
|
+
deps,
|
|
1033
|
+
input,
|
|
1034
|
+
keywords2,
|
|
1035
|
+
limit,
|
|
1036
|
+
logger,
|
|
1037
|
+
peerPeers
|
|
1038
|
+
);
|
|
815
1039
|
let semanticHits = [];
|
|
816
1040
|
if (typeof deps.embedQuery === "function") {
|
|
817
1041
|
try {
|
|
@@ -823,7 +1047,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
823
1047
|
queryEmbedding,
|
|
824
1048
|
limit,
|
|
825
1049
|
threshold,
|
|
826
|
-
logger
|
|
1050
|
+
logger,
|
|
1051
|
+
peerPeers
|
|
827
1052
|
);
|
|
828
1053
|
} catch (error) {
|
|
829
1054
|
logger.warn?.(
|
|
@@ -868,7 +1093,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
868
1093
|
});
|
|
869
1094
|
const keywords2 = deriveLexicalKeywords(input.query);
|
|
870
1095
|
const lexical2 = filterByDateRange(
|
|
871
|
-
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
1096
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger, peerPeers),
|
|
872
1097
|
input.dateFrom,
|
|
873
1098
|
input.dateTo
|
|
874
1099
|
);
|
|
@@ -894,7 +1119,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
894
1119
|
);
|
|
895
1120
|
const lists = await Promise.all(
|
|
896
1121
|
embeddings.map(
|
|
897
|
-
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
1122
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger, peerPeers)
|
|
898
1123
|
)
|
|
899
1124
|
);
|
|
900
1125
|
semantic = mergeByMaxScore(lists);
|
|
@@ -910,7 +1135,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
910
1135
|
}
|
|
911
1136
|
if (semantic.length === 0) {
|
|
912
1137
|
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
913
|
-
semantic = await runSemanticSearchForEmbedding(
|
|
1138
|
+
semantic = await runSemanticSearchForEmbedding(
|
|
1139
|
+
deps,
|
|
1140
|
+
input,
|
|
1141
|
+
queryEmbedding,
|
|
1142
|
+
limit,
|
|
1143
|
+
threshold,
|
|
1144
|
+
logger,
|
|
1145
|
+
peerPeers
|
|
1146
|
+
);
|
|
914
1147
|
}
|
|
915
1148
|
let lexical = [];
|
|
916
1149
|
const keywords = deriveLexicalKeywords(input.query);
|
|
@@ -919,13 +1152,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
919
1152
|
try {
|
|
920
1153
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
921
1154
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
1155
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
922
1156
|
lexical = (await Promise.all(
|
|
923
1157
|
lexFilters.map(
|
|
924
1158
|
(botId) => searchRawMessagesLexical({
|
|
925
1159
|
userId: input.userId,
|
|
926
1160
|
keywords,
|
|
927
1161
|
limit,
|
|
928
|
-
botId
|
|
1162
|
+
botId,
|
|
1163
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1164
|
+
...factTypes ? { factTypes } : {}
|
|
929
1165
|
})
|
|
930
1166
|
)
|
|
931
1167
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -953,32 +1189,284 @@ function createUnifiedSearch(deps = {}) {
|
|
|
953
1189
|
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
954
1190
|
};
|
|
955
1191
|
}
|
|
956
|
-
async function
|
|
1192
|
+
async function searchUnifiedMemory(input) {
|
|
1193
|
+
const query = input.query.trim();
|
|
1194
|
+
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
957
1195
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
958
1196
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1197
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1198
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1199
|
+
);
|
|
959
1200
|
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1201
|
+
const warnings = [];
|
|
1202
|
+
const peerScope = input.peerFilter && input.peerFilter.length > 0 ? await resolveScopePeer({
|
|
1203
|
+
userId: input.userId,
|
|
1204
|
+
peerFilter: input.peerFilter,
|
|
1205
|
+
scopeCheck: deps.peerScopeCheck
|
|
1206
|
+
}) : { peers: [], warnings: [] };
|
|
1207
|
+
warnings.push(...peerScope.warnings);
|
|
1208
|
+
const peerPeers = peerScope.peers;
|
|
1209
|
+
if (!query) {
|
|
1210
|
+
return {
|
|
1211
|
+
query,
|
|
1212
|
+
sources,
|
|
1213
|
+
results: [],
|
|
1214
|
+
count: 0,
|
|
1215
|
+
warnings
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
let memorySubs;
|
|
1219
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
1220
|
+
if (input.dateFrom || input.dateTo) {
|
|
1221
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1222
|
+
}
|
|
1223
|
+
if (sources.includes("memory")) {
|
|
1224
|
+
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1225
|
+
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1226
|
+
try {
|
|
1227
|
+
memorySubs = await runMemorySource(
|
|
1228
|
+
input,
|
|
1229
|
+
limit,
|
|
1230
|
+
threshold,
|
|
1231
|
+
warnings,
|
|
1232
|
+
reasoningStrategy,
|
|
1233
|
+
reasoningInfo,
|
|
1234
|
+
peerPeers
|
|
1235
|
+
);
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1238
|
+
warnings.push({
|
|
1239
|
+
source: "memory",
|
|
1240
|
+
code: "memory_search_failed",
|
|
1241
|
+
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
} else {
|
|
1245
|
+
warnings.push({
|
|
1246
|
+
source: "memory",
|
|
1247
|
+
code: "raw_message_storage_unavailable",
|
|
1248
|
+
message: "Raw memory storage is not available in this environment."
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
const insightHits = [];
|
|
1253
|
+
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
1254
|
+
try {
|
|
1255
|
+
const hits = await deps.searchInsights({
|
|
1256
|
+
userId: input.userId,
|
|
1257
|
+
query: input.query,
|
|
1258
|
+
limit,
|
|
1259
|
+
threshold,
|
|
1260
|
+
botIds: input.botIds,
|
|
1261
|
+
includeArchived: input.includeArchivedInsights,
|
|
1262
|
+
authToken: input.authToken,
|
|
1263
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1264
|
+
});
|
|
1265
|
+
for (const hit of hits) {
|
|
1266
|
+
insightHits.push({
|
|
1267
|
+
type: "insight",
|
|
1268
|
+
id: hit.id,
|
|
1269
|
+
content: hit.content,
|
|
1270
|
+
similarity: hit.similarity,
|
|
1271
|
+
metadata: hit.metadata
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
logger.warn?.("[memory-store] insights source failed:", error);
|
|
1276
|
+
warnings.push({
|
|
1277
|
+
source: "insights",
|
|
1278
|
+
code: "insights_search_failed",
|
|
1279
|
+
message: error.message ?? "insights_search_failed"
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
} else if (sources.includes("insights")) ;
|
|
1283
|
+
const knowledgeHits = [];
|
|
1284
|
+
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
1285
|
+
try {
|
|
1286
|
+
const hits = await deps.searchKnowledge({
|
|
1287
|
+
userId: input.userId,
|
|
1288
|
+
query: input.query,
|
|
1289
|
+
options: {
|
|
1290
|
+
limit,
|
|
1291
|
+
threshold,
|
|
1292
|
+
documentIds: input.documentIds
|
|
1293
|
+
},
|
|
1294
|
+
authToken: input.authToken,
|
|
1295
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1296
|
+
});
|
|
1297
|
+
for (const hit of hits) {
|
|
1298
|
+
knowledgeHits.push(toKnowledgeResult(hit));
|
|
1299
|
+
}
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
1302
|
+
warnings.push({
|
|
1303
|
+
source: "knowledge",
|
|
1304
|
+
code: "knowledge_search_failed",
|
|
1305
|
+
message: error.message ?? "knowledge_search_failed"
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
} else if (sources.includes("knowledge")) ;
|
|
1309
|
+
const merged = mergeAcrossSources({
|
|
1310
|
+
memorySubs,
|
|
1311
|
+
insightHits,
|
|
1312
|
+
knowledgeHits,
|
|
1313
|
+
limit,
|
|
1314
|
+
strategy: mergeStrategy
|
|
1315
|
+
});
|
|
1316
|
+
const ranked = await applyReranker(deps.reranker, input.query, merged);
|
|
1317
|
+
const output = {
|
|
1318
|
+
query: input.query,
|
|
1319
|
+
sources,
|
|
1320
|
+
results: ranked,
|
|
1321
|
+
count: ranked.length,
|
|
1322
|
+
warnings
|
|
1323
|
+
};
|
|
1324
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
1325
|
+
output.reasoning = reasoningInfo;
|
|
1326
|
+
}
|
|
1327
|
+
return output;
|
|
1328
|
+
}
|
|
1329
|
+
async function searchRawMemorySemantically(input) {
|
|
1330
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1331
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1332
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1333
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1334
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1335
|
+
);
|
|
1336
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
1337
|
+
return mergeAcrossSources({
|
|
1338
|
+
memorySubs: sub,
|
|
1339
|
+
insightHits: [],
|
|
1340
|
+
knowledgeHits: [],
|
|
1341
|
+
limit,
|
|
1342
|
+
strategy: mergeStrategy
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
async function search(input) {
|
|
1346
|
+
const query = input.query.trim();
|
|
1347
|
+
const wantsSynthesis = typeof input.synthesize === "boolean" ? input.synthesize : typeof input.synthesize === "object" && input.synthesize !== null;
|
|
1348
|
+
const synthesisSchema = typeof input.synthesize === "object" && input.synthesize !== null ? input.synthesize.responseSchema ?? input.responseSchema : input.responseSchema;
|
|
1349
|
+
const peerScope = await resolveSearchScopePeers({
|
|
1350
|
+
deps,
|
|
1351
|
+
userId: input.userId,
|
|
1352
|
+
peerFilter: input.peerFilter
|
|
1353
|
+
});
|
|
1354
|
+
if (!query) {
|
|
1355
|
+
const empty = {
|
|
1356
|
+
query,
|
|
1357
|
+
sources: input.sources ? [...input.sources] : [],
|
|
1358
|
+
results: [],
|
|
1359
|
+
evidence: [],
|
|
1360
|
+
count: 0,
|
|
1361
|
+
warnings: peerScope.warnings,
|
|
1362
|
+
answer: ""
|
|
1363
|
+
};
|
|
1364
|
+
return empty;
|
|
1365
|
+
}
|
|
1366
|
+
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
|
|
1367
|
+
const wantsSummaryTier = tiers.includes("summary");
|
|
1368
|
+
const wantsRawTier = tiers.includes("raw");
|
|
1369
|
+
const wantsInsightTier = tiers.includes("insight");
|
|
1370
|
+
const wantsKnowledgeTier = tiers.includes("knowledge");
|
|
1371
|
+
const warnings = [...peerScope.warnings];
|
|
1372
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1373
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1374
|
+
let unifiedHits = [];
|
|
1375
|
+
let sources = input.sources ? [...input.sources] : [];
|
|
1376
|
+
let unifiedReasoning;
|
|
1377
|
+
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
|
|
1378
|
+
const inferredSources = sources.length > 0 ? sources : [
|
|
1379
|
+
...wantsRawTier ? ["memory"] : [],
|
|
1380
|
+
...wantsInsightTier ? ["insights"] : [],
|
|
1381
|
+
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
1382
|
+
];
|
|
1383
|
+
const unified = await searchUnifiedMemory({
|
|
1384
|
+
...searchInputToUnified(input),
|
|
1385
|
+
sources: inferredSources.length > 0 ? inferredSources : void 0
|
|
1386
|
+
});
|
|
1387
|
+
unifiedHits = unified.results;
|
|
1388
|
+
sources = unified.sources;
|
|
1389
|
+
unifiedReasoning = unified.reasoning;
|
|
1390
|
+
warnings.push(...unified.warnings);
|
|
1391
|
+
}
|
|
1392
|
+
let summaryHits = [];
|
|
1393
|
+
if (wantsSummaryTier && typeof deps.searchSummaries === "function") {
|
|
1394
|
+
const summaryBucket = await gatherSummaries(
|
|
1395
|
+
deps,
|
|
1396
|
+
{ userId: input.userId, query: input.query, authToken: input.authToken },
|
|
1397
|
+
limit,
|
|
1398
|
+
threshold,
|
|
1399
|
+
logger,
|
|
1400
|
+
peerScope.peers
|
|
1401
|
+
);
|
|
1402
|
+
warnings.push(...summaryBucket.warnings);
|
|
1403
|
+
summaryHits = summaryBucket.hits;
|
|
1404
|
+
}
|
|
1405
|
+
const tierLists = [];
|
|
1406
|
+
if (unifiedHits.length > 0) {
|
|
1407
|
+
tierLists.push({ name: "memory", hits: unifiedHits });
|
|
1408
|
+
}
|
|
1409
|
+
if (summaryHits.length > 0) {
|
|
1410
|
+
tierLists.push({ name: "summary", hits: summaryHits });
|
|
1411
|
+
}
|
|
1412
|
+
let hits;
|
|
1413
|
+
if (tierLists.length === 0) {
|
|
1414
|
+
hits = [];
|
|
1415
|
+
} else if (tierLists.length === 1) {
|
|
1416
|
+
hits = tierLists[0].hits.slice(0, limit);
|
|
1417
|
+
} else {
|
|
1418
|
+
hits = mergeUnifiedMemorySearchResultsRrf(tierLists, limit);
|
|
1419
|
+
}
|
|
1420
|
+
const evidence = hits.map((hit) => ({
|
|
1421
|
+
id: hit.id,
|
|
1422
|
+
source: mapHitToEvidenceSource(hit),
|
|
1423
|
+
snippet: hit.content,
|
|
1424
|
+
score: hit.similarity,
|
|
1425
|
+
timestamp: getCandidateTimestamp(hit.metadata)
|
|
1426
|
+
}));
|
|
1427
|
+
const base = {
|
|
1428
|
+
query,
|
|
1429
|
+
sources,
|
|
1430
|
+
results: hits,
|
|
1431
|
+
evidence,
|
|
1432
|
+
count: hits.length,
|
|
1433
|
+
warnings
|
|
1434
|
+
};
|
|
1435
|
+
if (unifiedReasoning) {
|
|
1436
|
+
base.reasoning = unifiedReasoning;
|
|
1437
|
+
}
|
|
1438
|
+
if (!wantsSynthesis) {
|
|
1439
|
+
return base;
|
|
1440
|
+
}
|
|
1441
|
+
const { answer, warnings: synthWarnings } = await synthesizeAnswer({
|
|
1442
|
+
query,
|
|
1443
|
+
hits,
|
|
1444
|
+
responseSchema: synthesisSchema,
|
|
1445
|
+
deps,
|
|
1446
|
+
logger
|
|
1447
|
+
});
|
|
1448
|
+
base.answer = answer;
|
|
1449
|
+
base.warnings = [...base.warnings, ...synthWarnings];
|
|
1450
|
+
return base;
|
|
1451
|
+
}
|
|
1452
|
+
return {
|
|
1453
|
+
search,
|
|
1454
|
+
searchUnifiedMemory,
|
|
1455
|
+
searchRawMemorySemantically,
|
|
1456
|
+
consolidate: (input) => applyReflectedPlan(deps, {}, input)
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function mergeAcrossSources(input) {
|
|
1460
|
+
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
1461
|
+
const all = [
|
|
1462
|
+
...memorySubs.semantic,
|
|
1463
|
+
...memorySubs.lexical,
|
|
1464
|
+
...input.insightHits,
|
|
1465
|
+
...input.knowledgeHits
|
|
1466
|
+
];
|
|
1467
|
+
if (input.strategy !== "rrf") {
|
|
1468
|
+
return mergeUnifiedMemorySearchResults(all, input.limit);
|
|
1469
|
+
}
|
|
982
1470
|
const lists = [];
|
|
983
1471
|
if (memorySubs.semantic.length > 0) {
|
|
984
1472
|
lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
|
|
@@ -998,9 +1486,283 @@ function mergeAcrossSources(input) {
|
|
|
998
1486
|
return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
|
|
999
1487
|
}
|
|
1000
1488
|
|
|
1489
|
+
// src/search/apply-reflect.ts
|
|
1490
|
+
var DEFAULT_TIERS2 = ["summary", "raw", "insight", "knowledge"];
|
|
1491
|
+
function reflectTierToEvidenceTier(source) {
|
|
1492
|
+
switch (source) {
|
|
1493
|
+
case "raw":
|
|
1494
|
+
return "short";
|
|
1495
|
+
case "summary":
|
|
1496
|
+
return "long";
|
|
1497
|
+
default:
|
|
1498
|
+
return "mid";
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
function toMemoryEvidenceRecord(input, fallbackUserId) {
|
|
1502
|
+
const tier = reflectTierToEvidenceTier(input.source);
|
|
1503
|
+
const timestamp = input.timestamp ?? Date.now();
|
|
1504
|
+
return {
|
|
1505
|
+
id: input.id,
|
|
1506
|
+
userId: fallbackUserId,
|
|
1507
|
+
timestamp,
|
|
1508
|
+
tier,
|
|
1509
|
+
text: input.snippet,
|
|
1510
|
+
metadata: {
|
|
1511
|
+
source: input.source,
|
|
1512
|
+
score: input.score,
|
|
1513
|
+
...input.timestamp !== void 0 ? { originalTimestamp: input.timestamp } : {}
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
async function applyReflectedPlan(deps, store, input, _logger) {
|
|
1518
|
+
const query = input.query.trim();
|
|
1519
|
+
const warnings = [];
|
|
1520
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1521
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1522
|
+
const tiers = input.tiers && input.tiers.length > 0 ? input.tiers : DEFAULT_TIERS2;
|
|
1523
|
+
const searchInput = {
|
|
1524
|
+
userId: input.userId,
|
|
1525
|
+
query,
|
|
1526
|
+
botIds: input.botIds,
|
|
1527
|
+
dateFrom: input.dateFrom,
|
|
1528
|
+
dateTo: input.dateTo,
|
|
1529
|
+
tiers,
|
|
1530
|
+
limit,
|
|
1531
|
+
threshold,
|
|
1532
|
+
authToken: input.authToken,
|
|
1533
|
+
peerFilter: input.peerFilter,
|
|
1534
|
+
synthesize: true
|
|
1535
|
+
};
|
|
1536
|
+
const search = createUnifiedSearch(deps);
|
|
1537
|
+
const result = await search.search(searchInput);
|
|
1538
|
+
warnings.push(...result.warnings);
|
|
1539
|
+
const evidence = result.evidence.map((item) => ({
|
|
1540
|
+
id: item.id,
|
|
1541
|
+
source: item.source ?? "raw",
|
|
1542
|
+
snippet: item.snippet,
|
|
1543
|
+
score: item.score,
|
|
1544
|
+
timestamp: item.timestamp
|
|
1545
|
+
}));
|
|
1546
|
+
const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
|
|
1547
|
+
const applyInput = {
|
|
1548
|
+
userId: input.userId,
|
|
1549
|
+
ownerScope: input.ownerScope,
|
|
1550
|
+
records,
|
|
1551
|
+
plan: input.plan,
|
|
1552
|
+
dryRun: input.dryRun,
|
|
1553
|
+
expectedVersion: input.expectedVersion,
|
|
1554
|
+
getClusterKey: input.getClusterKey ?? defaultClusterKey,
|
|
1555
|
+
llmReview: deps.reasoning?.complete ? {
|
|
1556
|
+
complete: deps.reasoning.complete,
|
|
1557
|
+
maxTokens: input.llmPlanReview?.maxTokens
|
|
1558
|
+
} : void 0,
|
|
1559
|
+
graphStore: input.graphStore ?? store.graphStore,
|
|
1560
|
+
storage: input.storage ?? store.storage
|
|
1561
|
+
};
|
|
1562
|
+
const applyOutput = await applyReflectedConsolidationPlan(applyInput);
|
|
1563
|
+
for (const warning of applyOutput.warnings) {
|
|
1564
|
+
warnings.push({
|
|
1565
|
+
source: "memory",
|
|
1566
|
+
code: warning.code,
|
|
1567
|
+
message: warning.message
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
const summaryAnswer = makePlanSummary(applyOutput.plan);
|
|
1571
|
+
return {
|
|
1572
|
+
answer: result.answer ? `${result.answer}
|
|
1573
|
+
|
|
1574
|
+
Plan summary:
|
|
1575
|
+
${summaryAnswer}` : summaryAnswer,
|
|
1576
|
+
plan: applyOutput.plan,
|
|
1577
|
+
persistenceResult: applyOutput.persistenceResult,
|
|
1578
|
+
deprecationCounts: applyOutput.deprecationCounts,
|
|
1579
|
+
warnings,
|
|
1580
|
+
applied: applyOutput.applied,
|
|
1581
|
+
evidence
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function defaultClusterKey(record) {
|
|
1585
|
+
return record.text ?? record.id;
|
|
1586
|
+
}
|
|
1587
|
+
function makePlanSummary(plan) {
|
|
1588
|
+
const lines = [];
|
|
1589
|
+
const counts = {
|
|
1590
|
+
preserve: plan.actions.preserve.length,
|
|
1591
|
+
observe: plan.actions.observe.length,
|
|
1592
|
+
decay: plan.actions.decay.length,
|
|
1593
|
+
deprecate: plan.actions.deprecate.length
|
|
1594
|
+
};
|
|
1595
|
+
lines.push(
|
|
1596
|
+
`- preserve=${counts.preserve} observe=${counts.observe} decay=${counts.decay} deprecate=${counts.deprecate}`
|
|
1597
|
+
);
|
|
1598
|
+
for (const entry of plan.actions.deprecate.slice(0, 5)) {
|
|
1599
|
+
lines.push(
|
|
1600
|
+
` deprecate ${entry.recordIds.length} record(s) from cluster ${entry.clusterKey} -> ${entry.supersededBySummaryId ?? entry.winningClusterKey}`
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
return lines.join("\n");
|
|
1604
|
+
}
|
|
1605
|
+
var DEFAULT_MAX_FACTS = 1e3;
|
|
1606
|
+
var LOW_CONFIDENCE_THRESHOLD = 0.1;
|
|
1607
|
+
function toHRR(data) {
|
|
1608
|
+
const arr = data instanceof Float32Array ? data : new Float32Array(data);
|
|
1609
|
+
return { dim: arr.length, data: arr };
|
|
1610
|
+
}
|
|
1611
|
+
function generateFactId() {
|
|
1612
|
+
const stamp = Date.now().toString(36);
|
|
1613
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
1614
|
+
return `vsa-${stamp}-${rand}`;
|
|
1615
|
+
}
|
|
1616
|
+
function createVsaRecall(storage) {
|
|
1617
|
+
return {
|
|
1618
|
+
async storeFact(input) {
|
|
1619
|
+
const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
|
|
1620
|
+
const fillerVector = vsaNormalizeVector(input.fillerVector, "fillerVector");
|
|
1621
|
+
vsaAssertSameDim(roleVector, fillerVector, "storeFact");
|
|
1622
|
+
const dim = input.dim ?? roleVector.length;
|
|
1623
|
+
if (roleVector.length !== dim) {
|
|
1624
|
+
throw new Error(`storeFact: roleVector.length (${roleVector.length}) must equal dim (${dim})`);
|
|
1625
|
+
}
|
|
1626
|
+
const now = Date.now();
|
|
1627
|
+
const fact = {
|
|
1628
|
+
factId: input.factId ?? generateFactId(),
|
|
1629
|
+
userId: input.userId,
|
|
1630
|
+
roleLabel: input.roleLabel,
|
|
1631
|
+
fillerLabel: input.fillerLabel,
|
|
1632
|
+
roleVector,
|
|
1633
|
+
fillerVector,
|
|
1634
|
+
dim,
|
|
1635
|
+
scopeTag: input.scopeTag ?? "default",
|
|
1636
|
+
botId: input.botId,
|
|
1637
|
+
createdAt: now
|
|
1638
|
+
};
|
|
1639
|
+
await storage.storeFact(fact);
|
|
1640
|
+
return { factId: fact.factId, createdAt: now };
|
|
1641
|
+
},
|
|
1642
|
+
async recall(input) {
|
|
1643
|
+
const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
|
|
1644
|
+
const scopeTag = input.scopeTag ?? "default";
|
|
1645
|
+
const maxFacts = input.maxFacts ?? DEFAULT_MAX_FACTS;
|
|
1646
|
+
if (input.vocabulary.length === 0) {
|
|
1647
|
+
throw new Error("vsaRecall: vocabulary must contain at least one entry");
|
|
1648
|
+
}
|
|
1649
|
+
const facts = await storage.queryFacts({
|
|
1650
|
+
userId: input.userId,
|
|
1651
|
+
scopeTag,
|
|
1652
|
+
botId: input.botId,
|
|
1653
|
+
includeDeprecated: false,
|
|
1654
|
+
limit: maxFacts
|
|
1655
|
+
});
|
|
1656
|
+
const warnings = [];
|
|
1657
|
+
if (facts.length === 0) {
|
|
1658
|
+
warnings.push({
|
|
1659
|
+
code: "vsa_no_facts",
|
|
1660
|
+
message: `No VSA facts for userId=${input.userId} scopeTag=${scopeTag}`
|
|
1661
|
+
});
|
|
1662
|
+
return {
|
|
1663
|
+
fillerLabel: "",
|
|
1664
|
+
score: 0,
|
|
1665
|
+
allScores: [],
|
|
1666
|
+
factCount: 0,
|
|
1667
|
+
warnings
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
const refDim = facts[0].dim;
|
|
1671
|
+
const dimMatchedFacts = facts.filter((fact) => {
|
|
1672
|
+
if (fact.dim === refDim) return true;
|
|
1673
|
+
warnings.push({
|
|
1674
|
+
code: "vsa_dim_mismatch",
|
|
1675
|
+
message: `Fact ${fact.factId} has dim=${fact.dim}, expected ${refDim}; skipping`
|
|
1676
|
+
});
|
|
1677
|
+
return false;
|
|
1678
|
+
});
|
|
1679
|
+
if (roleVector.length !== refDim) {
|
|
1680
|
+
throw new Error(
|
|
1681
|
+
`vsaRecall: roleVector dim (${roleVector.length}) must match stored fact dim (${refDim})`
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
if (dimMatchedFacts.length === 0) {
|
|
1685
|
+
warnings.push({
|
|
1686
|
+
code: "vsa_no_facts",
|
|
1687
|
+
message: `All stored facts for userId=${input.userId} scopeTag=${scopeTag} had a non-matching dim`
|
|
1688
|
+
});
|
|
1689
|
+
return {
|
|
1690
|
+
fillerLabel: "",
|
|
1691
|
+
score: 0,
|
|
1692
|
+
allScores: [],
|
|
1693
|
+
factCount: 0,
|
|
1694
|
+
warnings
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
const memoryVec = superpose(
|
|
1698
|
+
dimMatchedFacts.map((fact) => bind(toHRR(fact.roleVector), toHRR(fact.fillerVector)))
|
|
1699
|
+
);
|
|
1700
|
+
const candidate = unbind(memoryVec, toHRR(roleVector));
|
|
1701
|
+
const vocabVecs = input.vocabulary.map(
|
|
1702
|
+
(entry) => toHRR(vsaNormalizeVector(entry.vector, `vocabulary[${entry.label}].vector`))
|
|
1703
|
+
);
|
|
1704
|
+
const bestVec = cleanup(candidate, vocabVecs);
|
|
1705
|
+
const bestIdx = vocabVecs.indexOf(bestVec);
|
|
1706
|
+
const allScores = input.vocabulary.map((entry, idx) => ({
|
|
1707
|
+
label: entry.label,
|
|
1708
|
+
score: cosineSimilarity(candidate, vocabVecs[idx])
|
|
1709
|
+
}));
|
|
1710
|
+
allScores.sort((a, b) => b.score - a.score);
|
|
1711
|
+
const fillerLabel = bestIdx >= 0 ? input.vocabulary[bestIdx].label : "";
|
|
1712
|
+
const score = allScores[0]?.score ?? 0;
|
|
1713
|
+
if (score < LOW_CONFIDENCE_THRESHOLD) {
|
|
1714
|
+
warnings.push({
|
|
1715
|
+
code: "vsa_low_confidence",
|
|
1716
|
+
message: `Best match "${fillerLabel}" score ${score.toFixed(3)} below ${LOW_CONFIDENCE_THRESHOLD}`
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
if (facts.length >= maxFacts) {
|
|
1720
|
+
warnings.push({
|
|
1721
|
+
code: "vsa_fact_limit_reached",
|
|
1722
|
+
message: `Loaded ${facts.length} facts (cap=${maxFacts}); recall may degrade with more`
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
return {
|
|
1726
|
+
fillerLabel,
|
|
1727
|
+
score,
|
|
1728
|
+
allScores,
|
|
1729
|
+
factCount: facts.length,
|
|
1730
|
+
warnings
|
|
1731
|
+
};
|
|
1732
|
+
},
|
|
1733
|
+
async listFacts(input) {
|
|
1734
|
+
const facts = await storage.queryFacts({
|
|
1735
|
+
userId: input.userId,
|
|
1736
|
+
scopeTag: input.scopeTag,
|
|
1737
|
+
botId: input.botId,
|
|
1738
|
+
includeDeprecated: input.includeDeprecated
|
|
1739
|
+
});
|
|
1740
|
+
return facts.map((fact) => ({
|
|
1741
|
+
factId: fact.factId,
|
|
1742
|
+
userId: fact.userId,
|
|
1743
|
+
roleLabel: fact.roleLabel,
|
|
1744
|
+
fillerLabel: fact.fillerLabel,
|
|
1745
|
+
scopeTag: fact.scopeTag,
|
|
1746
|
+
botId: fact.botId,
|
|
1747
|
+
createdAt: fact.createdAt,
|
|
1748
|
+
deprecatedAt: fact.deprecatedAt
|
|
1749
|
+
}));
|
|
1750
|
+
},
|
|
1751
|
+
async forget(input) {
|
|
1752
|
+
const result = await storage.deprecateFacts({
|
|
1753
|
+
userId: input.userId,
|
|
1754
|
+
factIds: input.factIds,
|
|
1755
|
+
reason: input.reason
|
|
1756
|
+
});
|
|
1757
|
+
return { deprecatedCount: result.deprecatedCount };
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1001
1762
|
// src/mcp.ts
|
|
1002
1763
|
init_chroma_memory_index();
|
|
1003
1764
|
init_raw_message_store();
|
|
1765
|
+
init_sqlite_raw_message_store();
|
|
1004
1766
|
var DEFAULT_NAME = "@melandlabs/memory-store";
|
|
1005
1767
|
var DEFAULT_VERSION = "0.1.0";
|
|
1006
1768
|
async function embedMissingMessages(messages, deps) {
|
|
@@ -1040,6 +1802,9 @@ async function startMcpServer(options = {}) {
|
|
|
1040
1802
|
env: options.env
|
|
1041
1803
|
});
|
|
1042
1804
|
const search = createUnifiedSearch(options.unified);
|
|
1805
|
+
const vsaDbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
|
|
1806
|
+
const vsaStorage = await getSQLiteVsaStore({ dbPath: vsaDbPath });
|
|
1807
|
+
const vsa = createVsaRecall(vsaStorage);
|
|
1043
1808
|
server.tool("memory.health", "Returns the health status of the memory store.", async () => ({
|
|
1044
1809
|
content: [{ type: "text", text: JSON.stringify({ ok: true }) }]
|
|
1045
1810
|
}));
|
|
@@ -1047,6 +1812,15 @@ async function startMcpServer(options = {}) {
|
|
|
1047
1812
|
userId: z.string(),
|
|
1048
1813
|
query: z.string().min(1),
|
|
1049
1814
|
sources: z.array(z.enum(["memory", "insights", "knowledge"])).optional().describe("Default: ['memory','insights','knowledge']"),
|
|
1815
|
+
tiers: z.array(z.enum(["summary", "raw", "insight", "knowledge"])).optional().describe(
|
|
1816
|
+
"Per-tier evidence scope for synthesis. Default: ['summary','raw','insight','knowledge']. Ignored when `synthesize` is falsy."
|
|
1817
|
+
),
|
|
1818
|
+
synthesize: z.union([z.boolean(), z.object({ responseSchema: z.record(z.string(), z.unknown()).optional() })]).optional().describe(
|
|
1819
|
+
"Opt-in LLM synthesis. `true` returns `{ answer, evidence, results, warnings }`. `synthesize.responseSchema` extracts `answer` from a JSON payload."
|
|
1820
|
+
),
|
|
1821
|
+
// Top-level pass-through kept for backward-shaped callers; the SDK
|
|
1822
|
+
// coerces it into `synthesize.responseSchema` when present.
|
|
1823
|
+
responseSchema: z.record(z.string(), z.unknown()).optional(),
|
|
1050
1824
|
limit: z.number().int().min(1).max(50).default(10),
|
|
1051
1825
|
threshold: z.number().min(-1).max(1).default(0.7),
|
|
1052
1826
|
botIds: z.array(z.string()).optional(),
|
|
@@ -1079,17 +1853,51 @@ async function startMcpServer(options = {}) {
|
|
|
1079
1853
|
userId: z.string(),
|
|
1080
1854
|
messageId: z.string()
|
|
1081
1855
|
};
|
|
1856
|
+
const reflectApplySchema = {
|
|
1857
|
+
userId: z.string(),
|
|
1858
|
+
query: z.string().min(1),
|
|
1859
|
+
ownerScopeUserId: z.string().describe("Required: who owns the writes."),
|
|
1860
|
+
botIds: z.array(z.string()).optional(),
|
|
1861
|
+
dateFrom: z.string().optional(),
|
|
1862
|
+
dateTo: z.string().optional(),
|
|
1863
|
+
tiers: z.array(z.enum(["summary", "raw", "insight", "knowledge"])).optional().describe("Default: ['summary','raw','insight','knowledge']"),
|
|
1864
|
+
limit: z.number().int().min(1).max(200).default(20),
|
|
1865
|
+
threshold: z.number().min(-1).max(1).default(0.7),
|
|
1866
|
+
dryRun: z.boolean().default(false),
|
|
1867
|
+
expectedVersion: z.string().optional(),
|
|
1868
|
+
llmPlanReviewMaxTokens: z.number().int().min(64).max(8192).optional(),
|
|
1869
|
+
authToken: z.string().optional()
|
|
1870
|
+
};
|
|
1082
1871
|
server.registerTool(
|
|
1083
|
-
"memory.
|
|
1872
|
+
"memory.search",
|
|
1084
1873
|
{
|
|
1085
|
-
title: "Search
|
|
1086
|
-
description: "
|
|
1874
|
+
title: "Search Memory",
|
|
1875
|
+
description: "Cross-source semantic search across raw messages, insights, and uploaded knowledge. Set `synthesize: true` to opt into LLM synthesis over the gathered evidence. With synthesis enabled, the response carries `answer`, `evidence`, `results`, and `warnings`; otherwise just `results`, `evidence`, and `count`.",
|
|
1087
1876
|
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
1088
1877
|
inputSchema: searchSchema
|
|
1089
1878
|
},
|
|
1090
1879
|
async (args) => {
|
|
1091
1880
|
const a = args;
|
|
1092
|
-
const
|
|
1881
|
+
const wantsSynthesis = typeof a.synthesize === "boolean" ? a.synthesize : typeof a.synthesize === "object" && a.synthesize !== null;
|
|
1882
|
+
const responseSchema = typeof a.synthesize === "object" && a.synthesize !== null ? a.synthesize.responseSchema ?? a.responseSchema : a.responseSchema;
|
|
1883
|
+
const input = {
|
|
1884
|
+
userId: a.userId,
|
|
1885
|
+
query: a.query,
|
|
1886
|
+
sources: a.sources,
|
|
1887
|
+
limit: a.limit,
|
|
1888
|
+
threshold: a.threshold,
|
|
1889
|
+
botIds: a.botIds,
|
|
1890
|
+
documentIds: a.documentIds,
|
|
1891
|
+
authToken: a.authToken,
|
|
1892
|
+
includeArchivedInsights: a.includeArchivedInsights,
|
|
1893
|
+
...a.tiers ? { tiers: a.tiers } : {},
|
|
1894
|
+
...wantsSynthesis ? {
|
|
1895
|
+
synthesize: {
|
|
1896
|
+
...responseSchema ? { responseSchema } : {}
|
|
1897
|
+
}
|
|
1898
|
+
} : {}
|
|
1899
|
+
};
|
|
1900
|
+
const result = await search.search(input);
|
|
1093
1901
|
return {
|
|
1094
1902
|
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1095
1903
|
};
|
|
@@ -1157,6 +1965,212 @@ async function startMcpServer(options = {}) {
|
|
|
1157
1965
|
};
|
|
1158
1966
|
}
|
|
1159
1967
|
);
|
|
1968
|
+
server.registerTool(
|
|
1969
|
+
"memory.consolidate",
|
|
1970
|
+
{
|
|
1971
|
+
title: "Consolidate",
|
|
1972
|
+
description: "Agentic write-back: gathers evidence like `memory.search({ synthesize: true })`, builds a memory-consolidation plan, optionally asks the LLM to veto unsafe entries, then persists via the attached graph store + soft-deprecates via the storage adapter. Set `dryRun: true` to inspect the plan without writing.",
|
|
1973
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
1974
|
+
inputSchema: reflectApplySchema
|
|
1975
|
+
},
|
|
1976
|
+
async (args) => {
|
|
1977
|
+
const a = args;
|
|
1978
|
+
const input = {
|
|
1979
|
+
userId: a.userId,
|
|
1980
|
+
query: a.query,
|
|
1981
|
+
ownerScope: { userId: a.ownerScopeUserId },
|
|
1982
|
+
botIds: a.botIds,
|
|
1983
|
+
dateFrom: a.dateFrom,
|
|
1984
|
+
dateTo: a.dateTo,
|
|
1985
|
+
tiers: a.tiers,
|
|
1986
|
+
limit: a.limit,
|
|
1987
|
+
threshold: a.threshold,
|
|
1988
|
+
dryRun: a.dryRun ?? false,
|
|
1989
|
+
expectedVersion: a.expectedVersion,
|
|
1990
|
+
authToken: a.authToken,
|
|
1991
|
+
llmPlanReview: a.llmPlanReviewMaxTokens !== void 0 ? { maxTokens: a.llmPlanReviewMaxTokens } : void 0
|
|
1992
|
+
};
|
|
1993
|
+
const result = await applyReflectedPlan(
|
|
1994
|
+
{ ...options.unified ?? {} },
|
|
1995
|
+
{ graphStore: options.graphStore, storage: options.storage },
|
|
1996
|
+
input);
|
|
1997
|
+
return {
|
|
1998
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
);
|
|
2002
|
+
const vsaVocabularyEntry = {
|
|
2003
|
+
label: z.string().min(1),
|
|
2004
|
+
vector: z.array(z.number()).describe("Float32 vector for the vocabulary entry.")
|
|
2005
|
+
};
|
|
2006
|
+
const vsaStoreSchema = {
|
|
2007
|
+
userId: z.string(),
|
|
2008
|
+
roleLabel: z.string().min(1),
|
|
2009
|
+
fillerLabel: z.string().min(1),
|
|
2010
|
+
roleVector: z.array(z.number()),
|
|
2011
|
+
fillerVector: z.array(z.number()),
|
|
2012
|
+
scopeTag: z.string().optional(),
|
|
2013
|
+
botId: z.string().optional(),
|
|
2014
|
+
factId: z.string().optional()
|
|
2015
|
+
};
|
|
2016
|
+
const vsaRecallSchema = {
|
|
2017
|
+
userId: z.string(),
|
|
2018
|
+
roleLabel: z.string().min(1),
|
|
2019
|
+
roleVector: z.array(z.number()),
|
|
2020
|
+
vocabulary: z.array(z.object(vsaVocabularyEntry).passthrough()),
|
|
2021
|
+
scopeTag: z.string().optional(),
|
|
2022
|
+
botId: z.string().optional(),
|
|
2023
|
+
maxFacts: z.number().int().min(1).max(1e4).optional()
|
|
2024
|
+
};
|
|
2025
|
+
const vsaListSchema = {
|
|
2026
|
+
userId: z.string(),
|
|
2027
|
+
scopeTag: z.string().optional(),
|
|
2028
|
+
botId: z.string().optional(),
|
|
2029
|
+
includeDeprecated: z.boolean().default(false)
|
|
2030
|
+
};
|
|
2031
|
+
const vsaForgetSchema = {
|
|
2032
|
+
userId: z.string(),
|
|
2033
|
+
factIds: z.array(z.string()).min(1),
|
|
2034
|
+
reason: z.string().optional()
|
|
2035
|
+
};
|
|
2036
|
+
server.registerTool(
|
|
2037
|
+
"memory.vsaStore",
|
|
2038
|
+
{
|
|
2039
|
+
title: "VSA Store Fact",
|
|
2040
|
+
description: "Persist a (role, filler) binding into the user's VSA memory. The vectors are stored as Float32 BLOBs alongside the labels; later `memory.vsaRecall` rebuilds the memory vector and picks the best vocabulary match.",
|
|
2041
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
2042
|
+
inputSchema: vsaStoreSchema
|
|
2043
|
+
},
|
|
2044
|
+
async (args) => {
|
|
2045
|
+
const a = args;
|
|
2046
|
+
try {
|
|
2047
|
+
const result = await vsa.storeFact({
|
|
2048
|
+
userId: a.userId,
|
|
2049
|
+
roleLabel: a.roleLabel,
|
|
2050
|
+
fillerLabel: a.fillerLabel,
|
|
2051
|
+
roleVector: a.roleVector,
|
|
2052
|
+
fillerVector: a.fillerVector,
|
|
2053
|
+
scopeTag: a.scopeTag,
|
|
2054
|
+
botId: a.botId,
|
|
2055
|
+
factId: a.factId
|
|
2056
|
+
});
|
|
2057
|
+
return {
|
|
2058
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
2059
|
+
};
|
|
2060
|
+
} catch (error) {
|
|
2061
|
+
return {
|
|
2062
|
+
content: [
|
|
2063
|
+
{
|
|
2064
|
+
type: "text",
|
|
2065
|
+
text: JSON.stringify({ error: error.message ?? "vsa.store failed" })
|
|
2066
|
+
}
|
|
2067
|
+
],
|
|
2068
|
+
isError: true
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
);
|
|
2073
|
+
server.registerTool(
|
|
2074
|
+
"memory.vsaRecall",
|
|
2075
|
+
{
|
|
2076
|
+
title: "VSA Recall",
|
|
2077
|
+
description: "Rebuild the user's VSA memory vector from stored (role, filler) facts, unbind by the requested role, and cleanup against the supplied vocabulary. Returns the best-match label plus a sorted score list and warnings (e.g. low confidence, dim mismatch).",
|
|
2078
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
2079
|
+
inputSchema: vsaRecallSchema
|
|
2080
|
+
},
|
|
2081
|
+
async (args) => {
|
|
2082
|
+
const a = args;
|
|
2083
|
+
try {
|
|
2084
|
+
const result = await vsa.recall({
|
|
2085
|
+
userId: a.userId,
|
|
2086
|
+
roleLabel: a.roleLabel,
|
|
2087
|
+
roleVector: a.roleVector,
|
|
2088
|
+
vocabulary: a.vocabulary,
|
|
2089
|
+
scopeTag: a.scopeTag,
|
|
2090
|
+
botId: a.botId,
|
|
2091
|
+
maxFacts: a.maxFacts
|
|
2092
|
+
});
|
|
2093
|
+
return {
|
|
2094
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
2095
|
+
};
|
|
2096
|
+
} catch (error) {
|
|
2097
|
+
return {
|
|
2098
|
+
content: [
|
|
2099
|
+
{
|
|
2100
|
+
type: "text",
|
|
2101
|
+
text: JSON.stringify({ error: error.message ?? "vsa.recall failed" })
|
|
2102
|
+
}
|
|
2103
|
+
],
|
|
2104
|
+
isError: true
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
);
|
|
2109
|
+
server.registerTool(
|
|
2110
|
+
"memory.vsaList",
|
|
2111
|
+
{
|
|
2112
|
+
title: "VSA List Facts",
|
|
2113
|
+
description: "List the (role, filler) facts stored for a user. Vectors are stripped from the response \u2014 for the underlying storage layer, query SQLite directly.",
|
|
2114
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
2115
|
+
inputSchema: vsaListSchema
|
|
2116
|
+
},
|
|
2117
|
+
async (args) => {
|
|
2118
|
+
const a = args;
|
|
2119
|
+
try {
|
|
2120
|
+
const facts = await vsa.listFacts({
|
|
2121
|
+
userId: a.userId,
|
|
2122
|
+
scopeTag: a.scopeTag,
|
|
2123
|
+
botId: a.botId,
|
|
2124
|
+
includeDeprecated: a.includeDeprecated
|
|
2125
|
+
});
|
|
2126
|
+
return {
|
|
2127
|
+
content: [{ type: "text", text: JSON.stringify({ facts }) }]
|
|
2128
|
+
};
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
return {
|
|
2131
|
+
content: [
|
|
2132
|
+
{
|
|
2133
|
+
type: "text",
|
|
2134
|
+
text: JSON.stringify({ error: error.message ?? "vsa.list failed" })
|
|
2135
|
+
}
|
|
2136
|
+
],
|
|
2137
|
+
isError: true
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
);
|
|
2142
|
+
server.registerTool(
|
|
2143
|
+
"memory.vsaForget",
|
|
2144
|
+
{
|
|
2145
|
+
title: "VSA Forget Facts",
|
|
2146
|
+
description: "Soft-delete one or more VSA facts by id. Idempotent \u2014 facts already deprecated return `deprecatedCount: 0`.",
|
|
2147
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
2148
|
+
inputSchema: vsaForgetSchema
|
|
2149
|
+
},
|
|
2150
|
+
async (args) => {
|
|
2151
|
+
const a = args;
|
|
2152
|
+
try {
|
|
2153
|
+
const result = await vsa.forget({
|
|
2154
|
+
userId: a.userId,
|
|
2155
|
+
factIds: a.factIds,
|
|
2156
|
+
reason: a.reason
|
|
2157
|
+
});
|
|
2158
|
+
return {
|
|
2159
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
2160
|
+
};
|
|
2161
|
+
} catch (error) {
|
|
2162
|
+
return {
|
|
2163
|
+
content: [
|
|
2164
|
+
{
|
|
2165
|
+
type: "text",
|
|
2166
|
+
text: JSON.stringify({ error: error.message ?? "vsa.forget failed" })
|
|
2167
|
+
}
|
|
2168
|
+
],
|
|
2169
|
+
isError: true
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
);
|
|
1160
2174
|
const transport = new StdioServerTransport();
|
|
1161
2175
|
await server.connect(transport);
|
|
1162
2176
|
return server;
|