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