@melandlabs/memory-store 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -56
- package/dist/{config-2Q092MUz.d.ts → config-CnbqSek5.d.ts} +215 -2
- package/dist/contracts.d.ts +1 -0
- package/dist/contracts.js +2 -0
- package/dist/http.d.ts +21 -5
- package/dist/http.js +1051 -155
- package/dist/index.d.ts +230 -15
- package/dist/index.js +1087 -152
- package/dist/mcp.d.ts +14 -5
- package/dist/mcp.js +1188 -174
- package/dist/policies/memory-graph-correction-policy.d.ts +1 -0
- package/dist/policies/memory-graph-correction-policy.js +4 -0
- package/dist/policies/memory-graph-write-policy.d.ts +1 -0
- package/dist/policies/memory-graph-write-policy.js +2 -0
- package/dist/search/unified-search.d.ts +6 -19
- package/dist/search/unified-search.js +749 -145
- package/dist/server/cli-http.js +1051 -155
- package/dist/server/cli-mcp.js +1185 -171
- package/dist/server/cli-shared.d.ts +5 -2
- package/dist/server/cli-shared.js +6 -4
- package/dist/storage/chroma-memory-index.d.ts +1 -0
- package/dist/storage/chroma-memory-index.js +1 -2
- package/dist/storage/postgres-raw-message-factory.d.ts +4 -1
- package/dist/storage/raw-message-store.d.ts +5 -2
- package/dist/storage/raw-message-store.js +6 -4
- package/dist/storage/sqlite-raw-message-store.d.ts +5 -1
- package/dist/storage/sqlite-raw-message-store.js +6 -4
- package/dist/storage/sqlite-vector-index.d.ts +4 -1
- package/dist/storage/sqlite-vector-index.js +4 -4
- package/dist/unified-search-Bft57TRH.d.ts +122 -0
- package/package.json +11 -7
package/dist/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
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import { getOpenContextPath } from '@melandlabs/env-config';
|
|
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
|
}
|
|
@@ -117,7 +120,7 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
117
120
|
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
118
121
|
if (dbPath && dbPath.length > 0) return dbPath;
|
|
119
122
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
120
|
-
return fromEnv && fromEnv.length > 0 ? fromEnv :
|
|
123
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : getOpenContextPath("memory", "store.db");
|
|
121
124
|
}
|
|
122
125
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
123
126
|
return true;
|
|
@@ -151,12 +154,11 @@ async function lexicalSearchRawMessages(input) {
|
|
|
151
154
|
}
|
|
152
155
|
return mgr.lexicalSearchMessages(input);
|
|
153
156
|
}
|
|
154
|
-
var manager
|
|
157
|
+
var manager;
|
|
155
158
|
var init_sqlite_raw_message_store = __esm({
|
|
156
159
|
"src/storage/sqlite-raw-message-store.ts"() {
|
|
157
160
|
init_chroma_memory_index();
|
|
158
161
|
manager = null;
|
|
159
|
-
DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
160
162
|
}
|
|
161
163
|
});
|
|
162
164
|
|
|
@@ -377,6 +379,416 @@ function isRawMemorySemanticResult(result) {
|
|
|
377
379
|
const item = result;
|
|
378
380
|
return typeof item.id === "string" && typeof item.content === "string" && typeof item.similarity === "number" && Boolean(item.metadata) && typeof item.metadata === "object";
|
|
379
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
|
+
}
|
|
380
792
|
|
|
381
793
|
// src/search/unified-search.ts
|
|
382
794
|
function deriveLexicalKeywords(query) {
|
|
@@ -448,7 +860,7 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
448
860
|
authToken: input.authToken
|
|
449
861
|
});
|
|
450
862
|
}
|
|
451
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
863
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers = []) {
|
|
452
864
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
453
865
|
let semantic = [];
|
|
454
866
|
if (isRawMessageChromaEnabled()) {
|
|
@@ -478,6 +890,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
478
890
|
}
|
|
479
891
|
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
480
892
|
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
893
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
481
894
|
semantic = (await Promise.all(
|
|
482
895
|
filters.map(
|
|
483
896
|
(filter) => searchRawMessagesAnn({
|
|
@@ -485,7 +898,9 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
485
898
|
queryEmbedding,
|
|
486
899
|
limit,
|
|
487
900
|
threshold,
|
|
488
|
-
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 } : {}
|
|
489
904
|
})
|
|
490
905
|
)
|
|
491
906
|
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
@@ -495,11 +910,14 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
495
910
|
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
496
911
|
const manager2 = await getRawMessageManager2();
|
|
497
912
|
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
913
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
498
914
|
const results = await manager2.searchMessagesSemantically({
|
|
499
915
|
userId: input.userId,
|
|
500
916
|
queryEmbedding,
|
|
501
917
|
limit,
|
|
502
|
-
threshold
|
|
918
|
+
threshold,
|
|
919
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
920
|
+
...factTypes ? { factTypes } : {}
|
|
503
921
|
});
|
|
504
922
|
semantic = results.map((r) => ({
|
|
505
923
|
type: "memory",
|
|
@@ -535,7 +953,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
535
953
|
metadata: hit.metadata
|
|
536
954
|
};
|
|
537
955
|
}
|
|
538
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
956
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers = []) {
|
|
539
957
|
if (keywords.length === 0) {
|
|
540
958
|
return [];
|
|
541
959
|
}
|
|
@@ -543,13 +961,16 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
543
961
|
try {
|
|
544
962
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
545
963
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
964
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
546
965
|
return (await Promise.all(
|
|
547
966
|
filters.map(
|
|
548
967
|
(botId) => searchRawMessagesLexical({
|
|
549
968
|
userId: input.userId,
|
|
550
969
|
keywords,
|
|
551
970
|
limit: Math.ceil(limit / filters.length),
|
|
552
|
-
botId
|
|
971
|
+
botId,
|
|
972
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
973
|
+
...factTypes ? { factTypes } : {}
|
|
553
974
|
})
|
|
554
975
|
)
|
|
555
976
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -563,13 +984,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
563
984
|
try {
|
|
564
985
|
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
565
986
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
987
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
566
988
|
return (await Promise.all(
|
|
567
989
|
filters.map(
|
|
568
990
|
(botId) => lexicalSearchRawMessages2({
|
|
569
991
|
userId: input.userId,
|
|
570
992
|
keywords,
|
|
571
993
|
limit: Math.ceil(limit / filters.length),
|
|
572
|
-
botId
|
|
994
|
+
botId,
|
|
995
|
+
...factTypes ? { factTypes } : {}
|
|
573
996
|
})
|
|
574
997
|
)
|
|
575
998
|
)).flat().filter(Boolean).map(
|
|
@@ -582,6 +1005,42 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
|
|
|
582
1005
|
}
|
|
583
1006
|
return [];
|
|
584
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
|
+
}
|
|
585
1044
|
function createUnifiedSearch(deps = {}) {
|
|
586
1045
|
const logger = deps.logger ?? console;
|
|
587
1046
|
function resolveReasoningStrategy(input) {
|
|
@@ -591,130 +1050,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
591
1050
|
}
|
|
592
1051
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
593
1052
|
}
|
|
594
|
-
async function
|
|
595
|
-
const query = input.query.trim();
|
|
596
|
-
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
597
|
-
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
598
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
599
|
-
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
600
|
-
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
601
|
-
const warnings = [];
|
|
602
|
-
if (!query) {
|
|
603
|
-
return {
|
|
604
|
-
query,
|
|
605
|
-
sources,
|
|
606
|
-
results: [],
|
|
607
|
-
count: 0,
|
|
608
|
-
warnings
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
let memorySubs;
|
|
612
|
-
const reasoningInfo = { strategy: reasoningStrategy };
|
|
613
|
-
if (input.dateFrom || input.dateTo) {
|
|
614
|
-
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
615
|
-
}
|
|
616
|
-
if (sources.includes("memory")) {
|
|
617
|
-
if (isRawMessageStorageAvailable()) {
|
|
618
|
-
try {
|
|
619
|
-
memorySubs = await runMemorySource(
|
|
620
|
-
input,
|
|
621
|
-
limit,
|
|
622
|
-
threshold,
|
|
623
|
-
warnings,
|
|
624
|
-
reasoningStrategy,
|
|
625
|
-
reasoningInfo
|
|
626
|
-
);
|
|
627
|
-
} catch (error) {
|
|
628
|
-
logger.warn?.("[memory-store] memory source failed:", error);
|
|
629
|
-
warnings.push({
|
|
630
|
-
source: "memory",
|
|
631
|
-
code: "memory_search_failed",
|
|
632
|
-
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
} else {
|
|
636
|
-
warnings.push({
|
|
637
|
-
source: "memory",
|
|
638
|
-
code: "raw_message_storage_unavailable",
|
|
639
|
-
message: "Raw memory storage is not available in this environment."
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
const insightHits = [];
|
|
644
|
-
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
645
|
-
try {
|
|
646
|
-
const hits = await deps.searchInsights({
|
|
647
|
-
userId: input.userId,
|
|
648
|
-
query: input.query,
|
|
649
|
-
limit,
|
|
650
|
-
threshold,
|
|
651
|
-
botIds: input.botIds,
|
|
652
|
-
includeArchived: input.includeArchivedInsights,
|
|
653
|
-
authToken: input.authToken
|
|
654
|
-
});
|
|
655
|
-
for (const hit of hits) {
|
|
656
|
-
insightHits.push({
|
|
657
|
-
type: "insight",
|
|
658
|
-
id: hit.id,
|
|
659
|
-
content: hit.content,
|
|
660
|
-
similarity: hit.similarity,
|
|
661
|
-
metadata: hit.metadata
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
} catch (error) {
|
|
665
|
-
logger.warn?.("[memory-store] insights source failed:", error);
|
|
666
|
-
warnings.push({
|
|
667
|
-
source: "insights",
|
|
668
|
-
code: "insights_search_failed",
|
|
669
|
-
message: error.message ?? "insights_search_failed"
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
} else if (sources.includes("insights")) ;
|
|
673
|
-
const knowledgeHits = [];
|
|
674
|
-
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
675
|
-
try {
|
|
676
|
-
const hits = await deps.searchKnowledge({
|
|
677
|
-
userId: input.userId,
|
|
678
|
-
query: input.query,
|
|
679
|
-
options: {
|
|
680
|
-
limit,
|
|
681
|
-
threshold,
|
|
682
|
-
documentIds: input.documentIds
|
|
683
|
-
},
|
|
684
|
-
authToken: input.authToken
|
|
685
|
-
});
|
|
686
|
-
for (const hit of hits) {
|
|
687
|
-
knowledgeHits.push(toKnowledgeResult(hit));
|
|
688
|
-
}
|
|
689
|
-
} catch (error) {
|
|
690
|
-
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
691
|
-
warnings.push({
|
|
692
|
-
source: "knowledge",
|
|
693
|
-
code: "knowledge_search_failed",
|
|
694
|
-
message: error.message ?? "knowledge_search_failed"
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
} else if (sources.includes("knowledge")) ;
|
|
698
|
-
const merged = mergeAcrossSources({
|
|
699
|
-
memorySubs,
|
|
700
|
-
insightHits,
|
|
701
|
-
knowledgeHits,
|
|
702
|
-
limit,
|
|
703
|
-
strategy: mergeStrategy
|
|
704
|
-
});
|
|
705
|
-
const output = {
|
|
706
|
-
query: input.query,
|
|
707
|
-
sources,
|
|
708
|
-
results: merged,
|
|
709
|
-
count: merged.length,
|
|
710
|
-
warnings
|
|
711
|
-
};
|
|
712
|
-
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
713
|
-
output.reasoning = reasoningInfo;
|
|
714
|
-
}
|
|
715
|
-
return output;
|
|
716
|
-
}
|
|
717
|
-
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
1053
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
|
|
718
1054
|
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
719
1055
|
warnings.push({
|
|
720
1056
|
source: "memory",
|
|
@@ -740,7 +1076,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
740
1076
|
executor: {
|
|
741
1077
|
search: async (request) => {
|
|
742
1078
|
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
743
|
-
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1079
|
+
const lexicalHits = await runLexicalSearchForKeywords(
|
|
1080
|
+
deps,
|
|
1081
|
+
input,
|
|
1082
|
+
keywords2,
|
|
1083
|
+
limit,
|
|
1084
|
+
logger,
|
|
1085
|
+
peerPeers
|
|
1086
|
+
);
|
|
744
1087
|
let semanticHits = [];
|
|
745
1088
|
if (typeof deps.embedQuery === "function") {
|
|
746
1089
|
try {
|
|
@@ -752,7 +1095,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
752
1095
|
queryEmbedding,
|
|
753
1096
|
limit,
|
|
754
1097
|
threshold,
|
|
755
|
-
logger
|
|
1098
|
+
logger,
|
|
1099
|
+
peerPeers
|
|
756
1100
|
);
|
|
757
1101
|
} catch (error) {
|
|
758
1102
|
logger.warn?.(
|
|
@@ -797,7 +1141,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
797
1141
|
});
|
|
798
1142
|
const keywords2 = deriveLexicalKeywords(input.query);
|
|
799
1143
|
const lexical2 = filterByDateRange(
|
|
800
|
-
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
1144
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger, peerPeers),
|
|
801
1145
|
input.dateFrom,
|
|
802
1146
|
input.dateTo
|
|
803
1147
|
);
|
|
@@ -823,7 +1167,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
823
1167
|
);
|
|
824
1168
|
const lists = await Promise.all(
|
|
825
1169
|
embeddings.map(
|
|
826
|
-
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
1170
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger, peerPeers)
|
|
827
1171
|
)
|
|
828
1172
|
);
|
|
829
1173
|
semantic = mergeByMaxScore(lists);
|
|
@@ -839,7 +1183,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
839
1183
|
}
|
|
840
1184
|
if (semantic.length === 0) {
|
|
841
1185
|
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
842
|
-
semantic = await runSemanticSearchForEmbedding(
|
|
1186
|
+
semantic = await runSemanticSearchForEmbedding(
|
|
1187
|
+
deps,
|
|
1188
|
+
input,
|
|
1189
|
+
queryEmbedding,
|
|
1190
|
+
limit,
|
|
1191
|
+
threshold,
|
|
1192
|
+
logger,
|
|
1193
|
+
peerPeers
|
|
1194
|
+
);
|
|
843
1195
|
}
|
|
844
1196
|
let lexical = [];
|
|
845
1197
|
const keywords = deriveLexicalKeywords(input.query);
|
|
@@ -848,13 +1200,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
848
1200
|
try {
|
|
849
1201
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
850
1202
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
1203
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
851
1204
|
lexical = (await Promise.all(
|
|
852
1205
|
lexFilters.map(
|
|
853
1206
|
(botId) => searchRawMessagesLexical({
|
|
854
1207
|
userId: input.userId,
|
|
855
1208
|
keywords,
|
|
856
1209
|
limit,
|
|
857
|
-
botId
|
|
1210
|
+
botId,
|
|
1211
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1212
|
+
...factTypes ? { factTypes } : {}
|
|
858
1213
|
})
|
|
859
1214
|
)
|
|
860
1215
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -882,20 +1237,272 @@ function createUnifiedSearch(deps = {}) {
|
|
|
882
1237
|
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
883
1238
|
};
|
|
884
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
|
+
}
|
|
885
1377
|
async function searchRawMemorySemantically(input) {
|
|
886
1378
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
887
1379
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
888
1380
|
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1381
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1382
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1383
|
+
);
|
|
889
1384
|
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
890
1385
|
return mergeAcrossSources({
|
|
891
1386
|
memorySubs: sub,
|
|
892
1387
|
insightHits: [],
|
|
893
1388
|
knowledgeHits: [],
|
|
894
1389
|
limit,
|
|
895
|
-
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
|
|
896
1495
|
});
|
|
1496
|
+
base.answer = answer;
|
|
1497
|
+
base.warnings = [...base.warnings, ...synthWarnings];
|
|
1498
|
+
return base;
|
|
897
1499
|
}
|
|
898
|
-
return {
|
|
1500
|
+
return {
|
|
1501
|
+
search,
|
|
1502
|
+
searchUnifiedMemory,
|
|
1503
|
+
searchRawMemorySemantically,
|
|
1504
|
+
consolidate: (input) => applyReflectedPlan(deps, {}, input)
|
|
1505
|
+
};
|
|
899
1506
|
}
|
|
900
1507
|
function mergeAcrossSources(input) {
|
|
901
1508
|
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
@@ -926,11 +1533,185 @@ function mergeAcrossSources(input) {
|
|
|
926
1533
|
}
|
|
927
1534
|
return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
|
|
928
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
|
+
}
|
|
929
1692
|
|
|
930
1693
|
// src/index.ts
|
|
931
1694
|
init_raw_message_store();
|
|
1695
|
+
init_sqlite_raw_message_store();
|
|
932
1696
|
init_raw_message_store();
|
|
933
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
|
+
}
|
|
934
1715
|
|
|
935
1716
|
// src/search/query-rewriter.ts
|
|
936
1717
|
var SYSTEM_PROMPT = `You are a memory retrieval assistant.
|
|
@@ -1286,19 +2067,173 @@ function createIdentityIterativePlanner() {
|
|
|
1286
2067
|
};
|
|
1287
2068
|
}
|
|
1288
2069
|
|
|
1289
|
-
// 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
|
+
}
|
|
1290
2194
|
async function createMemoryStore(config = {}) {
|
|
1291
2195
|
const dbPath = config.dbPath ?? (config.db && "path" in config.db ? config.db.path : void 0);
|
|
1292
2196
|
const raw = createRawMessageStore({ env: config.env, dbPath });
|
|
1293
|
-
const search = createUnifiedSearch(config.unified);
|
|
2197
|
+
const search = createUnifiedSearch({ ...config.unified, logger: config.logger });
|
|
1294
2198
|
configureRawMessageStore(config);
|
|
1295
|
-
|
|
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 = {
|
|
1296
2214
|
raw,
|
|
1297
|
-
search
|
|
2215
|
+
// Flat search verb — `search({ synthesize: true })` opts into the
|
|
2216
|
+
// read-only LLM synthesis path.
|
|
2217
|
+
search: search.search,
|
|
2218
|
+
vsa,
|
|
1298
2219
|
getRawMessageManager,
|
|
1299
2220
|
searchUnifiedMemory: search.searchUnifiedMemory,
|
|
1300
|
-
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
|
+
}
|
|
1301
2235
|
};
|
|
2236
|
+
return store;
|
|
1302
2237
|
}
|
|
1303
2238
|
|
|
1304
|
-
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 };
|