@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.
@@ -2158,8 +2158,9 @@ async function updateConversationPinnedOp(ctx, id, pinned) {
2158
2158
  }
2159
2159
  return false;
2160
2160
  }
2161
- async function getMessagesOp(ctx, convId) {
2162
- const results = await ctx.messagesCollection.query(Q.where("conversation_id", convId), Q.sortBy("message_id", Q.asc)).unsafeFetchRaw();
2161
+ async function getMessagesOp(ctx, convId, opts) {
2162
+ const fetched = await ctx.messagesCollection.query(Q.where("conversation_id", convId), Q.sortBy("message_id", Q.asc)).unsafeFetchRaw();
2163
+ const results = opts?.rawFilter ? fetched.filter(opts.rawFilter) : fetched;
2163
2164
  return Promise.all(
2164
2165
  results.map(
2165
2166
  (raw) => messageRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
@@ -3915,6 +3916,22 @@ var postApiV1Embeddings = (options) => (options.client ?? client).post({
3915
3916
  });
3916
3917
  var getApiV1Models = (options) => (options?.client ?? client).get({ url: "/api/v1/models", ...options });
3917
3918
 
3919
+ // src/lib/memoryEngine/embeddingCacheState.ts
3920
+ var EMBEDDING_CACHE_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/embedding-cache/v1");
3921
+ var cacheGlobal = globalThis;
3922
+ var fallbackState = {
3923
+ scopedViews: /* @__PURE__ */ new WeakMap(),
3924
+ inFlightByCache: /* @__PURE__ */ new WeakMap(),
3925
+ cachedEmbeddingModels: /* @__PURE__ */ new WeakMap()
3926
+ };
3927
+ function getEmbeddingCacheState() {
3928
+ try {
3929
+ return cacheGlobal[EMBEDDING_CACHE_STATE_KEY] ?? (cacheGlobal[EMBEDDING_CACHE_STATE_KEY] = fallbackState);
3930
+ } catch {
3931
+ return fallbackState;
3932
+ }
3933
+ }
3934
+
3918
3935
  // src/lib/memoryEngine/generate.ts
3919
3936
  var EMBED_MAX_ATTEMPTS = 4;
3920
3937
  var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
@@ -4006,15 +4023,18 @@ async function generateEmbedding(text14, options) {
4006
4023
  if (total === void 0) return embedOne(text14, options, void 0);
4007
4024
  return withDeadline((signal) => embedOne(text14, options, signal), total, "embedding");
4008
4025
  }
4009
- var inFlightByCache = /* @__PURE__ */ new WeakMap();
4026
+ var { inFlightByCache, cachedEmbeddingModels } = getEmbeddingCacheState();
4010
4027
  async function embedOne(text14, options, outer) {
4011
4028
  const { cache } = options;
4012
4029
  if (!cache) return Array.from(await requestEmbedding(text14, options, outer));
4030
+ const model = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
4013
4031
  const cached = cache.get(text14);
4014
- if (cached) return Array.from(cached);
4032
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
4033
+ if (cached && (cachedModel === void 0 || cachedModel === model)) return Array.from(cached);
4015
4034
  const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
4016
4035
  inFlightByCache.set(cache, inFlight);
4017
- const pending = inFlight.get(text14);
4036
+ const requestKey = JSON.stringify([model, text14]);
4037
+ const pending = inFlight.get(requestKey);
4018
4038
  if (pending) {
4019
4039
  try {
4020
4040
  return Array.from(await pending);
@@ -4023,13 +4043,14 @@ async function embedOne(text14, options, outer) {
4023
4043
  }
4024
4044
  }
4025
4045
  const request = requestEmbedding(text14, options, outer);
4026
- inFlight.set(text14, request);
4046
+ inFlight.set(requestKey, request);
4027
4047
  try {
4028
4048
  const embedding2 = await request;
4049
+ cachedEmbeddingModels.set(embedding2, model);
4029
4050
  cache.set(text14, embedding2);
4030
4051
  return Array.from(embedding2);
4031
4052
  } finally {
4032
- if (inFlight.get(text14) === request) inFlight.delete(text14);
4053
+ if (inFlight.get(requestKey) === request) inFlight.delete(requestKey);
4033
4054
  }
4034
4055
  }
4035
4056
  async function requestEmbedding(text14, options, outer) {
@@ -4112,13 +4133,15 @@ async function embedMany(texts, options, outer) {
4112
4133
  const { baseUrl = BASE_URL, model, batchSize, cache } = options;
4113
4134
  const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
4114
4135
  const chunkSize = batchSize ?? DEFAULT_EMBEDDING_BATCH_SIZE;
4136
+ const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
4115
4137
  const results = new Array(texts.length).fill(null);
4116
4138
  const uncachedIndices = [];
4117
4139
  const uncachedTexts = [];
4118
4140
  for (let i = 0; i < texts.length; i++) {
4119
4141
  if (cache) {
4120
4142
  const cached = cache.get(texts[i]);
4121
- if (cached) {
4143
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
4144
+ if (cached && (cachedModel === void 0 || cachedModel === embeddingModel)) {
4122
4145
  results[i] = Array.from(cached);
4123
4146
  continue;
4124
4147
  }
@@ -4134,7 +4157,6 @@ async function embedMany(texts, options, outer) {
4134
4157
  ...taskTypeHeader(options.taskType)
4135
4158
  };
4136
4159
  if (outer?.aborted) throw outer.reason;
4137
- const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
4138
4160
  let newEmbeddings;
4139
4161
  if (uncachedTexts.length <= chunkSize) {
4140
4162
  newEmbeddings = await generateEmbeddingsBatch(
@@ -4182,6 +4204,7 @@ async function embedMany(texts, options, outer) {
4182
4204
  const f32Embedding = Float32Array.from(newEmbeddings[i]);
4183
4205
  results[uncachedIndices[i]] = Array.from(f32Embedding);
4184
4206
  if (cache) {
4207
+ cachedEmbeddingModels.set(f32Embedding, embeddingModel);
4185
4208
  cache.set(uncachedTexts[i], f32Embedding);
4186
4209
  }
4187
4210
  }
@@ -4306,6 +4329,37 @@ async function chunkAndEmbedMessage(ctx, messageId, options) {
4306
4329
  }));
4307
4330
  return updateMessageChunksOp(ctx, messageId, messageChunks, embeddingModel);
4308
4331
  }
4332
+ var MIN_SEALED_EMBEDDING_HEX = 256;
4333
+ function rawHasEmbedding(value) {
4334
+ if (Array.isArray(value)) return value.length > 0;
4335
+ if (typeof value !== "string" || value.length === 0) return false;
4336
+ if (isEncrypted(value)) {
4337
+ return value.length - value.indexOf(":", 4) - 1 >= MIN_SEALED_EMBEDDING_HEX;
4338
+ }
4339
+ try {
4340
+ const parsed = JSON.parse(value);
4341
+ return Array.isArray(parsed) && parsed.length > 0;
4342
+ } catch {
4343
+ return false;
4344
+ }
4345
+ }
4346
+ function sealedVersion(value) {
4347
+ if (typeof value !== "string" || !isEncrypted(value)) return null;
4348
+ return value.startsWith("enc:v2:") ? "v2" : "v3";
4349
+ }
4350
+ function mayNeedChunkEmbedding(raw, embeddingModel, filter) {
4351
+ const role = raw.role;
4352
+ if (role === "system") return false;
4353
+ if (filter?.roles && !filter.roles.includes(role ?? "")) return false;
4354
+ if (isNonEmbeddableOrigin(raw.origin, filter?.reembedDiscarded)) {
4355
+ return false;
4356
+ }
4357
+ const model = raw.embedding_model;
4358
+ if (typeof model === "string" && model !== embeddingModel) return true;
4359
+ if (rawHasEmbedding(raw.chunks)) return false;
4360
+ if (rawHasEmbedding(raw.vector) && !filter?.rechunkExisting) return false;
4361
+ return true;
4362
+ }
4309
4363
  async function chunkAndEmbedAllMessages(ctx, options, filter) {
4310
4364
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
4311
4365
  const { chunkSize = DEFAULT_CHUNK_SIZE } = options;
@@ -4318,11 +4372,31 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4318
4372
  let considered = 0;
4319
4373
  let sealedRowsSeen = 0;
4320
4374
  let rowsSeen = 0;
4375
+ const keptSealed = /* @__PURE__ */ new Map();
4376
+ const candidatesStillSealed = /* @__PURE__ */ new Map();
4377
+ const droppedSealed = /* @__PURE__ */ new Map();
4378
+ const rawFilter = (raw) => {
4379
+ rowsSeen++;
4380
+ const keep = mayNeedChunkEmbedding(raw, embeddingModel, filter);
4381
+ const version = sealedVersion(raw.content);
4382
+ if (!version) return keep;
4383
+ if (keep) {
4384
+ keptSealed.set(version, (keptSealed.get(version) ?? 0) + 1);
4385
+ } else {
4386
+ const dropped = droppedSealed.get(version);
4387
+ if (dropped) dropped.count++;
4388
+ else droppedSealed.set(version, { count: 1, probeId: raw.id });
4389
+ }
4390
+ return keep;
4391
+ };
4321
4392
  for (const conv of targetConversations) {
4322
- const messages = await getMessagesOp(ctx, conv.conversationId);
4393
+ const messages = await getMessagesOp(ctx, conv.conversationId, { rawFilter });
4323
4394
  for (const message of messages) {
4324
- rowsSeen++;
4325
- if (isEncrypted(message.content)) sealedRowsSeen++;
4395
+ const stillSealed = sealedVersion(message.content);
4396
+ if (stillSealed) {
4397
+ sealedRowsSeen++;
4398
+ candidatesStillSealed.set(stillSealed, (candidatesStillSealed.get(stillSealed) ?? 0) + 1);
4399
+ }
4326
4400
  const isStale = message.embeddingModel !== void 0 && message.embeddingModel !== null && message.embeddingModel !== embeddingModel;
4327
4401
  if (message.chunks && message.chunks.length > 0 && !isStale) continue;
4328
4402
  const hasVector = message.vector && message.vector.length > 0;
@@ -4335,6 +4409,7 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4335
4409
  stillEncrypted++;
4336
4410
  continue;
4337
4411
  }
4412
+ if (isToolResultsRow(message)) continue;
4338
4413
  if (message.content.length < minLength) continue;
4339
4414
  if (shouldChunkMessage(message.content, chunkSize)) {
4340
4415
  longMessages.push({
@@ -4346,6 +4421,12 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4346
4421
  }
4347
4422
  }
4348
4423
  }
4424
+ for (const [version, dropped] of droppedSealed) {
4425
+ const keyOpenedACandidate = (keptSealed.get(version) ?? 0) > (candidatesStillSealed.get(version) ?? 0);
4426
+ if (keyOpenedACandidate) continue;
4427
+ const probe = await getMessageOp(ctx, dropped.probeId);
4428
+ if (!probe || isEncrypted(probe.content)) sealedRowsSeen += dropped.count;
4429
+ }
4349
4430
  if (stillEncrypted > 0 || sealedRowsSeen > 0) {
4350
4431
  getLogger().error(
4351
4432
  "memoryEngine: messages still encrypted (key unavailable?) \u2014 excluded from embedding",
@@ -9551,7 +9632,7 @@ var MaskScopedEmbeddingCache = class extends Map {
9551
9632
  return this;
9552
9633
  }
9553
9634
  };
9554
- var scopedViews = /* @__PURE__ */ new WeakMap();
9635
+ var { scopedViews } = getEmbeddingCacheState();
9555
9636
  function maskScopedEmbeddingCache(cache, masked) {
9556
9637
  const views = scopedViews.get(cache) ?? {};
9557
9638
  scopedViews.set(cache, views);
@@ -11050,6 +11131,11 @@ import { Q as Q5 } from "@nozbe/watermelondb";
11050
11131
 
11051
11132
  // src/lib/db/entities/operations.ts
11052
11133
  import { Q as Q4 } from "@nozbe/watermelondb";
11134
+ async function assertCanWrite(ctx) {
11135
+ if (ctx.canWrite && !await ctx.canWrite()) {
11136
+ throw new Error("Memory source is no longer eligible");
11137
+ }
11138
+ }
11053
11139
  function entityToStored(e) {
11054
11140
  return {
11055
11141
  uniqueId: e.id,
@@ -11190,6 +11276,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
11190
11276
  let skipped = false;
11191
11277
  let entities = [];
11192
11278
  await ctx.database.write(async () => {
11279
+ await assertCanWrite(ctx);
11193
11280
  const {
11194
11281
  entities: byName,
11195
11282
  operations: entityOps,
@@ -11265,6 +11352,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
11265
11352
  let skipped = false;
11266
11353
  let entities = [];
11267
11354
  await ctx.database.write(async () => {
11355
+ await assertCanWrite(ctx);
11268
11356
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
11269
11357
  if (options.guarded && autoLinkBlocked(vaultRow)) {
11270
11358
  skipped = true;
@@ -11325,6 +11413,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
11325
11413
  const links = await ctx.memoryEntityCollection.query(Q4.where("memory_id", Q4.oneOf(Array.from(memoryIds)))).fetch();
11326
11414
  if (links.length === 0) return;
11327
11415
  await ctx.database.write(async () => {
11416
+ await assertCanWrite(ctx);
11328
11417
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
11329
11418
  });
11330
11419
  }
@@ -11333,6 +11422,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
11333
11422
  const links = await ctx.memoryEntityCollection.query(Q4.where("user_id", userId)).fetch();
11334
11423
  if (links.length === 0) return;
11335
11424
  await ctx.database.write(async () => {
11425
+ await assertCanWrite(ctx);
11336
11426
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
11337
11427
  });
11338
11428
  }
@@ -11496,6 +11586,11 @@ function visibilityConditions(requested) {
11496
11586
  function isKinded(record2) {
11497
11587
  return record2.kind !== null && record2.kind !== void 0;
11498
11588
  }
11589
+ async function assertCanWrite2(ctx) {
11590
+ if (ctx.canWrite && !await ctx.canWrite()) {
11591
+ throw new Error("Memory source is no longer eligible");
11592
+ }
11593
+ }
11499
11594
  function assertValidKindLevel(kind, level) {
11500
11595
  const hasKind = kind !== null && kind !== void 0;
11501
11596
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -11676,8 +11771,7 @@ async function createVaultMemoryOp(ctx, opts) {
11676
11771
  ctx.embeddedWalletSigner
11677
11772
  ) : opts.content;
11678
11773
  const created = await ctx.database.write(async () => {
11679
- if (ctx.canWrite && !await ctx.canWrite())
11680
- throw new Error("Memory source is no longer eligible");
11774
+ await assertCanWrite2(ctx);
11681
11775
  return ctx.vaultMemoryCollection.create(
11682
11776
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
11683
11777
  );
@@ -11696,8 +11790,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
11696
11790
  ) : opts.content;
11697
11791
  let createdRecord = null;
11698
11792
  await ctx.database.write(async () => {
11699
- if (ctx.canWrite && !await ctx.canWrite())
11700
- throw new Error("Memory source is no longer eligible");
11793
+ await assertCanWrite2(ctx);
11701
11794
  let target;
11702
11795
  try {
11703
11796
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -11789,8 +11882,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
11789
11882
  })
11790
11883
  );
11791
11884
  const created = await ctx.database.write(async () => {
11792
- if (ctx.canWrite && !await ctx.canWrite())
11793
- throw new Error("Memory source is no longer eligible");
11885
+ await assertCanWrite2(ctx);
11794
11886
  const prepared = optionsArray.map(
11795
11887
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
11796
11888
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -12095,8 +12187,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
12095
12187
  const record2 = probe;
12096
12188
  const originalUpdatedAt = record2.updatedAt.getTime();
12097
12189
  await ctx.database.write(async () => {
12098
- if (ctx.canWrite && !await ctx.canWrite())
12099
- throw new Error("Memory source is no longer eligible");
12190
+ await assertCanWrite2(ctx);
12100
12191
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
12101
12192
  stale = true;
12102
12193
  return;
@@ -12207,6 +12298,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
12207
12298
  let stale = false;
12208
12299
  const originalUpdatedAt = record2.updatedAt.getTime();
12209
12300
  await ctx.database.write(async (writer) => {
12301
+ await assertCanWrite2(ctx);
12210
12302
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12211
12303
  stale = true;
12212
12304
  return;
@@ -12248,6 +12340,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
12248
12340
  const originalUpdatedAt = record2.updatedAt.getTime();
12249
12341
  let cleared = false;
12250
12342
  await ctx.database.write(async () => {
12343
+ await assertCanWrite2(ctx);
12251
12344
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
12252
12345
  cleared = true;
12253
12346
  await record2.update((r) => {
@@ -12271,6 +12364,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
12271
12364
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
12272
12365
  let stale = false;
12273
12366
  await ctx.database.write(async () => {
12367
+ await assertCanWrite2(ctx);
12274
12368
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12275
12369
  stale = true;
12276
12370
  return;
@@ -12299,6 +12393,7 @@ async function deleteVaultMemoryOp(ctx, id) {
12299
12393
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
12300
12394
  let stale = false;
12301
12395
  await ctx.database.write(async () => {
12396
+ await assertCanWrite2(ctx);
12302
12397
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12303
12398
  stale = true;
12304
12399
  return;
@@ -12336,8 +12431,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
12336
12431
  }
12337
12432
  let stale = false;
12338
12433
  await ctx.database.write(async () => {
12339
- if (ctx.canWrite && !await ctx.canWrite())
12340
- throw new Error("Memory source is no longer eligible");
12434
+ await assertCanWrite2(ctx);
12341
12435
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
12342
12436
  stale = true;
12343
12437
  return;
@@ -12499,6 +12593,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
12499
12593
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
12500
12594
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
12501
12595
  await ctx.database.write(async () => {
12596
+ await assertCanWrite2(ctx);
12502
12597
  const records = [];
12503
12598
  for (const id of chunkIds) {
12504
12599
  try {
@@ -12532,6 +12627,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
12532
12627
  for (const id of Array.from(new Set(memoryIds))) {
12533
12628
  try {
12534
12629
  await ctx.database.write(async (writer) => {
12630
+ await assertCanWrite2(ctx);
12535
12631
  let record2;
12536
12632
  try {
12537
12633
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -12562,6 +12658,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
12562
12658
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
12563
12659
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
12564
12660
  await ctx.database.write(async () => {
12661
+ await assertCanWrite2(ctx);
12565
12662
  const writes = [];
12566
12663
  for (const id of chunkIds) {
12567
12664
  let record2;
@@ -12594,6 +12691,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
12594
12691
  const records = await ctx.vaultMemoryCollection.query(Q5.where("user_id", userId), Q5.where("is_deleted", false)).fetch();
12595
12692
  if (records.length === 0) return 0;
12596
12693
  await ctx.database.write(async () => {
12694
+ await assertCanWrite2(ctx);
12597
12695
  const prepared = records.map(
12598
12696
  (record2) => record2.prepareUpdate((r) => {
12599
12697
  r._setRaw("is_deleted", true);
@@ -12648,6 +12746,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
12648
12746
  let stale = false;
12649
12747
  const archivedAtValue = opts?.now ?? Date.now();
12650
12748
  await ctx.database.write(async () => {
12749
+ await assertCanWrite2(ctx);
12651
12750
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
12652
12751
  stale = true;
12653
12752
  return;
@@ -12671,6 +12770,7 @@ async function restoreVaultMemoryOp(ctx, id) {
12671
12770
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
12672
12771
  let stale = false;
12673
12772
  await ctx.database.write(async () => {
12773
+ await assertCanWrite2(ctx);
12674
12774
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12675
12775
  stale = true;
12676
12776
  return;
@@ -12691,6 +12791,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
12691
12791
  const now = opts.now ?? Date.now();
12692
12792
  let stale = false;
12693
12793
  await ctx.database.write(async () => {
12794
+ await assertCanWrite2(ctx);
12694
12795
  const archivedAt = record2.archivedAt;
12695
12796
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
12696
12797
  stale = true;
@@ -12730,6 +12831,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
12730
12831
  }
12731
12832
  let written = false;
12732
12833
  await ctx.database.write(async () => {
12834
+ await assertCanWrite2(ctx);
12733
12835
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
12734
12836
  const originalUpdatedAt = record2.updatedAt.getTime();
12735
12837
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -16242,6 +16344,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
16242
16344
  ).map((c) => c.candidate);
16243
16345
  }
16244
16346
  async function extractAndRetain(messages, retainCtx, options) {
16347
+ const { memoryStore, signal } = options;
16348
+ if (memoryStore === null) throw new Error("Memory store is not ready");
16349
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
16350
+ throw new Error("Remote memory extraction does not support folders");
16351
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
16352
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
16353
+ signal?.throwIfAborted();
16245
16354
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
16246
16355
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
16247
16356
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -16275,6 +16384,7 @@ async function extractAndRetain(messages, retainCtx, options) {
16275
16384
  }
16276
16385
  });
16277
16386
  const extractMs = Date.now() - tExtract;
16387
+ signal?.throwIfAborted();
16278
16388
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
16279
16389
  const log = getLogger();
16280
16390
  const screened = screenCandidatesForInjection(filtered);
@@ -16322,16 +16432,33 @@ async function extractAndRetain(messages, retainCtx, options) {
16322
16432
  const results = [];
16323
16433
  const quarantinedInfo = [];
16324
16434
  let failedWrites = 0;
16435
+ let auditRows;
16325
16436
  const tRetain = Date.now();
16326
16437
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
16327
16438
  try {
16439
+ signal?.throwIfAborted();
16328
16440
  if (isQuarantined) {
16329
- const existingId = await findQuarantinedDuplicateOp(
16330
- retainCtx.vaultCtx,
16331
- candidate.content,
16332
- candidate.sourceMessageIds,
16333
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
16334
- ).catch(() => null);
16441
+ let existingId;
16442
+ if (memoryStore) {
16443
+ auditRows ?? (auditRows = memoryStore.list({
16444
+ scopes: [options.scope ?? "private"],
16445
+ includeArchived: true,
16446
+ includeQuarantined: true,
16447
+ includeSuperseded: true
16448
+ }));
16449
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
16450
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
16451
+ existingId = (await auditRows).find(
16452
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
16453
+ )?.uniqueId ?? null;
16454
+ } else {
16455
+ existingId = await findQuarantinedDuplicateOp(
16456
+ retainCtx.vaultCtx,
16457
+ candidate.content,
16458
+ candidate.sourceMessageIds,
16459
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
16460
+ ).catch(() => null);
16461
+ }
16335
16462
  if (existingId) {
16336
16463
  quarantinedInfo.push({
16337
16464
  candidate,
@@ -16342,18 +16469,26 @@ async function extractAndRetain(messages, retainCtx, options) {
16342
16469
  continue;
16343
16470
  }
16344
16471
  }
16345
- const result = await retain(candidate.content, retainCtx, {
16472
+ const retainOptions = {
16346
16473
  source: "auto-extracted",
16347
16474
  sourceChunkIds: candidate.sourceMessageIds,
16348
16475
  respectTombstones: true,
16349
16476
  ...options.scope !== void 0 && { scope: options.scope },
16350
- ...options.folderId !== void 0 && { folderId: options.folderId },
16351
16477
  ...consolidateOptions !== void 0 && { consolidateOptions },
16352
16478
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
16353
16479
  factType: candidate.type,
16354
16480
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
16481
+ };
16482
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
16483
+ ...retainOptions,
16484
+ canWrite: retainCtx.vaultCtx.canWrite,
16485
+ signal
16486
+ }) : await retain(candidate.content, retainCtx, {
16487
+ ...retainOptions,
16488
+ ...options.folderId !== void 0 && { folderId: options.folderId }
16355
16489
  });
16356
16490
  if (isQuarantined) {
16491
+ auditRows = void 0;
16357
16492
  const info = {
16358
16493
  candidate,
16359
16494
  memoryId: result.memoryId,
@@ -16372,9 +16507,16 @@ async function extractAndRetain(messages, retainCtx, options) {
16372
16507
  }
16373
16508
  succeededCandidates.push(candidate);
16374
16509
  results.push(result);
16375
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
16510
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
16376
16511
  try {
16377
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
16512
+ signal?.throwIfAborted();
16513
+ if (memoryStore) {
16514
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
16515
+ unlessUserManaged: true,
16516
+ canWrite: retainCtx.vaultCtx.canWrite,
16517
+ signal
16518
+ });
16519
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
16378
16520
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
16379
16521
  unlessTopicsUserManaged: true
16380
16522
  });
@@ -16622,7 +16764,7 @@ function createAutoExtractor(options) {
16622
16764
  };
16623
16765
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
16624
16766
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
16625
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
16767
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
16626
16768
  getLogger().warn(
16627
16769
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
16628
16770
  );
@@ -16667,6 +16809,8 @@ function createAutoExtractor(options) {
16667
16809
  try {
16668
16810
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
16669
16811
  extract,
16812
+ memoryStore: options.memoryStore,
16813
+ signal: options.signal,
16670
16814
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
16671
16815
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
16672
16816
  ...options.scope !== void 0 && { scope: options.scope },
@@ -17715,44 +17859,48 @@ async function recall(query, ctx, options = {}) {
17715
17859
  embeddingOptions,
17716
17860
  ctx.vaultCache,
17717
17861
  searchOptions
17718
- )))(query, {
17719
- limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
17720
- minSimilarity: vaultMinScore,
17721
- useFusion: true,
17722
- rerank: flags.rerank,
17723
- ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
17724
- ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
17725
- ...options.rerankLoadTimeoutMs !== void 0 && {
17726
- rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
17727
- },
17728
- ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
17729
- ...options.recency && { recency: options.recency },
17730
- ...options.mmr !== void 0 && { mmr: options.mmr },
17731
- ...options.supersessionBoost !== void 0 && {
17732
- supersessionBoost: options.supersessionBoost
17733
- },
17734
- ...options.supersessionWindow !== void 0 && {
17735
- supersessionWindow: options.supersessionWindow
17736
- },
17737
- ...options.proofCountAlpha !== void 0 && {
17738
- proofCountAlpha: options.proofCountAlpha
17739
- },
17740
- ...options.bm25AdmissionDivisor !== void 0 && {
17741
- bm25AdmissionDivisor: options.bm25AdmissionDivisor
17862
+ )))(
17863
+ query,
17864
+ {
17865
+ limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
17866
+ minSimilarity: vaultMinScore,
17867
+ useFusion: true,
17868
+ rerank: flags.rerank,
17869
+ ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
17870
+ ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
17871
+ ...options.rerankLoadTimeoutMs !== void 0 && {
17872
+ rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
17873
+ },
17874
+ ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
17875
+ ...options.recency && { recency: options.recency },
17876
+ ...options.mmr !== void 0 && { mmr: options.mmr },
17877
+ ...options.supersessionBoost !== void 0 && {
17878
+ supersessionBoost: options.supersessionBoost
17879
+ },
17880
+ ...options.supersessionWindow !== void 0 && {
17881
+ supersessionWindow: options.supersessionWindow
17882
+ },
17883
+ ...options.proofCountAlpha !== void 0 && {
17884
+ proofCountAlpha: options.proofCountAlpha
17885
+ },
17886
+ ...options.bm25AdmissionDivisor !== void 0 && {
17887
+ bm25AdmissionDivisor: options.bm25AdmissionDivisor
17888
+ },
17889
+ ...options.rrfK !== void 0 && { rrfK: options.rrfK },
17890
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
17891
+ ...subQueries && { subQueries },
17892
+ ...options.scopes && { scopes: options.scopes },
17893
+ ...options.folderId !== void 0 && { folderId: options.folderId },
17894
+ ...options.factTypes?.length && { factTypes: options.factTypes },
17895
+ ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
17896
+ ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
17897
+ ...entityRanking.length > 0 && { entityRanking },
17898
+ ...temporalRanking.length > 0 && { temporalRanking },
17899
+ ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
17900
+ queryEmbedTotalTimeoutMs
17742
17901
  },
17743
- ...options.rrfK !== void 0 && { rrfK: options.rrfK },
17744
- ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
17745
- ...subQueries && { subQueries },
17746
- ...options.scopes && { scopes: options.scopes },
17747
- ...options.folderId !== void 0 && { folderId: options.folderId },
17748
- ...options.factTypes?.length && { factTypes: options.factTypes },
17749
- ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
17750
- ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
17751
- ...entityRanking.length > 0 && { entityRanking },
17752
- ...temporalRanking.length > 0 && { temporalRanking },
17753
- ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
17754
- queryEmbedTotalTimeoutMs
17755
- });
17902
+ options.embeddingCache
17903
+ );
17756
17904
  factResults.push(
17757
17905
  ...dedupeBy(
17758
17906
  results,
@@ -18749,6 +18897,7 @@ function createLocalMemoryStore(options) {
18749
18897
  database,
18750
18898
  entityCollection: database.get("entity"),
18751
18899
  memoryEntityCollection: database.get("memory_entity"),
18900
+ canWrite: options.canWrite,
18752
18901
  ...userId !== void 0 && { userId },
18753
18902
  ...options.allowUnscopedRows !== void 0 && {
18754
18903
  allowUnscopedRows: options.allowUnscopedRows
@@ -18760,10 +18909,21 @@ function createLocalMemoryStore(options) {
18760
18909
  walletAddress: options.walletAddress,
18761
18910
  signMessage: options.signMessage,
18762
18911
  embeddedWalletSigner: options.embeddedWalletSigner,
18912
+ canWrite: options.canWrite,
18763
18913
  ...userId !== void 0 && { userId },
18764
18914
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
18765
18915
  entityCtx
18766
18916
  };
18917
+ const assertWrite = async (writeOptions) => {
18918
+ writeOptions?.signal?.throwIfAborted();
18919
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
18920
+ throw new Error("Memory source is no longer eligible");
18921
+ }
18922
+ if (options.canWrite && !await options.canWrite()) {
18923
+ throw new Error("Memory source is no longer eligible");
18924
+ }
18925
+ writeOptions?.signal?.throwIfAborted();
18926
+ };
18767
18927
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
18768
18928
  const ownedBy = userId !== void 0 ? [Q8.where("user_id", userId)] : [];
18769
18929
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [Q8.or(Q8.where("user_id", userId), Q8.where("user_id", null))] : [Q8.where("user_id", userId)];
@@ -18875,9 +19035,25 @@ function createLocalMemoryStore(options) {
18875
19035
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
18876
19036
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
18877
19037
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
18878
- addTopics: (memoryId, topics) => database.write(async (writer) => {
19038
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
19039
+ await assertWrite(writeOptions);
18879
19040
  const owned = await vaultCtx.vaultMemoryCollection.query(Q8.where("id", memoryId), Q8.where("is_deleted", false), ...ownedBy).fetchCount();
18880
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
19041
+ return owned > 0 ? writer.callWriter(
19042
+ () => linkMemoryEntitiesOp(
19043
+ {
19044
+ ...entityCtx,
19045
+ canWrite: async () => {
19046
+ await assertWrite(writeOptions);
19047
+ return true;
19048
+ }
19049
+ },
19050
+ memoryId,
19051
+ topics,
19052
+ {
19053
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
19054
+ }
19055
+ )
19056
+ ) : [];
18881
19057
  }),
18882
19058
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
18883
19059
  recall: (query, recallOptions) => recall(
@@ -18887,14 +19063,33 @@ function createLocalMemoryStore(options) {
18887
19063
  ),
18888
19064
  retain: (content, retainOptions) => retain(
18889
19065
  content,
18890
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
19066
+ {
19067
+ vaultCtx: {
19068
+ ...vaultCtx,
19069
+ canWrite: async () => {
19070
+ await assertWrite(retainOptions);
19071
+ return true;
19072
+ }
19073
+ },
19074
+ embeddingOptions: options.embeddingOptions,
19075
+ vaultCache
19076
+ },
18891
19077
  retainOptions
18892
19078
  ),
18893
19079
  factSource: {
18894
- search: (query, searchOptions) => searchVaultMemoriesWithSize(
19080
+ search: (query, searchOptions, embeddingCache) => searchVaultMemoriesWithSize(
18895
19081
  query,
18896
19082
  vaultCtx,
18897
- { ...options.embeddingOptions, taskType: "memory_recall_embed" },
19083
+ {
19084
+ ...options.embeddingOptions,
19085
+ taskType: "memory_recall_embed",
19086
+ ...embeddingCache && !options.embeddingOptions.cache && {
19087
+ cache: maskScopedEmbeddingCache(
19088
+ embeddingCache,
19089
+ Boolean(options.embeddingOptions.maskInput)
19090
+ )
19091
+ }
19092
+ },
18898
19093
  vaultCache,
18899
19094
  searchOptions
18900
19095
  ),
@@ -19524,7 +19719,14 @@ function stored(row) {
19524
19719
  function createRemoteMemoryPipeline(options) {
19525
19720
  const { persistence, embeddingOptions } = options;
19526
19721
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
19527
- const operation = (mode) => {
19722
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
19723
+ const signal = writeOptions?.signal;
19724
+ const assertWrite = async () => {
19725
+ signal?.throwIfAborted();
19726
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
19727
+ throw new Error("Memory source is no longer eligible");
19728
+ signal?.throwIfAborted();
19729
+ };
19528
19730
  const rejectUndecryptable = (failed) => {
19529
19731
  if (!failed.length) return;
19530
19732
  if (mode === "retain")
@@ -19542,7 +19744,7 @@ function createRemoteMemoryPipeline(options) {
19542
19744
  return items.map((item) => stored(item.memory));
19543
19745
  };
19544
19746
  const get = async (id) => {
19545
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
19747
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
19546
19748
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
19547
19749
  snapshots.set(id, snapshot);
19548
19750
  return stored(snapshot.memory);
@@ -19555,7 +19757,7 @@ function createRemoteMemoryPipeline(options) {
19555
19757
  const start = Date.now();
19556
19758
  const budgetMs = search.queryEmbedTotalTimeoutMs ?? 8e3;
19557
19759
  const embed = (texts) => generateEmbeddings(texts, {
19558
- ...embeddingOptions,
19760
+ ...queryOptions,
19559
19761
  taskType: mode === "retain" ? "memory_retain_embed" : "memory_recall_embed",
19560
19762
  totalTimeoutMs: budgetMs
19561
19763
  });
@@ -19575,7 +19777,11 @@ function createRemoteMemoryPipeline(options) {
19575
19777
  }
19576
19778
  };
19577
19779
  const [primary, facetEmbeddings] = await Promise.all([
19578
- search.queryEmbedding ?? embed([query]).then((vectors2) => vectors2[0]),
19780
+ search.queryEmbedding ?? (mode === "recall" && queryOptions.cache ? generateEmbedding(query, {
19781
+ ...queryOptions,
19782
+ taskType: "memory_recall_embed",
19783
+ totalTimeoutMs: budgetMs
19784
+ }) : embed([query]).then((vectors2) => vectors2[0])),
19579
19785
  embedFacets()
19580
19786
  ]);
19581
19787
  const queryEmbedding = storedVector(primary);
@@ -19588,7 +19794,7 @@ function createRemoteMemoryPipeline(options) {
19588
19794
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
19589
19795
  }
19590
19796
  const forceIds = [...forced];
19591
- const windowOptions = (i, signal) => ({
19797
+ const windowOptions = (i, signal2) => ({
19592
19798
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
19593
19799
  force_ids: i === 0 ? forceIds : [],
19594
19800
  ...search.scopes && { scopes: search.scopes },
@@ -19596,13 +19802,15 @@ function createRemoteMemoryPipeline(options) {
19596
19802
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
19597
19803
  include_archived: search.includeArchived,
19598
19804
  embedding_model: model,
19599
- ...signal && { signal }
19805
+ ...signal2 && { signal: signal2 }
19600
19806
  });
19601
19807
  const vectors = [queryEmbedding, ...facetEmbeddings];
19602
19808
  let windows;
19603
19809
  if (mode === "retain") {
19604
19810
  windows = await Promise.all(
19605
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
19811
+ vectors.map(
19812
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
19813
+ )
19606
19814
  );
19607
19815
  } else {
19608
19816
  const deadline = new AbortController();
@@ -19690,12 +19898,15 @@ function createRemoteMemoryPipeline(options) {
19690
19898
  twin_opt_in: false
19691
19899
  };
19692
19900
  };
19693
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
19901
+ const write = async (memory, snapshot) => {
19902
+ await assertWrite();
19903
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
19904
+ };
19694
19905
  const port = {
19695
19906
  prepare,
19696
19907
  get,
19697
19908
  getFresh: async (id) => {
19698
- const current = await persistence.get(id);
19909
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
19699
19910
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
19700
19911
  },
19701
19912
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -19706,7 +19917,8 @@ function createRemoteMemoryPipeline(options) {
19706
19917
  embedding_model: embeddingModel,
19707
19918
  strict_model: true,
19708
19919
  include_deleted: true,
19709
- deleted_only: true
19920
+ deleted_only: true,
19921
+ signal
19710
19922
  });
19711
19923
  rejectUndecryptable(window2.failed);
19712
19924
  return remember(window2.items);
@@ -19762,14 +19974,18 @@ function createRemoteMemoryPipeline(options) {
19762
19974
  if (!target || !newer || id === successor) return false;
19763
19975
  const old = snapshots.get(id);
19764
19976
  const next = snapshots.get(successor);
19977
+ await assertWrite();
19765
19978
  remember(
19766
- await persistence.putMany([
19767
- {
19768
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
19769
- expectedVersion: old
19770
- },
19771
- { memory: next.memory, expectedVersion: next }
19772
- ])
19979
+ await persistence.putMany(
19980
+ [
19981
+ {
19982
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
19983
+ expectedVersion: old
19984
+ },
19985
+ { memory: next.memory, expectedVersion: next }
19986
+ ],
19987
+ signal
19988
+ )
19773
19989
  );
19774
19990
  return true;
19775
19991
  },
@@ -19778,18 +19994,22 @@ function createRemoteMemoryPipeline(options) {
19778
19994
  if (!target) return { created: null, retired: false };
19779
19995
  const snapshot = snapshots.get(targetId);
19780
19996
  const successor = createRow(input);
19997
+ await assertWrite();
19781
19998
  const rows = remember(
19782
- await persistence.putMany([
19783
- { memory: successor, expectedVersion: 0 },
19784
- {
19785
- memory: {
19786
- ...snapshot.memory,
19787
- superseded_by: successor.memory_id,
19788
- superseded_at: Date.now()
19789
- },
19790
- expectedVersion: snapshot
19791
- }
19792
- ])
19999
+ await persistence.putMany(
20000
+ [
20001
+ { memory: successor, expectedVersion: 0 },
20002
+ {
20003
+ memory: {
20004
+ ...snapshot.memory,
20005
+ superseded_by: successor.memory_id,
20006
+ superseded_at: Date.now()
20007
+ },
20008
+ expectedVersion: snapshot
20009
+ }
20010
+ ],
20011
+ signal
20012
+ )
19793
20013
  );
19794
20014
  return { created: rows[0], retired: true };
19795
20015
  }
@@ -19799,15 +20019,17 @@ function createRemoteMemoryPipeline(options) {
19799
20019
  const factSource = {
19800
20020
  graphRanking: options.graphRanking,
19801
20021
  temporalRanking: options.temporalRanking,
19802
- search: async (text14, searchOptions) => {
19803
- const { prepare } = operation("recall");
20022
+ search: async (text14, searchOptions, embeddingCache) => {
20023
+ const queryOptions = {
20024
+ ...embeddingOptions,
20025
+ taskType: "memory_recall_embed",
20026
+ ...embeddingCache && !embeddingOptions.cache && {
20027
+ cache: maskScopedEmbeddingCache(embeddingCache, Boolean(embeddingOptions.maskInput))
20028
+ }
20029
+ };
20030
+ const { prepare } = operation("recall", queryOptions);
19804
20031
  const prepared = await prepare(text14, searchOptions);
19805
- const ranked = await rankPreparedVaultCandidates(
19806
- text14,
19807
- prepared,
19808
- { ...embeddingOptions, taskType: "memory_recall_embed" },
19809
- searchOptions
19810
- );
20032
+ const ranked = await rankPreparedVaultCandidates(text14, prepared, queryOptions, searchOptions);
19811
20033
  return {
19812
20034
  ...ranked,
19813
20035
  rankedOnCosine: prepared.memories.length > 0 && !prepared.embeddingsUnavailable,
@@ -19822,7 +20044,7 @@ function createRemoteMemoryPipeline(options) {
19822
20044
  factSource,
19823
20045
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
19824
20046
  retain: async (content, retainOptions) => {
19825
- const { port } = operation("retain");
20047
+ const { port } = operation("retain", embeddingOptions, retainOptions);
19826
20048
  return retainWithPersistence(
19827
20049
  content,
19828
20050
  {
@@ -20404,16 +20626,21 @@ function createRemoteMemoryStore(options) {
20404
20626
  const record2 = await persistence.get(id);
20405
20627
  return record2 && !record2.memory.is_deleted ? record2 : null;
20406
20628
  };
20407
- const mutateLocked = async (id, change, edit) => {
20408
- const record2 = await live(id);
20629
+ const mutateLocked = async (id, change, edit, writeOptions) => {
20630
+ writeOptions?.signal?.throwIfAborted();
20631
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
20409
20632
  if (!record2) return null;
20633
+ if (record2.memory.is_deleted) return null;
20634
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
20635
+ throw new Error("Memory source is no longer eligible");
20636
+ writeOptions?.signal?.throwIfAborted();
20410
20637
  const next = edit(structuredClone(record2.memory));
20411
20638
  if (!next) return null;
20412
- const saved = await persistence.put(next, record2);
20639
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
20413
20640
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
20414
20641
  return saved;
20415
20642
  };
20416
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
20643
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
20417
20644
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
20418
20645
  const mutateMany = async (ids, change, edit) => {
20419
20646
  const saved = /* @__PURE__ */ new Map();
@@ -20926,13 +21153,21 @@ function createRemoteMemoryStore(options) {
20926
21153
  }))
20927
21154
  );
20928
21155
  },
20929
- addTopics: async (memoryId, topics) => {
21156
+ addTopics: async (memoryId, topics, writeOptions) => {
20930
21157
  const added = await withKnownKinds(topicInputs(topics, "auto"));
20931
- const saved = await mutate(memoryId, "edit", (memory) => ({
20932
- ...memory,
20933
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
20934
- topics_updated_at: Date.now()
20935
- }));
21158
+ const saved = await mutate(
21159
+ memoryId,
21160
+ "edit",
21161
+ (memory) => {
21162
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
21163
+ return {
21164
+ ...memory,
21165
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
21166
+ topics_updated_at: Date.now()
21167
+ };
21168
+ },
21169
+ writeOptions
21170
+ );
20936
21171
  if (!saved) return [];
20937
21172
  const now = /* @__PURE__ */ new Date();
20938
21173
  return mergeTopics([], added).map(
@@ -21783,6 +22018,7 @@ function createDurableAutoExtractor(options) {
21783
22018
  };
21784
22019
  const folderId = options.folderId ?? null;
21785
22020
  let disposed = false;
22021
+ let activeBatch = null;
21786
22022
  let running = false;
21787
22023
  let wakeRequested = false;
21788
22024
  let timer;
@@ -21819,14 +22055,18 @@ function createDurableAutoExtractor(options) {
21819
22055
  }, delay);
21820
22056
  }
21821
22057
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
22058
+ if (disposed) throw new Error("Extraction session was disposed");
21822
22059
  let watchdog;
21823
22060
  let cancelled = false;
22061
+ const controller = new AbortController();
22062
+ activeBatch = controller;
21824
22063
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
21825
22064
  try {
21826
22065
  return await Promise.race([
21827
22066
  new Promise((resolve, reject) => {
21828
22067
  const worker = createAutoExtractor({
21829
22068
  ...options,
22069
+ signal: controller.signal,
21830
22070
  retainCtx: {
21831
22071
  ...options.retainCtx,
21832
22072
  vaultCtx: {
@@ -21855,6 +22095,8 @@ function createDurableAutoExtractor(options) {
21855
22095
  ]);
21856
22096
  } finally {
21857
22097
  cancelled = true;
22098
+ controller.abort();
22099
+ if (activeBatch === controller) activeBatch = null;
21858
22100
  if (watchdog !== void 0) clearTimeout(watchdog);
21859
22101
  }
21860
22102
  }
@@ -21866,6 +22108,7 @@ function createDurableAutoExtractor(options) {
21866
22108
  for (const message of loaded)
21867
22109
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
21868
22110
  await database.write(async () => {
22111
+ if (disposed) return;
21869
22112
  const current = idsOf(job);
21870
22113
  const acknowledged = ids.filter((id) => current.includes(id));
21871
22114
  if (!acknowledged.length) return;
@@ -22038,6 +22281,7 @@ function createDurableAutoExtractor(options) {
22038
22281
  jobScope,
22039
22282
  jobFolderId
22040
22283
  );
22284
+ if (disposed) throw new Error("Extraction session was disposed");
22041
22285
  options.onTurnComplete?.(result);
22042
22286
  return result;
22043
22287
  };
@@ -22074,6 +22318,7 @@ function createDurableAutoExtractor(options) {
22074
22318
  );
22075
22319
  scheduleAfterSuccess = true;
22076
22320
  } catch (error) {
22321
+ if (disposed) break;
22077
22322
  attempts.set(job.id, attempted + 1);
22078
22323
  retry = true;
22079
22324
  reportError(error, conversationId);
@@ -22096,6 +22341,14 @@ function createDurableAutoExtractor(options) {
22096
22341
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
22097
22342
  }
22098
22343
  }
22344
+ function dispose() {
22345
+ disposed = true;
22346
+ activeBatch?.abort(options.signal?.reason);
22347
+ if (timer !== void 0) clearTimeout(timer);
22348
+ options.signal?.removeEventListener("abort", dispose);
22349
+ }
22350
+ if (options.signal?.aborted) dispose();
22351
+ else options.signal?.addEventListener("abort", dispose, { once: true });
22099
22352
  schedule(0);
22100
22353
  return {
22101
22354
  processTurn(messages, conversationId) {
@@ -22175,10 +22428,7 @@ function createDurableAutoExtractor(options) {
22175
22428
  return true;
22176
22429
  },
22177
22430
  isProcessing: () => running,
22178
- dispose() {
22179
- disposed = true;
22180
- if (timer !== void 0) clearTimeout(timer);
22181
- }
22431
+ dispose
22182
22432
  };
22183
22433
  }
22184
22434
 
@@ -25427,6 +25677,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
25427
25677
  var NO_CONVERSATION_KEY = "__no_conversation__";
25428
25678
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
25429
25679
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
25680
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
25430
25681
  var conversationRedactors = /* @__PURE__ */ new Map();
25431
25682
  var clientToolFilterCache = /* @__PURE__ */ new Map();
25432
25683
  function getConversationRedactor(conversationId, nerDetector) {
@@ -25571,7 +25822,7 @@ function useChatStorage(options) {
25571
25822
  [resolvedPiiRedaction]
25572
25823
  );
25573
25824
  const maskEmbeddingInput = isPiiRedactor(resolvedPiiRedaction) ? maskForEmbedding : void 0;
25574
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
25825
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
25575
25826
  const messagesCollection = useMemo(() => database.get("history"), [database]);
25576
25827
  const conversationsCollection = useMemo(
25577
25828
  () => database.get("conversations"),
@@ -25820,6 +26071,7 @@ function useChatStorage(options) {
25820
26071
  }, [vaultEmbeddingCache]);
25821
26072
  const retainVaultMemory = useCallback4(
25822
26073
  async (input) => {
26074
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25823
26075
  if (memoryStore) {
25824
26076
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
25825
26077
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -25865,6 +26117,7 @@ function useChatStorage(options) {
25865
26117
  );
25866
26118
  const createMemoryVaultTool2 = useCallback4(
25867
26119
  (options2) => {
26120
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25868
26121
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
25869
26122
  return createMemoryVaultTool(
25870
26123
  memoryStore ?? vaultCtx,
@@ -25875,6 +26128,7 @@ function useChatStorage(options) {
25875
26128
  );
25876
26129
  const createRecallTool2 = useCallback4(
25877
26130
  (toolOptions, callbacks) => {
26131
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25878
26132
  if (!getToken && !memoryStore) {
25879
26133
  throw new Error("getToken is required for recall tool");
25880
26134
  }
@@ -25920,7 +26174,7 @@ function useChatStorage(options) {
25920
26174
  );
25921
26175
  const recallFn = useCallback4(
25922
26176
  async (query, options2) => {
25923
- if (!getToken && !memoryStore) {
26177
+ if (memoryStore === null || !getToken && !memoryStore) {
25924
26178
  return {
25925
26179
  memories: [],
25926
26180
  usedBudget: options2?.budget ?? "low",
@@ -25969,6 +26223,7 @@ function useChatStorage(options) {
25969
26223
  );
25970
26224
  const getVaultMemories = useCallback4(
25971
26225
  (options2) => {
26226
+ if (memoryStore === null) return Promise.resolve([]);
25972
26227
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
25973
26228
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
25974
26229
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -25979,7 +26234,8 @@ function useChatStorage(options) {
25979
26234
  [vaultCtx, memoryStore]
25980
26235
  );
25981
26236
  const deleteVaultMemory = useCallback4(
25982
- (id) => {
26237
+ async (id) => {
26238
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25983
26239
  return memoryStore ? memoryStore.delete(id) : deleteVaultMemoryOp(vaultCtx, id);
25984
26240
  },
25985
26241
  [vaultCtx, memoryStore]