@anuma/sdk 7.0.7 → 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.
@@ -12292,6 +12292,11 @@ function parseTopics(value) {
12292
12292
  }
12293
12293
 
12294
12294
  // src/lib/db/entities/operations.ts
12295
+ async function assertCanWrite(ctx) {
12296
+ if (ctx.canWrite && !await ctx.canWrite()) {
12297
+ throw new Error("Memory source is no longer eligible");
12298
+ }
12299
+ }
12295
12300
  function entityToStored(e) {
12296
12301
  return {
12297
12302
  uniqueId: e.id,
@@ -12432,6 +12437,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
12432
12437
  let skipped = false;
12433
12438
  let entities = [];
12434
12439
  await ctx.database.write(async () => {
12440
+ await assertCanWrite(ctx);
12435
12441
  const {
12436
12442
  entities: byName,
12437
12443
  operations: entityOps,
@@ -12507,6 +12513,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
12507
12513
  let skipped = false;
12508
12514
  let entities = [];
12509
12515
  await ctx.database.write(async () => {
12516
+ await assertCanWrite(ctx);
12510
12517
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
12511
12518
  if (options.guarded && autoLinkBlocked(vaultRow)) {
12512
12519
  skipped = true;
@@ -12567,6 +12574,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
12567
12574
  const links = await ctx.memoryEntityCollection.query(import_watermelondb11.Q.where("memory_id", import_watermelondb11.Q.oneOf(Array.from(memoryIds)))).fetch();
12568
12575
  if (links.length === 0) return;
12569
12576
  await ctx.database.write(async () => {
12577
+ await assertCanWrite(ctx);
12570
12578
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
12571
12579
  });
12572
12580
  }
@@ -12575,6 +12583,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
12575
12583
  const links = await ctx.memoryEntityCollection.query(import_watermelondb11.Q.where("user_id", userId)).fetch();
12576
12584
  if (links.length === 0) return;
12577
12585
  await ctx.database.write(async () => {
12586
+ await assertCanWrite(ctx);
12578
12587
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
12579
12588
  });
12580
12589
  }
@@ -12594,6 +12603,7 @@ async function backfillMemoryEntityUserIdsOp(ctx, vaultMemoryCollection) {
12594
12603
  }
12595
12604
  if (toUpdate.length === 0) return 0;
12596
12605
  await ctx.database.write(async () => {
12606
+ await assertCanWrite(ctx);
12597
12607
  await ctx.database.batch(
12598
12608
  ...toUpdate.map(({ link, userId }) => link.prepareUpdate((r) => r._setRaw("user_id", userId)))
12599
12609
  );
@@ -14998,6 +15008,11 @@ function visibilityConditions(requested) {
14998
15008
  function isKinded(record2) {
14999
15009
  return record2.kind !== null && record2.kind !== void 0;
15000
15010
  }
15011
+ async function assertCanWrite2(ctx) {
15012
+ if (ctx.canWrite && !await ctx.canWrite()) {
15013
+ throw new Error("Memory source is no longer eligible");
15014
+ }
15015
+ }
15001
15016
  function assertValidKindLevel(kind, level) {
15002
15017
  const hasKind = kind !== null && kind !== void 0;
15003
15018
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -15178,8 +15193,7 @@ async function createVaultMemoryOp(ctx, opts) {
15178
15193
  ctx.embeddedWalletSigner
15179
15194
  ) : opts.content;
15180
15195
  const created = await ctx.database.write(async () => {
15181
- if (ctx.canWrite && !await ctx.canWrite())
15182
- throw new Error("Memory source is no longer eligible");
15196
+ await assertCanWrite2(ctx);
15183
15197
  return ctx.vaultMemoryCollection.create(
15184
15198
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
15185
15199
  );
@@ -15198,8 +15212,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
15198
15212
  ) : opts.content;
15199
15213
  let createdRecord = null;
15200
15214
  await ctx.database.write(async () => {
15201
- if (ctx.canWrite && !await ctx.canWrite())
15202
- throw new Error("Memory source is no longer eligible");
15215
+ await assertCanWrite2(ctx);
15203
15216
  let target;
15204
15217
  try {
15205
15218
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -15291,8 +15304,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
15291
15304
  })
15292
15305
  );
15293
15306
  const created = await ctx.database.write(async () => {
15294
- if (ctx.canWrite && !await ctx.canWrite())
15295
- throw new Error("Memory source is no longer eligible");
15307
+ await assertCanWrite2(ctx);
15296
15308
  const prepared = optionsArray.map(
15297
15309
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
15298
15310
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -15597,8 +15609,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
15597
15609
  const record2 = probe;
15598
15610
  const originalUpdatedAt = record2.updatedAt.getTime();
15599
15611
  await ctx.database.write(async () => {
15600
- if (ctx.canWrite && !await ctx.canWrite())
15601
- throw new Error("Memory source is no longer eligible");
15612
+ await assertCanWrite2(ctx);
15602
15613
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
15603
15614
  stale = true;
15604
15615
  return;
@@ -15709,6 +15720,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
15709
15720
  let stale = false;
15710
15721
  const originalUpdatedAt = record2.updatedAt.getTime();
15711
15722
  await ctx.database.write(async (writer) => {
15723
+ await assertCanWrite2(ctx);
15712
15724
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15713
15725
  stale = true;
15714
15726
  return;
@@ -15750,6 +15762,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
15750
15762
  const originalUpdatedAt = record2.updatedAt.getTime();
15751
15763
  let cleared = false;
15752
15764
  await ctx.database.write(async () => {
15765
+ await assertCanWrite2(ctx);
15753
15766
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
15754
15767
  cleared = true;
15755
15768
  await record2.update((r) => {
@@ -15773,6 +15786,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
15773
15786
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
15774
15787
  let stale = false;
15775
15788
  await ctx.database.write(async () => {
15789
+ await assertCanWrite2(ctx);
15776
15790
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15777
15791
  stale = true;
15778
15792
  return;
@@ -15801,6 +15815,7 @@ async function deleteVaultMemoryOp(ctx, id) {
15801
15815
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
15802
15816
  let stale = false;
15803
15817
  await ctx.database.write(async () => {
15818
+ await assertCanWrite2(ctx);
15804
15819
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15805
15820
  stale = true;
15806
15821
  return;
@@ -15838,8 +15853,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
15838
15853
  }
15839
15854
  let stale = false;
15840
15855
  await ctx.database.write(async () => {
15841
- if (ctx.canWrite && !await ctx.canWrite())
15842
- throw new Error("Memory source is no longer eligible");
15856
+ await assertCanWrite2(ctx);
15843
15857
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
15844
15858
  stale = true;
15845
15859
  return;
@@ -16001,6 +16015,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
16001
16015
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
16002
16016
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
16003
16017
  await ctx.database.write(async () => {
16018
+ await assertCanWrite2(ctx);
16004
16019
  const records = [];
16005
16020
  for (const id of chunkIds) {
16006
16021
  try {
@@ -16034,6 +16049,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
16034
16049
  for (const id of Array.from(new Set(memoryIds))) {
16035
16050
  try {
16036
16051
  await ctx.database.write(async (writer) => {
16052
+ await assertCanWrite2(ctx);
16037
16053
  let record2;
16038
16054
  try {
16039
16055
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -16064,6 +16080,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
16064
16080
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
16065
16081
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
16066
16082
  await ctx.database.write(async () => {
16083
+ await assertCanWrite2(ctx);
16067
16084
  const writes = [];
16068
16085
  for (const id of chunkIds) {
16069
16086
  let record2;
@@ -16096,6 +16113,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
16096
16113
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb23.Q.where("user_id", userId), import_watermelondb23.Q.where("is_deleted", false)).fetch();
16097
16114
  if (records.length === 0) return 0;
16098
16115
  await ctx.database.write(async () => {
16116
+ await assertCanWrite2(ctx);
16099
16117
  const prepared = records.map(
16100
16118
  (record2) => record2.prepareUpdate((r) => {
16101
16119
  r._setRaw("is_deleted", true);
@@ -16150,6 +16168,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
16150
16168
  let stale = false;
16151
16169
  const archivedAtValue = opts?.now ?? Date.now();
16152
16170
  await ctx.database.write(async () => {
16171
+ await assertCanWrite2(ctx);
16153
16172
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
16154
16173
  stale = true;
16155
16174
  return;
@@ -16173,6 +16192,7 @@ async function restoreVaultMemoryOp(ctx, id) {
16173
16192
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
16174
16193
  let stale = false;
16175
16194
  await ctx.database.write(async () => {
16195
+ await assertCanWrite2(ctx);
16176
16196
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
16177
16197
  stale = true;
16178
16198
  return;
@@ -16193,6 +16213,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
16193
16213
  const now = opts.now ?? Date.now();
16194
16214
  let stale = false;
16195
16215
  await ctx.database.write(async () => {
16216
+ await assertCanWrite2(ctx);
16196
16217
  const archivedAt = record2.archivedAt;
16197
16218
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
16198
16219
  stale = true;
@@ -16232,6 +16253,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
16232
16253
  }
16233
16254
  let written = false;
16234
16255
  await ctx.database.write(async () => {
16256
+ await assertCanWrite2(ctx);
16235
16257
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
16236
16258
  const originalUpdatedAt = record2.updatedAt.getTime();
16237
16259
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -20228,6 +20250,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
20228
20250
  ).map((c) => c.candidate);
20229
20251
  }
20230
20252
  async function extractAndRetain(messages, retainCtx, options) {
20253
+ const { memoryStore, signal } = options;
20254
+ if (memoryStore === null) throw new Error("Memory store is not ready");
20255
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
20256
+ throw new Error("Remote memory extraction does not support folders");
20257
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
20258
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
20259
+ signal?.throwIfAborted();
20231
20260
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
20232
20261
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
20233
20262
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -20261,6 +20290,7 @@ async function extractAndRetain(messages, retainCtx, options) {
20261
20290
  }
20262
20291
  });
20263
20292
  const extractMs = Date.now() - tExtract;
20293
+ signal?.throwIfAborted();
20264
20294
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
20265
20295
  const log = getLogger();
20266
20296
  const screened = screenCandidatesForInjection(filtered);
@@ -20308,16 +20338,33 @@ async function extractAndRetain(messages, retainCtx, options) {
20308
20338
  const results = [];
20309
20339
  const quarantinedInfo = [];
20310
20340
  let failedWrites = 0;
20341
+ let auditRows;
20311
20342
  const tRetain = Date.now();
20312
20343
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
20313
20344
  try {
20345
+ signal?.throwIfAborted();
20314
20346
  if (isQuarantined) {
20315
- const existingId = await findQuarantinedDuplicateOp(
20316
- retainCtx.vaultCtx,
20317
- candidate.content,
20318
- candidate.sourceMessageIds,
20319
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
20320
- ).catch(() => null);
20347
+ let existingId;
20348
+ if (memoryStore) {
20349
+ auditRows ?? (auditRows = memoryStore.list({
20350
+ scopes: [options.scope ?? "private"],
20351
+ includeArchived: true,
20352
+ includeQuarantined: true,
20353
+ includeSuperseded: true
20354
+ }));
20355
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
20356
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
20357
+ existingId = (await auditRows).find(
20358
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
20359
+ )?.uniqueId ?? null;
20360
+ } else {
20361
+ existingId = await findQuarantinedDuplicateOp(
20362
+ retainCtx.vaultCtx,
20363
+ candidate.content,
20364
+ candidate.sourceMessageIds,
20365
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
20366
+ ).catch(() => null);
20367
+ }
20321
20368
  if (existingId) {
20322
20369
  quarantinedInfo.push({
20323
20370
  candidate,
@@ -20328,18 +20375,26 @@ async function extractAndRetain(messages, retainCtx, options) {
20328
20375
  continue;
20329
20376
  }
20330
20377
  }
20331
- const result = await retain(candidate.content, retainCtx, {
20378
+ const retainOptions = {
20332
20379
  source: "auto-extracted",
20333
20380
  sourceChunkIds: candidate.sourceMessageIds,
20334
20381
  respectTombstones: true,
20335
20382
  ...options.scope !== void 0 && { scope: options.scope },
20336
- ...options.folderId !== void 0 && { folderId: options.folderId },
20337
20383
  ...consolidateOptions !== void 0 && { consolidateOptions },
20338
20384
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
20339
20385
  factType: candidate.type,
20340
20386
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
20387
+ };
20388
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
20389
+ ...retainOptions,
20390
+ canWrite: retainCtx.vaultCtx.canWrite,
20391
+ signal
20392
+ }) : await retain(candidate.content, retainCtx, {
20393
+ ...retainOptions,
20394
+ ...options.folderId !== void 0 && { folderId: options.folderId }
20341
20395
  });
20342
20396
  if (isQuarantined) {
20397
+ auditRows = void 0;
20343
20398
  const info = {
20344
20399
  candidate,
20345
20400
  memoryId: result.memoryId,
@@ -20358,9 +20413,16 @@ async function extractAndRetain(messages, retainCtx, options) {
20358
20413
  }
20359
20414
  succeededCandidates.push(candidate);
20360
20415
  results.push(result);
20361
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
20416
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
20362
20417
  try {
20363
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
20418
+ signal?.throwIfAborted();
20419
+ if (memoryStore) {
20420
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
20421
+ unlessUserManaged: true,
20422
+ canWrite: retainCtx.vaultCtx.canWrite,
20423
+ signal
20424
+ });
20425
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
20364
20426
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
20365
20427
  unlessTopicsUserManaged: true
20366
20428
  });
@@ -20610,7 +20672,7 @@ function createAutoExtractor(options) {
20610
20672
  };
20611
20673
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
20612
20674
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
20613
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
20675
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
20614
20676
  getLogger().warn(
20615
20677
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
20616
20678
  );
@@ -20655,6 +20717,8 @@ function createAutoExtractor(options) {
20655
20717
  try {
20656
20718
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
20657
20719
  extract,
20720
+ memoryStore: options.memoryStore,
20721
+ signal: options.signal,
20658
20722
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
20659
20723
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
20660
20724
  ...options.scope !== void 0 && { scope: options.scope },
@@ -22786,6 +22850,7 @@ function createLocalMemoryStore(options) {
22786
22850
  database,
22787
22851
  entityCollection: database.get("entity"),
22788
22852
  memoryEntityCollection: database.get("memory_entity"),
22853
+ canWrite: options.canWrite,
22789
22854
  ...userId !== void 0 && { userId },
22790
22855
  ...options.allowUnscopedRows !== void 0 && {
22791
22856
  allowUnscopedRows: options.allowUnscopedRows
@@ -22797,10 +22862,21 @@ function createLocalMemoryStore(options) {
22797
22862
  walletAddress: options.walletAddress,
22798
22863
  signMessage: options.signMessage,
22799
22864
  embeddedWalletSigner: options.embeddedWalletSigner,
22865
+ canWrite: options.canWrite,
22800
22866
  ...userId !== void 0 && { userId },
22801
22867
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
22802
22868
  entityCtx
22803
22869
  };
22870
+ const assertWrite = async (writeOptions) => {
22871
+ writeOptions?.signal?.throwIfAborted();
22872
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
22873
+ throw new Error("Memory source is no longer eligible");
22874
+ }
22875
+ if (options.canWrite && !await options.canWrite()) {
22876
+ throw new Error("Memory source is no longer eligible");
22877
+ }
22878
+ writeOptions?.signal?.throwIfAborted();
22879
+ };
22804
22880
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
22805
22881
  const ownedBy = userId !== void 0 ? [import_watermelondb32.Q.where("user_id", userId)] : [];
22806
22882
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [import_watermelondb32.Q.or(import_watermelondb32.Q.where("user_id", userId), import_watermelondb32.Q.where("user_id", null))] : [import_watermelondb32.Q.where("user_id", userId)];
@@ -22912,9 +22988,25 @@ function createLocalMemoryStore(options) {
22912
22988
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
22913
22989
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
22914
22990
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
22915
- addTopics: (memoryId, topics) => database.write(async (writer) => {
22991
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
22992
+ await assertWrite(writeOptions);
22916
22993
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb32.Q.where("id", memoryId), import_watermelondb32.Q.where("is_deleted", false), ...ownedBy).fetchCount();
22917
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
22994
+ return owned > 0 ? writer.callWriter(
22995
+ () => linkMemoryEntitiesOp(
22996
+ {
22997
+ ...entityCtx,
22998
+ canWrite: async () => {
22999
+ await assertWrite(writeOptions);
23000
+ return true;
23001
+ }
23002
+ },
23003
+ memoryId,
23004
+ topics,
23005
+ {
23006
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
23007
+ }
23008
+ )
23009
+ ) : [];
22918
23010
  }),
22919
23011
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
22920
23012
  recall: (query, recallOptions) => recall(
@@ -22924,7 +23016,17 @@ function createLocalMemoryStore(options) {
22924
23016
  ),
22925
23017
  retain: (content, retainOptions) => retain(
22926
23018
  content,
22927
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
23019
+ {
23020
+ vaultCtx: {
23021
+ ...vaultCtx,
23022
+ canWrite: async () => {
23023
+ await assertWrite(retainOptions);
23024
+ return true;
23025
+ }
23026
+ },
23027
+ embeddingOptions: options.embeddingOptions,
23028
+ vaultCache
23029
+ },
22928
23030
  retainOptions
22929
23031
  ),
22930
23032
  factSource: {
@@ -23579,7 +23681,14 @@ function stored(row) {
23579
23681
  function createRemoteMemoryPipeline(options) {
23580
23682
  const { persistence, embeddingOptions } = options;
23581
23683
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
23582
- const operation = (mode, queryOptions = embeddingOptions) => {
23684
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
23685
+ const signal = writeOptions?.signal;
23686
+ const assertWrite = async () => {
23687
+ signal?.throwIfAborted();
23688
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
23689
+ throw new Error("Memory source is no longer eligible");
23690
+ signal?.throwIfAborted();
23691
+ };
23583
23692
  const rejectUndecryptable = (failed) => {
23584
23693
  if (!failed.length) return;
23585
23694
  if (mode === "retain")
@@ -23597,7 +23706,7 @@ function createRemoteMemoryPipeline(options) {
23597
23706
  return items.map((item) => stored(item.memory));
23598
23707
  };
23599
23708
  const get = async (id) => {
23600
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
23709
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
23601
23710
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
23602
23711
  snapshots.set(id, snapshot);
23603
23712
  return stored(snapshot.memory);
@@ -23647,7 +23756,7 @@ function createRemoteMemoryPipeline(options) {
23647
23756
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
23648
23757
  }
23649
23758
  const forceIds = [...forced];
23650
- const windowOptions = (i, signal) => ({
23759
+ const windowOptions = (i, signal2) => ({
23651
23760
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
23652
23761
  force_ids: i === 0 ? forceIds : [],
23653
23762
  ...search.scopes && { scopes: search.scopes },
@@ -23655,13 +23764,15 @@ function createRemoteMemoryPipeline(options) {
23655
23764
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
23656
23765
  include_archived: search.includeArchived,
23657
23766
  embedding_model: model,
23658
- ...signal && { signal }
23767
+ ...signal2 && { signal: signal2 }
23659
23768
  });
23660
23769
  const vectors = [queryEmbedding, ...facetEmbeddings];
23661
23770
  let windows;
23662
23771
  if (mode === "retain") {
23663
23772
  windows = await Promise.all(
23664
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
23773
+ vectors.map(
23774
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
23775
+ )
23665
23776
  );
23666
23777
  } else {
23667
23778
  const deadline = new AbortController();
@@ -23749,12 +23860,15 @@ function createRemoteMemoryPipeline(options) {
23749
23860
  twin_opt_in: false
23750
23861
  };
23751
23862
  };
23752
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
23863
+ const write = async (memory, snapshot) => {
23864
+ await assertWrite();
23865
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
23866
+ };
23753
23867
  const port = {
23754
23868
  prepare,
23755
23869
  get,
23756
23870
  getFresh: async (id) => {
23757
- const current = await persistence.get(id);
23871
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
23758
23872
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
23759
23873
  },
23760
23874
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -23765,7 +23879,8 @@ function createRemoteMemoryPipeline(options) {
23765
23879
  embedding_model: embeddingModel,
23766
23880
  strict_model: true,
23767
23881
  include_deleted: true,
23768
- deleted_only: true
23882
+ deleted_only: true,
23883
+ signal
23769
23884
  });
23770
23885
  rejectUndecryptable(window2.failed);
23771
23886
  return remember(window2.items);
@@ -23821,14 +23936,18 @@ function createRemoteMemoryPipeline(options) {
23821
23936
  if (!target || !newer || id === successor) return false;
23822
23937
  const old = snapshots.get(id);
23823
23938
  const next = snapshots.get(successor);
23939
+ await assertWrite();
23824
23940
  remember(
23825
- await persistence.putMany([
23826
- {
23827
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
23828
- expectedVersion: old
23829
- },
23830
- { memory: next.memory, expectedVersion: next }
23831
- ])
23941
+ await persistence.putMany(
23942
+ [
23943
+ {
23944
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
23945
+ expectedVersion: old
23946
+ },
23947
+ { memory: next.memory, expectedVersion: next }
23948
+ ],
23949
+ signal
23950
+ )
23832
23951
  );
23833
23952
  return true;
23834
23953
  },
@@ -23837,18 +23956,22 @@ function createRemoteMemoryPipeline(options) {
23837
23956
  if (!target) return { created: null, retired: false };
23838
23957
  const snapshot = snapshots.get(targetId);
23839
23958
  const successor = createRow(input);
23959
+ await assertWrite();
23840
23960
  const rows = remember(
23841
- await persistence.putMany([
23842
- { memory: successor, expectedVersion: 0 },
23843
- {
23844
- memory: {
23845
- ...snapshot.memory,
23846
- superseded_by: successor.memory_id,
23847
- superseded_at: Date.now()
23848
- },
23849
- expectedVersion: snapshot
23850
- }
23851
- ])
23961
+ await persistence.putMany(
23962
+ [
23963
+ { memory: successor, expectedVersion: 0 },
23964
+ {
23965
+ memory: {
23966
+ ...snapshot.memory,
23967
+ superseded_by: successor.memory_id,
23968
+ superseded_at: Date.now()
23969
+ },
23970
+ expectedVersion: snapshot
23971
+ }
23972
+ ],
23973
+ signal
23974
+ )
23852
23975
  );
23853
23976
  return { created: rows[0], retired: true };
23854
23977
  }
@@ -23883,7 +24006,7 @@ function createRemoteMemoryPipeline(options) {
23883
24006
  factSource,
23884
24007
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
23885
24008
  retain: async (content, retainOptions) => {
23886
- const { port } = operation("retain");
24009
+ const { port } = operation("retain", embeddingOptions, retainOptions);
23887
24010
  return retainWithPersistence(
23888
24011
  content,
23889
24012
  {
@@ -24545,16 +24668,21 @@ function createRemoteMemoryStore(options) {
24545
24668
  const record2 = await persistence.get(id);
24546
24669
  return record2 && !record2.memory.is_deleted ? record2 : null;
24547
24670
  };
24548
- const mutateLocked = async (id, change, edit) => {
24549
- const record2 = await live(id);
24671
+ const mutateLocked = async (id, change, edit, writeOptions) => {
24672
+ writeOptions?.signal?.throwIfAborted();
24673
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
24550
24674
  if (!record2) return null;
24675
+ if (record2.memory.is_deleted) return null;
24676
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
24677
+ throw new Error("Memory source is no longer eligible");
24678
+ writeOptions?.signal?.throwIfAborted();
24551
24679
  const next = edit(structuredClone(record2.memory));
24552
24680
  if (!next) return null;
24553
- const saved = await persistence.put(next, record2);
24681
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
24554
24682
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
24555
24683
  return saved;
24556
24684
  };
24557
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
24685
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
24558
24686
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
24559
24687
  const mutateMany = async (ids, change, edit) => {
24560
24688
  const saved = /* @__PURE__ */ new Map();
@@ -25067,13 +25195,21 @@ function createRemoteMemoryStore(options) {
25067
25195
  }))
25068
25196
  );
25069
25197
  },
25070
- addTopics: async (memoryId, topics) => {
25198
+ addTopics: async (memoryId, topics, writeOptions) => {
25071
25199
  const added = await withKnownKinds(topicInputs(topics, "auto"));
25072
- const saved = await mutate(memoryId, "edit", (memory) => ({
25073
- ...memory,
25074
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
25075
- topics_updated_at: Date.now()
25076
- }));
25200
+ const saved = await mutate(
25201
+ memoryId,
25202
+ "edit",
25203
+ (memory) => {
25204
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
25205
+ return {
25206
+ ...memory,
25207
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
25208
+ topics_updated_at: Date.now()
25209
+ };
25210
+ },
25211
+ writeOptions
25212
+ );
25077
25213
  if (!saved) return [];
25078
25214
  const now = /* @__PURE__ */ new Date();
25079
25215
  return mergeTopics([], added).map(
@@ -25926,6 +26062,7 @@ function createDurableAutoExtractor(options) {
25926
26062
  };
25927
26063
  const folderId = options.folderId ?? null;
25928
26064
  let disposed = false;
26065
+ let activeBatch = null;
25929
26066
  let running = false;
25930
26067
  let wakeRequested = false;
25931
26068
  let timer;
@@ -25962,14 +26099,18 @@ function createDurableAutoExtractor(options) {
25962
26099
  }, delay);
25963
26100
  }
25964
26101
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
26102
+ if (disposed) throw new Error("Extraction session was disposed");
25965
26103
  let watchdog;
25966
26104
  let cancelled = false;
26105
+ const controller = new AbortController();
26106
+ activeBatch = controller;
25967
26107
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
25968
26108
  try {
25969
26109
  return await Promise.race([
25970
26110
  new Promise((resolve, reject) => {
25971
26111
  const worker = createAutoExtractor({
25972
26112
  ...options,
26113
+ signal: controller.signal,
25973
26114
  retainCtx: {
25974
26115
  ...options.retainCtx,
25975
26116
  vaultCtx: {
@@ -25998,6 +26139,8 @@ function createDurableAutoExtractor(options) {
25998
26139
  ]);
25999
26140
  } finally {
26000
26141
  cancelled = true;
26142
+ controller.abort();
26143
+ if (activeBatch === controller) activeBatch = null;
26001
26144
  if (watchdog !== void 0) clearTimeout(watchdog);
26002
26145
  }
26003
26146
  }
@@ -26009,6 +26152,7 @@ function createDurableAutoExtractor(options) {
26009
26152
  for (const message of loaded)
26010
26153
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
26011
26154
  await database.write(async () => {
26155
+ if (disposed) return;
26012
26156
  const current = idsOf(job);
26013
26157
  const acknowledged = ids.filter((id) => current.includes(id));
26014
26158
  if (!acknowledged.length) return;
@@ -26181,6 +26325,7 @@ function createDurableAutoExtractor(options) {
26181
26325
  jobScope,
26182
26326
  jobFolderId
26183
26327
  );
26328
+ if (disposed) throw new Error("Extraction session was disposed");
26184
26329
  options.onTurnComplete?.(result);
26185
26330
  return result;
26186
26331
  };
@@ -26217,6 +26362,7 @@ function createDurableAutoExtractor(options) {
26217
26362
  );
26218
26363
  scheduleAfterSuccess = true;
26219
26364
  } catch (error) {
26365
+ if (disposed) break;
26220
26366
  attempts.set(job.id, attempted + 1);
26221
26367
  retry = true;
26222
26368
  reportError(error, conversationId);
@@ -26239,6 +26385,14 @@ function createDurableAutoExtractor(options) {
26239
26385
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
26240
26386
  }
26241
26387
  }
26388
+ function dispose() {
26389
+ disposed = true;
26390
+ activeBatch?.abort(options.signal?.reason);
26391
+ if (timer !== void 0) clearTimeout(timer);
26392
+ options.signal?.removeEventListener("abort", dispose);
26393
+ }
26394
+ if (options.signal?.aborted) dispose();
26395
+ else options.signal?.addEventListener("abort", dispose, { once: true });
26242
26396
  schedule(0);
26243
26397
  return {
26244
26398
  processTurn(messages, conversationId) {
@@ -26318,10 +26472,7 @@ function createDurableAutoExtractor(options) {
26318
26472
  return true;
26319
26473
  },
26320
26474
  isProcessing: () => running,
26321
- dispose() {
26322
- disposed = true;
26323
- if (timer !== void 0) clearTimeout(timer);
26324
- }
26475
+ dispose
26325
26476
  };
26326
26477
  }
26327
26478
 
@@ -32592,6 +32743,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
32592
32743
  var NO_CONVERSATION_KEY = "__no_conversation__";
32593
32744
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
32594
32745
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
32746
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
32595
32747
  var conversationRedactors = /* @__PURE__ */ new Map();
32596
32748
  function getConversationRedactor(conversationId, nerDetector) {
32597
32749
  const key = conversationId ?? NO_CONVERSATION_KEY;
@@ -32682,7 +32834,7 @@ function useChatStorage(options) {
32682
32834
  () => ({ getToken, baseUrl, model: embeddingModel, maskInput: maskEmbeddingInput }),
32683
32835
  [getToken, baseUrl, embeddingModel, maskEmbeddingInput]
32684
32836
  );
32685
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
32837
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
32686
32838
  const blobManagerRef = (0, import_react6.useRef)(new BlobUrlManager());
32687
32839
  (0, import_react6.useEffect)(() => {
32688
32840
  const manager = blobManagerRef.current;
@@ -32959,6 +33111,7 @@ function useChatStorage(options) {
32959
33111
  );
32960
33112
  const retainVaultMemory = (0, import_react6.useCallback)(
32961
33113
  async (input) => {
33114
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32962
33115
  if (memoryStore) {
32963
33116
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32964
33117
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -32990,6 +33143,7 @@ function useChatStorage(options) {
32990
33143
  );
32991
33144
  const createMemoryVaultTool2 = (0, import_react6.useCallback)(
32992
33145
  (options2) => {
33146
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32993
33147
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32994
33148
  const embOpts = getToken ? vaultEmbeddingOptions : void 0;
32995
33149
  return createMemoryVaultTool(
@@ -33011,6 +33165,7 @@ function useChatStorage(options) {
33011
33165
  );
33012
33166
  const getVaultMemories = (0, import_react6.useCallback)(
33013
33167
  (options2) => {
33168
+ if (memoryStore === null) return Promise.resolve([]);
33014
33169
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
33015
33170
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
33016
33171
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -33022,6 +33177,7 @@ function useChatStorage(options) {
33022
33177
  );
33023
33178
  const createVaultMemory = (0, import_react6.useCallback)(
33024
33179
  async (content, scope) => {
33180
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
33025
33181
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
33026
33182
  if (memoryStore) return memoryStore.create({ content, scope });
33027
33183
  const result = await createVaultMemoryOp(vaultCtx, { content, scope });
@@ -33043,6 +33199,7 @@ function useChatStorage(options) {
33043
33199
  );
33044
33200
  const updateVaultMemory = (0, import_react6.useCallback)(
33045
33201
  async (id, content, scope) => {
33202
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
33046
33203
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
33047
33204
  if (memoryStore) return memoryStore.update(id, { content, scope });
33048
33205
  const existing = await getVaultMemoryOp(vaultCtx, id);
@@ -33068,6 +33225,7 @@ function useChatStorage(options) {
33068
33225
  );
33069
33226
  const deleteVaultMemory = (0, import_react6.useCallback)(
33070
33227
  async (id) => {
33228
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
33071
33229
  if (memoryStore) return memoryStore.delete(id);
33072
33230
  const existing = await getVaultMemoryOp(vaultCtx, id);
33073
33231
  const result = await deleteVaultMemoryOp(vaultCtx, id);
@@ -33081,7 +33239,7 @@ function useChatStorage(options) {
33081
33239
  const chunkVectorCacheRef = (0, import_react6.useRef)(createChunkVectorCache());
33082
33240
  const clientToolEmbeddingsCacheRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
33083
33241
  (0, import_react6.useEffect)(() => {
33084
- if (!getToken || memoryStore) return;
33242
+ if (!getToken || memoryStore !== void 0) return;
33085
33243
  void (async () => {
33086
33244
  try {
33087
33245
  await preEmbedVaultMemories(vaultCtx, vaultEmbeddingOptions, vaultEmbeddingCache);
@@ -33097,6 +33255,7 @@ function useChatStorage(options) {
33097
33255
  }, [vaultEmbeddingCache]);
33098
33256
  const createMemoryVaultSearchTool2 = (0, import_react6.useCallback)(
33099
33257
  (searchOptions) => {
33258
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
33100
33259
  if (!getToken && !memoryStore) {
33101
33260
  throw new Error("getToken is required for memory vault search tool");
33102
33261
  }
@@ -33112,6 +33271,7 @@ function useChatStorage(options) {
33112
33271
  );
33113
33272
  const createRecallTool2 = (0, import_react6.useCallback)(
33114
33273
  (toolOptions, callbacks) => {
33274
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
33115
33275
  if (!getToken && !memoryStore) {
33116
33276
  throw new Error("getToken is required for recall tool");
33117
33277
  }
@@ -33151,6 +33311,7 @@ function useChatStorage(options) {
33151
33311
  );
33152
33312
  const searchVaultMemoriesFn = (0, import_react6.useCallback)(
33153
33313
  async (query, searchOptions) => {
33314
+ if (memoryStore === null) return [];
33154
33315
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
33155
33316
  if (memoryStore) return (await memoryStore.factSource.search(query, searchOptions)).results;
33156
33317
  if (!getToken) return [];
@@ -33166,7 +33327,7 @@ function useChatStorage(options) {
33166
33327
  );
33167
33328
  const recallFn = (0, import_react6.useCallback)(
33168
33329
  async (query, options2) => {
33169
- if (!getToken && !memoryStore) {
33330
+ if (memoryStore === null || !getToken && !memoryStore) {
33170
33331
  return {
33171
33332
  memories: [],
33172
33333
  usedBudget: options2?.budget ?? "low",