@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/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
|
|
1
2
|
import '@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 } from '@melandlabs/sqlite';
|
|
8
|
+
export { closeSQLiteVsaStore, getSQLiteVsaStore } from '@melandlabs/sqlite';
|
|
9
|
+
import { applyReflectedConsolidationPlan } from '@melandlabs/memory-consolidation';
|
|
10
|
+
import { superpose, bind, unbind, cleanup, cosineSimilarity } from '@melandlabs/vsa';
|
|
11
|
+
import { createRawMessageMemoryGraphStore } from '@melandlabs/indexeddb';
|
|
7
12
|
|
|
8
13
|
var __defProp = Object.defineProperty;
|
|
9
14
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -14,8 +19,6 @@ var __export = (target, all) => {
|
|
|
14
19
|
for (var name in all)
|
|
15
20
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
21
|
};
|
|
17
|
-
|
|
18
|
-
// src/contracts.ts
|
|
19
22
|
var init_contracts = __esm({
|
|
20
23
|
"src/contracts.ts"() {
|
|
21
24
|
}
|
|
@@ -376,6 +379,416 @@ function isRawMemorySemanticResult(result) {
|
|
|
376
379
|
const item = result;
|
|
377
380
|
return typeof item.id === "string" && typeof item.content === "string" && typeof item.similarity === "number" && Boolean(item.metadata) && typeof item.metadata === "object";
|
|
378
381
|
}
|
|
382
|
+
async function resolveScopePeer(input) {
|
|
383
|
+
const peers = input.peerFilter ?? [];
|
|
384
|
+
if (peers.length === 0) {
|
|
385
|
+
return { peers: [], warnings: [] };
|
|
386
|
+
}
|
|
387
|
+
if (typeof input.scopeCheck !== "function") {
|
|
388
|
+
return { peers, warnings: [] };
|
|
389
|
+
}
|
|
390
|
+
const retained = [];
|
|
391
|
+
const dropped = [];
|
|
392
|
+
for (const peer of peers) {
|
|
393
|
+
const ok = await input.scopeCheck({ userId: input.userId, peers: [peer] });
|
|
394
|
+
if (ok) {
|
|
395
|
+
retained.push(peer);
|
|
396
|
+
} else {
|
|
397
|
+
dropped.push(peer);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (dropped.length === 0) {
|
|
401
|
+
return { peers, warnings: [] };
|
|
402
|
+
}
|
|
403
|
+
if (retained.length === 0) {
|
|
404
|
+
return {
|
|
405
|
+
peers: [],
|
|
406
|
+
warnings: [
|
|
407
|
+
{
|
|
408
|
+
source: "memory",
|
|
409
|
+
code: "peer_filter_outside_user_scope",
|
|
410
|
+
message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored; falling back to userId scope`
|
|
411
|
+
}
|
|
412
|
+
]
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
peers: retained,
|
|
417
|
+
warnings: [
|
|
418
|
+
{
|
|
419
|
+
source: "memory",
|
|
420
|
+
code: "peer_filter_outside_user_scope",
|
|
421
|
+
message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored because they fall outside userId ${input.userId}`
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/search/apply-reflect.ts
|
|
428
|
+
var DEFAULT_TIERS = ["summary", "raw", "insight", "knowledge"];
|
|
429
|
+
function reflectTierToEvidenceTier(source) {
|
|
430
|
+
switch (source) {
|
|
431
|
+
case "raw":
|
|
432
|
+
return "short";
|
|
433
|
+
case "summary":
|
|
434
|
+
return "long";
|
|
435
|
+
default:
|
|
436
|
+
return "mid";
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function toMemoryEvidenceRecord(input, fallbackUserId) {
|
|
440
|
+
const tier = reflectTierToEvidenceTier(input.source);
|
|
441
|
+
const timestamp = input.timestamp ?? Date.now();
|
|
442
|
+
return {
|
|
443
|
+
id: input.id,
|
|
444
|
+
userId: fallbackUserId,
|
|
445
|
+
timestamp,
|
|
446
|
+
tier,
|
|
447
|
+
text: input.snippet,
|
|
448
|
+
metadata: {
|
|
449
|
+
source: input.source,
|
|
450
|
+
score: input.score,
|
|
451
|
+
...input.timestamp !== void 0 ? { originalTimestamp: input.timestamp } : {}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
async function applyReflectedPlan(deps, store, input, _logger) {
|
|
456
|
+
const query = input.query.trim();
|
|
457
|
+
const warnings = [];
|
|
458
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
459
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
460
|
+
const tiers = input.tiers && input.tiers.length > 0 ? input.tiers : DEFAULT_TIERS;
|
|
461
|
+
const searchInput = {
|
|
462
|
+
userId: input.userId,
|
|
463
|
+
query,
|
|
464
|
+
botIds: input.botIds,
|
|
465
|
+
dateFrom: input.dateFrom,
|
|
466
|
+
dateTo: input.dateTo,
|
|
467
|
+
tiers,
|
|
468
|
+
limit,
|
|
469
|
+
threshold,
|
|
470
|
+
authToken: input.authToken,
|
|
471
|
+
peerFilter: input.peerFilter,
|
|
472
|
+
synthesize: true
|
|
473
|
+
};
|
|
474
|
+
const search = createUnifiedSearch(deps);
|
|
475
|
+
const result = await search.search(searchInput);
|
|
476
|
+
warnings.push(...result.warnings);
|
|
477
|
+
const evidence = result.evidence.map((item) => ({
|
|
478
|
+
id: item.id,
|
|
479
|
+
source: item.source ?? "raw",
|
|
480
|
+
snippet: item.snippet,
|
|
481
|
+
score: item.score,
|
|
482
|
+
timestamp: item.timestamp
|
|
483
|
+
}));
|
|
484
|
+
const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
|
|
485
|
+
const applyInput = {
|
|
486
|
+
userId: input.userId,
|
|
487
|
+
ownerScope: input.ownerScope,
|
|
488
|
+
records,
|
|
489
|
+
plan: input.plan,
|
|
490
|
+
dryRun: input.dryRun,
|
|
491
|
+
expectedVersion: input.expectedVersion,
|
|
492
|
+
getClusterKey: input.getClusterKey ?? defaultClusterKey,
|
|
493
|
+
llmReview: deps.reasoning?.complete ? {
|
|
494
|
+
complete: deps.reasoning.complete,
|
|
495
|
+
maxTokens: input.llmPlanReview?.maxTokens
|
|
496
|
+
} : void 0,
|
|
497
|
+
graphStore: input.graphStore ?? store.graphStore,
|
|
498
|
+
storage: input.storage ?? store.storage
|
|
499
|
+
};
|
|
500
|
+
const applyOutput = await applyReflectedConsolidationPlan(applyInput);
|
|
501
|
+
for (const warning of applyOutput.warnings) {
|
|
502
|
+
warnings.push({
|
|
503
|
+
source: "memory",
|
|
504
|
+
code: warning.code,
|
|
505
|
+
message: warning.message
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
const summaryAnswer = makePlanSummary(applyOutput.plan);
|
|
509
|
+
return {
|
|
510
|
+
answer: result.answer ? `${result.answer}
|
|
511
|
+
|
|
512
|
+
Plan summary:
|
|
513
|
+
${summaryAnswer}` : summaryAnswer,
|
|
514
|
+
plan: applyOutput.plan,
|
|
515
|
+
persistenceResult: applyOutput.persistenceResult,
|
|
516
|
+
deprecationCounts: applyOutput.deprecationCounts,
|
|
517
|
+
warnings,
|
|
518
|
+
applied: applyOutput.applied,
|
|
519
|
+
evidence
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function defaultClusterKey(record) {
|
|
523
|
+
return record.text ?? record.id;
|
|
524
|
+
}
|
|
525
|
+
function makePlanSummary(plan) {
|
|
526
|
+
const lines = [];
|
|
527
|
+
const counts = {
|
|
528
|
+
preserve: plan.actions.preserve.length,
|
|
529
|
+
observe: plan.actions.observe.length,
|
|
530
|
+
decay: plan.actions.decay.length,
|
|
531
|
+
deprecate: plan.actions.deprecate.length
|
|
532
|
+
};
|
|
533
|
+
lines.push(
|
|
534
|
+
`- preserve=${counts.preserve} observe=${counts.observe} decay=${counts.decay} deprecate=${counts.deprecate}`
|
|
535
|
+
);
|
|
536
|
+
for (const entry of plan.actions.deprecate.slice(0, 5)) {
|
|
537
|
+
lines.push(
|
|
538
|
+
` deprecate ${entry.recordIds.length} record(s) from cluster ${entry.clusterKey} -> ${entry.supersededBySummaryId ?? entry.winningClusterKey}`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return lines.join("\n");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/search/reranker.ts
|
|
545
|
+
async function applyReranker(reranker, query, results) {
|
|
546
|
+
if (!reranker || results.length === 0) {
|
|
547
|
+
return results;
|
|
548
|
+
}
|
|
549
|
+
const scores = await reranker.rerank({
|
|
550
|
+
query,
|
|
551
|
+
candidates: results.map((result) => ({
|
|
552
|
+
id: result.id,
|
|
553
|
+
content: result.content,
|
|
554
|
+
metadata: result.metadata
|
|
555
|
+
}))
|
|
556
|
+
});
|
|
557
|
+
if (scores.length === 0) {
|
|
558
|
+
return results;
|
|
559
|
+
}
|
|
560
|
+
const byId = new Map(results.map((result) => [result.id, result]));
|
|
561
|
+
const reordered = [];
|
|
562
|
+
const seen = /* @__PURE__ */ new Set();
|
|
563
|
+
for (const { id, score } of scores) {
|
|
564
|
+
const hit = byId.get(id);
|
|
565
|
+
if (!hit || seen.has(id)) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
seen.add(id);
|
|
569
|
+
reordered.push({ ...hit, metadata: { ...hit.metadata, rerankerScore: score } });
|
|
570
|
+
}
|
|
571
|
+
for (const hit of results) {
|
|
572
|
+
if (!seen.has(hit.id)) {
|
|
573
|
+
reordered.push(hit);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return reordered;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/search/gather-evidence.ts
|
|
580
|
+
var DEFAULT_TIERS2 = ["summary", "raw", "insight", "knowledge"];
|
|
581
|
+
var MAX_EVIDENCE_CHARS = 600;
|
|
582
|
+
function toSummaryResult(row) {
|
|
583
|
+
const start = typeof row.startTimestamp === "number" ? row.startTimestamp : void 0;
|
|
584
|
+
const end = typeof row.endTimestamp === "number" ? row.endTimestamp : void 0;
|
|
585
|
+
return {
|
|
586
|
+
type: "memory",
|
|
587
|
+
id: row.summaryId,
|
|
588
|
+
content: row.summaryText,
|
|
589
|
+
similarity: 1,
|
|
590
|
+
metadata: {
|
|
591
|
+
tier: "summary",
|
|
592
|
+
summaryTier: row.summaryTier,
|
|
593
|
+
keywords: row.keywords,
|
|
594
|
+
startTimestamp: start,
|
|
595
|
+
endTimestamp: end
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers) {
|
|
600
|
+
if (typeof deps.searchSummaries !== "function") {
|
|
601
|
+
return { hits: [], warnings: [] };
|
|
602
|
+
}
|
|
603
|
+
const keywords = input.query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
604
|
+
try {
|
|
605
|
+
const rows = await deps.searchSummaries({
|
|
606
|
+
userId: input.userId,
|
|
607
|
+
query: input.query,
|
|
608
|
+
keywords,
|
|
609
|
+
limit,
|
|
610
|
+
threshold,
|
|
611
|
+
authToken: input.authToken,
|
|
612
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
613
|
+
});
|
|
614
|
+
const hits = rows.map(toSummaryResult);
|
|
615
|
+
return { hits, warnings: [] };
|
|
616
|
+
} catch (error) {
|
|
617
|
+
logger.warn?.("[memory-store] gather-evidence: summary search failed:", error);
|
|
618
|
+
return {
|
|
619
|
+
hits: [],
|
|
620
|
+
warnings: [
|
|
621
|
+
{
|
|
622
|
+
source: "memory",
|
|
623
|
+
code: "reflect_summaries_failed",
|
|
624
|
+
message: error.message ?? "reflect_summaries_failed"
|
|
625
|
+
}
|
|
626
|
+
]
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function truncate(input, max = MAX_EVIDENCE_CHARS) {
|
|
631
|
+
if (input.length <= max) {
|
|
632
|
+
return input;
|
|
633
|
+
}
|
|
634
|
+
return `${input.slice(0, max - 1)}\u2026`;
|
|
635
|
+
}
|
|
636
|
+
function buildSynthesisPrompt(input) {
|
|
637
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
638
|
+
for (const item of input.evidence) {
|
|
639
|
+
const bucket = grouped.get(item.source) ?? [];
|
|
640
|
+
bucket.push(item);
|
|
641
|
+
grouped.set(item.source, bucket);
|
|
642
|
+
}
|
|
643
|
+
const sections = [];
|
|
644
|
+
for (const tier of DEFAULT_TIERS2) {
|
|
645
|
+
const items = grouped.get(tier) ?? [];
|
|
646
|
+
if (items.length === 0) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
const lines = items.map(
|
|
650
|
+
(item, index) => ` [${index + 1}] (${item.id}, score=${item.score.toFixed(4)}) ${item.snippet}`
|
|
651
|
+
);
|
|
652
|
+
sections.push(`## ${tier}
|
|
653
|
+
${lines.join("\n")}`);
|
|
654
|
+
}
|
|
655
|
+
const schemaDirective = input.responseSchema ? `
|
|
656
|
+
Respond with a JSON object that conforms to the following schema:
|
|
657
|
+
${JSON.stringify(input.responseSchema, null, 2)}
|
|
658
|
+
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.";
|
|
659
|
+
if (sections.length === 0) {
|
|
660
|
+
return `You are a reflection assistant.
|
|
661
|
+
|
|
662
|
+
Question: ${input.query}
|
|
663
|
+
|
|
664
|
+
No evidence was found across any tier. State that explicitly.${schemaDirective}`;
|
|
665
|
+
}
|
|
666
|
+
return `You are a reflection assistant. Synthesise the user's question using only the evidence below. Do not invent facts.
|
|
667
|
+
|
|
668
|
+
Question: ${input.query}
|
|
669
|
+
|
|
670
|
+
Evidence (grouped by tier):
|
|
671
|
+
${sections.join("\n\n")}${schemaDirective}`;
|
|
672
|
+
}
|
|
673
|
+
function extractJsonPayload(text) {
|
|
674
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
675
|
+
if (fenced?.[1]) {
|
|
676
|
+
return fenced[1];
|
|
677
|
+
}
|
|
678
|
+
const start = text.indexOf("{");
|
|
679
|
+
if (start === -1) {
|
|
680
|
+
return void 0;
|
|
681
|
+
}
|
|
682
|
+
let depth = 0;
|
|
683
|
+
let inString = false;
|
|
684
|
+
let escaped = false;
|
|
685
|
+
for (let i = start; i < text.length; i += 1) {
|
|
686
|
+
const ch = text[i];
|
|
687
|
+
if (escaped) {
|
|
688
|
+
escaped = false;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (inString) {
|
|
692
|
+
if (ch === "\\") {
|
|
693
|
+
escaped = true;
|
|
694
|
+
} else if (ch === '"') {
|
|
695
|
+
inString = false;
|
|
696
|
+
}
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (ch === '"') {
|
|
700
|
+
inString = true;
|
|
701
|
+
} else if (ch === "{") {
|
|
702
|
+
depth += 1;
|
|
703
|
+
} else if (ch === "}") {
|
|
704
|
+
depth -= 1;
|
|
705
|
+
if (depth === 0) {
|
|
706
|
+
return text.slice(start, i + 1);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return void 0;
|
|
711
|
+
}
|
|
712
|
+
function coerceAnswer(text, schema) {
|
|
713
|
+
if (!schema) {
|
|
714
|
+
return text;
|
|
715
|
+
}
|
|
716
|
+
const payload = extractJsonPayload(text);
|
|
717
|
+
if (!payload) {
|
|
718
|
+
return text;
|
|
719
|
+
}
|
|
720
|
+
try {
|
|
721
|
+
const parsed = JSON.parse(payload);
|
|
722
|
+
if (parsed && typeof parsed === "object") {
|
|
723
|
+
const answer = parsed.answer;
|
|
724
|
+
if (typeof answer === "string") {
|
|
725
|
+
return answer;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
} catch {
|
|
729
|
+
}
|
|
730
|
+
return text;
|
|
731
|
+
}
|
|
732
|
+
async function synthesizeAnswer(input) {
|
|
733
|
+
const { query, hits, responseSchema, deps, logger } = input;
|
|
734
|
+
const evidence = hits.map((hit) => ({
|
|
735
|
+
id: hit.id,
|
|
736
|
+
source: mapHitToTier(hit),
|
|
737
|
+
snippet: truncate(hit.content),
|
|
738
|
+
score: hit.similarity
|
|
739
|
+
}));
|
|
740
|
+
const warnings = [];
|
|
741
|
+
const complete = deps.reasoning?.complete;
|
|
742
|
+
if (!complete) {
|
|
743
|
+
warnings.push({
|
|
744
|
+
source: "memory",
|
|
745
|
+
code: "reflect_llm_not_configured",
|
|
746
|
+
message: "No `reasoning.complete` callback is configured; search() returned evidence without an LLM synthesis."
|
|
747
|
+
});
|
|
748
|
+
return { answer: "", warnings };
|
|
749
|
+
}
|
|
750
|
+
const prompt = buildSynthesisPrompt({ query, evidence, responseSchema });
|
|
751
|
+
try {
|
|
752
|
+
const raw = await complete(prompt);
|
|
753
|
+
const answer = coerceAnswer(raw, responseSchema);
|
|
754
|
+
logger.log?.("[memory-store] search synthesis completed", {
|
|
755
|
+
evidenceCount: evidence.length,
|
|
756
|
+
answerChars: answer.length
|
|
757
|
+
});
|
|
758
|
+
return { answer, warnings };
|
|
759
|
+
} catch (error) {
|
|
760
|
+
logger.warn?.("[memory-store] search synthesis failed:", error);
|
|
761
|
+
warnings.push({
|
|
762
|
+
source: "memory",
|
|
763
|
+
code: "reflect_llm_failed",
|
|
764
|
+
message: error.message ?? "reflect_llm_failed"
|
|
765
|
+
});
|
|
766
|
+
return { answer: "", warnings };
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function mapHitToTier(hit) {
|
|
770
|
+
const tierMarker = hit.metadata?.tier;
|
|
771
|
+
if (tierMarker === "summary") {
|
|
772
|
+
return "summary";
|
|
773
|
+
}
|
|
774
|
+
if (hit.type === "memory") {
|
|
775
|
+
return "raw";
|
|
776
|
+
}
|
|
777
|
+
if (hit.type === "insight") {
|
|
778
|
+
return "insight";
|
|
779
|
+
}
|
|
780
|
+
if (hit.type === "knowledge") {
|
|
781
|
+
return "knowledge";
|
|
782
|
+
}
|
|
783
|
+
return hit.type;
|
|
784
|
+
}
|
|
785
|
+
async function resolveSearchScopePeers(input) {
|
|
786
|
+
return resolveScopePeer({
|
|
787
|
+
userId: input.userId,
|
|
788
|
+
peerFilter: input.peerFilter,
|
|
789
|
+
scopeCheck: input.deps.peerScopeCheck
|
|
790
|
+
});
|
|
791
|
+
}
|
|
379
792
|
|
|
380
793
|
// src/search/unified-search.ts
|
|
381
794
|
function deriveLexicalKeywords(query) {
|
|
@@ -447,7 +860,7 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
447
860
|
authToken: input.authToken
|
|
448
861
|
});
|
|
449
862
|
}
|
|
450
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
863
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers = []) {
|
|
451
864
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
452
865
|
let semantic = [];
|
|
453
866
|
if (isRawMessageChromaEnabled()) {
|
|
@@ -477,6 +890,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
477
890
|
}
|
|
478
891
|
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
479
892
|
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
893
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
480
894
|
semantic = (await Promise.all(
|
|
481
895
|
filters.map(
|
|
482
896
|
(filter) => searchRawMessagesAnn({
|
|
@@ -484,7 +898,9 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
484
898
|
queryEmbedding,
|
|
485
899
|
limit,
|
|
486
900
|
threshold,
|
|
487
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
901
|
+
botId: "botId" in filter ? filter.botId : void 0,
|
|
902
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
903
|
+
...factTypes ? { factTypes } : {}
|
|
488
904
|
})
|
|
489
905
|
)
|
|
490
906
|
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
@@ -494,11 +910,14 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
494
910
|
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
495
911
|
const manager2 = await getRawMessageManager2();
|
|
496
912
|
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
913
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
497
914
|
const results = await manager2.searchMessagesSemantically({
|
|
498
915
|
userId: input.userId,
|
|
499
916
|
queryEmbedding,
|
|
500
917
|
limit,
|
|
501
|
-
threshold
|
|
918
|
+
threshold,
|
|
919
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
920
|
+
...factTypes ? { factTypes } : {}
|
|
502
921
|
});
|
|
503
922
|
semantic = results.map((r) => ({
|
|
504
923
|
type: "memory",
|
|
@@ -534,7 +953,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
534
953
|
metadata: hit.metadata
|
|
535
954
|
};
|
|
536
955
|
}
|
|
537
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
956
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers = []) {
|
|
538
957
|
if (keywords.length === 0) {
|
|
539
958
|
return [];
|
|
540
959
|
}
|
|
@@ -542,13 +961,16 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
542
961
|
try {
|
|
543
962
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
544
963
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
964
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
545
965
|
return (await Promise.all(
|
|
546
966
|
filters.map(
|
|
547
967
|
(botId) => searchRawMessagesLexical({
|
|
548
968
|
userId: input.userId,
|
|
549
969
|
keywords,
|
|
550
970
|
limit: Math.ceil(limit / filters.length),
|
|
551
|
-
botId
|
|
971
|
+
botId,
|
|
972
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
973
|
+
...factTypes ? { factTypes } : {}
|
|
552
974
|
})
|
|
553
975
|
)
|
|
554
976
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -562,13 +984,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
562
984
|
try {
|
|
563
985
|
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
564
986
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
987
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
565
988
|
return (await Promise.all(
|
|
566
989
|
filters.map(
|
|
567
990
|
(botId) => lexicalSearchRawMessages2({
|
|
568
991
|
userId: input.userId,
|
|
569
992
|
keywords,
|
|
570
993
|
limit: Math.ceil(limit / filters.length),
|
|
571
|
-
botId
|
|
994
|
+
botId,
|
|
995
|
+
...factTypes ? { factTypes } : {}
|
|
572
996
|
})
|
|
573
997
|
)
|
|
574
998
|
)).flat().filter(Boolean).map(
|
|
@@ -581,6 +1005,42 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
581
1005
|
}
|
|
582
1006
|
return [];
|
|
583
1007
|
}
|
|
1008
|
+
function searchInputToUnified(input) {
|
|
1009
|
+
return {
|
|
1010
|
+
userId: input.userId,
|
|
1011
|
+
query: input.query,
|
|
1012
|
+
sources: input.sources ? [...input.sources] : void 0,
|
|
1013
|
+
limit: input.limit,
|
|
1014
|
+
threshold: input.threshold,
|
|
1015
|
+
authToken: input.authToken,
|
|
1016
|
+
includeArchivedInsights: input.includeArchivedInsights,
|
|
1017
|
+
botIds: input.botIds,
|
|
1018
|
+
documentIds: input.documentIds,
|
|
1019
|
+
asOf: input.asOf,
|
|
1020
|
+
dateFrom: input.dateFrom,
|
|
1021
|
+
dateTo: input.dateTo,
|
|
1022
|
+
mergeStrategy: input.mergeStrategy,
|
|
1023
|
+
peerFilter: input.peerFilter,
|
|
1024
|
+
reasoningStrategy: input.reasoningStrategy,
|
|
1025
|
+
factTypes: input.factTypes
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
function mapHitToEvidenceSource(hit) {
|
|
1029
|
+
const tierMarker = hit.metadata?.tier;
|
|
1030
|
+
if (tierMarker === "summary") {
|
|
1031
|
+
return "summary";
|
|
1032
|
+
}
|
|
1033
|
+
if (hit.type === "memory") {
|
|
1034
|
+
return "raw";
|
|
1035
|
+
}
|
|
1036
|
+
if (hit.type === "insight") {
|
|
1037
|
+
return "insight";
|
|
1038
|
+
}
|
|
1039
|
+
if (hit.type === "knowledge") {
|
|
1040
|
+
return "knowledge";
|
|
1041
|
+
}
|
|
1042
|
+
return hit.type;
|
|
1043
|
+
}
|
|
584
1044
|
function createUnifiedSearch(deps = {}) {
|
|
585
1045
|
const logger = deps.logger ?? console;
|
|
586
1046
|
function resolveReasoningStrategy(input) {
|
|
@@ -590,130 +1050,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
590
1050
|
}
|
|
591
1051
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
592
1052
|
}
|
|
593
|
-
async function
|
|
594
|
-
const query = input.query.trim();
|
|
595
|
-
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
596
|
-
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
597
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
598
|
-
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
599
|
-
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
600
|
-
const warnings = [];
|
|
601
|
-
if (!query) {
|
|
602
|
-
return {
|
|
603
|
-
query,
|
|
604
|
-
sources,
|
|
605
|
-
results: [],
|
|
606
|
-
count: 0,
|
|
607
|
-
warnings
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
let memorySubs;
|
|
611
|
-
const reasoningInfo = { strategy: reasoningStrategy };
|
|
612
|
-
if (input.dateFrom || input.dateTo) {
|
|
613
|
-
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
614
|
-
}
|
|
615
|
-
if (sources.includes("memory")) {
|
|
616
|
-
if (isRawMessageStorageAvailable()) {
|
|
617
|
-
try {
|
|
618
|
-
memorySubs = await runMemorySource(
|
|
619
|
-
input,
|
|
620
|
-
limit,
|
|
621
|
-
threshold,
|
|
622
|
-
warnings,
|
|
623
|
-
reasoningStrategy,
|
|
624
|
-
reasoningInfo
|
|
625
|
-
);
|
|
626
|
-
} catch (error) {
|
|
627
|
-
logger.warn?.("[memory-store] memory source failed:", error);
|
|
628
|
-
warnings.push({
|
|
629
|
-
source: "memory",
|
|
630
|
-
code: "memory_search_failed",
|
|
631
|
-
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
} else {
|
|
635
|
-
warnings.push({
|
|
636
|
-
source: "memory",
|
|
637
|
-
code: "raw_message_storage_unavailable",
|
|
638
|
-
message: "Raw memory storage is not available in this environment."
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
const insightHits = [];
|
|
643
|
-
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
644
|
-
try {
|
|
645
|
-
const hits = await deps.searchInsights({
|
|
646
|
-
userId: input.userId,
|
|
647
|
-
query: input.query,
|
|
648
|
-
limit,
|
|
649
|
-
threshold,
|
|
650
|
-
botIds: input.botIds,
|
|
651
|
-
includeArchived: input.includeArchivedInsights,
|
|
652
|
-
authToken: input.authToken
|
|
653
|
-
});
|
|
654
|
-
for (const hit of hits) {
|
|
655
|
-
insightHits.push({
|
|
656
|
-
type: "insight",
|
|
657
|
-
id: hit.id,
|
|
658
|
-
content: hit.content,
|
|
659
|
-
similarity: hit.similarity,
|
|
660
|
-
metadata: hit.metadata
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
} catch (error) {
|
|
664
|
-
logger.warn?.("[memory-store] insights source failed:", error);
|
|
665
|
-
warnings.push({
|
|
666
|
-
source: "insights",
|
|
667
|
-
code: "insights_search_failed",
|
|
668
|
-
message: error.message ?? "insights_search_failed"
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
} else if (sources.includes("insights")) ;
|
|
672
|
-
const knowledgeHits = [];
|
|
673
|
-
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
674
|
-
try {
|
|
675
|
-
const hits = await deps.searchKnowledge({
|
|
676
|
-
userId: input.userId,
|
|
677
|
-
query: input.query,
|
|
678
|
-
options: {
|
|
679
|
-
limit,
|
|
680
|
-
threshold,
|
|
681
|
-
documentIds: input.documentIds
|
|
682
|
-
},
|
|
683
|
-
authToken: input.authToken
|
|
684
|
-
});
|
|
685
|
-
for (const hit of hits) {
|
|
686
|
-
knowledgeHits.push(toKnowledgeResult(hit));
|
|
687
|
-
}
|
|
688
|
-
} catch (error) {
|
|
689
|
-
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
690
|
-
warnings.push({
|
|
691
|
-
source: "knowledge",
|
|
692
|
-
code: "knowledge_search_failed",
|
|
693
|
-
message: error.message ?? "knowledge_search_failed"
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
} else if (sources.includes("knowledge")) ;
|
|
697
|
-
const merged = mergeAcrossSources({
|
|
698
|
-
memorySubs,
|
|
699
|
-
insightHits,
|
|
700
|
-
knowledgeHits,
|
|
701
|
-
limit,
|
|
702
|
-
strategy: mergeStrategy
|
|
703
|
-
});
|
|
704
|
-
const output = {
|
|
705
|
-
query: input.query,
|
|
706
|
-
sources,
|
|
707
|
-
results: merged,
|
|
708
|
-
count: merged.length,
|
|
709
|
-
warnings
|
|
710
|
-
};
|
|
711
|
-
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
712
|
-
output.reasoning = reasoningInfo;
|
|
713
|
-
}
|
|
714
|
-
return output;
|
|
715
|
-
}
|
|
716
|
-
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
1053
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
|
|
717
1054
|
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
718
1055
|
warnings.push({
|
|
719
1056
|
source: "memory",
|
|
@@ -739,7 +1076,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
739
1076
|
executor: {
|
|
740
1077
|
search: async (request) => {
|
|
741
1078
|
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
742
|
-
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1079
|
+
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1080
|
+
deps,
|
|
1081
|
+
input,
|
|
1082
|
+
keywords2,
|
|
1083
|
+
limit,
|
|
1084
|
+
logger,
|
|
1085
|
+
peerPeers
|
|
1086
|
+
);
|
|
743
1087
|
let semanticHits = [];
|
|
744
1088
|
if (typeof deps.embedQuery === "function") {
|
|
745
1089
|
try {
|
|
@@ -751,7 +1095,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
751
1095
|
queryEmbedding,
|
|
752
1096
|
limit,
|
|
753
1097
|
threshold,
|
|
754
|
-
logger
|
|
1098
|
+
logger,
|
|
1099
|
+
peerPeers
|
|
755
1100
|
);
|
|
756
1101
|
} catch (error) {
|
|
757
1102
|
logger.warn?.(
|
|
@@ -796,7 +1141,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
796
1141
|
});
|
|
797
1142
|
const keywords2 = deriveLexicalKeywords(input.query);
|
|
798
1143
|
const lexical2 = filterByDateRange(
|
|
799
|
-
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
1144
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger, peerPeers),
|
|
800
1145
|
input.dateFrom,
|
|
801
1146
|
input.dateTo
|
|
802
1147
|
);
|
|
@@ -822,7 +1167,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
822
1167
|
);
|
|
823
1168
|
const lists = await Promise.all(
|
|
824
1169
|
embeddings.map(
|
|
825
|
-
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
1170
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger, peerPeers)
|
|
826
1171
|
)
|
|
827
1172
|
);
|
|
828
1173
|
semantic = mergeByMaxScore(lists);
|
|
@@ -838,7 +1183,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
838
1183
|
}
|
|
839
1184
|
if (semantic.length === 0) {
|
|
840
1185
|
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
841
|
-
semantic = await runSemanticSearchForEmbedding(
|
|
1186
|
+
semantic = await runSemanticSearchForEmbedding(
|
|
1187
|
+
deps,
|
|
1188
|
+
input,
|
|
1189
|
+
queryEmbedding,
|
|
1190
|
+
limit,
|
|
1191
|
+
threshold,
|
|
1192
|
+
logger,
|
|
1193
|
+
peerPeers
|
|
1194
|
+
);
|
|
842
1195
|
}
|
|
843
1196
|
let lexical = [];
|
|
844
1197
|
const keywords = deriveLexicalKeywords(input.query);
|
|
@@ -847,13 +1200,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
847
1200
|
try {
|
|
848
1201
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
849
1202
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
1203
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
850
1204
|
lexical = (await Promise.all(
|
|
851
1205
|
lexFilters.map(
|
|
852
1206
|
(botId) => searchRawMessagesLexical({
|
|
853
1207
|
userId: input.userId,
|
|
854
1208
|
keywords,
|
|
855
1209
|
limit,
|
|
856
|
-
botId
|
|
1210
|
+
botId,
|
|
1211
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1212
|
+
...factTypes ? { factTypes } : {}
|
|
857
1213
|
})
|
|
858
1214
|
)
|
|
859
1215
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -881,20 +1237,272 @@ function createUnifiedSearch(deps = {}) {
|
|
|
881
1237
|
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
882
1238
|
};
|
|
883
1239
|
}
|
|
1240
|
+
async function searchUnifiedMemory(input) {
|
|
1241
|
+
const query = input.query.trim();
|
|
1242
|
+
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
1243
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1244
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1245
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1246
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1247
|
+
);
|
|
1248
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1249
|
+
const warnings = [];
|
|
1250
|
+
const peerScope = input.peerFilter && input.peerFilter.length > 0 ? await resolveScopePeer({
|
|
1251
|
+
userId: input.userId,
|
|
1252
|
+
peerFilter: input.peerFilter,
|
|
1253
|
+
scopeCheck: deps.peerScopeCheck
|
|
1254
|
+
}) : { peers: [], warnings: [] };
|
|
1255
|
+
warnings.push(...peerScope.warnings);
|
|
1256
|
+
const peerPeers = peerScope.peers;
|
|
1257
|
+
if (!query) {
|
|
1258
|
+
return {
|
|
1259
|
+
query,
|
|
1260
|
+
sources,
|
|
1261
|
+
results: [],
|
|
1262
|
+
count: 0,
|
|
1263
|
+
warnings
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
let memorySubs;
|
|
1267
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
1268
|
+
if (input.dateFrom || input.dateTo) {
|
|
1269
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1270
|
+
}
|
|
1271
|
+
if (sources.includes("memory")) {
|
|
1272
|
+
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1273
|
+
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1274
|
+
try {
|
|
1275
|
+
memorySubs = await runMemorySource(
|
|
1276
|
+
input,
|
|
1277
|
+
limit,
|
|
1278
|
+
threshold,
|
|
1279
|
+
warnings,
|
|
1280
|
+
reasoningStrategy,
|
|
1281
|
+
reasoningInfo,
|
|
1282
|
+
peerPeers
|
|
1283
|
+
);
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1286
|
+
warnings.push({
|
|
1287
|
+
source: "memory",
|
|
1288
|
+
code: "memory_search_failed",
|
|
1289
|
+
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
} else {
|
|
1293
|
+
warnings.push({
|
|
1294
|
+
source: "memory",
|
|
1295
|
+
code: "raw_message_storage_unavailable",
|
|
1296
|
+
message: "Raw memory storage is not available in this environment."
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
const insightHits = [];
|
|
1301
|
+
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
1302
|
+
try {
|
|
1303
|
+
const hits = await deps.searchInsights({
|
|
1304
|
+
userId: input.userId,
|
|
1305
|
+
query: input.query,
|
|
1306
|
+
limit,
|
|
1307
|
+
threshold,
|
|
1308
|
+
botIds: input.botIds,
|
|
1309
|
+
includeArchived: input.includeArchivedInsights,
|
|
1310
|
+
authToken: input.authToken,
|
|
1311
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1312
|
+
});
|
|
1313
|
+
for (const hit of hits) {
|
|
1314
|
+
insightHits.push({
|
|
1315
|
+
type: "insight",
|
|
1316
|
+
id: hit.id,
|
|
1317
|
+
content: hit.content,
|
|
1318
|
+
similarity: hit.similarity,
|
|
1319
|
+
metadata: hit.metadata
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
logger.warn?.("[memory-store] insights source failed:", error);
|
|
1324
|
+
warnings.push({
|
|
1325
|
+
source: "insights",
|
|
1326
|
+
code: "insights_search_failed",
|
|
1327
|
+
message: error.message ?? "insights_search_failed"
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
} else if (sources.includes("insights")) ;
|
|
1331
|
+
const knowledgeHits = [];
|
|
1332
|
+
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
1333
|
+
try {
|
|
1334
|
+
const hits = await deps.searchKnowledge({
|
|
1335
|
+
userId: input.userId,
|
|
1336
|
+
query: input.query,
|
|
1337
|
+
options: {
|
|
1338
|
+
limit,
|
|
1339
|
+
threshold,
|
|
1340
|
+
documentIds: input.documentIds
|
|
1341
|
+
},
|
|
1342
|
+
authToken: input.authToken,
|
|
1343
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1344
|
+
});
|
|
1345
|
+
for (const hit of hits) {
|
|
1346
|
+
knowledgeHits.push(toKnowledgeResult(hit));
|
|
1347
|
+
}
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
1350
|
+
warnings.push({
|
|
1351
|
+
source: "knowledge",
|
|
1352
|
+
code: "knowledge_search_failed",
|
|
1353
|
+
message: error.message ?? "knowledge_search_failed"
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
} else if (sources.includes("knowledge")) ;
|
|
1357
|
+
const merged = mergeAcrossSources({
|
|
1358
|
+
memorySubs,
|
|
1359
|
+
insightHits,
|
|
1360
|
+
knowledgeHits,
|
|
1361
|
+
limit,
|
|
1362
|
+
strategy: mergeStrategy
|
|
1363
|
+
});
|
|
1364
|
+
const ranked = await applyReranker(deps.reranker, input.query, merged);
|
|
1365
|
+
const output = {
|
|
1366
|
+
query: input.query,
|
|
1367
|
+
sources,
|
|
1368
|
+
results: ranked,
|
|
1369
|
+
count: ranked.length,
|
|
1370
|
+
warnings
|
|
1371
|
+
};
|
|
1372
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
1373
|
+
output.reasoning = reasoningInfo;
|
|
1374
|
+
}
|
|
1375
|
+
return output;
|
|
1376
|
+
}
|
|
884
1377
|
async function searchRawMemorySemantically(input) {
|
|
885
1378
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
886
1379
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
887
1380
|
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1381
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1382
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1383
|
+
);
|
|
888
1384
|
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
889
1385
|
return mergeAcrossSources({
|
|
890
1386
|
memorySubs: sub,
|
|
891
1387
|
insightHits: [],
|
|
892
1388
|
knowledgeHits: [],
|
|
893
1389
|
limit,
|
|
894
|
-
strategy:
|
|
1390
|
+
strategy: mergeStrategy
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
async function search(input) {
|
|
1394
|
+
const query = input.query.trim();
|
|
1395
|
+
const wantsSynthesis = typeof input.synthesize === "boolean" ? input.synthesize : typeof input.synthesize === "object" && input.synthesize !== null;
|
|
1396
|
+
const synthesisSchema = typeof input.synthesize === "object" && input.synthesize !== null ? input.synthesize.responseSchema ?? input.responseSchema : input.responseSchema;
|
|
1397
|
+
const peerScope = await resolveSearchScopePeers({
|
|
1398
|
+
deps,
|
|
1399
|
+
userId: input.userId,
|
|
1400
|
+
peerFilter: input.peerFilter
|
|
1401
|
+
});
|
|
1402
|
+
if (!query) {
|
|
1403
|
+
const empty = {
|
|
1404
|
+
query,
|
|
1405
|
+
sources: input.sources ? [...input.sources] : [],
|
|
1406
|
+
results: [],
|
|
1407
|
+
evidence: [],
|
|
1408
|
+
count: 0,
|
|
1409
|
+
warnings: peerScope.warnings,
|
|
1410
|
+
answer: ""
|
|
1411
|
+
};
|
|
1412
|
+
return empty;
|
|
1413
|
+
}
|
|
1414
|
+
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
|
|
1415
|
+
const wantsSummaryTier = tiers.includes("summary");
|
|
1416
|
+
const wantsRawTier = tiers.includes("raw");
|
|
1417
|
+
const wantsInsightTier = tiers.includes("insight");
|
|
1418
|
+
const wantsKnowledgeTier = tiers.includes("knowledge");
|
|
1419
|
+
const warnings = [...peerScope.warnings];
|
|
1420
|
+
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1421
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1422
|
+
let unifiedHits = [];
|
|
1423
|
+
let sources = input.sources ? [...input.sources] : [];
|
|
1424
|
+
let unifiedReasoning;
|
|
1425
|
+
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
|
|
1426
|
+
const inferredSources = sources.length > 0 ? sources : [
|
|
1427
|
+
...wantsRawTier ? ["memory"] : [],
|
|
1428
|
+
...wantsInsightTier ? ["insights"] : [],
|
|
1429
|
+
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
1430
|
+
];
|
|
1431
|
+
const unified = await searchUnifiedMemory({
|
|
1432
|
+
...searchInputToUnified(input),
|
|
1433
|
+
sources: inferredSources.length > 0 ? inferredSources : void 0
|
|
1434
|
+
});
|
|
1435
|
+
unifiedHits = unified.results;
|
|
1436
|
+
sources = unified.sources;
|
|
1437
|
+
unifiedReasoning = unified.reasoning;
|
|
1438
|
+
warnings.push(...unified.warnings);
|
|
1439
|
+
}
|
|
1440
|
+
let summaryHits = [];
|
|
1441
|
+
if (wantsSummaryTier && typeof deps.searchSummaries === "function") {
|
|
1442
|
+
const summaryBucket = await gatherSummaries(
|
|
1443
|
+
deps,
|
|
1444
|
+
{ userId: input.userId, query: input.query, authToken: input.authToken },
|
|
1445
|
+
limit,
|
|
1446
|
+
threshold,
|
|
1447
|
+
logger,
|
|
1448
|
+
peerScope.peers
|
|
1449
|
+
);
|
|
1450
|
+
warnings.push(...summaryBucket.warnings);
|
|
1451
|
+
summaryHits = summaryBucket.hits;
|
|
1452
|
+
}
|
|
1453
|
+
const tierLists = [];
|
|
1454
|
+
if (unifiedHits.length > 0) {
|
|
1455
|
+
tierLists.push({ name: "memory", hits: unifiedHits });
|
|
1456
|
+
}
|
|
1457
|
+
if (summaryHits.length > 0) {
|
|
1458
|
+
tierLists.push({ name: "summary", hits: summaryHits });
|
|
1459
|
+
}
|
|
1460
|
+
let hits;
|
|
1461
|
+
if (tierLists.length === 0) {
|
|
1462
|
+
hits = [];
|
|
1463
|
+
} else if (tierLists.length === 1) {
|
|
1464
|
+
hits = tierLists[0].hits.slice(0, limit);
|
|
1465
|
+
} else {
|
|
1466
|
+
hits = mergeUnifiedMemorySearchResultsRrf(tierLists, limit);
|
|
1467
|
+
}
|
|
1468
|
+
const evidence = hits.map((hit) => ({
|
|
1469
|
+
id: hit.id,
|
|
1470
|
+
source: mapHitToEvidenceSource(hit),
|
|
1471
|
+
snippet: hit.content,
|
|
1472
|
+
score: hit.similarity,
|
|
1473
|
+
timestamp: getCandidateTimestamp(hit.metadata)
|
|
1474
|
+
}));
|
|
1475
|
+
const base = {
|
|
1476
|
+
query,
|
|
1477
|
+
sources,
|
|
1478
|
+
results: hits,
|
|
1479
|
+
evidence,
|
|
1480
|
+
count: hits.length,
|
|
1481
|
+
warnings
|
|
1482
|
+
};
|
|
1483
|
+
if (unifiedReasoning) {
|
|
1484
|
+
base.reasoning = unifiedReasoning;
|
|
1485
|
+
}
|
|
1486
|
+
if (!wantsSynthesis) {
|
|
1487
|
+
return base;
|
|
1488
|
+
}
|
|
1489
|
+
const { answer, warnings: synthWarnings } = await synthesizeAnswer({
|
|
1490
|
+
query,
|
|
1491
|
+
hits,
|
|
1492
|
+
responseSchema: synthesisSchema,
|
|
1493
|
+
deps,
|
|
1494
|
+
logger
|
|
895
1495
|
});
|
|
1496
|
+
base.answer = answer;
|
|
1497
|
+
base.warnings = [...base.warnings, ...synthWarnings];
|
|
1498
|
+
return base;
|
|
896
1499
|
}
|
|
897
|
-
return {
|
|
1500
|
+
return {
|
|
1501
|
+
search,
|
|
1502
|
+
searchUnifiedMemory,
|
|
1503
|
+
searchRawMemorySemantically,
|
|
1504
|
+
consolidate: (input) => applyReflectedPlan(deps, {}, input)
|
|
1505
|
+
};
|
|
898
1506
|
}
|
|
899
1507
|
function mergeAcrossSources(input) {
|
|
900
1508
|
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
@@ -925,11 +1533,185 @@ function mergeAcrossSources(input) {
|
|
|
925
1533
|
}
|
|
926
1534
|
return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
|
|
927
1535
|
}
|
|
1536
|
+
var DEFAULT_MAX_FACTS = 1e3;
|
|
1537
|
+
var LOW_CONFIDENCE_THRESHOLD = 0.1;
|
|
1538
|
+
function toHRR(data) {
|
|
1539
|
+
const arr = data instanceof Float32Array ? data : new Float32Array(data);
|
|
1540
|
+
return { dim: arr.length, data: arr };
|
|
1541
|
+
}
|
|
1542
|
+
function generateFactId() {
|
|
1543
|
+
const stamp = Date.now().toString(36);
|
|
1544
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
1545
|
+
return `vsa-${stamp}-${rand}`;
|
|
1546
|
+
}
|
|
1547
|
+
function createVsaRecall(storage) {
|
|
1548
|
+
return {
|
|
1549
|
+
async storeFact(input) {
|
|
1550
|
+
const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
|
|
1551
|
+
const fillerVector = vsaNormalizeVector(input.fillerVector, "fillerVector");
|
|
1552
|
+
vsaAssertSameDim(roleVector, fillerVector, "storeFact");
|
|
1553
|
+
const dim = input.dim ?? roleVector.length;
|
|
1554
|
+
if (roleVector.length !== dim) {
|
|
1555
|
+
throw new Error(`storeFact: roleVector.length (${roleVector.length}) must equal dim (${dim})`);
|
|
1556
|
+
}
|
|
1557
|
+
const now = Date.now();
|
|
1558
|
+
const fact = {
|
|
1559
|
+
factId: input.factId ?? generateFactId(),
|
|
1560
|
+
userId: input.userId,
|
|
1561
|
+
roleLabel: input.roleLabel,
|
|
1562
|
+
fillerLabel: input.fillerLabel,
|
|
1563
|
+
roleVector,
|
|
1564
|
+
fillerVector,
|
|
1565
|
+
dim,
|
|
1566
|
+
scopeTag: input.scopeTag ?? "default",
|
|
1567
|
+
botId: input.botId,
|
|
1568
|
+
createdAt: now
|
|
1569
|
+
};
|
|
1570
|
+
await storage.storeFact(fact);
|
|
1571
|
+
return { factId: fact.factId, createdAt: now };
|
|
1572
|
+
},
|
|
1573
|
+
async recall(input) {
|
|
1574
|
+
const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
|
|
1575
|
+
const scopeTag = input.scopeTag ?? "default";
|
|
1576
|
+
const maxFacts = input.maxFacts ?? DEFAULT_MAX_FACTS;
|
|
1577
|
+
if (input.vocabulary.length === 0) {
|
|
1578
|
+
throw new Error("vsaRecall: vocabulary must contain at least one entry");
|
|
1579
|
+
}
|
|
1580
|
+
const facts = await storage.queryFacts({
|
|
1581
|
+
userId: input.userId,
|
|
1582
|
+
scopeTag,
|
|
1583
|
+
botId: input.botId,
|
|
1584
|
+
includeDeprecated: false,
|
|
1585
|
+
limit: maxFacts
|
|
1586
|
+
});
|
|
1587
|
+
const warnings = [];
|
|
1588
|
+
if (facts.length === 0) {
|
|
1589
|
+
warnings.push({
|
|
1590
|
+
code: "vsa_no_facts",
|
|
1591
|
+
message: `No VSA facts for userId=${input.userId} scopeTag=${scopeTag}`
|
|
1592
|
+
});
|
|
1593
|
+
return {
|
|
1594
|
+
fillerLabel: "",
|
|
1595
|
+
score: 0,
|
|
1596
|
+
allScores: [],
|
|
1597
|
+
factCount: 0,
|
|
1598
|
+
warnings
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
const refDim = facts[0].dim;
|
|
1602
|
+
const dimMatchedFacts = facts.filter((fact) => {
|
|
1603
|
+
if (fact.dim === refDim) return true;
|
|
1604
|
+
warnings.push({
|
|
1605
|
+
code: "vsa_dim_mismatch",
|
|
1606
|
+
message: `Fact ${fact.factId} has dim=${fact.dim}, expected ${refDim}; skipping`
|
|
1607
|
+
});
|
|
1608
|
+
return false;
|
|
1609
|
+
});
|
|
1610
|
+
if (roleVector.length !== refDim) {
|
|
1611
|
+
throw new Error(
|
|
1612
|
+
`vsaRecall: roleVector dim (${roleVector.length}) must match stored fact dim (${refDim})`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
if (dimMatchedFacts.length === 0) {
|
|
1616
|
+
warnings.push({
|
|
1617
|
+
code: "vsa_no_facts",
|
|
1618
|
+
message: `All stored facts for userId=${input.userId} scopeTag=${scopeTag} had a non-matching dim`
|
|
1619
|
+
});
|
|
1620
|
+
return {
|
|
1621
|
+
fillerLabel: "",
|
|
1622
|
+
score: 0,
|
|
1623
|
+
allScores: [],
|
|
1624
|
+
factCount: 0,
|
|
1625
|
+
warnings
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
const memoryVec = superpose(
|
|
1629
|
+
dimMatchedFacts.map((fact) => bind(toHRR(fact.roleVector), toHRR(fact.fillerVector)))
|
|
1630
|
+
);
|
|
1631
|
+
const candidate = unbind(memoryVec, toHRR(roleVector));
|
|
1632
|
+
const vocabVecs = input.vocabulary.map(
|
|
1633
|
+
(entry) => toHRR(vsaNormalizeVector(entry.vector, `vocabulary[${entry.label}].vector`))
|
|
1634
|
+
);
|
|
1635
|
+
const bestVec = cleanup(candidate, vocabVecs);
|
|
1636
|
+
const bestIdx = vocabVecs.indexOf(bestVec);
|
|
1637
|
+
const allScores = input.vocabulary.map((entry, idx) => ({
|
|
1638
|
+
label: entry.label,
|
|
1639
|
+
score: cosineSimilarity(candidate, vocabVecs[idx])
|
|
1640
|
+
}));
|
|
1641
|
+
allScores.sort((a, b) => b.score - a.score);
|
|
1642
|
+
const fillerLabel = bestIdx >= 0 ? input.vocabulary[bestIdx].label : "";
|
|
1643
|
+
const score = allScores[0]?.score ?? 0;
|
|
1644
|
+
if (score < LOW_CONFIDENCE_THRESHOLD) {
|
|
1645
|
+
warnings.push({
|
|
1646
|
+
code: "vsa_low_confidence",
|
|
1647
|
+
message: `Best match "${fillerLabel}" score ${score.toFixed(3)} below ${LOW_CONFIDENCE_THRESHOLD}`
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
if (facts.length >= maxFacts) {
|
|
1651
|
+
warnings.push({
|
|
1652
|
+
code: "vsa_fact_limit_reached",
|
|
1653
|
+
message: `Loaded ${facts.length} facts (cap=${maxFacts}); recall may degrade with more`
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
return {
|
|
1657
|
+
fillerLabel,
|
|
1658
|
+
score,
|
|
1659
|
+
allScores,
|
|
1660
|
+
factCount: facts.length,
|
|
1661
|
+
warnings
|
|
1662
|
+
};
|
|
1663
|
+
},
|
|
1664
|
+
async listFacts(input) {
|
|
1665
|
+
const facts = await storage.queryFacts({
|
|
1666
|
+
userId: input.userId,
|
|
1667
|
+
scopeTag: input.scopeTag,
|
|
1668
|
+
botId: input.botId,
|
|
1669
|
+
includeDeprecated: input.includeDeprecated
|
|
1670
|
+
});
|
|
1671
|
+
return facts.map((fact) => ({
|
|
1672
|
+
factId: fact.factId,
|
|
1673
|
+
userId: fact.userId,
|
|
1674
|
+
roleLabel: fact.roleLabel,
|
|
1675
|
+
fillerLabel: fact.fillerLabel,
|
|
1676
|
+
scopeTag: fact.scopeTag,
|
|
1677
|
+
botId: fact.botId,
|
|
1678
|
+
createdAt: fact.createdAt,
|
|
1679
|
+
deprecatedAt: fact.deprecatedAt
|
|
1680
|
+
}));
|
|
1681
|
+
},
|
|
1682
|
+
async forget(input) {
|
|
1683
|
+
const result = await storage.deprecateFacts({
|
|
1684
|
+
userId: input.userId,
|
|
1685
|
+
factIds: input.factIds,
|
|
1686
|
+
reason: input.reason
|
|
1687
|
+
});
|
|
1688
|
+
return { deprecatedCount: result.deprecatedCount };
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
928
1692
|
|
|
929
1693
|
// src/index.ts
|
|
930
1694
|
init_raw_message_store();
|
|
1695
|
+
init_sqlite_raw_message_store();
|
|
931
1696
|
init_raw_message_store();
|
|
932
1697
|
init_postgres_raw_message_factory();
|
|
1698
|
+
function attachMemoryGraphStore(store, input) {
|
|
1699
|
+
const logger = input.logger ?? console;
|
|
1700
|
+
if (typeof input.storage.compareAndSwapGraphLedger !== "function") {
|
|
1701
|
+
logger.warn?.(
|
|
1702
|
+
"[memory-store] attachMemoryGraphStore: storage is missing `compareAndSwapGraphLedger`; no graph store attached."
|
|
1703
|
+
);
|
|
1704
|
+
return store;
|
|
1705
|
+
}
|
|
1706
|
+
const graphStore = createRawMessageMemoryGraphStore({
|
|
1707
|
+
storage: input.storage,
|
|
1708
|
+
ownerScope: input.ownerScope,
|
|
1709
|
+
botId: input.botId,
|
|
1710
|
+
now: input.now
|
|
1711
|
+
});
|
|
1712
|
+
store.graphStore = graphStore;
|
|
1713
|
+
return store;
|
|
1714
|
+
}
|
|
933
1715
|
|
|
934
1716
|
// src/search/query-rewriter.ts
|
|
935
1717
|
var SYSTEM_PROMPT = `You are a memory retrieval assistant.
|
|
@@ -1285,19 +2067,173 @@ function createIdentityIterativePlanner() {
|
|
|
1285
2067
|
};
|
|
1286
2068
|
}
|
|
1287
2069
|
|
|
1288
|
-
// src/
|
|
2070
|
+
// src/search/peer-profile.ts
|
|
2071
|
+
var DEFAULT_PROFILE_LIMIT = 20;
|
|
2072
|
+
var DEFAULT_RELATIONSHIP_LIMIT = 5;
|
|
2073
|
+
var DEFAULT_PROFILE_THRESHOLD = 0.1;
|
|
2074
|
+
var DEFAULT_RELATIONSHIP_THRESHOLD = 0.1;
|
|
2075
|
+
function indexKnownPeers(peers) {
|
|
2076
|
+
const map = /* @__PURE__ */ new Map();
|
|
2077
|
+
for (const peer of peers) {
|
|
2078
|
+
map.set(peer.id, peer);
|
|
2079
|
+
}
|
|
2080
|
+
return map;
|
|
2081
|
+
}
|
|
2082
|
+
function resolvePeerFromMap(peerId, known, resolvePeer) {
|
|
2083
|
+
if (resolvePeer) {
|
|
2084
|
+
const explicit = resolvePeer(peerId);
|
|
2085
|
+
if (explicit) return explicit;
|
|
2086
|
+
}
|
|
2087
|
+
return known.get(peerId);
|
|
2088
|
+
}
|
|
2089
|
+
function buildProfilePrompt(peerId) {
|
|
2090
|
+
return `Summarize ${peerId}'s role, interests, and recent activity based on the evidence below.`;
|
|
2091
|
+
}
|
|
2092
|
+
function buildRelationshipPrompt(fromId, toId) {
|
|
2093
|
+
return `How do ${fromId} and ${toId} interact? Describe the relationship.`;
|
|
2094
|
+
}
|
|
2095
|
+
async function collectMentions(postings) {
|
|
2096
|
+
if (!postings) return [];
|
|
2097
|
+
const result = postings();
|
|
2098
|
+
if (Array.isArray(result)) {
|
|
2099
|
+
return result;
|
|
2100
|
+
}
|
|
2101
|
+
return await result;
|
|
2102
|
+
}
|
|
2103
|
+
function collectCoMentions(postings, targetId) {
|
|
2104
|
+
const coCounts = /* @__PURE__ */ new Map();
|
|
2105
|
+
for (const posting of postings) {
|
|
2106
|
+
const mentionIds = posting.mentionedPeers?.map((p) => p.id) ?? posting.mentionedIds ?? [];
|
|
2107
|
+
const involvesTarget = posting.authorId === targetId || mentionIds.includes(targetId) || (posting.mentionedPeers ?? []).some((p) => p.id === targetId);
|
|
2108
|
+
if (!involvesTarget) {
|
|
2109
|
+
continue;
|
|
2110
|
+
}
|
|
2111
|
+
if (posting.authorId !== targetId) {
|
|
2112
|
+
const entry = coCounts.get(posting.authorId) ?? { count: 0, evidenceIds: [] };
|
|
2113
|
+
entry.count += 1;
|
|
2114
|
+
entry.evidenceIds.push(posting.id);
|
|
2115
|
+
coCounts.set(posting.authorId, entry);
|
|
2116
|
+
}
|
|
2117
|
+
for (const otherId of mentionIds) {
|
|
2118
|
+
if (otherId === targetId) continue;
|
|
2119
|
+
const entry = coCounts.get(otherId) ?? { count: 0, evidenceIds: [] };
|
|
2120
|
+
entry.count += 1;
|
|
2121
|
+
entry.evidenceIds.push(posting.id);
|
|
2122
|
+
coCounts.set(otherId, entry);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return coCounts;
|
|
2126
|
+
}
|
|
2127
|
+
function createPeerProfile(deps) {
|
|
2128
|
+
const { search, userId, resolvePeer, listKnownPeers } = deps;
|
|
2129
|
+
return {
|
|
2130
|
+
getKnownPeers() {
|
|
2131
|
+
return listKnownPeers();
|
|
2132
|
+
},
|
|
2133
|
+
async getProfile(peerId) {
|
|
2134
|
+
const known = indexKnownPeers(listKnownPeers());
|
|
2135
|
+
const peer = resolvePeerFromMap(peerId, known, resolvePeer);
|
|
2136
|
+
if (!peer) {
|
|
2137
|
+
throw new Error(`peer_profile: unknown peerId "${peerId}"`);
|
|
2138
|
+
}
|
|
2139
|
+
const out = await search.search({
|
|
2140
|
+
userId,
|
|
2141
|
+
query: buildProfilePrompt(peer.id),
|
|
2142
|
+
peerFilter: [peer],
|
|
2143
|
+
tiers: ["raw"],
|
|
2144
|
+
limit: DEFAULT_PROFILE_LIMIT,
|
|
2145
|
+
threshold: deps.profileThreshold ?? DEFAULT_PROFILE_THRESHOLD,
|
|
2146
|
+
synthesize: {
|
|
2147
|
+
responseSchema: { answer: "string", confidence: "number" }
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
return {
|
|
2151
|
+
peer,
|
|
2152
|
+
answer: out.answer ?? "",
|
|
2153
|
+
evidenceIds: out.evidence.map((item) => item.id),
|
|
2154
|
+
warnings: out.warnings.map((warning) => warning.code)
|
|
2155
|
+
};
|
|
2156
|
+
},
|
|
2157
|
+
async getRelationships(peerId) {
|
|
2158
|
+
const known = indexKnownPeers(listKnownPeers());
|
|
2159
|
+
const peer = resolvePeerFromMap(peerId, known, resolvePeer);
|
|
2160
|
+
if (!peer) {
|
|
2161
|
+
throw new Error(`peer_profile: unknown peerId "${peerId}"`);
|
|
2162
|
+
}
|
|
2163
|
+
const postings = await collectMentions(deps.listPeerMentions);
|
|
2164
|
+
const coCounts = collectCoMentions(postings, peer.id);
|
|
2165
|
+
if (coCounts.size === 0) {
|
|
2166
|
+
return [];
|
|
2167
|
+
}
|
|
2168
|
+
const results = [];
|
|
2169
|
+
for (const [otherId, info] of coCounts) {
|
|
2170
|
+
const other = resolvePeerFromMap(otherId, known, resolvePeer);
|
|
2171
|
+
if (!other) continue;
|
|
2172
|
+
const confirmation = await search.search({
|
|
2173
|
+
userId,
|
|
2174
|
+
query: buildRelationshipPrompt(peer.id, other.id),
|
|
2175
|
+
peerFilter: [peer, other],
|
|
2176
|
+
sources: ["memory"],
|
|
2177
|
+
limit: DEFAULT_RELATIONSHIP_LIMIT,
|
|
2178
|
+
threshold: deps.relationshipThreshold ?? DEFAULT_RELATIONSHIP_THRESHOLD,
|
|
2179
|
+
mergeStrategy: "rrf"
|
|
2180
|
+
});
|
|
2181
|
+
const evidenceIds = confirmation.results.map((hit) => hit.id);
|
|
2182
|
+
results.push({
|
|
2183
|
+
from: peer,
|
|
2184
|
+
to: other,
|
|
2185
|
+
summary: confirmation.results.length > 0 ? `${confirmation.results.length} cross-mention${confirmation.results.length === 1 ? "" : "s"} between ${peer.id} and ${other.id}` : "no cross-mentions found",
|
|
2186
|
+
evidenceIds,
|
|
2187
|
+
strength: info.count
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
return results.sort((a, b) => b.strength - a.strength);
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
1289
2194
|
async function createMemoryStore(config = {}) {
|
|
1290
2195
|
const dbPath = config.dbPath ?? (config.db && "path" in config.db ? config.db.path : void 0);
|
|
1291
2196
|
const raw = createRawMessageStore({ env: config.env, dbPath });
|
|
1292
|
-
const search = createUnifiedSearch(config.unified);
|
|
2197
|
+
const search = createUnifiedSearch({ ...config.unified, logger: config.logger });
|
|
1293
2198
|
configureRawMessageStore(config);
|
|
1294
|
-
|
|
2199
|
+
const wiring = {
|
|
2200
|
+
graphStore: config.graphStore,
|
|
2201
|
+
storage: config.storage
|
|
2202
|
+
};
|
|
2203
|
+
const vsaDbPath = config.dbPath ?? (config.db && "path" in config.db ? config.db.path : void 0) ?? resolveSQLiteRawMessageDbPath();
|
|
2204
|
+
const vsaStorage = await getSQLiteVsaStore({
|
|
2205
|
+
dbPath: vsaDbPath
|
|
2206
|
+
});
|
|
2207
|
+
const vsa = createVsaRecall(vsaStorage);
|
|
2208
|
+
const applyConsolidate = (input) => search.consolidate({
|
|
2209
|
+
...input,
|
|
2210
|
+
graphStore: input.graphStore ?? wiring.graphStore,
|
|
2211
|
+
storage: input.storage ?? wiring.storage
|
|
2212
|
+
});
|
|
2213
|
+
const store = {
|
|
1295
2214
|
raw,
|
|
1296
|
-
search
|
|
2215
|
+
// Flat search verb — `search({ synthesize: true })` opts into the
|
|
2216
|
+
// read-only LLM synthesis path.
|
|
2217
|
+
search: search.search,
|
|
2218
|
+
vsa,
|
|
1297
2219
|
getRawMessageManager,
|
|
1298
2220
|
searchUnifiedMemory: search.searchUnifiedMemory,
|
|
1299
|
-
searchRawMemorySemantically: search.searchRawMemorySemantically
|
|
2221
|
+
searchRawMemorySemantically: search.searchRawMemorySemantically,
|
|
2222
|
+
consolidate: applyConsolidate,
|
|
2223
|
+
get graphStore() {
|
|
2224
|
+
return wiring.graphStore;
|
|
2225
|
+
},
|
|
2226
|
+
set graphStore(value) {
|
|
2227
|
+
wiring.graphStore = value;
|
|
2228
|
+
},
|
|
2229
|
+
get storage() {
|
|
2230
|
+
return wiring.storage;
|
|
2231
|
+
},
|
|
2232
|
+
set storage(value) {
|
|
2233
|
+
wiring.storage = value;
|
|
2234
|
+
}
|
|
1300
2235
|
};
|
|
2236
|
+
return store;
|
|
1301
2237
|
}
|
|
1302
2238
|
|
|
1303
|
-
export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, clearPostgresFactory, closeRawMessageStore, createIdentityIterativePlanner, createIdentityRewriter, createIterativeRecallPlanner, createMemoryStore, createRawMessageStore, createUnifiedSearch, createUserVoiceRewriter, getRawMessageManager, getRawMessageStorageBackend, hasPostgresFactory, isRawMemorySemanticResult, isRawMessageStorageAvailable, mergeUnifiedMemorySearchResults, normalizeUnifiedMemoryReasoningStrategy, normalizeUnifiedMemorySearchSources, registerPostgresFactory, resolvePostgresFactory, toKnowledgeResult, toMemoryResult };
|
|
2239
|
+
export { applyReflectedPlan, attachMemoryGraphStore, clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, clearPostgresFactory, closeRawMessageStore, createIdentityIterativePlanner, createIdentityRewriter, createIterativeRecallPlanner, createMemoryStore, createPeerProfile, createRawMessageStore, createUnifiedSearch, createUserVoiceRewriter, createVsaRecall, getRawMessageManager, getRawMessageStorageBackend, hasPostgresFactory, isRawMemorySemanticResult, isRawMessageStorageAvailable, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryReasoningStrategy, normalizeUnifiedMemorySearchSources, registerPostgresFactory, resolvePostgresFactory, toKnowledgeResult, toMemoryResult };
|