@anuma/sdk 7.0.3 → 7.0.4-next.20261015012621

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.
@@ -3731,8 +3731,9 @@ async function updateConversationPinnedOp(ctx, id, pinned) {
3731
3731
  }
3732
3732
  return false;
3733
3733
  }
3734
- async function getMessagesOp(ctx, convId) {
3735
- const results = await ctx.messagesCollection.query(import_watermelondb2.Q.where("conversation_id", convId), import_watermelondb2.Q.sortBy("message_id", import_watermelondb2.Q.asc)).unsafeFetchRaw();
3734
+ async function getMessagesOp(ctx, convId, opts) {
3735
+ const fetched = await ctx.messagesCollection.query(import_watermelondb2.Q.where("conversation_id", convId), import_watermelondb2.Q.sortBy("message_id", import_watermelondb2.Q.asc)).unsafeFetchRaw();
3736
+ const results = opts?.rawFilter ? fetched.filter(opts.rawFilter) : fetched;
3736
3737
  return Promise.all(
3737
3738
  results.map(
3738
3739
  (raw) => messageRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
@@ -5495,6 +5496,24 @@ var getApiV1Models = (options) => (options?.client ?? client).get({ url: "/api/v
5495
5496
  // src/lib/memoryEngine/generate.ts
5496
5497
  init_clientConfig();
5497
5498
  init_taskType();
5499
+
5500
+ // src/lib/memoryEngine/embeddingCacheState.ts
5501
+ var EMBEDDING_CACHE_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/embedding-cache/v1");
5502
+ var cacheGlobal = globalThis;
5503
+ var fallbackState = {
5504
+ scopedViews: /* @__PURE__ */ new WeakMap(),
5505
+ inFlightByCache: /* @__PURE__ */ new WeakMap(),
5506
+ cachedEmbeddingModels: /* @__PURE__ */ new WeakMap()
5507
+ };
5508
+ function getEmbeddingCacheState() {
5509
+ try {
5510
+ return cacheGlobal[EMBEDDING_CACHE_STATE_KEY] ?? (cacheGlobal[EMBEDDING_CACHE_STATE_KEY] = fallbackState);
5511
+ } catch {
5512
+ return fallbackState;
5513
+ }
5514
+ }
5515
+
5516
+ // src/lib/memoryEngine/generate.ts
5498
5517
  var EMBED_MAX_ATTEMPTS = 4;
5499
5518
  var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
5500
5519
  var DEFAULT_EMBEDDING_TOKEN_TIMEOUT_MS = 1e4;
@@ -5585,15 +5604,18 @@ async function generateEmbedding(text14, options) {
5585
5604
  if (total === void 0) return embedOne(text14, options, void 0);
5586
5605
  return withDeadline((signal) => embedOne(text14, options, signal), total, "embedding");
5587
5606
  }
5588
- var inFlightByCache = /* @__PURE__ */ new WeakMap();
5607
+ var { inFlightByCache, cachedEmbeddingModels } = getEmbeddingCacheState();
5589
5608
  async function embedOne(text14, options, outer) {
5590
5609
  const { cache } = options;
5591
5610
  if (!cache) return Array.from(await requestEmbedding(text14, options, outer));
5611
+ const model = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
5592
5612
  const cached = cache.get(text14);
5593
- if (cached) return Array.from(cached);
5613
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
5614
+ if (cached && (cachedModel === void 0 || cachedModel === model)) return Array.from(cached);
5594
5615
  const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
5595
5616
  inFlightByCache.set(cache, inFlight);
5596
- const pending = inFlight.get(text14);
5617
+ const requestKey = JSON.stringify([model, text14]);
5618
+ const pending = inFlight.get(requestKey);
5597
5619
  if (pending) {
5598
5620
  try {
5599
5621
  return Array.from(await pending);
@@ -5602,13 +5624,14 @@ async function embedOne(text14, options, outer) {
5602
5624
  }
5603
5625
  }
5604
5626
  const request = requestEmbedding(text14, options, outer);
5605
- inFlight.set(text14, request);
5627
+ inFlight.set(requestKey, request);
5606
5628
  try {
5607
5629
  const embedding2 = await request;
5630
+ cachedEmbeddingModels.set(embedding2, model);
5608
5631
  cache.set(text14, embedding2);
5609
5632
  return Array.from(embedding2);
5610
5633
  } finally {
5611
- if (inFlight.get(text14) === request) inFlight.delete(text14);
5634
+ if (inFlight.get(requestKey) === request) inFlight.delete(requestKey);
5612
5635
  }
5613
5636
  }
5614
5637
  async function requestEmbedding(text14, options, outer) {
@@ -5691,13 +5714,15 @@ async function embedMany(texts, options, outer) {
5691
5714
  const { baseUrl = BASE_URL, model, batchSize, cache } = options;
5692
5715
  const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
5693
5716
  const chunkSize = batchSize ?? DEFAULT_EMBEDDING_BATCH_SIZE;
5717
+ const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
5694
5718
  const results = new Array(texts.length).fill(null);
5695
5719
  const uncachedIndices = [];
5696
5720
  const uncachedTexts = [];
5697
5721
  for (let i = 0; i < texts.length; i++) {
5698
5722
  if (cache) {
5699
5723
  const cached = cache.get(texts[i]);
5700
- if (cached) {
5724
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
5725
+ if (cached && (cachedModel === void 0 || cachedModel === embeddingModel)) {
5701
5726
  results[i] = Array.from(cached);
5702
5727
  continue;
5703
5728
  }
@@ -5713,7 +5738,6 @@ async function embedMany(texts, options, outer) {
5713
5738
  ...taskTypeHeader(options.taskType)
5714
5739
  };
5715
5740
  if (outer?.aborted) throw outer.reason;
5716
- const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
5717
5741
  let newEmbeddings;
5718
5742
  if (uncachedTexts.length <= chunkSize) {
5719
5743
  newEmbeddings = await generateEmbeddingsBatch(
@@ -5761,6 +5785,7 @@ async function embedMany(texts, options, outer) {
5761
5785
  const f32Embedding = Float32Array.from(newEmbeddings[i]);
5762
5786
  results[uncachedIndices[i]] = Array.from(f32Embedding);
5763
5787
  if (cache) {
5788
+ cachedEmbeddingModels.set(f32Embedding, embeddingModel);
5764
5789
  cache.set(uncachedTexts[i], f32Embedding);
5765
5790
  }
5766
5791
  }
@@ -5885,6 +5910,37 @@ async function chunkAndEmbedMessage(ctx, messageId, options) {
5885
5910
  }));
5886
5911
  return updateMessageChunksOp(ctx, messageId, messageChunks, embeddingModel);
5887
5912
  }
5913
+ var MIN_SEALED_EMBEDDING_HEX = 256;
5914
+ function rawHasEmbedding(value) {
5915
+ if (Array.isArray(value)) return value.length > 0;
5916
+ if (typeof value !== "string" || value.length === 0) return false;
5917
+ if (isEncrypted(value)) {
5918
+ return value.length - value.indexOf(":", 4) - 1 >= MIN_SEALED_EMBEDDING_HEX;
5919
+ }
5920
+ try {
5921
+ const parsed = JSON.parse(value);
5922
+ return Array.isArray(parsed) && parsed.length > 0;
5923
+ } catch {
5924
+ return false;
5925
+ }
5926
+ }
5927
+ function sealedVersion(value) {
5928
+ if (typeof value !== "string" || !isEncrypted(value)) return null;
5929
+ return value.startsWith("enc:v2:") ? "v2" : "v3";
5930
+ }
5931
+ function mayNeedChunkEmbedding(raw, embeddingModel, filter) {
5932
+ const role = raw.role;
5933
+ if (role === "system") return false;
5934
+ if (filter?.roles && !filter.roles.includes(role ?? "")) return false;
5935
+ if (isNonEmbeddableOrigin(raw.origin, filter?.reembedDiscarded)) {
5936
+ return false;
5937
+ }
5938
+ const model = raw.embedding_model;
5939
+ if (typeof model === "string" && model !== embeddingModel) return true;
5940
+ if (rawHasEmbedding(raw.chunks)) return false;
5941
+ if (rawHasEmbedding(raw.vector) && !filter?.rechunkExisting) return false;
5942
+ return true;
5943
+ }
5888
5944
  async function chunkAndEmbedAllMessages(ctx, options, filter) {
5889
5945
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
5890
5946
  const { chunkSize = DEFAULT_CHUNK_SIZE } = options;
@@ -5897,11 +5953,31 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
5897
5953
  let considered = 0;
5898
5954
  let sealedRowsSeen = 0;
5899
5955
  let rowsSeen = 0;
5956
+ const keptSealed = /* @__PURE__ */ new Map();
5957
+ const candidatesStillSealed = /* @__PURE__ */ new Map();
5958
+ const droppedSealed = /* @__PURE__ */ new Map();
5959
+ const rawFilter = (raw) => {
5960
+ rowsSeen++;
5961
+ const keep = mayNeedChunkEmbedding(raw, embeddingModel, filter);
5962
+ const version = sealedVersion(raw.content);
5963
+ if (!version) return keep;
5964
+ if (keep) {
5965
+ keptSealed.set(version, (keptSealed.get(version) ?? 0) + 1);
5966
+ } else {
5967
+ const dropped = droppedSealed.get(version);
5968
+ if (dropped) dropped.count++;
5969
+ else droppedSealed.set(version, { count: 1, probeId: raw.id });
5970
+ }
5971
+ return keep;
5972
+ };
5900
5973
  for (const conv of targetConversations) {
5901
- const messages = await getMessagesOp(ctx, conv.conversationId);
5974
+ const messages = await getMessagesOp(ctx, conv.conversationId, { rawFilter });
5902
5975
  for (const message of messages) {
5903
- rowsSeen++;
5904
- if (isEncrypted(message.content)) sealedRowsSeen++;
5976
+ const stillSealed = sealedVersion(message.content);
5977
+ if (stillSealed) {
5978
+ sealedRowsSeen++;
5979
+ candidatesStillSealed.set(stillSealed, (candidatesStillSealed.get(stillSealed) ?? 0) + 1);
5980
+ }
5905
5981
  const isStale = message.embeddingModel !== void 0 && message.embeddingModel !== null && message.embeddingModel !== embeddingModel;
5906
5982
  if (message.chunks && message.chunks.length > 0 && !isStale) continue;
5907
5983
  const hasVector = message.vector && message.vector.length > 0;
@@ -5914,6 +5990,7 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
5914
5990
  stillEncrypted++;
5915
5991
  continue;
5916
5992
  }
5993
+ if (isToolResultsRow(message)) continue;
5917
5994
  if (message.content.length < minLength) continue;
5918
5995
  if (shouldChunkMessage(message.content, chunkSize)) {
5919
5996
  longMessages.push({
@@ -5925,6 +6002,12 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
5925
6002
  }
5926
6003
  }
5927
6004
  }
6005
+ for (const [version, dropped] of droppedSealed) {
6006
+ const keyOpenedACandidate = (keptSealed.get(version) ?? 0) > (candidatesStillSealed.get(version) ?? 0);
6007
+ if (keyOpenedACandidate) continue;
6008
+ const probe = await getMessageOp(ctx, dropped.probeId);
6009
+ if (!probe || isEncrypted(probe.content)) sealedRowsSeen += dropped.count;
6010
+ }
5928
6011
  if (stillEncrypted > 0 || sealedRowsSeen > 0) {
5929
6012
  getLogger().error(
5930
6013
  "memoryEngine: messages still encrypted (key unavailable?) \u2014 excluded from embedding",
@@ -11144,7 +11227,7 @@ var MaskScopedEmbeddingCache = class extends Map {
11144
11227
  return this;
11145
11228
  }
11146
11229
  };
11147
- var scopedViews = /* @__PURE__ */ new WeakMap();
11230
+ var { scopedViews } = getEmbeddingCacheState();
11148
11231
  function maskScopedEmbeddingCache(cache, masked) {
11149
11232
  const views = scopedViews.get(cache) ?? {};
11150
11233
  scopedViews.set(cache, views);
@@ -12639,6 +12722,11 @@ init_logger();
12639
12722
 
12640
12723
  // src/lib/db/entities/operations.ts
12641
12724
  var import_watermelondb19 = require("@nozbe/watermelondb");
12725
+ async function assertCanWrite(ctx) {
12726
+ if (ctx.canWrite && !await ctx.canWrite()) {
12727
+ throw new Error("Memory source is no longer eligible");
12728
+ }
12729
+ }
12642
12730
  function entityToStored(e) {
12643
12731
  return {
12644
12732
  uniqueId: e.id,
@@ -12779,6 +12867,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
12779
12867
  let skipped = false;
12780
12868
  let entities = [];
12781
12869
  await ctx.database.write(async () => {
12870
+ await assertCanWrite(ctx);
12782
12871
  const {
12783
12872
  entities: byName,
12784
12873
  operations: entityOps,
@@ -12854,6 +12943,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
12854
12943
  let skipped = false;
12855
12944
  let entities = [];
12856
12945
  await ctx.database.write(async () => {
12946
+ await assertCanWrite(ctx);
12857
12947
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
12858
12948
  if (options.guarded && autoLinkBlocked(vaultRow)) {
12859
12949
  skipped = true;
@@ -12914,6 +13004,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
12914
13004
  const links = await ctx.memoryEntityCollection.query(import_watermelondb19.Q.where("memory_id", import_watermelondb19.Q.oneOf(Array.from(memoryIds)))).fetch();
12915
13005
  if (links.length === 0) return;
12916
13006
  await ctx.database.write(async () => {
13007
+ await assertCanWrite(ctx);
12917
13008
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
12918
13009
  });
12919
13010
  }
@@ -12922,6 +13013,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
12922
13013
  const links = await ctx.memoryEntityCollection.query(import_watermelondb19.Q.where("user_id", userId)).fetch();
12923
13014
  if (links.length === 0) return;
12924
13015
  await ctx.database.write(async () => {
13016
+ await assertCanWrite(ctx);
12925
13017
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
12926
13018
  });
12927
13019
  }
@@ -13086,6 +13178,11 @@ function visibilityConditions(requested) {
13086
13178
  function isKinded(record2) {
13087
13179
  return record2.kind !== null && record2.kind !== void 0;
13088
13180
  }
13181
+ async function assertCanWrite2(ctx) {
13182
+ if (ctx.canWrite && !await ctx.canWrite()) {
13183
+ throw new Error("Memory source is no longer eligible");
13184
+ }
13185
+ }
13089
13186
  function assertValidKindLevel(kind, level) {
13090
13187
  const hasKind = kind !== null && kind !== void 0;
13091
13188
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -13266,8 +13363,7 @@ async function createVaultMemoryOp(ctx, opts) {
13266
13363
  ctx.embeddedWalletSigner
13267
13364
  ) : opts.content;
13268
13365
  const created = await ctx.database.write(async () => {
13269
- if (ctx.canWrite && !await ctx.canWrite())
13270
- throw new Error("Memory source is no longer eligible");
13366
+ await assertCanWrite2(ctx);
13271
13367
  return ctx.vaultMemoryCollection.create(
13272
13368
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
13273
13369
  );
@@ -13286,8 +13382,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
13286
13382
  ) : opts.content;
13287
13383
  let createdRecord = null;
13288
13384
  await ctx.database.write(async () => {
13289
- if (ctx.canWrite && !await ctx.canWrite())
13290
- throw new Error("Memory source is no longer eligible");
13385
+ await assertCanWrite2(ctx);
13291
13386
  let target;
13292
13387
  try {
13293
13388
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -13379,8 +13474,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
13379
13474
  })
13380
13475
  );
13381
13476
  const created = await ctx.database.write(async () => {
13382
- if (ctx.canWrite && !await ctx.canWrite())
13383
- throw new Error("Memory source is no longer eligible");
13477
+ await assertCanWrite2(ctx);
13384
13478
  const prepared = optionsArray.map(
13385
13479
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
13386
13480
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -13685,8 +13779,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
13685
13779
  const record2 = probe;
13686
13780
  const originalUpdatedAt = record2.updatedAt.getTime();
13687
13781
  await ctx.database.write(async () => {
13688
- if (ctx.canWrite && !await ctx.canWrite())
13689
- throw new Error("Memory source is no longer eligible");
13782
+ await assertCanWrite2(ctx);
13690
13783
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
13691
13784
  stale = true;
13692
13785
  return;
@@ -13797,6 +13890,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
13797
13890
  let stale = false;
13798
13891
  const originalUpdatedAt = record2.updatedAt.getTime();
13799
13892
  await ctx.database.write(async (writer) => {
13893
+ await assertCanWrite2(ctx);
13800
13894
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13801
13895
  stale = true;
13802
13896
  return;
@@ -13838,6 +13932,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
13838
13932
  const originalUpdatedAt = record2.updatedAt.getTime();
13839
13933
  let cleared = false;
13840
13934
  await ctx.database.write(async () => {
13935
+ await assertCanWrite2(ctx);
13841
13936
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
13842
13937
  cleared = true;
13843
13938
  await record2.update((r) => {
@@ -13861,6 +13956,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
13861
13956
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
13862
13957
  let stale = false;
13863
13958
  await ctx.database.write(async () => {
13959
+ await assertCanWrite2(ctx);
13864
13960
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13865
13961
  stale = true;
13866
13962
  return;
@@ -13889,6 +13985,7 @@ async function deleteVaultMemoryOp(ctx, id) {
13889
13985
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
13890
13986
  let stale = false;
13891
13987
  await ctx.database.write(async () => {
13988
+ await assertCanWrite2(ctx);
13892
13989
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13893
13990
  stale = true;
13894
13991
  return;
@@ -13926,8 +14023,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
13926
14023
  }
13927
14024
  let stale = false;
13928
14025
  await ctx.database.write(async () => {
13929
- if (ctx.canWrite && !await ctx.canWrite())
13930
- throw new Error("Memory source is no longer eligible");
14026
+ await assertCanWrite2(ctx);
13931
14027
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
13932
14028
  stale = true;
13933
14029
  return;
@@ -14089,6 +14185,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
14089
14185
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14090
14186
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14091
14187
  await ctx.database.write(async () => {
14188
+ await assertCanWrite2(ctx);
14092
14189
  const records = [];
14093
14190
  for (const id of chunkIds) {
14094
14191
  try {
@@ -14122,6 +14219,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
14122
14219
  for (const id of Array.from(new Set(memoryIds))) {
14123
14220
  try {
14124
14221
  await ctx.database.write(async (writer) => {
14222
+ await assertCanWrite2(ctx);
14125
14223
  let record2;
14126
14224
  try {
14127
14225
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -14152,6 +14250,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
14152
14250
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14153
14251
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14154
14252
  await ctx.database.write(async () => {
14253
+ await assertCanWrite2(ctx);
14155
14254
  const writes = [];
14156
14255
  for (const id of chunkIds) {
14157
14256
  let record2;
@@ -14184,6 +14283,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
14184
14283
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb20.Q.where("user_id", userId), import_watermelondb20.Q.where("is_deleted", false)).fetch();
14185
14284
  if (records.length === 0) return 0;
14186
14285
  await ctx.database.write(async () => {
14286
+ await assertCanWrite2(ctx);
14187
14287
  const prepared = records.map(
14188
14288
  (record2) => record2.prepareUpdate((r) => {
14189
14289
  r._setRaw("is_deleted", true);
@@ -14238,6 +14338,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
14238
14338
  let stale = false;
14239
14339
  const archivedAtValue = opts?.now ?? Date.now();
14240
14340
  await ctx.database.write(async () => {
14341
+ await assertCanWrite2(ctx);
14241
14342
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
14242
14343
  stale = true;
14243
14344
  return;
@@ -14261,6 +14362,7 @@ async function restoreVaultMemoryOp(ctx, id) {
14261
14362
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
14262
14363
  let stale = false;
14263
14364
  await ctx.database.write(async () => {
14365
+ await assertCanWrite2(ctx);
14264
14366
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
14265
14367
  stale = true;
14266
14368
  return;
@@ -14281,6 +14383,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
14281
14383
  const now = opts.now ?? Date.now();
14282
14384
  let stale = false;
14283
14385
  await ctx.database.write(async () => {
14386
+ await assertCanWrite2(ctx);
14284
14387
  const archivedAt = record2.archivedAt;
14285
14388
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
14286
14389
  stale = true;
@@ -14320,6 +14423,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
14320
14423
  }
14321
14424
  let written = false;
14322
14425
  await ctx.database.write(async () => {
14426
+ await assertCanWrite2(ctx);
14323
14427
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
14324
14428
  const originalUpdatedAt = record2.updatedAt.getTime();
14325
14429
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -17856,6 +17960,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
17856
17960
  ).map((c) => c.candidate);
17857
17961
  }
17858
17962
  async function extractAndRetain(messages, retainCtx, options) {
17963
+ const { memoryStore, signal } = options;
17964
+ if (memoryStore === null) throw new Error("Memory store is not ready");
17965
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
17966
+ throw new Error("Remote memory extraction does not support folders");
17967
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
17968
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
17969
+ signal?.throwIfAborted();
17859
17970
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
17860
17971
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
17861
17972
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -17889,6 +18000,7 @@ async function extractAndRetain(messages, retainCtx, options) {
17889
18000
  }
17890
18001
  });
17891
18002
  const extractMs = Date.now() - tExtract;
18003
+ signal?.throwIfAborted();
17892
18004
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
17893
18005
  const log = getLogger();
17894
18006
  const screened = screenCandidatesForInjection(filtered);
@@ -17936,16 +18048,33 @@ async function extractAndRetain(messages, retainCtx, options) {
17936
18048
  const results = [];
17937
18049
  const quarantinedInfo = [];
17938
18050
  let failedWrites = 0;
18051
+ let auditRows;
17939
18052
  const tRetain = Date.now();
17940
18053
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
17941
18054
  try {
18055
+ signal?.throwIfAborted();
17942
18056
  if (isQuarantined) {
17943
- const existingId = await findQuarantinedDuplicateOp(
17944
- retainCtx.vaultCtx,
17945
- candidate.content,
17946
- candidate.sourceMessageIds,
17947
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
17948
- ).catch(() => null);
18057
+ let existingId;
18058
+ if (memoryStore) {
18059
+ auditRows ?? (auditRows = memoryStore.list({
18060
+ scopes: [options.scope ?? "private"],
18061
+ includeArchived: true,
18062
+ includeQuarantined: true,
18063
+ includeSuperseded: true
18064
+ }));
18065
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
18066
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
18067
+ existingId = (await auditRows).find(
18068
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
18069
+ )?.uniqueId ?? null;
18070
+ } else {
18071
+ existingId = await findQuarantinedDuplicateOp(
18072
+ retainCtx.vaultCtx,
18073
+ candidate.content,
18074
+ candidate.sourceMessageIds,
18075
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
18076
+ ).catch(() => null);
18077
+ }
17949
18078
  if (existingId) {
17950
18079
  quarantinedInfo.push({
17951
18080
  candidate,
@@ -17956,18 +18085,26 @@ async function extractAndRetain(messages, retainCtx, options) {
17956
18085
  continue;
17957
18086
  }
17958
18087
  }
17959
- const result = await retain(candidate.content, retainCtx, {
18088
+ const retainOptions = {
17960
18089
  source: "auto-extracted",
17961
18090
  sourceChunkIds: candidate.sourceMessageIds,
17962
18091
  respectTombstones: true,
17963
18092
  ...options.scope !== void 0 && { scope: options.scope },
17964
- ...options.folderId !== void 0 && { folderId: options.folderId },
17965
18093
  ...consolidateOptions !== void 0 && { consolidateOptions },
17966
18094
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
17967
18095
  factType: candidate.type,
17968
18096
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
18097
+ };
18098
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
18099
+ ...retainOptions,
18100
+ canWrite: retainCtx.vaultCtx.canWrite,
18101
+ signal
18102
+ }) : await retain(candidate.content, retainCtx, {
18103
+ ...retainOptions,
18104
+ ...options.folderId !== void 0 && { folderId: options.folderId }
17969
18105
  });
17970
18106
  if (isQuarantined) {
18107
+ auditRows = void 0;
17971
18108
  const info = {
17972
18109
  candidate,
17973
18110
  memoryId: result.memoryId,
@@ -17986,9 +18123,16 @@ async function extractAndRetain(messages, retainCtx, options) {
17986
18123
  }
17987
18124
  succeededCandidates.push(candidate);
17988
18125
  results.push(result);
17989
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
18126
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
17990
18127
  try {
17991
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
18128
+ signal?.throwIfAborted();
18129
+ if (memoryStore) {
18130
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
18131
+ unlessUserManaged: true,
18132
+ canWrite: retainCtx.vaultCtx.canWrite,
18133
+ signal
18134
+ });
18135
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
17992
18136
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
17993
18137
  unlessTopicsUserManaged: true
17994
18138
  });
@@ -18238,7 +18382,7 @@ function createAutoExtractor(options) {
18238
18382
  };
18239
18383
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
18240
18384
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
18241
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18385
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18242
18386
  getLogger().warn(
18243
18387
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
18244
18388
  );
@@ -18283,6 +18427,8 @@ function createAutoExtractor(options) {
18283
18427
  try {
18284
18428
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
18285
18429
  extract,
18430
+ memoryStore: options.memoryStore,
18431
+ signal: options.signal,
18286
18432
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
18287
18433
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
18288
18434
  ...options.scope !== void 0 && { scope: options.scope },
@@ -19342,44 +19488,48 @@ async function recall(query, ctx, options = {}) {
19342
19488
  embeddingOptions,
19343
19489
  ctx.vaultCache,
19344
19490
  searchOptions
19345
- )))(query, {
19346
- limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
19347
- minSimilarity: vaultMinScore,
19348
- useFusion: true,
19349
- rerank: flags.rerank,
19350
- ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
19351
- ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
19352
- ...options.rerankLoadTimeoutMs !== void 0 && {
19353
- rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
19354
- },
19355
- ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
19356
- ...options.recency && { recency: options.recency },
19357
- ...options.mmr !== void 0 && { mmr: options.mmr },
19358
- ...options.supersessionBoost !== void 0 && {
19359
- supersessionBoost: options.supersessionBoost
19360
- },
19361
- ...options.supersessionWindow !== void 0 && {
19362
- supersessionWindow: options.supersessionWindow
19363
- },
19364
- ...options.proofCountAlpha !== void 0 && {
19365
- proofCountAlpha: options.proofCountAlpha
19366
- },
19367
- ...options.bm25AdmissionDivisor !== void 0 && {
19368
- bm25AdmissionDivisor: options.bm25AdmissionDivisor
19491
+ )))(
19492
+ query,
19493
+ {
19494
+ limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
19495
+ minSimilarity: vaultMinScore,
19496
+ useFusion: true,
19497
+ rerank: flags.rerank,
19498
+ ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
19499
+ ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
19500
+ ...options.rerankLoadTimeoutMs !== void 0 && {
19501
+ rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
19502
+ },
19503
+ ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
19504
+ ...options.recency && { recency: options.recency },
19505
+ ...options.mmr !== void 0 && { mmr: options.mmr },
19506
+ ...options.supersessionBoost !== void 0 && {
19507
+ supersessionBoost: options.supersessionBoost
19508
+ },
19509
+ ...options.supersessionWindow !== void 0 && {
19510
+ supersessionWindow: options.supersessionWindow
19511
+ },
19512
+ ...options.proofCountAlpha !== void 0 && {
19513
+ proofCountAlpha: options.proofCountAlpha
19514
+ },
19515
+ ...options.bm25AdmissionDivisor !== void 0 && {
19516
+ bm25AdmissionDivisor: options.bm25AdmissionDivisor
19517
+ },
19518
+ ...options.rrfK !== void 0 && { rrfK: options.rrfK },
19519
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
19520
+ ...subQueries && { subQueries },
19521
+ ...options.scopes && { scopes: options.scopes },
19522
+ ...options.folderId !== void 0 && { folderId: options.folderId },
19523
+ ...options.factTypes?.length && { factTypes: options.factTypes },
19524
+ ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
19525
+ ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
19526
+ ...entityRanking.length > 0 && { entityRanking },
19527
+ ...temporalRanking.length > 0 && { temporalRanking },
19528
+ ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
19529
+ queryEmbedTotalTimeoutMs
19369
19530
  },
19370
- ...options.rrfK !== void 0 && { rrfK: options.rrfK },
19371
- ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
19372
- ...subQueries && { subQueries },
19373
- ...options.scopes && { scopes: options.scopes },
19374
- ...options.folderId !== void 0 && { folderId: options.folderId },
19375
- ...options.factTypes?.length && { factTypes: options.factTypes },
19376
- ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
19377
- ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
19378
- ...entityRanking.length > 0 && { entityRanking },
19379
- ...temporalRanking.length > 0 && { temporalRanking },
19380
- ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
19381
- queryEmbedTotalTimeoutMs
19382
- });
19531
+ options.embeddingCache
19532
+ );
19383
19533
  factResults.push(
19384
19534
  ...dedupeBy(
19385
19535
  results,
@@ -20384,6 +20534,7 @@ function createLocalMemoryStore(options) {
20384
20534
  database,
20385
20535
  entityCollection: database.get("entity"),
20386
20536
  memoryEntityCollection: database.get("memory_entity"),
20537
+ canWrite: options.canWrite,
20387
20538
  ...userId !== void 0 && { userId },
20388
20539
  ...options.allowUnscopedRows !== void 0 && {
20389
20540
  allowUnscopedRows: options.allowUnscopedRows
@@ -20395,10 +20546,21 @@ function createLocalMemoryStore(options) {
20395
20546
  walletAddress: options.walletAddress,
20396
20547
  signMessage: options.signMessage,
20397
20548
  embeddedWalletSigner: options.embeddedWalletSigner,
20549
+ canWrite: options.canWrite,
20398
20550
  ...userId !== void 0 && { userId },
20399
20551
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
20400
20552
  entityCtx
20401
20553
  };
20554
+ const assertWrite = async (writeOptions) => {
20555
+ writeOptions?.signal?.throwIfAborted();
20556
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
20557
+ throw new Error("Memory source is no longer eligible");
20558
+ }
20559
+ if (options.canWrite && !await options.canWrite()) {
20560
+ throw new Error("Memory source is no longer eligible");
20561
+ }
20562
+ writeOptions?.signal?.throwIfAborted();
20563
+ };
20402
20564
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
20403
20565
  const ownedBy = userId !== void 0 ? [import_watermelondb23.Q.where("user_id", userId)] : [];
20404
20566
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [import_watermelondb23.Q.or(import_watermelondb23.Q.where("user_id", userId), import_watermelondb23.Q.where("user_id", null))] : [import_watermelondb23.Q.where("user_id", userId)];
@@ -20510,9 +20672,25 @@ function createLocalMemoryStore(options) {
20510
20672
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
20511
20673
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
20512
20674
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
20513
- addTopics: (memoryId, topics) => database.write(async (writer) => {
20675
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
20676
+ await assertWrite(writeOptions);
20514
20677
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb23.Q.where("id", memoryId), import_watermelondb23.Q.where("is_deleted", false), ...ownedBy).fetchCount();
20515
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
20678
+ return owned > 0 ? writer.callWriter(
20679
+ () => linkMemoryEntitiesOp(
20680
+ {
20681
+ ...entityCtx,
20682
+ canWrite: async () => {
20683
+ await assertWrite(writeOptions);
20684
+ return true;
20685
+ }
20686
+ },
20687
+ memoryId,
20688
+ topics,
20689
+ {
20690
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
20691
+ }
20692
+ )
20693
+ ) : [];
20516
20694
  }),
20517
20695
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
20518
20696
  recall: (query, recallOptions) => recall(
@@ -20522,14 +20700,33 @@ function createLocalMemoryStore(options) {
20522
20700
  ),
20523
20701
  retain: (content, retainOptions) => retain(
20524
20702
  content,
20525
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
20703
+ {
20704
+ vaultCtx: {
20705
+ ...vaultCtx,
20706
+ canWrite: async () => {
20707
+ await assertWrite(retainOptions);
20708
+ return true;
20709
+ }
20710
+ },
20711
+ embeddingOptions: options.embeddingOptions,
20712
+ vaultCache
20713
+ },
20526
20714
  retainOptions
20527
20715
  ),
20528
20716
  factSource: {
20529
- search: (query, searchOptions) => searchVaultMemoriesWithSize(
20717
+ search: (query, searchOptions, embeddingCache) => searchVaultMemoriesWithSize(
20530
20718
  query,
20531
20719
  vaultCtx,
20532
- { ...options.embeddingOptions, taskType: "memory_recall_embed" },
20720
+ {
20721
+ ...options.embeddingOptions,
20722
+ taskType: "memory_recall_embed",
20723
+ ...embeddingCache && !options.embeddingOptions.cache && {
20724
+ cache: maskScopedEmbeddingCache(
20725
+ embeddingCache,
20726
+ Boolean(options.embeddingOptions.maskInput)
20727
+ )
20728
+ }
20729
+ },
20533
20730
  vaultCache,
20534
20731
  searchOptions
20535
20732
  ),
@@ -21161,7 +21358,14 @@ function stored(row) {
21161
21358
  function createRemoteMemoryPipeline(options) {
21162
21359
  const { persistence, embeddingOptions } = options;
21163
21360
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
21164
- const operation = (mode) => {
21361
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
21362
+ const signal = writeOptions?.signal;
21363
+ const assertWrite = async () => {
21364
+ signal?.throwIfAborted();
21365
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
21366
+ throw new Error("Memory source is no longer eligible");
21367
+ signal?.throwIfAborted();
21368
+ };
21165
21369
  const rejectUndecryptable = (failed) => {
21166
21370
  if (!failed.length) return;
21167
21371
  if (mode === "retain")
@@ -21179,7 +21383,7 @@ function createRemoteMemoryPipeline(options) {
21179
21383
  return items.map((item) => stored(item.memory));
21180
21384
  };
21181
21385
  const get = async (id) => {
21182
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
21386
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
21183
21387
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
21184
21388
  snapshots.set(id, snapshot);
21185
21389
  return stored(snapshot.memory);
@@ -21192,7 +21396,7 @@ function createRemoteMemoryPipeline(options) {
21192
21396
  const start = Date.now();
21193
21397
  const budgetMs = search.queryEmbedTotalTimeoutMs ?? 8e3;
21194
21398
  const embed = (texts) => generateEmbeddings(texts, {
21195
- ...embeddingOptions,
21399
+ ...queryOptions,
21196
21400
  taskType: mode === "retain" ? "memory_retain_embed" : "memory_recall_embed",
21197
21401
  totalTimeoutMs: budgetMs
21198
21402
  });
@@ -21212,7 +21416,11 @@ function createRemoteMemoryPipeline(options) {
21212
21416
  }
21213
21417
  };
21214
21418
  const [primary, facetEmbeddings] = await Promise.all([
21215
- search.queryEmbedding ?? embed([query]).then((vectors2) => vectors2[0]),
21419
+ search.queryEmbedding ?? (mode === "recall" && queryOptions.cache ? generateEmbedding(query, {
21420
+ ...queryOptions,
21421
+ taskType: "memory_recall_embed",
21422
+ totalTimeoutMs: budgetMs
21423
+ }) : embed([query]).then((vectors2) => vectors2[0])),
21216
21424
  embedFacets()
21217
21425
  ]);
21218
21426
  const queryEmbedding = storedVector(primary);
@@ -21225,7 +21433,7 @@ function createRemoteMemoryPipeline(options) {
21225
21433
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
21226
21434
  }
21227
21435
  const forceIds = [...forced];
21228
- const windowOptions = (i, signal) => ({
21436
+ const windowOptions = (i, signal2) => ({
21229
21437
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
21230
21438
  force_ids: i === 0 ? forceIds : [],
21231
21439
  ...search.scopes && { scopes: search.scopes },
@@ -21233,13 +21441,15 @@ function createRemoteMemoryPipeline(options) {
21233
21441
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
21234
21442
  include_archived: search.includeArchived,
21235
21443
  embedding_model: model,
21236
- ...signal && { signal }
21444
+ ...signal2 && { signal: signal2 }
21237
21445
  });
21238
21446
  const vectors = [queryEmbedding, ...facetEmbeddings];
21239
21447
  let windows;
21240
21448
  if (mode === "retain") {
21241
21449
  windows = await Promise.all(
21242
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
21450
+ vectors.map(
21451
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
21452
+ )
21243
21453
  );
21244
21454
  } else {
21245
21455
  const deadline = new AbortController();
@@ -21327,12 +21537,15 @@ function createRemoteMemoryPipeline(options) {
21327
21537
  twin_opt_in: false
21328
21538
  };
21329
21539
  };
21330
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
21540
+ const write = async (memory, snapshot) => {
21541
+ await assertWrite();
21542
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
21543
+ };
21331
21544
  const port = {
21332
21545
  prepare,
21333
21546
  get,
21334
21547
  getFresh: async (id) => {
21335
- const current = await persistence.get(id);
21548
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
21336
21549
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
21337
21550
  },
21338
21551
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -21343,7 +21556,8 @@ function createRemoteMemoryPipeline(options) {
21343
21556
  embedding_model: embeddingModel,
21344
21557
  strict_model: true,
21345
21558
  include_deleted: true,
21346
- deleted_only: true
21559
+ deleted_only: true,
21560
+ signal
21347
21561
  });
21348
21562
  rejectUndecryptable(window2.failed);
21349
21563
  return remember(window2.items);
@@ -21399,14 +21613,18 @@ function createRemoteMemoryPipeline(options) {
21399
21613
  if (!target || !newer || id === successor) return false;
21400
21614
  const old = snapshots.get(id);
21401
21615
  const next = snapshots.get(successor);
21616
+ await assertWrite();
21402
21617
  remember(
21403
- await persistence.putMany([
21404
- {
21405
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
21406
- expectedVersion: old
21407
- },
21408
- { memory: next.memory, expectedVersion: next }
21409
- ])
21618
+ await persistence.putMany(
21619
+ [
21620
+ {
21621
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
21622
+ expectedVersion: old
21623
+ },
21624
+ { memory: next.memory, expectedVersion: next }
21625
+ ],
21626
+ signal
21627
+ )
21410
21628
  );
21411
21629
  return true;
21412
21630
  },
@@ -21415,18 +21633,22 @@ function createRemoteMemoryPipeline(options) {
21415
21633
  if (!target) return { created: null, retired: false };
21416
21634
  const snapshot = snapshots.get(targetId);
21417
21635
  const successor = createRow(input);
21636
+ await assertWrite();
21418
21637
  const rows = remember(
21419
- await persistence.putMany([
21420
- { memory: successor, expectedVersion: 0 },
21421
- {
21422
- memory: {
21423
- ...snapshot.memory,
21424
- superseded_by: successor.memory_id,
21425
- superseded_at: Date.now()
21426
- },
21427
- expectedVersion: snapshot
21428
- }
21429
- ])
21638
+ await persistence.putMany(
21639
+ [
21640
+ { memory: successor, expectedVersion: 0 },
21641
+ {
21642
+ memory: {
21643
+ ...snapshot.memory,
21644
+ superseded_by: successor.memory_id,
21645
+ superseded_at: Date.now()
21646
+ },
21647
+ expectedVersion: snapshot
21648
+ }
21649
+ ],
21650
+ signal
21651
+ )
21430
21652
  );
21431
21653
  return { created: rows[0], retired: true };
21432
21654
  }
@@ -21436,15 +21658,17 @@ function createRemoteMemoryPipeline(options) {
21436
21658
  const factSource = {
21437
21659
  graphRanking: options.graphRanking,
21438
21660
  temporalRanking: options.temporalRanking,
21439
- search: async (text14, searchOptions) => {
21440
- const { prepare } = operation("recall");
21661
+ search: async (text14, searchOptions, embeddingCache) => {
21662
+ const queryOptions = {
21663
+ ...embeddingOptions,
21664
+ taskType: "memory_recall_embed",
21665
+ ...embeddingCache && !embeddingOptions.cache && {
21666
+ cache: maskScopedEmbeddingCache(embeddingCache, Boolean(embeddingOptions.maskInput))
21667
+ }
21668
+ };
21669
+ const { prepare } = operation("recall", queryOptions);
21441
21670
  const prepared = await prepare(text14, searchOptions);
21442
- const ranked = await rankPreparedVaultCandidates(
21443
- text14,
21444
- prepared,
21445
- { ...embeddingOptions, taskType: "memory_recall_embed" },
21446
- searchOptions
21447
- );
21671
+ const ranked = await rankPreparedVaultCandidates(text14, prepared, queryOptions, searchOptions);
21448
21672
  return {
21449
21673
  ...ranked,
21450
21674
  rankedOnCosine: prepared.memories.length > 0 && !prepared.embeddingsUnavailable,
@@ -21459,7 +21683,7 @@ function createRemoteMemoryPipeline(options) {
21459
21683
  factSource,
21460
21684
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
21461
21685
  retain: async (content, retainOptions) => {
21462
- const { port } = operation("retain");
21686
+ const { port } = operation("retain", embeddingOptions, retainOptions);
21463
21687
  return retainWithPersistence(
21464
21688
  content,
21465
21689
  {
@@ -22042,16 +22266,21 @@ function createRemoteMemoryStore(options) {
22042
22266
  const record2 = await persistence.get(id);
22043
22267
  return record2 && !record2.memory.is_deleted ? record2 : null;
22044
22268
  };
22045
- const mutateLocked = async (id, change, edit) => {
22046
- const record2 = await live(id);
22269
+ const mutateLocked = async (id, change, edit, writeOptions) => {
22270
+ writeOptions?.signal?.throwIfAborted();
22271
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
22047
22272
  if (!record2) return null;
22273
+ if (record2.memory.is_deleted) return null;
22274
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
22275
+ throw new Error("Memory source is no longer eligible");
22276
+ writeOptions?.signal?.throwIfAborted();
22048
22277
  const next = edit(structuredClone(record2.memory));
22049
22278
  if (!next) return null;
22050
- const saved = await persistence.put(next, record2);
22279
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
22051
22280
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
22052
22281
  return saved;
22053
22282
  };
22054
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
22283
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
22055
22284
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
22056
22285
  const mutateMany = async (ids, change, edit) => {
22057
22286
  const saved = /* @__PURE__ */ new Map();
@@ -22564,13 +22793,21 @@ function createRemoteMemoryStore(options) {
22564
22793
  }))
22565
22794
  );
22566
22795
  },
22567
- addTopics: async (memoryId, topics) => {
22796
+ addTopics: async (memoryId, topics, writeOptions) => {
22568
22797
  const added = await withKnownKinds(topicInputs(topics, "auto"));
22569
- const saved = await mutate(memoryId, "edit", (memory) => ({
22570
- ...memory,
22571
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
22572
- topics_updated_at: Date.now()
22573
- }));
22798
+ const saved = await mutate(
22799
+ memoryId,
22800
+ "edit",
22801
+ (memory) => {
22802
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
22803
+ return {
22804
+ ...memory,
22805
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
22806
+ topics_updated_at: Date.now()
22807
+ };
22808
+ },
22809
+ writeOptions
22810
+ );
22574
22811
  if (!saved) return [];
22575
22812
  const now = /* @__PURE__ */ new Date();
22576
22813
  return mergeTopics([], added).map(
@@ -23423,6 +23660,7 @@ function createDurableAutoExtractor(options) {
23423
23660
  };
23424
23661
  const folderId = options.folderId ?? null;
23425
23662
  let disposed = false;
23663
+ let activeBatch = null;
23426
23664
  let running = false;
23427
23665
  let wakeRequested = false;
23428
23666
  let timer;
@@ -23459,14 +23697,18 @@ function createDurableAutoExtractor(options) {
23459
23697
  }, delay);
23460
23698
  }
23461
23699
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
23700
+ if (disposed) throw new Error("Extraction session was disposed");
23462
23701
  let watchdog;
23463
23702
  let cancelled = false;
23703
+ const controller = new AbortController();
23704
+ activeBatch = controller;
23464
23705
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
23465
23706
  try {
23466
23707
  return await Promise.race([
23467
23708
  new Promise((resolve, reject) => {
23468
23709
  const worker = createAutoExtractor({
23469
23710
  ...options,
23711
+ signal: controller.signal,
23470
23712
  retainCtx: {
23471
23713
  ...options.retainCtx,
23472
23714
  vaultCtx: {
@@ -23495,6 +23737,8 @@ function createDurableAutoExtractor(options) {
23495
23737
  ]);
23496
23738
  } finally {
23497
23739
  cancelled = true;
23740
+ controller.abort();
23741
+ if (activeBatch === controller) activeBatch = null;
23498
23742
  if (watchdog !== void 0) clearTimeout(watchdog);
23499
23743
  }
23500
23744
  }
@@ -23506,6 +23750,7 @@ function createDurableAutoExtractor(options) {
23506
23750
  for (const message of loaded)
23507
23751
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
23508
23752
  await database.write(async () => {
23753
+ if (disposed) return;
23509
23754
  const current = idsOf(job);
23510
23755
  const acknowledged = ids.filter((id) => current.includes(id));
23511
23756
  if (!acknowledged.length) return;
@@ -23678,6 +23923,7 @@ function createDurableAutoExtractor(options) {
23678
23923
  jobScope,
23679
23924
  jobFolderId
23680
23925
  );
23926
+ if (disposed) throw new Error("Extraction session was disposed");
23681
23927
  options.onTurnComplete?.(result);
23682
23928
  return result;
23683
23929
  };
@@ -23714,6 +23960,7 @@ function createDurableAutoExtractor(options) {
23714
23960
  );
23715
23961
  scheduleAfterSuccess = true;
23716
23962
  } catch (error) {
23963
+ if (disposed) break;
23717
23964
  attempts.set(job.id, attempted + 1);
23718
23965
  retry = true;
23719
23966
  reportError(error, conversationId);
@@ -23736,6 +23983,14 @@ function createDurableAutoExtractor(options) {
23736
23983
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
23737
23984
  }
23738
23985
  }
23986
+ function dispose() {
23987
+ disposed = true;
23988
+ activeBatch?.abort(options.signal?.reason);
23989
+ if (timer !== void 0) clearTimeout(timer);
23990
+ options.signal?.removeEventListener("abort", dispose);
23991
+ }
23992
+ if (options.signal?.aborted) dispose();
23993
+ else options.signal?.addEventListener("abort", dispose, { once: true });
23739
23994
  schedule(0);
23740
23995
  return {
23741
23996
  processTurn(messages, conversationId) {
@@ -23815,10 +24070,7 @@ function createDurableAutoExtractor(options) {
23815
24070
  return true;
23816
24071
  },
23817
24072
  isProcessing: () => running,
23818
- dispose() {
23819
- disposed = true;
23820
- if (timer !== void 0) clearTimeout(timer);
23821
- }
24073
+ dispose
23822
24074
  };
23823
24075
  }
23824
24076
 
@@ -27090,6 +27342,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
27090
27342
  var NO_CONVERSATION_KEY = "__no_conversation__";
27091
27343
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
27092
27344
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
27345
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
27093
27346
  var conversationRedactors = /* @__PURE__ */ new Map();
27094
27347
  var clientToolFilterCache = /* @__PURE__ */ new Map();
27095
27348
  function getConversationRedactor(conversationId, nerDetector) {
@@ -27234,7 +27487,7 @@ function useChatStorage(options) {
27234
27487
  [resolvedPiiRedaction]
27235
27488
  );
27236
27489
  const maskEmbeddingInput = isPiiRedactor(resolvedPiiRedaction) ? maskForEmbedding : void 0;
27237
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
27490
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
27238
27491
  const messagesCollection = (0, import_react5.useMemo)(() => database.get("history"), [database]);
27239
27492
  const conversationsCollection = (0, import_react5.useMemo)(
27240
27493
  () => database.get("conversations"),
@@ -27483,6 +27736,7 @@ function useChatStorage(options) {
27483
27736
  }, [vaultEmbeddingCache]);
27484
27737
  const retainVaultMemory = (0, import_react5.useCallback)(
27485
27738
  async (input) => {
27739
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27486
27740
  if (memoryStore) {
27487
27741
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
27488
27742
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -27528,6 +27782,7 @@ function useChatStorage(options) {
27528
27782
  );
27529
27783
  const createMemoryVaultTool2 = (0, import_react5.useCallback)(
27530
27784
  (options2) => {
27785
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27531
27786
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
27532
27787
  return createMemoryVaultTool(
27533
27788
  memoryStore ?? vaultCtx,
@@ -27538,6 +27793,7 @@ function useChatStorage(options) {
27538
27793
  );
27539
27794
  const createRecallTool2 = (0, import_react5.useCallback)(
27540
27795
  (toolOptions, callbacks) => {
27796
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27541
27797
  if (!getToken && !memoryStore) {
27542
27798
  throw new Error("getToken is required for recall tool");
27543
27799
  }
@@ -27583,7 +27839,7 @@ function useChatStorage(options) {
27583
27839
  );
27584
27840
  const recallFn = (0, import_react5.useCallback)(
27585
27841
  async (query, options2) => {
27586
- if (!getToken && !memoryStore) {
27842
+ if (memoryStore === null || !getToken && !memoryStore) {
27587
27843
  return {
27588
27844
  memories: [],
27589
27845
  usedBudget: options2?.budget ?? "low",
@@ -27632,6 +27888,7 @@ function useChatStorage(options) {
27632
27888
  );
27633
27889
  const getVaultMemories = (0, import_react5.useCallback)(
27634
27890
  (options2) => {
27891
+ if (memoryStore === null) return Promise.resolve([]);
27635
27892
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
27636
27893
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
27637
27894
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -27642,7 +27899,8 @@ function useChatStorage(options) {
27642
27899
  [vaultCtx, memoryStore]
27643
27900
  );
27644
27901
  const deleteVaultMemory = (0, import_react5.useCallback)(
27645
- (id) => {
27902
+ async (id) => {
27903
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27646
27904
  return memoryStore ? memoryStore.delete(id) : deleteVaultMemoryOp(vaultCtx, id);
27647
27905
  },
27648
27906
  [vaultCtx, memoryStore]