@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-Y7PMXJT6.mjs";
3
+ } from "./chunk-GZRK43CA.mjs";
4
4
  import "./chunk-DFVCS3QQ.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {
@@ -16157,6 +16157,36 @@ async function getMemoriesByEntityNamesOp(ctx, entityNames) {
16157
16157
  }
16158
16158
  return out;
16159
16159
  }
16160
+ async function getEntitiesByMemoryIdsOp(ctx, memoryIds) {
16161
+ const unique = Array.from(new Set(memoryIds.filter((id) => id.length > 0)));
16162
+ if (unique.length === 0) return /* @__PURE__ */ new Map();
16163
+ const linkConditions = [import_watermelondb2.Q.where("memory_id", import_watermelondb2.Q.oneOf(unique))];
16164
+ if (ctx.userId !== void 0) {
16165
+ if (ctx.allowUnscopedRows) {
16166
+ linkConditions.push(import_watermelondb2.Q.or(import_watermelondb2.Q.where("user_id", ctx.userId), import_watermelondb2.Q.where("user_id", null)));
16167
+ } else {
16168
+ linkConditions.push(import_watermelondb2.Q.where("user_id", ctx.userId));
16169
+ }
16170
+ }
16171
+ const links = await ctx.memoryEntityCollection.query(...linkConditions).fetch();
16172
+ if (links.length === 0) return /* @__PURE__ */ new Map();
16173
+ const entityIds = Array.from(new Set(links.map((l) => String(l.entityId))));
16174
+ const entityRows = await ctx.entityCollection.query(import_watermelondb2.Q.where("id", import_watermelondb2.Q.oneOf(entityIds))).fetch();
16175
+ const entityIdToName = new Map(entityRows.map((e) => [e.id, e.canonicalName]));
16176
+ const out = /* @__PURE__ */ new Map();
16177
+ for (const link of links) {
16178
+ const entityName = entityIdToName.get(String(link.entityId));
16179
+ if (!entityName) continue;
16180
+ const memoryId = String(link.memoryId);
16181
+ let bucket = out.get(memoryId);
16182
+ if (!bucket) {
16183
+ bucket = /* @__PURE__ */ new Set();
16184
+ out.set(memoryId, bucket);
16185
+ }
16186
+ bucket.add(entityName);
16187
+ }
16188
+ return out;
16189
+ }
16160
16190
 
16161
16191
  // src/lib/db/memoryVault/operations.ts
16162
16192
  var import_watermelondb3 = __toESM(require_watermelondb());
@@ -16194,11 +16224,18 @@ function isOwnedByCtxUser(ctx, record) {
16194
16224
  function baseVaultConditions(ctx, options) {
16195
16225
  return [
16196
16226
  ...options?.includeDeleted ? [] : [import_watermelondb3.Q.where("is_deleted", false)],
16227
+ ...options?.includeArchived ? [] : [import_watermelondb3.Q.where("archived_at", import_watermelondb3.Q.eq(null))],
16228
+ ...options?.includeQuarantined ? [] : [import_watermelondb3.Q.where("trust_tier", import_watermelondb3.Q.notEq("quarantined"))],
16197
16229
  ...options?.includeSuperseded ? [] : [import_watermelondb3.Q.where("superseded_by", null)],
16198
16230
  ...ctx.userId !== void 0 ? [import_watermelondb3.Q.where("user_id", ctx.userId)] : [],
16199
16231
  ...options?.since ? [import_watermelondb3.Q.where("updated_at", import_watermelondb3.Q.gt(options.since.getTime()))] : []
16200
16232
  ];
16201
16233
  }
16234
+ var KNOWN_TRUST_TIERS = /* @__PURE__ */ new Set(["quarantined", "trusted"]);
16235
+ function normalizeTrustTier(value) {
16236
+ if (value === void 0 || value === null) return null;
16237
+ return KNOWN_TRUST_TIERS.has(value) ? value : null;
16238
+ }
16202
16239
  async function mapInBatches(items, fn) {
16203
16240
  const BATCH = 50;
16204
16241
  const results = [];
@@ -16239,6 +16276,9 @@ function vaultMemoryToStoredRaw(memory) {
16239
16276
  supersededBy: memory.supersededBy ?? null,
16240
16277
  supersededAt: memory.supersededAt ?? null,
16241
16278
  lastObservedAt: memory.lastObservedAt ?? null,
16279
+ factType: memory.factType ?? null,
16280
+ archivedAt: memory.archivedAt ?? null,
16281
+ trustTier: memory.trustTier ?? null,
16242
16282
  createdAt: memory.createdAt,
16243
16283
  updatedAt: memory.updatedAt,
16244
16284
  isDeleted: memory.isDeleted
@@ -16280,6 +16320,12 @@ async function createVaultMemoryOp(ctx, opts) {
16280
16320
  record._setRaw("event_time_end", opts.eventTime.end ?? null);
16281
16321
  record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
16282
16322
  }
16323
+ if (opts.factType !== void 0) {
16324
+ record._setRaw("fact_type", opts.factType);
16325
+ }
16326
+ if (opts.trustTier !== void 0) {
16327
+ record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
16328
+ }
16283
16329
  });
16284
16330
  });
16285
16331
  return vaultMemoryToStored(created, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
@@ -16420,6 +16466,12 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
16420
16466
  record._setRaw("event_time_end", opts.eventTime.end ?? null);
16421
16467
  record._setRaw("event_time_kind", opts.eventTime.kind ?? null);
16422
16468
  }
16469
+ if (opts.factType !== void 0) {
16470
+ record._setRaw("fact_type", opts.factType);
16471
+ }
16472
+ if (opts.trustTier !== void 0) {
16473
+ record._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
16474
+ }
16423
16475
  })
16424
16476
  );
16425
16477
  await ctx.database.batch(...prepared);
@@ -16480,6 +16532,9 @@ function vaultMemoryRawToStoredRaw(raw) {
16480
16532
  supersededBy: raw.superseded_by ?? null,
16481
16533
  supersededAt: raw.superseded_at ?? null,
16482
16534
  lastObservedAt: raw.last_observed_at ?? null,
16535
+ factType: raw.fact_type ?? null,
16536
+ archivedAt: raw.archived_at ?? null,
16537
+ trustTier: raw.trust_tier ?? null,
16483
16538
  createdAt: new Date(raw.created_at),
16484
16539
  updatedAt: new Date(raw.updated_at),
16485
16540
  isDeleted: raw.is_deleted === true || raw.is_deleted === 1
@@ -16497,6 +16552,7 @@ async function getAllVaultMemoriesOp(ctx, options) {
16497
16552
  ...baseVaultConditions(ctx, options),
16498
16553
  ...options?.scopes?.length ? [import_watermelondb3.Q.where("scope", import_watermelondb3.Q.oneOf(options.scopes))] : [],
16499
16554
  ...options?.folderId !== void 0 ? [import_watermelondb3.Q.where("folder_id", options.folderId)] : [],
16555
+ ...options?.factTypes?.length ? [import_watermelondb3.Q.where("fact_type", import_watermelondb3.Q.oneOf(options.factTypes))] : [],
16500
16556
  import_watermelondb3.Q.sortBy(options?.since ? "updated_at" : "created_at", import_watermelondb3.Q.desc),
16501
16557
  ...options?.limit !== null && options?.limit !== void 0 && options.limit > 0 ? [import_watermelondb3.Q.take(options.limit)] : []
16502
16558
  ];
@@ -16518,6 +16574,14 @@ async function getAllVaultMemoryContentsOp(ctx, options) {
16518
16574
  return stored.content;
16519
16575
  });
16520
16576
  }
16577
+ async function countActiveVaultMemoriesOp(ctx) {
16578
+ return ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx)).fetchCount();
16579
+ }
16580
+ async function getActiveVaultMemoryIdsOp(ctx, ids) {
16581
+ if (ids.length === 0) return /* @__PURE__ */ new Set();
16582
+ const rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), import_watermelondb3.Q.where("id", import_watermelondb3.Q.oneOf(ids))).unsafeFetchRaw();
16583
+ return new Set(rows.map((r) => r.id));
16584
+ }
16521
16585
  async function updateVaultMemoryOp(ctx, id, opts) {
16522
16586
  try {
16523
16587
  const probe = await ctx.vaultMemoryCollection.find(id);
@@ -16571,6 +16635,15 @@ async function updateVaultMemoryOp(ctx, id, opts) {
16571
16635
  if (opts.lastObservedAt !== void 0) {
16572
16636
  r._setRaw("last_observed_at", opts.lastObservedAt);
16573
16637
  }
16638
+ if (opts.factType !== void 0) {
16639
+ r._setRaw("fact_type", opts.factType);
16640
+ }
16641
+ if (opts.trustTier !== void 0) {
16642
+ r._setRaw("trust_tier", normalizeTrustTier(opts.trustTier));
16643
+ }
16644
+ if (opts.restore) {
16645
+ r._setRaw("archived_at", null);
16646
+ }
16574
16647
  if (opts.preserveUpdatedAt) {
16575
16648
  r._setRaw("updated_at", originalUpdatedAt);
16576
16649
  }
@@ -16770,6 +16843,98 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
16770
16843
  }
16771
16844
  return records.length;
16772
16845
  }
16846
+ function assertVaultScopeForSweep(ctx) {
16847
+ if (ctx.userId === void 0 && ctx.singleTenant !== true) {
16848
+ throw new Error(
16849
+ "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.)"
16850
+ );
16851
+ }
16852
+ }
16853
+ async function getDecayCandidatesRawOp(ctx) {
16854
+ assertVaultScopeForSweep(ctx);
16855
+ const results = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true })).unsafeFetchRaw();
16856
+ return results.map((raw) => ({
16857
+ uniqueId: raw.id,
16858
+ factType: raw.fact_type ?? null,
16859
+ eventTimeEnd: raw.event_time_end ?? null,
16860
+ eventTimeKind: raw.event_time_kind ?? null,
16861
+ updatedAt: raw.updated_at,
16862
+ archivedAt: raw.archived_at ?? null,
16863
+ source: raw.source ?? null,
16864
+ trustTier: raw.trust_tier ?? null
16865
+ }));
16866
+ }
16867
+ async function archiveVaultMemoryOp(ctx, id, opts) {
16868
+ try {
16869
+ const record = await ctx.vaultMemoryCollection.find(id);
16870
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
16871
+ let stale = false;
16872
+ const archivedAtValue = opts?.now ?? Date.now();
16873
+ await ctx.database.write(async () => {
16874
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.archivedAt !== null) {
16875
+ stale = true;
16876
+ return;
16877
+ }
16878
+ if (opts?.expectedUpdatedAt !== void 0 && record.updatedAt.getTime() !== opts.expectedUpdatedAt) {
16879
+ stale = true;
16880
+ return;
16881
+ }
16882
+ await record.update((r) => {
16883
+ r._setRaw("archived_at", archivedAtValue);
16884
+ });
16885
+ });
16886
+ return !stale;
16887
+ } catch {
16888
+ return false;
16889
+ }
16890
+ }
16891
+ async function restoreVaultMemoryOp(ctx, id) {
16892
+ try {
16893
+ const record = await ctx.vaultMemoryCollection.find(id);
16894
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
16895
+ let stale = false;
16896
+ await ctx.database.write(async () => {
16897
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
16898
+ stale = true;
16899
+ return;
16900
+ }
16901
+ await record.update((r) => {
16902
+ r._setRaw("archived_at", null);
16903
+ });
16904
+ });
16905
+ return !stale;
16906
+ } catch {
16907
+ return false;
16908
+ }
16909
+ }
16910
+ async function hardDeleteDecayedOp(ctx, id, opts) {
16911
+ try {
16912
+ const record = await ctx.vaultMemoryCollection.find(id);
16913
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
16914
+ const now = opts.now ?? Date.now();
16915
+ let stale = false;
16916
+ await ctx.database.write(async () => {
16917
+ const archivedAt = record.archivedAt;
16918
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
16919
+ stale = true;
16920
+ return;
16921
+ }
16922
+ await record.update((r) => {
16923
+ r._setRaw("is_deleted", true);
16924
+ });
16925
+ });
16926
+ if (stale) return false;
16927
+ if (ctx.entityCtx) {
16928
+ try {
16929
+ await unlinkMemoryEntitiesOp(ctx.entityCtx, [id]);
16930
+ } catch {
16931
+ }
16932
+ }
16933
+ return true;
16934
+ } catch {
16935
+ return false;
16936
+ }
16937
+ }
16773
16938
  async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel) {
16774
16939
  try {
16775
16940
  const record = await ctx.vaultMemoryCollection.find(id);
@@ -17795,6 +17960,7 @@ function rankVaultMemories(_query, queryEmbedding, items, options) {
17795
17960
  sourceChunkIds: item.sourceChunkIds,
17796
17961
  eventTimeEnd: item.eventTimeEnd,
17797
17962
  eventTimeKind: item.eventTimeKind,
17963
+ factType: item.factType,
17798
17964
  similarity: cosineSimilarity(queryEmbedding, item.embedding)
17799
17965
  }));
17800
17966
  const filtered = scored.filter((r) => r.similarity >= minSimilarity);
@@ -17845,6 +18011,7 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
17845
18011
  const recencyAlpha = options?.recencyAlpha ?? 1;
17846
18012
  const proofCountAlpha = options?.proofCountAlpha ?? 0.1;
17847
18013
  const bm25AdmissionDivisor = options?.bm25AdmissionDivisor ?? 50;
18014
+ const factTypeWeights = options?.factTypeWeights;
17848
18015
  if (items.length === 0) return [];
17849
18016
  const baseRanked = rankVaultMemories(query, queryEmbedding, items, {
17850
18017
  limit: items.length,
@@ -17876,7 +18043,8 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
17876
18043
  eventTimeStart: item.eventTimeStart,
17877
18044
  sourceChunkIds: item.sourceChunkIds,
17878
18045
  eventTimeEnd: item.eventTimeEnd,
17879
- eventTimeKind: item.eventTimeKind
18046
+ eventTimeKind: item.eventTimeKind,
18047
+ factType: item.factType
17880
18048
  });
17881
18049
  }
17882
18050
  const boostFor = (id) => {
@@ -17885,7 +18053,9 @@ function rankFusedVaultMemories(query, queryEmbedding, items, options) {
17885
18053
  const recencyBoost = 1 + recencyAlpha * (recency - 0.5);
17886
18054
  const proofCount = Math.max(1, item?.proofCount ?? 1);
17887
18055
  const proofBoost = 1 + proofCountAlpha * Math.log(1 + proofCount) - proofCountAlpha * Math.log(2);
17888
- return recencyBoost * proofBoost;
18056
+ const rawTypeWeight = item?.factType ? factTypeWeights?.[item.factType] : void 0;
18057
+ const typeWeight = rawTypeWeight !== void 0 && Number.isFinite(rawTypeWeight) && rawTypeWeight > 0 ? rawTypeWeight : 1;
18058
+ return recencyBoost * proofBoost * typeWeight;
17889
18059
  };
17890
18060
  let combined = [...baseRanked, ...admitted].map((r) => ({
17891
18061
  ...r,
@@ -17937,7 +18107,8 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
17937
18107
  supersessionWindow: options?.supersessionWindow,
17938
18108
  proofCountAlpha: options?.proofCountAlpha,
17939
18109
  bm25AdmissionDivisor: options?.bm25AdmissionDivisor,
17940
- rrfK: options?.rrfK
18110
+ rrfK: options?.rrfK,
18111
+ ...options?.factTypeWeights && { factTypeWeights: options.factTypeWeights }
17941
18112
  });
17942
18113
  if (options?.v2HeadStats) {
17943
18114
  options.v2HeadStats.hadResults = v2Ranked.length > 0;
@@ -18030,7 +18201,8 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
18030
18201
  eventTimeStart: orig?.eventTimeStart,
18031
18202
  sourceChunkIds: orig?.sourceChunkIds,
18032
18203
  eventTimeEnd: orig?.eventTimeEnd,
18033
- eventTimeKind: orig?.eventTimeKind
18204
+ eventTimeKind: orig?.eventTimeKind,
18205
+ factType: orig?.factType
18034
18206
  };
18035
18207
  });
18036
18208
  const remainingTail = combined.filter((r) => !pickedIds.has(r.uniqueId));
@@ -18056,7 +18228,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
18056
18228
  ...options?.bm25AdmissionDivisor !== void 0 && {
18057
18229
  bm25AdmissionDivisor: options.bm25AdmissionDivisor
18058
18230
  },
18059
- ...options?.rrfK !== void 0 && { rrfK: options.rrfK }
18231
+ ...options?.rrfK !== void 0 && { rrfK: options.rrfK },
18232
+ ...options?.factTypeWeights && { factTypeWeights: options.factTypeWeights }
18060
18233
  };
18061
18234
  const originalRanked = rankFusedVaultMemories(originalQuery, originalQueryEmbedding, items, {
18062
18235
  limit: perFacetTopN,
@@ -18094,7 +18267,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
18094
18267
  eventTimeStart: item.eventTimeStart,
18095
18268
  sourceChunkIds: item.sourceChunkIds,
18096
18269
  eventTimeEnd: item.eventTimeEnd,
18097
- eventTimeKind: item.eventTimeKind
18270
+ eventTimeKind: item.eventTimeKind,
18271
+ factType: item.factType
18098
18272
  };
18099
18273
  }
18100
18274
  );
@@ -18118,7 +18292,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
18118
18292
  eventTimeStart: item.eventTimeStart,
18119
18293
  sourceChunkIds: item.sourceChunkIds,
18120
18294
  eventTimeEnd: item.eventTimeEnd,
18121
- eventTimeKind: item.eventTimeKind
18295
+ eventTimeKind: item.eventTimeKind,
18296
+ factType: item.factType
18122
18297
  });
18123
18298
  }
18124
18299
  }
@@ -18175,7 +18350,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
18175
18350
  eventTimeStart: orig?.eventTimeStart,
18176
18351
  sourceChunkIds: orig?.sourceChunkIds,
18177
18352
  eventTimeEnd: orig?.eventTimeEnd,
18178
- eventTimeKind: orig?.eventTimeKind
18353
+ eventTimeKind: orig?.eventTimeKind,
18354
+ factType: orig?.factType
18179
18355
  };
18180
18356
  });
18181
18357
  const remainingTail = combined.filter((r) => !pickedIds.has(r.uniqueId));
@@ -18247,6 +18423,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
18247
18423
  const queryOpts = {};
18248
18424
  if (scopes?.length) queryOpts.scopes = scopes;
18249
18425
  if (folderId !== void 0) queryOpts.folderId = folderId;
18426
+ if (searchOptions?.factTypes?.length) queryOpts.factTypes = searchOptions.factTypes;
18427
+ if (searchOptions?.includeArchived) queryOpts.includeArchived = true;
18250
18428
  const loaded = await getAllVaultMemoriesOp(
18251
18429
  vaultCtx,
18252
18430
  Object.keys(queryOpts).length > 0 ? queryOpts : void 0
@@ -18318,7 +18496,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
18318
18496
  eventTimeStart: m.eventTimeStart,
18319
18497
  sourceChunkIds: m.sourceChunkIds,
18320
18498
  eventTimeEnd: m.eventTimeEnd,
18321
- eventTimeKind: normalizeEventTimeKind(m.eventTimeKind)
18499
+ eventTimeKind: normalizeEventTimeKind(m.eventTimeKind),
18500
+ factType: m.factType
18322
18501
  }));
18323
18502
  if (queryEmbedding.length > 0) {
18324
18503
  const mismatched = embeddedItems.filter(
@@ -18362,7 +18541,8 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
18362
18541
  ...searchOptions?.bm25AdmissionDivisor !== void 0 && {
18363
18542
  bm25AdmissionDivisor: searchOptions.bm25AdmissionDivisor
18364
18543
  },
18365
- ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK }
18544
+ ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
18545
+ ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
18366
18546
  };
18367
18547
  if (useFusion && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
18368
18548
  const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
@@ -18529,7 +18709,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
18529
18709
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
18530
18710
  );
18531
18711
  }
18532
- const { recall: recall2 } = await import("./recall-Z72DM2JK.mjs");
18712
+ const { recall: recall2 } = await import("./recall-76WPPKLR.mjs");
18533
18713
  const result = await recall2(
18534
18714
  query,
18535
18715
  { vaultCtx, embeddingOptions, vaultCache: cache },
@@ -18640,6 +18820,159 @@ function extractQueryEntities(query) {
18640
18820
  return [...seen];
18641
18821
  }
18642
18822
 
18823
+ // src/lib/memory/graphTraversal.ts
18824
+ var MAX_HOPS = 2;
18825
+ var ENTITY_FANOUT = 8;
18826
+ var MIN_REFINER_CANDIDATES = 16;
18827
+ var MAX_REFINER_CANDIDATES = 64;
18828
+ var NODE_BUDGET = 64;
18829
+ var VAULT_SIZE_HOP_CAP = 1e3;
18830
+ function clampPositiveInt(value, fallback) {
18831
+ if (value === void 0 || !Number.isFinite(value) || value < 1) return fallback;
18832
+ return Math.floor(value);
18833
+ }
18834
+ function capHopsForDensity(maxHops, vaultSize) {
18835
+ if (vaultSize !== void 0 && vaultSize > VAULT_SIZE_HOP_CAP) return Math.min(maxHops, 1);
18836
+ return maxHops;
18837
+ }
18838
+ function rankMemoriesByOverlap(map) {
18839
+ return [...map.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
18840
+ }
18841
+ async function traverseGraphLane(query, entityCtx, options = {}) {
18842
+ const seedNames = extractQueryEntities(query);
18843
+ if (seedNames.length === 0) return [];
18844
+ const entityFanout = clampPositiveInt(options.entityFanout, ENTITY_FANOUT);
18845
+ const nodeBudget = clampPositiveInt(options.nodeBudget, NODE_BUDGET);
18846
+ const maxHops = capHopsForDensity(clampPositiveInt(options.maxHops, MAX_HOPS), options.vaultSize);
18847
+ const keepActive = async (map) => {
18848
+ if (!options.filterActiveMemoryIds || map.size === 0) return map;
18849
+ const active = await options.filterActiveMemoryIds([...map.keys()]);
18850
+ const filtered = /* @__PURE__ */ new Map();
18851
+ for (const [id, names] of map) if (active.has(id)) filtered.set(id, names);
18852
+ return filtered;
18853
+ };
18854
+ const hop1 = await keepActive(await getMemoriesByEntityNamesOp(entityCtx, seedNames));
18855
+ if (hop1.size === 0) return [];
18856
+ const hop1Ranking = rankMemoriesByOverlap(hop1);
18857
+ if (maxHops <= 1) return hop1Ranking;
18858
+ const hop1Emitted = hop1Ranking.slice(0, nodeBudget);
18859
+ const perHopRankings = [hop1Emitted];
18860
+ const firstHopOf = /* @__PURE__ */ new Map();
18861
+ for (const id of hop1Emitted) if (!firstHopOf.has(id)) firstHopOf.set(id, 1);
18862
+ const accumulated = new Set(hop1Emitted);
18863
+ const seenEntities = new Set(seedNames);
18864
+ let frontier = hop1Emitted;
18865
+ for (let hop = 2; hop <= maxHops; hop++) {
18866
+ if (frontier.length === 0) break;
18867
+ if (accumulated.size >= nodeBudget) break;
18868
+ const memoryToEntities = await getEntitiesByMemoryIdsOp(entityCtx, frontier);
18869
+ const neighborCounts = /* @__PURE__ */ new Map();
18870
+ for (const names of memoryToEntities.values()) {
18871
+ for (const name of names) {
18872
+ if (seenEntities.has(name)) continue;
18873
+ neighborCounts.set(name, (neighborCounts.get(name) ?? 0) + 1);
18874
+ }
18875
+ }
18876
+ if (neighborCounts.size === 0) break;
18877
+ const rankedNeighbors = [...neighborCounts.entries()].sort((a, b) => b[1] - a[1]).map(([name]) => name);
18878
+ let topNeighbors = rankedNeighbors.slice(0, entityFanout);
18879
+ if (options.refineNeighbors && rankedNeighbors.length > entityFanout) {
18880
+ try {
18881
+ const refinerCandidateCap = Math.min(
18882
+ Math.max(entityFanout * 2, MIN_REFINER_CANDIDATES),
18883
+ MAX_REFINER_CANDIDATES
18884
+ );
18885
+ const refinerCandidates = rankedNeighbors.slice(0, refinerCandidateCap);
18886
+ const refined = await options.refineNeighbors.refine(
18887
+ query,
18888
+ refinerCandidates,
18889
+ entityFanout
18890
+ );
18891
+ const valid = [];
18892
+ const seen = /* @__PURE__ */ new Set();
18893
+ for (const name of refined) {
18894
+ if (!neighborCounts.has(name) || seen.has(name)) continue;
18895
+ seen.add(name);
18896
+ valid.push(name);
18897
+ if (valid.length >= entityFanout) break;
18898
+ }
18899
+ if (valid.length > 0) topNeighbors = valid;
18900
+ } catch (err) {
18901
+ getLogger().warn(
18902
+ `[memory/graph] neighbor refine failed; using co-occurrence order: ${err instanceof Error ? err.message : String(err)}`
18903
+ );
18904
+ }
18905
+ }
18906
+ for (const name of topNeighbors) seenEntities.add(name);
18907
+ const hopMap = await keepActive(await getMemoriesByEntityNamesOp(entityCtx, topNeighbors));
18908
+ if (hopMap.size === 0) break;
18909
+ const hopRanking = rankMemoriesByOverlap(hopMap);
18910
+ const newlyDiscovered = [];
18911
+ for (const id of hopRanking) {
18912
+ if (accumulated.has(id)) continue;
18913
+ accumulated.add(id);
18914
+ newlyDiscovered.push(id);
18915
+ firstHopOf.set(id, hop);
18916
+ if (accumulated.size >= nodeBudget) break;
18917
+ }
18918
+ if (newlyDiscovered.length === 0) break;
18919
+ perHopRankings.push(newlyDiscovered);
18920
+ frontier = newlyDiscovered;
18921
+ }
18922
+ const fused = rrfFuse(perHopRankings, options.rrfK);
18923
+ return [...fused.entries()].sort((a, b) => {
18924
+ const hopA = firstHopOf.get(a[0]) ?? Infinity;
18925
+ const hopB = firstHopOf.get(b[0]) ?? Infinity;
18926
+ if (hopA !== hopB) return hopA - hopB;
18927
+ return b[1] - a[1];
18928
+ }).map(([id]) => id);
18929
+ }
18930
+ var DEFAULT_REFINER_MODEL = "inclusionai/ling-2.6-flash";
18931
+ var DEFAULT_REFINER_ATTEMPTS = 1;
18932
+ var DEFAULT_REFINER_TOTAL_TIMEOUT_MS = 8e3;
18933
+ function createLlmNeighborRefiner(options) {
18934
+ return {
18935
+ async refine(query, candidates, limit) {
18936
+ if (candidates.length === 0) return [];
18937
+ const numbered = candidates.map((name, i) => `[${i + 1}] ${name}`).join("\n");
18938
+ const systemPrompt = `You help a memory-retrieval system decide which related topics to explore.
18939
+
18940
+ 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.
18941
+
18942
+ Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
18943
+ const parsed = await callPortalJsonCompletion({
18944
+ ...options.apiKey !== void 0 && { apiKey: options.apiKey },
18945
+ ...options.getToken !== void 0 && { getToken: options.getToken },
18946
+ ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
18947
+ model: options.model ?? DEFAULT_REFINER_MODEL,
18948
+ systemPrompt,
18949
+ userMessage: `Question: ${query}
18950
+
18951
+ Candidate topics:
18952
+ ${numbered}
18953
+
18954
+ Which should be expanded?`,
18955
+ tag: "memory/graph-refine",
18956
+ maxAttempts: options.maxAttempts ?? DEFAULT_REFINER_ATTEMPTS,
18957
+ totalTimeoutMs: options.totalTimeoutMs ?? DEFAULT_REFINER_TOTAL_TIMEOUT_MS,
18958
+ ...options.backoffMs && { backoffMs: options.backoffMs },
18959
+ ...options.fetchFn && { fetchFn: options.fetchFn }
18960
+ });
18961
+ if (parsed === null || typeof parsed !== "object") return [];
18962
+ const list = parsed.expand;
18963
+ if (!Array.isArray(list)) return [];
18964
+ const byNormalized = new Map(candidates.map((c) => [normalizeEntityName(c), c]));
18965
+ const out = [];
18966
+ for (const raw of list) {
18967
+ if (typeof raw !== "string") continue;
18968
+ const match = byNormalized.get(normalizeEntityName(raw));
18969
+ if (match) out.push(match);
18970
+ }
18971
+ return out;
18972
+ }
18973
+ };
18974
+ }
18975
+
18643
18976
  // src/lib/memory/queryTemporal.ts
18644
18977
  var RELATIVE_DAY_ENTRIES = [
18645
18978
  [/\btoday\b/, "today", 0],
@@ -18852,12 +19185,12 @@ var DEFAULT_CHUNK_MIN_SCORE = 0.5;
18852
19185
  function flagsForBudget(budget) {
18853
19186
  switch (budget) {
18854
19187
  case "high":
18855
- return { rerank: true, decompose: true };
19188
+ return { rerank: true, decompose: true, traverse: true };
18856
19189
  case "mid":
18857
- return { rerank: true, decompose: false };
19190
+ return { rerank: true, decompose: false, traverse: false };
18858
19191
  case "low":
18859
19192
  default:
18860
- return { rerank: false, decompose: false };
19193
+ return { rerank: false, decompose: false, traverse: false };
18861
19194
  }
18862
19195
  }
18863
19196
  function dedupeBy(items, ...keys) {
@@ -18925,11 +19258,25 @@ async function recall(query, ctx, options = {}) {
18925
19258
  }
18926
19259
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
18927
19260
  const wantsTemporal = types.includes("fact") && ctx.vaultCtx;
19261
+ const graphRefiner = flags.traverse && options.graphRefine && options.decomposeOptions ? createLlmNeighborRefiner(options.decomposeOptions) : void 0;
18928
19262
  const prepStart = nowMs();
18929
19263
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
18930
19264
  needsChunkEmbedding ? generateEmbedding(query, ctx.embeddingOptions) : Promise.resolve(void 0),
18931
- buildGraphLaneRanking(query, ctx),
18932
- wantsTemporal ? buildTemporalLaneRanking(query, ctx.vaultCtx, options.now) : Promise.resolve([])
19265
+ // The graph + temporal lanes are AUXILIARY (RRF side-signals). A transient
19266
+ // WatermelonDB throw in either must NOT reject this Promise.all and take
19267
+ // PRIMARY cosine/BM25 recall down with it — degrade the failing lane to an
19268
+ // empty ranking instead (mirrors safeCountVault's fail-soft posture).
19269
+ safeLane(
19270
+ "graph",
19271
+ () => buildGraphLaneRanking(query, ctx, flags.traverse, {
19272
+ ...options.maxHops !== void 0 && { maxHops: options.maxHops },
19273
+ ...options.entityFanout !== void 0 && { entityFanout: options.entityFanout },
19274
+ ...options.nodeBudget !== void 0 && { nodeBudget: options.nodeBudget },
19275
+ ...options.rrfK !== void 0 && { rrfK: options.rrfK },
19276
+ ...graphRefiner && { refineNeighbors: graphRefiner }
19277
+ })
19278
+ ),
19279
+ wantsTemporal ? safeLane("temporal", () => buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)) : Promise.resolve([])
18933
19280
  ]);
18934
19281
  prepMs = nowMs() - prepStart;
18935
19282
  if (types.includes("fact") && ctx.vaultCtx && ctx.vaultCache) {
@@ -18979,6 +19326,8 @@ async function recall(query, ctx, options = {}) {
18979
19326
  },
18980
19327
  ...options.scopes && { scopes: options.scopes },
18981
19328
  ...options.folderId !== void 0 && { folderId: options.folderId },
19329
+ ...options.factTypes?.length && { factTypes: options.factTypes },
19330
+ ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
18982
19331
  ...entityRanking.length > 0 && { entityRanking },
18983
19332
  ...temporalRanking.length > 0 && { temporalRanking }
18984
19333
  }
@@ -19100,6 +19449,9 @@ function toFactMemory(r) {
19100
19449
  ...r.eventTimeStart !== void 0 && { eventTimeStart: r.eventTimeStart },
19101
19450
  ...r.eventTimeEnd !== void 0 && { eventTimeEnd: r.eventTimeEnd },
19102
19451
  ...r.eventTimeKind !== void 0 && { eventTimeKind: r.eventTimeKind },
19452
+ // Typed memory (PR1) — surfaced from VaultSearchResult so recall results
19453
+ // (and UI reading them) carry the fact's type. Undefined when untyped.
19454
+ ...r.factType !== void 0 && { factType: r.factType },
19103
19455
  ...r.sourceChunkIds !== void 0 && r.sourceChunkIds !== null && { sourceChunkIds: r.sourceChunkIds },
19104
19456
  // r.similarity from searchVaultMemoriesWithSize is the fused score
19105
19457
  // (cosine + BM25 + RRF + recency + proof) when useFusion=true (the
@@ -19113,14 +19465,47 @@ function toFactMemory(r) {
19113
19465
  updatedAt: r.updatedAt ?? /* @__PURE__ */ new Date(0)
19114
19466
  };
19115
19467
  }
19116
- async function buildGraphLaneRanking(query, ctx) {
19468
+ async function buildGraphLaneRanking(query, ctx, traverse = false, traversalOptions = {}) {
19117
19469
  const entityCtx = ctx.entityCtx ?? ctx.vaultCtx?.entityCtx;
19118
19470
  if (!entityCtx) return [];
19471
+ if (traverse) {
19472
+ const vaultSize = await safeCountVault(ctx);
19473
+ const vaultCtx2 = ctx.vaultCtx;
19474
+ return traverseGraphLane(query, entityCtx, {
19475
+ ...traversalOptions,
19476
+ ...vaultSize !== void 0 && { vaultSize },
19477
+ ...vaultCtx2 && {
19478
+ filterActiveMemoryIds: (ids) => getActiveVaultMemoryIdsOp(vaultCtx2, ids)
19479
+ }
19480
+ });
19481
+ }
19119
19482
  const queryEntities = extractQueryEntities(query);
19120
19483
  if (queryEntities.length === 0) return [];
19121
19484
  const memoryToEntities = await getMemoriesByEntityNamesOp(entityCtx, queryEntities);
19122
19485
  if (memoryToEntities.size === 0) return [];
19123
- return [...memoryToEntities.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
19486
+ const ranked = [...memoryToEntities.entries()].sort((a, b) => b[1].size - a[1].size).map(([memoryId]) => memoryId);
19487
+ const vaultCtx = ctx.vaultCtx;
19488
+ if (!vaultCtx) return ranked;
19489
+ const activeIds = await getActiveVaultMemoryIdsOp(vaultCtx, ranked);
19490
+ return ranked.filter((id) => activeIds.has(id));
19491
+ }
19492
+ async function safeCountVault(ctx) {
19493
+ if (!ctx.vaultCtx) return void 0;
19494
+ try {
19495
+ return await countActiveVaultMemoriesOp(ctx.vaultCtx);
19496
+ } catch {
19497
+ return void 0;
19498
+ }
19499
+ }
19500
+ async function safeLane(label, run) {
19501
+ try {
19502
+ return await run();
19503
+ } catch (err) {
19504
+ getLogger().warn(
19505
+ `[memory/recall] ${label} lane failed; continuing without it: ${err instanceof Error ? err.message : String(err)}`
19506
+ );
19507
+ return [];
19508
+ }
19124
19509
  }
19125
19510
  async function buildTemporalLaneRanking(query, vaultCtx, now) {
19126
19511
  const window2 = parseQueryTimeWindow(query, now);
@@ -19235,6 +19620,11 @@ export {
19235
19620
  getMemoriesNeedingTopicExtractionOp,
19236
19621
  stampTopicsExtractedAtOp,
19237
19622
  deleteAllVaultMemoriesForUserOp,
19623
+ assertVaultScopeForSweep,
19624
+ getDecayCandidatesRawOp,
19625
+ archiveVaultMemoryOp,
19626
+ restoreVaultMemoryOp,
19627
+ hardDeleteDecayedOp,
19238
19628
  updateVaultMemoryEmbeddingOp,
19239
19629
  DEFAULT_CHUNK_SIZE,
19240
19630
  DEFAULT_CHUNK_OVERLAP,
@@ -19253,6 +19643,13 @@ export {
19253
19643
  DEFAULT_VAULT_CACHE_SIZE,
19254
19644
  LRUCache,
19255
19645
  createVaultEmbeddingCache,
19646
+ MAX_HOPS,
19647
+ ENTITY_FANOUT,
19648
+ NODE_BUDGET,
19649
+ VAULT_SIZE_HOP_CAP,
19650
+ capHopsForDensity,
19651
+ traverseGraphLane,
19652
+ createLlmNeighborRefiner,
19256
19653
  recall,
19257
19654
  preEmbedVaultMemories,
19258
19655
  eagerEmbedContent,