@melandlabs/memory-store 1.3.8 → 1.4.1
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
|
@@ -416,16 +416,21 @@ function deriveLexicalKeywords(query) {
|
|
|
416
416
|
}
|
|
417
417
|
var DEFAULT_LIMIT = 10;
|
|
418
418
|
var DEFAULT_THRESHOLD = 0.7;
|
|
419
|
-
var DEFAULT_SOURCES = ["memory", "insights", "knowledge"];
|
|
419
|
+
var DEFAULT_SOURCES = ["memory", "insights", "knowledge", "workspace"];
|
|
420
420
|
var SOURCE_SET = new Set(DEFAULT_SOURCES);
|
|
421
421
|
function normalizeUnifiedMemorySearchSources(sources) {
|
|
422
|
-
if (
|
|
422
|
+
if (sources === void 0) {
|
|
423
423
|
return [...DEFAULT_SOURCES];
|
|
424
424
|
}
|
|
425
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
426
|
+
throw new TypeError("Search sources must be a non-empty array when specified");
|
|
427
|
+
}
|
|
425
428
|
const normalized = sources.filter((source) => typeof source === "string").map((source) => source.trim().toLowerCase()).filter(
|
|
426
429
|
(source) => SOURCE_SET.has(source)
|
|
427
430
|
);
|
|
428
|
-
|
|
431
|
+
if (normalized.length !== sources.length)
|
|
432
|
+
throw new TypeError("Search sources contain an unsupported source");
|
|
433
|
+
return Array.from(new Set(normalized));
|
|
429
434
|
}
|
|
430
435
|
function clampUnifiedMemorySearchLimit(limit) {
|
|
431
436
|
const parsed = typeof limit === "number" ? limit : Number(limit ?? DEFAULT_LIMIT);
|
|
@@ -467,6 +472,20 @@ function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none"
|
|
|
467
472
|
return defaultStrategy;
|
|
468
473
|
}
|
|
469
474
|
var DEFAULT_RRF_K = 60;
|
|
475
|
+
function mergeSearchResultProvenance(preferred, other) {
|
|
476
|
+
const first = preferred.provenance;
|
|
477
|
+
const second = other.provenance;
|
|
478
|
+
const firstVersion = first?.version ?? first?.citation?.version_id;
|
|
479
|
+
const secondVersion = second?.version ?? second?.citation?.version_id;
|
|
480
|
+
const firstHash = first?.contentHash ?? first?.citation?.content_hash;
|
|
481
|
+
const secondHash = second?.contentHash ?? second?.citation?.content_hash;
|
|
482
|
+
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))
|
|
483
|
+
return preferred;
|
|
484
|
+
const metadata = { ...other.metadata, ...preferred.metadata };
|
|
485
|
+
if (!second) return { ...preferred, metadata };
|
|
486
|
+
const knownFirst = Object.fromEntries(Object.entries(first ?? {}).filter(([, value]) => value != null));
|
|
487
|
+
return { ...preferred, metadata, provenance: { ...second, ...knownFirst } };
|
|
488
|
+
}
|
|
470
489
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, weights = {}) {
|
|
471
490
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
472
491
|
return [];
|
|
@@ -480,6 +499,7 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
480
499
|
const scores = /* @__PURE__ */ new Map();
|
|
481
500
|
const order = [];
|
|
482
501
|
for (const list of lists) {
|
|
502
|
+
const seenInList = /* @__PURE__ */ new Set();
|
|
483
503
|
const channel = listNameToChannel(list.name);
|
|
484
504
|
const configuredWeight = channel ? effectiveWeights[channel] : void 0;
|
|
485
505
|
const weight = typeof configuredWeight === "number" && Number.isFinite(configuredWeight) && configuredWeight >= 0 ? configuredWeight : 1;
|
|
@@ -487,7 +507,8 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
487
507
|
for (let index = 0; index < list.hits.length; index += 1) {
|
|
488
508
|
const hit = list.hits[index];
|
|
489
509
|
const key = `${hit.type}::${hit.id}`;
|
|
490
|
-
const contribution = weight / (safeK +
|
|
510
|
+
const contribution = seenInList.has(key) ? 0 : weight / (safeK + seenInList.size + 1);
|
|
511
|
+
seenInList.add(key);
|
|
491
512
|
const existing = scores.get(key);
|
|
492
513
|
const rankedHit = hit.type === "memory" ? withMatchedEvidence(hit, list.name, index + 1) : hit;
|
|
493
514
|
if (existing) {
|
|
@@ -495,6 +516,7 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
495
516
|
if (hit.type === "memory" && existing.hit.type === "memory") {
|
|
496
517
|
existing.hit = mergeMatchedEvidence(existing.hit, rankedHit);
|
|
497
518
|
}
|
|
519
|
+
existing.hit = mergeSearchResultProvenance(existing.hit, rankedHit);
|
|
498
520
|
} else {
|
|
499
521
|
scores.set(key, { hit: rankedHit, rrf: contribution });
|
|
500
522
|
order.push(key);
|
|
@@ -697,7 +719,8 @@ async function applyReflectedPlan(deps, store, input, _logger) {
|
|
|
697
719
|
source: item.source ?? "raw",
|
|
698
720
|
snippet: item.snippet,
|
|
699
721
|
score: item.score,
|
|
700
|
-
timestamp: item.timestamp
|
|
722
|
+
timestamp: item.timestamp,
|
|
723
|
+
provenance: item.provenance
|
|
701
724
|
}));
|
|
702
725
|
const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
|
|
703
726
|
const applyInput = {
|
|
@@ -759,6 +782,46 @@ function makePlanSummary(plan) {
|
|
|
759
782
|
return lines.join("\n");
|
|
760
783
|
}
|
|
761
784
|
|
|
785
|
+
// src/search/execution-budget.ts
|
|
786
|
+
function boundedSearchOperation(operation, budget) {
|
|
787
|
+
const timeout = budget.sourceTimeoutMs ?? 5e3;
|
|
788
|
+
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
789
|
+
throw new TypeError("sourceTimeoutMs must be positive and finite");
|
|
790
|
+
return (...args) => new Promise((resolve, reject) => {
|
|
791
|
+
const signal = budget.signal;
|
|
792
|
+
if (signal?.aborted) {
|
|
793
|
+
reject(signal.reason);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const cleanup = () => {
|
|
797
|
+
clearTimeout(timer);
|
|
798
|
+
signal?.removeEventListener("abort", onAbort);
|
|
799
|
+
};
|
|
800
|
+
const onAbort = () => {
|
|
801
|
+
cleanup();
|
|
802
|
+
reject(signal?.reason);
|
|
803
|
+
};
|
|
804
|
+
const timer = setTimeout(() => {
|
|
805
|
+
cleanup();
|
|
806
|
+
reject(new Error(`Search operation exceeded ${timeout}ms waiting budget`));
|
|
807
|
+
}, timeout);
|
|
808
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
809
|
+
Promise.resolve().then(() => {
|
|
810
|
+
signal?.throwIfAborted();
|
|
811
|
+
return operation(...args);
|
|
812
|
+
}).then(
|
|
813
|
+
(value) => {
|
|
814
|
+
cleanup();
|
|
815
|
+
resolve(value);
|
|
816
|
+
},
|
|
817
|
+
(error) => {
|
|
818
|
+
cleanup();
|
|
819
|
+
reject(error);
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
762
825
|
// src/search/reranker.ts
|
|
763
826
|
async function applyReranker(reranker, query, results) {
|
|
764
827
|
if (!reranker || results.length === 0) {
|
|
@@ -860,14 +923,15 @@ function buildSynthesisPrompt(input) {
|
|
|
860
923
|
grouped.set(item.source, bucket);
|
|
861
924
|
}
|
|
862
925
|
const sections = [];
|
|
863
|
-
for (const tier of DEFAULT_TIERS2) {
|
|
926
|
+
for (const tier of [...DEFAULT_TIERS2, "workspace"]) {
|
|
864
927
|
const items = grouped.get(tier) ?? [];
|
|
865
928
|
if (items.length === 0) {
|
|
866
929
|
continue;
|
|
867
930
|
}
|
|
868
931
|
const lines = items.map((item, index) => {
|
|
869
932
|
const factSource = typeof item.metadata?.source === "string" && item.metadata.source.length > 0 ? item.metadata.source : item.source ?? "unknown";
|
|
870
|
-
|
|
933
|
+
const provenance = item.provenance ? ` provenance=${JSON.stringify(item.provenance)}` : "";
|
|
934
|
+
return ` [${index + 1}] (${item.id}, source=${factSource}, score=${item.score.toFixed(4)}) ${item.snippet}${provenance}`;
|
|
871
935
|
});
|
|
872
936
|
sections.push(`## ${tier}
|
|
873
937
|
${lines.join("\n")}`);
|
|
@@ -958,7 +1022,8 @@ async function synthesizeAnswer(input) {
|
|
|
958
1022
|
score: item.score,
|
|
959
1023
|
// Forward per-fact provenance (e.g. `metadata.source` from
|
|
960
1024
|
// `opencontext add --source`) so the synthesis prompt can cite it.
|
|
961
|
-
metadata: item.metadata
|
|
1025
|
+
metadata: item.metadata,
|
|
1026
|
+
provenance: item.provenance
|
|
962
1027
|
}));
|
|
963
1028
|
const warnings = [];
|
|
964
1029
|
const complete = deps.reasoning?.complete;
|
|
@@ -1179,7 +1244,7 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
1179
1244
|
authToken: input.authToken
|
|
1180
1245
|
});
|
|
1181
1246
|
}
|
|
1182
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext) {
|
|
1247
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext, execution) {
|
|
1183
1248
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
1184
1249
|
let semantic = [];
|
|
1185
1250
|
if (typeof deps.searchRawMessagesAnn === "function") {
|
|
@@ -1207,7 +1272,10 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1207
1272
|
semantic = (await Promise.all(
|
|
1208
1273
|
filters.map((filter) => {
|
|
1209
1274
|
const botId = "botId" in filter ? filter.botId : void 0;
|
|
1210
|
-
return
|
|
1275
|
+
return boundedSearchOperation(
|
|
1276
|
+
searchRawMessagesWithChroma,
|
|
1277
|
+
deps
|
|
1278
|
+
)({
|
|
1211
1279
|
userId: input.userId,
|
|
1212
1280
|
queryEmbedding,
|
|
1213
1281
|
limit,
|
|
@@ -1216,19 +1284,21 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1216
1284
|
});
|
|
1217
1285
|
})
|
|
1218
1286
|
)).flat().map(toMemoryResult);
|
|
1287
|
+
if (execution) execution.completed++;
|
|
1219
1288
|
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
1220
1289
|
backend: "chroma",
|
|
1221
1290
|
dimensions: queryEmbedding.length,
|
|
1222
1291
|
count: semantic.length
|
|
1223
1292
|
});
|
|
1224
1293
|
} catch (error) {
|
|
1294
|
+
if (execution) execution.failed++;
|
|
1225
1295
|
logger.warn?.(
|
|
1226
1296
|
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
1227
1297
|
error
|
|
1228
1298
|
);
|
|
1229
1299
|
}
|
|
1230
1300
|
}
|
|
1231
|
-
if (semantic.length === 0 && runtimeContext !== void 0) {
|
|
1301
|
+
if (semantic.length === 0 && runtimeContext !== void 0 && !deps.searchRawMessagesAnn) {
|
|
1232
1302
|
warnApplicabilityNotEnforced(warnings);
|
|
1233
1303
|
return [];
|
|
1234
1304
|
}
|
|
@@ -1238,7 +1308,10 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1238
1308
|
const manager2 = await getRawMessageManager2();
|
|
1239
1309
|
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
1240
1310
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1241
|
-
const results = await
|
|
1311
|
+
const results = await boundedSearchOperation(
|
|
1312
|
+
manager2.searchMessagesSemantically.bind(manager2),
|
|
1313
|
+
deps
|
|
1314
|
+
)({
|
|
1242
1315
|
userId: input.userId,
|
|
1243
1316
|
queryEmbedding,
|
|
1244
1317
|
limit,
|
|
@@ -1248,6 +1321,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1248
1321
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1249
1322
|
...factTypes ? { factTypes } : {}
|
|
1250
1323
|
});
|
|
1324
|
+
if (execution) execution.completed++;
|
|
1251
1325
|
semantic = results.map((r) => ({
|
|
1252
1326
|
type: "memory",
|
|
1253
1327
|
id: r.id,
|
|
@@ -1257,6 +1331,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1257
1331
|
}));
|
|
1258
1332
|
}
|
|
1259
1333
|
} catch (error) {
|
|
1334
|
+
if (execution) execution.failed++;
|
|
1260
1335
|
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
1261
1336
|
}
|
|
1262
1337
|
}
|
|
@@ -1307,7 +1382,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
1307
1382
|
metadata: hit.metadata
|
|
1308
1383
|
};
|
|
1309
1384
|
}
|
|
1310
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext) {
|
|
1385
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext, execution) {
|
|
1311
1386
|
if (keywords.length === 0) {
|
|
1312
1387
|
return [];
|
|
1313
1388
|
}
|
|
@@ -1357,9 +1432,12 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1357
1432
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1358
1433
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1359
1434
|
const includeDeprecated = input.includeDeprecated === true;
|
|
1360
|
-
|
|
1435
|
+
const results = (await Promise.all(
|
|
1361
1436
|
filters.map(
|
|
1362
|
-
(botId) =>
|
|
1437
|
+
(botId) => boundedSearchOperation(
|
|
1438
|
+
lexicalSearchRawMessages2,
|
|
1439
|
+
deps
|
|
1440
|
+
)({
|
|
1363
1441
|
userId: input.userId,
|
|
1364
1442
|
keywords,
|
|
1365
1443
|
limit: Math.ceil(limit / filters.length),
|
|
@@ -1374,7 +1452,10 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1374
1452
|
r
|
|
1375
1453
|
)
|
|
1376
1454
|
);
|
|
1455
|
+
if (execution) execution.completed++;
|
|
1456
|
+
return results;
|
|
1377
1457
|
} catch (error) {
|
|
1458
|
+
if (execution) execution.failed++;
|
|
1378
1459
|
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
1379
1460
|
}
|
|
1380
1461
|
return [];
|
|
@@ -1421,7 +1502,21 @@ function mapHitToEvidenceSource(hit) {
|
|
|
1421
1502
|
}
|
|
1422
1503
|
return hit.type;
|
|
1423
1504
|
}
|
|
1424
|
-
function createUnifiedSearch(
|
|
1505
|
+
function createUnifiedSearch(inputDeps = {}) {
|
|
1506
|
+
const deps = {
|
|
1507
|
+
...inputDeps,
|
|
1508
|
+
embedQuery: inputDeps.embedQuery && boundedSearchOperation(inputDeps.embedQuery, inputDeps),
|
|
1509
|
+
searchRawMessagesAnn: inputDeps.searchRawMessagesAnn && boundedSearchOperation(inputDeps.searchRawMessagesAnn, inputDeps),
|
|
1510
|
+
searchRawMessagesHybrid: inputDeps.searchRawMessagesHybrid && boundedSearchOperation(inputDeps.searchRawMessagesHybrid, inputDeps),
|
|
1511
|
+
searchRawMessagesLexical: inputDeps.searchRawMessagesLexical && boundedSearchOperation(inputDeps.searchRawMessagesLexical, inputDeps),
|
|
1512
|
+
searchWorkspace: inputDeps.searchWorkspace && boundedSearchOperation(inputDeps.searchWorkspace, inputDeps),
|
|
1513
|
+
searchInsights: inputDeps.searchInsights && boundedSearchOperation(inputDeps.searchInsights, inputDeps),
|
|
1514
|
+
searchKnowledge: inputDeps.searchKnowledge && boundedSearchOperation(inputDeps.searchKnowledge, inputDeps),
|
|
1515
|
+
searchSummaries: inputDeps.searchSummaries && boundedSearchOperation(inputDeps.searchSummaries, inputDeps),
|
|
1516
|
+
getRawMessageRetrievalStatus: inputDeps.getRawMessageRetrievalStatus && boundedSearchOperation(inputDeps.getRawMessageRetrievalStatus, inputDeps),
|
|
1517
|
+
entitySearch: inputDeps.entitySearch && boundedSearchOperation(inputDeps.entitySearch, inputDeps),
|
|
1518
|
+
searchRawMessageNeighbors: inputDeps.searchRawMessageNeighbors && boundedSearchOperation(inputDeps.searchRawMessageNeighbors, inputDeps)
|
|
1519
|
+
};
|
|
1425
1520
|
const logger = deps.logger ?? console;
|
|
1426
1521
|
function resolveReasoningStrategy(input) {
|
|
1427
1522
|
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
@@ -1431,6 +1526,25 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1431
1526
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
1432
1527
|
}
|
|
1433
1528
|
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = [], runtimeContext) {
|
|
1529
|
+
const execution = { completed: 0, failed: 0 };
|
|
1530
|
+
const observe = (operation, preparation = false) => operation && (async (...args) => {
|
|
1531
|
+
try {
|
|
1532
|
+
const result = await operation(...args);
|
|
1533
|
+
if (!preparation) execution.completed++;
|
|
1534
|
+
return result;
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
execution.failed++;
|
|
1537
|
+
throw error;
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
const sourceDeps = {
|
|
1541
|
+
...deps,
|
|
1542
|
+
embedQuery: observe(deps.embedQuery, true),
|
|
1543
|
+
searchRawMessagesAnn: observe(deps.searchRawMessagesAnn),
|
|
1544
|
+
searchRawMessagesHybrid: observe(deps.searchRawMessagesHybrid),
|
|
1545
|
+
searchRawMessagesLexical: observe(deps.searchRawMessagesLexical),
|
|
1546
|
+
entitySearch: observe(deps.entitySearch)
|
|
1547
|
+
};
|
|
1434
1548
|
const sourceStartedAt = monotonicNow();
|
|
1435
1549
|
const timings = {
|
|
1436
1550
|
semanticMs: 0,
|
|
@@ -1448,9 +1562,10 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1448
1562
|
};
|
|
1449
1563
|
const finish = (results) => ({
|
|
1450
1564
|
results,
|
|
1451
|
-
timings: { ...timings, memorySourceMs: monotonicNow() - sourceStartedAt }
|
|
1565
|
+
timings: { ...timings, memorySourceMs: monotonicNow() - sourceStartedAt },
|
|
1566
|
+
availability: execution.failed > 0 ? execution.completed > 0 ? "partial" : "failed" : execution.completed > 0 || Object.values(results).some((hits) => hits.length > 0) ? "complete" : "not_ready"
|
|
1452
1567
|
});
|
|
1453
|
-
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !
|
|
1568
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !sourceDeps.reasoning?.iterativePlanner) {
|
|
1454
1569
|
warnings.push({
|
|
1455
1570
|
source: "memory",
|
|
1456
1571
|
code: "memory_iterative_planner_not_configured",
|
|
@@ -1458,7 +1573,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1458
1573
|
});
|
|
1459
1574
|
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
1460
1575
|
}
|
|
1461
|
-
if (reasoningStrategy === "rewrite" && !
|
|
1576
|
+
if (reasoningStrategy === "rewrite" && !sourceDeps.reasoning?.queryRewriter) {
|
|
1462
1577
|
warnings.push({
|
|
1463
1578
|
source: "memory",
|
|
1464
1579
|
code: "memory_query_rewrite_not_configured",
|
|
@@ -1467,8 +1582,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1467
1582
|
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
1468
1583
|
}
|
|
1469
1584
|
let unionEvidence = null;
|
|
1470
|
-
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") &&
|
|
1471
|
-
const planner =
|
|
1585
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && sourceDeps.reasoning?.iterativePlanner) {
|
|
1586
|
+
const planner = sourceDeps.reasoning.iterativePlanner;
|
|
1472
1587
|
const result = await planner.plan({
|
|
1473
1588
|
query: input.query,
|
|
1474
1589
|
...input.includeRetrievalDiagnostics ? { options: { collectDiagnostics: true } } : {},
|
|
@@ -1480,26 +1595,27 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1480
1595
|
const lexicalHits = await measure(
|
|
1481
1596
|
"lexicalMs",
|
|
1482
1597
|
() => runLexicalSearchForKeywords(
|
|
1483
|
-
|
|
1598
|
+
sourceDeps,
|
|
1484
1599
|
input,
|
|
1485
1600
|
keywords2,
|
|
1486
1601
|
limit,
|
|
1487
1602
|
logger,
|
|
1488
1603
|
peerPeers,
|
|
1489
1604
|
warnings,
|
|
1490
|
-
runtimeContext
|
|
1605
|
+
runtimeContext,
|
|
1606
|
+
execution
|
|
1491
1607
|
)
|
|
1492
1608
|
);
|
|
1493
1609
|
let semanticHits = [];
|
|
1494
|
-
if (typeof
|
|
1610
|
+
if (typeof sourceDeps.embedQuery === "function") {
|
|
1495
1611
|
try {
|
|
1496
1612
|
semanticHits = await measure("semanticMs", async () => {
|
|
1497
1613
|
const semanticQuery = keywords2.join(" ");
|
|
1498
|
-
const queryEmbedder =
|
|
1614
|
+
const queryEmbedder = sourceDeps.embedQuery;
|
|
1499
1615
|
if (typeof queryEmbedder !== "function") return [];
|
|
1500
1616
|
const queryEmbedding = await embedQueryVariant(queryEmbedder, input, semanticQuery);
|
|
1501
1617
|
return runSemanticSearchForEmbedding(
|
|
1502
|
-
|
|
1618
|
+
sourceDeps,
|
|
1503
1619
|
input,
|
|
1504
1620
|
queryEmbedding,
|
|
1505
1621
|
limit,
|
|
@@ -1507,7 +1623,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1507
1623
|
logger,
|
|
1508
1624
|
peerPeers,
|
|
1509
1625
|
warnings,
|
|
1510
|
-
runtimeContext
|
|
1626
|
+
runtimeContext,
|
|
1627
|
+
execution
|
|
1511
1628
|
);
|
|
1512
1629
|
});
|
|
1513
1630
|
} catch (error) {
|
|
@@ -1560,66 +1677,74 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1560
1677
|
}
|
|
1561
1678
|
unionEvidence = evidence;
|
|
1562
1679
|
}
|
|
1563
|
-
|
|
1680
|
+
const lexicalFallback = async () => {
|
|
1681
|
+
const lexical2 = await runLexicalSearchForKeywords(
|
|
1682
|
+
sourceDeps,
|
|
1683
|
+
input,
|
|
1684
|
+
deriveLexicalKeywords(input.query),
|
|
1685
|
+
limit,
|
|
1686
|
+
logger,
|
|
1687
|
+
peerPeers,
|
|
1688
|
+
warnings,
|
|
1689
|
+
runtimeContext,
|
|
1690
|
+
execution
|
|
1691
|
+
);
|
|
1692
|
+
return finish({ semantic: [], lexical: filterByDateRange(lexical2, input.dateFrom, input.dateTo) });
|
|
1693
|
+
};
|
|
1694
|
+
if (typeof sourceDeps.embedQuery !== "function") {
|
|
1564
1695
|
warnings.push({
|
|
1565
1696
|
source: "memory",
|
|
1566
1697
|
code: "memory_lexical_search_fallback",
|
|
1567
1698
|
message: "No embedding provider is configured; using lexical (BM25) search as fallback. Pass an `embedQuery` function via `unified.embedQuery` to enable semantic retrieval."
|
|
1568
1699
|
});
|
|
1569
|
-
|
|
1570
|
-
const lexical2 = filterByDateRange(
|
|
1571
|
-
await runLexicalSearchForKeywords(
|
|
1572
|
-
deps,
|
|
1573
|
-
input,
|
|
1574
|
-
keywords2,
|
|
1575
|
-
limit,
|
|
1576
|
-
logger,
|
|
1577
|
-
peerPeers,
|
|
1578
|
-
warnings,
|
|
1579
|
-
runtimeContext
|
|
1580
|
-
),
|
|
1581
|
-
input.dateFrom,
|
|
1582
|
-
input.dateTo
|
|
1583
|
-
);
|
|
1584
|
-
return finish({ semantic: [], lexical: lexical2 });
|
|
1700
|
+
return lexicalFallback();
|
|
1585
1701
|
}
|
|
1586
1702
|
let semantic = [];
|
|
1587
1703
|
let lexicalQueries = [input.query];
|
|
1588
|
-
const embedQuery =
|
|
1704
|
+
const embedQuery = sourceDeps.embedQuery;
|
|
1589
1705
|
const activeMergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1590
|
-
input.mergeStrategy ??
|
|
1706
|
+
input.mergeStrategy ?? sourceDeps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1591
1707
|
);
|
|
1592
|
-
if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof
|
|
1593
|
-
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1708
|
+
if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof sourceDeps.searchRawMessagesHybrid === "function") {
|
|
1709
|
+
try {
|
|
1710
|
+
const hybrid = await measure("hybridMs", async () => {
|
|
1711
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
1712
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1713
|
+
return (await Promise.all(
|
|
1714
|
+
filters.map(
|
|
1715
|
+
(botId) => sourceDeps.searchRawMessagesHybrid?.({
|
|
1716
|
+
userId: input.userId,
|
|
1717
|
+
query: input.query,
|
|
1718
|
+
queryEmbedding,
|
|
1719
|
+
limit: Math.ceil(limit / filters.length),
|
|
1720
|
+
botId,
|
|
1721
|
+
...runtimeContext ?? {}
|
|
1722
|
+
})
|
|
1723
|
+
)
|
|
1724
|
+
)).flatMap((items) => items ?? []).filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
1725
|
+
});
|
|
1726
|
+
return finish({
|
|
1727
|
+
semantic: [],
|
|
1728
|
+
lexical: [],
|
|
1729
|
+
hybrid: filterByDateRange(hybrid, input.dateFrom, input.dateTo)
|
|
1730
|
+
});
|
|
1731
|
+
} catch (error) {
|
|
1732
|
+
warnings.push({
|
|
1733
|
+
source: "memory",
|
|
1734
|
+
code: "memory_hybrid_search_failed",
|
|
1735
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1736
|
+
});
|
|
1737
|
+
return lexicalFallback();
|
|
1738
|
+
}
|
|
1614
1739
|
}
|
|
1615
|
-
if (reasoningStrategy === "rewrite" &&
|
|
1740
|
+
if (reasoningStrategy === "rewrite" && sourceDeps.reasoning?.queryRewriter) {
|
|
1616
1741
|
try {
|
|
1617
|
-
const variants = await
|
|
1742
|
+
const variants = await sourceDeps.reasoning.queryRewriter.rewrite({
|
|
1618
1743
|
query: input.query,
|
|
1619
1744
|
userId: input.userId,
|
|
1620
1745
|
authToken: input.authToken
|
|
1621
1746
|
});
|
|
1622
|
-
if (
|
|
1747
|
+
if (sourceDeps.reasoning.rewriteLexical === true) {
|
|
1623
1748
|
const seen = /* @__PURE__ */ new Set();
|
|
1624
1749
|
lexicalQueries = [input.query, ...variants].map((query) => query.trim()).filter((query) => {
|
|
1625
1750
|
const key = query.toLowerCase();
|
|
@@ -1630,7 +1755,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1630
1755
|
}
|
|
1631
1756
|
if (reasoningInfo) {
|
|
1632
1757
|
reasoningInfo.rewrittenQueries = variants;
|
|
1633
|
-
if (
|
|
1758
|
+
if (sourceDeps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
1634
1759
|
reasoningInfo.degraded = true;
|
|
1635
1760
|
}
|
|
1636
1761
|
}
|
|
@@ -1641,7 +1766,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1641
1766
|
return Promise.all(
|
|
1642
1767
|
embeddings.map(
|
|
1643
1768
|
(embedding) => runSemanticSearchForEmbedding(
|
|
1644
|
-
|
|
1769
|
+
sourceDeps,
|
|
1645
1770
|
input,
|
|
1646
1771
|
embedding,
|
|
1647
1772
|
limit,
|
|
@@ -1649,12 +1774,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1649
1774
|
logger,
|
|
1650
1775
|
peerPeers,
|
|
1651
1776
|
warnings,
|
|
1652
|
-
runtimeContext
|
|
1777
|
+
runtimeContext,
|
|
1778
|
+
execution
|
|
1653
1779
|
)
|
|
1654
1780
|
)
|
|
1655
1781
|
);
|
|
1656
1782
|
});
|
|
1657
|
-
if (activeMergeStrategy === "rrf" &&
|
|
1783
|
+
if (activeMergeStrategy === "rrf" && sourceDeps.reasoning.rewriteSemanticMerge === "rrf" && lists.length > 1 && lists.some((hits) => hits.length > 0)) {
|
|
1658
1784
|
semantic = mergeUnifiedMemorySearchResultsRrf(
|
|
1659
1785
|
lists.map((hits, index) => ({
|
|
1660
1786
|
name: `memory-semantic-query:${index}`,
|
|
@@ -1677,24 +1803,33 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1677
1803
|
}
|
|
1678
1804
|
}
|
|
1679
1805
|
if (semantic.length === 0) {
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1806
|
+
try {
|
|
1807
|
+
semantic = await measure("semanticMs", async () => {
|
|
1808
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
1809
|
+
return runSemanticSearchForEmbedding(
|
|
1810
|
+
sourceDeps,
|
|
1811
|
+
input,
|
|
1812
|
+
queryEmbedding,
|
|
1813
|
+
limit,
|
|
1814
|
+
threshold,
|
|
1815
|
+
logger,
|
|
1816
|
+
peerPeers,
|
|
1817
|
+
warnings,
|
|
1818
|
+
runtimeContext,
|
|
1819
|
+
execution
|
|
1820
|
+
);
|
|
1821
|
+
});
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
warnings.push({
|
|
1824
|
+
source: "memory",
|
|
1825
|
+
code: "memory_semantic_search_failed",
|
|
1826
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1694
1829
|
}
|
|
1695
1830
|
let lexical = [];
|
|
1696
1831
|
const keywords = deriveLexicalKeywords(input.query);
|
|
1697
|
-
if (activeMergeStrategy === "rrf" && lexicalQueries.length > 1 && typeof
|
|
1832
|
+
if (activeMergeStrategy === "rrf" && lexicalQueries.length > 1 && typeof sourceDeps.searchRawMessagesLexical === "function") {
|
|
1698
1833
|
const lists = await Promise.all(
|
|
1699
1834
|
lexicalQueries.map(async (query) => ({
|
|
1700
1835
|
name: "memory-bm25",
|
|
@@ -1702,14 +1837,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1702
1837
|
await measure(
|
|
1703
1838
|
"lexicalMs",
|
|
1704
1839
|
() => runLexicalSearchForKeywords(
|
|
1705
|
-
|
|
1840
|
+
sourceDeps,
|
|
1706
1841
|
input,
|
|
1707
1842
|
deriveLexicalKeywords(query),
|
|
1708
1843
|
limit,
|
|
1709
1844
|
logger,
|
|
1710
1845
|
peerPeers,
|
|
1711
1846
|
warnings,
|
|
1712
|
-
runtimeContext
|
|
1847
|
+
runtimeContext,
|
|
1848
|
+
execution
|
|
1713
1849
|
)
|
|
1714
1850
|
),
|
|
1715
1851
|
"memory-bm25"
|
|
@@ -1722,11 +1858,11 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1722
1858
|
}));
|
|
1723
1859
|
if (reasoningInfo) reasoningInfo.lexicalRewrittenQueries = lexicalQueries;
|
|
1724
1860
|
} else if (keywords.length > 0) {
|
|
1725
|
-
if (typeof
|
|
1861
|
+
if (typeof sourceDeps.searchRawMessagesLexical === "function") {
|
|
1726
1862
|
const lexicalStartedAt = monotonicNow();
|
|
1727
1863
|
try {
|
|
1728
1864
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1729
|
-
const searchRawMessagesLexical =
|
|
1865
|
+
const searchRawMessagesLexical = sourceDeps.searchRawMessagesLexical;
|
|
1730
1866
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1731
1867
|
const includeDeprecated = input.includeDeprecated === true;
|
|
1732
1868
|
lexical = (await Promise.all(
|
|
@@ -1766,16 +1902,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1766
1902
|
}
|
|
1767
1903
|
}
|
|
1768
1904
|
let entity;
|
|
1769
|
-
if (typeof
|
|
1905
|
+
if (typeof sourceDeps.entitySearch === "function") {
|
|
1770
1906
|
entity = filterByDateRange(
|
|
1771
|
-
await runEntitySearchForQuery(
|
|
1907
|
+
await runEntitySearchForQuery(sourceDeps, input, limit, logger, peerPeers, runtimeContext),
|
|
1772
1908
|
input.dateFrom,
|
|
1773
1909
|
input.dateTo
|
|
1774
1910
|
);
|
|
1775
1911
|
if (entity.length === 0) {
|
|
1776
1912
|
entity = void 0;
|
|
1777
1913
|
} else if (normalizeUnifiedMemoryMergeStrategy(
|
|
1778
|
-
input.mergeStrategy ??
|
|
1914
|
+
input.mergeStrategy ?? sourceDeps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1779
1915
|
) !== "rrf") {
|
|
1780
1916
|
warnings.push({
|
|
1781
1917
|
source: "memory",
|
|
@@ -1806,6 +1942,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1806
1942
|
const searchStartedAt = monotonicNow();
|
|
1807
1943
|
const query = input.query.trim();
|
|
1808
1944
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
1945
|
+
const sourceStates = [];
|
|
1809
1946
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1810
1947
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1811
1948
|
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
@@ -1831,7 +1968,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1831
1968
|
sources,
|
|
1832
1969
|
results: [],
|
|
1833
1970
|
count: 0,
|
|
1834
|
-
warnings
|
|
1971
|
+
warnings,
|
|
1972
|
+
sourceStates
|
|
1835
1973
|
};
|
|
1836
1974
|
}
|
|
1837
1975
|
let memorySubs;
|
|
@@ -1842,6 +1980,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1842
1980
|
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1843
1981
|
}
|
|
1844
1982
|
if (sources.includes("memory")) {
|
|
1983
|
+
let availability = "not_ready";
|
|
1845
1984
|
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesHybrid === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1846
1985
|
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1847
1986
|
try {
|
|
@@ -1856,6 +1995,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1856
1995
|
runtimeContext
|
|
1857
1996
|
);
|
|
1858
1997
|
memorySubs = memoryRun.results;
|
|
1998
|
+
availability = memoryRun.availability;
|
|
1859
1999
|
memoryTimings = memoryRun.timings;
|
|
1860
2000
|
memorySubs = {
|
|
1861
2001
|
semantic: dedupeChannelByParent(memorySubs.semantic, "memory-semantic"),
|
|
@@ -1880,11 +2020,12 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1880
2020
|
warnings.push({ source: "memory", code, message });
|
|
1881
2021
|
}
|
|
1882
2022
|
} catch (error) {
|
|
2023
|
+
availability = availability === "complete" || availability === "partial" ? "partial" : "failed";
|
|
1883
2024
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1884
2025
|
warnings.push({
|
|
1885
2026
|
source: "memory",
|
|
1886
2027
|
code: "memory_search_failed",
|
|
1887
|
-
message: `Memory search failed: ${error.message ?? "Unknown error"}
|
|
2028
|
+
message: `Memory search failed: ${error.message ?? "Unknown error"}.`
|
|
1888
2029
|
});
|
|
1889
2030
|
}
|
|
1890
2031
|
} else {
|
|
@@ -1894,6 +2035,51 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1894
2035
|
message: "Raw memory storage is not available in this environment."
|
|
1895
2036
|
});
|
|
1896
2037
|
}
|
|
2038
|
+
const count = new Set(
|
|
2039
|
+
[
|
|
2040
|
+
...memorySubs?.semantic ?? [],
|
|
2041
|
+
...memorySubs?.lexical ?? [],
|
|
2042
|
+
...memorySubs?.hybrid ?? [],
|
|
2043
|
+
...memorySubs?.planner ?? [],
|
|
2044
|
+
...memorySubs?.entity ?? []
|
|
2045
|
+
].map((hit) => hit.id)
|
|
2046
|
+
).size;
|
|
2047
|
+
if (availability === "complete" && count === 0 && retrievalStatus?.lexicalReady === false && (!retrievalStatus.semanticReady || !deps.embedQuery)) {
|
|
2048
|
+
availability = "not_ready";
|
|
2049
|
+
}
|
|
2050
|
+
const memoryWarnings = warnings.filter((warning) => warning.source === "memory");
|
|
2051
|
+
sourceStates.push({
|
|
2052
|
+
source: "memory",
|
|
2053
|
+
status: availability,
|
|
2054
|
+
count,
|
|
2055
|
+
...memoryWarnings.length ? { message: memoryWarnings.map((warning) => warning.message).join("; ") } : {}
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
const workspaceHits = [];
|
|
2059
|
+
if (sources.includes("workspace")) {
|
|
2060
|
+
if (!deps.searchWorkspace) {
|
|
2061
|
+
sourceStates.push({ source: "workspace", status: "not_configured", count: 0 });
|
|
2062
|
+
} else {
|
|
2063
|
+
try {
|
|
2064
|
+
const result = await deps.searchWorkspace({
|
|
2065
|
+
userId: input.userId,
|
|
2066
|
+
query,
|
|
2067
|
+
limit: candidateLimit,
|
|
2068
|
+
...runtimeContext ?? {}
|
|
2069
|
+
});
|
|
2070
|
+
workspaceHits.push(...result.hits.map((hit) => ({ ...hit, type: "workspace" })));
|
|
2071
|
+
sourceStates.push({
|
|
2072
|
+
source: "workspace",
|
|
2073
|
+
status: result.status,
|
|
2074
|
+
count: result.hits.length,
|
|
2075
|
+
...result.message ? { message: result.message } : {}
|
|
2076
|
+
});
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2079
|
+
sourceStates.push({ source: "workspace", status: "failed", count: 0, message });
|
|
2080
|
+
warnings.push({ source: "workspace", code: "workspace_search_failed", message });
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
1897
2083
|
}
|
|
1898
2084
|
const insightHits = [];
|
|
1899
2085
|
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
@@ -1915,10 +2101,18 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1915
2101
|
id: hit.id,
|
|
1916
2102
|
content: hit.content,
|
|
1917
2103
|
similarity: hit.similarity,
|
|
1918
|
-
metadata: hit.metadata
|
|
2104
|
+
metadata: hit.metadata,
|
|
2105
|
+
provenance: hit.provenance
|
|
1919
2106
|
});
|
|
1920
2107
|
}
|
|
2108
|
+
sourceStates.push({ source: "insights", status: "complete", count: insightHits.length });
|
|
1921
2109
|
} catch (error) {
|
|
2110
|
+
sourceStates.push({
|
|
2111
|
+
source: "insights",
|
|
2112
|
+
status: "failed",
|
|
2113
|
+
count: 0,
|
|
2114
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2115
|
+
});
|
|
1922
2116
|
logger.warn?.("[memory-store] insights source failed:", error);
|
|
1923
2117
|
warnings.push({
|
|
1924
2118
|
source: "insights",
|
|
@@ -1926,7 +2120,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1926
2120
|
message: error.message ?? "insights_search_failed"
|
|
1927
2121
|
});
|
|
1928
2122
|
}
|
|
1929
|
-
} else if (sources.includes("insights"))
|
|
2123
|
+
} else if (sources.includes("insights")) {
|
|
2124
|
+
sourceStates.push({ source: "insights", status: "not_configured", count: 0 });
|
|
2125
|
+
}
|
|
1930
2126
|
const knowledgeHits = [];
|
|
1931
2127
|
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
1932
2128
|
try {
|
|
@@ -1945,7 +2141,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1945
2141
|
for (const hit of hits) {
|
|
1946
2142
|
knowledgeHits.push(toKnowledgeResult(hit));
|
|
1947
2143
|
}
|
|
2144
|
+
sourceStates.push({ source: "knowledge", status: "complete", count: knowledgeHits.length });
|
|
1948
2145
|
} catch (error) {
|
|
2146
|
+
sourceStates.push({
|
|
2147
|
+
source: "knowledge",
|
|
2148
|
+
status: "failed",
|
|
2149
|
+
count: 0,
|
|
2150
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2151
|
+
});
|
|
1949
2152
|
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
1950
2153
|
warnings.push({
|
|
1951
2154
|
source: "knowledge",
|
|
@@ -1953,12 +2156,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1953
2156
|
message: error.message ?? "knowledge_search_failed"
|
|
1954
2157
|
});
|
|
1955
2158
|
}
|
|
1956
|
-
} else if (sources.includes("knowledge"))
|
|
2159
|
+
} else if (sources.includes("knowledge")) {
|
|
2160
|
+
sourceStates.push({ source: "knowledge", status: "not_configured", count: 0 });
|
|
2161
|
+
}
|
|
1957
2162
|
const fusionStartedAt = monotonicNow();
|
|
1958
2163
|
const merged = mergeAcrossSources({
|
|
1959
2164
|
memorySubs,
|
|
1960
2165
|
insightHits,
|
|
1961
2166
|
knowledgeHits,
|
|
2167
|
+
workspaceHits,
|
|
1962
2168
|
limit: candidateLimit,
|
|
1963
2169
|
strategy: mergeStrategy,
|
|
1964
2170
|
rrfK,
|
|
@@ -1985,7 +2191,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1985
2191
|
sources,
|
|
1986
2192
|
results: ranked,
|
|
1987
2193
|
count: ranked.length,
|
|
1988
|
-
warnings
|
|
2194
|
+
warnings,
|
|
2195
|
+
sourceStates
|
|
1989
2196
|
};
|
|
1990
2197
|
if (input.includeRetrievalDiagnostics) {
|
|
1991
2198
|
output.retrievalDiagnostics = {
|
|
@@ -2095,15 +2302,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2095
2302
|
evidence: [],
|
|
2096
2303
|
count: 0,
|
|
2097
2304
|
warnings: peerScope.warnings,
|
|
2305
|
+
sourceStates: [],
|
|
2098
2306
|
answer: ""
|
|
2099
2307
|
};
|
|
2100
2308
|
return empty;
|
|
2101
2309
|
}
|
|
2102
|
-
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
|
|
2310
|
+
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge", "workspace"] : ["raw", "insight", "knowledge", "workspace"];
|
|
2103
2311
|
const wantsSummaryTier = tiers.includes("summary");
|
|
2104
2312
|
const wantsRawTier = tiers.includes("raw");
|
|
2105
2313
|
const wantsInsightTier = tiers.includes("insight");
|
|
2106
2314
|
const wantsKnowledgeTier = tiers.includes("knowledge");
|
|
2315
|
+
const wantsWorkspaceTier = tiers.includes("workspace");
|
|
2107
2316
|
const warnings = [...peerScope.warnings];
|
|
2108
2317
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
2109
2318
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
@@ -2111,11 +2320,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2111
2320
|
let sources = input.sources ? [...input.sources] : [];
|
|
2112
2321
|
let unifiedReasoning;
|
|
2113
2322
|
let retrievalDiagnostics;
|
|
2114
|
-
|
|
2323
|
+
let sourceStates = [];
|
|
2324
|
+
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier || wantsWorkspaceTier) {
|
|
2115
2325
|
const inferredSources = sources.length > 0 ? sources : [
|
|
2116
2326
|
...wantsRawTier ? ["memory"] : [],
|
|
2117
2327
|
...wantsInsightTier ? ["insights"] : [],
|
|
2118
|
-
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
2328
|
+
...wantsKnowledgeTier ? ["knowledge"] : [],
|
|
2329
|
+
...wantsWorkspaceTier ? ["workspace"] : []
|
|
2119
2330
|
];
|
|
2120
2331
|
const unified = await searchUnifiedMemoryWithRuntime(
|
|
2121
2332
|
{
|
|
@@ -2128,6 +2339,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2128
2339
|
sources = unified.sources;
|
|
2129
2340
|
unifiedReasoning = unified.reasoning;
|
|
2130
2341
|
retrievalDiagnostics = unified.retrievalDiagnostics;
|
|
2342
|
+
sourceStates = unified.sourceStates;
|
|
2131
2343
|
warnings.push(...unified.warnings);
|
|
2132
2344
|
}
|
|
2133
2345
|
let summaryHits = [];
|
|
@@ -2166,6 +2378,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2166
2378
|
source: mapHitToEvidenceSource(hit),
|
|
2167
2379
|
snippet: hit.content,
|
|
2168
2380
|
score: hit.similarity,
|
|
2381
|
+
provenance: {
|
|
2382
|
+
citation: hit.provenance?.citation ?? null,
|
|
2383
|
+
origin: hit.provenance?.origin ?? null,
|
|
2384
|
+
businessReference: hit.provenance?.businessReference ?? null,
|
|
2385
|
+
version: hit.provenance?.version ?? hit.provenance?.citation?.version_id ?? null,
|
|
2386
|
+
contentHash: hit.provenance?.contentHash ?? hit.provenance?.citation?.content_hash ?? null,
|
|
2387
|
+
scope: hit.provenance?.scope ?? null,
|
|
2388
|
+
derivedFrom: hit.provenance?.derivedFrom ?? null,
|
|
2389
|
+
confirmation: hit.provenance?.confirmation ?? null,
|
|
2390
|
+
freshness: hit.provenance?.freshness ?? null
|
|
2391
|
+
},
|
|
2169
2392
|
timestamp: getCandidateTimestamp(hit.metadata),
|
|
2170
2393
|
metadata: hit.metadata
|
|
2171
2394
|
}));
|
|
@@ -2175,7 +2398,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2175
2398
|
results: hits,
|
|
2176
2399
|
evidence,
|
|
2177
2400
|
count: hits.length,
|
|
2178
|
-
warnings
|
|
2401
|
+
warnings,
|
|
2402
|
+
sourceStates
|
|
2179
2403
|
};
|
|
2180
2404
|
if (unifiedReasoning) {
|
|
2181
2405
|
base.reasoning = unifiedReasoning;
|
|
@@ -2198,8 +2422,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2198
2422
|
return base;
|
|
2199
2423
|
}
|
|
2200
2424
|
async function search(input, runtimeContext) {
|
|
2425
|
+
deps.signal?.throwIfAborted();
|
|
2426
|
+
if (input.sources !== void 0) normalizeUnifiedMemorySearchSources(input.sources);
|
|
2201
2427
|
const resolvedRuntime = resolveSearchRuntimeContext(input, runtimeContext);
|
|
2202
|
-
|
|
2428
|
+
const runSearch = boundedSearchOperation(() => searchWithResolvedRuntime(input, resolvedRuntime), {
|
|
2429
|
+
signal: deps.signal,
|
|
2430
|
+
sourceTimeoutMs: deps.searchTimeoutMs ?? 3e4
|
|
2431
|
+
});
|
|
2432
|
+
const result = await runSearch();
|
|
2433
|
+
deps.signal?.throwIfAborted();
|
|
2434
|
+
return result;
|
|
2203
2435
|
}
|
|
2204
2436
|
return {
|
|
2205
2437
|
search,
|
|
@@ -2314,6 +2546,7 @@ function selectSessionNeighborResults(results, options, limit) {
|
|
|
2314
2546
|
function mergeAcrossSources(input) {
|
|
2315
2547
|
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
2316
2548
|
const lists = buildChannelLists(memorySubs, input.insightHits, input.knowledgeHits);
|
|
2549
|
+
if (input.workspaceHits.length) lists.push({ name: "workspace", hits: input.workspaceHits });
|
|
2317
2550
|
if (input.strategy !== "rrf") {
|
|
2318
2551
|
const seen = /* @__PURE__ */ new Map();
|
|
2319
2552
|
for (const hit of [
|
|
@@ -2322,7 +2555,8 @@ function mergeAcrossSources(input) {
|
|
|
2322
2555
|
...memorySubs.planner ?? [],
|
|
2323
2556
|
...memorySubs.hybrid ?? [],
|
|
2324
2557
|
...input.insightHits,
|
|
2325
|
-
...input.knowledgeHits
|
|
2558
|
+
...input.knowledgeHits,
|
|
2559
|
+
...input.workspaceHits
|
|
2326
2560
|
]) {
|
|
2327
2561
|
const key = `${hit.type}::${hit.id}`;
|
|
2328
2562
|
const existing = seen.get(key);
|
|
@@ -2333,7 +2567,10 @@ function mergeAcrossSources(input) {
|
|
|
2333
2567
|
const other = preferred === hit ? existing : hit;
|
|
2334
2568
|
seen.set(
|
|
2335
2569
|
key,
|
|
2336
|
-
|
|
2570
|
+
mergeSearchResultProvenance(
|
|
2571
|
+
preferred.type === "memory" && other.type === "memory" ? mergeMatchedEvidence(preferred, other) : preferred,
|
|
2572
|
+
other
|
|
2573
|
+
)
|
|
2337
2574
|
);
|
|
2338
2575
|
}
|
|
2339
2576
|
}
|
|
@@ -2349,7 +2586,8 @@ function mergeAcrossSources(input) {
|
|
|
2349
2586
|
...memorySubs.hybrid ?? [],
|
|
2350
2587
|
...memorySubs.entity ?? [],
|
|
2351
2588
|
...input.insightHits,
|
|
2352
|
-
...input.knowledgeHits
|
|
2589
|
+
...input.knowledgeHits,
|
|
2590
|
+
...input.workspaceHits
|
|
2353
2591
|
];
|
|
2354
2592
|
const merged = mergeUnifiedMemorySearchResultsRrf(
|
|
2355
2593
|
lists.length > 0 ? lists : [{ name: "memory-semantic", hits: all }],
|