@anuma/sdk 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1955,6 +1955,36 @@ async function getMemoriesByEntityNamesOp(ctx, entityNames) {
1955
1955
  }
1956
1956
  return out;
1957
1957
  }
1958
+ async function getEntitiesByMemoryIdsOp(ctx, memoryIds) {
1959
+ const unique = Array.from(new Set(memoryIds.filter((id) => id.length > 0)));
1960
+ if (unique.length === 0) return /* @__PURE__ */ new Map();
1961
+ const linkConditions = [Q2.where("memory_id", Q2.oneOf(unique))];
1962
+ if (ctx.userId !== void 0) {
1963
+ if (ctx.allowUnscopedRows) {
1964
+ linkConditions.push(Q2.or(Q2.where("user_id", ctx.userId), Q2.where("user_id", null)));
1965
+ } else {
1966
+ linkConditions.push(Q2.where("user_id", ctx.userId));
1967
+ }
1968
+ }
1969
+ const links = await ctx.memoryEntityCollection.query(...linkConditions).fetch();
1970
+ if (links.length === 0) return /* @__PURE__ */ new Map();
1971
+ const entityIds = Array.from(new Set(links.map((l) => String(l.entityId))));
1972
+ const entityRows = await ctx.entityCollection.query(Q2.where("id", Q2.oneOf(entityIds))).fetch();
1973
+ const entityIdToName = new Map(entityRows.map((e) => [e.id, e.canonicalName]));
1974
+ const out = /* @__PURE__ */ new Map();
1975
+ for (const link of links) {
1976
+ const entityName = entityIdToName.get(String(link.entityId));
1977
+ if (!entityName) continue;
1978
+ const memoryId = String(link.memoryId);
1979
+ let bucket = out.get(memoryId);
1980
+ if (!bucket) {
1981
+ bucket = /* @__PURE__ */ new Set();
1982
+ out.set(memoryId, bucket);
1983
+ }
1984
+ bucket.add(entityName);
1985
+ }
1986
+ return out;
1987
+ }
1958
1988
 
1959
1989
  // src/lib/db/memoryVault/operations.ts
1960
1990
  import { Q as Q3 } from "@nozbe/watermelondb";
@@ -1992,11 +2022,18 @@ function isOwnedByCtxUser(ctx, record) {
1992
2022
  function baseVaultConditions(ctx, options) {
1993
2023
  return [
1994
2024
  ...options?.includeDeleted ? [] : [Q3.where("is_deleted", false)],
2025
+ ...options?.includeArchived ? [] : [Q3.where("archived_at", Q3.eq(null))],
2026
+ ...options?.includeQuarantined ? [] : [Q3.where("trust_tier", Q3.notEq("quarantined"))],
1995
2027
  ...options?.includeSuperseded ? [] : [Q3.where("superseded_by", null)],
1996
2028
  ...ctx.userId !== void 0 ? [Q3.where("user_id", ctx.userId)] : [],
1997
2029
  ...options?.since ? [Q3.where("updated_at", Q3.gt(options.since.getTime()))] : []
1998
2030
  ];
1999
2031
  }
2032
+ var KNOWN_TRUST_TIERS = /* @__PURE__ */ new Set(["quarantined", "trusted"]);
2033
+ function normalizeTrustTier(value) {
2034
+ if (value === void 0 || value === null) return null;
2035
+ return KNOWN_TRUST_TIERS.has(value) ? value : null;
2036
+ }
2000
2037
  async function mapInBatches(items, fn) {
2001
2038
  const BATCH = 50;
2002
2039
  const results = [];
@@ -2037,6 +2074,9 @@ function vaultMemoryToStoredRaw(memory) {
2037
2074
  supersededBy: memory.supersededBy ?? null,
2038
2075
  supersededAt: memory.supersededAt ?? null,
2039
2076
  lastObservedAt: memory.lastObservedAt ?? null,
2077
+ factType: memory.factType ?? null,
2078
+ archivedAt: memory.archivedAt ?? null,
2079
+ trustTier: memory.trustTier ?? null,
2040
2080
  createdAt: memory.createdAt,
2041
2081
  updatedAt: memory.updatedAt,
2042
2082
  isDeleted: memory.isDeleted
@@ -2078,6 +2118,12 @@ async function createVaultMemoryOp(ctx, opts) {
2078
2118
  record._setRaw("event_time_end", opts.eventTime.end ?? null);
2079
2119
  record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
2080
2120
  }
2121
+ if (opts.factType !== void 0) {
2122
+ record._setRaw("fact_type", opts.factType);
2123
+ }
2124
+ if (opts.trustTier !== void 0) {
2125
+ record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
2126
+ }
2081
2127
  });
2082
2128
  });
2083
2129
  return vaultMemoryToStored(created, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
@@ -2218,6 +2264,12 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
2218
2264
  record._setRaw("event_time_end", opts.eventTime.end ?? null);
2219
2265
  record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
2220
2266
  }
2267
+ if (opts.factType !== void 0) {
2268
+ record._setRaw("fact_type", opts.factType);
2269
+ }
2270
+ if (opts.trustTier !== void 0) {
2271
+ record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
2272
+ }
2221
2273
  })
2222
2274
  );
2223
2275
  await ctx.database.batch(...prepared);
@@ -2278,6 +2330,9 @@ function vaultMemoryRawToStoredRaw(raw) {
2278
2330
  supersededBy: raw.superseded_by ?? null,
2279
2331
  supersededAt: raw.superseded_at ?? null,
2280
2332
  lastObservedAt: raw.last_observed_at ?? null,
2333
+ factType: raw.fact_type ?? null,
2334
+ archivedAt: raw.archived_at ?? null,
2335
+ trustTier: raw.trust_tier ?? null,
2281
2336
  createdAt: new Date(raw.created_at),
2282
2337
  updatedAt: new Date(raw.updated_at),
2283
2338
  isDeleted: raw.is_deleted === true || raw.is_deleted === 1
@@ -2295,6 +2350,7 @@ async function getAllVaultMemoriesOp(ctx, options) {
2295
2350
  ...baseVaultConditions(ctx, options),
2296
2351
  ...options?.scopes?.length ? [Q3.where("scope", Q3.oneOf(options.scopes))] : [],
2297
2352
  ...options?.folderId !== void 0 ? [Q3.where("folder_id", options.folderId)] : [],
2353
+ ...options?.factTypes?.length ? [Q3.where("fact_type", Q3.oneOf(options.factTypes))] : [],
2298
2354
  Q3.sortBy(options?.since ? "updated_at" : "created_at", Q3.desc),
2299
2355
  ...options?.limit !== null && options?.limit !== void 0 && options.limit > 0 ? [Q3.take(options.limit)] : []
2300
2356
  ];
@@ -2316,6 +2372,14 @@ async function getAllVaultMemoryContentsOp(ctx, options) {
2316
2372
  return stored.content;
2317
2373
  });
2318
2374
  }
2375
+ async function countActiveVaultMemoriesOp(ctx) {
2376
+ return ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx)).fetchCount();
2377
+ }
2378
+ async function getActiveVaultMemoryIdsOp(ctx, ids) {
2379
+ if (ids.length === 0) return /* @__PURE__ */ new Set();
2380
+ const rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), Q3.where("id", Q3.oneOf(ids))).unsafeFetchRaw();
2381
+ return new Set(rows.map((r) => r.id));
2382
+ }
2319
2383
  async function updateVaultMemoryOp(ctx, id, opts) {
2320
2384
  try {
2321
2385
  const probe = await ctx.vaultMemoryCollection.find(id);
@@ -2369,6 +2433,15 @@ async function updateVaultMemoryOp(ctx, id, opts) {
2369
2433
  if (opts.lastObservedAt !== void 0) {
2370
2434
  r._setRaw("last_observed_at", opts.lastObservedAt);
2371
2435
  }
2436
+ if (opts.factType !== void 0) {
2437
+ r._setRaw("fact_type", opts.factType);
2438
+ }
2439
+ if (opts.trustTier !== void 0) {
2440
+ r._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
2441
+ }
2442
+ if (opts.restore) {
2443
+ r._setRaw("archived_at", null);
2444
+ }
2372
2445
  if (opts.preserveUpdatedAt) {
2373
2446
  r._setRaw("updated_at", originalUpdatedAt);
2374
2447
  }
@@ -2568,6 +2641,98 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
2568
2641
  }
2569
2642
  return records.length;
2570
2643
  }
2644
+ function assertVaultScopeForSweep(ctx) {
2645
+ if (ctx.userId === void 0 && ctx.singleTenant !== true) {
2646
+ throw new Error(
2647
+ "Refusing to run a decay sweep on an unscoped vault context: it has no userId and is not marked singleTenant, so it would sweep across all tenants. Set ctx.userId on server/multi-tenant contexts, or ctx.singleTenant = true on a per-wallet, physically single-tenant client DB. (walletAddress alone is no longer accepted \u2014 it does not scope the sweep query.)"
2648
+ );
2649
+ }
2650
+ }
2651
+ async function getDecayCandidatesRawOp(ctx) {
2652
+ assertVaultScopeForSweep(ctx);
2653
+ const results = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true })).unsafeFetchRaw();
2654
+ return results.map((raw) => ({
2655
+ uniqueId: raw.id,
2656
+ factType: raw.fact_type ?? null,
2657
+ eventTimeEnd: raw.event_time_end ?? null,
2658
+ eventTimeKind: raw.event_time_kind ?? null,
2659
+ updatedAt: raw.updated_at,
2660
+ archivedAt: raw.archived_at ?? null,
2661
+ source: raw.source ?? null,
2662
+ trustTier: raw.trust_tier ?? null
2663
+ }));
2664
+ }
2665
+ async function archiveVaultMemoryOp(ctx, id, opts) {
2666
+ try {
2667
+ const record = await ctx.vaultMemoryCollection.find(id);
2668
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
2669
+ let stale = false;
2670
+ const archivedAtValue = opts?.now ?? Date.now();
2671
+ await ctx.database.write(async () => {
2672
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.archivedAt !== null) {
2673
+ stale = true;
2674
+ return;
2675
+ }
2676
+ if (opts?.expectedUpdatedAt !== void 0 && record.updatedAt.getTime() !== opts.expectedUpdatedAt) {
2677
+ stale = true;
2678
+ return;
2679
+ }
2680
+ await record.update((r) => {
2681
+ r._setRaw("archived_at", archivedAtValue);
2682
+ });
2683
+ });
2684
+ return !stale;
2685
+ } catch {
2686
+ return false;
2687
+ }
2688
+ }
2689
+ async function restoreVaultMemoryOp(ctx, id) {
2690
+ try {
2691
+ const record = await ctx.vaultMemoryCollection.find(id);
2692
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
2693
+ let stale = false;
2694
+ await ctx.database.write(async () => {
2695
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
2696
+ stale = true;
2697
+ return;
2698
+ }
2699
+ await record.update((r) => {
2700
+ r._setRaw("archived_at", null);
2701
+ });
2702
+ });
2703
+ return !stale;
2704
+ } catch {
2705
+ return false;
2706
+ }
2707
+ }
2708
+ async function hardDeleteDecayedOp(ctx, id, opts) {
2709
+ try {
2710
+ const record = await ctx.vaultMemoryCollection.find(id);
2711
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
2712
+ const now = opts.now ?? Date.now();
2713
+ let stale = false;
2714
+ await ctx.database.write(async () => {
2715
+ const archivedAt = record.archivedAt;
2716
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
2717
+ stale = true;
2718
+ return;
2719
+ }
2720
+ await record.update((r) => {
2721
+ r._setRaw("is_deleted", true);
2722
+ });
2723
+ });
2724
+ if (stale) return false;
2725
+ if (ctx.entityCtx) {
2726
+ try {
2727
+ await unlinkMemoryEntitiesOp(ctx.entityCtx, [id]);
2728
+ } catch {
2729
+ }
2730
+ }
2731
+ return true;
2732
+ } catch {
2733
+ return false;
2734
+ }
2735
+ }
2571
2736
  async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel) {
2572
2737
  try {
2573
2738
  const record = await ctx.vaultMemoryCollection.find(id);
@@ -3593,6 +3758,7 @@ function rankVaultMemories(_query, queryEmbedding, items, options) {
3593
3758
  sourceChunkIds: item.sourceChunkIds,
3594
3759
  eventTimeEnd: item.eventTimeEnd,
3595
3760
  eventTimeKind: item.eventTimeKind,
3761
+ factType: item.factType,
3596
3762
  similarity: cosineSimilarity(queryEmbedding, item.embedding)
3597
3763
  }));
3598
3764
  const filtered = scored.filter((r) => r.similarity >= minSimilarity);
@@ -3643,6 +3809,7 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
3643
3809
  const recencyAlpha = options?.recencyAlpha ?? 1;
3644
3810
  const proofCountAlpha = options?.proofCountAlpha ?? 0.1;
3645
3811
  const bm25AdmissionDivisor = options?.bm25AdmissionDivisor ?? 50;
3812
+ const factTypeWeights = options?.factTypeWeights;
3646
3813
  if (items.length === 0) return [];
3647
3814
  const baseRanked = rankVaultMemories(query, queryEmbedding, items, {
3648
3815
  limit: items.length,
@@ -3674,7 +3841,8 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
3674
3841
  eventTimeStart: item.eventTimeStart,
3675
3842
  sourceChunkIds: item.sourceChunkIds,
3676
3843
  eventTimeEnd: item.eventTimeEnd,
3677
- eventTimeKind: item.eventTimeKind
3844
+ eventTimeKind: item.eventTimeKind,
3845
+ factType: item.factType
3678
3846
  });
3679
3847
  }
3680
3848
  const boostFor = (id) => {
@@ -3683,7 +3851,9 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
3683
3851
  const recencyBoost = 1 + recencyAlpha * (recency - 0.5);
3684
3852
  const proofCount = Math.max(1, item?.proofCount ?? 1);
3685
3853
  const proofBoost = 1 + proofCountAlpha * Math.log(1 + proofCount) - proofCountAlpha * Math.log(2);
3686
- return recencyBoost * proofBoost;
3854
+ const rawTypeWeight = item?.factType ? factTypeWeights?.[item.factType] : void 0;
3855
+ const typeWeight = rawTypeWeight !== void 0 && Number.isFinite(rawTypeWeight) && rawTypeWeight > 0 ? rawTypeWeight : 1;
3856
+ return recencyBoost * proofBoost * typeWeight;
3687
3857
  };
3688
3858
  let combined = [...baseRanked, ...admitted].map((r) => ({
3689
3859
  ...r,
@@ -3735,7 +3905,8 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
3735
3905
  supersessionWindow: options?.supersessionWindow,
3736
3906
  proofCountAlpha: options?.proofCountAlpha,
3737
3907
  bm25AdmissionDivisor: options?.bm25AdmissionDivisor,
3738
- rrfK: options?.rrfK
3908
+ rrfK: options?.rrfK,
3909
+ ...options?.factTypeWeights && { factTypeWeights: options.factTypeWeights }
3739
3910
  });
3740
3911
  if (options?.v2HeadStats) {
3741
3912
  options.v2HeadStats.hadResults = v2Ranked.length > 0;
@@ -3828,7 +3999,8 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
3828
3999
  eventTimeStart: orig?.eventTimeStart,
3829
4000
  sourceChunkIds: orig?.sourceChunkIds,
3830
4001
  eventTimeEnd: orig?.eventTimeEnd,
3831
- eventTimeKind: orig?.eventTimeKind
4002
+ eventTimeKind: orig?.eventTimeKind,
4003
+ factType: orig?.factType
3832
4004
  };
3833
4005
  });
3834
4006
  const remainingTail = combined.filter((r) => !pickedIds.has(r.uniqueId));
@@ -3854,7 +4026,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
3854
4026
  ...options?.bm25AdmissionDivisor !== void 0 && {
3855
4027
  bm25AdmissionDivisor: options.bm25AdmissionDivisor
3856
4028
  },
3857
- ...options?.rrfK !== void 0 && { rrfK: options.rrfK }
4029
+ ...options?.rrfK !== void 0 && { rrfK: options.rrfK },
4030
+ ...options?.factTypeWeights && { factTypeWeights: options.factTypeWeights }
3858
4031
  };
3859
4032
  const originalRanked = rankFusedVaultMemories(originalQuery, originalQueryEmbedding, items, {
3860
4033
  limit: perFacetTopN,
@@ -3892,7 +4065,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
3892
4065
  eventTimeStart: item.eventTimeStart,
3893
4066
  sourceChunkIds: item.sourceChunkIds,
3894
4067
  eventTimeEnd: item.eventTimeEnd,
3895
- eventTimeKind: item.eventTimeKind
4068
+ eventTimeKind: item.eventTimeKind,
4069
+ factType: item.factType
3896
4070
  };
3897
4071
  }
3898
4072
  );
@@ -3916,7 +4090,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
3916
4090
  eventTimeStart: item.eventTimeStart,
3917
4091
  sourceChunkIds: item.sourceChunkIds,
3918
4092
  eventTimeEnd: item.eventTimeEnd,
3919
- eventTimeKind: item.eventTimeKind
4093
+ eventTimeKind: item.eventTimeKind,
4094
+ factType: item.factType
3920
4095
  });
3921
4096
  }
3922
4097
  }
@@ -3973,7 +4148,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
3973
4148
  eventTimeStart: orig?.eventTimeStart,
3974
4149
  sourceChunkIds: orig?.sourceChunkIds,
3975
4150
  eventTimeEnd: orig?.eventTimeEnd,
3976
- eventTimeKind: orig?.eventTimeKind
4151
+ eventTimeKind: orig?.eventTimeKind,
4152
+ factType: orig?.factType
3977
4153
  };
3978
4154
  });
3979
4155
  const remainingTail = combined.filter((r) => !pickedIds.has(r.uniqueId));
@@ -4045,6 +4221,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
4045
4221
  const queryOpts = {};
4046
4222
  if (scopes?.length) queryOpts.scopes = scopes;
4047
4223
  if (folderId !== void 0) queryOpts.folderId = folderId;
4224
+ if (searchOptions?.factTypes?.length) queryOpts.factTypes = searchOptions.factTypes;
4225
+ if (searchOptions?.includeArchived) queryOpts.includeArchived = true;
4048
4226
  const loaded = await getAllVaultMemoriesOp(
4049
4227
  vaultCtx,
4050
4228
  Object.keys(queryOpts).length > 0 ? queryOpts : void 0
@@ -4116,7 +4294,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
4116
4294
  eventTimeStart: m.eventTimeStart,
4117
4295
  sourceChunkIds: m.sourceChunkIds,
4118
4296
  eventTimeEnd: m.eventTimeEnd,
4119
- eventTimeKind: normalizeEventTimeKind(m.eventTimeKind)
4297
+ eventTimeKind: normalizeEventTimeKind(m.eventTimeKind),
4298
+ factType: m.factType
4120
4299
  }));
4121
4300
  if (queryEmbedding.length > 0) {
4122
4301
  const mismatched = embeddedItems.filter(
@@ -4160,7 +4339,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
4160
4339
  ...searchOptions?.bm25AdmissionDivisor !== void 0 && {
4161
4340
  bm25AdmissionDivisor: searchOptions.bm25AdmissionDivisor
4162
4341
  },
4163
- ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK }
4342
+ ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
4343
+ ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
4164
4344
  };
4165
4345
  if (useFusion && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
4166
4346
  const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
@@ -4327,7 +4507,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
4327
4507
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
4328
4508
  );
4329
4509
  }
4330
- const { recall: recall2 } = await import("./recall-3YPU4PS2.mjs");
4510
+ const { recall: recall2 } = await import("./recall-7FUJRHOI.mjs");
4331
4511
  const result = await recall2(
4332
4512
  query,
4333
4513
  { vaultCtx, embeddingOptions, vaultCache: cache },
@@ -4438,6 +4618,159 @@ function extractQueryEntities(query) {
4438
4618
  return [...seen];
4439
4619
  }
4440
4620
 
4621
+ // src/lib/memory/graphTraversal.ts
4622
+ var MAX_HOPS = 2;
4623
+ var ENTITY_FANOUT = 8;
4624
+ var MIN_REFINER_CANDIDATES = 16;
4625
+ var MAX_REFINER_CANDIDATES = 64;
4626
+ var NODE_BUDGET = 64;
4627
+ var VAULT_SIZE_HOP_CAP = 1e3;
4628
+ function clampPositiveInt(value, fallback) {
4629
+ if (value === void 0 || !Number.isFinite(value) || value < 1) return fallback;
4630
+ return Math.floor(value);
4631
+ }
4632
+ function capHopsForDensity(maxHops, vaultSize) {
4633
+ if (vaultSize !== void 0 && vaultSize > VAULT_SIZE_HOP_CAP) return Math.min(maxHops, 1);
4634
+ return maxHops;
4635
+ }
4636
+ function rankMemoriesByOverlap(map) {
4637
+ return [...map.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
4638
+ }
4639
+ async function traverseGraphLane(query, entityCtx, options = {}) {
4640
+ const seedNames = extractQueryEntities(query);
4641
+ if (seedNames.length === 0) return [];
4642
+ const entityFanout = clampPositiveInt(options.entityFanout, ENTITY_FANOUT);
4643
+ const nodeBudget = clampPositiveInt(options.nodeBudget, NODE_BUDGET);
4644
+ const maxHops = capHopsForDensity(clampPositiveInt(options.maxHops, MAX_HOPS), options.vaultSize);
4645
+ const keepActive = async (map) => {
4646
+ if (!options.filterActiveMemoryIds || map.size === 0) return map;
4647
+ const active = await options.filterActiveMemoryIds([...map.keys()]);
4648
+ const filtered = /* @__PURE__ */ new Map();
4649
+ for (const [id, names] of map) if (active.has(id)) filtered.set(id, names);
4650
+ return filtered;
4651
+ };
4652
+ const hop1 = await keepActive(await getMemoriesByEntityNamesOp(entityCtx, seedNames));
4653
+ if (hop1.size === 0) return [];
4654
+ const hop1Ranking = rankMemoriesByOverlap(hop1);
4655
+ if (maxHops <= 1) return hop1Ranking;
4656
+ const hop1Emitted = hop1Ranking.slice(0, nodeBudget);
4657
+ const perHopRankings = [hop1Emitted];
4658
+ const firstHopOf = /* @__PURE__ */ new Map();
4659
+ for (const id of hop1Emitted) if (!firstHopOf.has(id)) firstHopOf.set(id, 1);
4660
+ const accumulated = new Set(hop1Emitted);
4661
+ const seenEntities = new Set(seedNames);
4662
+ let frontier = hop1Emitted;
4663
+ for (let hop = 2; hop <= maxHops; hop++) {
4664
+ if (frontier.length === 0) break;
4665
+ if (accumulated.size >= nodeBudget) break;
4666
+ const memoryToEntities = await getEntitiesByMemoryIdsOp(entityCtx, frontier);
4667
+ const neighborCounts = /* @__PURE__ */ new Map();
4668
+ for (const names of memoryToEntities.values()) {
4669
+ for (const name of names) {
4670
+ if (seenEntities.has(name)) continue;
4671
+ neighborCounts.set(name, (neighborCounts.get(name) ?? 0) + 1);
4672
+ }
4673
+ }
4674
+ if (neighborCounts.size === 0) break;
4675
+ const rankedNeighbors = [...neighborCounts.entries()].sort((a, b) => b[1] - a[1]).map(([name]) => name);
4676
+ let topNeighbors = rankedNeighbors.slice(0, entityFanout);
4677
+ if (options.refineNeighbors && rankedNeighbors.length > entityFanout) {
4678
+ try {
4679
+ const refinerCandidateCap = Math.min(
4680
+ Math.max(entityFanout * 2, MIN_REFINER_CANDIDATES),
4681
+ MAX_REFINER_CANDIDATES
4682
+ );
4683
+ const refinerCandidates = rankedNeighbors.slice(0, refinerCandidateCap);
4684
+ const refined = await options.refineNeighbors.refine(
4685
+ query,
4686
+ refinerCandidates,
4687
+ entityFanout
4688
+ );
4689
+ const valid = [];
4690
+ const seen = /* @__PURE__ */ new Set();
4691
+ for (const name of refined) {
4692
+ if (!neighborCounts.has(name) || seen.has(name)) continue;
4693
+ seen.add(name);
4694
+ valid.push(name);
4695
+ if (valid.length >= entityFanout) break;
4696
+ }
4697
+ if (valid.length > 0) topNeighbors = valid;
4698
+ } catch (err) {
4699
+ getLogger().warn(
4700
+ `[memory/graph] neighbor refine failed; using co-occurrence order: ${err instanceof Error ? err.message : String(err)}`
4701
+ );
4702
+ }
4703
+ }
4704
+ for (const name of topNeighbors) seenEntities.add(name);
4705
+ const hopMap = await keepActive(await getMemoriesByEntityNamesOp(entityCtx, topNeighbors));
4706
+ if (hopMap.size === 0) break;
4707
+ const hopRanking = rankMemoriesByOverlap(hopMap);
4708
+ const newlyDiscovered = [];
4709
+ for (const id of hopRanking) {
4710
+ if (accumulated.has(id)) continue;
4711
+ accumulated.add(id);
4712
+ newlyDiscovered.push(id);
4713
+ firstHopOf.set(id, hop);
4714
+ if (accumulated.size >= nodeBudget) break;
4715
+ }
4716
+ if (newlyDiscovered.length === 0) break;
4717
+ perHopRankings.push(newlyDiscovered);
4718
+ frontier = newlyDiscovered;
4719
+ }
4720
+ const fused = rrfFuse(perHopRankings, options.rrfK);
4721
+ return [...fused.entries()].sort((a, b) => {
4722
+ const hopA = firstHopOf.get(a[0]) ?? Infinity;
4723
+ const hopB = firstHopOf.get(b[0]) ?? Infinity;
4724
+ if (hopA !== hopB) return hopA - hopB;
4725
+ return b[1] - a[1];
4726
+ }).map(([id]) => id);
4727
+ }
4728
+ var DEFAULT_REFINER_MODEL = "inclusionai/ling-2.6-flash";
4729
+ var DEFAULT_REFINER_ATTEMPTS = 1;
4730
+ var DEFAULT_REFINER_TOTAL_TIMEOUT_MS = 8e3;
4731
+ function createLlmNeighborRefiner(options) {
4732
+ return {
4733
+ async refine(query, candidates, limit) {
4734
+ if (candidates.length === 0) return [];
4735
+ const numbered = candidates.map((name, i) => `[${i + 1}] ${name}`).join("\n");
4736
+ const systemPrompt = `You help a memory-retrieval system decide which related topics to explore.
4737
+
4738
+ Given a user's question and a numbered list of candidate topics/entities linked to memories found so far, pick the ones most likely to lead to memories that help ANSWER the question. Choose at most ${limit}. Prefer topics semantically related to the question; ignore incidental ones.
4739
+
4740
+ Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
4741
+ const parsed = await callPortalJsonCompletion({
4742
+ ...options.apiKey !== void 0 && { apiKey: options.apiKey },
4743
+ ...options.getToken !== void 0 && { getToken: options.getToken },
4744
+ ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
4745
+ model: options.model ?? DEFAULT_REFINER_MODEL,
4746
+ systemPrompt,
4747
+ userMessage: `Question: ${query}
4748
+
4749
+ Candidate topics:
4750
+ ${numbered}
4751
+
4752
+ Which should be expanded?`,
4753
+ tag: "memory/graph-refine",
4754
+ maxAttempts: options.maxAttempts ?? DEFAULT_REFINER_ATTEMPTS,
4755
+ totalTimeoutMs: options.totalTimeoutMs ?? DEFAULT_REFINER_TOTAL_TIMEOUT_MS,
4756
+ ...options.backoffMs && { backoffMs: options.backoffMs },
4757
+ ...options.fetchFn && { fetchFn: options.fetchFn }
4758
+ });
4759
+ if (parsed === null || typeof parsed !== "object") return [];
4760
+ const list = parsed.expand;
4761
+ if (!Array.isArray(list)) return [];
4762
+ const byNormalized = new Map(candidates.map((c) => [normalizeEntityName(c), c]));
4763
+ const out = [];
4764
+ for (const raw of list) {
4765
+ if (typeof raw !== "string") continue;
4766
+ const match = byNormalized.get(normalizeEntityName(raw));
4767
+ if (match) out.push(match);
4768
+ }
4769
+ return out;
4770
+ }
4771
+ };
4772
+ }
4773
+
4441
4774
  // src/lib/memory/queryTemporal.ts
4442
4775
  var RELATIVE_DAY_ENTRIES = [
4443
4776
  [/\btoday\b/, "today", 0],
@@ -4650,12 +4983,12 @@ var DEFAULT_CHUNK_MIN_SCORE = 0.5;
4650
4983
  function flagsForBudget(budget) {
4651
4984
  switch (budget) {
4652
4985
  case "high":
4653
- return { rerank: true, decompose: true };
4986
+ return { rerank: true, decompose: true, traverse: true };
4654
4987
  case "mid":
4655
- return { rerank: true, decompose: false };
4988
+ return { rerank: true, decompose: false, traverse: false };
4656
4989
  case "low":
4657
4990
  default:
4658
- return { rerank: false, decompose: false };
4991
+ return { rerank: false, decompose: false, traverse: false };
4659
4992
  }
4660
4993
  }
4661
4994
  function dedupeBy(items, ...keys) {
@@ -4723,11 +5056,25 @@ async function recall(query, ctx, options = {}) {
4723
5056
  }
4724
5057
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
4725
5058
  const wantsTemporal = types.includes("fact") && ctx.vaultCtx;
5059
+ const graphRefiner = flags.traverse && options.graphRefine && options.decomposeOptions ? createLlmNeighborRefiner(options.decomposeOptions) : void 0;
4726
5060
  const prepStart = nowMs();
4727
5061
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
4728
5062
  needsChunkEmbedding ? generateEmbedding(query, ctx.embeddingOptions) : Promise.resolve(void 0),
4729
- buildGraphLaneRanking(query, ctx),
4730
- wantsTemporal ? buildTemporalLaneRanking(query, ctx.vaultCtx, options.now) : Promise.resolve([])
5063
+ // The graph + temporal lanes are AUXILIARY (RRF side-signals). A transient
5064
+ // WatermelonDB throw in either must NOT reject this Promise.all and take
5065
+ // PRIMARY cosine/BM25 recall down with it — degrade the failing lane to an
5066
+ // empty ranking instead (mirrors safeCountVault's fail-soft posture).
5067
+ safeLane(
5068
+ "graph",
5069
+ () => buildGraphLaneRanking(query, ctx, flags.traverse, {
5070
+ ...options.maxHops !== void 0 && { maxHops: options.maxHops },
5071
+ ...options.entityFanout !== void 0 && { entityFanout: options.entityFanout },
5072
+ ...options.nodeBudget !== void 0 && { nodeBudget: options.nodeBudget },
5073
+ ...options.rrfK !== void 0 && { rrfK: options.rrfK },
5074
+ ...graphRefiner && { refineNeighbors: graphRefiner }
5075
+ })
5076
+ ),
5077
+ wantsTemporal ? safeLane("temporal", () => buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)) : Promise.resolve([])
4731
5078
  ]);
4732
5079
  prepMs = nowMs() - prepStart;
4733
5080
  if (types.includes("fact") && ctx.vaultCtx && ctx.vaultCache) {
@@ -4777,6 +5124,8 @@ async function recall(query, ctx, options = {}) {
4777
5124
  },
4778
5125
  ...options.scopes && { scopes: options.scopes },
4779
5126
  ...options.folderId !== void 0 && { folderId: options.folderId },
5127
+ ...options.factTypes?.length && { factTypes: options.factTypes },
5128
+ ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
4780
5129
  ...entityRanking.length > 0 && { entityRanking },
4781
5130
  ...temporalRanking.length > 0 && { temporalRanking }
4782
5131
  }
@@ -4898,6 +5247,9 @@ function toFactMemory(r) {
4898
5247
  ...r.eventTimeStart !== void 0 && { eventTimeStart: r.eventTimeStart },
4899
5248
  ...r.eventTimeEnd !== void 0 && { eventTimeEnd: r.eventTimeEnd },
4900
5249
  ...r.eventTimeKind !== void 0 && { eventTimeKind: r.eventTimeKind },
5250
+ // Typed memory (PR1) — surfaced from VaultSearchResult so recall results
5251
+ // (and UI reading them) carry the fact's type. Undefined when untyped.
5252
+ ...r.factType !== void 0 && { factType: r.factType },
4901
5253
  ...r.sourceChunkIds !== void 0 && r.sourceChunkIds !== null && { sourceChunkIds: r.sourceChunkIds },
4902
5254
  // r.similarity from searchVaultMemoriesWithSize is the fused score
4903
5255
  // (cosine + BM25 + RRF + recency + proof) when useFusion=true (the
@@ -4911,14 +5263,47 @@ function toFactMemory(r) {
4911
5263
  updatedAt: r.updatedAt ?? /* @__PURE__ */ new Date(0)
4912
5264
  };
4913
5265
  }
4914
- async function buildGraphLaneRanking(query, ctx) {
5266
+ async function buildGraphLaneRanking(query, ctx, traverse = false, traversalOptions = {}) {
4915
5267
  const entityCtx = ctx.entityCtx ?? ctx.vaultCtx?.entityCtx;
4916
5268
  if (!entityCtx) return [];
5269
+ if (traverse) {
5270
+ const vaultSize = await safeCountVault(ctx);
5271
+ const vaultCtx2 = ctx.vaultCtx;
5272
+ return traverseGraphLane(query, entityCtx, {
5273
+ ...traversalOptions,
5274
+ ...vaultSize !== void 0 && { vaultSize },
5275
+ ...vaultCtx2 && {
5276
+ filterActiveMemoryIds: (ids) => getActiveVaultMemoryIdsOp(vaultCtx2, ids)
5277
+ }
5278
+ });
5279
+ }
4917
5280
  const queryEntities = extractQueryEntities(query);
4918
5281
  if (queryEntities.length === 0) return [];
4919
5282
  const memoryToEntities = await getMemoriesByEntityNamesOp(entityCtx, queryEntities);
4920
5283
  if (memoryToEntities.size === 0) return [];
4921
- return [...memoryToEntities.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
5284
+ const ranked = [...memoryToEntities.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
5285
+ const vaultCtx = ctx.vaultCtx;
5286
+ if (!vaultCtx) return ranked;
5287
+ const activeIds = await getActiveVaultMemoryIdsOp(vaultCtx, ranked);
5288
+ return ranked.filter((id) => activeIds.has(id));
5289
+ }
5290
+ async function safeCountVault(ctx) {
5291
+ if (!ctx.vaultCtx) return void 0;
5292
+ try {
5293
+ return await countActiveVaultMemoriesOp(ctx.vaultCtx);
5294
+ } catch {
5295
+ return void 0;
5296
+ }
5297
+ }
5298
+ async function safeLane(label, run) {
5299
+ try {
5300
+ return await run();
5301
+ } catch (err) {
5302
+ getLogger().warn(
5303
+ `[memory/recall] ${label} lane failed; continuing without it: ${err instanceof Error ? err.message : String(err)}`
5304
+ );
5305
+ return [];
5306
+ }
4922
5307
  }
4923
5308
  async function buildTemporalLaneRanking(query, vaultCtx, now) {
4924
5309
  const window2 = parseQueryTimeWindow(query, now);
@@ -5024,12 +5409,24 @@ export {
5024
5409
  getMemoriesNeedingTopicExtractionOp,
5025
5410
  stampTopicsExtractedAtOp,
5026
5411
  deleteAllVaultMemoriesForUserOp,
5412
+ assertVaultScopeForSweep,
5413
+ getDecayCandidatesRawOp,
5414
+ archiveVaultMemoryOp,
5415
+ restoreVaultMemoryOp,
5416
+ hardDeleteDecayedOp,
5027
5417
  updateVaultMemoryEmbeddingOp,
5028
5418
  RerankerUnavailableError,
5029
5419
  isRerankerAvailable,
5030
5420
  DEFAULT_VAULT_CACHE_SIZE,
5031
5421
  LRUCache,
5032
5422
  createVaultEmbeddingCache,
5423
+ MAX_HOPS,
5424
+ ENTITY_FANOUT,
5425
+ NODE_BUDGET,
5426
+ VAULT_SIZE_HOP_CAP,
5427
+ capHopsForDensity,
5428
+ traverseGraphLane,
5429
+ createLlmNeighborRefiner,
5033
5430
  recall,
5034
5431
  preEmbedVaultMemories,
5035
5432
  eagerEmbedContent,