@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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-KXIUA7ZV.mjs";
3
+ } from "./chunk-NMZMXIUM.mjs";
4
4
  import "./chunk-DFVCS3QQ.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {
@@ -2105,6 +2105,36 @@ async function getMemoriesByEntityNamesOp(ctx, entityNames) {
2105
2105
  }
2106
2106
  return out;
2107
2107
  }
2108
+ async function getEntitiesByMemoryIdsOp(ctx, memoryIds) {
2109
+ const unique = Array.from(new Set(memoryIds.filter((id) => id.length > 0)));
2110
+ if (unique.length === 0) return /* @__PURE__ */ new Map();
2111
+ const linkConditions = [Q2.where("memory_id", Q2.oneOf(unique))];
2112
+ if (ctx.userId !== void 0) {
2113
+ if (ctx.allowUnscopedRows) {
2114
+ linkConditions.push(Q2.or(Q2.where("user_id", ctx.userId), Q2.where("user_id", null)));
2115
+ } else {
2116
+ linkConditions.push(Q2.where("user_id", ctx.userId));
2117
+ }
2118
+ }
2119
+ const links = await ctx.memoryEntityCollection.query(...linkConditions).fetch();
2120
+ if (links.length === 0) return /* @__PURE__ */ new Map();
2121
+ const entityIds = Array.from(new Set(links.map((l) => String(l.entityId))));
2122
+ const entityRows = await ctx.entityCollection.query(Q2.where("id", Q2.oneOf(entityIds))).fetch();
2123
+ const entityIdToName = new Map(entityRows.map((e) => [e.id, e.canonicalName]));
2124
+ const out = /* @__PURE__ */ new Map();
2125
+ for (const link of links) {
2126
+ const entityName = entityIdToName.get(String(link.entityId));
2127
+ if (!entityName) continue;
2128
+ const memoryId = String(link.memoryId);
2129
+ let bucket = out.get(memoryId);
2130
+ if (!bucket) {
2131
+ bucket = /* @__PURE__ */ new Set();
2132
+ out.set(memoryId, bucket);
2133
+ }
2134
+ bucket.add(entityName);
2135
+ }
2136
+ return out;
2137
+ }
2108
2138
 
2109
2139
  // src/lib/db/memoryVault/operations.ts
2110
2140
  import { Q as Q3 } from "@nozbe/watermelondb";
@@ -2142,11 +2172,18 @@ function isOwnedByCtxUser(ctx, record) {
2142
2172
  function baseVaultConditions(ctx, options) {
2143
2173
  return [
2144
2174
  ...options?.includeDeleted ? [] : [Q3.where("is_deleted", false)],
2175
+ ...options?.includeArchived ? [] : [Q3.where("archived_at", Q3.eq(null))],
2176
+ ...options?.includeQuarantined ? [] : [Q3.where("trust_tier", Q3.notEq("quarantined"))],
2145
2177
  ...options?.includeSuperseded ? [] : [Q3.where("superseded_by", null)],
2146
2178
  ...ctx.userId !== void 0 ? [Q3.where("user_id", ctx.userId)] : [],
2147
2179
  ...options?.since ? [Q3.where("updated_at", Q3.gt(options.since.getTime()))] : []
2148
2180
  ];
2149
2181
  }
2182
+ var KNOWN_TRUST_TIERS = /* @__PURE__ */ new Set(["quarantined", "trusted"]);
2183
+ function normalizeTrustTier(value) {
2184
+ if (value === void 0 || value === null) return null;
2185
+ return KNOWN_TRUST_TIERS.has(value) ? value : null;
2186
+ }
2150
2187
  async function mapInBatches(items, fn) {
2151
2188
  const BATCH = 50;
2152
2189
  const results = [];
@@ -2187,6 +2224,9 @@ function vaultMemoryToStoredRaw(memory) {
2187
2224
  supersededBy: memory.supersededBy ?? null,
2188
2225
  supersededAt: memory.supersededAt ?? null,
2189
2226
  lastObservedAt: memory.lastObservedAt ?? null,
2227
+ factType: memory.factType ?? null,
2228
+ archivedAt: memory.archivedAt ?? null,
2229
+ trustTier: memory.trustTier ?? null,
2190
2230
  createdAt: memory.createdAt,
2191
2231
  updatedAt: memory.updatedAt,
2192
2232
  isDeleted: memory.isDeleted
@@ -2228,6 +2268,12 @@ async function createVaultMemoryOp(ctx, opts) {
2228
2268
  record._setRaw("event_time_end", opts.eventTime.end ?? null);
2229
2269
  record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
2230
2270
  }
2271
+ if (opts.factType !== void 0) {
2272
+ record._setRaw("fact_type", opts.factType);
2273
+ }
2274
+ if (opts.trustTier !== void 0) {
2275
+ record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
2276
+ }
2231
2277
  });
2232
2278
  });
2233
2279
  return vaultMemoryToStored(created, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
@@ -2368,6 +2414,12 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
2368
2414
  record._setRaw("event_time_end", opts.eventTime.end ?? null);
2369
2415
  record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
2370
2416
  }
2417
+ if (opts.factType !== void 0) {
2418
+ record._setRaw("fact_type", opts.factType);
2419
+ }
2420
+ if (opts.trustTier !== void 0) {
2421
+ record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
2422
+ }
2371
2423
  })
2372
2424
  );
2373
2425
  await ctx.database.batch(...prepared);
@@ -2428,6 +2480,9 @@ function vaultMemoryRawToStoredRaw(raw) {
2428
2480
  supersededBy: raw.superseded_by ?? null,
2429
2481
  supersededAt: raw.superseded_at ?? null,
2430
2482
  lastObservedAt: raw.last_observed_at ?? null,
2483
+ factType: raw.fact_type ?? null,
2484
+ archivedAt: raw.archived_at ?? null,
2485
+ trustTier: raw.trust_tier ?? null,
2431
2486
  createdAt: new Date(raw.created_at),
2432
2487
  updatedAt: new Date(raw.updated_at),
2433
2488
  isDeleted: raw.is_deleted === true || raw.is_deleted === 1
@@ -2445,6 +2500,7 @@ async function getAllVaultMemoriesOp(ctx, options) {
2445
2500
  ...baseVaultConditions(ctx, options),
2446
2501
  ...options?.scopes?.length ? [Q3.where("scope", Q3.oneOf(options.scopes))] : [],
2447
2502
  ...options?.folderId !== void 0 ? [Q3.where("folder_id", options.folderId)] : [],
2503
+ ...options?.factTypes?.length ? [Q3.where("fact_type", Q3.oneOf(options.factTypes))] : [],
2448
2504
  Q3.sortBy(options?.since ? "updated_at" : "created_at", Q3.desc),
2449
2505
  ...options?.limit !== null && options?.limit !== void 0 && options.limit > 0 ? [Q3.take(options.limit)] : []
2450
2506
  ];
@@ -2466,6 +2522,14 @@ async function getAllVaultMemoryContentsOp(ctx, options) {
2466
2522
  return stored.content;
2467
2523
  });
2468
2524
  }
2525
+ async function countActiveVaultMemoriesOp(ctx) {
2526
+ return ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx)).fetchCount();
2527
+ }
2528
+ async function getActiveVaultMemoryIdsOp(ctx, ids) {
2529
+ if (ids.length === 0) return /* @__PURE__ */ new Set();
2530
+ const rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), Q3.where("id", Q3.oneOf(ids))).unsafeFetchRaw();
2531
+ return new Set(rows.map((r) => r.id));
2532
+ }
2469
2533
  async function updateVaultMemoryOp(ctx, id, opts) {
2470
2534
  try {
2471
2535
  const probe = await ctx.vaultMemoryCollection.find(id);
@@ -2519,6 +2583,15 @@ async function updateVaultMemoryOp(ctx, id, opts) {
2519
2583
  if (opts.lastObservedAt !== void 0) {
2520
2584
  r._setRaw("last_observed_at", opts.lastObservedAt);
2521
2585
  }
2586
+ if (opts.factType !== void 0) {
2587
+ r._setRaw("fact_type", opts.factType);
2588
+ }
2589
+ if (opts.trustTier !== void 0) {
2590
+ r._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
2591
+ }
2592
+ if (opts.restore) {
2593
+ r._setRaw("archived_at", null);
2594
+ }
2522
2595
  if (opts.preserveUpdatedAt) {
2523
2596
  r._setRaw("updated_at", originalUpdatedAt);
2524
2597
  }
@@ -2775,6 +2848,98 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
2775
2848
  }
2776
2849
  return records.length;
2777
2850
  }
2851
+ function assertVaultScopeForSweep(ctx) {
2852
+ if (ctx.userId === void 0 && ctx.singleTenant !== true) {
2853
+ throw new Error(
2854
+ "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.)"
2855
+ );
2856
+ }
2857
+ }
2858
+ async function getDecayCandidatesRawOp(ctx) {
2859
+ assertVaultScopeForSweep(ctx);
2860
+ const results = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true })).unsafeFetchRaw();
2861
+ return results.map((raw) => ({
2862
+ uniqueId: raw.id,
2863
+ factType: raw.fact_type ?? null,
2864
+ eventTimeEnd: raw.event_time_end ?? null,
2865
+ eventTimeKind: raw.event_time_kind ?? null,
2866
+ updatedAt: raw.updated_at,
2867
+ archivedAt: raw.archived_at ?? null,
2868
+ source: raw.source ?? null,
2869
+ trustTier: raw.trust_tier ?? null
2870
+ }));
2871
+ }
2872
+ async function archiveVaultMemoryOp(ctx, id, opts) {
2873
+ try {
2874
+ const record = await ctx.vaultMemoryCollection.find(id);
2875
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
2876
+ let stale = false;
2877
+ const archivedAtValue = opts?.now ?? Date.now();
2878
+ await ctx.database.write(async () => {
2879
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.archivedAt !== null) {
2880
+ stale = true;
2881
+ return;
2882
+ }
2883
+ if (opts?.expectedUpdatedAt !== void 0 && record.updatedAt.getTime() !== opts.expectedUpdatedAt) {
2884
+ stale = true;
2885
+ return;
2886
+ }
2887
+ await record.update((r) => {
2888
+ r._setRaw("archived_at", archivedAtValue);
2889
+ });
2890
+ });
2891
+ return !stale;
2892
+ } catch {
2893
+ return false;
2894
+ }
2895
+ }
2896
+ async function restoreVaultMemoryOp(ctx, id) {
2897
+ try {
2898
+ const record = await ctx.vaultMemoryCollection.find(id);
2899
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
2900
+ let stale = false;
2901
+ await ctx.database.write(async () => {
2902
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
2903
+ stale = true;
2904
+ return;
2905
+ }
2906
+ await record.update((r) => {
2907
+ r._setRaw("archived_at", null);
2908
+ });
2909
+ });
2910
+ return !stale;
2911
+ } catch {
2912
+ return false;
2913
+ }
2914
+ }
2915
+ async function hardDeleteDecayedOp(ctx, id, opts) {
2916
+ try {
2917
+ const record = await ctx.vaultMemoryCollection.find(id);
2918
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
2919
+ const now = opts.now ?? Date.now();
2920
+ let stale = false;
2921
+ await ctx.database.write(async () => {
2922
+ const archivedAt = record.archivedAt;
2923
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
2924
+ stale = true;
2925
+ return;
2926
+ }
2927
+ await record.update((r) => {
2928
+ r._setRaw("is_deleted", true);
2929
+ });
2930
+ });
2931
+ if (stale) return false;
2932
+ if (ctx.entityCtx) {
2933
+ try {
2934
+ await unlinkMemoryEntitiesOp(ctx.entityCtx, [id]);
2935
+ } catch {
2936
+ }
2937
+ }
2938
+ return true;
2939
+ } catch {
2940
+ return false;
2941
+ }
2942
+ }
2778
2943
  async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel) {
2779
2944
  try {
2780
2945
  const record = await ctx.vaultMemoryCollection.find(id);
@@ -3800,6 +3965,7 @@ function rankVaultMemories(_query, queryEmbedding, items, options) {
3800
3965
  sourceChunkIds: item.sourceChunkIds,
3801
3966
  eventTimeEnd: item.eventTimeEnd,
3802
3967
  eventTimeKind: item.eventTimeKind,
3968
+ factType: item.factType,
3803
3969
  similarity: cosineSimilarity(queryEmbedding, item.embedding)
3804
3970
  }));
3805
3971
  const filtered = scored.filter((r) => r.similarity >= minSimilarity);
@@ -3850,6 +4016,7 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
3850
4016
  const recencyAlpha = options?.recencyAlpha ?? 1;
3851
4017
  const proofCountAlpha = options?.proofCountAlpha ?? 0.1;
3852
4018
  const bm25AdmissionDivisor = options?.bm25AdmissionDivisor ?? 50;
4019
+ const factTypeWeights = options?.factTypeWeights;
3853
4020
  if (items.length === 0) return [];
3854
4021
  const baseRanked = rankVaultMemories(query, queryEmbedding, items, {
3855
4022
  limit: items.length,
@@ -3881,7 +4048,8 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
3881
4048
  eventTimeStart: item.eventTimeStart,
3882
4049
  sourceChunkIds: item.sourceChunkIds,
3883
4050
  eventTimeEnd: item.eventTimeEnd,
3884
- eventTimeKind: item.eventTimeKind
4051
+ eventTimeKind: item.eventTimeKind,
4052
+ factType: item.factType
3885
4053
  });
3886
4054
  }
3887
4055
  const boostFor = (id) => {
@@ -3890,7 +4058,9 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
3890
4058
  const recencyBoost = 1 + recencyAlpha * (recency - 0.5);
3891
4059
  const proofCount = Math.max(1, item?.proofCount ?? 1);
3892
4060
  const proofBoost = 1 + proofCountAlpha * Math.log(1 + proofCount) - proofCountAlpha * Math.log(2);
3893
- return recencyBoost * proofBoost;
4061
+ const rawTypeWeight = item?.factType ? factTypeWeights?.[item.factType] : void 0;
4062
+ const typeWeight = rawTypeWeight !== void 0 && Number.isFinite(rawTypeWeight) && rawTypeWeight > 0 ? rawTypeWeight : 1;
4063
+ return recencyBoost * proofBoost * typeWeight;
3894
4064
  };
3895
4065
  let combined = [...baseRanked, ...admitted].map((r) => ({
3896
4066
  ...r,
@@ -3942,7 +4112,8 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
3942
4112
  supersessionWindow: options?.supersessionWindow,
3943
4113
  proofCountAlpha: options?.proofCountAlpha,
3944
4114
  bm25AdmissionDivisor: options?.bm25AdmissionDivisor,
3945
- rrfK: options?.rrfK
4115
+ rrfK: options?.rrfK,
4116
+ ...options?.factTypeWeights && { factTypeWeights: options.factTypeWeights }
3946
4117
  });
3947
4118
  if (options?.v2HeadStats) {
3948
4119
  options.v2HeadStats.hadResults = v2Ranked.length > 0;
@@ -4035,7 +4206,8 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
4035
4206
  eventTimeStart: orig?.eventTimeStart,
4036
4207
  sourceChunkIds: orig?.sourceChunkIds,
4037
4208
  eventTimeEnd: orig?.eventTimeEnd,
4038
- eventTimeKind: orig?.eventTimeKind
4209
+ eventTimeKind: orig?.eventTimeKind,
4210
+ factType: orig?.factType
4039
4211
  };
4040
4212
  });
4041
4213
  const remainingTail = combined.filter((r) => !pickedIds.has(r.uniqueId));
@@ -4061,7 +4233,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
4061
4233
  ...options?.bm25AdmissionDivisor !== void 0 && {
4062
4234
  bm25AdmissionDivisor: options.bm25AdmissionDivisor
4063
4235
  },
4064
- ...options?.rrfK !== void 0 && { rrfK: options.rrfK }
4236
+ ...options?.rrfK !== void 0 && { rrfK: options.rrfK },
4237
+ ...options?.factTypeWeights && { factTypeWeights: options.factTypeWeights }
4065
4238
  };
4066
4239
  const originalRanked = rankFusedVaultMemories(originalQuery, originalQueryEmbedding, items, {
4067
4240
  limit: perFacetTopN,
@@ -4099,7 +4272,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
4099
4272
  eventTimeStart: item.eventTimeStart,
4100
4273
  sourceChunkIds: item.sourceChunkIds,
4101
4274
  eventTimeEnd: item.eventTimeEnd,
4102
- eventTimeKind: item.eventTimeKind
4275
+ eventTimeKind: item.eventTimeKind,
4276
+ factType: item.factType
4103
4277
  };
4104
4278
  }
4105
4279
  );
@@ -4123,7 +4297,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
4123
4297
  eventTimeStart: item.eventTimeStart,
4124
4298
  sourceChunkIds: item.sourceChunkIds,
4125
4299
  eventTimeEnd: item.eventTimeEnd,
4126
- eventTimeKind: item.eventTimeKind
4300
+ eventTimeKind: item.eventTimeKind,
4301
+ factType: item.factType
4127
4302
  });
4128
4303
  }
4129
4304
  }
@@ -4180,7 +4355,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
4180
4355
  eventTimeStart: orig?.eventTimeStart,
4181
4356
  sourceChunkIds: orig?.sourceChunkIds,
4182
4357
  eventTimeEnd: orig?.eventTimeEnd,
4183
- eventTimeKind: orig?.eventTimeKind
4358
+ eventTimeKind: orig?.eventTimeKind,
4359
+ factType: orig?.factType
4184
4360
  };
4185
4361
  });
4186
4362
  const remainingTail = combined.filter((r) => !pickedIds.has(r.uniqueId));
@@ -4252,6 +4428,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
4252
4428
  const queryOpts = {};
4253
4429
  if (scopes?.length) queryOpts.scopes = scopes;
4254
4430
  if (folderId !== void 0) queryOpts.folderId = folderId;
4431
+ if (searchOptions?.factTypes?.length) queryOpts.factTypes = searchOptions.factTypes;
4432
+ if (searchOptions?.includeArchived) queryOpts.includeArchived = true;
4255
4433
  const loaded = await getAllVaultMemoriesOp(
4256
4434
  vaultCtx,
4257
4435
  Object.keys(queryOpts).length > 0 ? queryOpts : void 0
@@ -4323,7 +4501,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
4323
4501
  eventTimeStart: m.eventTimeStart,
4324
4502
  sourceChunkIds: m.sourceChunkIds,
4325
4503
  eventTimeEnd: m.eventTimeEnd,
4326
- eventTimeKind: normalizeEventTimeKind(m.eventTimeKind)
4504
+ eventTimeKind: normalizeEventTimeKind(m.eventTimeKind),
4505
+ factType: m.factType
4327
4506
  }));
4328
4507
  if (queryEmbedding.length > 0) {
4329
4508
  const mismatched = embeddedItems.filter(
@@ -4367,7 +4546,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
4367
4546
  ...searchOptions?.bm25AdmissionDivisor !== void 0 && {
4368
4547
  bm25AdmissionDivisor: searchOptions.bm25AdmissionDivisor
4369
4548
  },
4370
- ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK }
4549
+ ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
4550
+ ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
4371
4551
  };
4372
4552
  if (useFusion && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
4373
4553
  const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
@@ -4534,7 +4714,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
4534
4714
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
4535
4715
  );
4536
4716
  }
4537
- const { recall: recall2 } = await import("./recall-GYNSL2DS.mjs");
4717
+ const { recall: recall2 } = await import("./recall-YZGWZC5W.mjs");
4538
4718
  const result = await recall2(
4539
4719
  query,
4540
4720
  { vaultCtx, embeddingOptions, vaultCache: cache },
@@ -4645,6 +4825,159 @@ function extractQueryEntities(query) {
4645
4825
  return [...seen];
4646
4826
  }
4647
4827
 
4828
+ // src/lib/memory/graphTraversal.ts
4829
+ var MAX_HOPS = 2;
4830
+ var ENTITY_FANOUT = 8;
4831
+ var MIN_REFINER_CANDIDATES = 16;
4832
+ var MAX_REFINER_CANDIDATES = 64;
4833
+ var NODE_BUDGET = 64;
4834
+ var VAULT_SIZE_HOP_CAP = 1e3;
4835
+ function clampPositiveInt(value, fallback) {
4836
+ if (value === void 0 || !Number.isFinite(value) || value < 1) return fallback;
4837
+ return Math.floor(value);
4838
+ }
4839
+ function capHopsForDensity(maxHops, vaultSize) {
4840
+ if (vaultSize !== void 0 && vaultSize > VAULT_SIZE_HOP_CAP) return Math.min(maxHops, 1);
4841
+ return maxHops;
4842
+ }
4843
+ function rankMemoriesByOverlap(map) {
4844
+ return [...map.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
4845
+ }
4846
+ async function traverseGraphLane(query, entityCtx, options = {}) {
4847
+ const seedNames = extractQueryEntities(query);
4848
+ if (seedNames.length === 0) return [];
4849
+ const entityFanout = clampPositiveInt(options.entityFanout, ENTITY_FANOUT);
4850
+ const nodeBudget = clampPositiveInt(options.nodeBudget, NODE_BUDGET);
4851
+ const maxHops = capHopsForDensity(clampPositiveInt(options.maxHops, MAX_HOPS), options.vaultSize);
4852
+ const keepActive = async (map) => {
4853
+ if (!options.filterActiveMemoryIds || map.size === 0) return map;
4854
+ const active = await options.filterActiveMemoryIds([...map.keys()]);
4855
+ const filtered = /* @__PURE__ */ new Map();
4856
+ for (const [id, names] of map) if (active.has(id)) filtered.set(id, names);
4857
+ return filtered;
4858
+ };
4859
+ const hop1 = await keepActive(await getMemoriesByEntityNamesOp(entityCtx, seedNames));
4860
+ if (hop1.size === 0) return [];
4861
+ const hop1Ranking = rankMemoriesByOverlap(hop1);
4862
+ if (maxHops <= 1) return hop1Ranking;
4863
+ const hop1Emitted = hop1Ranking.slice(0, nodeBudget);
4864
+ const perHopRankings = [hop1Emitted];
4865
+ const firstHopOf = /* @__PURE__ */ new Map();
4866
+ for (const id of hop1Emitted) if (!firstHopOf.has(id)) firstHopOf.set(id, 1);
4867
+ const accumulated = new Set(hop1Emitted);
4868
+ const seenEntities = new Set(seedNames);
4869
+ let frontier = hop1Emitted;
4870
+ for (let hop = 2; hop <= maxHops; hop++) {
4871
+ if (frontier.length === 0) break;
4872
+ if (accumulated.size >= nodeBudget) break;
4873
+ const memoryToEntities = await getEntitiesByMemoryIdsOp(entityCtx, frontier);
4874
+ const neighborCounts = /* @__PURE__ */ new Map();
4875
+ for (const names of memoryToEntities.values()) {
4876
+ for (const name of names) {
4877
+ if (seenEntities.has(name)) continue;
4878
+ neighborCounts.set(name, (neighborCounts.get(name) ?? 0) + 1);
4879
+ }
4880
+ }
4881
+ if (neighborCounts.size === 0) break;
4882
+ const rankedNeighbors = [...neighborCounts.entries()].sort((a, b) => b[1] - a[1]).map(([name]) => name);
4883
+ let topNeighbors = rankedNeighbors.slice(0, entityFanout);
4884
+ if (options.refineNeighbors && rankedNeighbors.length > entityFanout) {
4885
+ try {
4886
+ const refinerCandidateCap = Math.min(
4887
+ Math.max(entityFanout * 2, MIN_REFINER_CANDIDATES),
4888
+ MAX_REFINER_CANDIDATES
4889
+ );
4890
+ const refinerCandidates = rankedNeighbors.slice(0, refinerCandidateCap);
4891
+ const refined = await options.refineNeighbors.refine(
4892
+ query,
4893
+ refinerCandidates,
4894
+ entityFanout
4895
+ );
4896
+ const valid = [];
4897
+ const seen = /* @__PURE__ */ new Set();
4898
+ for (const name of refined) {
4899
+ if (!neighborCounts.has(name) || seen.has(name)) continue;
4900
+ seen.add(name);
4901
+ valid.push(name);
4902
+ if (valid.length >= entityFanout) break;
4903
+ }
4904
+ if (valid.length > 0) topNeighbors = valid;
4905
+ } catch (err) {
4906
+ getLogger().warn(
4907
+ `[memory/graph] neighbor refine failed; using co-occurrence order: ${err instanceof Error ? err.message : String(err)}`
4908
+ );
4909
+ }
4910
+ }
4911
+ for (const name of topNeighbors) seenEntities.add(name);
4912
+ const hopMap = await keepActive(await getMemoriesByEntityNamesOp(entityCtx, topNeighbors));
4913
+ if (hopMap.size === 0) break;
4914
+ const hopRanking = rankMemoriesByOverlap(hopMap);
4915
+ const newlyDiscovered = [];
4916
+ for (const id of hopRanking) {
4917
+ if (accumulated.has(id)) continue;
4918
+ accumulated.add(id);
4919
+ newlyDiscovered.push(id);
4920
+ firstHopOf.set(id, hop);
4921
+ if (accumulated.size >= nodeBudget) break;
4922
+ }
4923
+ if (newlyDiscovered.length === 0) break;
4924
+ perHopRankings.push(newlyDiscovered);
4925
+ frontier = newlyDiscovered;
4926
+ }
4927
+ const fused = rrfFuse(perHopRankings, options.rrfK);
4928
+ return [...fused.entries()].sort((a, b) => {
4929
+ const hopA = firstHopOf.get(a[0]) ?? Infinity;
4930
+ const hopB = firstHopOf.get(b[0]) ?? Infinity;
4931
+ if (hopA !== hopB) return hopA - hopB;
4932
+ return b[1] - a[1];
4933
+ }).map(([id]) => id);
4934
+ }
4935
+ var DEFAULT_REFINER_MODEL = "inclusionai/ling-2.6-flash";
4936
+ var DEFAULT_REFINER_ATTEMPTS = 1;
4937
+ var DEFAULT_REFINER_TOTAL_TIMEOUT_MS = 8e3;
4938
+ function createLlmNeighborRefiner(options) {
4939
+ return {
4940
+ async refine(query, candidates, limit) {
4941
+ if (candidates.length === 0) return [];
4942
+ const numbered = candidates.map((name, i) => `[${i + 1}] ${name}`).join("\n");
4943
+ const systemPrompt = `You help a memory-retrieval system decide which related topics to explore.
4944
+
4945
+ 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.
4946
+
4947
+ Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
4948
+ const parsed = await callPortalJsonCompletion({
4949
+ ...options.apiKey !== void 0 && { apiKey: options.apiKey },
4950
+ ...options.getToken !== void 0 && { getToken: options.getToken },
4951
+ ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
4952
+ model: options.model ?? DEFAULT_REFINER_MODEL,
4953
+ systemPrompt,
4954
+ userMessage: `Question: ${query}
4955
+
4956
+ Candidate topics:
4957
+ ${numbered}
4958
+
4959
+ Which should be expanded?`,
4960
+ tag: "memory/graph-refine",
4961
+ maxAttempts: options.maxAttempts ?? DEFAULT_REFINER_ATTEMPTS,
4962
+ totalTimeoutMs: options.totalTimeoutMs ?? DEFAULT_REFINER_TOTAL_TIMEOUT_MS,
4963
+ ...options.backoffMs && { backoffMs: options.backoffMs },
4964
+ ...options.fetchFn && { fetchFn: options.fetchFn }
4965
+ });
4966
+ if (parsed === null || typeof parsed !== "object") return [];
4967
+ const list = parsed.expand;
4968
+ if (!Array.isArray(list)) return [];
4969
+ const byNormalized = new Map(candidates.map((c) => [normalizeEntityName(c), c]));
4970
+ const out = [];
4971
+ for (const raw of list) {
4972
+ if (typeof raw !== "string") continue;
4973
+ const match = byNormalized.get(normalizeEntityName(raw));
4974
+ if (match) out.push(match);
4975
+ }
4976
+ return out;
4977
+ }
4978
+ };
4979
+ }
4980
+
4648
4981
  // src/lib/memory/queryTemporal.ts
4649
4982
  var RELATIVE_DAY_ENTRIES = [
4650
4983
  [/\btoday\b/, "today", 0],
@@ -4857,12 +5190,12 @@ var DEFAULT_CHUNK_MIN_SCORE = 0.5;
4857
5190
  function flagsForBudget(budget) {
4858
5191
  switch (budget) {
4859
5192
  case "high":
4860
- return { rerank: true, decompose: true };
5193
+ return { rerank: true, decompose: true, traverse: true };
4861
5194
  case "mid":
4862
- return { rerank: true, decompose: false };
5195
+ return { rerank: true, decompose: false, traverse: false };
4863
5196
  case "low":
4864
5197
  default:
4865
- return { rerank: false, decompose: false };
5198
+ return { rerank: false, decompose: false, traverse: false };
4866
5199
  }
4867
5200
  }
4868
5201
  function dedupeBy(items, ...keys) {
@@ -4930,11 +5263,25 @@ async function recall(query, ctx, options = {}) {
4930
5263
  }
4931
5264
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
4932
5265
  const wantsTemporal = types.includes("fact") && ctx.vaultCtx;
5266
+ const graphRefiner = flags.traverse && options.graphRefine && options.decomposeOptions ? createLlmNeighborRefiner(options.decomposeOptions) : void 0;
4933
5267
  const prepStart = nowMs();
4934
5268
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
4935
5269
  needsChunkEmbedding ? generateEmbedding(query, ctx.embeddingOptions) : Promise.resolve(void 0),
4936
- buildGraphLaneRanking(query, ctx),
4937
- wantsTemporal ? buildTemporalLaneRanking(query, ctx.vaultCtx, options.now) : Promise.resolve([])
5270
+ // The graph + temporal lanes are AUXILIARY (RRF side-signals). A transient
5271
+ // WatermelonDB throw in either must NOT reject this Promise.all and take
5272
+ // PRIMARY cosine/BM25 recall down with it — degrade the failing lane to an
5273
+ // empty ranking instead (mirrors safeCountVault's fail-soft posture).
5274
+ safeLane(
5275
+ "graph",
5276
+ () => buildGraphLaneRanking(query, ctx, flags.traverse, {
5277
+ ...options.maxHops !== void 0 && { maxHops: options.maxHops },
5278
+ ...options.entityFanout !== void 0 && { entityFanout: options.entityFanout },
5279
+ ...options.nodeBudget !== void 0 && { nodeBudget: options.nodeBudget },
5280
+ ...options.rrfK !== void 0 && { rrfK: options.rrfK },
5281
+ ...graphRefiner && { refineNeighbors: graphRefiner }
5282
+ })
5283
+ ),
5284
+ wantsTemporal ? safeLane("temporal", () => buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)) : Promise.resolve([])
4938
5285
  ]);
4939
5286
  prepMs = nowMs() - prepStart;
4940
5287
  if (types.includes("fact") && ctx.vaultCtx && ctx.vaultCache) {
@@ -4984,6 +5331,8 @@ async function recall(query, ctx, options = {}) {
4984
5331
  },
4985
5332
  ...options.scopes && { scopes: options.scopes },
4986
5333
  ...options.folderId !== void 0 && { folderId: options.folderId },
5334
+ ...options.factTypes?.length && { factTypes: options.factTypes },
5335
+ ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
4987
5336
  ...entityRanking.length > 0 && { entityRanking },
4988
5337
  ...temporalRanking.length > 0 && { temporalRanking }
4989
5338
  }
@@ -5105,6 +5454,9 @@ function toFactMemory(r) {
5105
5454
  ...r.eventTimeStart !== void 0 && { eventTimeStart: r.eventTimeStart },
5106
5455
  ...r.eventTimeEnd !== void 0 && { eventTimeEnd: r.eventTimeEnd },
5107
5456
  ...r.eventTimeKind !== void 0 && { eventTimeKind: r.eventTimeKind },
5457
+ // Typed memory (PR1) — surfaced from VaultSearchResult so recall results
5458
+ // (and UI reading them) carry the fact's type. Undefined when untyped.
5459
+ ...r.factType !== void 0 && { factType: r.factType },
5108
5460
  ...r.sourceChunkIds !== void 0 && r.sourceChunkIds !== null && { sourceChunkIds: r.sourceChunkIds },
5109
5461
  // r.similarity from searchVaultMemoriesWithSize is the fused score
5110
5462
  // (cosine + BM25 + RRF + recency + proof) when useFusion=true (the
@@ -5118,14 +5470,47 @@ function toFactMemory(r) {
5118
5470
  updatedAt: r.updatedAt ?? /* @__PURE__ */ new Date(0)
5119
5471
  };
5120
5472
  }
5121
- async function buildGraphLaneRanking(query, ctx) {
5473
+ async function buildGraphLaneRanking(query, ctx, traverse = false, traversalOptions = {}) {
5122
5474
  const entityCtx = ctx.entityCtx ?? ctx.vaultCtx?.entityCtx;
5123
5475
  if (!entityCtx) return [];
5476
+ if (traverse) {
5477
+ const vaultSize = await safeCountVault(ctx);
5478
+ const vaultCtx2 = ctx.vaultCtx;
5479
+ return traverseGraphLane(query, entityCtx, {
5480
+ ...traversalOptions,
5481
+ ...vaultSize !== void 0 && { vaultSize },
5482
+ ...vaultCtx2 && {
5483
+ filterActiveMemoryIds: (ids) => getActiveVaultMemoryIdsOp(vaultCtx2, ids)
5484
+ }
5485
+ });
5486
+ }
5124
5487
  const queryEntities = extractQueryEntities(query);
5125
5488
  if (queryEntities.length === 0) return [];
5126
5489
  const memoryToEntities = await getMemoriesByEntityNamesOp(entityCtx, queryEntities);
5127
5490
  if (memoryToEntities.size === 0) return [];
5128
- return [...memoryToEntities.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
5491
+ const ranked = [...memoryToEntities.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
5492
+ const vaultCtx = ctx.vaultCtx;
5493
+ if (!vaultCtx) return ranked;
5494
+ const activeIds = await getActiveVaultMemoryIdsOp(vaultCtx, ranked);
5495
+ return ranked.filter((id) => activeIds.has(id));
5496
+ }
5497
+ async function safeCountVault(ctx) {
5498
+ if (!ctx.vaultCtx) return void 0;
5499
+ try {
5500
+ return await countActiveVaultMemoriesOp(ctx.vaultCtx);
5501
+ } catch {
5502
+ return void 0;
5503
+ }
5504
+ }
5505
+ async function safeLane(label, run) {
5506
+ try {
5507
+ return await run();
5508
+ } catch (err) {
5509
+ getLogger().warn(
5510
+ `[memory/recall] ${label} lane failed; continuing without it: ${err instanceof Error ? err.message : String(err)}`
5511
+ );
5512
+ return [];
5513
+ }
5129
5514
  }
5130
5515
  async function buildTemporalLaneRanking(query, vaultCtx, now) {
5131
5516
  const window2 = parseQueryTimeWindow(query, now);
@@ -5239,6 +5624,7 @@ export {
5239
5624
  replaceMemoryEntitiesGuardedOp,
5240
5625
  backfillMemoryEntityUserIdsOp,
5241
5626
  getMemoriesByEntityNamesOp,
5627
+ getEntitiesByMemoryIdsOp,
5242
5628
  vaultMemoryToStored,
5243
5629
  createVaultMemoryOp,
5244
5630
  createSupersedingMemoryOp,
@@ -5256,12 +5642,24 @@ export {
5256
5642
  getMemoriesNeedingTopicExtractionOp,
5257
5643
  stampTopicsExtractedAtOp,
5258
5644
  deleteAllVaultMemoriesForUserOp,
5645
+ assertVaultScopeForSweep,
5646
+ getDecayCandidatesRawOp,
5647
+ archiveVaultMemoryOp,
5648
+ restoreVaultMemoryOp,
5649
+ hardDeleteDecayedOp,
5259
5650
  updateVaultMemoryEmbeddingOp,
5260
5651
  RerankerUnavailableError,
5261
5652
  isRerankerAvailable,
5262
5653
  DEFAULT_VAULT_CACHE_SIZE,
5263
5654
  LRUCache,
5264
5655
  createVaultEmbeddingCache,
5656
+ MAX_HOPS,
5657
+ ENTITY_FANOUT,
5658
+ NODE_BUDGET,
5659
+ VAULT_SIZE_HOP_CAP,
5660
+ capHopsForDensity,
5661
+ traverseGraphLane,
5662
+ createLlmNeighborRefiner,
5265
5663
  recall,
5266
5664
  preEmbedVaultMemories,
5267
5665
  eagerEmbedContent,