@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.
@@ -4456,8 +4456,9 @@ async function getConversationsByProjectOp(ctx, projectId) {
4456
4456
  )
4457
4457
  );
4458
4458
  }
4459
- async function getMessagesOp(ctx, convId) {
4460
- const results = await ctx.messagesCollection.query(import_watermelondb2.Q.where("conversation_id", convId), import_watermelondb2.Q.sortBy("message_id", import_watermelondb2.Q.asc)).unsafeFetchRaw();
4459
+ async function getMessagesOp(ctx, convId, opts) {
4460
+ const fetched = await ctx.messagesCollection.query(import_watermelondb2.Q.where("conversation_id", convId), import_watermelondb2.Q.sortBy("message_id", import_watermelondb2.Q.asc)).unsafeFetchRaw();
4461
+ const results = opts?.rawFilter ? fetched.filter(opts.rawFilter) : fetched;
4461
4462
  return Promise.all(
4462
4463
  results.map(
4463
4464
  (raw) => messageRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
@@ -6318,6 +6319,24 @@ var deleteApiV1WalletsBindingByAddress = (options) => (options.client ?? client)
6318
6319
  // src/lib/memoryEngine/generate.ts
6319
6320
  init_clientConfig();
6320
6321
  init_taskType();
6322
+
6323
+ // src/lib/memoryEngine/embeddingCacheState.ts
6324
+ var EMBEDDING_CACHE_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/embedding-cache/v1");
6325
+ var cacheGlobal = globalThis;
6326
+ var fallbackState = {
6327
+ scopedViews: /* @__PURE__ */ new WeakMap(),
6328
+ inFlightByCache: /* @__PURE__ */ new WeakMap(),
6329
+ cachedEmbeddingModels: /* @__PURE__ */ new WeakMap()
6330
+ };
6331
+ function getEmbeddingCacheState() {
6332
+ try {
6333
+ return cacheGlobal[EMBEDDING_CACHE_STATE_KEY] ?? (cacheGlobal[EMBEDDING_CACHE_STATE_KEY] = fallbackState);
6334
+ } catch {
6335
+ return fallbackState;
6336
+ }
6337
+ }
6338
+
6339
+ // src/lib/memoryEngine/generate.ts
6321
6340
  var EMBED_MAX_ATTEMPTS = 4;
6322
6341
  var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
6323
6342
  var DEFAULT_EMBEDDING_TOKEN_TIMEOUT_MS = 1e4;
@@ -6408,15 +6427,18 @@ async function generateEmbedding(text14, options) {
6408
6427
  if (total === void 0) return embedOne(text14, options, void 0);
6409
6428
  return withDeadline((signal) => embedOne(text14, options, signal), total, "embedding");
6410
6429
  }
6411
- var inFlightByCache = /* @__PURE__ */ new WeakMap();
6430
+ var { inFlightByCache, cachedEmbeddingModels } = getEmbeddingCacheState();
6412
6431
  async function embedOne(text14, options, outer) {
6413
6432
  const { cache } = options;
6414
6433
  if (!cache) return Array.from(await requestEmbedding(text14, options, outer));
6434
+ const model = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
6415
6435
  const cached = cache.get(text14);
6416
- if (cached) return Array.from(cached);
6436
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
6437
+ if (cached && (cachedModel === void 0 || cachedModel === model)) return Array.from(cached);
6417
6438
  const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
6418
6439
  inFlightByCache.set(cache, inFlight);
6419
- const pending = inFlight.get(text14);
6440
+ const requestKey = JSON.stringify([model, text14]);
6441
+ const pending = inFlight.get(requestKey);
6420
6442
  if (pending) {
6421
6443
  try {
6422
6444
  return Array.from(await pending);
@@ -6425,13 +6447,14 @@ async function embedOne(text14, options, outer) {
6425
6447
  }
6426
6448
  }
6427
6449
  const request = requestEmbedding(text14, options, outer);
6428
- inFlight.set(text14, request);
6450
+ inFlight.set(requestKey, request);
6429
6451
  try {
6430
6452
  const embedding2 = await request;
6453
+ cachedEmbeddingModels.set(embedding2, model);
6431
6454
  cache.set(text14, embedding2);
6432
6455
  return Array.from(embedding2);
6433
6456
  } finally {
6434
- if (inFlight.get(text14) === request) inFlight.delete(text14);
6457
+ if (inFlight.get(requestKey) === request) inFlight.delete(requestKey);
6435
6458
  }
6436
6459
  }
6437
6460
  async function requestEmbedding(text14, options, outer) {
@@ -6514,13 +6537,15 @@ async function embedMany(texts, options, outer) {
6514
6537
  const { baseUrl = BASE_URL, model, batchSize, cache } = options;
6515
6538
  const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
6516
6539
  const chunkSize = batchSize ?? DEFAULT_EMBEDDING_BATCH_SIZE;
6540
+ const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
6517
6541
  const results = new Array(texts.length).fill(null);
6518
6542
  const uncachedIndices = [];
6519
6543
  const uncachedTexts = [];
6520
6544
  for (let i = 0; i < texts.length; i++) {
6521
6545
  if (cache) {
6522
6546
  const cached = cache.get(texts[i]);
6523
- if (cached) {
6547
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
6548
+ if (cached && (cachedModel === void 0 || cachedModel === embeddingModel)) {
6524
6549
  results[i] = Array.from(cached);
6525
6550
  continue;
6526
6551
  }
@@ -6536,7 +6561,6 @@ async function embedMany(texts, options, outer) {
6536
6561
  ...taskTypeHeader(options.taskType)
6537
6562
  };
6538
6563
  if (outer?.aborted) throw outer.reason;
6539
- const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
6540
6564
  let newEmbeddings;
6541
6565
  if (uncachedTexts.length <= chunkSize) {
6542
6566
  newEmbeddings = await generateEmbeddingsBatch(
@@ -6584,6 +6608,7 @@ async function embedMany(texts, options, outer) {
6584
6608
  const f32Embedding = Float32Array.from(newEmbeddings[i]);
6585
6609
  results[uncachedIndices[i]] = Array.from(f32Embedding);
6586
6610
  if (cache) {
6611
+ cachedEmbeddingModels.set(f32Embedding, embeddingModel);
6587
6612
  cache.set(uncachedTexts[i], f32Embedding);
6588
6613
  }
6589
6614
  }
@@ -6708,6 +6733,37 @@ async function chunkAndEmbedMessage(ctx, messageId, options) {
6708
6733
  }));
6709
6734
  return updateMessageChunksOp(ctx, messageId, messageChunks, embeddingModel);
6710
6735
  }
6736
+ var MIN_SEALED_EMBEDDING_HEX = 256;
6737
+ function rawHasEmbedding(value) {
6738
+ if (Array.isArray(value)) return value.length > 0;
6739
+ if (typeof value !== "string" || value.length === 0) return false;
6740
+ if (isEncrypted(value)) {
6741
+ return value.length - value.indexOf(":", 4) - 1 >= MIN_SEALED_EMBEDDING_HEX;
6742
+ }
6743
+ try {
6744
+ const parsed = JSON.parse(value);
6745
+ return Array.isArray(parsed) && parsed.length > 0;
6746
+ } catch {
6747
+ return false;
6748
+ }
6749
+ }
6750
+ function sealedVersion(value) {
6751
+ if (typeof value !== "string" || !isEncrypted(value)) return null;
6752
+ return value.startsWith("enc:v2:") ? "v2" : "v3";
6753
+ }
6754
+ function mayNeedChunkEmbedding(raw, embeddingModel, filter) {
6755
+ const role = raw.role;
6756
+ if (role === "system") return false;
6757
+ if (filter?.roles && !filter.roles.includes(role ?? "")) return false;
6758
+ if (isNonEmbeddableOrigin(raw.origin, filter?.reembedDiscarded)) {
6759
+ return false;
6760
+ }
6761
+ const model = raw.embedding_model;
6762
+ if (typeof model === "string" && model !== embeddingModel) return true;
6763
+ if (rawHasEmbedding(raw.chunks)) return false;
6764
+ if (rawHasEmbedding(raw.vector) && !filter?.rechunkExisting) return false;
6765
+ return true;
6766
+ }
6711
6767
  async function chunkAndEmbedAllMessages(ctx, options, filter) {
6712
6768
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
6713
6769
  const { chunkSize = DEFAULT_CHUNK_SIZE } = options;
@@ -6720,11 +6776,31 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
6720
6776
  let considered = 0;
6721
6777
  let sealedRowsSeen = 0;
6722
6778
  let rowsSeen = 0;
6779
+ const keptSealed = /* @__PURE__ */ new Map();
6780
+ const candidatesStillSealed = /* @__PURE__ */ new Map();
6781
+ const droppedSealed = /* @__PURE__ */ new Map();
6782
+ const rawFilter = (raw) => {
6783
+ rowsSeen++;
6784
+ const keep = mayNeedChunkEmbedding(raw, embeddingModel, filter);
6785
+ const version = sealedVersion(raw.content);
6786
+ if (!version) return keep;
6787
+ if (keep) {
6788
+ keptSealed.set(version, (keptSealed.get(version) ?? 0) + 1);
6789
+ } else {
6790
+ const dropped = droppedSealed.get(version);
6791
+ if (dropped) dropped.count++;
6792
+ else droppedSealed.set(version, { count: 1, probeId: raw.id });
6793
+ }
6794
+ return keep;
6795
+ };
6723
6796
  for (const conv of targetConversations) {
6724
- const messages = await getMessagesOp(ctx, conv.conversationId);
6797
+ const messages = await getMessagesOp(ctx, conv.conversationId, { rawFilter });
6725
6798
  for (const message of messages) {
6726
- rowsSeen++;
6727
- if (isEncrypted(message.content)) sealedRowsSeen++;
6799
+ const stillSealed = sealedVersion(message.content);
6800
+ if (stillSealed) {
6801
+ sealedRowsSeen++;
6802
+ candidatesStillSealed.set(stillSealed, (candidatesStillSealed.get(stillSealed) ?? 0) + 1);
6803
+ }
6728
6804
  const isStale = message.embeddingModel !== void 0 && message.embeddingModel !== null && message.embeddingModel !== embeddingModel;
6729
6805
  if (message.chunks && message.chunks.length > 0 && !isStale) continue;
6730
6806
  const hasVector = message.vector && message.vector.length > 0;
@@ -6737,6 +6813,7 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
6737
6813
  stillEncrypted++;
6738
6814
  continue;
6739
6815
  }
6816
+ if (isToolResultsRow(message)) continue;
6740
6817
  if (message.content.length < minLength) continue;
6741
6818
  if (shouldChunkMessage(message.content, chunkSize)) {
6742
6819
  longMessages.push({
@@ -6748,6 +6825,12 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
6748
6825
  }
6749
6826
  }
6750
6827
  }
6828
+ for (const [version, dropped] of droppedSealed) {
6829
+ const keyOpenedACandidate = (keptSealed.get(version) ?? 0) > (candidatesStillSealed.get(version) ?? 0);
6830
+ if (keyOpenedACandidate) continue;
6831
+ const probe = await getMessageOp(ctx, dropped.probeId);
6832
+ if (!probe || isEncrypted(probe.content)) sealedRowsSeen += dropped.count;
6833
+ }
6751
6834
  if (stillEncrypted > 0 || sealedRowsSeen > 0) {
6752
6835
  getLogger().error(
6753
6836
  "memoryEngine: messages still encrypted (key unavailable?) \u2014 excluded from embedding",
@@ -12209,6 +12292,11 @@ function parseTopics(value) {
12209
12292
  }
12210
12293
 
12211
12294
  // src/lib/db/entities/operations.ts
12295
+ async function assertCanWrite(ctx) {
12296
+ if (ctx.canWrite && !await ctx.canWrite()) {
12297
+ throw new Error("Memory source is no longer eligible");
12298
+ }
12299
+ }
12212
12300
  function entityToStored(e) {
12213
12301
  return {
12214
12302
  uniqueId: e.id,
@@ -12349,6 +12437,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
12349
12437
  let skipped = false;
12350
12438
  let entities = [];
12351
12439
  await ctx.database.write(async () => {
12440
+ await assertCanWrite(ctx);
12352
12441
  const {
12353
12442
  entities: byName,
12354
12443
  operations: entityOps,
@@ -12424,6 +12513,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
12424
12513
  let skipped = false;
12425
12514
  let entities = [];
12426
12515
  await ctx.database.write(async () => {
12516
+ await assertCanWrite(ctx);
12427
12517
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
12428
12518
  if (options.guarded && autoLinkBlocked(vaultRow)) {
12429
12519
  skipped = true;
@@ -12484,6 +12574,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
12484
12574
  const links = await ctx.memoryEntityCollection.query(import_watermelondb11.Q.where("memory_id", import_watermelondb11.Q.oneOf(Array.from(memoryIds)))).fetch();
12485
12575
  if (links.length === 0) return;
12486
12576
  await ctx.database.write(async () => {
12577
+ await assertCanWrite(ctx);
12487
12578
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
12488
12579
  });
12489
12580
  }
@@ -12492,6 +12583,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
12492
12583
  const links = await ctx.memoryEntityCollection.query(import_watermelondb11.Q.where("user_id", userId)).fetch();
12493
12584
  if (links.length === 0) return;
12494
12585
  await ctx.database.write(async () => {
12586
+ await assertCanWrite(ctx);
12495
12587
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
12496
12588
  });
12497
12589
  }
@@ -12511,6 +12603,7 @@ async function backfillMemoryEntityUserIdsOp(ctx, vaultMemoryCollection) {
12511
12603
  }
12512
12604
  if (toUpdate.length === 0) return 0;
12513
12605
  await ctx.database.write(async () => {
12606
+ await assertCanWrite(ctx);
12514
12607
  await ctx.database.batch(
12515
12608
  ...toUpdate.map(({ link, userId }) => link.prepareUpdate((r) => r._setRaw("user_id", userId)))
12516
12609
  );
@@ -14904,6 +14997,11 @@ function visibilityConditions(requested) {
14904
14997
  function isKinded(record2) {
14905
14998
  return record2.kind !== null && record2.kind !== void 0;
14906
14999
  }
15000
+ async function assertCanWrite2(ctx) {
15001
+ if (ctx.canWrite && !await ctx.canWrite()) {
15002
+ throw new Error("Memory source is no longer eligible");
15003
+ }
15004
+ }
14907
15005
  function assertValidKindLevel(kind, level) {
14908
15006
  const hasKind = kind !== null && kind !== void 0;
14909
15007
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -15084,8 +15182,7 @@ async function createVaultMemoryOp(ctx, opts) {
15084
15182
  ctx.embeddedWalletSigner
15085
15183
  ) : opts.content;
15086
15184
  const created = await ctx.database.write(async () => {
15087
- if (ctx.canWrite && !await ctx.canWrite())
15088
- throw new Error("Memory source is no longer eligible");
15185
+ await assertCanWrite2(ctx);
15089
15186
  return ctx.vaultMemoryCollection.create(
15090
15187
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
15091
15188
  );
@@ -15104,8 +15201,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
15104
15201
  ) : opts.content;
15105
15202
  let createdRecord = null;
15106
15203
  await ctx.database.write(async () => {
15107
- if (ctx.canWrite && !await ctx.canWrite())
15108
- throw new Error("Memory source is no longer eligible");
15204
+ await assertCanWrite2(ctx);
15109
15205
  let target;
15110
15206
  try {
15111
15207
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -15197,8 +15293,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
15197
15293
  })
15198
15294
  );
15199
15295
  const created = await ctx.database.write(async () => {
15200
- if (ctx.canWrite && !await ctx.canWrite())
15201
- throw new Error("Memory source is no longer eligible");
15296
+ await assertCanWrite2(ctx);
15202
15297
  const prepared = optionsArray.map(
15203
15298
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
15204
15299
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -15503,8 +15598,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
15503
15598
  const record2 = probe;
15504
15599
  const originalUpdatedAt = record2.updatedAt.getTime();
15505
15600
  await ctx.database.write(async () => {
15506
- if (ctx.canWrite && !await ctx.canWrite())
15507
- throw new Error("Memory source is no longer eligible");
15601
+ await assertCanWrite2(ctx);
15508
15602
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
15509
15603
  stale = true;
15510
15604
  return;
@@ -15615,6 +15709,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
15615
15709
  let stale = false;
15616
15710
  const originalUpdatedAt = record2.updatedAt.getTime();
15617
15711
  await ctx.database.write(async (writer) => {
15712
+ await assertCanWrite2(ctx);
15618
15713
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15619
15714
  stale = true;
15620
15715
  return;
@@ -15656,6 +15751,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
15656
15751
  const originalUpdatedAt = record2.updatedAt.getTime();
15657
15752
  let cleared = false;
15658
15753
  await ctx.database.write(async () => {
15754
+ await assertCanWrite2(ctx);
15659
15755
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
15660
15756
  cleared = true;
15661
15757
  await record2.update((r) => {
@@ -15679,6 +15775,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
15679
15775
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
15680
15776
  let stale = false;
15681
15777
  await ctx.database.write(async () => {
15778
+ await assertCanWrite2(ctx);
15682
15779
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15683
15780
  stale = true;
15684
15781
  return;
@@ -15707,6 +15804,7 @@ async function deleteVaultMemoryOp(ctx, id) {
15707
15804
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
15708
15805
  let stale = false;
15709
15806
  await ctx.database.write(async () => {
15807
+ await assertCanWrite2(ctx);
15710
15808
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15711
15809
  stale = true;
15712
15810
  return;
@@ -15744,8 +15842,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
15744
15842
  }
15745
15843
  let stale = false;
15746
15844
  await ctx.database.write(async () => {
15747
- if (ctx.canWrite && !await ctx.canWrite())
15748
- throw new Error("Memory source is no longer eligible");
15845
+ await assertCanWrite2(ctx);
15749
15846
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
15750
15847
  stale = true;
15751
15848
  return;
@@ -15907,6 +16004,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
15907
16004
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
15908
16005
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
15909
16006
  await ctx.database.write(async () => {
16007
+ await assertCanWrite2(ctx);
15910
16008
  const records = [];
15911
16009
  for (const id of chunkIds) {
15912
16010
  try {
@@ -15940,6 +16038,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
15940
16038
  for (const id of Array.from(new Set(memoryIds))) {
15941
16039
  try {
15942
16040
  await ctx.database.write(async (writer) => {
16041
+ await assertCanWrite2(ctx);
15943
16042
  let record2;
15944
16043
  try {
15945
16044
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -15970,6 +16069,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
15970
16069
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
15971
16070
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
15972
16071
  await ctx.database.write(async () => {
16072
+ await assertCanWrite2(ctx);
15973
16073
  const writes = [];
15974
16074
  for (const id of chunkIds) {
15975
16075
  let record2;
@@ -16002,6 +16102,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
16002
16102
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb23.Q.where("user_id", userId), import_watermelondb23.Q.where("is_deleted", false)).fetch();
16003
16103
  if (records.length === 0) return 0;
16004
16104
  await ctx.database.write(async () => {
16105
+ await assertCanWrite2(ctx);
16005
16106
  const prepared = records.map(
16006
16107
  (record2) => record2.prepareUpdate((r) => {
16007
16108
  r._setRaw("is_deleted", true);
@@ -16056,6 +16157,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
16056
16157
  let stale = false;
16057
16158
  const archivedAtValue = opts?.now ?? Date.now();
16058
16159
  await ctx.database.write(async () => {
16160
+ await assertCanWrite2(ctx);
16059
16161
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
16060
16162
  stale = true;
16061
16163
  return;
@@ -16079,6 +16181,7 @@ async function restoreVaultMemoryOp(ctx, id) {
16079
16181
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
16080
16182
  let stale = false;
16081
16183
  await ctx.database.write(async () => {
16184
+ await assertCanWrite2(ctx);
16082
16185
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
16083
16186
  stale = true;
16084
16187
  return;
@@ -16099,6 +16202,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
16099
16202
  const now = opts.now ?? Date.now();
16100
16203
  let stale = false;
16101
16204
  await ctx.database.write(async () => {
16205
+ await assertCanWrite2(ctx);
16102
16206
  const archivedAt = record2.archivedAt;
16103
16207
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
16104
16208
  stale = true;
@@ -16138,6 +16242,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
16138
16242
  }
16139
16243
  let written = false;
16140
16244
  await ctx.database.write(async () => {
16245
+ await assertCanWrite2(ctx);
16141
16246
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
16142
16247
  const originalUpdatedAt = record2.updatedAt.getTime();
16143
16248
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -20134,6 +20239,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
20134
20239
  ).map((c) => c.candidate);
20135
20240
  }
20136
20241
  async function extractAndRetain(messages, retainCtx, options) {
20242
+ const { memoryStore, signal } = options;
20243
+ if (memoryStore === null) throw new Error("Memory store is not ready");
20244
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
20245
+ throw new Error("Remote memory extraction does not support folders");
20246
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
20247
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
20248
+ signal?.throwIfAborted();
20137
20249
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
20138
20250
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
20139
20251
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -20167,6 +20279,7 @@ async function extractAndRetain(messages, retainCtx, options) {
20167
20279
  }
20168
20280
  });
20169
20281
  const extractMs = Date.now() - tExtract;
20282
+ signal?.throwIfAborted();
20170
20283
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
20171
20284
  const log = getLogger();
20172
20285
  const screened = screenCandidatesForInjection(filtered);
@@ -20214,16 +20327,33 @@ async function extractAndRetain(messages, retainCtx, options) {
20214
20327
  const results = [];
20215
20328
  const quarantinedInfo = [];
20216
20329
  let failedWrites = 0;
20330
+ let auditRows;
20217
20331
  const tRetain = Date.now();
20218
20332
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
20219
20333
  try {
20334
+ signal?.throwIfAborted();
20220
20335
  if (isQuarantined) {
20221
- const existingId = await findQuarantinedDuplicateOp(
20222
- retainCtx.vaultCtx,
20223
- candidate.content,
20224
- candidate.sourceMessageIds,
20225
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
20226
- ).catch(() => null);
20336
+ let existingId;
20337
+ if (memoryStore) {
20338
+ auditRows ?? (auditRows = memoryStore.list({
20339
+ scopes: [options.scope ?? "private"],
20340
+ includeArchived: true,
20341
+ includeQuarantined: true,
20342
+ includeSuperseded: true
20343
+ }));
20344
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
20345
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
20346
+ existingId = (await auditRows).find(
20347
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
20348
+ )?.uniqueId ?? null;
20349
+ } else {
20350
+ existingId = await findQuarantinedDuplicateOp(
20351
+ retainCtx.vaultCtx,
20352
+ candidate.content,
20353
+ candidate.sourceMessageIds,
20354
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
20355
+ ).catch(() => null);
20356
+ }
20227
20357
  if (existingId) {
20228
20358
  quarantinedInfo.push({
20229
20359
  candidate,
@@ -20234,18 +20364,26 @@ async function extractAndRetain(messages, retainCtx, options) {
20234
20364
  continue;
20235
20365
  }
20236
20366
  }
20237
- const result = await retain(candidate.content, retainCtx, {
20367
+ const retainOptions = {
20238
20368
  source: "auto-extracted",
20239
20369
  sourceChunkIds: candidate.sourceMessageIds,
20240
20370
  respectTombstones: true,
20241
20371
  ...options.scope !== void 0 && { scope: options.scope },
20242
- ...options.folderId !== void 0 && { folderId: options.folderId },
20243
20372
  ...consolidateOptions !== void 0 && { consolidateOptions },
20244
20373
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
20245
20374
  factType: candidate.type,
20246
20375
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
20376
+ };
20377
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
20378
+ ...retainOptions,
20379
+ canWrite: retainCtx.vaultCtx.canWrite,
20380
+ signal
20381
+ }) : await retain(candidate.content, retainCtx, {
20382
+ ...retainOptions,
20383
+ ...options.folderId !== void 0 && { folderId: options.folderId }
20247
20384
  });
20248
20385
  if (isQuarantined) {
20386
+ auditRows = void 0;
20249
20387
  const info = {
20250
20388
  candidate,
20251
20389
  memoryId: result.memoryId,
@@ -20264,9 +20402,16 @@ async function extractAndRetain(messages, retainCtx, options) {
20264
20402
  }
20265
20403
  succeededCandidates.push(candidate);
20266
20404
  results.push(result);
20267
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
20405
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
20268
20406
  try {
20269
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
20407
+ signal?.throwIfAborted();
20408
+ if (memoryStore) {
20409
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
20410
+ unlessUserManaged: true,
20411
+ canWrite: retainCtx.vaultCtx.canWrite,
20412
+ signal
20413
+ });
20414
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
20270
20415
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
20271
20416
  unlessTopicsUserManaged: true
20272
20417
  });
@@ -20516,7 +20661,7 @@ function createAutoExtractor(options) {
20516
20661
  };
20517
20662
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
20518
20663
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
20519
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
20664
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
20520
20665
  getLogger().warn(
20521
20666
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
20522
20667
  );
@@ -20561,6 +20706,8 @@ function createAutoExtractor(options) {
20561
20706
  try {
20562
20707
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
20563
20708
  extract,
20709
+ memoryStore: options.memoryStore,
20710
+ signal: options.signal,
20564
20711
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
20565
20712
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
20566
20713
  ...options.scope !== void 0 && { scope: options.scope },
@@ -21221,7 +21368,7 @@ var MaskScopedEmbeddingCache = class extends Map {
21221
21368
  return this;
21222
21369
  }
21223
21370
  };
21224
- var scopedViews = /* @__PURE__ */ new WeakMap();
21371
+ var { scopedViews } = getEmbeddingCacheState();
21225
21372
  function maskScopedEmbeddingCache(cache, masked) {
21226
21373
  const views = scopedViews.get(cache) ?? {};
21227
21374
  scopedViews.set(cache, views);
@@ -21646,44 +21793,48 @@ async function recall(query, ctx, options = {}) {
21646
21793
  embeddingOptions,
21647
21794
  ctx.vaultCache,
21648
21795
  searchOptions
21649
- )))(query, {
21650
- limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
21651
- minSimilarity: vaultMinScore,
21652
- useFusion: true,
21653
- rerank: flags.rerank,
21654
- ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
21655
- ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
21656
- ...options.rerankLoadTimeoutMs !== void 0 && {
21657
- rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
21658
- },
21659
- ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
21660
- ...options.recency && { recency: options.recency },
21661
- ...options.mmr !== void 0 && { mmr: options.mmr },
21662
- ...options.supersessionBoost !== void 0 && {
21663
- supersessionBoost: options.supersessionBoost
21664
- },
21665
- ...options.supersessionWindow !== void 0 && {
21666
- supersessionWindow: options.supersessionWindow
21667
- },
21668
- ...options.proofCountAlpha !== void 0 && {
21669
- proofCountAlpha: options.proofCountAlpha
21670
- },
21671
- ...options.bm25AdmissionDivisor !== void 0 && {
21672
- bm25AdmissionDivisor: options.bm25AdmissionDivisor
21796
+ )))(
21797
+ query,
21798
+ {
21799
+ limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
21800
+ minSimilarity: vaultMinScore,
21801
+ useFusion: true,
21802
+ rerank: flags.rerank,
21803
+ ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
21804
+ ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
21805
+ ...options.rerankLoadTimeoutMs !== void 0 && {
21806
+ rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
21807
+ },
21808
+ ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
21809
+ ...options.recency && { recency: options.recency },
21810
+ ...options.mmr !== void 0 && { mmr: options.mmr },
21811
+ ...options.supersessionBoost !== void 0 && {
21812
+ supersessionBoost: options.supersessionBoost
21813
+ },
21814
+ ...options.supersessionWindow !== void 0 && {
21815
+ supersessionWindow: options.supersessionWindow
21816
+ },
21817
+ ...options.proofCountAlpha !== void 0 && {
21818
+ proofCountAlpha: options.proofCountAlpha
21819
+ },
21820
+ ...options.bm25AdmissionDivisor !== void 0 && {
21821
+ bm25AdmissionDivisor: options.bm25AdmissionDivisor
21822
+ },
21823
+ ...options.rrfK !== void 0 && { rrfK: options.rrfK },
21824
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
21825
+ ...subQueries && { subQueries },
21826
+ ...options.scopes && { scopes: options.scopes },
21827
+ ...options.folderId !== void 0 && { folderId: options.folderId },
21828
+ ...options.factTypes?.length && { factTypes: options.factTypes },
21829
+ ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
21830
+ ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
21831
+ ...entityRanking.length > 0 && { entityRanking },
21832
+ ...temporalRanking.length > 0 && { temporalRanking },
21833
+ ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
21834
+ queryEmbedTotalTimeoutMs
21673
21835
  },
21674
- ...options.rrfK !== void 0 && { rrfK: options.rrfK },
21675
- ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
21676
- ...subQueries && { subQueries },
21677
- ...options.scopes && { scopes: options.scopes },
21678
- ...options.folderId !== void 0 && { folderId: options.folderId },
21679
- ...options.factTypes?.length && { factTypes: options.factTypes },
21680
- ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
21681
- ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
21682
- ...entityRanking.length > 0 && { entityRanking },
21683
- ...temporalRanking.length > 0 && { temporalRanking },
21684
- ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
21685
- queryEmbedTotalTimeoutMs
21686
- });
21836
+ options.embeddingCache
21837
+ );
21687
21838
  factResults.push(
21688
21839
  ...dedupeBy(
21689
21840
  results,
@@ -22688,6 +22839,7 @@ function createLocalMemoryStore(options) {
22688
22839
  database,
22689
22840
  entityCollection: database.get("entity"),
22690
22841
  memoryEntityCollection: database.get("memory_entity"),
22842
+ canWrite: options.canWrite,
22691
22843
  ...userId !== void 0 && { userId },
22692
22844
  ...options.allowUnscopedRows !== void 0 && {
22693
22845
  allowUnscopedRows: options.allowUnscopedRows
@@ -22699,10 +22851,21 @@ function createLocalMemoryStore(options) {
22699
22851
  walletAddress: options.walletAddress,
22700
22852
  signMessage: options.signMessage,
22701
22853
  embeddedWalletSigner: options.embeddedWalletSigner,
22854
+ canWrite: options.canWrite,
22702
22855
  ...userId !== void 0 && { userId },
22703
22856
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
22704
22857
  entityCtx
22705
22858
  };
22859
+ const assertWrite = async (writeOptions) => {
22860
+ writeOptions?.signal?.throwIfAborted();
22861
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
22862
+ throw new Error("Memory source is no longer eligible");
22863
+ }
22864
+ if (options.canWrite && !await options.canWrite()) {
22865
+ throw new Error("Memory source is no longer eligible");
22866
+ }
22867
+ writeOptions?.signal?.throwIfAborted();
22868
+ };
22706
22869
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
22707
22870
  const ownedBy = userId !== void 0 ? [import_watermelondb32.Q.where("user_id", userId)] : [];
22708
22871
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [import_watermelondb32.Q.or(import_watermelondb32.Q.where("user_id", userId), import_watermelondb32.Q.where("user_id", null))] : [import_watermelondb32.Q.where("user_id", userId)];
@@ -22814,9 +22977,25 @@ function createLocalMemoryStore(options) {
22814
22977
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
22815
22978
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
22816
22979
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
22817
- addTopics: (memoryId, topics) => database.write(async (writer) => {
22980
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
22981
+ await assertWrite(writeOptions);
22818
22982
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb32.Q.where("id", memoryId), import_watermelondb32.Q.where("is_deleted", false), ...ownedBy).fetchCount();
22819
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
22983
+ return owned > 0 ? writer.callWriter(
22984
+ () => linkMemoryEntitiesOp(
22985
+ {
22986
+ ...entityCtx,
22987
+ canWrite: async () => {
22988
+ await assertWrite(writeOptions);
22989
+ return true;
22990
+ }
22991
+ },
22992
+ memoryId,
22993
+ topics,
22994
+ {
22995
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
22996
+ }
22997
+ )
22998
+ ) : [];
22820
22999
  }),
22821
23000
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
22822
23001
  recall: (query, recallOptions) => recall(
@@ -22826,14 +23005,33 @@ function createLocalMemoryStore(options) {
22826
23005
  ),
22827
23006
  retain: (content, retainOptions) => retain(
22828
23007
  content,
22829
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
23008
+ {
23009
+ vaultCtx: {
23010
+ ...vaultCtx,
23011
+ canWrite: async () => {
23012
+ await assertWrite(retainOptions);
23013
+ return true;
23014
+ }
23015
+ },
23016
+ embeddingOptions: options.embeddingOptions,
23017
+ vaultCache
23018
+ },
22830
23019
  retainOptions
22831
23020
  ),
22832
23021
  factSource: {
22833
- search: (query, searchOptions) => searchVaultMemoriesWithSize(
23022
+ search: (query, searchOptions, embeddingCache) => searchVaultMemoriesWithSize(
22834
23023
  query,
22835
23024
  vaultCtx,
22836
- { ...options.embeddingOptions, taskType: "memory_recall_embed" },
23025
+ {
23026
+ ...options.embeddingOptions,
23027
+ taskType: "memory_recall_embed",
23028
+ ...embeddingCache && !options.embeddingOptions.cache && {
23029
+ cache: maskScopedEmbeddingCache(
23030
+ embeddingCache,
23031
+ Boolean(options.embeddingOptions.maskInput)
23032
+ )
23033
+ }
23034
+ },
22837
23035
  vaultCache,
22838
23036
  searchOptions
22839
23037
  ),
@@ -23465,7 +23663,14 @@ function stored(row) {
23465
23663
  function createRemoteMemoryPipeline(options) {
23466
23664
  const { persistence, embeddingOptions } = options;
23467
23665
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
23468
- const operation = (mode) => {
23666
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
23667
+ const signal = writeOptions?.signal;
23668
+ const assertWrite = async () => {
23669
+ signal?.throwIfAborted();
23670
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
23671
+ throw new Error("Memory source is no longer eligible");
23672
+ signal?.throwIfAborted();
23673
+ };
23469
23674
  const rejectUndecryptable = (failed) => {
23470
23675
  if (!failed.length) return;
23471
23676
  if (mode === "retain")
@@ -23483,7 +23688,7 @@ function createRemoteMemoryPipeline(options) {
23483
23688
  return items.map((item) => stored(item.memory));
23484
23689
  };
23485
23690
  const get = async (id) => {
23486
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
23691
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
23487
23692
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
23488
23693
  snapshots.set(id, snapshot);
23489
23694
  return stored(snapshot.memory);
@@ -23496,7 +23701,7 @@ function createRemoteMemoryPipeline(options) {
23496
23701
  const start = Date.now();
23497
23702
  const budgetMs = search.queryEmbedTotalTimeoutMs ?? 8e3;
23498
23703
  const embed = (texts) => generateEmbeddings(texts, {
23499
- ...embeddingOptions,
23704
+ ...queryOptions,
23500
23705
  taskType: mode === "retain" ? "memory_retain_embed" : "memory_recall_embed",
23501
23706
  totalTimeoutMs: budgetMs
23502
23707
  });
@@ -23516,7 +23721,11 @@ function createRemoteMemoryPipeline(options) {
23516
23721
  }
23517
23722
  };
23518
23723
  const [primary, facetEmbeddings] = await Promise.all([
23519
- search.queryEmbedding ?? embed([query]).then((vectors2) => vectors2[0]),
23724
+ search.queryEmbedding ?? (mode === "recall" && queryOptions.cache ? generateEmbedding(query, {
23725
+ ...queryOptions,
23726
+ taskType: "memory_recall_embed",
23727
+ totalTimeoutMs: budgetMs
23728
+ }) : embed([query]).then((vectors2) => vectors2[0])),
23520
23729
  embedFacets()
23521
23730
  ]);
23522
23731
  const queryEmbedding = storedVector(primary);
@@ -23529,7 +23738,7 @@ function createRemoteMemoryPipeline(options) {
23529
23738
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
23530
23739
  }
23531
23740
  const forceIds = [...forced];
23532
- const windowOptions = (i, signal) => ({
23741
+ const windowOptions = (i, signal2) => ({
23533
23742
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
23534
23743
  force_ids: i === 0 ? forceIds : [],
23535
23744
  ...search.scopes && { scopes: search.scopes },
@@ -23537,13 +23746,15 @@ function createRemoteMemoryPipeline(options) {
23537
23746
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
23538
23747
  include_archived: search.includeArchived,
23539
23748
  embedding_model: model,
23540
- ...signal && { signal }
23749
+ ...signal2 && { signal: signal2 }
23541
23750
  });
23542
23751
  const vectors = [queryEmbedding, ...facetEmbeddings];
23543
23752
  let windows;
23544
23753
  if (mode === "retain") {
23545
23754
  windows = await Promise.all(
23546
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
23755
+ vectors.map(
23756
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
23757
+ )
23547
23758
  );
23548
23759
  } else {
23549
23760
  const deadline = new AbortController();
@@ -23631,12 +23842,15 @@ function createRemoteMemoryPipeline(options) {
23631
23842
  twin_opt_in: false
23632
23843
  };
23633
23844
  };
23634
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
23845
+ const write = async (memory, snapshot) => {
23846
+ await assertWrite();
23847
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
23848
+ };
23635
23849
  const port = {
23636
23850
  prepare,
23637
23851
  get,
23638
23852
  getFresh: async (id) => {
23639
- const current = await persistence.get(id);
23853
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
23640
23854
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
23641
23855
  },
23642
23856
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -23647,7 +23861,8 @@ function createRemoteMemoryPipeline(options) {
23647
23861
  embedding_model: embeddingModel,
23648
23862
  strict_model: true,
23649
23863
  include_deleted: true,
23650
- deleted_only: true
23864
+ deleted_only: true,
23865
+ signal
23651
23866
  });
23652
23867
  rejectUndecryptable(window2.failed);
23653
23868
  return remember(window2.items);
@@ -23703,14 +23918,18 @@ function createRemoteMemoryPipeline(options) {
23703
23918
  if (!target || !newer || id === successor) return false;
23704
23919
  const old = snapshots.get(id);
23705
23920
  const next = snapshots.get(successor);
23921
+ await assertWrite();
23706
23922
  remember(
23707
- await persistence.putMany([
23708
- {
23709
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
23710
- expectedVersion: old
23711
- },
23712
- { memory: next.memory, expectedVersion: next }
23713
- ])
23923
+ await persistence.putMany(
23924
+ [
23925
+ {
23926
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
23927
+ expectedVersion: old
23928
+ },
23929
+ { memory: next.memory, expectedVersion: next }
23930
+ ],
23931
+ signal
23932
+ )
23714
23933
  );
23715
23934
  return true;
23716
23935
  },
@@ -23719,18 +23938,22 @@ function createRemoteMemoryPipeline(options) {
23719
23938
  if (!target) return { created: null, retired: false };
23720
23939
  const snapshot = snapshots.get(targetId);
23721
23940
  const successor = createRow(input);
23941
+ await assertWrite();
23722
23942
  const rows = remember(
23723
- await persistence.putMany([
23724
- { memory: successor, expectedVersion: 0 },
23725
- {
23726
- memory: {
23727
- ...snapshot.memory,
23728
- superseded_by: successor.memory_id,
23729
- superseded_at: Date.now()
23730
- },
23731
- expectedVersion: snapshot
23732
- }
23733
- ])
23943
+ await persistence.putMany(
23944
+ [
23945
+ { memory: successor, expectedVersion: 0 },
23946
+ {
23947
+ memory: {
23948
+ ...snapshot.memory,
23949
+ superseded_by: successor.memory_id,
23950
+ superseded_at: Date.now()
23951
+ },
23952
+ expectedVersion: snapshot
23953
+ }
23954
+ ],
23955
+ signal
23956
+ )
23734
23957
  );
23735
23958
  return { created: rows[0], retired: true };
23736
23959
  }
@@ -23740,15 +23963,17 @@ function createRemoteMemoryPipeline(options) {
23740
23963
  const factSource = {
23741
23964
  graphRanking: options.graphRanking,
23742
23965
  temporalRanking: options.temporalRanking,
23743
- search: async (text14, searchOptions) => {
23744
- const { prepare } = operation("recall");
23966
+ search: async (text14, searchOptions, embeddingCache) => {
23967
+ const queryOptions = {
23968
+ ...embeddingOptions,
23969
+ taskType: "memory_recall_embed",
23970
+ ...embeddingCache && !embeddingOptions.cache && {
23971
+ cache: maskScopedEmbeddingCache(embeddingCache, Boolean(embeddingOptions.maskInput))
23972
+ }
23973
+ };
23974
+ const { prepare } = operation("recall", queryOptions);
23745
23975
  const prepared = await prepare(text14, searchOptions);
23746
- const ranked = await rankPreparedVaultCandidates(
23747
- text14,
23748
- prepared,
23749
- { ...embeddingOptions, taskType: "memory_recall_embed" },
23750
- searchOptions
23751
- );
23976
+ const ranked = await rankPreparedVaultCandidates(text14, prepared, queryOptions, searchOptions);
23752
23977
  return {
23753
23978
  ...ranked,
23754
23979
  rankedOnCosine: prepared.memories.length > 0 && !prepared.embeddingsUnavailable,
@@ -23763,7 +23988,7 @@ function createRemoteMemoryPipeline(options) {
23763
23988
  factSource,
23764
23989
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
23765
23990
  retain: async (content, retainOptions) => {
23766
- const { port } = operation("retain");
23991
+ const { port } = operation("retain", embeddingOptions, retainOptions);
23767
23992
  return retainWithPersistence(
23768
23993
  content,
23769
23994
  {
@@ -24346,16 +24571,21 @@ function createRemoteMemoryStore(options) {
24346
24571
  const record2 = await persistence.get(id);
24347
24572
  return record2 && !record2.memory.is_deleted ? record2 : null;
24348
24573
  };
24349
- const mutateLocked = async (id, change, edit) => {
24350
- const record2 = await live(id);
24574
+ const mutateLocked = async (id, change, edit, writeOptions) => {
24575
+ writeOptions?.signal?.throwIfAborted();
24576
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
24351
24577
  if (!record2) return null;
24578
+ if (record2.memory.is_deleted) return null;
24579
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
24580
+ throw new Error("Memory source is no longer eligible");
24581
+ writeOptions?.signal?.throwIfAborted();
24352
24582
  const next = edit(structuredClone(record2.memory));
24353
24583
  if (!next) return null;
24354
- const saved = await persistence.put(next, record2);
24584
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
24355
24585
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
24356
24586
  return saved;
24357
24587
  };
24358
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
24588
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
24359
24589
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
24360
24590
  const mutateMany = async (ids, change, edit) => {
24361
24591
  const saved = /* @__PURE__ */ new Map();
@@ -24868,13 +25098,21 @@ function createRemoteMemoryStore(options) {
24868
25098
  }))
24869
25099
  );
24870
25100
  },
24871
- addTopics: async (memoryId, topics) => {
25101
+ addTopics: async (memoryId, topics, writeOptions) => {
24872
25102
  const added = await withKnownKinds(topicInputs(topics, "auto"));
24873
- const saved = await mutate(memoryId, "edit", (memory) => ({
24874
- ...memory,
24875
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
24876
- topics_updated_at: Date.now()
24877
- }));
25103
+ const saved = await mutate(
25104
+ memoryId,
25105
+ "edit",
25106
+ (memory) => {
25107
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
25108
+ return {
25109
+ ...memory,
25110
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
25111
+ topics_updated_at: Date.now()
25112
+ };
25113
+ },
25114
+ writeOptions
25115
+ );
24878
25116
  if (!saved) return [];
24879
25117
  const now = /* @__PURE__ */ new Date();
24880
25118
  return mergeTopics([], added).map(
@@ -25727,6 +25965,7 @@ function createDurableAutoExtractor(options) {
25727
25965
  };
25728
25966
  const folderId = options.folderId ?? null;
25729
25967
  let disposed = false;
25968
+ let activeBatch = null;
25730
25969
  let running = false;
25731
25970
  let wakeRequested = false;
25732
25971
  let timer;
@@ -25763,14 +26002,18 @@ function createDurableAutoExtractor(options) {
25763
26002
  }, delay);
25764
26003
  }
25765
26004
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
26005
+ if (disposed) throw new Error("Extraction session was disposed");
25766
26006
  let watchdog;
25767
26007
  let cancelled = false;
26008
+ const controller = new AbortController();
26009
+ activeBatch = controller;
25768
26010
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
25769
26011
  try {
25770
26012
  return await Promise.race([
25771
26013
  new Promise((resolve, reject) => {
25772
26014
  const worker = createAutoExtractor({
25773
26015
  ...options,
26016
+ signal: controller.signal,
25774
26017
  retainCtx: {
25775
26018
  ...options.retainCtx,
25776
26019
  vaultCtx: {
@@ -25799,6 +26042,8 @@ function createDurableAutoExtractor(options) {
25799
26042
  ]);
25800
26043
  } finally {
25801
26044
  cancelled = true;
26045
+ controller.abort();
26046
+ if (activeBatch === controller) activeBatch = null;
25802
26047
  if (watchdog !== void 0) clearTimeout(watchdog);
25803
26048
  }
25804
26049
  }
@@ -25810,6 +26055,7 @@ function createDurableAutoExtractor(options) {
25810
26055
  for (const message of loaded)
25811
26056
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
25812
26057
  await database.write(async () => {
26058
+ if (disposed) return;
25813
26059
  const current = idsOf(job);
25814
26060
  const acknowledged = ids.filter((id) => current.includes(id));
25815
26061
  if (!acknowledged.length) return;
@@ -25982,6 +26228,7 @@ function createDurableAutoExtractor(options) {
25982
26228
  jobScope,
25983
26229
  jobFolderId
25984
26230
  );
26231
+ if (disposed) throw new Error("Extraction session was disposed");
25985
26232
  options.onTurnComplete?.(result);
25986
26233
  return result;
25987
26234
  };
@@ -26018,6 +26265,7 @@ function createDurableAutoExtractor(options) {
26018
26265
  );
26019
26266
  scheduleAfterSuccess = true;
26020
26267
  } catch (error) {
26268
+ if (disposed) break;
26021
26269
  attempts.set(job.id, attempted + 1);
26022
26270
  retry = true;
26023
26271
  reportError(error, conversationId);
@@ -26040,6 +26288,14 @@ function createDurableAutoExtractor(options) {
26040
26288
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
26041
26289
  }
26042
26290
  }
26291
+ function dispose() {
26292
+ disposed = true;
26293
+ activeBatch?.abort(options.signal?.reason);
26294
+ if (timer !== void 0) clearTimeout(timer);
26295
+ options.signal?.removeEventListener("abort", dispose);
26296
+ }
26297
+ if (options.signal?.aborted) dispose();
26298
+ else options.signal?.addEventListener("abort", dispose, { once: true });
26043
26299
  schedule(0);
26044
26300
  return {
26045
26301
  processTurn(messages, conversationId) {
@@ -26119,10 +26375,7 @@ function createDurableAutoExtractor(options) {
26119
26375
  return true;
26120
26376
  },
26121
26377
  isProcessing: () => running,
26122
- dispose() {
26123
- disposed = true;
26124
- if (timer !== void 0) clearTimeout(timer);
26125
- }
26378
+ dispose
26126
26379
  };
26127
26380
  }
26128
26381
 
@@ -32392,6 +32645,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
32392
32645
  var NO_CONVERSATION_KEY = "__no_conversation__";
32393
32646
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
32394
32647
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
32648
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
32395
32649
  var conversationRedactors = /* @__PURE__ */ new Map();
32396
32650
  function getConversationRedactor(conversationId, nerDetector) {
32397
32651
  const key = conversationId ?? NO_CONVERSATION_KEY;
@@ -32482,7 +32736,7 @@ function useChatStorage(options) {
32482
32736
  () => ({ getToken, baseUrl, model: embeddingModel, maskInput: maskEmbeddingInput }),
32483
32737
  [getToken, baseUrl, embeddingModel, maskEmbeddingInput]
32484
32738
  );
32485
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
32739
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
32486
32740
  const blobManagerRef = (0, import_react6.useRef)(new BlobUrlManager());
32487
32741
  (0, import_react6.useEffect)(() => {
32488
32742
  const manager = blobManagerRef.current;
@@ -32759,6 +33013,7 @@ function useChatStorage(options) {
32759
33013
  );
32760
33014
  const retainVaultMemory = (0, import_react6.useCallback)(
32761
33015
  async (input) => {
33016
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32762
33017
  if (memoryStore) {
32763
33018
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32764
33019
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -32790,6 +33045,7 @@ function useChatStorage(options) {
32790
33045
  );
32791
33046
  const createMemoryVaultTool2 = (0, import_react6.useCallback)(
32792
33047
  (options2) => {
33048
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32793
33049
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32794
33050
  const embOpts = getToken ? vaultEmbeddingOptions : void 0;
32795
33051
  return createMemoryVaultTool(
@@ -32811,6 +33067,7 @@ function useChatStorage(options) {
32811
33067
  );
32812
33068
  const getVaultMemories = (0, import_react6.useCallback)(
32813
33069
  (options2) => {
33070
+ if (memoryStore === null) return Promise.resolve([]);
32814
33071
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
32815
33072
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
32816
33073
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -32822,6 +33079,7 @@ function useChatStorage(options) {
32822
33079
  );
32823
33080
  const createVaultMemory = (0, import_react6.useCallback)(
32824
33081
  async (content, scope) => {
33082
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32825
33083
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32826
33084
  if (memoryStore) return memoryStore.create({ content, scope });
32827
33085
  const result = await createVaultMemoryOp(vaultCtx, { content, scope });
@@ -32843,6 +33101,7 @@ function useChatStorage(options) {
32843
33101
  );
32844
33102
  const updateVaultMemory = (0, import_react6.useCallback)(
32845
33103
  async (id, content, scope) => {
33104
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32846
33105
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32847
33106
  if (memoryStore) return memoryStore.update(id, { content, scope });
32848
33107
  const existing = await getVaultMemoryOp(vaultCtx, id);
@@ -32868,6 +33127,7 @@ function useChatStorage(options) {
32868
33127
  );
32869
33128
  const deleteVaultMemory = (0, import_react6.useCallback)(
32870
33129
  async (id) => {
33130
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32871
33131
  if (memoryStore) return memoryStore.delete(id);
32872
33132
  const existing = await getVaultMemoryOp(vaultCtx, id);
32873
33133
  const result = await deleteVaultMemoryOp(vaultCtx, id);
@@ -32881,7 +33141,7 @@ function useChatStorage(options) {
32881
33141
  const chunkVectorCacheRef = (0, import_react6.useRef)(createChunkVectorCache());
32882
33142
  const clientToolEmbeddingsCacheRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
32883
33143
  (0, import_react6.useEffect)(() => {
32884
- if (!getToken || memoryStore) return;
33144
+ if (!getToken || memoryStore !== void 0) return;
32885
33145
  void (async () => {
32886
33146
  try {
32887
33147
  await preEmbedVaultMemories(vaultCtx, vaultEmbeddingOptions, vaultEmbeddingCache);
@@ -32897,6 +33157,7 @@ function useChatStorage(options) {
32897
33157
  }, [vaultEmbeddingCache]);
32898
33158
  const createMemoryVaultSearchTool2 = (0, import_react6.useCallback)(
32899
33159
  (searchOptions) => {
33160
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32900
33161
  if (!getToken && !memoryStore) {
32901
33162
  throw new Error("getToken is required for memory vault search tool");
32902
33163
  }
@@ -32912,6 +33173,7 @@ function useChatStorage(options) {
32912
33173
  );
32913
33174
  const createRecallTool2 = (0, import_react6.useCallback)(
32914
33175
  (toolOptions, callbacks) => {
33176
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32915
33177
  if (!getToken && !memoryStore) {
32916
33178
  throw new Error("getToken is required for recall tool");
32917
33179
  }
@@ -32951,6 +33213,7 @@ function useChatStorage(options) {
32951
33213
  );
32952
33214
  const searchVaultMemoriesFn = (0, import_react6.useCallback)(
32953
33215
  async (query, searchOptions) => {
33216
+ if (memoryStore === null) return [];
32954
33217
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32955
33218
  if (memoryStore) return (await memoryStore.factSource.search(query, searchOptions)).results;
32956
33219
  if (!getToken) return [];
@@ -32966,7 +33229,7 @@ function useChatStorage(options) {
32966
33229
  );
32967
33230
  const recallFn = (0, import_react6.useCallback)(
32968
33231
  async (query, options2) => {
32969
- if (!getToken && !memoryStore) {
33232
+ if (memoryStore === null || !getToken && !memoryStore) {
32970
33233
  return {
32971
33234
  memories: [],
32972
33235
  usedBudget: options2?.budget ?? "low",