@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.
@@ -14928,6 +14928,10 @@ var require_stepsForMigration = __commonJS({
14928
14928
  }
14929
14929
  });
14930
14930
 
14931
+ // src/lib/chat/toolResultMessage.ts
14932
+ var FOOTER = "\n\nBased on these results, continue with the task.";
14933
+ var TOOL_RESULT_FOOTER_LINE = FOOTER.trim();
14934
+
14931
14935
  // src/lib/tools/toolCatalog.ts
14932
14936
  var TOOL_CATALOG = {
14933
14937
  gmail_search_messages: { label: "Search emails", provider: "gmail", connector: "Gmail" },
@@ -15104,7 +15108,32 @@ ${neutralised}
15104
15108
  }
15105
15109
 
15106
15110
  // src/lib/chat/toolResults.ts
15111
+ var TOOL_RESULTS_PREFIX = "[Tool Execution Results]";
15112
+ var TOOL_LINE = /^Tool "([^"]+)" returned: (.*)$/;
15107
15113
  var TOOL_RESULT_ORIGIN = "tool_result";
15114
+ function isToolResultsRow(row) {
15115
+ if (row.origin === TOOL_RESULT_ORIGIN) return true;
15116
+ if (typeof row.origin === "string") return false;
15117
+ if (row.role !== "user" || !row.content.startsWith(TOOL_RESULTS_PREFIX)) return false;
15118
+ return parseToolResultSegments(row.content).length > 0;
15119
+ }
15120
+ function parseToolResultSegments(content) {
15121
+ const segments = [];
15122
+ for (const raw of content.split("\n")) {
15123
+ const line = raw.trim();
15124
+ const match = TOOL_LINE.exec(line);
15125
+ if (match) {
15126
+ segments.push({ name: match[1], line });
15127
+ continue;
15128
+ }
15129
+ const current = segments[segments.length - 1];
15130
+ if (!current || line === "" || line === TOOL_RESULT_FOOTER_LINE) continue;
15131
+ if (line === TOOL_RESULTS_PREFIX) continue;
15132
+ current.line = `${current.line}
15133
+ ${line}`;
15134
+ }
15135
+ return segments;
15136
+ }
15108
15137
 
15109
15138
  // src/lib/db/chat/operations.ts
15110
15139
  var import_watermelondb2 = __toESM(require_watermelondb());
@@ -16720,8 +16749,9 @@ async function getConversationsByProjectOp(ctx, projectId) {
16720
16749
  )
16721
16750
  );
16722
16751
  }
16723
- async function getMessagesOp(ctx, convId) {
16724
- const results = await ctx.messagesCollection.query(import_watermelondb2.Q.where("conversation_id", convId), import_watermelondb2.Q.sortBy("message_id", import_watermelondb2.Q.asc)).unsafeFetchRaw();
16752
+ async function getMessagesOp(ctx, convId, opts) {
16753
+ const fetched = await ctx.messagesCollection.query(import_watermelondb2.Q.where("conversation_id", convId), import_watermelondb2.Q.sortBy("message_id", import_watermelondb2.Q.asc)).unsafeFetchRaw();
16754
+ const results = opts?.rawFilter ? fetched.filter(opts.rawFilter) : fetched;
16725
16755
  return Promise.all(
16726
16756
  results.map(
16727
16757
  (raw) => messageRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
@@ -18448,6 +18478,22 @@ var postApiV1Embeddings = (options) => (options.client ?? client).post({
18448
18478
  }
18449
18479
  });
18450
18480
 
18481
+ // src/lib/memoryEngine/embeddingCacheState.ts
18482
+ var EMBEDDING_CACHE_STATE_KEY = /* @__PURE__ */ Symbol.for("@anuma/sdk/embedding-cache/v1");
18483
+ var cacheGlobal = globalThis;
18484
+ var fallbackState = {
18485
+ scopedViews: /* @__PURE__ */ new WeakMap(),
18486
+ inFlightByCache: /* @__PURE__ */ new WeakMap(),
18487
+ cachedEmbeddingModels: /* @__PURE__ */ new WeakMap()
18488
+ };
18489
+ function getEmbeddingCacheState() {
18490
+ try {
18491
+ return cacheGlobal[EMBEDDING_CACHE_STATE_KEY] ?? (cacheGlobal[EMBEDDING_CACHE_STATE_KEY] = fallbackState);
18492
+ } catch {
18493
+ return fallbackState;
18494
+ }
18495
+ }
18496
+
18451
18497
  // src/lib/memoryEngine/generate.ts
18452
18498
  var EMBED_MAX_ATTEMPTS = 4;
18453
18499
  var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
@@ -18539,15 +18585,18 @@ async function generateEmbedding(text12, options) {
18539
18585
  if (total === void 0) return embedOne(text12, options, void 0);
18540
18586
  return withDeadline((signal) => embedOne(text12, options, signal), total, "embedding");
18541
18587
  }
18542
- var inFlightByCache = /* @__PURE__ */ new WeakMap();
18588
+ var { inFlightByCache, cachedEmbeddingModels } = getEmbeddingCacheState();
18543
18589
  async function embedOne(text12, options, outer) {
18544
18590
  const { cache } = options;
18545
18591
  if (!cache) return Array.from(await requestEmbedding(text12, options, outer));
18592
+ const model = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
18546
18593
  const cached = cache.get(text12);
18547
- if (cached) return Array.from(cached);
18594
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
18595
+ if (cached && (cachedModel === void 0 || cachedModel === model)) return Array.from(cached);
18548
18596
  const inFlight = inFlightByCache.get(cache) ?? /* @__PURE__ */ new Map();
18549
18597
  inFlightByCache.set(cache, inFlight);
18550
- const pending = inFlight.get(text12);
18598
+ const requestKey = JSON.stringify([model, text12]);
18599
+ const pending = inFlight.get(requestKey);
18551
18600
  if (pending) {
18552
18601
  try {
18553
18602
  return Array.from(await pending);
@@ -18556,13 +18605,14 @@ async function embedOne(text12, options, outer) {
18556
18605
  }
18557
18606
  }
18558
18607
  const request = requestEmbedding(text12, options, outer);
18559
- inFlight.set(text12, request);
18608
+ inFlight.set(requestKey, request);
18560
18609
  try {
18561
18610
  const embedding = await request;
18611
+ cachedEmbeddingModels.set(embedding, model);
18562
18612
  cache.set(text12, embedding);
18563
18613
  return Array.from(embedding);
18564
18614
  } finally {
18565
- if (inFlight.get(text12) === request) inFlight.delete(text12);
18615
+ if (inFlight.get(requestKey) === request) inFlight.delete(requestKey);
18566
18616
  }
18567
18617
  }
18568
18618
  async function requestEmbedding(text12, options, outer) {
@@ -18645,13 +18695,15 @@ async function embedMany(texts, options, outer) {
18645
18695
  const { baseUrl = BASE_URL, model, batchSize, cache } = options;
18646
18696
  const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
18647
18697
  const chunkSize = batchSize ?? DEFAULT_EMBEDDING_BATCH_SIZE;
18698
+ const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
18648
18699
  const results = new Array(texts.length).fill(null);
18649
18700
  const uncachedIndices = [];
18650
18701
  const uncachedTexts = [];
18651
18702
  for (let i = 0; i < texts.length; i++) {
18652
18703
  if (cache) {
18653
18704
  const cached = cache.get(texts[i]);
18654
- if (cached) {
18705
+ const cachedModel = cached && cachedEmbeddingModels.get(cached);
18706
+ if (cached && (cachedModel === void 0 || cachedModel === embeddingModel)) {
18655
18707
  results[i] = Array.from(cached);
18656
18708
  continue;
18657
18709
  }
@@ -18667,7 +18719,6 @@ async function embedMany(texts, options, outer) {
18667
18719
  ...taskTypeHeader(options.taskType)
18668
18720
  };
18669
18721
  if (outer?.aborted) throw outer.reason;
18670
- const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
18671
18722
  let newEmbeddings;
18672
18723
  if (uncachedTexts.length <= chunkSize) {
18673
18724
  newEmbeddings = await generateEmbeddingsBatch(
@@ -18715,6 +18766,7 @@ async function embedMany(texts, options, outer) {
18715
18766
  const f32Embedding = Float32Array.from(newEmbeddings[i]);
18716
18767
  results[uncachedIndices[i]] = Array.from(f32Embedding);
18717
18768
  if (cache) {
18769
+ cachedEmbeddingModels.set(f32Embedding, embeddingModel);
18718
18770
  cache.set(uncachedTexts[i], f32Embedding);
18719
18771
  }
18720
18772
  }
@@ -18839,6 +18891,37 @@ async function chunkAndEmbedMessage(ctx, messageId, options) {
18839
18891
  }));
18840
18892
  return updateMessageChunksOp(ctx, messageId, messageChunks, embeddingModel);
18841
18893
  }
18894
+ var MIN_SEALED_EMBEDDING_HEX = 256;
18895
+ function rawHasEmbedding(value) {
18896
+ if (Array.isArray(value)) return value.length > 0;
18897
+ if (typeof value !== "string" || value.length === 0) return false;
18898
+ if (isEncrypted(value)) {
18899
+ return value.length - value.indexOf(":", 4) - 1 >= MIN_SEALED_EMBEDDING_HEX;
18900
+ }
18901
+ try {
18902
+ const parsed = JSON.parse(value);
18903
+ return Array.isArray(parsed) && parsed.length > 0;
18904
+ } catch {
18905
+ return false;
18906
+ }
18907
+ }
18908
+ function sealedVersion(value) {
18909
+ if (typeof value !== "string" || !isEncrypted(value)) return null;
18910
+ return value.startsWith("enc:v2:") ? "v2" : "v3";
18911
+ }
18912
+ function mayNeedChunkEmbedding(raw, embeddingModel, filter) {
18913
+ const role = raw.role;
18914
+ if (role === "system") return false;
18915
+ if (filter?.roles && !filter.roles.includes(role ?? "")) return false;
18916
+ if (isNonEmbeddableOrigin(raw.origin, filter?.reembedDiscarded)) {
18917
+ return false;
18918
+ }
18919
+ const model = raw.embedding_model;
18920
+ if (typeof model === "string" && model !== embeddingModel) return true;
18921
+ if (rawHasEmbedding(raw.chunks)) return false;
18922
+ if (rawHasEmbedding(raw.vector) && !filter?.rechunkExisting) return false;
18923
+ return true;
18924
+ }
18842
18925
  async function chunkAndEmbedAllMessages(ctx, options, filter) {
18843
18926
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
18844
18927
  const { chunkSize = DEFAULT_CHUNK_SIZE } = options;
@@ -18851,11 +18934,31 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
18851
18934
  let considered = 0;
18852
18935
  let sealedRowsSeen = 0;
18853
18936
  let rowsSeen = 0;
18937
+ const keptSealed = /* @__PURE__ */ new Map();
18938
+ const candidatesStillSealed = /* @__PURE__ */ new Map();
18939
+ const droppedSealed = /* @__PURE__ */ new Map();
18940
+ const rawFilter = (raw) => {
18941
+ rowsSeen++;
18942
+ const keep = mayNeedChunkEmbedding(raw, embeddingModel, filter);
18943
+ const version = sealedVersion(raw.content);
18944
+ if (!version) return keep;
18945
+ if (keep) {
18946
+ keptSealed.set(version, (keptSealed.get(version) ?? 0) + 1);
18947
+ } else {
18948
+ const dropped = droppedSealed.get(version);
18949
+ if (dropped) dropped.count++;
18950
+ else droppedSealed.set(version, { count: 1, probeId: raw.id });
18951
+ }
18952
+ return keep;
18953
+ };
18854
18954
  for (const conv of targetConversations) {
18855
- const messages = await getMessagesOp(ctx, conv.conversationId);
18955
+ const messages = await getMessagesOp(ctx, conv.conversationId, { rawFilter });
18856
18956
  for (const message of messages) {
18857
- rowsSeen++;
18858
- if (isEncrypted(message.content)) sealedRowsSeen++;
18957
+ const stillSealed = sealedVersion(message.content);
18958
+ if (stillSealed) {
18959
+ sealedRowsSeen++;
18960
+ candidatesStillSealed.set(stillSealed, (candidatesStillSealed.get(stillSealed) ?? 0) + 1);
18961
+ }
18859
18962
  const isStale = message.embeddingModel !== void 0 && message.embeddingModel !== null && message.embeddingModel !== embeddingModel;
18860
18963
  if (message.chunks && message.chunks.length > 0 && !isStale) continue;
18861
18964
  const hasVector = message.vector && message.vector.length > 0;
@@ -18868,6 +18971,7 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
18868
18971
  stillEncrypted++;
18869
18972
  continue;
18870
18973
  }
18974
+ if (isToolResultsRow(message)) continue;
18871
18975
  if (message.content.length < minLength) continue;
18872
18976
  if (shouldChunkMessage(message.content, chunkSize)) {
18873
18977
  longMessages.push({
@@ -18879,6 +18983,12 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
18879
18983
  }
18880
18984
  }
18881
18985
  }
18986
+ for (const [version, dropped] of droppedSealed) {
18987
+ const keyOpenedACandidate = (keptSealed.get(version) ?? 0) > (candidatesStillSealed.get(version) ?? 0);
18988
+ if (keyOpenedACandidate) continue;
18989
+ const probe = await getMessageOp(ctx, dropped.probeId);
18990
+ if (!probe || isEncrypted(probe.content)) sealedRowsSeen += dropped.count;
18991
+ }
18882
18992
  if (stillEncrypted > 0 || sealedRowsSeen > 0) {
18883
18993
  getLogger().error(
18884
18994
  "memoryEngine: messages still encrypted (key unavailable?) \u2014 excluded from embedding",
@@ -26030,6 +26140,11 @@ function parseTopics(value) {
26030
26140
  }
26031
26141
 
26032
26142
  // src/lib/db/entities/operations.ts
26143
+ async function assertCanWrite(ctx) {
26144
+ if (ctx.canWrite && !await ctx.canWrite()) {
26145
+ throw new Error("Memory source is no longer eligible");
26146
+ }
26147
+ }
26033
26148
  function entityToStored(e) {
26034
26149
  return {
26035
26150
  uniqueId: e.id,
@@ -26170,6 +26285,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
26170
26285
  let skipped = false;
26171
26286
  let entities = [];
26172
26287
  await ctx.database.write(async () => {
26288
+ await assertCanWrite(ctx);
26173
26289
  const {
26174
26290
  entities: byName,
26175
26291
  operations: entityOps,
@@ -26245,6 +26361,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
26245
26361
  let skipped = false;
26246
26362
  let entities = [];
26247
26363
  await ctx.database.write(async () => {
26364
+ await assertCanWrite(ctx);
26248
26365
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
26249
26366
  if (options.guarded && autoLinkBlocked(vaultRow)) {
26250
26367
  skipped = true;
@@ -26305,6 +26422,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
26305
26422
  const links = await ctx.memoryEntityCollection.query(import_watermelondb18.Q.where("memory_id", import_watermelondb18.Q.oneOf(Array.from(memoryIds)))).fetch();
26306
26423
  if (links.length === 0) return;
26307
26424
  await ctx.database.write(async () => {
26425
+ await assertCanWrite(ctx);
26308
26426
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
26309
26427
  });
26310
26428
  }
@@ -26313,6 +26431,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
26313
26431
  const links = await ctx.memoryEntityCollection.query(import_watermelondb18.Q.where("user_id", userId)).fetch();
26314
26432
  if (links.length === 0) return;
26315
26433
  await ctx.database.write(async () => {
26434
+ await assertCanWrite(ctx);
26316
26435
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
26317
26436
  });
26318
26437
  }
@@ -26476,6 +26595,11 @@ function visibilityConditions(requested) {
26476
26595
  function isKinded(record) {
26477
26596
  return record.kind !== null && record.kind !== void 0;
26478
26597
  }
26598
+ async function assertCanWrite2(ctx) {
26599
+ if (ctx.canWrite && !await ctx.canWrite()) {
26600
+ throw new Error("Memory source is no longer eligible");
26601
+ }
26602
+ }
26479
26603
  function assertValidKindLevel(kind, level) {
26480
26604
  const hasKind = kind !== null && kind !== void 0;
26481
26605
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -26656,8 +26780,7 @@ async function createVaultMemoryOp(ctx, opts) {
26656
26780
  ctx.embeddedWalletSigner
26657
26781
  ) : opts.content;
26658
26782
  const created = await ctx.database.write(async () => {
26659
- if (ctx.canWrite && !await ctx.canWrite())
26660
- throw new Error("Memory source is no longer eligible");
26783
+ await assertCanWrite2(ctx);
26661
26784
  return ctx.vaultMemoryCollection.create(
26662
26785
  (record) => populateNewVaultMemory(record, ctx, opts, encryptedContent, encryptedKindValue)
26663
26786
  );
@@ -26676,8 +26799,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
26676
26799
  ) : opts.content;
26677
26800
  let createdRecord = null;
26678
26801
  await ctx.database.write(async () => {
26679
- if (ctx.canWrite && !await ctx.canWrite())
26680
- throw new Error("Memory source is no longer eligible");
26802
+ await assertCanWrite2(ctx);
26681
26803
  let target;
26682
26804
  try {
26683
26805
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -26769,8 +26891,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
26769
26891
  })
26770
26892
  );
26771
26893
  const created = await ctx.database.write(async () => {
26772
- if (ctx.canWrite && !await ctx.canWrite())
26773
- throw new Error("Memory source is no longer eligible");
26894
+ await assertCanWrite2(ctx);
26774
26895
  const prepared = optionsArray.map(
26775
26896
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
26776
26897
  (record) => populateNewVaultMemory(record, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -27061,8 +27182,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
27061
27182
  const record = probe;
27062
27183
  const originalUpdatedAt = record.updatedAt.getTime();
27063
27184
  await ctx.database.write(async () => {
27064
- if (ctx.canWrite && !await ctx.canWrite())
27065
- throw new Error("Memory source is no longer eligible");
27185
+ await assertCanWrite2(ctx);
27066
27186
  if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record) || opts.freeFormOnly && isKinded(record)) {
27067
27187
  stale = true;
27068
27188
  return;
@@ -27173,6 +27293,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
27173
27293
  let stale = false;
27174
27294
  const originalUpdatedAt = record.updatedAt.getTime();
27175
27295
  await ctx.database.write(async (writer) => {
27296
+ await assertCanWrite2(ctx);
27176
27297
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
27177
27298
  stale = true;
27178
27299
  return;
@@ -27214,6 +27335,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
27214
27335
  const originalUpdatedAt = record.updatedAt.getTime();
27215
27336
  let cleared = false;
27216
27337
  await ctx.database.write(async () => {
27338
+ await assertCanWrite2(ctx);
27217
27339
  if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
27218
27340
  cleared = true;
27219
27341
  await record.update((r) => {
@@ -27237,6 +27359,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
27237
27359
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return null;
27238
27360
  let stale = false;
27239
27361
  await ctx.database.write(async () => {
27362
+ await assertCanWrite2(ctx);
27240
27363
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
27241
27364
  stale = true;
27242
27365
  return;
@@ -27265,6 +27388,7 @@ async function deleteVaultMemoryOp(ctx, id) {
27265
27388
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
27266
27389
  let stale = false;
27267
27390
  await ctx.database.write(async () => {
27391
+ await assertCanWrite2(ctx);
27268
27392
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
27269
27393
  stale = true;
27270
27394
  return;
@@ -27302,8 +27426,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
27302
27426
  }
27303
27427
  let stale = false;
27304
27428
  await ctx.database.write(async () => {
27305
- if (ctx.canWrite && !await ctx.canWrite())
27306
- throw new Error("Memory source is no longer eligible");
27429
+ await assertCanWrite2(ctx);
27307
27430
  if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record)) {
27308
27431
  stale = true;
27309
27432
  return;
@@ -27465,6 +27588,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
27465
27588
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
27466
27589
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
27467
27590
  await ctx.database.write(async () => {
27591
+ await assertCanWrite2(ctx);
27468
27592
  const records = [];
27469
27593
  for (const id of chunkIds) {
27470
27594
  try {
@@ -27498,6 +27622,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
27498
27622
  for (const id of Array.from(new Set(memoryIds))) {
27499
27623
  try {
27500
27624
  await ctx.database.write(async (writer) => {
27625
+ await assertCanWrite2(ctx);
27501
27626
  let record;
27502
27627
  try {
27503
27628
  record = await ctx.vaultMemoryCollection.find(id);
@@ -27528,6 +27653,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
27528
27653
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
27529
27654
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
27530
27655
  await ctx.database.write(async () => {
27656
+ await assertCanWrite2(ctx);
27531
27657
  const writes = [];
27532
27658
  for (const id of chunkIds) {
27533
27659
  let record;
@@ -27560,6 +27686,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
27560
27686
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb19.Q.where("user_id", userId), import_watermelondb19.Q.where("is_deleted", false)).fetch();
27561
27687
  if (records.length === 0) return 0;
27562
27688
  await ctx.database.write(async () => {
27689
+ await assertCanWrite2(ctx);
27563
27690
  const prepared = records.map(
27564
27691
  (record) => record.prepareUpdate((r) => {
27565
27692
  r._setRaw("is_deleted", true);
@@ -27614,6 +27741,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
27614
27741
  let stale = false;
27615
27742
  const archivedAtValue = opts?.now ?? Date.now();
27616
27743
  await ctx.database.write(async () => {
27744
+ await assertCanWrite2(ctx);
27617
27745
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.archivedAt !== null) {
27618
27746
  stale = true;
27619
27747
  return;
@@ -27637,6 +27765,7 @@ async function restoreVaultMemoryOp(ctx, id) {
27637
27765
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
27638
27766
  let stale = false;
27639
27767
  await ctx.database.write(async () => {
27768
+ await assertCanWrite2(ctx);
27640
27769
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
27641
27770
  stale = true;
27642
27771
  return;
@@ -27657,6 +27786,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
27657
27786
  const now = opts.now ?? Date.now();
27658
27787
  let stale = false;
27659
27788
  await ctx.database.write(async () => {
27789
+ await assertCanWrite2(ctx);
27660
27790
  const archivedAt = record.archivedAt;
27661
27791
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
27662
27792
  stale = true;
@@ -27696,6 +27826,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel,
27696
27826
  }
27697
27827
  let written = false;
27698
27828
  await ctx.database.write(async () => {
27829
+ await assertCanWrite2(ctx);
27699
27830
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return;
27700
27831
  const originalUpdatedAt = record.updatedAt.getTime();
27701
27832
  if (originalUpdatedAt !== readUpdatedAt || record._getRaw("content") !== readStoredContent)
@@ -31367,6 +31498,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
31367
31498
  ).map((c) => c.candidate);
31368
31499
  }
31369
31500
  async function extractAndRetain(messages, retainCtx, options) {
31501
+ const { memoryStore, signal } = options;
31502
+ if (memoryStore === null) throw new Error("Memory store is not ready");
31503
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
31504
+ throw new Error("Remote memory extraction does not support folders");
31505
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
31506
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
31507
+ signal?.throwIfAborted();
31370
31508
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
31371
31509
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
31372
31510
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -31400,6 +31538,7 @@ async function extractAndRetain(messages, retainCtx, options) {
31400
31538
  }
31401
31539
  });
31402
31540
  const extractMs = Date.now() - tExtract;
31541
+ signal?.throwIfAborted();
31403
31542
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
31404
31543
  const log = getLogger();
31405
31544
  const screened = screenCandidatesForInjection(filtered);
@@ -31447,16 +31586,33 @@ async function extractAndRetain(messages, retainCtx, options) {
31447
31586
  const results = [];
31448
31587
  const quarantinedInfo = [];
31449
31588
  let failedWrites = 0;
31589
+ let auditRows;
31450
31590
  const tRetain = Date.now();
31451
31591
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
31452
31592
  try {
31593
+ signal?.throwIfAborted();
31453
31594
  if (isQuarantined) {
31454
- const existingId = await findQuarantinedDuplicateOp(
31455
- retainCtx.vaultCtx,
31456
- candidate.content,
31457
- candidate.sourceMessageIds,
31458
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
31459
- ).catch(() => null);
31595
+ let existingId;
31596
+ if (memoryStore) {
31597
+ auditRows ?? (auditRows = memoryStore.list({
31598
+ scopes: [options.scope ?? "private"],
31599
+ includeArchived: true,
31600
+ includeQuarantined: true,
31601
+ includeSuperseded: true
31602
+ }));
31603
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
31604
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
31605
+ existingId = (await auditRows).find(
31606
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
31607
+ )?.uniqueId ?? null;
31608
+ } else {
31609
+ existingId = await findQuarantinedDuplicateOp(
31610
+ retainCtx.vaultCtx,
31611
+ candidate.content,
31612
+ candidate.sourceMessageIds,
31613
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
31614
+ ).catch(() => null);
31615
+ }
31460
31616
  if (existingId) {
31461
31617
  quarantinedInfo.push({
31462
31618
  candidate,
@@ -31467,18 +31623,26 @@ async function extractAndRetain(messages, retainCtx, options) {
31467
31623
  continue;
31468
31624
  }
31469
31625
  }
31470
- const result = await retain(candidate.content, retainCtx, {
31626
+ const retainOptions = {
31471
31627
  source: "auto-extracted",
31472
31628
  sourceChunkIds: candidate.sourceMessageIds,
31473
31629
  respectTombstones: true,
31474
31630
  ...options.scope !== void 0 && { scope: options.scope },
31475
- ...options.folderId !== void 0 && { folderId: options.folderId },
31476
31631
  ...consolidateOptions !== void 0 && { consolidateOptions },
31477
31632
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
31478
31633
  factType: candidate.type,
31479
31634
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
31635
+ };
31636
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
31637
+ ...retainOptions,
31638
+ canWrite: retainCtx.vaultCtx.canWrite,
31639
+ signal
31640
+ }) : await retain(candidate.content, retainCtx, {
31641
+ ...retainOptions,
31642
+ ...options.folderId !== void 0 && { folderId: options.folderId }
31480
31643
  });
31481
31644
  if (isQuarantined) {
31645
+ auditRows = void 0;
31482
31646
  const info = {
31483
31647
  candidate,
31484
31648
  memoryId: result.memoryId,
@@ -31497,9 +31661,16 @@ async function extractAndRetain(messages, retainCtx, options) {
31497
31661
  }
31498
31662
  succeededCandidates.push(candidate);
31499
31663
  results.push(result);
31500
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
31664
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
31501
31665
  try {
31502
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
31666
+ signal?.throwIfAborted();
31667
+ if (memoryStore) {
31668
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
31669
+ unlessUserManaged: true,
31670
+ canWrite: retainCtx.vaultCtx.canWrite,
31671
+ signal
31672
+ });
31673
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
31503
31674
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
31504
31675
  unlessTopicsUserManaged: true
31505
31676
  });
@@ -31747,7 +31918,7 @@ function createAutoExtractor(options) {
31747
31918
  };
31748
31919
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
31749
31920
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
31750
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
31921
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
31751
31922
  getLogger().warn(
31752
31923
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
31753
31924
  );
@@ -31792,6 +31963,8 @@ function createAutoExtractor(options) {
31792
31963
  try {
31793
31964
  const { candidates, results, failedCount, outcome, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
31794
31965
  extract,
31966
+ memoryStore: options.memoryStore,
31967
+ signal: options.signal,
31795
31968
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
31796
31969
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
31797
31970
  ...options.scope !== void 0 && { scope: options.scope },
@@ -32444,7 +32617,7 @@ var MaskScopedEmbeddingCache = class extends Map {
32444
32617
  return this;
32445
32618
  }
32446
32619
  };
32447
- var scopedViews = /* @__PURE__ */ new WeakMap();
32620
+ var { scopedViews } = getEmbeddingCacheState();
32448
32621
  function maskScopedEmbeddingCache(cache, masked) {
32449
32622
  const views = scopedViews.get(cache) ?? {};
32450
32623
  scopedViews.set(cache, views);
@@ -32866,44 +33039,48 @@ async function recall(query, ctx, options = {}) {
32866
33039
  embeddingOptions,
32867
33040
  ctx.vaultCache,
32868
33041
  searchOptions
32869
- )))(query, {
32870
- limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
32871
- minSimilarity: vaultMinScore,
32872
- useFusion: true,
32873
- rerank: flags.rerank,
32874
- ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
32875
- ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
32876
- ...options.rerankLoadTimeoutMs !== void 0 && {
32877
- rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
32878
- },
32879
- ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
32880
- ...options.recency && { recency: options.recency },
32881
- ...options.mmr !== void 0 && { mmr: options.mmr },
32882
- ...options.supersessionBoost !== void 0 && {
32883
- supersessionBoost: options.supersessionBoost
32884
- },
32885
- ...options.supersessionWindow !== void 0 && {
32886
- supersessionWindow: options.supersessionWindow
32887
- },
32888
- ...options.proofCountAlpha !== void 0 && {
32889
- proofCountAlpha: options.proofCountAlpha
32890
- },
32891
- ...options.bm25AdmissionDivisor !== void 0 && {
32892
- bm25AdmissionDivisor: options.bm25AdmissionDivisor
33042
+ )))(
33043
+ query,
33044
+ {
33045
+ limit: types.includes("chunk") ? Math.max(limit * 2, 16) : limit,
33046
+ minSimilarity: vaultMinScore,
33047
+ useFusion: true,
33048
+ rerank: flags.rerank,
33049
+ ...options.rerankTopN !== void 0 && { rerankTopN: options.rerankTopN },
33050
+ ...options.ceWeight !== void 0 && { ceWeight: options.ceWeight },
33051
+ ...options.rerankLoadTimeoutMs !== void 0 && {
33052
+ rerankLoadTimeoutMs: options.rerankLoadTimeoutMs
33053
+ },
33054
+ ...options.recencyAlpha !== void 0 && { recencyAlpha: options.recencyAlpha },
33055
+ ...options.recency && { recency: options.recency },
33056
+ ...options.mmr !== void 0 && { mmr: options.mmr },
33057
+ ...options.supersessionBoost !== void 0 && {
33058
+ supersessionBoost: options.supersessionBoost
33059
+ },
33060
+ ...options.supersessionWindow !== void 0 && {
33061
+ supersessionWindow: options.supersessionWindow
33062
+ },
33063
+ ...options.proofCountAlpha !== void 0 && {
33064
+ proofCountAlpha: options.proofCountAlpha
33065
+ },
33066
+ ...options.bm25AdmissionDivisor !== void 0 && {
33067
+ bm25AdmissionDivisor: options.bm25AdmissionDivisor
33068
+ },
33069
+ ...options.rrfK !== void 0 && { rrfK: options.rrfK },
33070
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
33071
+ ...subQueries && { subQueries },
33072
+ ...options.scopes && { scopes: options.scopes },
33073
+ ...options.folderId !== void 0 && { folderId: options.folderId },
33074
+ ...options.factTypes?.length && { factTypes: options.factTypes },
33075
+ ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
33076
+ ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
33077
+ ...entityRanking.length > 0 && { entityRanking },
33078
+ ...temporalRanking.length > 0 && { temporalRanking },
33079
+ ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
33080
+ queryEmbedTotalTimeoutMs
32893
33081
  },
32894
- ...options.rrfK !== void 0 && { rrfK: options.rrfK },
32895
- ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
32896
- ...subQueries && { subQueries },
32897
- ...options.scopes && { scopes: options.scopes },
32898
- ...options.folderId !== void 0 && { folderId: options.folderId },
32899
- ...options.factTypes?.length && { factTypes: options.factTypes },
32900
- ...options.memoryIds !== void 0 && { memoryIds: options.memoryIds },
32901
- ...options.factTypeWeights && { factTypeWeights: options.factTypeWeights },
32902
- ...entityRanking.length > 0 && { entityRanking },
32903
- ...temporalRanking.length > 0 && { temporalRanking },
32904
- ...needsChunkEmbedding && { queryEmbedding: queryEmbedding ?? [] },
32905
- queryEmbedTotalTimeoutMs
32906
- });
33082
+ options.embeddingCache
33083
+ );
32907
33084
  factResults.push(
32908
33085
  ...dedupeBy(
32909
33086
  results,
@@ -33900,6 +34077,7 @@ function createLocalMemoryStore(options) {
33900
34077
  database,
33901
34078
  entityCollection: database.get("entity"),
33902
34079
  memoryEntityCollection: database.get("memory_entity"),
34080
+ canWrite: options.canWrite,
33903
34081
  ...userId !== void 0 && { userId },
33904
34082
  ...options.allowUnscopedRows !== void 0 && {
33905
34083
  allowUnscopedRows: options.allowUnscopedRows
@@ -33911,10 +34089,21 @@ function createLocalMemoryStore(options) {
33911
34089
  walletAddress: options.walletAddress,
33912
34090
  signMessage: options.signMessage,
33913
34091
  embeddedWalletSigner: options.embeddedWalletSigner,
34092
+ canWrite: options.canWrite,
33914
34093
  ...userId !== void 0 && { userId },
33915
34094
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
33916
34095
  entityCtx
33917
34096
  };
34097
+ const assertWrite = async (writeOptions) => {
34098
+ writeOptions?.signal?.throwIfAborted();
34099
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
34100
+ throw new Error("Memory source is no longer eligible");
34101
+ }
34102
+ if (options.canWrite && !await options.canWrite()) {
34103
+ throw new Error("Memory source is no longer eligible");
34104
+ }
34105
+ writeOptions?.signal?.throwIfAborted();
34106
+ };
33918
34107
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
33919
34108
  const ownedBy = userId !== void 0 ? [import_watermelondb24.Q.where("user_id", userId)] : [];
33920
34109
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [import_watermelondb24.Q.or(import_watermelondb24.Q.where("user_id", userId), import_watermelondb24.Q.where("user_id", null))] : [import_watermelondb24.Q.where("user_id", userId)];
@@ -34026,9 +34215,25 @@ function createLocalMemoryStore(options) {
34026
34215
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
34027
34216
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
34028
34217
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
34029
- addTopics: (memoryId, topics) => database.write(async (writer) => {
34218
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
34219
+ await assertWrite(writeOptions);
34030
34220
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb24.Q.where("id", memoryId), import_watermelondb24.Q.where("is_deleted", false), ...ownedBy).fetchCount();
34031
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
34221
+ return owned > 0 ? writer.callWriter(
34222
+ () => linkMemoryEntitiesOp(
34223
+ {
34224
+ ...entityCtx,
34225
+ canWrite: async () => {
34226
+ await assertWrite(writeOptions);
34227
+ return true;
34228
+ }
34229
+ },
34230
+ memoryId,
34231
+ topics,
34232
+ {
34233
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
34234
+ }
34235
+ )
34236
+ ) : [];
34032
34237
  }),
34033
34238
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
34034
34239
  recall: (query, recallOptions) => recall(
@@ -34038,14 +34243,33 @@ function createLocalMemoryStore(options) {
34038
34243
  ),
34039
34244
  retain: (content, retainOptions) => retain(
34040
34245
  content,
34041
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
34246
+ {
34247
+ vaultCtx: {
34248
+ ...vaultCtx,
34249
+ canWrite: async () => {
34250
+ await assertWrite(retainOptions);
34251
+ return true;
34252
+ }
34253
+ },
34254
+ embeddingOptions: options.embeddingOptions,
34255
+ vaultCache
34256
+ },
34042
34257
  retainOptions
34043
34258
  ),
34044
34259
  factSource: {
34045
- search: (query, searchOptions) => searchVaultMemoriesWithSize(
34260
+ search: (query, searchOptions, embeddingCache) => searchVaultMemoriesWithSize(
34046
34261
  query,
34047
34262
  vaultCtx,
34048
- { ...options.embeddingOptions, taskType: "memory_recall_embed" },
34263
+ {
34264
+ ...options.embeddingOptions,
34265
+ taskType: "memory_recall_embed",
34266
+ ...embeddingCache && !options.embeddingOptions.cache && {
34267
+ cache: maskScopedEmbeddingCache(
34268
+ embeddingCache,
34269
+ Boolean(options.embeddingOptions.maskInput)
34270
+ )
34271
+ }
34272
+ },
34049
34273
  vaultCache,
34050
34274
  searchOptions
34051
34275
  ),
@@ -34879,6 +35103,7 @@ function createDurableAutoExtractor(options) {
34879
35103
  };
34880
35104
  const folderId = options.folderId ?? null;
34881
35105
  let disposed = false;
35106
+ let activeBatch = null;
34882
35107
  let running = false;
34883
35108
  let wakeRequested = false;
34884
35109
  let timer;
@@ -34915,14 +35140,18 @@ function createDurableAutoExtractor(options) {
34915
35140
  }, delay);
34916
35141
  }
34917
35142
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
35143
+ if (disposed) throw new Error("Extraction session was disposed");
34918
35144
  let watchdog;
34919
35145
  let cancelled = false;
35146
+ const controller = new AbortController();
35147
+ activeBatch = controller;
34920
35148
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
34921
35149
  try {
34922
35150
  return await Promise.race([
34923
35151
  new Promise((resolve, reject) => {
34924
35152
  const worker = createAutoExtractor({
34925
35153
  ...options,
35154
+ signal: controller.signal,
34926
35155
  retainCtx: {
34927
35156
  ...options.retainCtx,
34928
35157
  vaultCtx: {
@@ -34951,6 +35180,8 @@ function createDurableAutoExtractor(options) {
34951
35180
  ]);
34952
35181
  } finally {
34953
35182
  cancelled = true;
35183
+ controller.abort();
35184
+ if (activeBatch === controller) activeBatch = null;
34954
35185
  if (watchdog !== void 0) clearTimeout(watchdog);
34955
35186
  }
34956
35187
  }
@@ -34962,6 +35193,7 @@ function createDurableAutoExtractor(options) {
34962
35193
  for (const message of loaded)
34963
35194
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
34964
35195
  await database.write(async () => {
35196
+ if (disposed) return;
34965
35197
  const current = idsOf(job);
34966
35198
  const acknowledged = ids.filter((id) => current.includes(id));
34967
35199
  if (!acknowledged.length) return;
@@ -35134,6 +35366,7 @@ function createDurableAutoExtractor(options) {
35134
35366
  jobScope,
35135
35367
  jobFolderId
35136
35368
  );
35369
+ if (disposed) throw new Error("Extraction session was disposed");
35137
35370
  options.onTurnComplete?.(result);
35138
35371
  return result;
35139
35372
  };
@@ -35170,6 +35403,7 @@ function createDurableAutoExtractor(options) {
35170
35403
  );
35171
35404
  scheduleAfterSuccess = true;
35172
35405
  } catch (error) {
35406
+ if (disposed) break;
35173
35407
  attempts.set(job.id, attempted + 1);
35174
35408
  retry = true;
35175
35409
  reportError(error, conversationId);
@@ -35192,6 +35426,14 @@ function createDurableAutoExtractor(options) {
35192
35426
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
35193
35427
  }
35194
35428
  }
35429
+ function dispose() {
35430
+ disposed = true;
35431
+ activeBatch?.abort(options.signal?.reason);
35432
+ if (timer !== void 0) clearTimeout(timer);
35433
+ options.signal?.removeEventListener("abort", dispose);
35434
+ }
35435
+ if (options.signal?.aborted) dispose();
35436
+ else options.signal?.addEventListener("abort", dispose, { once: true });
35195
35437
  schedule(0);
35196
35438
  return {
35197
35439
  processTurn(messages, conversationId) {
@@ -35271,10 +35513,7 @@ function createDurableAutoExtractor(options) {
35271
35513
  return true;
35272
35514
  },
35273
35515
  isProcessing: () => running,
35274
- dispose() {
35275
- disposed = true;
35276
- if (timer !== void 0) clearTimeout(timer);
35277
- }
35516
+ dispose
35278
35517
  };
35279
35518
  }
35280
35519