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