@melandlabs/memory-store 1.3.8 → 1.4.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/dist/{config-9RzSCdXG.d.ts → config-Cnk2b7fH.d.ts} +25 -4
- package/dist/{evidence-selector-uOHsxwch.d.ts → evidence-selector-C_ZUQsio.d.ts} +34 -5
- package/dist/http.d.ts +2 -2
- package/dist/http.js +356 -112
- package/dist/index.d.ts +6 -6
- package/dist/index.js +349 -111
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +352 -114
- package/dist/search/derive.d.ts +2 -2
- package/dist/search/derive.js +1 -1
- package/dist/search/distill.d.ts +2 -2
- package/dist/search/evidence-selector.d.ts +2 -1
- package/dist/search/unified-search.d.ts +3 -3
- package/dist/search/unified-search.js +349 -111
- package/dist/server/cli-http.js +356 -112
- package/dist/server/cli-mcp.js +352 -114
- package/dist/server/cli-shared.d.ts +2 -2
- package/dist/storage/postgres-raw-message-factory.d.ts +2 -2
- package/dist/storage/raw-message-store.d.ts +2 -2
- package/dist/storage/sqlite-raw-message-store.d.ts +2 -2
- package/dist/storage/sqlite-vector-index.d.ts +2 -2
- package/dist/{unified-search-BBC3jkPJ.d.ts → unified-search-DfAYG3S9.d.ts} +3 -3
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -427,16 +427,21 @@ function deriveLexicalKeywords(query) {
|
|
|
427
427
|
}
|
|
428
428
|
var DEFAULT_LIMIT = 10;
|
|
429
429
|
var DEFAULT_THRESHOLD = 0.7;
|
|
430
|
-
var DEFAULT_SOURCES = ["memory", "insights", "knowledge"];
|
|
430
|
+
var DEFAULT_SOURCES = ["memory", "insights", "knowledge", "workspace"];
|
|
431
431
|
var SOURCE_SET = new Set(DEFAULT_SOURCES);
|
|
432
432
|
function normalizeUnifiedMemorySearchSources(sources) {
|
|
433
|
-
if (
|
|
433
|
+
if (sources === void 0) {
|
|
434
434
|
return [...DEFAULT_SOURCES];
|
|
435
435
|
}
|
|
436
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
437
|
+
throw new TypeError("Search sources must be a non-empty array when specified");
|
|
438
|
+
}
|
|
436
439
|
const normalized = sources.filter((source) => typeof source === "string").map((source) => source.trim().toLowerCase()).filter(
|
|
437
440
|
(source) => SOURCE_SET.has(source)
|
|
438
441
|
);
|
|
439
|
-
|
|
442
|
+
if (normalized.length !== sources.length)
|
|
443
|
+
throw new TypeError("Search sources contain an unsupported source");
|
|
444
|
+
return Array.from(new Set(normalized));
|
|
440
445
|
}
|
|
441
446
|
function clampUnifiedMemorySearchLimit(limit) {
|
|
442
447
|
const parsed = typeof limit === "number" ? limit : Number(limit ?? DEFAULT_LIMIT);
|
|
@@ -478,6 +483,20 @@ function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none"
|
|
|
478
483
|
return defaultStrategy;
|
|
479
484
|
}
|
|
480
485
|
var DEFAULT_RRF_K = 60;
|
|
486
|
+
function mergeSearchResultProvenance(preferred, other) {
|
|
487
|
+
const first = preferred.provenance;
|
|
488
|
+
const second = other.provenance;
|
|
489
|
+
const firstVersion = first?.version ?? first?.citation?.version_id;
|
|
490
|
+
const secondVersion = second?.version ?? second?.citation?.version_id;
|
|
491
|
+
const firstHash = first?.contentHash ?? first?.citation?.content_hash;
|
|
492
|
+
const secondHash = second?.contentHash ?? second?.citation?.content_hash;
|
|
493
|
+
if (firstVersion != null && secondVersion != null && firstVersion !== secondVersion || firstHash != null && secondHash != null && firstHash !== secondHash || first?.citation && second?.citation && first.citation.id !== second.citation.id || preferred.content !== other.content && !(firstHash && firstHash === secondHash))
|
|
494
|
+
return preferred;
|
|
495
|
+
const metadata = { ...other.metadata, ...preferred.metadata };
|
|
496
|
+
if (!second) return { ...preferred, metadata };
|
|
497
|
+
const knownFirst = Object.fromEntries(Object.entries(first ?? {}).filter(([, value]) => value != null));
|
|
498
|
+
return { ...preferred, metadata, provenance: { ...second, ...knownFirst } };
|
|
499
|
+
}
|
|
481
500
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, weights = {}) {
|
|
482
501
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
483
502
|
return [];
|
|
@@ -491,6 +510,7 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
491
510
|
const scores = /* @__PURE__ */ new Map();
|
|
492
511
|
const order = [];
|
|
493
512
|
for (const list of lists) {
|
|
513
|
+
const seenInList = /* @__PURE__ */ new Set();
|
|
494
514
|
const channel = listNameToChannel(list.name);
|
|
495
515
|
const configuredWeight = channel ? effectiveWeights[channel] : void 0;
|
|
496
516
|
const weight = typeof configuredWeight === "number" && Number.isFinite(configuredWeight) && configuredWeight >= 0 ? configuredWeight : 1;
|
|
@@ -498,7 +518,8 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
498
518
|
for (let index = 0; index < list.hits.length; index += 1) {
|
|
499
519
|
const hit = list.hits[index];
|
|
500
520
|
const key = `${hit.type}::${hit.id}`;
|
|
501
|
-
const contribution = weight / (safeK +
|
|
521
|
+
const contribution = seenInList.has(key) ? 0 : weight / (safeK + seenInList.size + 1);
|
|
522
|
+
seenInList.add(key);
|
|
502
523
|
const existing = scores.get(key);
|
|
503
524
|
const rankedHit = hit.type === "memory" ? withMatchedEvidence(hit, list.name, index + 1) : hit;
|
|
504
525
|
if (existing) {
|
|
@@ -506,6 +527,7 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
506
527
|
if (hit.type === "memory" && existing.hit.type === "memory") {
|
|
507
528
|
existing.hit = mergeMatchedEvidence(existing.hit, rankedHit);
|
|
508
529
|
}
|
|
530
|
+
existing.hit = mergeSearchResultProvenance(existing.hit, rankedHit);
|
|
509
531
|
} else {
|
|
510
532
|
scores.set(key, { hit: rankedHit, rrf: contribution });
|
|
511
533
|
order.push(key);
|
|
@@ -708,7 +730,8 @@ async function applyReflectedPlan(deps, store, input, _logger) {
|
|
|
708
730
|
source: item.source ?? "raw",
|
|
709
731
|
snippet: item.snippet,
|
|
710
732
|
score: item.score,
|
|
711
|
-
timestamp: item.timestamp
|
|
733
|
+
timestamp: item.timestamp,
|
|
734
|
+
provenance: item.provenance
|
|
712
735
|
}));
|
|
713
736
|
const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
|
|
714
737
|
const applyInput = {
|
|
@@ -770,6 +793,46 @@ function makePlanSummary(plan) {
|
|
|
770
793
|
return lines.join("\n");
|
|
771
794
|
}
|
|
772
795
|
|
|
796
|
+
// src/search/execution-budget.ts
|
|
797
|
+
function boundedSearchOperation(operation, budget) {
|
|
798
|
+
const timeout = budget.sourceTimeoutMs ?? 5e3;
|
|
799
|
+
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
800
|
+
throw new TypeError("sourceTimeoutMs must be positive and finite");
|
|
801
|
+
return (...args) => new Promise((resolve, reject) => {
|
|
802
|
+
const signal = budget.signal;
|
|
803
|
+
if (signal?.aborted) {
|
|
804
|
+
reject(signal.reason);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const cleanup2 = () => {
|
|
808
|
+
clearTimeout(timer);
|
|
809
|
+
signal?.removeEventListener("abort", onAbort);
|
|
810
|
+
};
|
|
811
|
+
const onAbort = () => {
|
|
812
|
+
cleanup2();
|
|
813
|
+
reject(signal?.reason);
|
|
814
|
+
};
|
|
815
|
+
const timer = setTimeout(() => {
|
|
816
|
+
cleanup2();
|
|
817
|
+
reject(new Error(`Search operation exceeded ${timeout}ms waiting budget`));
|
|
818
|
+
}, timeout);
|
|
819
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
820
|
+
Promise.resolve().then(() => {
|
|
821
|
+
signal?.throwIfAborted();
|
|
822
|
+
return operation(...args);
|
|
823
|
+
}).then(
|
|
824
|
+
(value) => {
|
|
825
|
+
cleanup2();
|
|
826
|
+
resolve(value);
|
|
827
|
+
},
|
|
828
|
+
(error) => {
|
|
829
|
+
cleanup2();
|
|
830
|
+
reject(error);
|
|
831
|
+
}
|
|
832
|
+
);
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
773
836
|
// src/search/reranker.ts
|
|
774
837
|
async function applyReranker(reranker, query, results) {
|
|
775
838
|
if (!reranker || results.length === 0) {
|
|
@@ -871,14 +934,15 @@ function buildSynthesisPrompt(input) {
|
|
|
871
934
|
grouped.set(item.source, bucket);
|
|
872
935
|
}
|
|
873
936
|
const sections = [];
|
|
874
|
-
for (const tier of DEFAULT_TIERS2) {
|
|
937
|
+
for (const tier of [...DEFAULT_TIERS2, "workspace"]) {
|
|
875
938
|
const items = grouped.get(tier) ?? [];
|
|
876
939
|
if (items.length === 0) {
|
|
877
940
|
continue;
|
|
878
941
|
}
|
|
879
942
|
const lines = items.map((item, index) => {
|
|
880
943
|
const factSource = typeof item.metadata?.source === "string" && item.metadata.source.length > 0 ? item.metadata.source : item.source ?? "unknown";
|
|
881
|
-
|
|
944
|
+
const provenance = item.provenance ? ` provenance=${JSON.stringify(item.provenance)}` : "";
|
|
945
|
+
return ` [${index + 1}] (${item.id}, source=${factSource}, score=${item.score.toFixed(4)}) ${item.snippet}${provenance}`;
|
|
882
946
|
});
|
|
883
947
|
sections.push(`## ${tier}
|
|
884
948
|
${lines.join("\n")}`);
|
|
@@ -969,7 +1033,8 @@ async function synthesizeAnswer(input) {
|
|
|
969
1033
|
score: item.score,
|
|
970
1034
|
// Forward per-fact provenance (e.g. `metadata.source` from
|
|
971
1035
|
// `opencontext add --source`) so the synthesis prompt can cite it.
|
|
972
|
-
metadata: item.metadata
|
|
1036
|
+
metadata: item.metadata,
|
|
1037
|
+
provenance: item.provenance
|
|
973
1038
|
}));
|
|
974
1039
|
const warnings = [];
|
|
975
1040
|
const complete = deps.reasoning?.complete;
|
|
@@ -1190,7 +1255,7 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
1190
1255
|
authToken: input.authToken
|
|
1191
1256
|
});
|
|
1192
1257
|
}
|
|
1193
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext) {
|
|
1258
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext, execution) {
|
|
1194
1259
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
1195
1260
|
let semantic = [];
|
|
1196
1261
|
if (typeof deps.searchRawMessagesAnn === "function") {
|
|
@@ -1218,7 +1283,10 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1218
1283
|
semantic = (await Promise.all(
|
|
1219
1284
|
filters.map((filter) => {
|
|
1220
1285
|
const botId = "botId" in filter ? filter.botId : void 0;
|
|
1221
|
-
return
|
|
1286
|
+
return boundedSearchOperation(
|
|
1287
|
+
searchRawMessagesWithChroma,
|
|
1288
|
+
deps
|
|
1289
|
+
)({
|
|
1222
1290
|
userId: input.userId,
|
|
1223
1291
|
queryEmbedding,
|
|
1224
1292
|
limit,
|
|
@@ -1227,19 +1295,21 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1227
1295
|
});
|
|
1228
1296
|
})
|
|
1229
1297
|
)).flat().map(toMemoryResult);
|
|
1298
|
+
if (execution) execution.completed++;
|
|
1230
1299
|
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
1231
1300
|
backend: "chroma",
|
|
1232
1301
|
dimensions: queryEmbedding.length,
|
|
1233
1302
|
count: semantic.length
|
|
1234
1303
|
});
|
|
1235
1304
|
} catch (error) {
|
|
1305
|
+
if (execution) execution.failed++;
|
|
1236
1306
|
logger.warn?.(
|
|
1237
1307
|
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
1238
1308
|
error
|
|
1239
1309
|
);
|
|
1240
1310
|
}
|
|
1241
1311
|
}
|
|
1242
|
-
if (semantic.length === 0 && runtimeContext !== void 0) {
|
|
1312
|
+
if (semantic.length === 0 && runtimeContext !== void 0 && !deps.searchRawMessagesAnn) {
|
|
1243
1313
|
warnApplicabilityNotEnforced(warnings);
|
|
1244
1314
|
return [];
|
|
1245
1315
|
}
|
|
@@ -1249,7 +1319,10 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1249
1319
|
const manager2 = await getRawMessageManager2();
|
|
1250
1320
|
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
1251
1321
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1252
|
-
const results = await
|
|
1322
|
+
const results = await boundedSearchOperation(
|
|
1323
|
+
manager2.searchMessagesSemantically.bind(manager2),
|
|
1324
|
+
deps
|
|
1325
|
+
)({
|
|
1253
1326
|
userId: input.userId,
|
|
1254
1327
|
queryEmbedding,
|
|
1255
1328
|
limit,
|
|
@@ -1259,6 +1332,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1259
1332
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1260
1333
|
...factTypes ? { factTypes } : {}
|
|
1261
1334
|
});
|
|
1335
|
+
if (execution) execution.completed++;
|
|
1262
1336
|
semantic = results.map((r) => ({
|
|
1263
1337
|
type: "memory",
|
|
1264
1338
|
id: r.id,
|
|
@@ -1268,6 +1342,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1268
1342
|
}));
|
|
1269
1343
|
}
|
|
1270
1344
|
} catch (error) {
|
|
1345
|
+
if (execution) execution.failed++;
|
|
1271
1346
|
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
1272
1347
|
}
|
|
1273
1348
|
}
|
|
@@ -1318,7 +1393,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
1318
1393
|
metadata: hit.metadata
|
|
1319
1394
|
};
|
|
1320
1395
|
}
|
|
1321
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext) {
|
|
1396
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext, execution) {
|
|
1322
1397
|
if (keywords.length === 0) {
|
|
1323
1398
|
return [];
|
|
1324
1399
|
}
|
|
@@ -1368,9 +1443,12 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1368
1443
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1369
1444
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1370
1445
|
const includeDeprecated = input.includeDeprecated === true;
|
|
1371
|
-
|
|
1446
|
+
const results = (await Promise.all(
|
|
1372
1447
|
filters.map(
|
|
1373
|
-
(botId) =>
|
|
1448
|
+
(botId) => boundedSearchOperation(
|
|
1449
|
+
lexicalSearchRawMessages2,
|
|
1450
|
+
deps
|
|
1451
|
+
)({
|
|
1374
1452
|
userId: input.userId,
|
|
1375
1453
|
keywords,
|
|
1376
1454
|
limit: Math.ceil(limit / filters.length),
|
|
@@ -1385,7 +1463,10 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1385
1463
|
r
|
|
1386
1464
|
)
|
|
1387
1465
|
);
|
|
1466
|
+
if (execution) execution.completed++;
|
|
1467
|
+
return results;
|
|
1388
1468
|
} catch (error) {
|
|
1469
|
+
if (execution) execution.failed++;
|
|
1389
1470
|
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
1390
1471
|
}
|
|
1391
1472
|
return [];
|
|
@@ -1432,7 +1513,21 @@ function mapHitToEvidenceSource(hit) {
|
|
|
1432
1513
|
}
|
|
1433
1514
|
return hit.type;
|
|
1434
1515
|
}
|
|
1435
|
-
function createUnifiedSearch(
|
|
1516
|
+
function createUnifiedSearch(inputDeps = {}) {
|
|
1517
|
+
const deps = {
|
|
1518
|
+
...inputDeps,
|
|
1519
|
+
embedQuery: inputDeps.embedQuery && boundedSearchOperation(inputDeps.embedQuery, inputDeps),
|
|
1520
|
+
searchRawMessagesAnn: inputDeps.searchRawMessagesAnn && boundedSearchOperation(inputDeps.searchRawMessagesAnn, inputDeps),
|
|
1521
|
+
searchRawMessagesHybrid: inputDeps.searchRawMessagesHybrid && boundedSearchOperation(inputDeps.searchRawMessagesHybrid, inputDeps),
|
|
1522
|
+
searchRawMessagesLexical: inputDeps.searchRawMessagesLexical && boundedSearchOperation(inputDeps.searchRawMessagesLexical, inputDeps),
|
|
1523
|
+
searchWorkspace: inputDeps.searchWorkspace && boundedSearchOperation(inputDeps.searchWorkspace, inputDeps),
|
|
1524
|
+
searchInsights: inputDeps.searchInsights && boundedSearchOperation(inputDeps.searchInsights, inputDeps),
|
|
1525
|
+
searchKnowledge: inputDeps.searchKnowledge && boundedSearchOperation(inputDeps.searchKnowledge, inputDeps),
|
|
1526
|
+
searchSummaries: inputDeps.searchSummaries && boundedSearchOperation(inputDeps.searchSummaries, inputDeps),
|
|
1527
|
+
getRawMessageRetrievalStatus: inputDeps.getRawMessageRetrievalStatus && boundedSearchOperation(inputDeps.getRawMessageRetrievalStatus, inputDeps),
|
|
1528
|
+
entitySearch: inputDeps.entitySearch && boundedSearchOperation(inputDeps.entitySearch, inputDeps),
|
|
1529
|
+
searchRawMessageNeighbors: inputDeps.searchRawMessageNeighbors && boundedSearchOperation(inputDeps.searchRawMessageNeighbors, inputDeps)
|
|
1530
|
+
};
|
|
1436
1531
|
const logger = deps.logger ?? console;
|
|
1437
1532
|
function resolveReasoningStrategy(input) {
|
|
1438
1533
|
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
@@ -1442,6 +1537,25 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1442
1537
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
1443
1538
|
}
|
|
1444
1539
|
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = [], runtimeContext) {
|
|
1540
|
+
const execution = { completed: 0, failed: 0 };
|
|
1541
|
+
const observe = (operation, preparation = false) => operation && (async (...args) => {
|
|
1542
|
+
try {
|
|
1543
|
+
const result = await operation(...args);
|
|
1544
|
+
if (!preparation) execution.completed++;
|
|
1545
|
+
return result;
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
execution.failed++;
|
|
1548
|
+
throw error;
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
const sourceDeps = {
|
|
1552
|
+
...deps,
|
|
1553
|
+
embedQuery: observe(deps.embedQuery, true),
|
|
1554
|
+
searchRawMessagesAnn: observe(deps.searchRawMessagesAnn),
|
|
1555
|
+
searchRawMessagesHybrid: observe(deps.searchRawMessagesHybrid),
|
|
1556
|
+
searchRawMessagesLexical: observe(deps.searchRawMessagesLexical),
|
|
1557
|
+
entitySearch: observe(deps.entitySearch)
|
|
1558
|
+
};
|
|
1445
1559
|
const sourceStartedAt = monotonicNow();
|
|
1446
1560
|
const timings = {
|
|
1447
1561
|
semanticMs: 0,
|
|
@@ -1459,9 +1573,10 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1459
1573
|
};
|
|
1460
1574
|
const finish = (results) => ({
|
|
1461
1575
|
results,
|
|
1462
|
-
timings: { ...timings, memorySourceMs: monotonicNow() - sourceStartedAt }
|
|
1576
|
+
timings: { ...timings, memorySourceMs: monotonicNow() - sourceStartedAt },
|
|
1577
|
+
availability: execution.failed > 0 ? execution.completed > 0 ? "partial" : "failed" : execution.completed > 0 || Object.values(results).some((hits) => hits.length > 0) ? "complete" : "not_ready"
|
|
1463
1578
|
});
|
|
1464
|
-
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !
|
|
1579
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !sourceDeps.reasoning?.iterativePlanner) {
|
|
1465
1580
|
warnings.push({
|
|
1466
1581
|
source: "memory",
|
|
1467
1582
|
code: "memory_iterative_planner_not_configured",
|
|
@@ -1469,7 +1584,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1469
1584
|
});
|
|
1470
1585
|
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
1471
1586
|
}
|
|
1472
|
-
if (reasoningStrategy === "rewrite" && !
|
|
1587
|
+
if (reasoningStrategy === "rewrite" && !sourceDeps.reasoning?.queryRewriter) {
|
|
1473
1588
|
warnings.push({
|
|
1474
1589
|
source: "memory",
|
|
1475
1590
|
code: "memory_query_rewrite_not_configured",
|
|
@@ -1478,8 +1593,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1478
1593
|
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
1479
1594
|
}
|
|
1480
1595
|
let unionEvidence = null;
|
|
1481
|
-
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") &&
|
|
1482
|
-
const planner =
|
|
1596
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && sourceDeps.reasoning?.iterativePlanner) {
|
|
1597
|
+
const planner = sourceDeps.reasoning.iterativePlanner;
|
|
1483
1598
|
const result = await planner.plan({
|
|
1484
1599
|
query: input.query,
|
|
1485
1600
|
...input.includeRetrievalDiagnostics ? { options: { collectDiagnostics: true } } : {},
|
|
@@ -1491,26 +1606,27 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1491
1606
|
const lexicalHits = await measure(
|
|
1492
1607
|
"lexicalMs",
|
|
1493
1608
|
() => runLexicalSearchForKeywords(
|
|
1494
|
-
|
|
1609
|
+
sourceDeps,
|
|
1495
1610
|
input,
|
|
1496
1611
|
keywords2,
|
|
1497
1612
|
limit,
|
|
1498
1613
|
logger,
|
|
1499
1614
|
peerPeers,
|
|
1500
1615
|
warnings,
|
|
1501
|
-
runtimeContext
|
|
1616
|
+
runtimeContext,
|
|
1617
|
+
execution
|
|
1502
1618
|
)
|
|
1503
1619
|
);
|
|
1504
1620
|
let semanticHits = [];
|
|
1505
|
-
if (typeof
|
|
1621
|
+
if (typeof sourceDeps.embedQuery === "function") {
|
|
1506
1622
|
try {
|
|
1507
1623
|
semanticHits = await measure("semanticMs", async () => {
|
|
1508
1624
|
const semanticQuery = keywords2.join(" ");
|
|
1509
|
-
const queryEmbedder =
|
|
1625
|
+
const queryEmbedder = sourceDeps.embedQuery;
|
|
1510
1626
|
if (typeof queryEmbedder !== "function") return [];
|
|
1511
1627
|
const queryEmbedding = await embedQueryVariant(queryEmbedder, input, semanticQuery);
|
|
1512
1628
|
return runSemanticSearchForEmbedding(
|
|
1513
|
-
|
|
1629
|
+
sourceDeps,
|
|
1514
1630
|
input,
|
|
1515
1631
|
queryEmbedding,
|
|
1516
1632
|
limit,
|
|
@@ -1518,7 +1634,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1518
1634
|
logger,
|
|
1519
1635
|
peerPeers,
|
|
1520
1636
|
warnings,
|
|
1521
|
-
runtimeContext
|
|
1637
|
+
runtimeContext,
|
|
1638
|
+
execution
|
|
1522
1639
|
);
|
|
1523
1640
|
});
|
|
1524
1641
|
} catch (error) {
|
|
@@ -1571,66 +1688,74 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1571
1688
|
}
|
|
1572
1689
|
unionEvidence = evidence;
|
|
1573
1690
|
}
|
|
1574
|
-
|
|
1691
|
+
const lexicalFallback = async () => {
|
|
1692
|
+
const lexical2 = await runLexicalSearchForKeywords(
|
|
1693
|
+
sourceDeps,
|
|
1694
|
+
input,
|
|
1695
|
+
deriveLexicalKeywords(input.query),
|
|
1696
|
+
limit,
|
|
1697
|
+
logger,
|
|
1698
|
+
peerPeers,
|
|
1699
|
+
warnings,
|
|
1700
|
+
runtimeContext,
|
|
1701
|
+
execution
|
|
1702
|
+
);
|
|
1703
|
+
return finish({ semantic: [], lexical: filterByDateRange(lexical2, input.dateFrom, input.dateTo) });
|
|
1704
|
+
};
|
|
1705
|
+
if (typeof sourceDeps.embedQuery !== "function") {
|
|
1575
1706
|
warnings.push({
|
|
1576
1707
|
source: "memory",
|
|
1577
1708
|
code: "memory_lexical_search_fallback",
|
|
1578
1709
|
message: "No embedding provider is configured; using lexical (BM25) search as fallback. Pass an `embedQuery` function via `unified.embedQuery` to enable semantic retrieval."
|
|
1579
1710
|
});
|
|
1580
|
-
|
|
1581
|
-
const lexical2 = filterByDateRange(
|
|
1582
|
-
await runLexicalSearchForKeywords(
|
|
1583
|
-
deps,
|
|
1584
|
-
input,
|
|
1585
|
-
keywords2,
|
|
1586
|
-
limit,
|
|
1587
|
-
logger,
|
|
1588
|
-
peerPeers,
|
|
1589
|
-
warnings,
|
|
1590
|
-
runtimeContext
|
|
1591
|
-
),
|
|
1592
|
-
input.dateFrom,
|
|
1593
|
-
input.dateTo
|
|
1594
|
-
);
|
|
1595
|
-
return finish({ semantic: [], lexical: lexical2 });
|
|
1711
|
+
return lexicalFallback();
|
|
1596
1712
|
}
|
|
1597
1713
|
let semantic = [];
|
|
1598
1714
|
let lexicalQueries = [input.query];
|
|
1599
|
-
const embedQuery =
|
|
1715
|
+
const embedQuery = sourceDeps.embedQuery;
|
|
1600
1716
|
const activeMergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1601
|
-
input.mergeStrategy ??
|
|
1717
|
+
input.mergeStrategy ?? sourceDeps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1602
1718
|
);
|
|
1603
|
-
if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof
|
|
1604
|
-
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1719
|
+
if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof sourceDeps.searchRawMessagesHybrid === "function") {
|
|
1720
|
+
try {
|
|
1721
|
+
const hybrid = await measure("hybridMs", async () => {
|
|
1722
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
1723
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1724
|
+
return (await Promise.all(
|
|
1725
|
+
filters.map(
|
|
1726
|
+
(botId) => sourceDeps.searchRawMessagesHybrid?.({
|
|
1727
|
+
userId: input.userId,
|
|
1728
|
+
query: input.query,
|
|
1729
|
+
queryEmbedding,
|
|
1730
|
+
limit: Math.ceil(limit / filters.length),
|
|
1731
|
+
botId,
|
|
1732
|
+
...runtimeContext ?? {}
|
|
1733
|
+
})
|
|
1734
|
+
)
|
|
1735
|
+
)).flatMap((items) => items ?? []).filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
1736
|
+
});
|
|
1737
|
+
return finish({
|
|
1738
|
+
semantic: [],
|
|
1739
|
+
lexical: [],
|
|
1740
|
+
hybrid: filterByDateRange(hybrid, input.dateFrom, input.dateTo)
|
|
1741
|
+
});
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
warnings.push({
|
|
1744
|
+
source: "memory",
|
|
1745
|
+
code: "memory_hybrid_search_failed",
|
|
1746
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1747
|
+
});
|
|
1748
|
+
return lexicalFallback();
|
|
1749
|
+
}
|
|
1625
1750
|
}
|
|
1626
|
-
if (reasoningStrategy === "rewrite" &&
|
|
1751
|
+
if (reasoningStrategy === "rewrite" && sourceDeps.reasoning?.queryRewriter) {
|
|
1627
1752
|
try {
|
|
1628
|
-
const variants = await
|
|
1753
|
+
const variants = await sourceDeps.reasoning.queryRewriter.rewrite({
|
|
1629
1754
|
query: input.query,
|
|
1630
1755
|
userId: input.userId,
|
|
1631
1756
|
authToken: input.authToken
|
|
1632
1757
|
});
|
|
1633
|
-
if (
|
|
1758
|
+
if (sourceDeps.reasoning.rewriteLexical === true) {
|
|
1634
1759
|
const seen = /* @__PURE__ */ new Set();
|
|
1635
1760
|
lexicalQueries = [input.query, ...variants].map((query) => query.trim()).filter((query) => {
|
|
1636
1761
|
const key = query.toLowerCase();
|
|
@@ -1641,7 +1766,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1641
1766
|
}
|
|
1642
1767
|
if (reasoningInfo) {
|
|
1643
1768
|
reasoningInfo.rewrittenQueries = variants;
|
|
1644
|
-
if (
|
|
1769
|
+
if (sourceDeps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
1645
1770
|
reasoningInfo.degraded = true;
|
|
1646
1771
|
}
|
|
1647
1772
|
}
|
|
@@ -1652,7 +1777,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1652
1777
|
return Promise.all(
|
|
1653
1778
|
embeddings.map(
|
|
1654
1779
|
(embedding) => runSemanticSearchForEmbedding(
|
|
1655
|
-
|
|
1780
|
+
sourceDeps,
|
|
1656
1781
|
input,
|
|
1657
1782
|
embedding,
|
|
1658
1783
|
limit,
|
|
@@ -1660,12 +1785,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1660
1785
|
logger,
|
|
1661
1786
|
peerPeers,
|
|
1662
1787
|
warnings,
|
|
1663
|
-
runtimeContext
|
|
1788
|
+
runtimeContext,
|
|
1789
|
+
execution
|
|
1664
1790
|
)
|
|
1665
1791
|
)
|
|
1666
1792
|
);
|
|
1667
1793
|
});
|
|
1668
|
-
if (activeMergeStrategy === "rrf" &&
|
|
1794
|
+
if (activeMergeStrategy === "rrf" && sourceDeps.reasoning.rewriteSemanticMerge === "rrf" && lists.length > 1 && lists.some((hits) => hits.length > 0)) {
|
|
1669
1795
|
semantic = mergeUnifiedMemorySearchResultsRrf(
|
|
1670
1796
|
lists.map((hits, index) => ({
|
|
1671
1797
|
name: `memory-semantic-query:${index}`,
|
|
@@ -1688,24 +1814,33 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1688
1814
|
}
|
|
1689
1815
|
}
|
|
1690
1816
|
if (semantic.length === 0) {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1817
|
+
try {
|
|
1818
|
+
semantic = await measure("semanticMs", async () => {
|
|
1819
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
1820
|
+
return runSemanticSearchForEmbedding(
|
|
1821
|
+
sourceDeps,
|
|
1822
|
+
input,
|
|
1823
|
+
queryEmbedding,
|
|
1824
|
+
limit,
|
|
1825
|
+
threshold,
|
|
1826
|
+
logger,
|
|
1827
|
+
peerPeers,
|
|
1828
|
+
warnings,
|
|
1829
|
+
runtimeContext,
|
|
1830
|
+
execution
|
|
1831
|
+
);
|
|
1832
|
+
});
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
warnings.push({
|
|
1835
|
+
source: "memory",
|
|
1836
|
+
code: "memory_semantic_search_failed",
|
|
1837
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1705
1840
|
}
|
|
1706
1841
|
let lexical = [];
|
|
1707
1842
|
const keywords = deriveLexicalKeywords(input.query);
|
|
1708
|
-
if (activeMergeStrategy === "rrf" && lexicalQueries.length > 1 && typeof
|
|
1843
|
+
if (activeMergeStrategy === "rrf" && lexicalQueries.length > 1 && typeof sourceDeps.searchRawMessagesLexical === "function") {
|
|
1709
1844
|
const lists = await Promise.all(
|
|
1710
1845
|
lexicalQueries.map(async (query) => ({
|
|
1711
1846
|
name: "memory-bm25",
|
|
@@ -1713,14 +1848,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1713
1848
|
await measure(
|
|
1714
1849
|
"lexicalMs",
|
|
1715
1850
|
() => runLexicalSearchForKeywords(
|
|
1716
|
-
|
|
1851
|
+
sourceDeps,
|
|
1717
1852
|
input,
|
|
1718
1853
|
deriveLexicalKeywords(query),
|
|
1719
1854
|
limit,
|
|
1720
1855
|
logger,
|
|
1721
1856
|
peerPeers,
|
|
1722
1857
|
warnings,
|
|
1723
|
-
runtimeContext
|
|
1858
|
+
runtimeContext,
|
|
1859
|
+
execution
|
|
1724
1860
|
)
|
|
1725
1861
|
),
|
|
1726
1862
|
"memory-bm25"
|
|
@@ -1733,11 +1869,11 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1733
1869
|
}));
|
|
1734
1870
|
if (reasoningInfo) reasoningInfo.lexicalRewrittenQueries = lexicalQueries;
|
|
1735
1871
|
} else if (keywords.length > 0) {
|
|
1736
|
-
if (typeof
|
|
1872
|
+
if (typeof sourceDeps.searchRawMessagesLexical === "function") {
|
|
1737
1873
|
const lexicalStartedAt = monotonicNow();
|
|
1738
1874
|
try {
|
|
1739
1875
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1740
|
-
const searchRawMessagesLexical =
|
|
1876
|
+
const searchRawMessagesLexical = sourceDeps.searchRawMessagesLexical;
|
|
1741
1877
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1742
1878
|
const includeDeprecated = input.includeDeprecated === true;
|
|
1743
1879
|
lexical = (await Promise.all(
|
|
@@ -1777,16 +1913,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1777
1913
|
}
|
|
1778
1914
|
}
|
|
1779
1915
|
let entity;
|
|
1780
|
-
if (typeof
|
|
1916
|
+
if (typeof sourceDeps.entitySearch === "function") {
|
|
1781
1917
|
entity = filterByDateRange(
|
|
1782
|
-
await runEntitySearchForQuery(
|
|
1918
|
+
await runEntitySearchForQuery(sourceDeps, input, limit, logger, peerPeers, runtimeContext),
|
|
1783
1919
|
input.dateFrom,
|
|
1784
1920
|
input.dateTo
|
|
1785
1921
|
);
|
|
1786
1922
|
if (entity.length === 0) {
|
|
1787
1923
|
entity = void 0;
|
|
1788
1924
|
} else if (normalizeUnifiedMemoryMergeStrategy(
|
|
1789
|
-
input.mergeStrategy ??
|
|
1925
|
+
input.mergeStrategy ?? sourceDeps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1790
1926
|
) !== "rrf") {
|
|
1791
1927
|
warnings.push({
|
|
1792
1928
|
source: "memory",
|
|
@@ -1817,6 +1953,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1817
1953
|
const searchStartedAt = monotonicNow();
|
|
1818
1954
|
const query = input.query.trim();
|
|
1819
1955
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
1956
|
+
const sourceStates = [];
|
|
1820
1957
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1821
1958
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1822
1959
|
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
@@ -1842,7 +1979,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1842
1979
|
sources,
|
|
1843
1980
|
results: [],
|
|
1844
1981
|
count: 0,
|
|
1845
|
-
warnings
|
|
1982
|
+
warnings,
|
|
1983
|
+
sourceStates
|
|
1846
1984
|
};
|
|
1847
1985
|
}
|
|
1848
1986
|
let memorySubs;
|
|
@@ -1853,6 +1991,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1853
1991
|
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1854
1992
|
}
|
|
1855
1993
|
if (sources.includes("memory")) {
|
|
1994
|
+
let availability = "not_ready";
|
|
1856
1995
|
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesHybrid === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1857
1996
|
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1858
1997
|
try {
|
|
@@ -1867,6 +2006,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1867
2006
|
runtimeContext
|
|
1868
2007
|
);
|
|
1869
2008
|
memorySubs = memoryRun.results;
|
|
2009
|
+
availability = memoryRun.availability;
|
|
1870
2010
|
memoryTimings = memoryRun.timings;
|
|
1871
2011
|
memorySubs = {
|
|
1872
2012
|
semantic: dedupeChannelByParent(memorySubs.semantic, "memory-semantic"),
|
|
@@ -1891,11 +2031,12 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1891
2031
|
warnings.push({ source: "memory", code, message });
|
|
1892
2032
|
}
|
|
1893
2033
|
} catch (error) {
|
|
2034
|
+
availability = availability === "complete" || availability === "partial" ? "partial" : "failed";
|
|
1894
2035
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1895
2036
|
warnings.push({
|
|
1896
2037
|
source: "memory",
|
|
1897
2038
|
code: "memory_search_failed",
|
|
1898
|
-
message: `Memory search failed: ${error.message ?? "Unknown error"}
|
|
2039
|
+
message: `Memory search failed: ${error.message ?? "Unknown error"}.`
|
|
1899
2040
|
});
|
|
1900
2041
|
}
|
|
1901
2042
|
} else {
|
|
@@ -1905,6 +2046,51 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1905
2046
|
message: "Raw memory storage is not available in this environment."
|
|
1906
2047
|
});
|
|
1907
2048
|
}
|
|
2049
|
+
const count = new Set(
|
|
2050
|
+
[
|
|
2051
|
+
...memorySubs?.semantic ?? [],
|
|
2052
|
+
...memorySubs?.lexical ?? [],
|
|
2053
|
+
...memorySubs?.hybrid ?? [],
|
|
2054
|
+
...memorySubs?.planner ?? [],
|
|
2055
|
+
...memorySubs?.entity ?? []
|
|
2056
|
+
].map((hit) => hit.id)
|
|
2057
|
+
).size;
|
|
2058
|
+
if (availability === "complete" && count === 0 && retrievalStatus?.lexicalReady === false && (!retrievalStatus.semanticReady || !deps.embedQuery)) {
|
|
2059
|
+
availability = "not_ready";
|
|
2060
|
+
}
|
|
2061
|
+
const memoryWarnings = warnings.filter((warning) => warning.source === "memory");
|
|
2062
|
+
sourceStates.push({
|
|
2063
|
+
source: "memory",
|
|
2064
|
+
status: availability,
|
|
2065
|
+
count,
|
|
2066
|
+
...memoryWarnings.length ? { message: memoryWarnings.map((warning) => warning.message).join("; ") } : {}
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
const workspaceHits = [];
|
|
2070
|
+
if (sources.includes("workspace")) {
|
|
2071
|
+
if (!deps.searchWorkspace) {
|
|
2072
|
+
sourceStates.push({ source: "workspace", status: "not_configured", count: 0 });
|
|
2073
|
+
} else {
|
|
2074
|
+
try {
|
|
2075
|
+
const result = await deps.searchWorkspace({
|
|
2076
|
+
userId: input.userId,
|
|
2077
|
+
query,
|
|
2078
|
+
limit: candidateLimit,
|
|
2079
|
+
...runtimeContext ?? {}
|
|
2080
|
+
});
|
|
2081
|
+
workspaceHits.push(...result.hits.map((hit) => ({ ...hit, type: "workspace" })));
|
|
2082
|
+
sourceStates.push({
|
|
2083
|
+
source: "workspace",
|
|
2084
|
+
status: result.status,
|
|
2085
|
+
count: result.hits.length,
|
|
2086
|
+
...result.message ? { message: result.message } : {}
|
|
2087
|
+
});
|
|
2088
|
+
} catch (error) {
|
|
2089
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2090
|
+
sourceStates.push({ source: "workspace", status: "failed", count: 0, message });
|
|
2091
|
+
warnings.push({ source: "workspace", code: "workspace_search_failed", message });
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
1908
2094
|
}
|
|
1909
2095
|
const insightHits = [];
|
|
1910
2096
|
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
@@ -1926,10 +2112,18 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1926
2112
|
id: hit.id,
|
|
1927
2113
|
content: hit.content,
|
|
1928
2114
|
similarity: hit.similarity,
|
|
1929
|
-
metadata: hit.metadata
|
|
2115
|
+
metadata: hit.metadata,
|
|
2116
|
+
provenance: hit.provenance
|
|
1930
2117
|
});
|
|
1931
2118
|
}
|
|
2119
|
+
sourceStates.push({ source: "insights", status: "complete", count: insightHits.length });
|
|
1932
2120
|
} catch (error) {
|
|
2121
|
+
sourceStates.push({
|
|
2122
|
+
source: "insights",
|
|
2123
|
+
status: "failed",
|
|
2124
|
+
count: 0,
|
|
2125
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2126
|
+
});
|
|
1933
2127
|
logger.warn?.("[memory-store] insights source failed:", error);
|
|
1934
2128
|
warnings.push({
|
|
1935
2129
|
source: "insights",
|
|
@@ -1937,7 +2131,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1937
2131
|
message: error.message ?? "insights_search_failed"
|
|
1938
2132
|
});
|
|
1939
2133
|
}
|
|
1940
|
-
} else if (sources.includes("insights"))
|
|
2134
|
+
} else if (sources.includes("insights")) {
|
|
2135
|
+
sourceStates.push({ source: "insights", status: "not_configured", count: 0 });
|
|
2136
|
+
}
|
|
1941
2137
|
const knowledgeHits = [];
|
|
1942
2138
|
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
1943
2139
|
try {
|
|
@@ -1956,7 +2152,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1956
2152
|
for (const hit of hits) {
|
|
1957
2153
|
knowledgeHits.push(toKnowledgeResult(hit));
|
|
1958
2154
|
}
|
|
2155
|
+
sourceStates.push({ source: "knowledge", status: "complete", count: knowledgeHits.length });
|
|
1959
2156
|
} catch (error) {
|
|
2157
|
+
sourceStates.push({
|
|
2158
|
+
source: "knowledge",
|
|
2159
|
+
status: "failed",
|
|
2160
|
+
count: 0,
|
|
2161
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2162
|
+
});
|
|
1960
2163
|
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
1961
2164
|
warnings.push({
|
|
1962
2165
|
source: "knowledge",
|
|
@@ -1964,12 +2167,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1964
2167
|
message: error.message ?? "knowledge_search_failed"
|
|
1965
2168
|
});
|
|
1966
2169
|
}
|
|
1967
|
-
} else if (sources.includes("knowledge"))
|
|
2170
|
+
} else if (sources.includes("knowledge")) {
|
|
2171
|
+
sourceStates.push({ source: "knowledge", status: "not_configured", count: 0 });
|
|
2172
|
+
}
|
|
1968
2173
|
const fusionStartedAt = monotonicNow();
|
|
1969
2174
|
const merged = mergeAcrossSources({
|
|
1970
2175
|
memorySubs,
|
|
1971
2176
|
insightHits,
|
|
1972
2177
|
knowledgeHits,
|
|
2178
|
+
workspaceHits,
|
|
1973
2179
|
limit: candidateLimit,
|
|
1974
2180
|
strategy: mergeStrategy,
|
|
1975
2181
|
rrfK,
|
|
@@ -1996,7 +2202,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1996
2202
|
sources,
|
|
1997
2203
|
results: ranked,
|
|
1998
2204
|
count: ranked.length,
|
|
1999
|
-
warnings
|
|
2205
|
+
warnings,
|
|
2206
|
+
sourceStates
|
|
2000
2207
|
};
|
|
2001
2208
|
if (input.includeRetrievalDiagnostics) {
|
|
2002
2209
|
output.retrievalDiagnostics = {
|
|
@@ -2106,15 +2313,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2106
2313
|
evidence: [],
|
|
2107
2314
|
count: 0,
|
|
2108
2315
|
warnings: peerScope.warnings,
|
|
2316
|
+
sourceStates: [],
|
|
2109
2317
|
answer: ""
|
|
2110
2318
|
};
|
|
2111
2319
|
return empty;
|
|
2112
2320
|
}
|
|
2113
|
-
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
|
|
2321
|
+
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge", "workspace"] : ["raw", "insight", "knowledge", "workspace"];
|
|
2114
2322
|
const wantsSummaryTier = tiers.includes("summary");
|
|
2115
2323
|
const wantsRawTier = tiers.includes("raw");
|
|
2116
2324
|
const wantsInsightTier = tiers.includes("insight");
|
|
2117
2325
|
const wantsKnowledgeTier = tiers.includes("knowledge");
|
|
2326
|
+
const wantsWorkspaceTier = tiers.includes("workspace");
|
|
2118
2327
|
const warnings = [...peerScope.warnings];
|
|
2119
2328
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
2120
2329
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
@@ -2122,11 +2331,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2122
2331
|
let sources = input.sources ? [...input.sources] : [];
|
|
2123
2332
|
let unifiedReasoning;
|
|
2124
2333
|
let retrievalDiagnostics;
|
|
2125
|
-
|
|
2334
|
+
let sourceStates = [];
|
|
2335
|
+
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier || wantsWorkspaceTier) {
|
|
2126
2336
|
const inferredSources = sources.length > 0 ? sources : [
|
|
2127
2337
|
...wantsRawTier ? ["memory"] : [],
|
|
2128
2338
|
...wantsInsightTier ? ["insights"] : [],
|
|
2129
|
-
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
2339
|
+
...wantsKnowledgeTier ? ["knowledge"] : [],
|
|
2340
|
+
...wantsWorkspaceTier ? ["workspace"] : []
|
|
2130
2341
|
];
|
|
2131
2342
|
const unified = await searchUnifiedMemoryWithRuntime(
|
|
2132
2343
|
{
|
|
@@ -2139,6 +2350,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2139
2350
|
sources = unified.sources;
|
|
2140
2351
|
unifiedReasoning = unified.reasoning;
|
|
2141
2352
|
retrievalDiagnostics = unified.retrievalDiagnostics;
|
|
2353
|
+
sourceStates = unified.sourceStates;
|
|
2142
2354
|
warnings.push(...unified.warnings);
|
|
2143
2355
|
}
|
|
2144
2356
|
let summaryHits = [];
|
|
@@ -2177,6 +2389,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2177
2389
|
source: mapHitToEvidenceSource(hit),
|
|
2178
2390
|
snippet: hit.content,
|
|
2179
2391
|
score: hit.similarity,
|
|
2392
|
+
provenance: {
|
|
2393
|
+
citation: hit.provenance?.citation ?? null,
|
|
2394
|
+
origin: hit.provenance?.origin ?? null,
|
|
2395
|
+
businessReference: hit.provenance?.businessReference ?? null,
|
|
2396
|
+
version: hit.provenance?.version ?? hit.provenance?.citation?.version_id ?? null,
|
|
2397
|
+
contentHash: hit.provenance?.contentHash ?? hit.provenance?.citation?.content_hash ?? null,
|
|
2398
|
+
scope: hit.provenance?.scope ?? null,
|
|
2399
|
+
derivedFrom: hit.provenance?.derivedFrom ?? null,
|
|
2400
|
+
confirmation: hit.provenance?.confirmation ?? null,
|
|
2401
|
+
freshness: hit.provenance?.freshness ?? null
|
|
2402
|
+
},
|
|
2180
2403
|
timestamp: getCandidateTimestamp(hit.metadata),
|
|
2181
2404
|
metadata: hit.metadata
|
|
2182
2405
|
}));
|
|
@@ -2186,7 +2409,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2186
2409
|
results: hits,
|
|
2187
2410
|
evidence,
|
|
2188
2411
|
count: hits.length,
|
|
2189
|
-
warnings
|
|
2412
|
+
warnings,
|
|
2413
|
+
sourceStates
|
|
2190
2414
|
};
|
|
2191
2415
|
if (unifiedReasoning) {
|
|
2192
2416
|
base.reasoning = unifiedReasoning;
|
|
@@ -2209,8 +2433,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2209
2433
|
return base;
|
|
2210
2434
|
}
|
|
2211
2435
|
async function search(input, runtimeContext) {
|
|
2436
|
+
deps.signal?.throwIfAborted();
|
|
2437
|
+
if (input.sources !== void 0) normalizeUnifiedMemorySearchSources(input.sources);
|
|
2212
2438
|
const resolvedRuntime = resolveSearchRuntimeContext(input, runtimeContext);
|
|
2213
|
-
|
|
2439
|
+
const runSearch = boundedSearchOperation(() => searchWithResolvedRuntime(input, resolvedRuntime), {
|
|
2440
|
+
signal: deps.signal,
|
|
2441
|
+
sourceTimeoutMs: deps.searchTimeoutMs ?? 3e4
|
|
2442
|
+
});
|
|
2443
|
+
const result = await runSearch();
|
|
2444
|
+
deps.signal?.throwIfAborted();
|
|
2445
|
+
return result;
|
|
2214
2446
|
}
|
|
2215
2447
|
return {
|
|
2216
2448
|
search,
|
|
@@ -2325,6 +2557,7 @@ function selectSessionNeighborResults(results, options, limit) {
|
|
|
2325
2557
|
function mergeAcrossSources(input) {
|
|
2326
2558
|
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
2327
2559
|
const lists = buildChannelLists(memorySubs, input.insightHits, input.knowledgeHits);
|
|
2560
|
+
if (input.workspaceHits.length) lists.push({ name: "workspace", hits: input.workspaceHits });
|
|
2328
2561
|
if (input.strategy !== "rrf") {
|
|
2329
2562
|
const seen = /* @__PURE__ */ new Map();
|
|
2330
2563
|
for (const hit of [
|
|
@@ -2333,7 +2566,8 @@ function mergeAcrossSources(input) {
|
|
|
2333
2566
|
...memorySubs.planner ?? [],
|
|
2334
2567
|
...memorySubs.hybrid ?? [],
|
|
2335
2568
|
...input.insightHits,
|
|
2336
|
-
...input.knowledgeHits
|
|
2569
|
+
...input.knowledgeHits,
|
|
2570
|
+
...input.workspaceHits
|
|
2337
2571
|
]) {
|
|
2338
2572
|
const key = `${hit.type}::${hit.id}`;
|
|
2339
2573
|
const existing = seen.get(key);
|
|
@@ -2344,7 +2578,10 @@ function mergeAcrossSources(input) {
|
|
|
2344
2578
|
const other = preferred === hit ? existing : hit;
|
|
2345
2579
|
seen.set(
|
|
2346
2580
|
key,
|
|
2347
|
-
|
|
2581
|
+
mergeSearchResultProvenance(
|
|
2582
|
+
preferred.type === "memory" && other.type === "memory" ? mergeMatchedEvidence(preferred, other) : preferred,
|
|
2583
|
+
other
|
|
2584
|
+
)
|
|
2348
2585
|
);
|
|
2349
2586
|
}
|
|
2350
2587
|
}
|
|
@@ -2360,7 +2597,8 @@ function mergeAcrossSources(input) {
|
|
|
2360
2597
|
...memorySubs.hybrid ?? [],
|
|
2361
2598
|
...memorySubs.entity ?? [],
|
|
2362
2599
|
...input.insightHits,
|
|
2363
|
-
...input.knowledgeHits
|
|
2600
|
+
...input.knowledgeHits,
|
|
2601
|
+
...input.workspaceHits
|
|
2364
2602
|
];
|
|
2365
2603
|
const merged = mergeUnifiedMemorySearchResultsRrf(
|
|
2366
2604
|
lists.length > 0 ? lists : [{ name: "memory-semantic", hits: all }],
|