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