@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.
@@ -2681,8 +2681,9 @@ async function getConversationsByProjectOp(ctx, projectId) {
2681
2681
  )
2682
2682
  );
2683
2683
  }
2684
- async function getMessagesOp(ctx, convId) {
2685
- const results = await ctx.messagesCollection.query(Q.where("conversation_id", convId), Q.sortBy("message_id", Q.asc)).unsafeFetchRaw();
2684
+ async function getMessagesOp(ctx, convId, opts) {
2685
+ const fetched = await ctx.messagesCollection.query(Q.where("conversation_id", convId), Q.sortBy("message_id", Q.asc)).unsafeFetchRaw();
2686
+ const results = opts?.rawFilter ? fetched.filter(opts.rawFilter) : fetched;
2686
2687
  return Promise.all(
2687
2688
  results.map(
2688
2689
  (raw) => messageRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
@@ -4536,6 +4537,22 @@ var postApiV1WalletsBinding = (options) => (options.client ?? client).post({
4536
4537
  var postApiV1WalletsBindingNonce = (options) => (options?.client ?? client).post({ url: "/api/v1/wallets/binding/nonce", ...options });
4537
4538
  var deleteApiV1WalletsBindingByAddress = (options) => (options.client ?? client).delete({ url: "/api/v1/wallets/binding/{address}", ...options });
4538
4539
 
4540
+ // src/lib/memoryEngine/embeddingCacheState.ts
4541
+ var EMBEDDING_CACHE_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/embedding-cache/v1");
4542
+ var cacheGlobal = globalThis;
4543
+ var fallbackState = {
4544
+ scopedViews: /* @__PURE__ */ new WeakMap(),
4545
+ inFlightByCache: /* @__PURE__ */ new WeakMap(),
4546
+ cachedEmbeddingModels: /* @__PURE__ */ new WeakMap()
4547
+ };
4548
+ function getEmbeddingCacheState() {
4549
+ try {
4550
+ return cacheGlobal[EMBEDDING_CACHE_STATE_KEY] ?? (cacheGlobal[EMBEDDING_CACHE_STATE_KEY] = fallbackState);
4551
+ } catch {
4552
+ return fallbackState;
4553
+ }
4554
+ }
4555
+
4539
4556
  // src/lib/memoryEngine/generate.ts
4540
4557
  var EMBED_MAX_ATTEMPTS = 4;
4541
4558
  var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
@@ -4627,15 +4644,18 @@ async function generateEmbedding(text14, options) {
4627
4644
  if (total === void 0) return embedOne(text14, options, void 0);
4628
4645
  return withDeadline((signal) => embedOne(text14, options, signal), total, "embedding");
4629
4646
  }
4630
- var inFlightByCache = /* @__PURE__ */ new WeakMap();
4647
+ var { inFlightByCache, cachedEmbeddingModels } = getEmbeddingCacheState();
4631
4648
  async function embedOne(text14, options, outer) {
4632
4649
  const { cache } = options;
4633
4650
  if (!cache) return Array.from(await requestEmbedding(text14, options, outer));
4651
+ const model = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
4634
4652
  const cached = cache.get(text14);
4635
- if (cached) return Array.from(cached);
4653
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
4654
+ if (cached && (cachedModel === void 0 || cachedModel === model)) return Array.from(cached);
4636
4655
  const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
4637
4656
  inFlightByCache.set(cache, inFlight);
4638
- const pending = inFlight.get(text14);
4657
+ const requestKey = JSON.stringify([model, text14]);
4658
+ const pending = inFlight.get(requestKey);
4639
4659
  if (pending) {
4640
4660
  try {
4641
4661
  return Array.from(await pending);
@@ -4644,13 +4664,14 @@ async function embedOne(text14, options, outer) {
4644
4664
  }
4645
4665
  }
4646
4666
  const request = requestEmbedding(text14, options, outer);
4647
- inFlight.set(text14, request);
4667
+ inFlight.set(requestKey, request);
4648
4668
  try {
4649
4669
  const embedding2 = await request;
4670
+ cachedEmbeddingModels.set(embedding2, model);
4650
4671
  cache.set(text14, embedding2);
4651
4672
  return Array.from(embedding2);
4652
4673
  } finally {
4653
- if (inFlight.get(text14) === request) inFlight.delete(text14);
4674
+ if (inFlight.get(requestKey) === request) inFlight.delete(requestKey);
4654
4675
  }
4655
4676
  }
4656
4677
  async function requestEmbedding(text14, options, outer) {
@@ -4733,13 +4754,15 @@ async function embedMany(texts, options, outer) {
4733
4754
  const { baseUrl = BASE_URL, model, batchSize, cache } = options;
4734
4755
  const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
4735
4756
  const chunkSize = batchSize ?? DEFAULT_EMBEDDING_BATCH_SIZE;
4757
+ const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
4736
4758
  const results = new Array(texts.length).fill(null);
4737
4759
  const uncachedIndices = [];
4738
4760
  const uncachedTexts = [];
4739
4761
  for (let i = 0; i < texts.length; i++) {
4740
4762
  if (cache) {
4741
4763
  const cached = cache.get(texts[i]);
4742
- if (cached) {
4764
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
4765
+ if (cached && (cachedModel === void 0 || cachedModel === embeddingModel)) {
4743
4766
  results[i] = Array.from(cached);
4744
4767
  continue;
4745
4768
  }
@@ -4755,7 +4778,6 @@ async function embedMany(texts, options, outer) {
4755
4778
  ...taskTypeHeader(options.taskType)
4756
4779
  };
4757
4780
  if (outer?.aborted) throw outer.reason;
4758
- const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
4759
4781
  let newEmbeddings;
4760
4782
  if (uncachedTexts.length <= chunkSize) {
4761
4783
  newEmbeddings = await generateEmbeddingsBatch(
@@ -4803,6 +4825,7 @@ async function embedMany(texts, options, outer) {
4803
4825
  const f32Embedding = Float32Array.from(newEmbeddings[i]);
4804
4826
  results[uncachedIndices[i]] = Array.from(f32Embedding);
4805
4827
  if (cache) {
4828
+ cachedEmbeddingModels.set(f32Embedding, embeddingModel);
4806
4829
  cache.set(uncachedTexts[i], f32Embedding);
4807
4830
  }
4808
4831
  }
@@ -4927,6 +4950,37 @@ async function chunkAndEmbedMessage(ctx, messageId, options) {
4927
4950
  }));
4928
4951
  return updateMessageChunksOp(ctx, messageId, messageChunks, embeddingModel);
4929
4952
  }
4953
+ var MIN_SEALED_EMBEDDING_HEX = 256;
4954
+ function rawHasEmbedding(value) {
4955
+ if (Array.isArray(value)) return value.length > 0;
4956
+ if (typeof value !== "string" || value.length === 0) return false;
4957
+ if (isEncrypted(value)) {
4958
+ return value.length - value.indexOf(":", 4) - 1 >= MIN_SEALED_EMBEDDING_HEX;
4959
+ }
4960
+ try {
4961
+ const parsed = JSON.parse(value);
4962
+ return Array.isArray(parsed) && parsed.length > 0;
4963
+ } catch {
4964
+ return false;
4965
+ }
4966
+ }
4967
+ function sealedVersion(value) {
4968
+ if (typeof value !== "string" || !isEncrypted(value)) return null;
4969
+ return value.startsWith("enc:v2:") ? "v2" : "v3";
4970
+ }
4971
+ function mayNeedChunkEmbedding(raw, embeddingModel, filter) {
4972
+ const role = raw.role;
4973
+ if (role === "system") return false;
4974
+ if (filter?.roles && !filter.roles.includes(role ?? "")) return false;
4975
+ if (isNonEmbeddableOrigin(raw.origin, filter?.reembedDiscarded)) {
4976
+ return false;
4977
+ }
4978
+ const model = raw.embedding_model;
4979
+ if (typeof model === "string" && model !== embeddingModel) return true;
4980
+ if (rawHasEmbedding(raw.chunks)) return false;
4981
+ if (rawHasEmbedding(raw.vector) && !filter?.rechunkExisting) return false;
4982
+ return true;
4983
+ }
4930
4984
  async function chunkAndEmbedAllMessages(ctx, options, filter) {
4931
4985
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
4932
4986
  const { chunkSize = DEFAULT_CHUNK_SIZE } = options;
@@ -4939,11 +4993,31 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4939
4993
  let considered = 0;
4940
4994
  let sealedRowsSeen = 0;
4941
4995
  let rowsSeen = 0;
4996
+ const keptSealed = /* @__PURE__ */ new Map();
4997
+ const candidatesStillSealed = /* @__PURE__ */ new Map();
4998
+ const droppedSealed = /* @__PURE__ */ new Map();
4999
+ const rawFilter = (raw) => {
5000
+ rowsSeen++;
5001
+ const keep = mayNeedChunkEmbedding(raw, embeddingModel, filter);
5002
+ const version = sealedVersion(raw.content);
5003
+ if (!version) return keep;
5004
+ if (keep) {
5005
+ keptSealed.set(version, (keptSealed.get(version) ?? 0) + 1);
5006
+ } else {
5007
+ const dropped = droppedSealed.get(version);
5008
+ if (dropped) dropped.count++;
5009
+ else droppedSealed.set(version, { count: 1, probeId: raw.id });
5010
+ }
5011
+ return keep;
5012
+ };
4942
5013
  for (const conv of targetConversations) {
4943
- const messages = await getMessagesOp(ctx, conv.conversationId);
5014
+ const messages = await getMessagesOp(ctx, conv.conversationId, { rawFilter });
4944
5015
  for (const message of messages) {
4945
- rowsSeen++;
4946
- if (isEncrypted(message.content)) sealedRowsSeen++;
5016
+ const stillSealed = sealedVersion(message.content);
5017
+ if (stillSealed) {
5018
+ sealedRowsSeen++;
5019
+ candidatesStillSealed.set(stillSealed, (candidatesStillSealed.get(stillSealed) ?? 0) + 1);
5020
+ }
4947
5021
  const isStale = message.embeddingModel !== void 0 && message.embeddingModel !== null && message.embeddingModel !== embeddingModel;
4948
5022
  if (message.chunks && message.chunks.length > 0 && !isStale) continue;
4949
5023
  const hasVector = message.vector && message.vector.length > 0;
@@ -4956,6 +5030,7 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4956
5030
  stillEncrypted++;
4957
5031
  continue;
4958
5032
  }
5033
+ if (isToolResultsRow(message)) continue;
4959
5034
  if (message.content.length < minLength) continue;
4960
5035
  if (shouldChunkMessage(message.content, chunkSize)) {
4961
5036
  longMessages.push({
@@ -4967,6 +5042,12 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4967
5042
  }
4968
5043
  }
4969
5044
  }
5045
+ for (const [version, dropped] of droppedSealed) {
5046
+ const keyOpenedACandidate = (keptSealed.get(version) ?? 0) > (candidatesStillSealed.get(version) ?? 0);
5047
+ if (keyOpenedACandidate) continue;
5048
+ const probe = await getMessageOp(ctx, dropped.probeId);
5049
+ if (!probe || isEncrypted(probe.content)) sealedRowsSeen += dropped.count;
5050
+ }
4970
5051
  if (stillEncrypted > 0 || sealedRowsSeen > 0) {
4971
5052
  getLogger().error(
4972
5053
  "memoryEngine: messages still encrypted (key unavailable?) \u2014 excluded from embedding",
@@ -10414,6 +10495,11 @@ function parseTopics(value) {
10414
10495
  }
10415
10496
 
10416
10497
  // src/lib/db/entities/operations.ts
10498
+ async function assertCanWrite(ctx) {
10499
+ if (ctx.canWrite && !await ctx.canWrite()) {
10500
+ throw new Error("Memory source is no longer eligible");
10501
+ }
10502
+ }
10417
10503
  function entityToStored(e) {
10418
10504
  return {
10419
10505
  uniqueId: e.id,
@@ -10554,6 +10640,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
10554
10640
  let skipped = false;
10555
10641
  let entities = [];
10556
10642
  await ctx.database.write(async () => {
10643
+ await assertCanWrite(ctx);
10557
10644
  const {
10558
10645
  entities: byName,
10559
10646
  operations: entityOps,
@@ -10629,6 +10716,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
10629
10716
  let skipped = false;
10630
10717
  let entities = [];
10631
10718
  await ctx.database.write(async () => {
10719
+ await assertCanWrite(ctx);
10632
10720
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
10633
10721
  if (options.guarded && autoLinkBlocked(vaultRow)) {
10634
10722
  skipped = true;
@@ -10689,6 +10777,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
10689
10777
  const links = await ctx.memoryEntityCollection.query(Q5.where("memory_id", Q5.oneOf(Array.from(memoryIds)))).fetch();
10690
10778
  if (links.length === 0) return;
10691
10779
  await ctx.database.write(async () => {
10780
+ await assertCanWrite(ctx);
10692
10781
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
10693
10782
  });
10694
10783
  }
@@ -10697,6 +10786,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
10697
10786
  const links = await ctx.memoryEntityCollection.query(Q5.where("user_id", userId)).fetch();
10698
10787
  if (links.length === 0) return;
10699
10788
  await ctx.database.write(async () => {
10789
+ await assertCanWrite(ctx);
10700
10790
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
10701
10791
  });
10702
10792
  }
@@ -10716,6 +10806,7 @@ async function backfillMemoryEntityUserIdsOp(ctx, vaultMemoryCollection) {
10716
10806
  }
10717
10807
  if (toUpdate.length === 0) return 0;
10718
10808
  await ctx.database.write(async () => {
10809
+ await assertCanWrite(ctx);
10719
10810
  await ctx.database.batch(
10720
10811
  ...toUpdate.map(({ link, userId }) => link.prepareUpdate((r) => r._setRaw("user_id", userId)))
10721
10812
  );
@@ -13109,6 +13200,11 @@ function visibilityConditions(requested) {
13109
13200
  function isKinded(record2) {
13110
13201
  return record2.kind !== null && record2.kind !== void 0;
13111
13202
  }
13203
+ async function assertCanWrite2(ctx) {
13204
+ if (ctx.canWrite && !await ctx.canWrite()) {
13205
+ throw new Error("Memory source is no longer eligible");
13206
+ }
13207
+ }
13112
13208
  function assertValidKindLevel(kind, level) {
13113
13209
  const hasKind = kind !== null && kind !== void 0;
13114
13210
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -13289,8 +13385,7 @@ async function createVaultMemoryOp(ctx, opts) {
13289
13385
  ctx.embeddedWalletSigner
13290
13386
  ) : opts.content;
13291
13387
  const created = await ctx.database.write(async () => {
13292
- if (ctx.canWrite && !await ctx.canWrite())
13293
- throw new Error("Memory source is no longer eligible");
13388
+ await assertCanWrite2(ctx);
13294
13389
  return ctx.vaultMemoryCollection.create(
13295
13390
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
13296
13391
  );
@@ -13309,8 +13404,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
13309
13404
  ) : opts.content;
13310
13405
  let createdRecord = null;
13311
13406
  await ctx.database.write(async () => {
13312
- if (ctx.canWrite && !await ctx.canWrite())
13313
- throw new Error("Memory source is no longer eligible");
13407
+ await assertCanWrite2(ctx);
13314
13408
  let target;
13315
13409
  try {
13316
13410
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -13402,8 +13496,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
13402
13496
  })
13403
13497
  );
13404
13498
  const created = await ctx.database.write(async () => {
13405
- if (ctx.canWrite && !await ctx.canWrite())
13406
- throw new Error("Memory source is no longer eligible");
13499
+ await assertCanWrite2(ctx);
13407
13500
  const prepared = optionsArray.map(
13408
13501
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
13409
13502
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -13708,8 +13801,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
13708
13801
  const record2 = probe;
13709
13802
  const originalUpdatedAt = record2.updatedAt.getTime();
13710
13803
  await ctx.database.write(async () => {
13711
- if (ctx.canWrite && !await ctx.canWrite())
13712
- throw new Error("Memory source is no longer eligible");
13804
+ await assertCanWrite2(ctx);
13713
13805
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
13714
13806
  stale = true;
13715
13807
  return;
@@ -13820,6 +13912,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
13820
13912
  let stale = false;
13821
13913
  const originalUpdatedAt = record2.updatedAt.getTime();
13822
13914
  await ctx.database.write(async (writer) => {
13915
+ await assertCanWrite2(ctx);
13823
13916
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13824
13917
  stale = true;
13825
13918
  return;
@@ -13861,6 +13954,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
13861
13954
  const originalUpdatedAt = record2.updatedAt.getTime();
13862
13955
  let cleared = false;
13863
13956
  await ctx.database.write(async () => {
13957
+ await assertCanWrite2(ctx);
13864
13958
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
13865
13959
  cleared = true;
13866
13960
  await record2.update((r) => {
@@ -13884,6 +13978,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
13884
13978
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
13885
13979
  let stale = false;
13886
13980
  await ctx.database.write(async () => {
13981
+ await assertCanWrite2(ctx);
13887
13982
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13888
13983
  stale = true;
13889
13984
  return;
@@ -13912,6 +14007,7 @@ async function deleteVaultMemoryOp(ctx, id) {
13912
14007
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
13913
14008
  let stale = false;
13914
14009
  await ctx.database.write(async () => {
14010
+ await assertCanWrite2(ctx);
13915
14011
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13916
14012
  stale = true;
13917
14013
  return;
@@ -13949,8 +14045,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
13949
14045
  }
13950
14046
  let stale = false;
13951
14047
  await ctx.database.write(async () => {
13952
- if (ctx.canWrite && !await ctx.canWrite())
13953
- throw new Error("Memory source is no longer eligible");
14048
+ await assertCanWrite2(ctx);
13954
14049
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
13955
14050
  stale = true;
13956
14051
  return;
@@ -14112,6 +14207,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
14112
14207
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14113
14208
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14114
14209
  await ctx.database.write(async () => {
14210
+ await assertCanWrite2(ctx);
14115
14211
  const records = [];
14116
14212
  for (const id of chunkIds) {
14117
14213
  try {
@@ -14145,6 +14241,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
14145
14241
  for (const id of Array.from(new Set(memoryIds))) {
14146
14242
  try {
14147
14243
  await ctx.database.write(async (writer) => {
14244
+ await assertCanWrite2(ctx);
14148
14245
  let record2;
14149
14246
  try {
14150
14247
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -14175,6 +14272,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
14175
14272
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14176
14273
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14177
14274
  await ctx.database.write(async () => {
14275
+ await assertCanWrite2(ctx);
14178
14276
  const writes = [];
14179
14277
  for (const id of chunkIds) {
14180
14278
  let record2;
@@ -14207,6 +14305,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
14207
14305
  const records = await ctx.vaultMemoryCollection.query(Q8.where("user_id", userId), Q8.where("is_deleted", false)).fetch();
14208
14306
  if (records.length === 0) return 0;
14209
14307
  await ctx.database.write(async () => {
14308
+ await assertCanWrite2(ctx);
14210
14309
  const prepared = records.map(
14211
14310
  (record2) => record2.prepareUpdate((r) => {
14212
14311
  r._setRaw("is_deleted", true);
@@ -14261,6 +14360,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
14261
14360
  let stale = false;
14262
14361
  const archivedAtValue = opts?.now ?? Date.now();
14263
14362
  await ctx.database.write(async () => {
14363
+ await assertCanWrite2(ctx);
14264
14364
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
14265
14365
  stale = true;
14266
14366
  return;
@@ -14284,6 +14384,7 @@ async function restoreVaultMemoryOp(ctx, id) {
14284
14384
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
14285
14385
  let stale = false;
14286
14386
  await ctx.database.write(async () => {
14387
+ await assertCanWrite2(ctx);
14287
14388
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
14288
14389
  stale = true;
14289
14390
  return;
@@ -14304,6 +14405,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
14304
14405
  const now = opts.now ?? Date.now();
14305
14406
  let stale = false;
14306
14407
  await ctx.database.write(async () => {
14408
+ await assertCanWrite2(ctx);
14307
14409
  const archivedAt = record2.archivedAt;
14308
14410
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
14309
14411
  stale = true;
@@ -14343,6 +14445,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
14343
14445
  }
14344
14446
  let written = false;
14345
14447
  await ctx.database.write(async () => {
14448
+ await assertCanWrite2(ctx);
14346
14449
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
14347
14450
  const originalUpdatedAt = record2.updatedAt.getTime();
14348
14451
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -18312,6 +18415,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
18312
18415
  ).map((c) => c.candidate);
18313
18416
  }
18314
18417
  async function extractAndRetain(messages, retainCtx, options) {
18418
+ const { memoryStore, signal } = options;
18419
+ if (memoryStore === null) throw new Error("Memory store is not ready");
18420
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
18421
+ throw new Error("Remote memory extraction does not support folders");
18422
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
18423
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
18424
+ signal?.throwIfAborted();
18315
18425
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
18316
18426
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
18317
18427
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -18345,6 +18455,7 @@ async function extractAndRetain(messages, retainCtx, options) {
18345
18455
  }
18346
18456
  });
18347
18457
  const extractMs = Date.now() - tExtract;
18458
+ signal?.throwIfAborted();
18348
18459
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
18349
18460
  const log = getLogger();
18350
18461
  const screened = screenCandidatesForInjection(filtered);
@@ -18392,16 +18503,33 @@ async function extractAndRetain(messages, retainCtx, options) {
18392
18503
  const results = [];
18393
18504
  const quarantinedInfo = [];
18394
18505
  let failedWrites = 0;
18506
+ let auditRows;
18395
18507
  const tRetain = Date.now();
18396
18508
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
18397
18509
  try {
18510
+ signal?.throwIfAborted();
18398
18511
  if (isQuarantined) {
18399
- const existingId = await findQuarantinedDuplicateOp(
18400
- retainCtx.vaultCtx,
18401
- candidate.content,
18402
- candidate.sourceMessageIds,
18403
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
18404
- ).catch(() => null);
18512
+ let existingId;
18513
+ if (memoryStore) {
18514
+ auditRows ?? (auditRows = memoryStore.list({
18515
+ scopes: [options.scope ?? "private"],
18516
+ includeArchived: true,
18517
+ includeQuarantined: true,
18518
+ includeSuperseded: true
18519
+ }));
18520
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
18521
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
18522
+ existingId = (await auditRows).find(
18523
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
18524
+ )?.uniqueId ?? null;
18525
+ } else {
18526
+ existingId = await findQuarantinedDuplicateOp(
18527
+ retainCtx.vaultCtx,
18528
+ candidate.content,
18529
+ candidate.sourceMessageIds,
18530
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
18531
+ ).catch(() => null);
18532
+ }
18405
18533
  if (existingId) {
18406
18534
  quarantinedInfo.push({
18407
18535
  candidate,
@@ -18412,18 +18540,26 @@ async function extractAndRetain(messages, retainCtx, options) {
18412
18540
  continue;
18413
18541
  }
18414
18542
  }
18415
- const result = await retain(candidate.content, retainCtx, {
18543
+ const retainOptions = {
18416
18544
  source: "auto-extracted",
18417
18545
  sourceChunkIds: candidate.sourceMessageIds,
18418
18546
  respectTombstones: true,
18419
18547
  ...options.scope !== void 0 && { scope: options.scope },
18420
- ...options.folderId !== void 0 && { folderId: options.folderId },
18421
18548
  ...consolidateOptions !== void 0 && { consolidateOptions },
18422
18549
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
18423
18550
  factType: candidate.type,
18424
18551
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
18552
+ };
18553
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
18554
+ ...retainOptions,
18555
+ canWrite: retainCtx.vaultCtx.canWrite,
18556
+ signal
18557
+ }) : await retain(candidate.content, retainCtx, {
18558
+ ...retainOptions,
18559
+ ...options.folderId !== void 0 && { folderId: options.folderId }
18425
18560
  });
18426
18561
  if (isQuarantined) {
18562
+ auditRows = void 0;
18427
18563
  const info = {
18428
18564
  candidate,
18429
18565
  memoryId: result.memoryId,
@@ -18442,9 +18578,16 @@ async function extractAndRetain(messages, retainCtx, options) {
18442
18578
  }
18443
18579
  succeededCandidates.push(candidate);
18444
18580
  results.push(result);
18445
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
18581
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
18446
18582
  try {
18447
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
18583
+ signal?.throwIfAborted();
18584
+ if (memoryStore) {
18585
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
18586
+ unlessUserManaged: true,
18587
+ canWrite: retainCtx.vaultCtx.canWrite,
18588
+ signal
18589
+ });
18590
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
18448
18591
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
18449
18592
  unlessTopicsUserManaged: true
18450
18593
  });
@@ -18692,7 +18835,7 @@ function createAutoExtractor(options) {
18692
18835
  };
18693
18836
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
18694
18837
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
18695
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18838
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18696
18839
  getLogger().warn(
18697
18840
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
18698
18841
  );
@@ -18737,6 +18880,8 @@ function createAutoExtractor(options) {
18737
18880
  try {
18738
18881
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
18739
18882
  extract,
18883
+ memoryStore: options.memoryStore,
18884
+ signal: options.signal,
18740
18885
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
18741
18886
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
18742
18887
  ...options.scope !== void 0 && { scope: options.scope },
@@ -19389,7 +19534,7 @@ var MaskScopedEmbeddingCache = class extends Map {
19389
19534
  return this;
19390
19535
  }
19391
19536
  };
19392
- var scopedViews = /* @__PURE__ */ new WeakMap();
19537
+ var { scopedViews } = getEmbeddingCacheState();
19393
19538
  function maskScopedEmbeddingCache(cache, masked) {
19394
19539
  const views = scopedViews.get(cache) ?? {};
19395
19540
  scopedViews.set(cache, views);
@@ -19811,44 +19956,48 @@ async function recall(query, ctx, options = {}) {
19811
19956
  embeddingOptions,
19812
19957
  ctx.vaultCache,
19813
19958
  searchOptions
19814
- )))(query, {
19815
- limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
19816
- minSimilarity: vaultMinScore,
19817
- useFusion: true,
19818
- rerank: flags.rerank,
19819
- ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
19820
- ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
19821
- ...options.rerankLoadTimeoutMs !== void 0 && {
19822
- rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
19823
- },
19824
- ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
19825
- ...options.recency && { recency: options.recency },
19826
- ...options.mmr !== void 0 && { mmr: options.mmr },
19827
- ...options.supersessionBoost !== void 0 && {
19828
- supersessionBoost: options.supersessionBoost
19829
- },
19830
- ...options.supersessionWindow !== void 0 && {
19831
- supersessionWindow: options.supersessionWindow
19832
- },
19833
- ...options.proofCountAlpha !== void 0 && {
19834
- proofCountAlpha: options.proofCountAlpha
19835
- },
19836
- ...options.bm25AdmissionDivisor !== void 0 && {
19837
- bm25AdmissionDivisor: options.bm25AdmissionDivisor
19959
+ )))(
19960
+ query,
19961
+ {
19962
+ limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
19963
+ minSimilarity: vaultMinScore,
19964
+ useFusion: true,
19965
+ rerank: flags.rerank,
19966
+ ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
19967
+ ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
19968
+ ...options.rerankLoadTimeoutMs !== void 0 && {
19969
+ rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
19970
+ },
19971
+ ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
19972
+ ...options.recency && { recency: options.recency },
19973
+ ...options.mmr !== void 0 && { mmr: options.mmr },
19974
+ ...options.supersessionBoost !== void 0 && {
19975
+ supersessionBoost: options.supersessionBoost
19976
+ },
19977
+ ...options.supersessionWindow !== void 0 && {
19978
+ supersessionWindow: options.supersessionWindow
19979
+ },
19980
+ ...options.proofCountAlpha !== void 0 && {
19981
+ proofCountAlpha: options.proofCountAlpha
19982
+ },
19983
+ ...options.bm25AdmissionDivisor !== void 0 && {
19984
+ bm25AdmissionDivisor: options.bm25AdmissionDivisor
19985
+ },
19986
+ ...options.rrfK !== void 0 && { rrfK: options.rrfK },
19987
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
19988
+ ...subQueries && { subQueries },
19989
+ ...options.scopes && { scopes: options.scopes },
19990
+ ...options.folderId !== void 0 && { folderId: options.folderId },
19991
+ ...options.factTypes?.length && { factTypes: options.factTypes },
19992
+ ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
19993
+ ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
19994
+ ...entityRanking.length > 0 && { entityRanking },
19995
+ ...temporalRanking.length > 0 && { temporalRanking },
19996
+ ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
19997
+ queryEmbedTotalTimeoutMs
19838
19998
  },
19839
- ...options.rrfK !== void 0 && { rrfK: options.rrfK },
19840
- ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
19841
- ...subQueries && { subQueries },
19842
- ...options.scopes && { scopes: options.scopes },
19843
- ...options.folderId !== void 0 && { folderId: options.folderId },
19844
- ...options.factTypes?.length && { factTypes: options.factTypes },
19845
- ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
19846
- ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
19847
- ...entityRanking.length > 0 && { entityRanking },
19848
- ...temporalRanking.length > 0 && { temporalRanking },
19849
- ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
19850
- queryEmbedTotalTimeoutMs
19851
- });
19999
+ options.embeddingCache
20000
+ );
19852
20001
  factResults.push(
19853
20002
  ...dedupeBy(
19854
20003
  results,
@@ -20845,6 +20994,7 @@ function createLocalMemoryStore(options) {
20845
20994
  database,
20846
20995
  entityCollection: database.get("entity"),
20847
20996
  memoryEntityCollection: database.get("memory_entity"),
20997
+ canWrite: options.canWrite,
20848
20998
  ...userId !== void 0 && { userId },
20849
20999
  ...options.allowUnscopedRows !== void 0 && {
20850
21000
  allowUnscopedRows: options.allowUnscopedRows
@@ -20856,10 +21006,21 @@ function createLocalMemoryStore(options) {
20856
21006
  walletAddress: options.walletAddress,
20857
21007
  signMessage: options.signMessage,
20858
21008
  embeddedWalletSigner: options.embeddedWalletSigner,
21009
+ canWrite: options.canWrite,
20859
21010
  ...userId !== void 0 && { userId },
20860
21011
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
20861
21012
  entityCtx
20862
21013
  };
21014
+ const assertWrite = async (writeOptions) => {
21015
+ writeOptions?.signal?.throwIfAborted();
21016
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
21017
+ throw new Error("Memory source is no longer eligible");
21018
+ }
21019
+ if (options.canWrite && !await options.canWrite()) {
21020
+ throw new Error("Memory source is no longer eligible");
21021
+ }
21022
+ writeOptions?.signal?.throwIfAborted();
21023
+ };
20863
21024
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
20864
21025
  const ownedBy = userId !== void 0 ? [Q15.where("user_id", userId)] : [];
20865
21026
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [Q15.or(Q15.where("user_id", userId), Q15.where("user_id", null))] : [Q15.where("user_id", userId)];
@@ -20971,9 +21132,25 @@ function createLocalMemoryStore(options) {
20971
21132
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
20972
21133
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
20973
21134
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
20974
- addTopics: (memoryId, topics) => database.write(async (writer) => {
21135
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
21136
+ await assertWrite(writeOptions);
20975
21137
  const owned = await vaultCtx.vaultMemoryCollection.query(Q15.where("id", memoryId), Q15.where("is_deleted", false), ...ownedBy).fetchCount();
20976
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
21138
+ return owned > 0 ? writer.callWriter(
21139
+ () => linkMemoryEntitiesOp(
21140
+ {
21141
+ ...entityCtx,
21142
+ canWrite: async () => {
21143
+ await assertWrite(writeOptions);
21144
+ return true;
21145
+ }
21146
+ },
21147
+ memoryId,
21148
+ topics,
21149
+ {
21150
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
21151
+ }
21152
+ )
21153
+ ) : [];
20977
21154
  }),
20978
21155
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
20979
21156
  recall: (query, recallOptions) => recall(
@@ -20983,14 +21160,33 @@ function createLocalMemoryStore(options) {
20983
21160
  ),
20984
21161
  retain: (content, retainOptions) => retain(
20985
21162
  content,
20986
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
21163
+ {
21164
+ vaultCtx: {
21165
+ ...vaultCtx,
21166
+ canWrite: async () => {
21167
+ await assertWrite(retainOptions);
21168
+ return true;
21169
+ }
21170
+ },
21171
+ embeddingOptions: options.embeddingOptions,
21172
+ vaultCache
21173
+ },
20987
21174
  retainOptions
20988
21175
  ),
20989
21176
  factSource: {
20990
- search: (query, searchOptions) => searchVaultMemoriesWithSize(
21177
+ search: (query, searchOptions, embeddingCache) => searchVaultMemoriesWithSize(
20991
21178
  query,
20992
21179
  vaultCtx,
20993
- { ...options.embeddingOptions, taskType: "memory_recall_embed" },
21180
+ {
21181
+ ...options.embeddingOptions,
21182
+ taskType: "memory_recall_embed",
21183
+ ...embeddingCache && !options.embeddingOptions.cache && {
21184
+ cache: maskScopedEmbeddingCache(
21185
+ embeddingCache,
21186
+ Boolean(options.embeddingOptions.maskInput)
21187
+ )
21188
+ }
21189
+ },
20994
21190
  vaultCache,
20995
21191
  searchOptions
20996
21192
  ),
@@ -21620,7 +21816,14 @@ function stored(row) {
21620
21816
  function createRemoteMemoryPipeline(options) {
21621
21817
  const { persistence, embeddingOptions } = options;
21622
21818
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
21623
- const operation = (mode) => {
21819
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
21820
+ const signal = writeOptions?.signal;
21821
+ const assertWrite = async () => {
21822
+ signal?.throwIfAborted();
21823
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
21824
+ throw new Error("Memory source is no longer eligible");
21825
+ signal?.throwIfAborted();
21826
+ };
21624
21827
  const rejectUndecryptable = (failed) => {
21625
21828
  if (!failed.length) return;
21626
21829
  if (mode === "retain")
@@ -21638,7 +21841,7 @@ function createRemoteMemoryPipeline(options) {
21638
21841
  return items.map((item) => stored(item.memory));
21639
21842
  };
21640
21843
  const get = async (id) => {
21641
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
21844
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
21642
21845
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
21643
21846
  snapshots.set(id, snapshot);
21644
21847
  return stored(snapshot.memory);
@@ -21651,7 +21854,7 @@ function createRemoteMemoryPipeline(options) {
21651
21854
  const start = Date.now();
21652
21855
  const budgetMs = search.queryEmbedTotalTimeoutMs ?? 8e3;
21653
21856
  const embed = (texts) => generateEmbeddings(texts, {
21654
- ...embeddingOptions,
21857
+ ...queryOptions,
21655
21858
  taskType: mode === "retain" ? "memory_retain_embed" : "memory_recall_embed",
21656
21859
  totalTimeoutMs: budgetMs
21657
21860
  });
@@ -21671,7 +21874,11 @@ function createRemoteMemoryPipeline(options) {
21671
21874
  }
21672
21875
  };
21673
21876
  const [primary, facetEmbeddings] = await Promise.all([
21674
- search.queryEmbedding ?? embed([query]).then((vectors2) => vectors2[0]),
21877
+ search.queryEmbedding ?? (mode === "recall" && queryOptions.cache ? generateEmbedding(query, {
21878
+ ...queryOptions,
21879
+ taskType: "memory_recall_embed",
21880
+ totalTimeoutMs: budgetMs
21881
+ }) : embed([query]).then((vectors2) => vectors2[0])),
21675
21882
  embedFacets()
21676
21883
  ]);
21677
21884
  const queryEmbedding = storedVector(primary);
@@ -21684,7 +21891,7 @@ function createRemoteMemoryPipeline(options) {
21684
21891
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
21685
21892
  }
21686
21893
  const forceIds = [...forced];
21687
- const windowOptions = (i, signal) => ({
21894
+ const windowOptions = (i, signal2) => ({
21688
21895
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
21689
21896
  force_ids: i === 0 ? forceIds : [],
21690
21897
  ...search.scopes && { scopes: search.scopes },
@@ -21692,13 +21899,15 @@ function createRemoteMemoryPipeline(options) {
21692
21899
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
21693
21900
  include_archived: search.includeArchived,
21694
21901
  embedding_model: model,
21695
- ...signal && { signal }
21902
+ ...signal2 && { signal: signal2 }
21696
21903
  });
21697
21904
  const vectors = [queryEmbedding, ...facetEmbeddings];
21698
21905
  let windows;
21699
21906
  if (mode === "retain") {
21700
21907
  windows = await Promise.all(
21701
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
21908
+ vectors.map(
21909
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
21910
+ )
21702
21911
  );
21703
21912
  } else {
21704
21913
  const deadline = new AbortController();
@@ -21786,12 +21995,15 @@ function createRemoteMemoryPipeline(options) {
21786
21995
  twin_opt_in: false
21787
21996
  };
21788
21997
  };
21789
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
21998
+ const write = async (memory, snapshot) => {
21999
+ await assertWrite();
22000
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
22001
+ };
21790
22002
  const port = {
21791
22003
  prepare,
21792
22004
  get,
21793
22005
  getFresh: async (id) => {
21794
- const current = await persistence.get(id);
22006
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
21795
22007
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
21796
22008
  },
21797
22009
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -21802,7 +22014,8 @@ function createRemoteMemoryPipeline(options) {
21802
22014
  embedding_model: embeddingModel,
21803
22015
  strict_model: true,
21804
22016
  include_deleted: true,
21805
- deleted_only: true
22017
+ deleted_only: true,
22018
+ signal
21806
22019
  });
21807
22020
  rejectUndecryptable(window2.failed);
21808
22021
  return remember(window2.items);
@@ -21858,14 +22071,18 @@ function createRemoteMemoryPipeline(options) {
21858
22071
  if (!target || !newer || id === successor) return false;
21859
22072
  const old = snapshots.get(id);
21860
22073
  const next = snapshots.get(successor);
22074
+ await assertWrite();
21861
22075
  remember(
21862
- await persistence.putMany([
21863
- {
21864
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
21865
- expectedVersion: old
21866
- },
21867
- { memory: next.memory, expectedVersion: next }
21868
- ])
22076
+ await persistence.putMany(
22077
+ [
22078
+ {
22079
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
22080
+ expectedVersion: old
22081
+ },
22082
+ { memory: next.memory, expectedVersion: next }
22083
+ ],
22084
+ signal
22085
+ )
21869
22086
  );
21870
22087
  return true;
21871
22088
  },
@@ -21874,18 +22091,22 @@ function createRemoteMemoryPipeline(options) {
21874
22091
  if (!target) return { created: null, retired: false };
21875
22092
  const snapshot = snapshots.get(targetId);
21876
22093
  const successor = createRow(input);
22094
+ await assertWrite();
21877
22095
  const rows = remember(
21878
- await persistence.putMany([
21879
- { memory: successor, expectedVersion: 0 },
21880
- {
21881
- memory: {
21882
- ...snapshot.memory,
21883
- superseded_by: successor.memory_id,
21884
- superseded_at: Date.now()
21885
- },
21886
- expectedVersion: snapshot
21887
- }
21888
- ])
22096
+ await persistence.putMany(
22097
+ [
22098
+ { memory: successor, expectedVersion: 0 },
22099
+ {
22100
+ memory: {
22101
+ ...snapshot.memory,
22102
+ superseded_by: successor.memory_id,
22103
+ superseded_at: Date.now()
22104
+ },
22105
+ expectedVersion: snapshot
22106
+ }
22107
+ ],
22108
+ signal
22109
+ )
21889
22110
  );
21890
22111
  return { created: rows[0], retired: true };
21891
22112
  }
@@ -21895,15 +22116,17 @@ function createRemoteMemoryPipeline(options) {
21895
22116
  const factSource = {
21896
22117
  graphRanking: options.graphRanking,
21897
22118
  temporalRanking: options.temporalRanking,
21898
- search: async (text14, searchOptions) => {
21899
- const { prepare } = operation("recall");
22119
+ search: async (text14, searchOptions, embeddingCache) => {
22120
+ const queryOptions = {
22121
+ ...embeddingOptions,
22122
+ taskType: "memory_recall_embed",
22123
+ ...embeddingCache && !embeddingOptions.cache && {
22124
+ cache: maskScopedEmbeddingCache(embeddingCache, Boolean(embeddingOptions.maskInput))
22125
+ }
22126
+ };
22127
+ const { prepare } = operation("recall", queryOptions);
21900
22128
  const prepared = await prepare(text14, searchOptions);
21901
- const ranked = await rankPreparedVaultCandidates(
21902
- text14,
21903
- prepared,
21904
- { ...embeddingOptions, taskType: "memory_recall_embed" },
21905
- searchOptions
21906
- );
22129
+ const ranked = await rankPreparedVaultCandidates(text14, prepared, queryOptions, searchOptions);
21907
22130
  return {
21908
22131
  ...ranked,
21909
22132
  rankedOnCosine: prepared.memories.length > 0 && !prepared.embeddingsUnavailable,
@@ -21918,7 +22141,7 @@ function createRemoteMemoryPipeline(options) {
21918
22141
  factSource,
21919
22142
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
21920
22143
  retain: async (content, retainOptions) => {
21921
- const { port } = operation("retain");
22144
+ const { port } = operation("retain", embeddingOptions, retainOptions);
21922
22145
  return retainWithPersistence(
21923
22146
  content,
21924
22147
  {
@@ -22500,16 +22723,21 @@ function createRemoteMemoryStore(options) {
22500
22723
  const record2 = await persistence.get(id);
22501
22724
  return record2 && !record2.memory.is_deleted ? record2 : null;
22502
22725
  };
22503
- const mutateLocked = async (id, change, edit) => {
22504
- const record2 = await live(id);
22726
+ const mutateLocked = async (id, change, edit, writeOptions) => {
22727
+ writeOptions?.signal?.throwIfAborted();
22728
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
22505
22729
  if (!record2) return null;
22730
+ if (record2.memory.is_deleted) return null;
22731
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
22732
+ throw new Error("Memory source is no longer eligible");
22733
+ writeOptions?.signal?.throwIfAborted();
22506
22734
  const next = edit(structuredClone(record2.memory));
22507
22735
  if (!next) return null;
22508
- const saved = await persistence.put(next, record2);
22736
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
22509
22737
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
22510
22738
  return saved;
22511
22739
  };
22512
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
22740
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
22513
22741
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
22514
22742
  const mutateMany = async (ids, change, edit) => {
22515
22743
  const saved = /* @__PURE__ */ new Map();
@@ -23022,13 +23250,21 @@ function createRemoteMemoryStore(options) {
23022
23250
  }))
23023
23251
  );
23024
23252
  },
23025
- addTopics: async (memoryId, topics) => {
23253
+ addTopics: async (memoryId, topics, writeOptions) => {
23026
23254
  const added = await withKnownKinds(topicInputs(topics, "auto"));
23027
- const saved = await mutate(memoryId, "edit", (memory) => ({
23028
- ...memory,
23029
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
23030
- topics_updated_at: Date.now()
23031
- }));
23255
+ const saved = await mutate(
23256
+ memoryId,
23257
+ "edit",
23258
+ (memory) => {
23259
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
23260
+ return {
23261
+ ...memory,
23262
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
23263
+ topics_updated_at: Date.now()
23264
+ };
23265
+ },
23266
+ writeOptions
23267
+ );
23032
23268
  if (!saved) return [];
23033
23269
  const now = /* @__PURE__ */ new Date();
23034
23270
  return mergeTopics([], added).map(
@@ -23879,6 +24115,7 @@ function createDurableAutoExtractor(options) {
23879
24115
  };
23880
24116
  const folderId = options.folderId ?? null;
23881
24117
  let disposed = false;
24118
+ let activeBatch = null;
23882
24119
  let running = false;
23883
24120
  let wakeRequested = false;
23884
24121
  let timer;
@@ -23915,14 +24152,18 @@ function createDurableAutoExtractor(options) {
23915
24152
  }, delay);
23916
24153
  }
23917
24154
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
24155
+ if (disposed) throw new Error("Extraction session was disposed");
23918
24156
  let watchdog;
23919
24157
  let cancelled = false;
24158
+ const controller = new AbortController();
24159
+ activeBatch = controller;
23920
24160
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
23921
24161
  try {
23922
24162
  return await Promise.race([
23923
24163
  new Promise((resolve, reject) => {
23924
24164
  const worker = createAutoExtractor({
23925
24165
  ...options,
24166
+ signal: controller.signal,
23926
24167
  retainCtx: {
23927
24168
  ...options.retainCtx,
23928
24169
  vaultCtx: {
@@ -23951,6 +24192,8 @@ function createDurableAutoExtractor(options) {
23951
24192
  ]);
23952
24193
  } finally {
23953
24194
  cancelled = true;
24195
+ controller.abort();
24196
+ if (activeBatch === controller) activeBatch = null;
23954
24197
  if (watchdog !== void 0) clearTimeout(watchdog);
23955
24198
  }
23956
24199
  }
@@ -23962,6 +24205,7 @@ function createDurableAutoExtractor(options) {
23962
24205
  for (const message of loaded)
23963
24206
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
23964
24207
  await database.write(async () => {
24208
+ if (disposed) return;
23965
24209
  const current = idsOf(job);
23966
24210
  const acknowledged = ids.filter((id) => current.includes(id));
23967
24211
  if (!acknowledged.length) return;
@@ -24134,6 +24378,7 @@ function createDurableAutoExtractor(options) {
24134
24378
  jobScope,
24135
24379
  jobFolderId
24136
24380
  );
24381
+ if (disposed) throw new Error("Extraction session was disposed");
24137
24382
  options.onTurnComplete?.(result);
24138
24383
  return result;
24139
24384
  };
@@ -24170,6 +24415,7 @@ function createDurableAutoExtractor(options) {
24170
24415
  );
24171
24416
  scheduleAfterSuccess = true;
24172
24417
  } catch (error) {
24418
+ if (disposed) break;
24173
24419
  attempts.set(job.id, attempted + 1);
24174
24420
  retry = true;
24175
24421
  reportError(error, conversationId);
@@ -24192,6 +24438,14 @@ function createDurableAutoExtractor(options) {
24192
24438
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
24193
24439
  }
24194
24440
  }
24441
+ function dispose() {
24442
+ disposed = true;
24443
+ activeBatch?.abort(options.signal?.reason);
24444
+ if (timer !== void 0) clearTimeout(timer);
24445
+ options.signal?.removeEventListener("abort", dispose);
24446
+ }
24447
+ if (options.signal?.aborted) dispose();
24448
+ else options.signal?.addEventListener("abort", dispose, { once: true });
24195
24449
  schedule(0);
24196
24450
  return {
24197
24451
  processTurn(messages, conversationId) {
@@ -24271,10 +24525,7 @@ function createDurableAutoExtractor(options) {
24271
24525
  return true;
24272
24526
  },
24273
24527
  isProcessing: () => running,
24274
- dispose() {
24275
- disposed = true;
24276
- if (timer !== void 0) clearTimeout(timer);
24277
- }
24528
+ dispose
24278
24529
  };
24279
24530
  }
24280
24531
 
@@ -30535,6 +30786,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
30535
30786
  var NO_CONVERSATION_KEY = "__no_conversation__";
30536
30787
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
30537
30788
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
30789
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
30538
30790
  var conversationRedactors = /* @__PURE__ */ new Map();
30539
30791
  function getConversationRedactor(conversationId, nerDetector) {
30540
30792
  const key = conversationId ?? NO_CONVERSATION_KEY;
@@ -30625,7 +30877,7 @@ function useChatStorage(options) {
30625
30877
  () => ({ getToken, baseUrl, model: embeddingModel, maskInput: maskEmbeddingInput }),
30626
30878
  [getToken, baseUrl, embeddingModel, maskEmbeddingInput]
30627
30879
  );
30628
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
30880
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
30629
30881
  const blobManagerRef = useRef4(new BlobUrlManager());
30630
30882
  useEffect4(() => {
30631
30883
  const manager = blobManagerRef.current;
@@ -30902,6 +31154,7 @@ function useChatStorage(options) {
30902
31154
  );
30903
31155
  const retainVaultMemory = useCallback5(
30904
31156
  async (input) => {
31157
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
30905
31158
  if (memoryStore) {
30906
31159
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
30907
31160
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -30933,6 +31186,7 @@ function useChatStorage(options) {
30933
31186
  );
30934
31187
  const createMemoryVaultTool2 = useCallback5(
30935
31188
  (options2) => {
31189
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
30936
31190
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
30937
31191
  const embOpts = getToken ? vaultEmbeddingOptions : void 0;
30938
31192
  return createMemoryVaultTool(
@@ -30954,6 +31208,7 @@ function useChatStorage(options) {
30954
31208
  );
30955
31209
  const getVaultMemories = useCallback5(
30956
31210
  (options2) => {
31211
+ if (memoryStore === null) return Promise.resolve([]);
30957
31212
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
30958
31213
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
30959
31214
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -30965,6 +31220,7 @@ function useChatStorage(options) {
30965
31220
  );
30966
31221
  const createVaultMemory = useCallback5(
30967
31222
  async (content, scope) => {
31223
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
30968
31224
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
30969
31225
  if (memoryStore) return memoryStore.create({ content, scope });
30970
31226
  const result = await createVaultMemoryOp(vaultCtx, { content, scope });
@@ -30986,6 +31242,7 @@ function useChatStorage(options) {
30986
31242
  );
30987
31243
  const updateVaultMemory = useCallback5(
30988
31244
  async (id, content, scope) => {
31245
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
30989
31246
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
30990
31247
  if (memoryStore) return memoryStore.update(id, { content, scope });
30991
31248
  const existing = await getVaultMemoryOp(vaultCtx, id);
@@ -31011,6 +31268,7 @@ function useChatStorage(options) {
31011
31268
  );
31012
31269
  const deleteVaultMemory = useCallback5(
31013
31270
  async (id) => {
31271
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31014
31272
  if (memoryStore) return memoryStore.delete(id);
31015
31273
  const existing = await getVaultMemoryOp(vaultCtx, id);
31016
31274
  const result = await deleteVaultMemoryOp(vaultCtx, id);
@@ -31024,7 +31282,7 @@ function useChatStorage(options) {
31024
31282
  const chunkVectorCacheRef = useRef4(createChunkVectorCache());
31025
31283
  const clientToolEmbeddingsCacheRef = useRef4(/* @__PURE__ */ new Map());
31026
31284
  useEffect4(() => {
31027
- if (!getToken || memoryStore) return;
31285
+ if (!getToken || memoryStore !== void 0) return;
31028
31286
  void (async () => {
31029
31287
  try {
31030
31288
  await preEmbedVaultMemories(vaultCtx, vaultEmbeddingOptions, vaultEmbeddingCache);
@@ -31040,6 +31298,7 @@ function useChatStorage(options) {
31040
31298
  }, [vaultEmbeddingCache]);
31041
31299
  const createMemoryVaultSearchTool2 = useCallback5(
31042
31300
  (searchOptions) => {
31301
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31043
31302
  if (!getToken && !memoryStore) {
31044
31303
  throw new Error("getToken is required for memory vault search tool");
31045
31304
  }
@@ -31055,6 +31314,7 @@ function useChatStorage(options) {
31055
31314
  );
31056
31315
  const createRecallTool2 = useCallback5(
31057
31316
  (toolOptions, callbacks) => {
31317
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31058
31318
  if (!getToken && !memoryStore) {
31059
31319
  throw new Error("getToken is required for recall tool");
31060
31320
  }
@@ -31094,6 +31354,7 @@ function useChatStorage(options) {
31094
31354
  );
31095
31355
  const searchVaultMemoriesFn = useCallback5(
31096
31356
  async (query, searchOptions) => {
31357
+ if (memoryStore === null) return [];
31097
31358
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
31098
31359
  if (memoryStore) return (await memoryStore.factSource.search(query, searchOptions)).results;
31099
31360
  if (!getToken) return [];
@@ -31109,7 +31370,7 @@ function useChatStorage(options) {
31109
31370
  );
31110
31371
  const recallFn = useCallback5(
31111
31372
  async (query, options2) => {
31112
- if (!getToken && !memoryStore) {
31373
+ if (memoryStore === null || !getToken && !memoryStore) {
31113
31374
  return {
31114
31375
  memories: [],
31115
31376
  usedBudget: options2?.budget ?? "low",