@anuma/sdk 7.0.7 → 7.1.1

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
  }
@@ -28282,6 +28291,11 @@ function visibilityConditions(requested) {
28282
28291
  function isKinded(record) {
28283
28292
  return record.kind !== null && record.kind !== void 0;
28284
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
+ }
28285
28299
  function assertValidKindLevel(kind, level) {
28286
28300
  const hasKind = kind !== null && kind !== void 0;
28287
28301
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -28462,8 +28476,7 @@ async function createVaultMemoryOp(ctx, opts) {
28462
28476
  ctx.embeddedWalletSigner
28463
28477
  ) : opts.content;
28464
28478
  const created = await ctx.database.write(async () => {
28465
- if (ctx.canWrite && !await ctx.canWrite())
28466
- throw new Error("Memory source is no longer eligible");
28479
+ await assertCanWrite2(ctx);
28467
28480
  return ctx.vaultMemoryCollection.create(
28468
28481
  (record) => populateNewVaultMemory(record, ctx, opts, encryptedContent, encryptedKindValue)
28469
28482
  );
@@ -28482,8 +28495,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
28482
28495
  ) : opts.content;
28483
28496
  let createdRecord = null;
28484
28497
  await ctx.database.write(async () => {
28485
- if (ctx.canWrite && !await ctx.canWrite())
28486
- throw new Error("Memory source is no longer eligible");
28498
+ await assertCanWrite2(ctx);
28487
28499
  let target;
28488
28500
  try {
28489
28501
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -28575,8 +28587,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
28575
28587
  })
28576
28588
  );
28577
28589
  const created = await ctx.database.write(async () => {
28578
- if (ctx.canWrite && !await ctx.canWrite())
28579
- throw new Error("Memory source is no longer eligible");
28590
+ await assertCanWrite2(ctx);
28580
28591
  const prepared = optionsArray.map(
28581
28592
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
28582
28593
  (record) => populateNewVaultMemory(record, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -28867,8 +28878,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
28867
28878
  const record = probe;
28868
28879
  const originalUpdatedAt = record.updatedAt.getTime();
28869
28880
  await ctx.database.write(async () => {
28870
- if (ctx.canWrite && !await ctx.canWrite())
28871
- throw new Error("Memory source is no longer eligible");
28881
+ await assertCanWrite2(ctx);
28872
28882
  if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record) || opts.freeFormOnly && isKinded(record)) {
28873
28883
  stale = true;
28874
28884
  return;
@@ -28979,6 +28989,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
28979
28989
  let stale = false;
28980
28990
  const originalUpdatedAt = record.updatedAt.getTime();
28981
28991
  await ctx.database.write(async (writer) => {
28992
+ await assertCanWrite2(ctx);
28982
28993
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
28983
28994
  stale = true;
28984
28995
  return;
@@ -29020,6 +29031,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
29020
29031
  const originalUpdatedAt = record.updatedAt.getTime();
29021
29032
  let cleared = false;
29022
29033
  await ctx.database.write(async () => {
29034
+ await assertCanWrite2(ctx);
29023
29035
  if (options?.unlessTopicsRecorded && parseTopics(record.topics) !== null) return;
29024
29036
  cleared = true;
29025
29037
  await record.update((r) => {
@@ -29043,6 +29055,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
29043
29055
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return null;
29044
29056
  let stale = false;
29045
29057
  await ctx.database.write(async () => {
29058
+ await assertCanWrite2(ctx);
29046
29059
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
29047
29060
  stale = true;
29048
29061
  return;
@@ -29071,6 +29084,7 @@ async function deleteVaultMemoryOp(ctx, id) {
29071
29084
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
29072
29085
  let stale = false;
29073
29086
  await ctx.database.write(async () => {
29087
+ await assertCanWrite2(ctx);
29074
29088
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
29075
29089
  stale = true;
29076
29090
  return;
@@ -29108,8 +29122,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
29108
29122
  }
29109
29123
  let stale = false;
29110
29124
  await ctx.database.write(async () => {
29111
- if (ctx.canWrite && !await ctx.canWrite())
29112
- throw new Error("Memory source is no longer eligible");
29125
+ await assertCanWrite2(ctx);
29113
29126
  if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record)) {
29114
29127
  stale = true;
29115
29128
  return;
@@ -29271,6 +29284,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
29271
29284
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
29272
29285
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
29273
29286
  await ctx.database.write(async () => {
29287
+ await assertCanWrite2(ctx);
29274
29288
  const records = [];
29275
29289
  for (const id of chunkIds) {
29276
29290
  try {
@@ -29304,6 +29318,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
29304
29318
  for (const id of Array.from(new Set(memoryIds))) {
29305
29319
  try {
29306
29320
  await ctx.database.write(async (writer) => {
29321
+ await assertCanWrite2(ctx);
29307
29322
  let record;
29308
29323
  try {
29309
29324
  record = await ctx.vaultMemoryCollection.find(id);
@@ -29334,6 +29349,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
29334
29349
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
29335
29350
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
29336
29351
  await ctx.database.write(async () => {
29352
+ await assertCanWrite2(ctx);
29337
29353
  const writes = [];
29338
29354
  for (const id of chunkIds) {
29339
29355
  let record;
@@ -29366,6 +29382,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
29366
29382
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb19.Q.where("user_id", userId), import_watermelondb19.Q.where("is_deleted", false)).fetch();
29367
29383
  if (records.length === 0) return 0;
29368
29384
  await ctx.database.write(async () => {
29385
+ await assertCanWrite2(ctx);
29369
29386
  const prepared = records.map(
29370
29387
  (record) => record.prepareUpdate((r) => {
29371
29388
  r._setRaw("is_deleted", true);
@@ -29420,6 +29437,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
29420
29437
  let stale = false;
29421
29438
  const archivedAtValue = opts?.now ?? Date.now();
29422
29439
  await ctx.database.write(async () => {
29440
+ await assertCanWrite2(ctx);
29423
29441
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.archivedAt !== null) {
29424
29442
  stale = true;
29425
29443
  return;
@@ -29443,6 +29461,7 @@ async function restoreVaultMemoryOp(ctx, id) {
29443
29461
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return false;
29444
29462
  let stale = false;
29445
29463
  await ctx.database.write(async () => {
29464
+ await assertCanWrite2(ctx);
29446
29465
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) {
29447
29466
  stale = true;
29448
29467
  return;
@@ -29463,6 +29482,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
29463
29482
  const now = opts.now ?? Date.now();
29464
29483
  let stale = false;
29465
29484
  await ctx.database.write(async () => {
29485
+ await assertCanWrite2(ctx);
29466
29486
  const archivedAt = record.archivedAt;
29467
29487
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
29468
29488
  stale = true;
@@ -29502,6 +29522,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding, embeddingModel,
29502
29522
  }
29503
29523
  let written = false;
29504
29524
  await ctx.database.write(async () => {
29525
+ await assertCanWrite2(ctx);
29505
29526
  if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) return;
29506
29527
  const originalUpdatedAt = record.updatedAt.getTime();
29507
29528
  if (originalUpdatedAt !== readUpdatedAt || record._getRaw("content") !== readStoredContent)
@@ -33194,6 +33215,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
33194
33215
  ).map((c) => c.candidate);
33195
33216
  }
33196
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();
33197
33225
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
33198
33226
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
33199
33227
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -33227,6 +33255,7 @@ async function extractAndRetain(messages, retainCtx, options) {
33227
33255
  }
33228
33256
  });
33229
33257
  const extractMs = Date.now() - tExtract;
33258
+ signal?.throwIfAborted();
33230
33259
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
33231
33260
  const log = getLogger();
33232
33261
  const screened = screenCandidatesForInjection(filtered);
@@ -33274,16 +33303,33 @@ async function extractAndRetain(messages, retainCtx, options) {
33274
33303
  const results = [];
33275
33304
  const quarantinedInfo = [];
33276
33305
  let failedWrites = 0;
33306
+ let auditRows;
33277
33307
  const tRetain = Date.now();
33278
33308
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
33279
33309
  try {
33310
+ signal?.throwIfAborted();
33280
33311
  if (isQuarantined) {
33281
- const existingId = await findQuarantinedDuplicateOp(
33282
- retainCtx.vaultCtx,
33283
- candidate.content,
33284
- candidate.sourceMessageIds,
33285
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
33286
- ).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
+ }
33287
33333
  if (existingId) {
33288
33334
  quarantinedInfo.push({
33289
33335
  candidate,
@@ -33294,18 +33340,26 @@ async function extractAndRetain(messages, retainCtx, options) {
33294
33340
  continue;
33295
33341
  }
33296
33342
  }
33297
- const result = await retain(candidate.content, retainCtx, {
33343
+ const retainOptions = {
33298
33344
  source: "auto-extracted",
33299
33345
  sourceChunkIds: candidate.sourceMessageIds,
33300
33346
  respectTombstones: true,
33301
33347
  ...options.scope !== void 0 && { scope: options.scope },
33302
- ...options.folderId !== void 0 && { folderId: options.folderId },
33303
33348
  ...consolidateOptions !== void 0 && { consolidateOptions },
33304
33349
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
33305
33350
  factType: candidate.type,
33306
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 }
33307
33360
  });
33308
33361
  if (isQuarantined) {
33362
+ auditRows = void 0;
33309
33363
  const info = {
33310
33364
  candidate,
33311
33365
  memoryId: result.memoryId,
@@ -33324,9 +33378,16 @@ async function extractAndRetain(messages, retainCtx, options) {
33324
33378
  }
33325
33379
  succeededCandidates.push(candidate);
33326
33380
  results.push(result);
33327
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
33381
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
33328
33382
  try {
33329
- 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)) {
33330
33391
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
33331
33392
  unlessTopicsUserManaged: true
33332
33393
  });
@@ -33576,7 +33637,7 @@ function createAutoExtractor(options) {
33576
33637
  };
33577
33638
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
33578
33639
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
33579
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
33640
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
33580
33641
  getLogger().warn(
33581
33642
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
33582
33643
  );
@@ -33621,6 +33682,8 @@ function createAutoExtractor(options) {
33621
33682
  try {
33622
33683
  const { candidates, results, failedCount, outcome, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
33623
33684
  extract,
33685
+ memoryStore: options.memoryStore,
33686
+ signal: options.signal,
33624
33687
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
33625
33688
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
33626
33689
  ...options.scope !== void 0 && { scope: options.scope },
@@ -35752,6 +35815,7 @@ function createLocalMemoryStore(options) {
35752
35815
  database,
35753
35816
  entityCollection: database.get("entity"),
35754
35817
  memoryEntityCollection: database.get("memory_entity"),
35818
+ canWrite: options.canWrite,
35755
35819
  ...userId !== void 0 && { userId },
35756
35820
  ...options.allowUnscopedRows !== void 0 && {
35757
35821
  allowUnscopedRows: options.allowUnscopedRows
@@ -35763,10 +35827,21 @@ function createLocalMemoryStore(options) {
35763
35827
  walletAddress: options.walletAddress,
35764
35828
  signMessage: options.signMessage,
35765
35829
  embeddedWalletSigner: options.embeddedWalletSigner,
35830
+ canWrite: options.canWrite,
35766
35831
  ...userId !== void 0 && { userId },
35767
35832
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
35768
35833
  entityCtx
35769
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
+ };
35770
35845
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
35771
35846
  const ownedBy = userId !== void 0 ? [import_watermelondb24.Q.where("user_id", userId)] : [];
35772
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)];
@@ -35878,9 +35953,25 @@ function createLocalMemoryStore(options) {
35878
35953
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
35879
35954
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
35880
35955
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
35881
- addTopics: (memoryId, topics) => database.write(async (writer) => {
35956
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
35957
+ await assertWrite(writeOptions);
35882
35958
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb24.Q.where("id", memoryId), import_watermelondb24.Q.where("is_deleted", false), ...ownedBy).fetchCount();
35883
- 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
+ ) : [];
35884
35975
  }),
35885
35976
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
35886
35977
  recall: (query, recallOptions) => recall(
@@ -35890,7 +35981,17 @@ function createLocalMemoryStore(options) {
35890
35981
  ),
35891
35982
  retain: (content, retainOptions) => retain(
35892
35983
  content,
35893
- { 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
+ },
35894
35995
  retainOptions
35895
35996
  ),
35896
35997
  factSource: {
@@ -36742,6 +36843,7 @@ function createDurableAutoExtractor(options) {
36742
36843
  };
36743
36844
  const folderId = options.folderId ?? null;
36744
36845
  let disposed = false;
36846
+ let activeBatch = null;
36745
36847
  let running = false;
36746
36848
  let wakeRequested = false;
36747
36849
  let timer;
@@ -36778,14 +36880,18 @@ function createDurableAutoExtractor(options) {
36778
36880
  }, delay);
36779
36881
  }
36780
36882
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
36883
+ if (disposed) throw new Error("Extraction session was disposed");
36781
36884
  let watchdog;
36782
36885
  let cancelled = false;
36886
+ const controller = new AbortController();
36887
+ activeBatch = controller;
36783
36888
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
36784
36889
  try {
36785
36890
  return await Promise.race([
36786
36891
  new Promise((resolve, reject) => {
36787
36892
  const worker = createAutoExtractor({
36788
36893
  ...options,
36894
+ signal: controller.signal,
36789
36895
  retainCtx: {
36790
36896
  ...options.retainCtx,
36791
36897
  vaultCtx: {
@@ -36814,6 +36920,8 @@ function createDurableAutoExtractor(options) {
36814
36920
  ]);
36815
36921
  } finally {
36816
36922
  cancelled = true;
36923
+ controller.abort();
36924
+ if (activeBatch === controller) activeBatch = null;
36817
36925
  if (watchdog !== void 0) clearTimeout(watchdog);
36818
36926
  }
36819
36927
  }
@@ -36825,6 +36933,7 @@ function createDurableAutoExtractor(options) {
36825
36933
  for (const message of loaded)
36826
36934
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
36827
36935
  await database.write(async () => {
36936
+ if (disposed) return;
36828
36937
  const current = idsOf(job);
36829
36938
  const acknowledged = ids.filter((id) => current.includes(id));
36830
36939
  if (!acknowledged.length) return;
@@ -36997,6 +37106,7 @@ function createDurableAutoExtractor(options) {
36997
37106
  jobScope,
36998
37107
  jobFolderId
36999
37108
  );
37109
+ if (disposed) throw new Error("Extraction session was disposed");
37000
37110
  options.onTurnComplete?.(result);
37001
37111
  return result;
37002
37112
  };
@@ -37033,6 +37143,7 @@ function createDurableAutoExtractor(options) {
37033
37143
  );
37034
37144
  scheduleAfterSuccess = true;
37035
37145
  } catch (error) {
37146
+ if (disposed) break;
37036
37147
  attempts.set(job.id, attempted + 1);
37037
37148
  retry = true;
37038
37149
  reportError(error, conversationId);
@@ -37055,6 +37166,14 @@ function createDurableAutoExtractor(options) {
37055
37166
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
37056
37167
  }
37057
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 });
37058
37177
  schedule(0);
37059
37178
  return {
37060
37179
  processTurn(messages, conversationId) {
@@ -37134,10 +37253,7 @@ function createDurableAutoExtractor(options) {
37134
37253
  return true;
37135
37254
  },
37136
37255
  isProcessing: () => running,
37137
- dispose() {
37138
- disposed = true;
37139
- if (timer !== void 0) clearTimeout(timer);
37140
- }
37256
+ dispose
37141
37257
  };
37142
37258
  }
37143
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