@anuma/sdk 7.0.6 → 7.1.0

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.
@@ -27824,6 +27824,11 @@ function parseTopics(value) {
27824
27824
  }
27825
27825
 
27826
27826
  // src/lib/db/entities/operations.ts
27827
+ async function assertCanWrite(ctx) {
27828
+ if (ctx.canWrite && !await ctx.canWrite()) {
27829
+ throw new Error("Memory source is no longer eligible");
27830
+ }
27831
+ }
27827
27832
  function entityToStored(e) {
27828
27833
  return {
27829
27834
  uniqueId: e.id,
@@ -27964,6 +27969,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
27964
27969
  let skipped = false;
27965
27970
  let entities = [];
27966
27971
  await ctx.database.write(async () => {
27972
+ await assertCanWrite(ctx);
27967
27973
  const {
27968
27974
  entities: byName,
27969
27975
  operations: entityOps,
@@ -28039,6 +28045,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
28039
28045
  let skipped = false;
28040
28046
  let entities = [];
28041
28047
  await ctx.database.write(async () => {
28048
+ await assertCanWrite(ctx);
28042
28049
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
28043
28050
  if (options.guarded && autoLinkBlocked(vaultRow)) {
28044
28051
  skipped = true;
@@ -28099,6 +28106,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
28099
28106
  const links = await ctx.memoryEntityCollection.query(import_watermelondb18.Q.where("memory_id", import_watermelondb18.Q.oneOf(Array.from(memoryIds)))).fetch();
28100
28107
  if (links.length === 0) return;
28101
28108
  await ctx.database.write(async () => {
28109
+ await assertCanWrite(ctx);
28102
28110
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
28103
28111
  });
28104
28112
  }
@@ -28107,6 +28115,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
28107
28115
  const links = await ctx.memoryEntityCollection.query(import_watermelondb18.Q.where("user_id", userId)).fetch();
28108
28116
  if (links.length === 0) return;
28109
28117
  await ctx.database.write(async () => {
28118
+ await assertCanWrite(ctx);
28110
28119
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
28111
28120
  });
28112
28121
  }
@@ -28199,6 +28208,17 @@ async function decryptVaultMemoryFields(memory, address, signMessage, embeddedWa
28199
28208
  };
28200
28209
  }
28201
28210
 
28211
+ // src/lib/db/memoryVault/sourceChunkIds.ts
28212
+ var MAX_SOURCE_CHUNK_IDS_BYTES = 16 << 10;
28213
+ var utf8 = new TextEncoder();
28214
+ function serializeSourceChunkIds(ids) {
28215
+ const sizes = ids.map((id) => utf8.encode(JSON.stringify(id)).length + 1);
28216
+ let total = sizes.reduce((sum, size) => sum + size, 1);
28217
+ let start = 0;
28218
+ while (total > MAX_SOURCE_CHUNK_IDS_BYTES && start < ids.length) total -= sizes[start++];
28219
+ return JSON.stringify(ids.slice(start));
28220
+ }
28221
+
28202
28222
  // src/lib/db/memoryVault/types.ts
28203
28223
  var MEMORY_KINDS = [
28204
28224
  "display_name",
@@ -28271,6 +28291,11 @@ function visibilityConditions(requested) {
28271
28291
  function isKinded(record) {
28272
28292
  return record.kind !== null && record.kind !== void 0;
28273
28293
  }
28294
+ async function assertCanWrite2(ctx) {
28295
+ if (ctx.canWrite && !await ctx.canWrite()) {
28296
+ throw new Error("Memory source is no longer eligible");
28297
+ }
28298
+ }
28274
28299
  function assertValidKindLevel(kind, level) {
28275
28300
  const hasKind = kind !== null && kind !== void 0;
28276
28301
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -28417,7 +28442,7 @@ function populateNewVaultMemory(record, ctx, opts, encryptedContent, encryptedKi
28417
28442
  record._setRaw("embedding_model", opts.embeddingModel ?? null);
28418
28443
  }
28419
28444
  if (opts.sourceChunkIds !== void 0) {
28420
- record._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
28445
+ record._setRaw("source_chunk_ids", serializeSourceChunkIds(opts.sourceChunkIds));
28421
28446
  }
28422
28447
  record._setRaw("proof_count", opts.proofCount ?? 1);
28423
28448
  record._setRaw("source", opts.source ?? "manual");
@@ -28451,8 +28476,7 @@ async function createVaultMemoryOp(ctx, opts) {
28451
28476
  ctx.embeddedWalletSigner
28452
28477
  ) : opts.content;
28453
28478
  const created = await ctx.database.write(async () => {
28454
- if (ctx.canWrite && !await ctx.canWrite())
28455
- throw new Error("Memory source is no longer eligible");
28479
+ await assertCanWrite2(ctx);
28456
28480
  return ctx.vaultMemoryCollection.create(
28457
28481
  (record) => populateNewVaultMemory(record, ctx, opts, encryptedContent, encryptedKindValue)
28458
28482
  );
@@ -28471,8 +28495,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
28471
28495
  ) : opts.content;
28472
28496
  let createdRecord = null;
28473
28497
  await ctx.database.write(async () => {
28474
- if (ctx.canWrite && !await ctx.canWrite())
28475
- throw new Error("Memory source is no longer eligible");
28498
+ await assertCanWrite2(ctx);
28476
28499
  let target;
28477
28500
  try {
28478
28501
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -28564,8 +28587,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
28564
28587
  })
28565
28588
  );
28566
28589
  const created = await ctx.database.write(async () => {
28567
- if (ctx.canWrite && !await ctx.canWrite())
28568
- throw new Error("Memory source is no longer eligible");
28590
+ await assertCanWrite2(ctx);
28569
28591
  const prepared = optionsArray.map(
28570
28592
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
28571
28593
  (record) => populateNewVaultMemory(record, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -28856,8 +28878,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
28856
28878
  const record = probe;
28857
28879
  const originalUpdatedAt = record.updatedAt.getTime();
28858
28880
  await ctx.database.write(async () => {
28859
- if (ctx.canWrite && !await ctx.canWrite())
28860
- throw new Error("Memory source is no longer eligible");
28881
+ await assertCanWrite2(ctx);
28861
28882
  if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record) || opts.freeFormOnly && isKinded(record)) {
28862
28883
  stale = true;
28863
28884
  return;
@@ -28901,7 +28922,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
28901
28922
  if (opts.sourceChunkIds !== void 0) {
28902
28923
  r._setRaw(
28903
28924
  "source_chunk_ids",
28904
- JSON.stringify(
28925
+ serializeSourceChunkIds(
28905
28926
  opts.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observedSources, ...opts.sourceChunkIds])] : opts.sourceChunkIds
28906
28927
  )
28907
28928
  );
@@ -28968,6 +28989,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
28968
28989
  let stale = false;
28969
28990
  const originalUpdatedAt = record.updatedAt.getTime();
28970
28991
  await ctx.database.write(async (writer) => {
28992
+ await assertCanWrite2(ctx);
28971
28993
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
28972
28994
  stale = true;
28973
28995
  return;
@@ -29009,6 +29031,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
29009
29031
  const originalUpdatedAt = record.updatedAt.getTime();
29010
29032
  let cleared = false;
29011
29033
  await ctx.database.write(async () => {
29034
+ await assertCanWrite2(ctx);
29012
29035
  if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
29013
29036
  cleared = true;
29014
29037
  await record.update((r) => {
@@ -29032,6 +29055,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
29032
29055
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return null;
29033
29056
  let stale = false;
29034
29057
  await ctx.database.write(async () => {
29058
+ await assertCanWrite2(ctx);
29035
29059
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
29036
29060
  stale = true;
29037
29061
  return;
@@ -29060,6 +29084,7 @@ async function deleteVaultMemoryOp(ctx, id) {
29060
29084
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
29061
29085
  let stale = false;
29062
29086
  await ctx.database.write(async () => {
29087
+ await assertCanWrite2(ctx);
29063
29088
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
29064
29089
  stale = true;
29065
29090
  return;
@@ -29097,8 +29122,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
29097
29122
  }
29098
29123
  let stale = false;
29099
29124
  await ctx.database.write(async () => {
29100
- if (ctx.canWrite && !await ctx.canWrite())
29101
- throw new Error("Memory source is no longer eligible");
29125
+ await assertCanWrite2(ctx);
29102
29126
  if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record)) {
29103
29127
  stale = true;
29104
29128
  return;
@@ -29260,6 +29284,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
29260
29284
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
29261
29285
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
29262
29286
  await ctx.database.write(async () => {
29287
+ await assertCanWrite2(ctx);
29263
29288
  const records = [];
29264
29289
  for (const id of chunkIds) {
29265
29290
  try {
@@ -29293,6 +29318,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
29293
29318
  for (const id of Array.from(new Set(memoryIds))) {
29294
29319
  try {
29295
29320
  await ctx.database.write(async (writer) => {
29321
+ await assertCanWrite2(ctx);
29296
29322
  let record;
29297
29323
  try {
29298
29324
  record = await ctx.vaultMemoryCollection.find(id);
@@ -29323,6 +29349,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
29323
29349
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
29324
29350
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
29325
29351
  await ctx.database.write(async () => {
29352
+ await assertCanWrite2(ctx);
29326
29353
  const writes = [];
29327
29354
  for (const id of chunkIds) {
29328
29355
  let record;
@@ -29355,6 +29382,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
29355
29382
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb19.Q.where("user_id", userId), import_watermelondb19.Q.where("is_deleted", false)).fetch();
29356
29383
  if (records.length === 0) return 0;
29357
29384
  await ctx.database.write(async () => {
29385
+ await assertCanWrite2(ctx);
29358
29386
  const prepared = records.map(
29359
29387
  (record) => record.prepareUpdate((r) => {
29360
29388
  r._setRaw("is_deleted", true);
@@ -29409,6 +29437,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
29409
29437
  let stale = false;
29410
29438
  const archivedAtValue = opts?.now ?? Date.now();
29411
29439
  await ctx.database.write(async () => {
29440
+ await assertCanWrite2(ctx);
29412
29441
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.archivedAt !== null) {
29413
29442
  stale = true;
29414
29443
  return;
@@ -29432,6 +29461,7 @@ async function restoreVaultMemoryOp(ctx, id) {
29432
29461
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
29433
29462
  let stale = false;
29434
29463
  await ctx.database.write(async () => {
29464
+ await assertCanWrite2(ctx);
29435
29465
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
29436
29466
  stale = true;
29437
29467
  return;
@@ -29452,6 +29482,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
29452
29482
  const now = opts.now ?? Date.now();
29453
29483
  let stale = false;
29454
29484
  await ctx.database.write(async () => {
29485
+ await assertCanWrite2(ctx);
29455
29486
  const archivedAt = record.archivedAt;
29456
29487
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
29457
29488
  stale = true;
@@ -29491,6 +29522,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel,
29491
29522
  }
29492
29523
  let written = false;
29493
29524
  await ctx.database.write(async () => {
29525
+ await assertCanWrite2(ctx);
29494
29526
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return;
29495
29527
  const originalUpdatedAt = record.updatedAt.getTime();
29496
29528
  if (originalUpdatedAt !== readUpdatedAt || record._getRaw("content") !== readStoredContent)
@@ -33183,6 +33215,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
33183
33215
  ).map((c) => c.candidate);
33184
33216
  }
33185
33217
  async function extractAndRetain(messages, retainCtx, options) {
33218
+ const { memoryStore, signal } = options;
33219
+ if (memoryStore === null) throw new Error("Memory store is not ready");
33220
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
33221
+ throw new Error("Remote memory extraction does not support folders");
33222
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
33223
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
33224
+ signal?.throwIfAborted();
33186
33225
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
33187
33226
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
33188
33227
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -33216,6 +33255,7 @@ async function extractAndRetain(messages, retainCtx, options) {
33216
33255
  }
33217
33256
  });
33218
33257
  const extractMs = Date.now() - tExtract;
33258
+ signal?.throwIfAborted();
33219
33259
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
33220
33260
  const log = getLogger();
33221
33261
  const screened = screenCandidatesForInjection(filtered);
@@ -33263,16 +33303,33 @@ async function extractAndRetain(messages, retainCtx, options) {
33263
33303
  const results = [];
33264
33304
  const quarantinedInfo = [];
33265
33305
  let failedWrites = 0;
33306
+ let auditRows;
33266
33307
  const tRetain = Date.now();
33267
33308
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
33268
33309
  try {
33310
+ signal?.throwIfAborted();
33269
33311
  if (isQuarantined) {
33270
- const existingId = await findQuarantinedDuplicateOp(
33271
- retainCtx.vaultCtx,
33272
- candidate.content,
33273
- candidate.sourceMessageIds,
33274
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
33275
- ).catch(() => null);
33312
+ let existingId;
33313
+ if (memoryStore) {
33314
+ auditRows ?? (auditRows = memoryStore.list({
33315
+ scopes: [options.scope ?? "private"],
33316
+ includeArchived: true,
33317
+ includeQuarantined: true,
33318
+ includeSuperseded: true
33319
+ }));
33320
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
33321
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
33322
+ existingId = (await auditRows).find(
33323
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
33324
+ )?.uniqueId ?? null;
33325
+ } else {
33326
+ existingId = await findQuarantinedDuplicateOp(
33327
+ retainCtx.vaultCtx,
33328
+ candidate.content,
33329
+ candidate.sourceMessageIds,
33330
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
33331
+ ).catch(() => null);
33332
+ }
33276
33333
  if (existingId) {
33277
33334
  quarantinedInfo.push({
33278
33335
  candidate,
@@ -33283,18 +33340,26 @@ async function extractAndRetain(messages, retainCtx, options) {
33283
33340
  continue;
33284
33341
  }
33285
33342
  }
33286
- const result = await retain(candidate.content, retainCtx, {
33343
+ const retainOptions = {
33287
33344
  source: "auto-extracted",
33288
33345
  sourceChunkIds: candidate.sourceMessageIds,
33289
33346
  respectTombstones: true,
33290
33347
  ...options.scope !== void 0 && { scope: options.scope },
33291
- ...options.folderId !== void 0 && { folderId: options.folderId },
33292
33348
  ...consolidateOptions !== void 0 && { consolidateOptions },
33293
33349
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
33294
33350
  factType: candidate.type,
33295
33351
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
33352
+ };
33353
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
33354
+ ...retainOptions,
33355
+ canWrite: retainCtx.vaultCtx.canWrite,
33356
+ signal
33357
+ }) : await retain(candidate.content, retainCtx, {
33358
+ ...retainOptions,
33359
+ ...options.folderId !== void 0 && { folderId: options.folderId }
33296
33360
  });
33297
33361
  if (isQuarantined) {
33362
+ auditRows = void 0;
33298
33363
  const info = {
33299
33364
  candidate,
33300
33365
  memoryId: result.memoryId,
@@ -33313,9 +33378,16 @@ async function extractAndRetain(messages, retainCtx, options) {
33313
33378
  }
33314
33379
  succeededCandidates.push(candidate);
33315
33380
  results.push(result);
33316
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
33381
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
33317
33382
  try {
33318
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
33383
+ signal?.throwIfAborted();
33384
+ if (memoryStore) {
33385
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
33386
+ unlessUserManaged: true,
33387
+ canWrite: retainCtx.vaultCtx.canWrite,
33388
+ signal
33389
+ });
33390
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
33319
33391
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
33320
33392
  unlessTopicsUserManaged: true
33321
33393
  });
@@ -33565,7 +33637,7 @@ function createAutoExtractor(options) {
33565
33637
  };
33566
33638
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
33567
33639
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
33568
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
33640
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
33569
33641
  getLogger().warn(
33570
33642
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
33571
33643
  );
@@ -33610,6 +33682,8 @@ function createAutoExtractor(options) {
33610
33682
  try {
33611
33683
  const { candidates, results, failedCount, outcome, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
33612
33684
  extract,
33685
+ memoryStore: options.memoryStore,
33686
+ signal: options.signal,
33613
33687
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
33614
33688
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
33615
33689
  ...options.scope !== void 0 && { scope: options.scope },
@@ -35741,6 +35815,7 @@ function createLocalMemoryStore(options) {
35741
35815
  database,
35742
35816
  entityCollection: database.get("entity"),
35743
35817
  memoryEntityCollection: database.get("memory_entity"),
35818
+ canWrite: options.canWrite,
35744
35819
  ...userId !== void 0 && { userId },
35745
35820
  ...options.allowUnscopedRows !== void 0 && {
35746
35821
  allowUnscopedRows: options.allowUnscopedRows
@@ -35752,10 +35827,21 @@ function createLocalMemoryStore(options) {
35752
35827
  walletAddress: options.walletAddress,
35753
35828
  signMessage: options.signMessage,
35754
35829
  embeddedWalletSigner: options.embeddedWalletSigner,
35830
+ canWrite: options.canWrite,
35755
35831
  ...userId !== void 0 && { userId },
35756
35832
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
35757
35833
  entityCtx
35758
35834
  };
35835
+ const assertWrite = async (writeOptions) => {
35836
+ writeOptions?.signal?.throwIfAborted();
35837
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
35838
+ throw new Error("Memory source is no longer eligible");
35839
+ }
35840
+ if (options.canWrite && !await options.canWrite()) {
35841
+ throw new Error("Memory source is no longer eligible");
35842
+ }
35843
+ writeOptions?.signal?.throwIfAborted();
35844
+ };
35759
35845
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
35760
35846
  const ownedBy = userId !== void 0 ? [import_watermelondb24.Q.where("user_id", userId)] : [];
35761
35847
  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)];
@@ -35867,9 +35953,25 @@ function createLocalMemoryStore(options) {
35867
35953
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
35868
35954
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
35869
35955
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
35870
- addTopics: (memoryId, topics) => database.write(async (writer) => {
35956
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
35957
+ await assertWrite(writeOptions);
35871
35958
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb24.Q.where("id", memoryId), import_watermelondb24.Q.where("is_deleted", false), ...ownedBy).fetchCount();
35872
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
35959
+ return owned > 0 ? writer.callWriter(
35960
+ () => linkMemoryEntitiesOp(
35961
+ {
35962
+ ...entityCtx,
35963
+ canWrite: async () => {
35964
+ await assertWrite(writeOptions);
35965
+ return true;
35966
+ }
35967
+ },
35968
+ memoryId,
35969
+ topics,
35970
+ {
35971
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
35972
+ }
35973
+ )
35974
+ ) : [];
35873
35975
  }),
35874
35976
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
35875
35977
  recall: (query, recallOptions) => recall(
@@ -35879,7 +35981,17 @@ function createLocalMemoryStore(options) {
35879
35981
  ),
35880
35982
  retain: (content, retainOptions) => retain(
35881
35983
  content,
35882
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
35984
+ {
35985
+ vaultCtx: {
35986
+ ...vaultCtx,
35987
+ canWrite: async () => {
35988
+ await assertWrite(retainOptions);
35989
+ return true;
35990
+ }
35991
+ },
35992
+ embeddingOptions: options.embeddingOptions,
35993
+ vaultCache
35994
+ },
35883
35995
  retainOptions
35884
35996
  ),
35885
35997
  factSource: {
@@ -36731,6 +36843,7 @@ function createDurableAutoExtractor(options) {
36731
36843
  };
36732
36844
  const folderId = options.folderId ?? null;
36733
36845
  let disposed = false;
36846
+ let activeBatch = null;
36734
36847
  let running = false;
36735
36848
  let wakeRequested = false;
36736
36849
  let timer;
@@ -36767,14 +36880,18 @@ function createDurableAutoExtractor(options) {
36767
36880
  }, delay);
36768
36881
  }
36769
36882
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
36883
+ if (disposed) throw new Error("Extraction session was disposed");
36770
36884
  let watchdog;
36771
36885
  let cancelled = false;
36886
+ const controller = new AbortController();
36887
+ activeBatch = controller;
36772
36888
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
36773
36889
  try {
36774
36890
  return await Promise.race([
36775
36891
  new Promise((resolve, reject) => {
36776
36892
  const worker = createAutoExtractor({
36777
36893
  ...options,
36894
+ signal: controller.signal,
36778
36895
  retainCtx: {
36779
36896
  ...options.retainCtx,
36780
36897
  vaultCtx: {
@@ -36803,6 +36920,8 @@ function createDurableAutoExtractor(options) {
36803
36920
  ]);
36804
36921
  } finally {
36805
36922
  cancelled = true;
36923
+ controller.abort();
36924
+ if (activeBatch === controller) activeBatch = null;
36806
36925
  if (watchdog !== void 0) clearTimeout(watchdog);
36807
36926
  }
36808
36927
  }
@@ -36814,6 +36933,7 @@ function createDurableAutoExtractor(options) {
36814
36933
  for (const message of loaded)
36815
36934
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
36816
36935
  await database.write(async () => {
36936
+ if (disposed) return;
36817
36937
  const current = idsOf(job);
36818
36938
  const acknowledged = ids.filter((id) => current.includes(id));
36819
36939
  if (!acknowledged.length) return;
@@ -36986,6 +37106,7 @@ function createDurableAutoExtractor(options) {
36986
37106
  jobScope,
36987
37107
  jobFolderId
36988
37108
  );
37109
+ if (disposed) throw new Error("Extraction session was disposed");
36989
37110
  options.onTurnComplete?.(result);
36990
37111
  return result;
36991
37112
  };
@@ -37022,6 +37143,7 @@ function createDurableAutoExtractor(options) {
37022
37143
  );
37023
37144
  scheduleAfterSuccess = true;
37024
37145
  } catch (error) {
37146
+ if (disposed) break;
37025
37147
  attempts.set(job.id, attempted + 1);
37026
37148
  retry = true;
37027
37149
  reportError(error, conversationId);
@@ -37044,6 +37166,14 @@ function createDurableAutoExtractor(options) {
37044
37166
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
37045
37167
  }
37046
37168
  }
37169
+ function dispose() {
37170
+ disposed = true;
37171
+ activeBatch?.abort(options.signal?.reason);
37172
+ if (timer !== void 0) clearTimeout(timer);
37173
+ options.signal?.removeEventListener("abort", dispose);
37174
+ }
37175
+ if (options.signal?.aborted) dispose();
37176
+ else options.signal?.addEventListener("abort", dispose, { once: true });
37047
37177
  schedule(0);
37048
37178
  return {
37049
37179
  processTurn(messages, conversationId) {
@@ -37123,10 +37253,7 @@ function createDurableAutoExtractor(options) {
37123
37253
  return true;
37124
37254
  },
37125
37255
  isProcessing: () => running,
37126
- dispose() {
37127
- disposed = true;
37128
- if (timer !== void 0) clearTimeout(timer);
37129
- }
37256
+ dispose
37130
37257
  };
37131
37258
  }
37132
37259
 
@@ -2779,6 +2779,7 @@ interface EntityOperationsContext {
2779
2779
  * (server / SQLite, where the migration backfill is authoritative).
2780
2780
  */
2781
2781
  allowUnscopedRows?: boolean;
2782
+ canWrite?: () => Promise<boolean>;
2782
2783
  }
2783
2784
 
2784
2785
  /**
@@ -4933,6 +4934,10 @@ type ExtractOutcome = "extracted" | "no-facts" | "empty-after-retry" | "dropped-
4933
4934
  */
4934
4935
  declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: RetainContext, options: {
4935
4936
  extract: ExtractFactsOptions;
4937
+ /** Route extracted facts through this store instead of the local vault. */
4938
+ memoryStore?: MemoryStore | null;
4939
+ /** Cancel remote writes for this extraction batch. */
4940
+ signal?: AbortSignal;
4936
4941
  minConfidence?: number;
4937
4942
  /** Override scope/folder for all retained facts. */
4938
4943
  scope?: string;
@@ -6180,7 +6185,12 @@ type MemoryRecallOptions = Omit<RecallOptions, "types" | "includeChunks" | "conv
6180
6185
  * and never receives these credentials, callbacks or redactor instances.
6181
6186
  * @public
6182
6187
  */
6183
- type MemoryRetainOptions = Omit<RetainOptions, "folderId">;
6188
+ type MemoryRetainOptions = Omit<RetainOptions, "folderId"> & {
6189
+ /** Reject writes when the originating extraction is no longer eligible. */
6190
+ canWrite?: () => Promise<boolean>;
6191
+ /** Abort pending remote requests when the originating extraction is cancelled. */
6192
+ signal?: AbortSignal;
6193
+ };
6184
6194
  /**
6185
6195
  * A topic and how many memories link to it.
6186
6196
  * @public
@@ -6309,7 +6319,11 @@ interface MemoryStore {
6309
6319
  /** Replace the memory's topics with a user-chosen set and stop auto-tagging it. */
6310
6320
  setTopics(memoryId: string, topics: readonly EntityInput[]): Promise<StoredVaultMemory | null>;
6311
6321
  /** Add topics alongside the existing ones (auto-tagging stays on). */
6312
- addTopics(memoryId: string, topics: readonly EntityInput[]): Promise<StoredEntity[]>;
6322
+ addTopics(memoryId: string, topics: readonly EntityInput[], options?: {
6323
+ unlessUserManaged?: boolean;
6324
+ canWrite?: () => Promise<boolean>;
6325
+ signal?: AbortSignal;
6326
+ }): Promise<StoredEntity[]>;
6313
6327
  setVisibility(id: string, visibility: VaultMemoryVisibility, options?: {
6314
6328
  twinOptIn?: boolean;
6315
6329
  }): Promise<StoredVaultMemory | null>;
@@ -9079,6 +9093,10 @@ interface SyncKeyValueStore {
9079
9093
  declare function createPlatformCursorStore(storage: SyncKeyValueStore, keyPrefix?: string): ExtractionCursorStore;
9080
9094
  interface CreateAutoExtractorOptions {
9081
9095
  retainCtx: RetainContext;
9096
+ /** Route retained facts and topics through this store; null disables memory writes. */
9097
+ memoryStore?: MemoryStore | null;
9098
+ /** Cancel remote writes for an extraction batch. */
9099
+ signal?: AbortSignal;
9082
9100
  extract: ExtractFactsOptions;
9083
9101
  /** Confidence floor for retained facts. Default 0.7. */
9084
9102
  minConfidence?: number;
@@ -9686,6 +9704,8 @@ interface LocalMemoryStoreOptions {
9686
9704
  walletAddress?: string;
9687
9705
  signMessage?: SignMessageFn;
9688
9706
  embeddedWalletSigner?: EmbeddedWalletSignerFn;
9707
+ /** Rechecked inside each serialized memory write. */
9708
+ canWrite?: () => Promise<boolean>;
9689
9709
  /**
9690
9710
  * Scope every read/write to this user — for a shared, multi-tenant database
9691
9711
  * whose rows all carry their `user_id`. Rows with no `user_id` are invisible
@@ -2779,6 +2779,7 @@ interface EntityOperationsContext {
2779
2779
  * (server / SQLite, where the migration backfill is authoritative).
2780
2780
  */
2781
2781
  allowUnscopedRows?: boolean;
2782
+ canWrite?: () => Promise<boolean>;
2782
2783
  }
2783
2784
 
2784
2785
  /**
@@ -4933,6 +4934,10 @@ type ExtractOutcome = "extracted" | "no-facts" | "empty-after-retry" | "dropped-
4933
4934
  */
4934
4935
  declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: RetainContext, options: {
4935
4936
  extract: ExtractFactsOptions;
4937
+ /** Route extracted facts through this store instead of the local vault. */
4938
+ memoryStore?: MemoryStore | null;
4939
+ /** Cancel remote writes for this extraction batch. */
4940
+ signal?: AbortSignal;
4936
4941
  minConfidence?: number;
4937
4942
  /** Override scope/folder for all retained facts. */
4938
4943
  scope?: string;
@@ -6180,7 +6185,12 @@ type MemoryRecallOptions = Omit<RecallOptions, "types" | "includeChunks" | "conv
6180
6185
  * and never receives these credentials, callbacks or redactor instances.
6181
6186
  * @public
6182
6187
  */
6183
- type MemoryRetainOptions = Omit<RetainOptions, "folderId">;
6188
+ type MemoryRetainOptions = Omit<RetainOptions, "folderId"> & {
6189
+ /** Reject writes when the originating extraction is no longer eligible. */
6190
+ canWrite?: () => Promise<boolean>;
6191
+ /** Abort pending remote requests when the originating extraction is cancelled. */
6192
+ signal?: AbortSignal;
6193
+ };
6184
6194
  /**
6185
6195
  * A topic and how many memories link to it.
6186
6196
  * @public
@@ -6309,7 +6319,11 @@ interface MemoryStore {
6309
6319
  /** Replace the memory's topics with a user-chosen set and stop auto-tagging it. */
6310
6320
  setTopics(memoryId: string, topics: readonly EntityInput[]): Promise<StoredVaultMemory | null>;
6311
6321
  /** Add topics alongside the existing ones (auto-tagging stays on). */
6312
- addTopics(memoryId: string, topics: readonly EntityInput[]): Promise<StoredEntity[]>;
6322
+ addTopics(memoryId: string, topics: readonly EntityInput[], options?: {
6323
+ unlessUserManaged?: boolean;
6324
+ canWrite?: () => Promise<boolean>;
6325
+ signal?: AbortSignal;
6326
+ }): Promise<StoredEntity[]>;
6313
6327
  setVisibility(id: string, visibility: VaultMemoryVisibility, options?: {
6314
6328
  twinOptIn?: boolean;
6315
6329
  }): Promise<StoredVaultMemory | null>;
@@ -9079,6 +9093,10 @@ interface SyncKeyValueStore {
9079
9093
  declare function createPlatformCursorStore(storage: SyncKeyValueStore, keyPrefix?: string): ExtractionCursorStore;
9080
9094
  interface CreateAutoExtractorOptions {
9081
9095
  retainCtx: RetainContext;
9096
+ /** Route retained facts and topics through this store; null disables memory writes. */
9097
+ memoryStore?: MemoryStore | null;
9098
+ /** Cancel remote writes for an extraction batch. */
9099
+ signal?: AbortSignal;
9082
9100
  extract: ExtractFactsOptions;
9083
9101
  /** Confidence floor for retained facts. Default 0.7. */
9084
9102
  minConfidence?: number;
@@ -9686,6 +9704,8 @@ interface LocalMemoryStoreOptions {
9686
9704
  walletAddress?: string;
9687
9705
  signMessage?: SignMessageFn;
9688
9706
  embeddedWalletSigner?: EmbeddedWalletSignerFn;
9707
+ /** Rechecked inside each serialized memory write. */
9708
+ canWrite?: () => Promise<boolean>;
9689
9709
  /**
9690
9710
  * Scope every read/write to this user — for a shared, multi-tenant database
9691
9711
  * whose rows all carry their `user_id`. Rows with no `user_id` are invisible