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