@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/server/cli-http.js
CHANGED
|
@@ -320,6 +320,46 @@ function resolveSearchRuntimeContext(input, runtimeContext, now = Date.now) {
|
|
|
320
320
|
};
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// src/search/execution-budget.ts
|
|
324
|
+
function boundedSearchOperation(operation, budget) {
|
|
325
|
+
const timeout = budget.sourceTimeoutMs ?? 5e3;
|
|
326
|
+
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
327
|
+
throw new TypeError("sourceTimeoutMs must be positive and finite");
|
|
328
|
+
return (...args) => new Promise((resolve, reject) => {
|
|
329
|
+
const signal = budget.signal;
|
|
330
|
+
if (signal?.aborted) {
|
|
331
|
+
reject(signal.reason);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const cleanup2 = () => {
|
|
335
|
+
clearTimeout(timer);
|
|
336
|
+
signal?.removeEventListener("abort", onAbort);
|
|
337
|
+
};
|
|
338
|
+
const onAbort = () => {
|
|
339
|
+
cleanup2();
|
|
340
|
+
reject(signal?.reason);
|
|
341
|
+
};
|
|
342
|
+
const timer = setTimeout(() => {
|
|
343
|
+
cleanup2();
|
|
344
|
+
reject(new Error(`Search operation exceeded ${timeout}ms waiting budget`));
|
|
345
|
+
}, timeout);
|
|
346
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
347
|
+
Promise.resolve().then(() => {
|
|
348
|
+
signal?.throwIfAborted();
|
|
349
|
+
return operation(...args);
|
|
350
|
+
}).then(
|
|
351
|
+
(value) => {
|
|
352
|
+
cleanup2();
|
|
353
|
+
resolve(value);
|
|
354
|
+
},
|
|
355
|
+
(error) => {
|
|
356
|
+
cleanup2();
|
|
357
|
+
reject(error);
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
323
363
|
// src/search/reranker.ts
|
|
324
364
|
async function applyReranker(reranker, query, results) {
|
|
325
365
|
if (!reranker || results.length === 0) {
|
|
@@ -466,16 +506,21 @@ function deriveLexicalKeywords(query) {
|
|
|
466
506
|
}
|
|
467
507
|
var DEFAULT_LIMIT = 10;
|
|
468
508
|
var DEFAULT_THRESHOLD = 0.7;
|
|
469
|
-
var DEFAULT_SOURCES = ["memory", "insights", "knowledge"];
|
|
509
|
+
var DEFAULT_SOURCES = ["memory", "insights", "knowledge", "workspace"];
|
|
470
510
|
var SOURCE_SET = new Set(DEFAULT_SOURCES);
|
|
471
511
|
function normalizeUnifiedMemorySearchSources(sources) {
|
|
472
|
-
if (
|
|
512
|
+
if (sources === void 0) {
|
|
473
513
|
return [...DEFAULT_SOURCES];
|
|
474
514
|
}
|
|
515
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
516
|
+
throw new TypeError("Search sources must be a non-empty array when specified");
|
|
517
|
+
}
|
|
475
518
|
const normalized = sources.filter((source) => typeof source === "string").map((source) => source.trim().toLowerCase()).filter(
|
|
476
519
|
(source) => SOURCE_SET.has(source)
|
|
477
520
|
);
|
|
478
|
-
|
|
521
|
+
if (normalized.length !== sources.length)
|
|
522
|
+
throw new TypeError("Search sources contain an unsupported source");
|
|
523
|
+
return Array.from(new Set(normalized));
|
|
479
524
|
}
|
|
480
525
|
function clampUnifiedMemorySearchLimit(limit) {
|
|
481
526
|
const parsed = typeof limit === "number" ? limit : Number(limit ?? DEFAULT_LIMIT);
|
|
@@ -517,6 +562,20 @@ function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none"
|
|
|
517
562
|
return defaultStrategy;
|
|
518
563
|
}
|
|
519
564
|
var DEFAULT_RRF_K = 60;
|
|
565
|
+
function mergeSearchResultProvenance(preferred, other) {
|
|
566
|
+
const first = preferred.provenance;
|
|
567
|
+
const second = other.provenance;
|
|
568
|
+
const firstVersion = first?.version ?? first?.citation?.version_id;
|
|
569
|
+
const secondVersion = second?.version ?? second?.citation?.version_id;
|
|
570
|
+
const firstHash = first?.contentHash ?? first?.citation?.content_hash;
|
|
571
|
+
const secondHash = second?.contentHash ?? second?.citation?.content_hash;
|
|
572
|
+
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))
|
|
573
|
+
return preferred;
|
|
574
|
+
const metadata = { ...other.metadata, ...preferred.metadata };
|
|
575
|
+
if (!second) return { ...preferred, metadata };
|
|
576
|
+
const knownFirst = Object.fromEntries(Object.entries(first ?? {}).filter(([, value]) => value != null));
|
|
577
|
+
return { ...preferred, metadata, provenance: { ...second, ...knownFirst } };
|
|
578
|
+
}
|
|
520
579
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, weights = {}) {
|
|
521
580
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
522
581
|
return [];
|
|
@@ -530,6 +589,7 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
530
589
|
const scores = /* @__PURE__ */ new Map();
|
|
531
590
|
const order = [];
|
|
532
591
|
for (const list of lists) {
|
|
592
|
+
const seenInList = /* @__PURE__ */ new Set();
|
|
533
593
|
const channel = listNameToChannel(list.name);
|
|
534
594
|
const configuredWeight = channel ? effectiveWeights[channel] : void 0;
|
|
535
595
|
const weight = typeof configuredWeight === "number" && Number.isFinite(configuredWeight) && configuredWeight >= 0 ? configuredWeight : 1;
|
|
@@ -537,7 +597,8 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
537
597
|
for (let index = 0; index < list.hits.length; index += 1) {
|
|
538
598
|
const hit = list.hits[index];
|
|
539
599
|
const key = `${hit.type}::${hit.id}`;
|
|
540
|
-
const contribution = weight / (safeK +
|
|
600
|
+
const contribution = seenInList.has(key) ? 0 : weight / (safeK + seenInList.size + 1);
|
|
601
|
+
seenInList.add(key);
|
|
541
602
|
const existing = scores.get(key);
|
|
542
603
|
const rankedHit = hit.type === "memory" ? withMatchedEvidence(hit, list.name, index + 1) : hit;
|
|
543
604
|
if (existing) {
|
|
@@ -545,6 +606,7 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
|
|
|
545
606
|
if (hit.type === "memory" && existing.hit.type === "memory") {
|
|
546
607
|
existing.hit = mergeMatchedEvidence(existing.hit, rankedHit);
|
|
547
608
|
}
|
|
609
|
+
existing.hit = mergeSearchResultProvenance(existing.hit, rankedHit);
|
|
548
610
|
} else {
|
|
549
611
|
scores.set(key, { hit: rankedHit, rrf: contribution });
|
|
550
612
|
order.push(key);
|
|
@@ -751,14 +813,15 @@ function buildSynthesisPrompt(input) {
|
|
|
751
813
|
grouped.set(item.source, bucket);
|
|
752
814
|
}
|
|
753
815
|
const sections = [];
|
|
754
|
-
for (const tier of DEFAULT_TIERS) {
|
|
816
|
+
for (const tier of [...DEFAULT_TIERS, "workspace"]) {
|
|
755
817
|
const items = grouped.get(tier) ?? [];
|
|
756
818
|
if (items.length === 0) {
|
|
757
819
|
continue;
|
|
758
820
|
}
|
|
759
821
|
const lines = items.map((item, index) => {
|
|
760
822
|
const factSource = typeof item.metadata?.source === "string" && item.metadata.source.length > 0 ? item.metadata.source : item.source ?? "unknown";
|
|
761
|
-
|
|
823
|
+
const provenance = item.provenance ? ` provenance=${JSON.stringify(item.provenance)}` : "";
|
|
824
|
+
return ` [${index + 1}] (${item.id}, source=${factSource}, score=${item.score.toFixed(4)}) ${item.snippet}${provenance}`;
|
|
762
825
|
});
|
|
763
826
|
sections.push(`## ${tier}
|
|
764
827
|
${lines.join("\n")}`);
|
|
@@ -849,7 +912,8 @@ async function synthesizeAnswer(input) {
|
|
|
849
912
|
score: item.score,
|
|
850
913
|
// Forward per-fact provenance (e.g. `metadata.source` from
|
|
851
914
|
// `opencontext add --source`) so the synthesis prompt can cite it.
|
|
852
|
-
metadata: item.metadata
|
|
915
|
+
metadata: item.metadata,
|
|
916
|
+
provenance: item.provenance
|
|
853
917
|
}));
|
|
854
918
|
const warnings = [];
|
|
855
919
|
const complete = deps.reasoning?.complete;
|
|
@@ -1070,7 +1134,7 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
1070
1134
|
authToken: input.authToken
|
|
1071
1135
|
});
|
|
1072
1136
|
}
|
|
1073
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext) {
|
|
1137
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext, execution) {
|
|
1074
1138
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
1075
1139
|
let semantic = [];
|
|
1076
1140
|
if (typeof deps.searchRawMessagesAnn === "function") {
|
|
@@ -1098,7 +1162,10 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1098
1162
|
semantic = (await Promise.all(
|
|
1099
1163
|
filters.map((filter) => {
|
|
1100
1164
|
const botId = "botId" in filter ? filter.botId : void 0;
|
|
1101
|
-
return
|
|
1165
|
+
return boundedSearchOperation(
|
|
1166
|
+
searchRawMessagesWithChroma,
|
|
1167
|
+
deps
|
|
1168
|
+
)({
|
|
1102
1169
|
userId: input.userId,
|
|
1103
1170
|
queryEmbedding,
|
|
1104
1171
|
limit,
|
|
@@ -1107,19 +1174,21 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1107
1174
|
});
|
|
1108
1175
|
})
|
|
1109
1176
|
)).flat().map(toMemoryResult);
|
|
1177
|
+
if (execution) execution.completed++;
|
|
1110
1178
|
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
1111
1179
|
backend: "chroma",
|
|
1112
1180
|
dimensions: queryEmbedding.length,
|
|
1113
1181
|
count: semantic.length
|
|
1114
1182
|
});
|
|
1115
1183
|
} catch (error) {
|
|
1184
|
+
if (execution) execution.failed++;
|
|
1116
1185
|
logger.warn?.(
|
|
1117
1186
|
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
1118
1187
|
error
|
|
1119
1188
|
);
|
|
1120
1189
|
}
|
|
1121
1190
|
}
|
|
1122
|
-
if (semantic.length === 0 && runtimeContext !== void 0) {
|
|
1191
|
+
if (semantic.length === 0 && runtimeContext !== void 0 && !deps.searchRawMessagesAnn) {
|
|
1123
1192
|
warnApplicabilityNotEnforced(warnings);
|
|
1124
1193
|
return [];
|
|
1125
1194
|
}
|
|
@@ -1129,7 +1198,10 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1129
1198
|
const manager2 = await getRawMessageManager2();
|
|
1130
1199
|
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
1131
1200
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1132
|
-
const results = await
|
|
1201
|
+
const results = await boundedSearchOperation(
|
|
1202
|
+
manager2.searchMessagesSemantically.bind(manager2),
|
|
1203
|
+
deps
|
|
1204
|
+
)({
|
|
1133
1205
|
userId: input.userId,
|
|
1134
1206
|
queryEmbedding,
|
|
1135
1207
|
limit,
|
|
@@ -1139,6 +1211,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1139
1211
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1140
1212
|
...factTypes ? { factTypes } : {}
|
|
1141
1213
|
});
|
|
1214
|
+
if (execution) execution.completed++;
|
|
1142
1215
|
semantic = results.map((r) => ({
|
|
1143
1216
|
type: "memory",
|
|
1144
1217
|
id: r.id,
|
|
@@ -1148,6 +1221,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1148
1221
|
}));
|
|
1149
1222
|
}
|
|
1150
1223
|
} catch (error) {
|
|
1224
|
+
if (execution) execution.failed++;
|
|
1151
1225
|
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
1152
1226
|
}
|
|
1153
1227
|
}
|
|
@@ -1198,7 +1272,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
1198
1272
|
metadata: hit.metadata
|
|
1199
1273
|
};
|
|
1200
1274
|
}
|
|
1201
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext) {
|
|
1275
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext, execution) {
|
|
1202
1276
|
if (keywords.length === 0) {
|
|
1203
1277
|
return [];
|
|
1204
1278
|
}
|
|
@@ -1248,9 +1322,12 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1248
1322
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1249
1323
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1250
1324
|
const includeDeprecated = input.includeDeprecated === true;
|
|
1251
|
-
|
|
1325
|
+
const results = (await Promise.all(
|
|
1252
1326
|
filters.map(
|
|
1253
|
-
(botId) =>
|
|
1327
|
+
(botId) => boundedSearchOperation(
|
|
1328
|
+
lexicalSearchRawMessages2,
|
|
1329
|
+
deps
|
|
1330
|
+
)({
|
|
1254
1331
|
userId: input.userId,
|
|
1255
1332
|
keywords,
|
|
1256
1333
|
limit: Math.ceil(limit / filters.length),
|
|
@@ -1265,7 +1342,10 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1265
1342
|
r
|
|
1266
1343
|
)
|
|
1267
1344
|
);
|
|
1345
|
+
if (execution) execution.completed++;
|
|
1346
|
+
return results;
|
|
1268
1347
|
} catch (error) {
|
|
1348
|
+
if (execution) execution.failed++;
|
|
1269
1349
|
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
1270
1350
|
}
|
|
1271
1351
|
return [];
|
|
@@ -1312,7 +1392,21 @@ function mapHitToEvidenceSource(hit) {
|
|
|
1312
1392
|
}
|
|
1313
1393
|
return hit.type;
|
|
1314
1394
|
}
|
|
1315
|
-
function createUnifiedSearch(
|
|
1395
|
+
function createUnifiedSearch(inputDeps = {}) {
|
|
1396
|
+
const deps = {
|
|
1397
|
+
...inputDeps,
|
|
1398
|
+
embedQuery: inputDeps.embedQuery && boundedSearchOperation(inputDeps.embedQuery, inputDeps),
|
|
1399
|
+
searchRawMessagesAnn: inputDeps.searchRawMessagesAnn && boundedSearchOperation(inputDeps.searchRawMessagesAnn, inputDeps),
|
|
1400
|
+
searchRawMessagesHybrid: inputDeps.searchRawMessagesHybrid && boundedSearchOperation(inputDeps.searchRawMessagesHybrid, inputDeps),
|
|
1401
|
+
searchRawMessagesLexical: inputDeps.searchRawMessagesLexical && boundedSearchOperation(inputDeps.searchRawMessagesLexical, inputDeps),
|
|
1402
|
+
searchWorkspace: inputDeps.searchWorkspace && boundedSearchOperation(inputDeps.searchWorkspace, inputDeps),
|
|
1403
|
+
searchInsights: inputDeps.searchInsights && boundedSearchOperation(inputDeps.searchInsights, inputDeps),
|
|
1404
|
+
searchKnowledge: inputDeps.searchKnowledge && boundedSearchOperation(inputDeps.searchKnowledge, inputDeps),
|
|
1405
|
+
searchSummaries: inputDeps.searchSummaries && boundedSearchOperation(inputDeps.searchSummaries, inputDeps),
|
|
1406
|
+
getRawMessageRetrievalStatus: inputDeps.getRawMessageRetrievalStatus && boundedSearchOperation(inputDeps.getRawMessageRetrievalStatus, inputDeps),
|
|
1407
|
+
entitySearch: inputDeps.entitySearch && boundedSearchOperation(inputDeps.entitySearch, inputDeps),
|
|
1408
|
+
searchRawMessageNeighbors: inputDeps.searchRawMessageNeighbors && boundedSearchOperation(inputDeps.searchRawMessageNeighbors, inputDeps)
|
|
1409
|
+
};
|
|
1316
1410
|
const logger = deps.logger ?? console;
|
|
1317
1411
|
function resolveReasoningStrategy(input) {
|
|
1318
1412
|
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
@@ -1322,6 +1416,25 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1322
1416
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
1323
1417
|
}
|
|
1324
1418
|
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = [], runtimeContext) {
|
|
1419
|
+
const execution = { completed: 0, failed: 0 };
|
|
1420
|
+
const observe = (operation, preparation = false) => operation && (async (...args) => {
|
|
1421
|
+
try {
|
|
1422
|
+
const result = await operation(...args);
|
|
1423
|
+
if (!preparation) execution.completed++;
|
|
1424
|
+
return result;
|
|
1425
|
+
} catch (error) {
|
|
1426
|
+
execution.failed++;
|
|
1427
|
+
throw error;
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
const sourceDeps = {
|
|
1431
|
+
...deps,
|
|
1432
|
+
embedQuery: observe(deps.embedQuery, true),
|
|
1433
|
+
searchRawMessagesAnn: observe(deps.searchRawMessagesAnn),
|
|
1434
|
+
searchRawMessagesHybrid: observe(deps.searchRawMessagesHybrid),
|
|
1435
|
+
searchRawMessagesLexical: observe(deps.searchRawMessagesLexical),
|
|
1436
|
+
entitySearch: observe(deps.entitySearch)
|
|
1437
|
+
};
|
|
1325
1438
|
const sourceStartedAt = monotonicNow();
|
|
1326
1439
|
const timings = {
|
|
1327
1440
|
semanticMs: 0,
|
|
@@ -1339,9 +1452,10 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1339
1452
|
};
|
|
1340
1453
|
const finish = (results) => ({
|
|
1341
1454
|
results,
|
|
1342
|
-
timings: { ...timings, memorySourceMs: monotonicNow() - sourceStartedAt }
|
|
1455
|
+
timings: { ...timings, memorySourceMs: monotonicNow() - sourceStartedAt },
|
|
1456
|
+
availability: execution.failed > 0 ? execution.completed > 0 ? "partial" : "failed" : execution.completed > 0 || Object.values(results).some((hits) => hits.length > 0) ? "complete" : "not_ready"
|
|
1343
1457
|
});
|
|
1344
|
-
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !
|
|
1458
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !sourceDeps.reasoning?.iterativePlanner) {
|
|
1345
1459
|
warnings.push({
|
|
1346
1460
|
source: "memory",
|
|
1347
1461
|
code: "memory_iterative_planner_not_configured",
|
|
@@ -1349,7 +1463,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1349
1463
|
});
|
|
1350
1464
|
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
1351
1465
|
}
|
|
1352
|
-
if (reasoningStrategy === "rewrite" && !
|
|
1466
|
+
if (reasoningStrategy === "rewrite" && !sourceDeps.reasoning?.queryRewriter) {
|
|
1353
1467
|
warnings.push({
|
|
1354
1468
|
source: "memory",
|
|
1355
1469
|
code: "memory_query_rewrite_not_configured",
|
|
@@ -1358,8 +1472,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1358
1472
|
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
1359
1473
|
}
|
|
1360
1474
|
let unionEvidence = null;
|
|
1361
|
-
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") &&
|
|
1362
|
-
const planner =
|
|
1475
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && sourceDeps.reasoning?.iterativePlanner) {
|
|
1476
|
+
const planner = sourceDeps.reasoning.iterativePlanner;
|
|
1363
1477
|
const result = await planner.plan({
|
|
1364
1478
|
query: input.query,
|
|
1365
1479
|
...input.includeRetrievalDiagnostics ? { options: { collectDiagnostics: true } } : {},
|
|
@@ -1371,26 +1485,27 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1371
1485
|
const lexicalHits = await measure(
|
|
1372
1486
|
"lexicalMs",
|
|
1373
1487
|
() => runLexicalSearchForKeywords(
|
|
1374
|
-
|
|
1488
|
+
sourceDeps,
|
|
1375
1489
|
input,
|
|
1376
1490
|
keywords2,
|
|
1377
1491
|
limit,
|
|
1378
1492
|
logger,
|
|
1379
1493
|
peerPeers,
|
|
1380
1494
|
warnings,
|
|
1381
|
-
runtimeContext
|
|
1495
|
+
runtimeContext,
|
|
1496
|
+
execution
|
|
1382
1497
|
)
|
|
1383
1498
|
);
|
|
1384
1499
|
let semanticHits = [];
|
|
1385
|
-
if (typeof
|
|
1500
|
+
if (typeof sourceDeps.embedQuery === "function") {
|
|
1386
1501
|
try {
|
|
1387
1502
|
semanticHits = await measure("semanticMs", async () => {
|
|
1388
1503
|
const semanticQuery = keywords2.join(" ");
|
|
1389
|
-
const queryEmbedder =
|
|
1504
|
+
const queryEmbedder = sourceDeps.embedQuery;
|
|
1390
1505
|
if (typeof queryEmbedder !== "function") return [];
|
|
1391
1506
|
const queryEmbedding = await embedQueryVariant(queryEmbedder, input, semanticQuery);
|
|
1392
1507
|
return runSemanticSearchForEmbedding(
|
|
1393
|
-
|
|
1508
|
+
sourceDeps,
|
|
1394
1509
|
input,
|
|
1395
1510
|
queryEmbedding,
|
|
1396
1511
|
limit,
|
|
@@ -1398,7 +1513,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1398
1513
|
logger,
|
|
1399
1514
|
peerPeers,
|
|
1400
1515
|
warnings,
|
|
1401
|
-
runtimeContext
|
|
1516
|
+
runtimeContext,
|
|
1517
|
+
execution
|
|
1402
1518
|
);
|
|
1403
1519
|
});
|
|
1404
1520
|
} catch (error) {
|
|
@@ -1451,66 +1567,74 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1451
1567
|
}
|
|
1452
1568
|
unionEvidence = evidence;
|
|
1453
1569
|
}
|
|
1454
|
-
|
|
1570
|
+
const lexicalFallback = async () => {
|
|
1571
|
+
const lexical2 = await runLexicalSearchForKeywords(
|
|
1572
|
+
sourceDeps,
|
|
1573
|
+
input,
|
|
1574
|
+
deriveLexicalKeywords(input.query),
|
|
1575
|
+
limit,
|
|
1576
|
+
logger,
|
|
1577
|
+
peerPeers,
|
|
1578
|
+
warnings,
|
|
1579
|
+
runtimeContext,
|
|
1580
|
+
execution
|
|
1581
|
+
);
|
|
1582
|
+
return finish({ semantic: [], lexical: filterByDateRange(lexical2, input.dateFrom, input.dateTo) });
|
|
1583
|
+
};
|
|
1584
|
+
if (typeof sourceDeps.embedQuery !== "function") {
|
|
1455
1585
|
warnings.push({
|
|
1456
1586
|
source: "memory",
|
|
1457
1587
|
code: "memory_lexical_search_fallback",
|
|
1458
1588
|
message: "No embedding provider is configured; using lexical (BM25) search as fallback. Pass an `embedQuery` function via `unified.embedQuery` to enable semantic retrieval."
|
|
1459
1589
|
});
|
|
1460
|
-
|
|
1461
|
-
const lexical2 = filterByDateRange(
|
|
1462
|
-
await runLexicalSearchForKeywords(
|
|
1463
|
-
deps,
|
|
1464
|
-
input,
|
|
1465
|
-
keywords2,
|
|
1466
|
-
limit,
|
|
1467
|
-
logger,
|
|
1468
|
-
peerPeers,
|
|
1469
|
-
warnings,
|
|
1470
|
-
runtimeContext
|
|
1471
|
-
),
|
|
1472
|
-
input.dateFrom,
|
|
1473
|
-
input.dateTo
|
|
1474
|
-
);
|
|
1475
|
-
return finish({ semantic: [], lexical: lexical2 });
|
|
1590
|
+
return lexicalFallback();
|
|
1476
1591
|
}
|
|
1477
1592
|
let semantic = [];
|
|
1478
1593
|
let lexicalQueries = [input.query];
|
|
1479
|
-
const embedQuery =
|
|
1594
|
+
const embedQuery = sourceDeps.embedQuery;
|
|
1480
1595
|
const activeMergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1481
|
-
input.mergeStrategy ??
|
|
1596
|
+
input.mergeStrategy ?? sourceDeps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1482
1597
|
);
|
|
1483
|
-
if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof
|
|
1484
|
-
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1598
|
+
if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof sourceDeps.searchRawMessagesHybrid === "function") {
|
|
1599
|
+
try {
|
|
1600
|
+
const hybrid = await measure("hybridMs", async () => {
|
|
1601
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
1602
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1603
|
+
return (await Promise.all(
|
|
1604
|
+
filters.map(
|
|
1605
|
+
(botId) => sourceDeps.searchRawMessagesHybrid?.({
|
|
1606
|
+
userId: input.userId,
|
|
1607
|
+
query: input.query,
|
|
1608
|
+
queryEmbedding,
|
|
1609
|
+
limit: Math.ceil(limit / filters.length),
|
|
1610
|
+
botId,
|
|
1611
|
+
...runtimeContext ?? {}
|
|
1612
|
+
})
|
|
1613
|
+
)
|
|
1614
|
+
)).flatMap((items) => items ?? []).filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
1615
|
+
});
|
|
1616
|
+
return finish({
|
|
1617
|
+
semantic: [],
|
|
1618
|
+
lexical: [],
|
|
1619
|
+
hybrid: filterByDateRange(hybrid, input.dateFrom, input.dateTo)
|
|
1620
|
+
});
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
warnings.push({
|
|
1623
|
+
source: "memory",
|
|
1624
|
+
code: "memory_hybrid_search_failed",
|
|
1625
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1626
|
+
});
|
|
1627
|
+
return lexicalFallback();
|
|
1628
|
+
}
|
|
1505
1629
|
}
|
|
1506
|
-
if (reasoningStrategy === "rewrite" &&
|
|
1630
|
+
if (reasoningStrategy === "rewrite" && sourceDeps.reasoning?.queryRewriter) {
|
|
1507
1631
|
try {
|
|
1508
|
-
const variants = await
|
|
1632
|
+
const variants = await sourceDeps.reasoning.queryRewriter.rewrite({
|
|
1509
1633
|
query: input.query,
|
|
1510
1634
|
userId: input.userId,
|
|
1511
1635
|
authToken: input.authToken
|
|
1512
1636
|
});
|
|
1513
|
-
if (
|
|
1637
|
+
if (sourceDeps.reasoning.rewriteLexical === true) {
|
|
1514
1638
|
const seen = /* @__PURE__ */ new Set();
|
|
1515
1639
|
lexicalQueries = [input.query, ...variants].map((query) => query.trim()).filter((query) => {
|
|
1516
1640
|
const key = query.toLowerCase();
|
|
@@ -1521,7 +1645,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1521
1645
|
}
|
|
1522
1646
|
if (reasoningInfo) {
|
|
1523
1647
|
reasoningInfo.rewrittenQueries = variants;
|
|
1524
|
-
if (
|
|
1648
|
+
if (sourceDeps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
1525
1649
|
reasoningInfo.degraded = true;
|
|
1526
1650
|
}
|
|
1527
1651
|
}
|
|
@@ -1532,7 +1656,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1532
1656
|
return Promise.all(
|
|
1533
1657
|
embeddings.map(
|
|
1534
1658
|
(embedding) => runSemanticSearchForEmbedding(
|
|
1535
|
-
|
|
1659
|
+
sourceDeps,
|
|
1536
1660
|
input,
|
|
1537
1661
|
embedding,
|
|
1538
1662
|
limit,
|
|
@@ -1540,12 +1664,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1540
1664
|
logger,
|
|
1541
1665
|
peerPeers,
|
|
1542
1666
|
warnings,
|
|
1543
|
-
runtimeContext
|
|
1667
|
+
runtimeContext,
|
|
1668
|
+
execution
|
|
1544
1669
|
)
|
|
1545
1670
|
)
|
|
1546
1671
|
);
|
|
1547
1672
|
});
|
|
1548
|
-
if (activeMergeStrategy === "rrf" &&
|
|
1673
|
+
if (activeMergeStrategy === "rrf" && sourceDeps.reasoning.rewriteSemanticMerge === "rrf" && lists.length > 1 && lists.some((hits) => hits.length > 0)) {
|
|
1549
1674
|
semantic = mergeUnifiedMemorySearchResultsRrf(
|
|
1550
1675
|
lists.map((hits, index) => ({
|
|
1551
1676
|
name: `memory-semantic-query:${index}`,
|
|
@@ -1568,24 +1693,33 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1568
1693
|
}
|
|
1569
1694
|
}
|
|
1570
1695
|
if (semantic.length === 0) {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1696
|
+
try {
|
|
1697
|
+
semantic = await measure("semanticMs", async () => {
|
|
1698
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
1699
|
+
return runSemanticSearchForEmbedding(
|
|
1700
|
+
sourceDeps,
|
|
1701
|
+
input,
|
|
1702
|
+
queryEmbedding,
|
|
1703
|
+
limit,
|
|
1704
|
+
threshold,
|
|
1705
|
+
logger,
|
|
1706
|
+
peerPeers,
|
|
1707
|
+
warnings,
|
|
1708
|
+
runtimeContext,
|
|
1709
|
+
execution
|
|
1710
|
+
);
|
|
1711
|
+
});
|
|
1712
|
+
} catch (error) {
|
|
1713
|
+
warnings.push({
|
|
1714
|
+
source: "memory",
|
|
1715
|
+
code: "memory_semantic_search_failed",
|
|
1716
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1585
1719
|
}
|
|
1586
1720
|
let lexical = [];
|
|
1587
1721
|
const keywords = deriveLexicalKeywords(input.query);
|
|
1588
|
-
if (activeMergeStrategy === "rrf" && lexicalQueries.length > 1 && typeof
|
|
1722
|
+
if (activeMergeStrategy === "rrf" && lexicalQueries.length > 1 && typeof sourceDeps.searchRawMessagesLexical === "function") {
|
|
1589
1723
|
const lists = await Promise.all(
|
|
1590
1724
|
lexicalQueries.map(async (query) => ({
|
|
1591
1725
|
name: "memory-bm25",
|
|
@@ -1593,14 +1727,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1593
1727
|
await measure(
|
|
1594
1728
|
"lexicalMs",
|
|
1595
1729
|
() => runLexicalSearchForKeywords(
|
|
1596
|
-
|
|
1730
|
+
sourceDeps,
|
|
1597
1731
|
input,
|
|
1598
1732
|
deriveLexicalKeywords(query),
|
|
1599
1733
|
limit,
|
|
1600
1734
|
logger,
|
|
1601
1735
|
peerPeers,
|
|
1602
1736
|
warnings,
|
|
1603
|
-
runtimeContext
|
|
1737
|
+
runtimeContext,
|
|
1738
|
+
execution
|
|
1604
1739
|
)
|
|
1605
1740
|
),
|
|
1606
1741
|
"memory-bm25"
|
|
@@ -1613,11 +1748,11 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1613
1748
|
}));
|
|
1614
1749
|
if (reasoningInfo) reasoningInfo.lexicalRewrittenQueries = lexicalQueries;
|
|
1615
1750
|
} else if (keywords.length > 0) {
|
|
1616
|
-
if (typeof
|
|
1751
|
+
if (typeof sourceDeps.searchRawMessagesLexical === "function") {
|
|
1617
1752
|
const lexicalStartedAt = monotonicNow();
|
|
1618
1753
|
try {
|
|
1619
1754
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1620
|
-
const searchRawMessagesLexical =
|
|
1755
|
+
const searchRawMessagesLexical = sourceDeps.searchRawMessagesLexical;
|
|
1621
1756
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1622
1757
|
const includeDeprecated = input.includeDeprecated === true;
|
|
1623
1758
|
lexical = (await Promise.all(
|
|
@@ -1657,16 +1792,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1657
1792
|
}
|
|
1658
1793
|
}
|
|
1659
1794
|
let entity;
|
|
1660
|
-
if (typeof
|
|
1795
|
+
if (typeof sourceDeps.entitySearch === "function") {
|
|
1661
1796
|
entity = filterByDateRange(
|
|
1662
|
-
await runEntitySearchForQuery(
|
|
1797
|
+
await runEntitySearchForQuery(sourceDeps, input, limit, logger, peerPeers, runtimeContext),
|
|
1663
1798
|
input.dateFrom,
|
|
1664
1799
|
input.dateTo
|
|
1665
1800
|
);
|
|
1666
1801
|
if (entity.length === 0) {
|
|
1667
1802
|
entity = void 0;
|
|
1668
1803
|
} else if (normalizeUnifiedMemoryMergeStrategy(
|
|
1669
|
-
input.mergeStrategy ??
|
|
1804
|
+
input.mergeStrategy ?? sourceDeps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1670
1805
|
) !== "rrf") {
|
|
1671
1806
|
warnings.push({
|
|
1672
1807
|
source: "memory",
|
|
@@ -1697,6 +1832,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1697
1832
|
const searchStartedAt = monotonicNow();
|
|
1698
1833
|
const query = input.query.trim();
|
|
1699
1834
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
1835
|
+
const sourceStates = [];
|
|
1700
1836
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1701
1837
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1702
1838
|
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
@@ -1722,7 +1858,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1722
1858
|
sources,
|
|
1723
1859
|
results: [],
|
|
1724
1860
|
count: 0,
|
|
1725
|
-
warnings
|
|
1861
|
+
warnings,
|
|
1862
|
+
sourceStates
|
|
1726
1863
|
};
|
|
1727
1864
|
}
|
|
1728
1865
|
let memorySubs;
|
|
@@ -1733,6 +1870,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1733
1870
|
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1734
1871
|
}
|
|
1735
1872
|
if (sources.includes("memory")) {
|
|
1873
|
+
let availability = "not_ready";
|
|
1736
1874
|
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesHybrid === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1737
1875
|
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1738
1876
|
try {
|
|
@@ -1747,6 +1885,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1747
1885
|
runtimeContext
|
|
1748
1886
|
);
|
|
1749
1887
|
memorySubs = memoryRun.results;
|
|
1888
|
+
availability = memoryRun.availability;
|
|
1750
1889
|
memoryTimings = memoryRun.timings;
|
|
1751
1890
|
memorySubs = {
|
|
1752
1891
|
semantic: dedupeChannelByParent(memorySubs.semantic, "memory-semantic"),
|
|
@@ -1771,11 +1910,12 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1771
1910
|
warnings.push({ source: "memory", code, message });
|
|
1772
1911
|
}
|
|
1773
1912
|
} catch (error) {
|
|
1913
|
+
availability = availability === "complete" || availability === "partial" ? "partial" : "failed";
|
|
1774
1914
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1775
1915
|
warnings.push({
|
|
1776
1916
|
source: "memory",
|
|
1777
1917
|
code: "memory_search_failed",
|
|
1778
|
-
message: `Memory search failed: ${error.message ?? "Unknown error"}
|
|
1918
|
+
message: `Memory search failed: ${error.message ?? "Unknown error"}.`
|
|
1779
1919
|
});
|
|
1780
1920
|
}
|
|
1781
1921
|
} else {
|
|
@@ -1785,6 +1925,51 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1785
1925
|
message: "Raw memory storage is not available in this environment."
|
|
1786
1926
|
});
|
|
1787
1927
|
}
|
|
1928
|
+
const count = new Set(
|
|
1929
|
+
[
|
|
1930
|
+
...memorySubs?.semantic ?? [],
|
|
1931
|
+
...memorySubs?.lexical ?? [],
|
|
1932
|
+
...memorySubs?.hybrid ?? [],
|
|
1933
|
+
...memorySubs?.planner ?? [],
|
|
1934
|
+
...memorySubs?.entity ?? []
|
|
1935
|
+
].map((hit) => hit.id)
|
|
1936
|
+
).size;
|
|
1937
|
+
if (availability === "complete" && count === 0 && retrievalStatus?.lexicalReady === false && (!retrievalStatus.semanticReady || !deps.embedQuery)) {
|
|
1938
|
+
availability = "not_ready";
|
|
1939
|
+
}
|
|
1940
|
+
const memoryWarnings = warnings.filter((warning) => warning.source === "memory");
|
|
1941
|
+
sourceStates.push({
|
|
1942
|
+
source: "memory",
|
|
1943
|
+
status: availability,
|
|
1944
|
+
count,
|
|
1945
|
+
...memoryWarnings.length ? { message: memoryWarnings.map((warning) => warning.message).join("; ") } : {}
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
const workspaceHits = [];
|
|
1949
|
+
if (sources.includes("workspace")) {
|
|
1950
|
+
if (!deps.searchWorkspace) {
|
|
1951
|
+
sourceStates.push({ source: "workspace", status: "not_configured", count: 0 });
|
|
1952
|
+
} else {
|
|
1953
|
+
try {
|
|
1954
|
+
const result = await deps.searchWorkspace({
|
|
1955
|
+
userId: input.userId,
|
|
1956
|
+
query,
|
|
1957
|
+
limit: candidateLimit,
|
|
1958
|
+
...runtimeContext ?? {}
|
|
1959
|
+
});
|
|
1960
|
+
workspaceHits.push(...result.hits.map((hit) => ({ ...hit, type: "workspace" })));
|
|
1961
|
+
sourceStates.push({
|
|
1962
|
+
source: "workspace",
|
|
1963
|
+
status: result.status,
|
|
1964
|
+
count: result.hits.length,
|
|
1965
|
+
...result.message ? { message: result.message } : {}
|
|
1966
|
+
});
|
|
1967
|
+
} catch (error) {
|
|
1968
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1969
|
+
sourceStates.push({ source: "workspace", status: "failed", count: 0, message });
|
|
1970
|
+
warnings.push({ source: "workspace", code: "workspace_search_failed", message });
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1788
1973
|
}
|
|
1789
1974
|
const insightHits = [];
|
|
1790
1975
|
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
@@ -1806,10 +1991,18 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1806
1991
|
id: hit.id,
|
|
1807
1992
|
content: hit.content,
|
|
1808
1993
|
similarity: hit.similarity,
|
|
1809
|
-
metadata: hit.metadata
|
|
1994
|
+
metadata: hit.metadata,
|
|
1995
|
+
provenance: hit.provenance
|
|
1810
1996
|
});
|
|
1811
1997
|
}
|
|
1998
|
+
sourceStates.push({ source: "insights", status: "complete", count: insightHits.length });
|
|
1812
1999
|
} catch (error) {
|
|
2000
|
+
sourceStates.push({
|
|
2001
|
+
source: "insights",
|
|
2002
|
+
status: "failed",
|
|
2003
|
+
count: 0,
|
|
2004
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2005
|
+
});
|
|
1813
2006
|
logger.warn?.("[memory-store] insights source failed:", error);
|
|
1814
2007
|
warnings.push({
|
|
1815
2008
|
source: "insights",
|
|
@@ -1817,7 +2010,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1817
2010
|
message: error.message ?? "insights_search_failed"
|
|
1818
2011
|
});
|
|
1819
2012
|
}
|
|
1820
|
-
} else if (sources.includes("insights"))
|
|
2013
|
+
} else if (sources.includes("insights")) {
|
|
2014
|
+
sourceStates.push({ source: "insights", status: "not_configured", count: 0 });
|
|
2015
|
+
}
|
|
1821
2016
|
const knowledgeHits = [];
|
|
1822
2017
|
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
1823
2018
|
try {
|
|
@@ -1836,7 +2031,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1836
2031
|
for (const hit of hits) {
|
|
1837
2032
|
knowledgeHits.push(toKnowledgeResult(hit));
|
|
1838
2033
|
}
|
|
2034
|
+
sourceStates.push({ source: "knowledge", status: "complete", count: knowledgeHits.length });
|
|
1839
2035
|
} catch (error) {
|
|
2036
|
+
sourceStates.push({
|
|
2037
|
+
source: "knowledge",
|
|
2038
|
+
status: "failed",
|
|
2039
|
+
count: 0,
|
|
2040
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2041
|
+
});
|
|
1840
2042
|
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
1841
2043
|
warnings.push({
|
|
1842
2044
|
source: "knowledge",
|
|
@@ -1844,12 +2046,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1844
2046
|
message: error.message ?? "knowledge_search_failed"
|
|
1845
2047
|
});
|
|
1846
2048
|
}
|
|
1847
|
-
} else if (sources.includes("knowledge"))
|
|
2049
|
+
} else if (sources.includes("knowledge")) {
|
|
2050
|
+
sourceStates.push({ source: "knowledge", status: "not_configured", count: 0 });
|
|
2051
|
+
}
|
|
1848
2052
|
const fusionStartedAt = monotonicNow();
|
|
1849
2053
|
const merged = mergeAcrossSources({
|
|
1850
2054
|
memorySubs,
|
|
1851
2055
|
insightHits,
|
|
1852
2056
|
knowledgeHits,
|
|
2057
|
+
workspaceHits,
|
|
1853
2058
|
limit: candidateLimit,
|
|
1854
2059
|
strategy: mergeStrategy,
|
|
1855
2060
|
rrfK,
|
|
@@ -1876,7 +2081,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1876
2081
|
sources,
|
|
1877
2082
|
results: ranked,
|
|
1878
2083
|
count: ranked.length,
|
|
1879
|
-
warnings
|
|
2084
|
+
warnings,
|
|
2085
|
+
sourceStates
|
|
1880
2086
|
};
|
|
1881
2087
|
if (input.includeRetrievalDiagnostics) {
|
|
1882
2088
|
output.retrievalDiagnostics = {
|
|
@@ -1986,15 +2192,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1986
2192
|
evidence: [],
|
|
1987
2193
|
count: 0,
|
|
1988
2194
|
warnings: peerScope.warnings,
|
|
2195
|
+
sourceStates: [],
|
|
1989
2196
|
answer: ""
|
|
1990
2197
|
};
|
|
1991
2198
|
return empty;
|
|
1992
2199
|
}
|
|
1993
|
-
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
|
|
2200
|
+
const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge", "workspace"] : ["raw", "insight", "knowledge", "workspace"];
|
|
1994
2201
|
const wantsSummaryTier = tiers.includes("summary");
|
|
1995
2202
|
const wantsRawTier = tiers.includes("raw");
|
|
1996
2203
|
const wantsInsightTier = tiers.includes("insight");
|
|
1997
2204
|
const wantsKnowledgeTier = tiers.includes("knowledge");
|
|
2205
|
+
const wantsWorkspaceTier = tiers.includes("workspace");
|
|
1998
2206
|
const warnings = [...peerScope.warnings];
|
|
1999
2207
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
2000
2208
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
@@ -2002,11 +2210,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2002
2210
|
let sources = input.sources ? [...input.sources] : [];
|
|
2003
2211
|
let unifiedReasoning;
|
|
2004
2212
|
let retrievalDiagnostics;
|
|
2005
|
-
|
|
2213
|
+
let sourceStates = [];
|
|
2214
|
+
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier || wantsWorkspaceTier) {
|
|
2006
2215
|
const inferredSources = sources.length > 0 ? sources : [
|
|
2007
2216
|
...wantsRawTier ? ["memory"] : [],
|
|
2008
2217
|
...wantsInsightTier ? ["insights"] : [],
|
|
2009
|
-
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
2218
|
+
...wantsKnowledgeTier ? ["knowledge"] : [],
|
|
2219
|
+
...wantsWorkspaceTier ? ["workspace"] : []
|
|
2010
2220
|
];
|
|
2011
2221
|
const unified = await searchUnifiedMemoryWithRuntime(
|
|
2012
2222
|
{
|
|
@@ -2019,6 +2229,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2019
2229
|
sources = unified.sources;
|
|
2020
2230
|
unifiedReasoning = unified.reasoning;
|
|
2021
2231
|
retrievalDiagnostics = unified.retrievalDiagnostics;
|
|
2232
|
+
sourceStates = unified.sourceStates;
|
|
2022
2233
|
warnings.push(...unified.warnings);
|
|
2023
2234
|
}
|
|
2024
2235
|
let summaryHits = [];
|
|
@@ -2057,6 +2268,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2057
2268
|
source: mapHitToEvidenceSource(hit),
|
|
2058
2269
|
snippet: hit.content,
|
|
2059
2270
|
score: hit.similarity,
|
|
2271
|
+
provenance: {
|
|
2272
|
+
citation: hit.provenance?.citation ?? null,
|
|
2273
|
+
origin: hit.provenance?.origin ?? null,
|
|
2274
|
+
businessReference: hit.provenance?.businessReference ?? null,
|
|
2275
|
+
version: hit.provenance?.version ?? hit.provenance?.citation?.version_id ?? null,
|
|
2276
|
+
contentHash: hit.provenance?.contentHash ?? hit.provenance?.citation?.content_hash ?? null,
|
|
2277
|
+
scope: hit.provenance?.scope ?? null,
|
|
2278
|
+
derivedFrom: hit.provenance?.derivedFrom ?? null,
|
|
2279
|
+
confirmation: hit.provenance?.confirmation ?? null,
|
|
2280
|
+
freshness: hit.provenance?.freshness ?? null
|
|
2281
|
+
},
|
|
2060
2282
|
timestamp: getCandidateTimestamp(hit.metadata),
|
|
2061
2283
|
metadata: hit.metadata
|
|
2062
2284
|
}));
|
|
@@ -2066,7 +2288,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2066
2288
|
results: hits,
|
|
2067
2289
|
evidence,
|
|
2068
2290
|
count: hits.length,
|
|
2069
|
-
warnings
|
|
2291
|
+
warnings,
|
|
2292
|
+
sourceStates
|
|
2070
2293
|
};
|
|
2071
2294
|
if (unifiedReasoning) {
|
|
2072
2295
|
base.reasoning = unifiedReasoning;
|
|
@@ -2089,8 +2312,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
2089
2312
|
return base;
|
|
2090
2313
|
}
|
|
2091
2314
|
async function search(input, runtimeContext) {
|
|
2315
|
+
deps.signal?.throwIfAborted();
|
|
2316
|
+
if (input.sources !== void 0) normalizeUnifiedMemorySearchSources(input.sources);
|
|
2092
2317
|
const resolvedRuntime = resolveSearchRuntimeContext(input, runtimeContext);
|
|
2093
|
-
|
|
2318
|
+
const runSearch = boundedSearchOperation(() => searchWithResolvedRuntime(input, resolvedRuntime), {
|
|
2319
|
+
signal: deps.signal,
|
|
2320
|
+
sourceTimeoutMs: deps.searchTimeoutMs ?? 3e4
|
|
2321
|
+
});
|
|
2322
|
+
const result = await runSearch();
|
|
2323
|
+
deps.signal?.throwIfAborted();
|
|
2324
|
+
return result;
|
|
2094
2325
|
}
|
|
2095
2326
|
return {
|
|
2096
2327
|
search,
|
|
@@ -2205,6 +2436,7 @@ function selectSessionNeighborResults(results, options, limit) {
|
|
|
2205
2436
|
function mergeAcrossSources(input) {
|
|
2206
2437
|
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
2207
2438
|
const lists = buildChannelLists(memorySubs, input.insightHits, input.knowledgeHits);
|
|
2439
|
+
if (input.workspaceHits.length) lists.push({ name: "workspace", hits: input.workspaceHits });
|
|
2208
2440
|
if (input.strategy !== "rrf") {
|
|
2209
2441
|
const seen = /* @__PURE__ */ new Map();
|
|
2210
2442
|
for (const hit of [
|
|
@@ -2213,7 +2445,8 @@ function mergeAcrossSources(input) {
|
|
|
2213
2445
|
...memorySubs.planner ?? [],
|
|
2214
2446
|
...memorySubs.hybrid ?? [],
|
|
2215
2447
|
...input.insightHits,
|
|
2216
|
-
...input.knowledgeHits
|
|
2448
|
+
...input.knowledgeHits,
|
|
2449
|
+
...input.workspaceHits
|
|
2217
2450
|
]) {
|
|
2218
2451
|
const key = `${hit.type}::${hit.id}`;
|
|
2219
2452
|
const existing = seen.get(key);
|
|
@@ -2224,7 +2457,10 @@ function mergeAcrossSources(input) {
|
|
|
2224
2457
|
const other = preferred === hit ? existing : hit;
|
|
2225
2458
|
seen.set(
|
|
2226
2459
|
key,
|
|
2227
|
-
|
|
2460
|
+
mergeSearchResultProvenance(
|
|
2461
|
+
preferred.type === "memory" && other.type === "memory" ? mergeMatchedEvidence(preferred, other) : preferred,
|
|
2462
|
+
other
|
|
2463
|
+
)
|
|
2228
2464
|
);
|
|
2229
2465
|
}
|
|
2230
2466
|
}
|
|
@@ -2240,7 +2476,8 @@ function mergeAcrossSources(input) {
|
|
|
2240
2476
|
...memorySubs.hybrid ?? [],
|
|
2241
2477
|
...memorySubs.entity ?? [],
|
|
2242
2478
|
...input.insightHits,
|
|
2243
|
-
...input.knowledgeHits
|
|
2479
|
+
...input.knowledgeHits,
|
|
2480
|
+
...input.workspaceHits
|
|
2244
2481
|
];
|
|
2245
2482
|
const merged = mergeUnifiedMemorySearchResultsRrf(
|
|
2246
2483
|
lists.length > 0 ? lists : [{ name: "memory-semantic", hits: all }],
|
|
@@ -2335,7 +2572,8 @@ async function applyReflectedPlan(deps, store, input, _logger) {
|
|
|
2335
2572
|
source: item.source ?? "raw",
|
|
2336
2573
|
snippet: item.snippet,
|
|
2337
2574
|
score: item.score,
|
|
2338
|
-
timestamp: item.timestamp
|
|
2575
|
+
timestamp: item.timestamp,
|
|
2576
|
+
provenance: item.provenance
|
|
2339
2577
|
}));
|
|
2340
2578
|
const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
|
|
2341
2579
|
const applyInput = {
|
|
@@ -3084,10 +3322,16 @@ async function startHttpServer(options = {}) {
|
|
|
3084
3322
|
}
|
|
3085
3323
|
const wantsSynthesis = typeof body.synthesize === "boolean" ? body.synthesize : typeof body.synthesize === "object" && body.synthesize !== null;
|
|
3086
3324
|
const responseSchema = typeof body.synthesize === "object" && body.synthesize !== null ? body.synthesize.responseSchema ?? body.responseSchema : body.responseSchema;
|
|
3325
|
+
let sources;
|
|
3326
|
+
try {
|
|
3327
|
+
if (body.sources !== void 0) sources = normalizeUnifiedMemorySearchSources(body.sources);
|
|
3328
|
+
} catch (error) {
|
|
3329
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
3330
|
+
}
|
|
3087
3331
|
const input = {
|
|
3088
3332
|
userId,
|
|
3089
3333
|
query,
|
|
3090
|
-
sources
|
|
3334
|
+
sources,
|
|
3091
3335
|
tiers: Array.isArray(body.tiers) ? body.tiers : void 0,
|
|
3092
3336
|
limit: typeof body.limit === "number" ? body.limit : void 0,
|
|
3093
3337
|
candidateLimit: typeof body.candidateLimit === "number" ? body.candidateLimit : void 0,
|