@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/http.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
|
|
1
2
|
import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
|
|
2
3
|
import { ChromaVectorStore } from '@melandlabs/rag';
|
|
3
4
|
import { mkdirSync } from 'fs';
|
|
4
5
|
import { dirname } from 'path';
|
|
5
6
|
import { getOpenContextPath } from '@melandlabs/env-config';
|
|
6
|
-
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
7
|
+
import { SQLiteRawMessageManager, getSQLiteVsaStore, closeSQLiteVsaStore } from '@melandlabs/sqlite';
|
|
7
8
|
import { serve } from '@hono/node-server';
|
|
9
|
+
import { registerOkfRoutes } from '@melandlabs/okf/http';
|
|
8
10
|
import { Hono } from 'hono';
|
|
11
|
+
import { applyReflectedConsolidationPlan } from '@melandlabs/memory-consolidation';
|
|
12
|
+
import { superpose, bind, unbind, cleanup, cosineSimilarity } from '@melandlabs/vsa';
|
|
9
13
|
|
|
10
14
|
var __defProp = Object.defineProperty;
|
|
11
15
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -16,8 +20,6 @@ var __export = (target, all) => {
|
|
|
16
20
|
for (var name in all)
|
|
17
21
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
22
|
};
|
|
19
|
-
|
|
20
|
-
// src/contracts.ts
|
|
21
23
|
function normalizeTimestampToMs(value) {
|
|
22
24
|
if (!Number.isFinite(value)) {
|
|
23
25
|
return 0;
|
|
@@ -324,6 +326,41 @@ var init_raw_message_store = __esm({
|
|
|
324
326
|
init_chroma_memory_index();
|
|
325
327
|
init_raw_message_store();
|
|
326
328
|
|
|
329
|
+
// src/search/reranker.ts
|
|
330
|
+
async function applyReranker(reranker, query, results) {
|
|
331
|
+
if (!reranker || results.length === 0) {
|
|
332
|
+
return results;
|
|
333
|
+
}
|
|
334
|
+
const scores = await reranker.rerank({
|
|
335
|
+
query,
|
|
336
|
+
candidates: results.map((result) => ({
|
|
337
|
+
id: result.id,
|
|
338
|
+
content: result.content,
|
|
339
|
+
metadata: result.metadata
|
|
340
|
+
}))
|
|
341
|
+
});
|
|
342
|
+
if (scores.length === 0) {
|
|
343
|
+
return results;
|
|
344
|
+
}
|
|
345
|
+
const byId = new Map(results.map((result) => [result.id, result]));
|
|
346
|
+
const reordered = [];
|
|
347
|
+
const seen = /* @__PURE__ */ new Set();
|
|
348
|
+
for (const { id, score } of scores) {
|
|
349
|
+
const hit = byId.get(id);
|
|
350
|
+
if (!hit || seen.has(id)) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
seen.add(id);
|
|
354
|
+
reordered.push({ ...hit, metadata: { ...hit.metadata, rerankerScore: score } });
|
|
355
|
+
}
|
|
356
|
+
for (const hit of results) {
|
|
357
|
+
if (!seen.has(hit.id)) {
|
|
358
|
+
reordered.push(hit);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return reordered;
|
|
362
|
+
}
|
|
363
|
+
|
|
327
364
|
// src/search/utilities.ts
|
|
328
365
|
var DEFAULT_LIMIT = 10;
|
|
329
366
|
var DEFAULT_THRESHOLD = 0.7;
|
|
@@ -445,6 +482,264 @@ function isRawMemorySemanticResult(result) {
|
|
|
445
482
|
const item = result;
|
|
446
483
|
return typeof item.id === "string" && typeof item.content === "string" && typeof item.similarity === "number" && Boolean(item.metadata) && typeof item.metadata === "object";
|
|
447
484
|
}
|
|
485
|
+
async function resolveScopePeer(input) {
|
|
486
|
+
const peers = input.peerFilter ?? [];
|
|
487
|
+
if (peers.length === 0) {
|
|
488
|
+
return { peers: [], warnings: [] };
|
|
489
|
+
}
|
|
490
|
+
if (typeof input.scopeCheck !== "function") {
|
|
491
|
+
return { peers, warnings: [] };
|
|
492
|
+
}
|
|
493
|
+
const retained = [];
|
|
494
|
+
const dropped = [];
|
|
495
|
+
for (const peer of peers) {
|
|
496
|
+
const ok = await input.scopeCheck({ userId: input.userId, peers: [peer] });
|
|
497
|
+
if (ok) {
|
|
498
|
+
retained.push(peer);
|
|
499
|
+
} else {
|
|
500
|
+
dropped.push(peer);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (dropped.length === 0) {
|
|
504
|
+
return { peers, warnings: [] };
|
|
505
|
+
}
|
|
506
|
+
if (retained.length === 0) {
|
|
507
|
+
return {
|
|
508
|
+
peers: [],
|
|
509
|
+
warnings: [
|
|
510
|
+
{
|
|
511
|
+
source: "memory",
|
|
512
|
+
code: "peer_filter_outside_user_scope",
|
|
513
|
+
message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored; falling back to userId scope`
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
peers: retained,
|
|
520
|
+
warnings: [
|
|
521
|
+
{
|
|
522
|
+
source: "memory",
|
|
523
|
+
code: "peer_filter_outside_user_scope",
|
|
524
|
+
message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored because they fall outside userId ${input.userId}`
|
|
525
|
+
}
|
|
526
|
+
]
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/search/gather-evidence.ts
|
|
531
|
+
var DEFAULT_TIERS = ["summary", "raw", "insight", "knowledge"];
|
|
532
|
+
var MAX_EVIDENCE_CHARS = 600;
|
|
533
|
+
function toSummaryResult(row) {
|
|
534
|
+
const start = typeof row.startTimestamp === "number" ? row.startTimestamp : void 0;
|
|
535
|
+
const end = typeof row.endTimestamp === "number" ? row.endTimestamp : void 0;
|
|
536
|
+
return {
|
|
537
|
+
type: "memory",
|
|
538
|
+
id: row.summaryId,
|
|
539
|
+
content: row.summaryText,
|
|
540
|
+
similarity: 1,
|
|
541
|
+
metadata: {
|
|
542
|
+
tier: "summary",
|
|
543
|
+
summaryTier: row.summaryTier,
|
|
544
|
+
keywords: row.keywords,
|
|
545
|
+
startTimestamp: start,
|
|
546
|
+
endTimestamp: end
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers) {
|
|
551
|
+
if (typeof deps.searchSummaries !== "function") {
|
|
552
|
+
return { hits: [], warnings: [] };
|
|
553
|
+
}
|
|
554
|
+
const keywords = input.query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
555
|
+
try {
|
|
556
|
+
const rows = await deps.searchSummaries({
|
|
557
|
+
userId: input.userId,
|
|
558
|
+
query: input.query,
|
|
559
|
+
keywords,
|
|
560
|
+
limit,
|
|
561
|
+
threshold,
|
|
562
|
+
authToken: input.authToken,
|
|
563
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
564
|
+
});
|
|
565
|
+
const hits = rows.map(toSummaryResult);
|
|
566
|
+
return { hits, warnings: [] };
|
|
567
|
+
} catch (error) {
|
|
568
|
+
logger.warn?.("[memory-store] gather-evidence: summary search failed:", error);
|
|
569
|
+
return {
|
|
570
|
+
hits: [],
|
|
571
|
+
warnings: [
|
|
572
|
+
{
|
|
573
|
+
source: "memory",
|
|
574
|
+
code: "reflect_summaries_failed",
|
|
575
|
+
message: error.message ?? "reflect_summaries_failed"
|
|
576
|
+
}
|
|
577
|
+
]
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function truncate(input, max = MAX_EVIDENCE_CHARS) {
|
|
582
|
+
if (input.length <= max) {
|
|
583
|
+
return input;
|
|
584
|
+
}
|
|
585
|
+
return `${input.slice(0, max - 1)}\u2026`;
|
|
586
|
+
}
|
|
587
|
+
function buildSynthesisPrompt(input) {
|
|
588
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
589
|
+
for (const item of input.evidence) {
|
|
590
|
+
const bucket = grouped.get(item.source) ?? [];
|
|
591
|
+
bucket.push(item);
|
|
592
|
+
grouped.set(item.source, bucket);
|
|
593
|
+
}
|
|
594
|
+
const sections = [];
|
|
595
|
+
for (const tier of DEFAULT_TIERS) {
|
|
596
|
+
const items = grouped.get(tier) ?? [];
|
|
597
|
+
if (items.length === 0) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
const lines = items.map(
|
|
601
|
+
(item, index) => ` [${index + 1}] (${item.id}, score=${item.score.toFixed(4)}) ${item.snippet}`
|
|
602
|
+
);
|
|
603
|
+
sections.push(`## ${tier}
|
|
604
|
+
${lines.join("\n")}`);
|
|
605
|
+
}
|
|
606
|
+
const schemaDirective = input.responseSchema ? `
|
|
607
|
+
Respond with a JSON object that conforms to the following schema:
|
|
608
|
+
${JSON.stringify(input.responseSchema, null, 2)}
|
|
609
|
+
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.";
|
|
610
|
+
if (sections.length === 0) {
|
|
611
|
+
return `You are a reflection assistant.
|
|
612
|
+
|
|
613
|
+
Question: ${input.query}
|
|
614
|
+
|
|
615
|
+
No evidence was found across any tier. State that explicitly.${schemaDirective}`;
|
|
616
|
+
}
|
|
617
|
+
return `You are a reflection assistant. Synthesise the user's question using only the evidence below. Do not invent facts.
|
|
618
|
+
|
|
619
|
+
Question: ${input.query}
|
|
620
|
+
|
|
621
|
+
Evidence (grouped by tier):
|
|
622
|
+
${sections.join("\n\n")}${schemaDirective}`;
|
|
623
|
+
}
|
|
624
|
+
function extractJsonPayload(text) {
|
|
625
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
626
|
+
if (fenced?.[1]) {
|
|
627
|
+
return fenced[1];
|
|
628
|
+
}
|
|
629
|
+
const start = text.indexOf("{");
|
|
630
|
+
if (start === -1) {
|
|
631
|
+
return void 0;
|
|
632
|
+
}
|
|
633
|
+
let depth = 0;
|
|
634
|
+
let inString = false;
|
|
635
|
+
let escaped = false;
|
|
636
|
+
for (let i = start; i < text.length; i += 1) {
|
|
637
|
+
const ch = text[i];
|
|
638
|
+
if (escaped) {
|
|
639
|
+
escaped = false;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (inString) {
|
|
643
|
+
if (ch === "\\") {
|
|
644
|
+
escaped = true;
|
|
645
|
+
} else if (ch === '"') {
|
|
646
|
+
inString = false;
|
|
647
|
+
}
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (ch === '"') {
|
|
651
|
+
inString = true;
|
|
652
|
+
} else if (ch === "{") {
|
|
653
|
+
depth += 1;
|
|
654
|
+
} else if (ch === "}") {
|
|
655
|
+
depth -= 1;
|
|
656
|
+
if (depth === 0) {
|
|
657
|
+
return text.slice(start, i + 1);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return void 0;
|
|
662
|
+
}
|
|
663
|
+
function coerceAnswer(text, schema) {
|
|
664
|
+
if (!schema) {
|
|
665
|
+
return text;
|
|
666
|
+
}
|
|
667
|
+
const payload = extractJsonPayload(text);
|
|
668
|
+
if (!payload) {
|
|
669
|
+
return text;
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
const parsed = JSON.parse(payload);
|
|
673
|
+
if (parsed && typeof parsed === "object") {
|
|
674
|
+
const answer = parsed.answer;
|
|
675
|
+
if (typeof answer === "string") {
|
|
676
|
+
return answer;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
return text;
|
|
682
|
+
}
|
|
683
|
+
async function synthesizeAnswer(input) {
|
|
684
|
+
const { query, hits, responseSchema, deps, logger } = input;
|
|
685
|
+
const evidence = hits.map((hit) => ({
|
|
686
|
+
id: hit.id,
|
|
687
|
+
source: mapHitToTier(hit),
|
|
688
|
+
snippet: truncate(hit.content),
|
|
689
|
+
score: hit.similarity
|
|
690
|
+
}));
|
|
691
|
+
const warnings = [];
|
|
692
|
+
const complete = deps.reasoning?.complete;
|
|
693
|
+
if (!complete) {
|
|
694
|
+
warnings.push({
|
|
695
|
+
source: "memory",
|
|
696
|
+
code: "reflect_llm_not_configured",
|
|
697
|
+
message: "No `reasoning.complete` callback is configured; search() returned evidence without an LLM synthesis."
|
|
698
|
+
});
|
|
699
|
+
return { answer: "", warnings };
|
|
700
|
+
}
|
|
701
|
+
const prompt = buildSynthesisPrompt({ query, evidence, responseSchema });
|
|
702
|
+
try {
|
|
703
|
+
const raw = await complete(prompt);
|
|
704
|
+
const answer = coerceAnswer(raw, responseSchema);
|
|
705
|
+
logger.log?.("[memory-store] search synthesis completed", {
|
|
706
|
+
evidenceCount: evidence.length,
|
|
707
|
+
answerChars: answer.length
|
|
708
|
+
});
|
|
709
|
+
return { answer, warnings };
|
|
710
|
+
} catch (error) {
|
|
711
|
+
logger.warn?.("[memory-store] search synthesis failed:", error);
|
|
712
|
+
warnings.push({
|
|
713
|
+
source: "memory",
|
|
714
|
+
code: "reflect_llm_failed",
|
|
715
|
+
message: error.message ?? "reflect_llm_failed"
|
|
716
|
+
});
|
|
717
|
+
return { answer: "", warnings };
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function mapHitToTier(hit) {
|
|
721
|
+
const tierMarker = hit.metadata?.tier;
|
|
722
|
+
if (tierMarker === "summary") {
|
|
723
|
+
return "summary";
|
|
724
|
+
}
|
|
725
|
+
if (hit.type === "memory") {
|
|
726
|
+
return "raw";
|
|
727
|
+
}
|
|
728
|
+
if (hit.type === "insight") {
|
|
729
|
+
return "insight";
|
|
730
|
+
}
|
|
731
|
+
if (hit.type === "knowledge") {
|
|
732
|
+
return "knowledge";
|
|
733
|
+
}
|
|
734
|
+
return hit.type;
|
|
735
|
+
}
|
|
736
|
+
async function resolveSearchScopePeers(input) {
|
|
737
|
+
return resolveScopePeer({
|
|
738
|
+
userId: input.userId,
|
|
739
|
+
peerFilter: input.peerFilter,
|
|
740
|
+
scopeCheck: input.deps.peerScopeCheck
|
|
741
|
+
});
|
|
742
|
+
}
|
|
448
743
|
|
|
449
744
|
// src/search/unified-search.ts
|
|
450
745
|
function deriveLexicalKeywords(query) {
|
|
@@ -516,7 +811,7 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
516
811
|
authToken: input.authToken
|
|
517
812
|
});
|
|
518
813
|
}
|
|
519
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
814
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers = []) {
|
|
520
815
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
521
816
|
let semantic = [];
|
|
522
817
|
if (isRawMessageChromaEnabled()) {
|
|
@@ -546,6 +841,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
546
841
|
}
|
|
547
842
|
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
548
843
|
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
844
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
549
845
|
semantic = (await Promise.all(
|
|
550
846
|
filters.map(
|
|
551
847
|
(filter) => searchRawMessagesAnn({
|
|
@@ -553,7 +849,9 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
553
849
|
queryEmbedding,
|
|
554
850
|
limit,
|
|
555
851
|
threshold,
|
|
556
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
852
|
+
botId: "botId" in filter ? filter.botId : void 0,
|
|
853
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
854
|
+
...factTypes ? { factTypes } : {}
|
|
557
855
|
})
|
|
558
856
|
)
|
|
559
857
|
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
@@ -563,11 +861,14 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
563
861
|
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
564
862
|
const manager2 = await getRawMessageManager2();
|
|
565
863
|
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
864
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
566
865
|
const results = await manager2.searchMessagesSemantically({
|
|
567
866
|
userId: input.userId,
|
|
568
867
|
queryEmbedding,
|
|
569
868
|
limit,
|
|
570
|
-
threshold
|
|
869
|
+
threshold,
|
|
870
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
871
|
+
...factTypes ? { factTypes } : {}
|
|
571
872
|
});
|
|
572
873
|
semantic = results.map((r) => ({
|
|
573
874
|
type: "memory",
|
|
@@ -603,7 +904,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
603
904
|
metadata: hit.metadata
|
|
604
905
|
};
|
|
605
906
|
}
|
|
606
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
907
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers = []) {
|
|
607
908
|
if (keywords.length === 0) {
|
|
608
909
|
return [];
|
|
609
910
|
}
|
|
@@ -611,13 +912,16 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
611
912
|
try {
|
|
612
913
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
613
914
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
915
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
614
916
|
return (await Promise.all(
|
|
615
917
|
filters.map(
|
|
616
918
|
(botId) => searchRawMessagesLexical({
|
|
617
919
|
userId: input.userId,
|
|
618
920
|
keywords,
|
|
619
921
|
limit: Math.ceil(limit / filters.length),
|
|
620
|
-
botId
|
|
922
|
+
botId,
|
|
923
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
924
|
+
...factTypes ? { factTypes } : {}
|
|
621
925
|
})
|
|
622
926
|
)
|
|
623
927
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -631,13 +935,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
631
935
|
try {
|
|
632
936
|
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
633
937
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
938
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
634
939
|
return (await Promise.all(
|
|
635
940
|
filters.map(
|
|
636
941
|
(botId) => lexicalSearchRawMessages2({
|
|
637
942
|
userId: input.userId,
|
|
638
943
|
keywords,
|
|
639
944
|
limit: Math.ceil(limit / filters.length),
|
|
640
|
-
botId
|
|
945
|
+
botId,
|
|
946
|
+
...factTypes ? { factTypes } : {}
|
|
641
947
|
})
|
|
642
948
|
)
|
|
643
949
|
)).flat().filter(Boolean).map(
|
|
@@ -650,6 +956,42 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
650
956
|
}
|
|
651
957
|
return [];
|
|
652
958
|
}
|
|
959
|
+
function searchInputToUnified(input) {
|
|
960
|
+
return {
|
|
961
|
+
userId: input.userId,
|
|
962
|
+
query: input.query,
|
|
963
|
+
sources: input.sources ? [...input.sources] : void 0,
|
|
964
|
+
limit: input.limit,
|
|
965
|
+
threshold: input.threshold,
|
|
966
|
+
authToken: input.authToken,
|
|
967
|
+
includeArchivedInsights: input.includeArchivedInsights,
|
|
968
|
+
botIds: input.botIds,
|
|
969
|
+
documentIds: input.documentIds,
|
|
970
|
+
asOf: input.asOf,
|
|
971
|
+
dateFrom: input.dateFrom,
|
|
972
|
+
dateTo: input.dateTo,
|
|
973
|
+
mergeStrategy: input.mergeStrategy,
|
|
974
|
+
peerFilter: input.peerFilter,
|
|
975
|
+
reasoningStrategy: input.reasoningStrategy,
|
|
976
|
+
factTypes: input.factTypes
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function mapHitToEvidenceSource(hit) {
|
|
980
|
+
const tierMarker = hit.metadata?.tier;
|
|
981
|
+
if (tierMarker === "summary") {
|
|
982
|
+
return "summary";
|
|
983
|
+
}
|
|
984
|
+
if (hit.type === "memory") {
|
|
985
|
+
return "raw";
|
|
986
|
+
}
|
|
987
|
+
if (hit.type === "insight") {
|
|
988
|
+
return "insight";
|
|
989
|
+
}
|
|
990
|
+
if (hit.type === "knowledge") {
|
|
991
|
+
return "knowledge";
|
|
992
|
+
}
|
|
993
|
+
return hit.type;
|
|
994
|
+
}
|
|
653
995
|
function createUnifiedSearch(deps = {}) {
|
|
654
996
|
const logger = deps.logger ?? console;
|
|
655
997
|
function resolveReasoningStrategy(input) {
|
|
@@ -659,130 +1001,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
659
1001
|
}
|
|
660
1002
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
661
1003
|
}
|
|
662
|
-
async function
|
|
663
|
-
const query = input.query.trim();
|
|
664
|
-
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
665
|
-
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
666
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
667
|
-
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
668
|
-
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
669
|
-
const warnings = [];
|
|
670
|
-
if (!query) {
|
|
671
|
-
return {
|
|
672
|
-
query,
|
|
673
|
-
sources,
|
|
674
|
-
results: [],
|
|
675
|
-
count: 0,
|
|
676
|
-
warnings
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
let memorySubs;
|
|
680
|
-
const reasoningInfo = { strategy: reasoningStrategy };
|
|
681
|
-
if (input.dateFrom || input.dateTo) {
|
|
682
|
-
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
683
|
-
}
|
|
684
|
-
if (sources.includes("memory")) {
|
|
685
|
-
if (isRawMessageStorageAvailable()) {
|
|
686
|
-
try {
|
|
687
|
-
memorySubs = await runMemorySource(
|
|
688
|
-
input,
|
|
689
|
-
limit,
|
|
690
|
-
threshold,
|
|
691
|
-
warnings,
|
|
692
|
-
reasoningStrategy,
|
|
693
|
-
reasoningInfo
|
|
694
|
-
);
|
|
695
|
-
} catch (error) {
|
|
696
|
-
logger.warn?.("[memory-store] memory source failed:", error);
|
|
697
|
-
warnings.push({
|
|
698
|
-
source: "memory",
|
|
699
|
-
code: "memory_search_failed",
|
|
700
|
-
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
} else {
|
|
704
|
-
warnings.push({
|
|
705
|
-
source: "memory",
|
|
706
|
-
code: "raw_message_storage_unavailable",
|
|
707
|
-
message: "Raw memory storage is not available in this environment."
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
const insightHits = [];
|
|
712
|
-
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
713
|
-
try {
|
|
714
|
-
const hits = await deps.searchInsights({
|
|
715
|
-
userId: input.userId,
|
|
716
|
-
query: input.query,
|
|
717
|
-
limit,
|
|
718
|
-
threshold,
|
|
719
|
-
botIds: input.botIds,
|
|
720
|
-
includeArchived: input.includeArchivedInsights,
|
|
721
|
-
authToken: input.authToken
|
|
722
|
-
});
|
|
723
|
-
for (const hit of hits) {
|
|
724
|
-
insightHits.push({
|
|
725
|
-
type: "insight",
|
|
726
|
-
id: hit.id,
|
|
727
|
-
content: hit.content,
|
|
728
|
-
similarity: hit.similarity,
|
|
729
|
-
metadata: hit.metadata
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
} catch (error) {
|
|
733
|
-
logger.warn?.("[memory-store] insights source failed:", error);
|
|
734
|
-
warnings.push({
|
|
735
|
-
source: "insights",
|
|
736
|
-
code: "insights_search_failed",
|
|
737
|
-
message: error.message ?? "insights_search_failed"
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
} else if (sources.includes("insights")) ;
|
|
741
|
-
const knowledgeHits = [];
|
|
742
|
-
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
743
|
-
try {
|
|
744
|
-
const hits = await deps.searchKnowledge({
|
|
745
|
-
userId: input.userId,
|
|
746
|
-
query: input.query,
|
|
747
|
-
options: {
|
|
748
|
-
limit,
|
|
749
|
-
threshold,
|
|
750
|
-
documentIds: input.documentIds
|
|
751
|
-
},
|
|
752
|
-
authToken: input.authToken
|
|
753
|
-
});
|
|
754
|
-
for (const hit of hits) {
|
|
755
|
-
knowledgeHits.push(toKnowledgeResult(hit));
|
|
756
|
-
}
|
|
757
|
-
} catch (error) {
|
|
758
|
-
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
759
|
-
warnings.push({
|
|
760
|
-
source: "knowledge",
|
|
761
|
-
code: "knowledge_search_failed",
|
|
762
|
-
message: error.message ?? "knowledge_search_failed"
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
} else if (sources.includes("knowledge")) ;
|
|
766
|
-
const merged = mergeAcrossSources({
|
|
767
|
-
memorySubs,
|
|
768
|
-
insightHits,
|
|
769
|
-
knowledgeHits,
|
|
770
|
-
limit,
|
|
771
|
-
strategy: mergeStrategy
|
|
772
|
-
});
|
|
773
|
-
const output = {
|
|
774
|
-
query: input.query,
|
|
775
|
-
sources,
|
|
776
|
-
results: merged,
|
|
777
|
-
count: merged.length,
|
|
778
|
-
warnings
|
|
779
|
-
};
|
|
780
|
-
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
781
|
-
output.reasoning = reasoningInfo;
|
|
782
|
-
}
|
|
783
|
-
return output;
|
|
784
|
-
}
|
|
785
|
-
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
1004
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
|
|
786
1005
|
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
787
1006
|
warnings.push({
|
|
788
1007
|
source: "memory",
|
|
@@ -808,7 +1027,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
808
1027
|
executor: {
|
|
809
1028
|
search: async (request) => {
|
|
810
1029
|
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
811
|
-
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1030
|
+
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1031
|
+
deps,
|
|
1032
|
+
input,
|
|
1033
|
+
keywords2,
|
|
1034
|
+
limit,
|
|
1035
|
+
logger,
|
|
1036
|
+
peerPeers
|
|
1037
|
+
);
|
|
812
1038
|
let semanticHits = [];
|
|
813
1039
|
if (typeof deps.embedQuery === "function") {
|
|
814
1040
|
try {
|
|
@@ -820,7 +1046,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
820
1046
|
queryEmbedding,
|
|
821
1047
|
limit,
|
|
822
1048
|
threshold,
|
|
823
|
-
logger
|
|
1049
|
+
logger,
|
|
1050
|
+
peerPeers
|
|
824
1051
|
);
|
|
825
1052
|
} catch (error) {
|
|
826
1053
|
logger.warn?.(
|
|
@@ -865,7 +1092,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
865
1092
|
});
|
|
866
1093
|
const keywords2 = deriveLexicalKeywords(input.query);
|
|
867
1094
|
const lexical2 = filterByDateRange(
|
|
868
|
-
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
1095
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger, peerPeers),
|
|
869
1096
|
input.dateFrom,
|
|
870
1097
|
input.dateTo
|
|
871
1098
|
);
|
|
@@ -891,7 +1118,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
891
1118
|
);
|
|
892
1119
|
const lists = await Promise.all(
|
|
893
1120
|
embeddings.map(
|
|
894
|
-
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
1121
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger, peerPeers)
|
|
895
1122
|
)
|
|
896
1123
|
);
|
|
897
1124
|
semantic = mergeByMaxScore(lists);
|
|
@@ -907,7 +1134,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
907
1134
|
}
|
|
908
1135
|
if (semantic.length === 0) {
|
|
909
1136
|
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
910
|
-
semantic = await runSemanticSearchForEmbedding(
|
|
1137
|
+
semantic = await runSemanticSearchForEmbedding(
|
|
1138
|
+
deps,
|
|
1139
|
+
input,
|
|
1140
|
+
queryEmbedding,
|
|
1141
|
+
limit,
|
|
1142
|
+
threshold,
|
|
1143
|
+
logger,
|
|
1144
|
+
peerPeers
|
|
1145
|
+
);
|
|
911
1146
|
}
|
|
912
1147
|
let lexical = [];
|
|
913
1148
|
const keywords = deriveLexicalKeywords(input.query);
|
|
@@ -916,13 +1151,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
916
1151
|
try {
|
|
917
1152
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
918
1153
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
1154
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
919
1155
|
lexical = (await Promise.all(
|
|
920
1156
|
lexFilters.map(
|
|
921
1157
|
(botId) => searchRawMessagesLexical({
|
|
922
1158
|
userId: input.userId,
|
|
923
1159
|
keywords,
|
|
924
1160
|
limit,
|
|
925
|
-
botId
|
|
1161
|
+
botId,
|
|
1162
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1163
|
+
...factTypes ? { factTypes } : {}
|
|
926
1164
|
})
|
|
927
1165
|
)
|
|
928
1166
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -950,20 +1188,272 @@ function createUnifiedSearch(deps = {}) {
|
|
|
950
1188
|
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
951
1189
|
};
|
|
952
1190
|
}
|
|
1191
|
+
async function searchUnifiedMemory(input) {
|
|
1192
|
+
const query = input.query.trim();
|
|
1193
|
+
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
1194
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1195
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1196
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1197
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1198
|
+
);
|
|
1199
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1200
|
+
const warnings = [];
|
|
1201
|
+
const peerScope = input.peerFilter && input.peerFilter.length > 0 ? await resolveScopePeer({
|
|
1202
|
+
userId: input.userId,
|
|
1203
|
+
peerFilter: input.peerFilter,
|
|
1204
|
+
scopeCheck: deps.peerScopeCheck
|
|
1205
|
+
}) : { peers: [], warnings: [] };
|
|
1206
|
+
warnings.push(...peerScope.warnings);
|
|
1207
|
+
const peerPeers = peerScope.peers;
|
|
1208
|
+
if (!query) {
|
|
1209
|
+
return {
|
|
1210
|
+
query,
|
|
1211
|
+
sources,
|
|
1212
|
+
results: [],
|
|
1213
|
+
count: 0,
|
|
1214
|
+
warnings
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
let memorySubs;
|
|
1218
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
1219
|
+
if (input.dateFrom || input.dateTo) {
|
|
1220
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1221
|
+
}
|
|
1222
|
+
if (sources.includes("memory")) {
|
|
1223
|
+
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1224
|
+
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1225
|
+
try {
|
|
1226
|
+
memorySubs = await runMemorySource(
|
|
1227
|
+
input,
|
|
1228
|
+
limit,
|
|
1229
|
+
threshold,
|
|
1230
|
+
warnings,
|
|
1231
|
+
reasoningStrategy,
|
|
1232
|
+
reasoningInfo,
|
|
1233
|
+
peerPeers
|
|
1234
|
+
);
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1237
|
+
warnings.push({
|
|
1238
|
+
source: "memory",
|
|
1239
|
+
code: "memory_search_failed",
|
|
1240
|
+
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
} else {
|
|
1244
|
+
warnings.push({
|
|
1245
|
+
source: "memory",
|
|
1246
|
+
code: "raw_message_storage_unavailable",
|
|
1247
|
+
message: "Raw memory storage is not available in this environment."
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const insightHits = [];
|
|
1252
|
+
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
1253
|
+
try {
|
|
1254
|
+
const hits = await deps.searchInsights({
|
|
1255
|
+
userId: input.userId,
|
|
1256
|
+
query: input.query,
|
|
1257
|
+
limit,
|
|
1258
|
+
threshold,
|
|
1259
|
+
botIds: input.botIds,
|
|
1260
|
+
includeArchived: input.includeArchivedInsights,
|
|
1261
|
+
authToken: input.authToken,
|
|
1262
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1263
|
+
});
|
|
1264
|
+
for (const hit of hits) {
|
|
1265
|
+
insightHits.push({
|
|
1266
|
+
type: "insight",
|
|
1267
|
+
id: hit.id,
|
|
1268
|
+
content: hit.content,
|
|
1269
|
+
similarity: hit.similarity,
|
|
1270
|
+
metadata: hit.metadata
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
logger.warn?.("[memory-store] insights source failed:", error);
|
|
1275
|
+
warnings.push({
|
|
1276
|
+
source: "insights",
|
|
1277
|
+
code: "insights_search_failed",
|
|
1278
|
+
message: error.message ?? "insights_search_failed"
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
} else if (sources.includes("insights")) ;
|
|
1282
|
+
const knowledgeHits = [];
|
|
1283
|
+
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
1284
|
+
try {
|
|
1285
|
+
const hits = await deps.searchKnowledge({
|
|
1286
|
+
userId: input.userId,
|
|
1287
|
+
query: input.query,
|
|
1288
|
+
options: {
|
|
1289
|
+
limit,
|
|
1290
|
+
threshold,
|
|
1291
|
+
documentIds: input.documentIds
|
|
1292
|
+
},
|
|
1293
|
+
authToken: input.authToken,
|
|
1294
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1295
|
+
});
|
|
1296
|
+
for (const hit of hits) {
|
|
1297
|
+
knowledgeHits.push(toKnowledgeResult(hit));
|
|
1298
|
+
}
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
1301
|
+
warnings.push({
|
|
1302
|
+
source: "knowledge",
|
|
1303
|
+
code: "knowledge_search_failed",
|
|
1304
|
+
message: error.message ?? "knowledge_search_failed"
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
} else if (sources.includes("knowledge")) ;
|
|
1308
|
+
const merged = mergeAcrossSources({
|
|
1309
|
+
memorySubs,
|
|
1310
|
+
insightHits,
|
|
1311
|
+
knowledgeHits,
|
|
1312
|
+
limit,
|
|
1313
|
+
strategy: mergeStrategy
|
|
1314
|
+
});
|
|
1315
|
+
const ranked = await applyReranker(deps.reranker, input.query, merged);
|
|
1316
|
+
const output = {
|
|
1317
|
+
query: input.query,
|
|
1318
|
+
sources,
|
|
1319
|
+
results: ranked,
|
|
1320
|
+
count: ranked.length,
|
|
1321
|
+
warnings
|
|
1322
|
+
};
|
|
1323
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
1324
|
+
output.reasoning = reasoningInfo;
|
|
1325
|
+
}
|
|
1326
|
+
return output;
|
|
1327
|
+
}
|
|
953
1328
|
async function searchRawMemorySemantically(input) {
|
|
954
1329
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
955
1330
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
956
1331
|
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1332
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1333
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1334
|
+
);
|
|
957
1335
|
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
958
1336
|
return mergeAcrossSources({
|
|
959
1337
|
memorySubs: sub,
|
|
960
1338
|
insightHits: [],
|
|
961
1339
|
knowledgeHits: [],
|
|
962
1340
|
limit,
|
|
963
|
-
strategy:
|
|
1341
|
+
strategy: mergeStrategy
|
|
964
1342
|
});
|
|
965
1343
|
}
|
|
966
|
-
|
|
1344
|
+
async function search(input) {
|
|
1345
|
+
const query = input.query.trim();
|
|
1346
|
+
const wantsSynthesis = typeof input.synthesize === "boolean" ? input.synthesize : typeof input.synthesize === "object" && input.synthesize !== null;
|
|
1347
|
+
const synthesisSchema = typeof input.synthesize === "object" && input.synthesize !== null ? input.synthesize.responseSchema ?? input.responseSchema : input.responseSchema;
|
|
1348
|
+
const peerScope = await resolveSearchScopePeers({
|
|
1349
|
+
deps,
|
|
1350
|
+
userId: input.userId,
|
|
1351
|
+
peerFilter: input.peerFilter
|
|
1352
|
+
});
|
|
1353
|
+
if (!query) {
|
|
1354
|
+
const empty = {
|
|
1355
|
+
query,
|
|
1356
|
+
sources: input.sources ? [...input.sources] : [],
|
|
1357
|
+
results: [],
|
|
1358
|
+
evidence: [],
|
|
1359
|
+
count: 0,
|
|
1360
|
+
warnings: peerScope.warnings,
|
|
1361
|
+
answer: ""
|
|
1362
|
+
};
|
|
1363
|
+
return empty;
|
|
1364
|
+
}
|
|
1365
|
+
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
|
|
1366
|
+
const wantsSummaryTier = tiers.includes("summary");
|
|
1367
|
+
const wantsRawTier = tiers.includes("raw");
|
|
1368
|
+
const wantsInsightTier = tiers.includes("insight");
|
|
1369
|
+
const wantsKnowledgeTier = tiers.includes("knowledge");
|
|
1370
|
+
const warnings = [...peerScope.warnings];
|
|
1371
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1372
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1373
|
+
let unifiedHits = [];
|
|
1374
|
+
let sources = input.sources ? [...input.sources] : [];
|
|
1375
|
+
let unifiedReasoning;
|
|
1376
|
+
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
|
|
1377
|
+
const inferredSources = sources.length > 0 ? sources : [
|
|
1378
|
+
...wantsRawTier ? ["memory"] : [],
|
|
1379
|
+
...wantsInsightTier ? ["insights"] : [],
|
|
1380
|
+
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
1381
|
+
];
|
|
1382
|
+
const unified = await searchUnifiedMemory({
|
|
1383
|
+
...searchInputToUnified(input),
|
|
1384
|
+
sources: inferredSources.length > 0 ? inferredSources : void 0
|
|
1385
|
+
});
|
|
1386
|
+
unifiedHits = unified.results;
|
|
1387
|
+
sources = unified.sources;
|
|
1388
|
+
unifiedReasoning = unified.reasoning;
|
|
1389
|
+
warnings.push(...unified.warnings);
|
|
1390
|
+
}
|
|
1391
|
+
let summaryHits = [];
|
|
1392
|
+
if (wantsSummaryTier && typeof deps.searchSummaries === "function") {
|
|
1393
|
+
const summaryBucket = await gatherSummaries(
|
|
1394
|
+
deps,
|
|
1395
|
+
{ userId: input.userId, query: input.query, authToken: input.authToken },
|
|
1396
|
+
limit,
|
|
1397
|
+
threshold,
|
|
1398
|
+
logger,
|
|
1399
|
+
peerScope.peers
|
|
1400
|
+
);
|
|
1401
|
+
warnings.push(...summaryBucket.warnings);
|
|
1402
|
+
summaryHits = summaryBucket.hits;
|
|
1403
|
+
}
|
|
1404
|
+
const tierLists = [];
|
|
1405
|
+
if (unifiedHits.length > 0) {
|
|
1406
|
+
tierLists.push({ name: "memory", hits: unifiedHits });
|
|
1407
|
+
}
|
|
1408
|
+
if (summaryHits.length > 0) {
|
|
1409
|
+
tierLists.push({ name: "summary", hits: summaryHits });
|
|
1410
|
+
}
|
|
1411
|
+
let hits;
|
|
1412
|
+
if (tierLists.length === 0) {
|
|
1413
|
+
hits = [];
|
|
1414
|
+
} else if (tierLists.length === 1) {
|
|
1415
|
+
hits = tierLists[0].hits.slice(0, limit);
|
|
1416
|
+
} else {
|
|
1417
|
+
hits = mergeUnifiedMemorySearchResultsRrf(tierLists, limit);
|
|
1418
|
+
}
|
|
1419
|
+
const evidence = hits.map((hit) => ({
|
|
1420
|
+
id: hit.id,
|
|
1421
|
+
source: mapHitToEvidenceSource(hit),
|
|
1422
|
+
snippet: hit.content,
|
|
1423
|
+
score: hit.similarity,
|
|
1424
|
+
timestamp: getCandidateTimestamp(hit.metadata)
|
|
1425
|
+
}));
|
|
1426
|
+
const base = {
|
|
1427
|
+
query,
|
|
1428
|
+
sources,
|
|
1429
|
+
results: hits,
|
|
1430
|
+
evidence,
|
|
1431
|
+
count: hits.length,
|
|
1432
|
+
warnings
|
|
1433
|
+
};
|
|
1434
|
+
if (unifiedReasoning) {
|
|
1435
|
+
base.reasoning = unifiedReasoning;
|
|
1436
|
+
}
|
|
1437
|
+
if (!wantsSynthesis) {
|
|
1438
|
+
return base;
|
|
1439
|
+
}
|
|
1440
|
+
const { answer, warnings: synthWarnings } = await synthesizeAnswer({
|
|
1441
|
+
query,
|
|
1442
|
+
hits,
|
|
1443
|
+
responseSchema: synthesisSchema,
|
|
1444
|
+
deps,
|
|
1445
|
+
logger
|
|
1446
|
+
});
|
|
1447
|
+
base.answer = answer;
|
|
1448
|
+
base.warnings = [...base.warnings, ...synthWarnings];
|
|
1449
|
+
return base;
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
search,
|
|
1453
|
+
searchUnifiedMemory,
|
|
1454
|
+
searchRawMemorySemantically,
|
|
1455
|
+
consolidate: (input) => applyReflectedPlan(deps, {}, input)
|
|
1456
|
+
};
|
|
967
1457
|
}
|
|
968
1458
|
function mergeAcrossSources(input) {
|
|
969
1459
|
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
@@ -995,9 +1485,283 @@ function mergeAcrossSources(input) {
|
|
|
995
1485
|
return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
|
|
996
1486
|
}
|
|
997
1487
|
|
|
1488
|
+
// src/search/apply-reflect.ts
|
|
1489
|
+
var DEFAULT_TIERS2 = ["summary", "raw", "insight", "knowledge"];
|
|
1490
|
+
function reflectTierToEvidenceTier(source) {
|
|
1491
|
+
switch (source) {
|
|
1492
|
+
case "raw":
|
|
1493
|
+
return "short";
|
|
1494
|
+
case "summary":
|
|
1495
|
+
return "long";
|
|
1496
|
+
default:
|
|
1497
|
+
return "mid";
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
function toMemoryEvidenceRecord(input, fallbackUserId) {
|
|
1501
|
+
const tier = reflectTierToEvidenceTier(input.source);
|
|
1502
|
+
const timestamp = input.timestamp ?? Date.now();
|
|
1503
|
+
return {
|
|
1504
|
+
id: input.id,
|
|
1505
|
+
userId: fallbackUserId,
|
|
1506
|
+
timestamp,
|
|
1507
|
+
tier,
|
|
1508
|
+
text: input.snippet,
|
|
1509
|
+
metadata: {
|
|
1510
|
+
source: input.source,
|
|
1511
|
+
score: input.score,
|
|
1512
|
+
...input.timestamp !== void 0 ? { originalTimestamp: input.timestamp } : {}
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
async function applyReflectedPlan(deps, store, input, _logger) {
|
|
1517
|
+
const query = input.query.trim();
|
|
1518
|
+
const warnings = [];
|
|
1519
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1520
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1521
|
+
const tiers = input.tiers && input.tiers.length > 0 ? input.tiers : DEFAULT_TIERS2;
|
|
1522
|
+
const searchInput = {
|
|
1523
|
+
userId: input.userId,
|
|
1524
|
+
query,
|
|
1525
|
+
botIds: input.botIds,
|
|
1526
|
+
dateFrom: input.dateFrom,
|
|
1527
|
+
dateTo: input.dateTo,
|
|
1528
|
+
tiers,
|
|
1529
|
+
limit,
|
|
1530
|
+
threshold,
|
|
1531
|
+
authToken: input.authToken,
|
|
1532
|
+
peerFilter: input.peerFilter,
|
|
1533
|
+
synthesize: true
|
|
1534
|
+
};
|
|
1535
|
+
const search = createUnifiedSearch(deps);
|
|
1536
|
+
const result = await search.search(searchInput);
|
|
1537
|
+
warnings.push(...result.warnings);
|
|
1538
|
+
const evidence = result.evidence.map((item) => ({
|
|
1539
|
+
id: item.id,
|
|
1540
|
+
source: item.source ?? "raw",
|
|
1541
|
+
snippet: item.snippet,
|
|
1542
|
+
score: item.score,
|
|
1543
|
+
timestamp: item.timestamp
|
|
1544
|
+
}));
|
|
1545
|
+
const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
|
|
1546
|
+
const applyInput = {
|
|
1547
|
+
userId: input.userId,
|
|
1548
|
+
ownerScope: input.ownerScope,
|
|
1549
|
+
records,
|
|
1550
|
+
plan: input.plan,
|
|
1551
|
+
dryRun: input.dryRun,
|
|
1552
|
+
expectedVersion: input.expectedVersion,
|
|
1553
|
+
getClusterKey: input.getClusterKey ?? defaultClusterKey,
|
|
1554
|
+
llmReview: deps.reasoning?.complete ? {
|
|
1555
|
+
complete: deps.reasoning.complete,
|
|
1556
|
+
maxTokens: input.llmPlanReview?.maxTokens
|
|
1557
|
+
} : void 0,
|
|
1558
|
+
graphStore: input.graphStore ?? store.graphStore,
|
|
1559
|
+
storage: input.storage ?? store.storage
|
|
1560
|
+
};
|
|
1561
|
+
const applyOutput = await applyReflectedConsolidationPlan(applyInput);
|
|
1562
|
+
for (const warning of applyOutput.warnings) {
|
|
1563
|
+
warnings.push({
|
|
1564
|
+
source: "memory",
|
|
1565
|
+
code: warning.code,
|
|
1566
|
+
message: warning.message
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
const summaryAnswer = makePlanSummary(applyOutput.plan);
|
|
1570
|
+
return {
|
|
1571
|
+
answer: result.answer ? `${result.answer}
|
|
1572
|
+
|
|
1573
|
+
Plan summary:
|
|
1574
|
+
${summaryAnswer}` : summaryAnswer,
|
|
1575
|
+
plan: applyOutput.plan,
|
|
1576
|
+
persistenceResult: applyOutput.persistenceResult,
|
|
1577
|
+
deprecationCounts: applyOutput.deprecationCounts,
|
|
1578
|
+
warnings,
|
|
1579
|
+
applied: applyOutput.applied,
|
|
1580
|
+
evidence
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
function defaultClusterKey(record) {
|
|
1584
|
+
return record.text ?? record.id;
|
|
1585
|
+
}
|
|
1586
|
+
function makePlanSummary(plan) {
|
|
1587
|
+
const lines = [];
|
|
1588
|
+
const counts = {
|
|
1589
|
+
preserve: plan.actions.preserve.length,
|
|
1590
|
+
observe: plan.actions.observe.length,
|
|
1591
|
+
decay: plan.actions.decay.length,
|
|
1592
|
+
deprecate: plan.actions.deprecate.length
|
|
1593
|
+
};
|
|
1594
|
+
lines.push(
|
|
1595
|
+
`- preserve=${counts.preserve} observe=${counts.observe} decay=${counts.decay} deprecate=${counts.deprecate}`
|
|
1596
|
+
);
|
|
1597
|
+
for (const entry of plan.actions.deprecate.slice(0, 5)) {
|
|
1598
|
+
lines.push(
|
|
1599
|
+
` deprecate ${entry.recordIds.length} record(s) from cluster ${entry.clusterKey} -> ${entry.supersededBySummaryId ?? entry.winningClusterKey}`
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
return lines.join("\n");
|
|
1603
|
+
}
|
|
1604
|
+
var DEFAULT_MAX_FACTS = 1e3;
|
|
1605
|
+
var LOW_CONFIDENCE_THRESHOLD = 0.1;
|
|
1606
|
+
function toHRR(data) {
|
|
1607
|
+
const arr = data instanceof Float32Array ? data : new Float32Array(data);
|
|
1608
|
+
return { dim: arr.length, data: arr };
|
|
1609
|
+
}
|
|
1610
|
+
function generateFactId() {
|
|
1611
|
+
const stamp = Date.now().toString(36);
|
|
1612
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
1613
|
+
return `vsa-${stamp}-${rand}`;
|
|
1614
|
+
}
|
|
1615
|
+
function createVsaRecall(storage) {
|
|
1616
|
+
return {
|
|
1617
|
+
async storeFact(input) {
|
|
1618
|
+
const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
|
|
1619
|
+
const fillerVector = vsaNormalizeVector(input.fillerVector, "fillerVector");
|
|
1620
|
+
vsaAssertSameDim(roleVector, fillerVector, "storeFact");
|
|
1621
|
+
const dim = input.dim ?? roleVector.length;
|
|
1622
|
+
if (roleVector.length !== dim) {
|
|
1623
|
+
throw new Error(`storeFact: roleVector.length (${roleVector.length}) must equal dim (${dim})`);
|
|
1624
|
+
}
|
|
1625
|
+
const now = Date.now();
|
|
1626
|
+
const fact = {
|
|
1627
|
+
factId: input.factId ?? generateFactId(),
|
|
1628
|
+
userId: input.userId,
|
|
1629
|
+
roleLabel: input.roleLabel,
|
|
1630
|
+
fillerLabel: input.fillerLabel,
|
|
1631
|
+
roleVector,
|
|
1632
|
+
fillerVector,
|
|
1633
|
+
dim,
|
|
1634
|
+
scopeTag: input.scopeTag ?? "default",
|
|
1635
|
+
botId: input.botId,
|
|
1636
|
+
createdAt: now
|
|
1637
|
+
};
|
|
1638
|
+
await storage.storeFact(fact);
|
|
1639
|
+
return { factId: fact.factId, createdAt: now };
|
|
1640
|
+
},
|
|
1641
|
+
async recall(input) {
|
|
1642
|
+
const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
|
|
1643
|
+
const scopeTag = input.scopeTag ?? "default";
|
|
1644
|
+
const maxFacts = input.maxFacts ?? DEFAULT_MAX_FACTS;
|
|
1645
|
+
if (input.vocabulary.length === 0) {
|
|
1646
|
+
throw new Error("vsaRecall: vocabulary must contain at least one entry");
|
|
1647
|
+
}
|
|
1648
|
+
const facts = await storage.queryFacts({
|
|
1649
|
+
userId: input.userId,
|
|
1650
|
+
scopeTag,
|
|
1651
|
+
botId: input.botId,
|
|
1652
|
+
includeDeprecated: false,
|
|
1653
|
+
limit: maxFacts
|
|
1654
|
+
});
|
|
1655
|
+
const warnings = [];
|
|
1656
|
+
if (facts.length === 0) {
|
|
1657
|
+
warnings.push({
|
|
1658
|
+
code: "vsa_no_facts",
|
|
1659
|
+
message: `No VSA facts for userId=${input.userId} scopeTag=${scopeTag}`
|
|
1660
|
+
});
|
|
1661
|
+
return {
|
|
1662
|
+
fillerLabel: "",
|
|
1663
|
+
score: 0,
|
|
1664
|
+
allScores: [],
|
|
1665
|
+
factCount: 0,
|
|
1666
|
+
warnings
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
const refDim = facts[0].dim;
|
|
1670
|
+
const dimMatchedFacts = facts.filter((fact) => {
|
|
1671
|
+
if (fact.dim === refDim) return true;
|
|
1672
|
+
warnings.push({
|
|
1673
|
+
code: "vsa_dim_mismatch",
|
|
1674
|
+
message: `Fact ${fact.factId} has dim=${fact.dim}, expected ${refDim}; skipping`
|
|
1675
|
+
});
|
|
1676
|
+
return false;
|
|
1677
|
+
});
|
|
1678
|
+
if (roleVector.length !== refDim) {
|
|
1679
|
+
throw new Error(
|
|
1680
|
+
`vsaRecall: roleVector dim (${roleVector.length}) must match stored fact dim (${refDim})`
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
if (dimMatchedFacts.length === 0) {
|
|
1684
|
+
warnings.push({
|
|
1685
|
+
code: "vsa_no_facts",
|
|
1686
|
+
message: `All stored facts for userId=${input.userId} scopeTag=${scopeTag} had a non-matching dim`
|
|
1687
|
+
});
|
|
1688
|
+
return {
|
|
1689
|
+
fillerLabel: "",
|
|
1690
|
+
score: 0,
|
|
1691
|
+
allScores: [],
|
|
1692
|
+
factCount: 0,
|
|
1693
|
+
warnings
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
const memoryVec = superpose(
|
|
1697
|
+
dimMatchedFacts.map((fact) => bind(toHRR(fact.roleVector), toHRR(fact.fillerVector)))
|
|
1698
|
+
);
|
|
1699
|
+
const candidate = unbind(memoryVec, toHRR(roleVector));
|
|
1700
|
+
const vocabVecs = input.vocabulary.map(
|
|
1701
|
+
(entry) => toHRR(vsaNormalizeVector(entry.vector, `vocabulary[${entry.label}].vector`))
|
|
1702
|
+
);
|
|
1703
|
+
const bestVec = cleanup(candidate, vocabVecs);
|
|
1704
|
+
const bestIdx = vocabVecs.indexOf(bestVec);
|
|
1705
|
+
const allScores = input.vocabulary.map((entry, idx) => ({
|
|
1706
|
+
label: entry.label,
|
|
1707
|
+
score: cosineSimilarity(candidate, vocabVecs[idx])
|
|
1708
|
+
}));
|
|
1709
|
+
allScores.sort((a, b) => b.score - a.score);
|
|
1710
|
+
const fillerLabel = bestIdx >= 0 ? input.vocabulary[bestIdx].label : "";
|
|
1711
|
+
const score = allScores[0]?.score ?? 0;
|
|
1712
|
+
if (score < LOW_CONFIDENCE_THRESHOLD) {
|
|
1713
|
+
warnings.push({
|
|
1714
|
+
code: "vsa_low_confidence",
|
|
1715
|
+
message: `Best match "${fillerLabel}" score ${score.toFixed(3)} below ${LOW_CONFIDENCE_THRESHOLD}`
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
if (facts.length >= maxFacts) {
|
|
1719
|
+
warnings.push({
|
|
1720
|
+
code: "vsa_fact_limit_reached",
|
|
1721
|
+
message: `Loaded ${facts.length} facts (cap=${maxFacts}); recall may degrade with more`
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
return {
|
|
1725
|
+
fillerLabel,
|
|
1726
|
+
score,
|
|
1727
|
+
allScores,
|
|
1728
|
+
factCount: facts.length,
|
|
1729
|
+
warnings
|
|
1730
|
+
};
|
|
1731
|
+
},
|
|
1732
|
+
async listFacts(input) {
|
|
1733
|
+
const facts = await storage.queryFacts({
|
|
1734
|
+
userId: input.userId,
|
|
1735
|
+
scopeTag: input.scopeTag,
|
|
1736
|
+
botId: input.botId,
|
|
1737
|
+
includeDeprecated: input.includeDeprecated
|
|
1738
|
+
});
|
|
1739
|
+
return facts.map((fact) => ({
|
|
1740
|
+
factId: fact.factId,
|
|
1741
|
+
userId: fact.userId,
|
|
1742
|
+
roleLabel: fact.roleLabel,
|
|
1743
|
+
fillerLabel: fact.fillerLabel,
|
|
1744
|
+
scopeTag: fact.scopeTag,
|
|
1745
|
+
botId: fact.botId,
|
|
1746
|
+
createdAt: fact.createdAt,
|
|
1747
|
+
deprecatedAt: fact.deprecatedAt
|
|
1748
|
+
}));
|
|
1749
|
+
},
|
|
1750
|
+
async forget(input) {
|
|
1751
|
+
const result = await storage.deprecateFacts({
|
|
1752
|
+
userId: input.userId,
|
|
1753
|
+
factIds: input.factIds,
|
|
1754
|
+
reason: input.reason
|
|
1755
|
+
});
|
|
1756
|
+
return { deprecatedCount: result.deprecatedCount };
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
|
|
998
1761
|
// src/http.ts
|
|
999
1762
|
init_chroma_memory_index();
|
|
1000
1763
|
init_raw_message_store();
|
|
1764
|
+
init_sqlite_raw_message_store();
|
|
1001
1765
|
async function embedMissingMessages(messages, deps) {
|
|
1002
1766
|
if (typeof deps.embedQuery !== "function") {
|
|
1003
1767
|
return messages;
|
|
@@ -1049,6 +1813,9 @@ async function startHttpServer(options = {}) {
|
|
|
1049
1813
|
return [];
|
|
1050
1814
|
}
|
|
1051
1815
|
});
|
|
1816
|
+
const vsaDbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
|
|
1817
|
+
const vsaStorage = await getSQLiteVsaStore({ dbPath: vsaDbPath });
|
|
1818
|
+
const vsa = createVsaRecall(vsaStorage);
|
|
1052
1819
|
const app = new Hono();
|
|
1053
1820
|
app.get("/health", (c) => c.json({ ok: true, store: "memory", ts: Date.now() }));
|
|
1054
1821
|
app.post("/v1/search", async (c) => {
|
|
@@ -1058,17 +1825,62 @@ async function startHttpServer(options = {}) {
|
|
|
1058
1825
|
if (!userId || !query) {
|
|
1059
1826
|
return c.json({ error: "userId and query are required" }, 400);
|
|
1060
1827
|
}
|
|
1061
|
-
const
|
|
1828
|
+
const wantsSynthesis = typeof body.synthesize === "boolean" ? body.synthesize : typeof body.synthesize === "object" && body.synthesize !== null;
|
|
1829
|
+
const responseSchema = typeof body.synthesize === "object" && body.synthesize !== null ? body.synthesize.responseSchema ?? body.responseSchema : body.responseSchema;
|
|
1830
|
+
const input = {
|
|
1062
1831
|
userId,
|
|
1063
1832
|
query,
|
|
1064
|
-
sources: body.sources,
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1833
|
+
sources: Array.isArray(body.sources) ? body.sources : void 0,
|
|
1834
|
+
tiers: Array.isArray(body.tiers) ? body.tiers : void 0,
|
|
1835
|
+
limit: typeof body.limit === "number" ? body.limit : void 0,
|
|
1836
|
+
threshold: typeof body.threshold === "number" ? body.threshold : void 0,
|
|
1837
|
+
botIds: Array.isArray(body.botIds) ? body.botIds : void 0,
|
|
1838
|
+
documentIds: Array.isArray(body.documentIds) ? body.documentIds : void 0,
|
|
1839
|
+
includeArchivedInsights: body.includeArchivedInsights === true,
|
|
1840
|
+
authToken: typeof body.authToken === "string" ? body.authToken : void 0,
|
|
1841
|
+
dateFrom: typeof body.dateFrom === "string" ? body.dateFrom : void 0,
|
|
1842
|
+
dateTo: typeof body.dateTo === "string" ? body.dateTo : void 0,
|
|
1843
|
+
...wantsSynthesis ? {
|
|
1844
|
+
synthesize: {
|
|
1845
|
+
...responseSchema ? { responseSchema } : {}
|
|
1846
|
+
}
|
|
1847
|
+
} : {}
|
|
1848
|
+
};
|
|
1849
|
+
const result = await search.search(input);
|
|
1850
|
+
return c.json(result);
|
|
1851
|
+
});
|
|
1852
|
+
app.post("/v1/consolidate:apply", async (c) => {
|
|
1853
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1854
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
1855
|
+
const query = typeof body.query === "string" ? body.query : "";
|
|
1856
|
+
const ownerScope = body.ownerScope ?? {};
|
|
1857
|
+
if (!userId || !query) {
|
|
1858
|
+
return c.json({ error: "userId and query are required" }, 400);
|
|
1859
|
+
}
|
|
1860
|
+
if (typeof ownerScope.userId !== "string") {
|
|
1861
|
+
return c.json({ error: "ownerScope.userId is required" }, 400);
|
|
1862
|
+
}
|
|
1863
|
+
const input = {
|
|
1864
|
+
userId,
|
|
1865
|
+
query,
|
|
1866
|
+
ownerScope: { userId: ownerScope.userId },
|
|
1867
|
+
botIds: Array.isArray(body.botIds) ? body.botIds : void 0,
|
|
1868
|
+
dateFrom: typeof body.dateFrom === "string" ? body.dateFrom : void 0,
|
|
1869
|
+
dateTo: typeof body.dateTo === "string" ? body.dateTo : void 0,
|
|
1870
|
+
tiers: Array.isArray(body.tiers) ? body.tiers : void 0,
|
|
1871
|
+
limit: typeof body.limit === "number" ? body.limit : void 0,
|
|
1872
|
+
threshold: typeof body.threshold === "number" ? body.threshold : void 0,
|
|
1873
|
+
dryRun: body.dryRun === true,
|
|
1874
|
+
expectedVersion: typeof body.expectedVersion === "string" ? body.expectedVersion : void 0,
|
|
1875
|
+
authToken: typeof body.authToken === "string" ? body.authToken : void 0,
|
|
1876
|
+
llmPlanReview: body.llmPlanReview && typeof body.llmPlanReview === "object" ? {
|
|
1877
|
+
maxTokens: typeof body.llmPlanReview.maxTokens === "number" ? body.llmPlanReview.maxTokens : void 0
|
|
1878
|
+
} : void 0
|
|
1879
|
+
};
|
|
1880
|
+
const result = await applyReflectedPlan(
|
|
1881
|
+
{ ...options.unified ?? {} },
|
|
1882
|
+
{ graphStore: options.graphStore, storage: options.storage },
|
|
1883
|
+
input);
|
|
1072
1884
|
return c.json(result);
|
|
1073
1885
|
});
|
|
1074
1886
|
app.post("/v1/raw-messages", async (c) => {
|
|
@@ -1134,12 +1946,99 @@ async function startHttpServer(options = {}) {
|
|
|
1134
1946
|
if (!row) return c.json({ error: "not found" }, 404);
|
|
1135
1947
|
return c.json({ message: row });
|
|
1136
1948
|
});
|
|
1949
|
+
app.post("/v1/vsa/store", async (c) => {
|
|
1950
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1951
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
1952
|
+
const roleLabel = typeof body.roleLabel === "string" ? body.roleLabel : null;
|
|
1953
|
+
const fillerLabel = typeof body.fillerLabel === "string" ? body.fillerLabel : null;
|
|
1954
|
+
if (!userId || !roleLabel || !fillerLabel) {
|
|
1955
|
+
return c.json({ error: "userId, roleLabel, and fillerLabel are required" }, 400);
|
|
1956
|
+
}
|
|
1957
|
+
if (!Array.isArray(body.roleVector) || !Array.isArray(body.fillerVector)) {
|
|
1958
|
+
return c.json({ error: "roleVector[] and fillerVector[] are required" }, 400);
|
|
1959
|
+
}
|
|
1960
|
+
try {
|
|
1961
|
+
const result = await vsa.storeFact({
|
|
1962
|
+
userId,
|
|
1963
|
+
roleLabel,
|
|
1964
|
+
fillerLabel,
|
|
1965
|
+
roleVector: body.roleVector,
|
|
1966
|
+
fillerVector: body.fillerVector,
|
|
1967
|
+
scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
|
|
1968
|
+
botId: typeof body.botId === "string" ? body.botId : void 0,
|
|
1969
|
+
factId: typeof body.factId === "string" ? body.factId : void 0
|
|
1970
|
+
});
|
|
1971
|
+
return c.json(result);
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
return c.json({ error: error.message ?? "vsa.store failed" }, 400);
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
app.post("/v1/vsa/recall", async (c) => {
|
|
1977
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1978
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
1979
|
+
const roleLabel = typeof body.roleLabel === "string" ? body.roleLabel : null;
|
|
1980
|
+
const roleVector = Array.isArray(body.roleVector) ? body.roleVector : null;
|
|
1981
|
+
const vocabulary = Array.isArray(body.vocabulary) ? body.vocabulary : null;
|
|
1982
|
+
if (!userId || !roleLabel || !roleVector || !vocabulary) {
|
|
1983
|
+
return c.json({ error: "userId, roleLabel, roleVector[], and vocabulary[] are required" }, 400);
|
|
1984
|
+
}
|
|
1985
|
+
try {
|
|
1986
|
+
const result = await vsa.recall({
|
|
1987
|
+
userId,
|
|
1988
|
+
roleLabel,
|
|
1989
|
+
roleVector,
|
|
1990
|
+
vocabulary,
|
|
1991
|
+
scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
|
|
1992
|
+
botId: typeof body.botId === "string" ? body.botId : void 0,
|
|
1993
|
+
maxFacts: typeof body.maxFacts === "number" ? body.maxFacts : void 0
|
|
1994
|
+
});
|
|
1995
|
+
return c.json(result);
|
|
1996
|
+
} catch (error) {
|
|
1997
|
+
return c.json({ error: error.message ?? "vsa.recall failed" }, 400);
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
app.post("/v1/vsa/list", async (c) => {
|
|
2001
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2002
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
2003
|
+
if (!userId) return c.json({ error: "userId is required" }, 400);
|
|
2004
|
+
try {
|
|
2005
|
+
const facts = await vsa.listFacts({
|
|
2006
|
+
userId,
|
|
2007
|
+
scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
|
|
2008
|
+
botId: typeof body.botId === "string" ? body.botId : void 0,
|
|
2009
|
+
includeDeprecated: body.includeDeprecated === true
|
|
2010
|
+
});
|
|
2011
|
+
return c.json({ facts });
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
return c.json({ error: error.message ?? "vsa.list failed" }, 400);
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
app.post("/v1/vsa/forget", async (c) => {
|
|
2017
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2018
|
+
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
2019
|
+
const factIds = Array.isArray(body.factIds) ? body.factIds : null;
|
|
2020
|
+
if (!userId || !factIds) {
|
|
2021
|
+
return c.json({ error: "userId and factIds[] are required" }, 400);
|
|
2022
|
+
}
|
|
2023
|
+
try {
|
|
2024
|
+
const result = await vsa.forget({
|
|
2025
|
+
userId,
|
|
2026
|
+
factIds,
|
|
2027
|
+
reason: typeof body.reason === "string" ? body.reason : void 0
|
|
2028
|
+
});
|
|
2029
|
+
return c.json(result);
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
return c.json({ error: error.message ?? "vsa.forget failed" }, 400);
|
|
2032
|
+
}
|
|
2033
|
+
});
|
|
2034
|
+
registerOkfRoutes(app, rawStore);
|
|
1137
2035
|
const server = serve({ fetch: app.fetch, port, hostname: host });
|
|
1138
2036
|
return {
|
|
1139
2037
|
url: `http://${host}:${port}`,
|
|
1140
2038
|
port,
|
|
1141
2039
|
async stop() {
|
|
1142
2040
|
await rawStore.close();
|
|
2041
|
+
await closeSQLiteVsaStore();
|
|
1143
2042
|
server.close();
|
|
1144
2043
|
}
|
|
1145
2044
|
};
|