@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.
@@ -12722,6 +12722,11 @@ init_logger();
12722
12722
 
12723
12723
  // src/lib/db/entities/operations.ts
12724
12724
  var import_watermelondb19 = require("@nozbe/watermelondb");
12725
+ async function assertCanWrite(ctx) {
12726
+ if (ctx.canWrite && !await ctx.canWrite()) {
12727
+ throw new Error("Memory source is no longer eligible");
12728
+ }
12729
+ }
12725
12730
  function entityToStored(e) {
12726
12731
  return {
12727
12732
  uniqueId: e.id,
@@ -12862,6 +12867,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
12862
12867
  let skipped = false;
12863
12868
  let entities = [];
12864
12869
  await ctx.database.write(async () => {
12870
+ await assertCanWrite(ctx);
12865
12871
  const {
12866
12872
  entities: byName,
12867
12873
  operations: entityOps,
@@ -12937,6 +12943,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
12937
12943
  let skipped = false;
12938
12944
  let entities = [];
12939
12945
  await ctx.database.write(async () => {
12946
+ await assertCanWrite(ctx);
12940
12947
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
12941
12948
  if (options.guarded && autoLinkBlocked(vaultRow)) {
12942
12949
  skipped = true;
@@ -12997,6 +13004,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
12997
13004
  const links = await ctx.memoryEntityCollection.query(import_watermelondb19.Q.where("memory_id", import_watermelondb19.Q.oneOf(Array.from(memoryIds)))).fetch();
12998
13005
  if (links.length === 0) return;
12999
13006
  await ctx.database.write(async () => {
13007
+ await assertCanWrite(ctx);
13000
13008
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
13001
13009
  });
13002
13010
  }
@@ -13005,6 +13013,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
13005
13013
  const links = await ctx.memoryEntityCollection.query(import_watermelondb19.Q.where("user_id", userId)).fetch();
13006
13014
  if (links.length === 0) return;
13007
13015
  await ctx.database.write(async () => {
13016
+ await assertCanWrite(ctx);
13008
13017
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
13009
13018
  });
13010
13019
  }
@@ -13180,6 +13189,11 @@ function visibilityConditions(requested) {
13180
13189
  function isKinded(record2) {
13181
13190
  return record2.kind !== null && record2.kind !== void 0;
13182
13191
  }
13192
+ async function assertCanWrite2(ctx) {
13193
+ if (ctx.canWrite && !await ctx.canWrite()) {
13194
+ throw new Error("Memory source is no longer eligible");
13195
+ }
13196
+ }
13183
13197
  function assertValidKindLevel(kind, level) {
13184
13198
  const hasKind = kind !== null && kind !== void 0;
13185
13199
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -13360,8 +13374,7 @@ async function createVaultMemoryOp(ctx, opts) {
13360
13374
  ctx.embeddedWalletSigner
13361
13375
  ) : opts.content;
13362
13376
  const created = await ctx.database.write(async () => {
13363
- if (ctx.canWrite && !await ctx.canWrite())
13364
- throw new Error("Memory source is no longer eligible");
13377
+ await assertCanWrite2(ctx);
13365
13378
  return ctx.vaultMemoryCollection.create(
13366
13379
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
13367
13380
  );
@@ -13380,8 +13393,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
13380
13393
  ) : opts.content;
13381
13394
  let createdRecord = null;
13382
13395
  await ctx.database.write(async () => {
13383
- if (ctx.canWrite && !await ctx.canWrite())
13384
- throw new Error("Memory source is no longer eligible");
13396
+ await assertCanWrite2(ctx);
13385
13397
  let target;
13386
13398
  try {
13387
13399
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -13473,8 +13485,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
13473
13485
  })
13474
13486
  );
13475
13487
  const created = await ctx.database.write(async () => {
13476
- if (ctx.canWrite && !await ctx.canWrite())
13477
- throw new Error("Memory source is no longer eligible");
13488
+ await assertCanWrite2(ctx);
13478
13489
  const prepared = optionsArray.map(
13479
13490
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
13480
13491
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -13779,8 +13790,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
13779
13790
  const record2 = probe;
13780
13791
  const originalUpdatedAt = record2.updatedAt.getTime();
13781
13792
  await ctx.database.write(async () => {
13782
- if (ctx.canWrite && !await ctx.canWrite())
13783
- throw new Error("Memory source is no longer eligible");
13793
+ await assertCanWrite2(ctx);
13784
13794
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
13785
13795
  stale = true;
13786
13796
  return;
@@ -13891,6 +13901,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
13891
13901
  let stale = false;
13892
13902
  const originalUpdatedAt = record2.updatedAt.getTime();
13893
13903
  await ctx.database.write(async (writer) => {
13904
+ await assertCanWrite2(ctx);
13894
13905
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13895
13906
  stale = true;
13896
13907
  return;
@@ -13932,6 +13943,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
13932
13943
  const originalUpdatedAt = record2.updatedAt.getTime();
13933
13944
  let cleared = false;
13934
13945
  await ctx.database.write(async () => {
13946
+ await assertCanWrite2(ctx);
13935
13947
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
13936
13948
  cleared = true;
13937
13949
  await record2.update((r) => {
@@ -13955,6 +13967,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
13955
13967
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
13956
13968
  let stale = false;
13957
13969
  await ctx.database.write(async () => {
13970
+ await assertCanWrite2(ctx);
13958
13971
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13959
13972
  stale = true;
13960
13973
  return;
@@ -13983,6 +13996,7 @@ async function deleteVaultMemoryOp(ctx, id) {
13983
13996
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
13984
13997
  let stale = false;
13985
13998
  await ctx.database.write(async () => {
13999
+ await assertCanWrite2(ctx);
13986
14000
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13987
14001
  stale = true;
13988
14002
  return;
@@ -14020,8 +14034,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
14020
14034
  }
14021
14035
  let stale = false;
14022
14036
  await ctx.database.write(async () => {
14023
- if (ctx.canWrite && !await ctx.canWrite())
14024
- throw new Error("Memory source is no longer eligible");
14037
+ await assertCanWrite2(ctx);
14025
14038
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
14026
14039
  stale = true;
14027
14040
  return;
@@ -14183,6 +14196,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
14183
14196
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14184
14197
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14185
14198
  await ctx.database.write(async () => {
14199
+ await assertCanWrite2(ctx);
14186
14200
  const records = [];
14187
14201
  for (const id of chunkIds) {
14188
14202
  try {
@@ -14216,6 +14230,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
14216
14230
  for (const id of Array.from(new Set(memoryIds))) {
14217
14231
  try {
14218
14232
  await ctx.database.write(async (writer) => {
14233
+ await assertCanWrite2(ctx);
14219
14234
  let record2;
14220
14235
  try {
14221
14236
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -14246,6 +14261,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
14246
14261
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14247
14262
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14248
14263
  await ctx.database.write(async () => {
14264
+ await assertCanWrite2(ctx);
14249
14265
  const writes = [];
14250
14266
  for (const id of chunkIds) {
14251
14267
  let record2;
@@ -14278,6 +14294,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
14278
14294
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb20.Q.where("user_id", userId), import_watermelondb20.Q.where("is_deleted", false)).fetch();
14279
14295
  if (records.length === 0) return 0;
14280
14296
  await ctx.database.write(async () => {
14297
+ await assertCanWrite2(ctx);
14281
14298
  const prepared = records.map(
14282
14299
  (record2) => record2.prepareUpdate((r) => {
14283
14300
  r._setRaw("is_deleted", true);
@@ -14332,6 +14349,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
14332
14349
  let stale = false;
14333
14350
  const archivedAtValue = opts?.now ?? Date.now();
14334
14351
  await ctx.database.write(async () => {
14352
+ await assertCanWrite2(ctx);
14335
14353
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
14336
14354
  stale = true;
14337
14355
  return;
@@ -14355,6 +14373,7 @@ async function restoreVaultMemoryOp(ctx, id) {
14355
14373
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
14356
14374
  let stale = false;
14357
14375
  await ctx.database.write(async () => {
14376
+ await assertCanWrite2(ctx);
14358
14377
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
14359
14378
  stale = true;
14360
14379
  return;
@@ -14375,6 +14394,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
14375
14394
  const now = opts.now ?? Date.now();
14376
14395
  let stale = false;
14377
14396
  await ctx.database.write(async () => {
14397
+ await assertCanWrite2(ctx);
14378
14398
  const archivedAt = record2.archivedAt;
14379
14399
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
14380
14400
  stale = true;
@@ -14414,6 +14434,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
14414
14434
  }
14415
14435
  let written = false;
14416
14436
  await ctx.database.write(async () => {
14437
+ await assertCanWrite2(ctx);
14417
14438
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
14418
14439
  const originalUpdatedAt = record2.updatedAt.getTime();
14419
14440
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -17950,6 +17971,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
17950
17971
  ).map((c) => c.candidate);
17951
17972
  }
17952
17973
  async function extractAndRetain(messages, retainCtx, options) {
17974
+ const { memoryStore, signal } = options;
17975
+ if (memoryStore === null) throw new Error("Memory store is not ready");
17976
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
17977
+ throw new Error("Remote memory extraction does not support folders");
17978
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
17979
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
17980
+ signal?.throwIfAborted();
17953
17981
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
17954
17982
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
17955
17983
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -17983,6 +18011,7 @@ async function extractAndRetain(messages, retainCtx, options) {
17983
18011
  }
17984
18012
  });
17985
18013
  const extractMs = Date.now() - tExtract;
18014
+ signal?.throwIfAborted();
17986
18015
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
17987
18016
  const log = getLogger();
17988
18017
  const screened = screenCandidatesForInjection(filtered);
@@ -18030,16 +18059,33 @@ async function extractAndRetain(messages, retainCtx, options) {
18030
18059
  const results = [];
18031
18060
  const quarantinedInfo = [];
18032
18061
  let failedWrites = 0;
18062
+ let auditRows;
18033
18063
  const tRetain = Date.now();
18034
18064
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
18035
18065
  try {
18066
+ signal?.throwIfAborted();
18036
18067
  if (isQuarantined) {
18037
- const existingId = await findQuarantinedDuplicateOp(
18038
- retainCtx.vaultCtx,
18039
- candidate.content,
18040
- candidate.sourceMessageIds,
18041
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
18042
- ).catch(() => null);
18068
+ let existingId;
18069
+ if (memoryStore) {
18070
+ auditRows ?? (auditRows = memoryStore.list({
18071
+ scopes: [options.scope ?? "private"],
18072
+ includeArchived: true,
18073
+ includeQuarantined: true,
18074
+ includeSuperseded: true
18075
+ }));
18076
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
18077
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
18078
+ existingId = (await auditRows).find(
18079
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
18080
+ )?.uniqueId ?? null;
18081
+ } else {
18082
+ existingId = await findQuarantinedDuplicateOp(
18083
+ retainCtx.vaultCtx,
18084
+ candidate.content,
18085
+ candidate.sourceMessageIds,
18086
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
18087
+ ).catch(() => null);
18088
+ }
18043
18089
  if (existingId) {
18044
18090
  quarantinedInfo.push({
18045
18091
  candidate,
@@ -18050,18 +18096,26 @@ async function extractAndRetain(messages, retainCtx, options) {
18050
18096
  continue;
18051
18097
  }
18052
18098
  }
18053
- const result = await retain(candidate.content, retainCtx, {
18099
+ const retainOptions = {
18054
18100
  source: "auto-extracted",
18055
18101
  sourceChunkIds: candidate.sourceMessageIds,
18056
18102
  respectTombstones: true,
18057
18103
  ...options.scope !== void 0 && { scope: options.scope },
18058
- ...options.folderId !== void 0 && { folderId: options.folderId },
18059
18104
  ...consolidateOptions !== void 0 && { consolidateOptions },
18060
18105
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
18061
18106
  factType: candidate.type,
18062
18107
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
18108
+ };
18109
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
18110
+ ...retainOptions,
18111
+ canWrite: retainCtx.vaultCtx.canWrite,
18112
+ signal
18113
+ }) : await retain(candidate.content, retainCtx, {
18114
+ ...retainOptions,
18115
+ ...options.folderId !== void 0 && { folderId: options.folderId }
18063
18116
  });
18064
18117
  if (isQuarantined) {
18118
+ auditRows = void 0;
18065
18119
  const info = {
18066
18120
  candidate,
18067
18121
  memoryId: result.memoryId,
@@ -18080,9 +18134,16 @@ async function extractAndRetain(messages, retainCtx, options) {
18080
18134
  }
18081
18135
  succeededCandidates.push(candidate);
18082
18136
  results.push(result);
18083
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
18137
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
18084
18138
  try {
18085
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
18139
+ signal?.throwIfAborted();
18140
+ if (memoryStore) {
18141
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
18142
+ unlessUserManaged: true,
18143
+ canWrite: retainCtx.vaultCtx.canWrite,
18144
+ signal
18145
+ });
18146
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
18086
18147
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
18087
18148
  unlessTopicsUserManaged: true
18088
18149
  });
@@ -18332,7 +18393,7 @@ function createAutoExtractor(options) {
18332
18393
  };
18333
18394
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
18334
18395
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
18335
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18396
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18336
18397
  getLogger().warn(
18337
18398
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
18338
18399
  );
@@ -18377,6 +18438,8 @@ function createAutoExtractor(options) {
18377
18438
  try {
18378
18439
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
18379
18440
  extract,
18441
+ memoryStore: options.memoryStore,
18442
+ signal: options.signal,
18380
18443
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
18381
18444
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
18382
18445
  ...options.scope !== void 0 && { scope: options.scope },
@@ -20482,6 +20545,7 @@ function createLocalMemoryStore(options) {
20482
20545
  database,
20483
20546
  entityCollection: database.get("entity"),
20484
20547
  memoryEntityCollection: database.get("memory_entity"),
20548
+ canWrite: options.canWrite,
20485
20549
  ...userId !== void 0 && { userId },
20486
20550
  ...options.allowUnscopedRows !== void 0 && {
20487
20551
  allowUnscopedRows: options.allowUnscopedRows
@@ -20493,10 +20557,21 @@ function createLocalMemoryStore(options) {
20493
20557
  walletAddress: options.walletAddress,
20494
20558
  signMessage: options.signMessage,
20495
20559
  embeddedWalletSigner: options.embeddedWalletSigner,
20560
+ canWrite: options.canWrite,
20496
20561
  ...userId !== void 0 && { userId },
20497
20562
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
20498
20563
  entityCtx
20499
20564
  };
20565
+ const assertWrite = async (writeOptions) => {
20566
+ writeOptions?.signal?.throwIfAborted();
20567
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
20568
+ throw new Error("Memory source is no longer eligible");
20569
+ }
20570
+ if (options.canWrite && !await options.canWrite()) {
20571
+ throw new Error("Memory source is no longer eligible");
20572
+ }
20573
+ writeOptions?.signal?.throwIfAborted();
20574
+ };
20500
20575
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
20501
20576
  const ownedBy = userId !== void 0 ? [import_watermelondb23.Q.where("user_id", userId)] : [];
20502
20577
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [import_watermelondb23.Q.or(import_watermelondb23.Q.where("user_id", userId), import_watermelondb23.Q.where("user_id", null))] : [import_watermelondb23.Q.where("user_id", userId)];
@@ -20608,9 +20683,25 @@ function createLocalMemoryStore(options) {
20608
20683
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
20609
20684
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
20610
20685
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
20611
- addTopics: (memoryId, topics) => database.write(async (writer) => {
20686
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
20687
+ await assertWrite(writeOptions);
20612
20688
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb23.Q.where("id", memoryId), import_watermelondb23.Q.where("is_deleted", false), ...ownedBy).fetchCount();
20613
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
20689
+ return owned > 0 ? writer.callWriter(
20690
+ () => linkMemoryEntitiesOp(
20691
+ {
20692
+ ...entityCtx,
20693
+ canWrite: async () => {
20694
+ await assertWrite(writeOptions);
20695
+ return true;
20696
+ }
20697
+ },
20698
+ memoryId,
20699
+ topics,
20700
+ {
20701
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
20702
+ }
20703
+ )
20704
+ ) : [];
20614
20705
  }),
20615
20706
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
20616
20707
  recall: (query, recallOptions) => recall(
@@ -20620,7 +20711,17 @@ function createLocalMemoryStore(options) {
20620
20711
  ),
20621
20712
  retain: (content, retainOptions) => retain(
20622
20713
  content,
20623
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
20714
+ {
20715
+ vaultCtx: {
20716
+ ...vaultCtx,
20717
+ canWrite: async () => {
20718
+ await assertWrite(retainOptions);
20719
+ return true;
20720
+ }
20721
+ },
20722
+ embeddingOptions: options.embeddingOptions,
20723
+ vaultCache
20724
+ },
20624
20725
  retainOptions
20625
20726
  ),
20626
20727
  factSource: {
@@ -21275,7 +21376,14 @@ function stored(row) {
21275
21376
  function createRemoteMemoryPipeline(options) {
21276
21377
  const { persistence, embeddingOptions } = options;
21277
21378
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
21278
- const operation = (mode, queryOptions = embeddingOptions) => {
21379
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
21380
+ const signal = writeOptions?.signal;
21381
+ const assertWrite = async () => {
21382
+ signal?.throwIfAborted();
21383
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
21384
+ throw new Error("Memory source is no longer eligible");
21385
+ signal?.throwIfAborted();
21386
+ };
21279
21387
  const rejectUndecryptable = (failed) => {
21280
21388
  if (!failed.length) return;
21281
21389
  if (mode === "retain")
@@ -21293,7 +21401,7 @@ function createRemoteMemoryPipeline(options) {
21293
21401
  return items.map((item) => stored(item.memory));
21294
21402
  };
21295
21403
  const get = async (id) => {
21296
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
21404
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
21297
21405
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
21298
21406
  snapshots.set(id, snapshot);
21299
21407
  return stored(snapshot.memory);
@@ -21343,7 +21451,7 @@ function createRemoteMemoryPipeline(options) {
21343
21451
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
21344
21452
  }
21345
21453
  const forceIds = [...forced];
21346
- const windowOptions = (i, signal) => ({
21454
+ const windowOptions = (i, signal2) => ({
21347
21455
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
21348
21456
  force_ids: i === 0 ? forceIds : [],
21349
21457
  ...search.scopes && { scopes: search.scopes },
@@ -21351,13 +21459,15 @@ function createRemoteMemoryPipeline(options) {
21351
21459
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
21352
21460
  include_archived: search.includeArchived,
21353
21461
  embedding_model: model,
21354
- ...signal && { signal }
21462
+ ...signal2 && { signal: signal2 }
21355
21463
  });
21356
21464
  const vectors = [queryEmbedding, ...facetEmbeddings];
21357
21465
  let windows;
21358
21466
  if (mode === "retain") {
21359
21467
  windows = await Promise.all(
21360
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
21468
+ vectors.map(
21469
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
21470
+ )
21361
21471
  );
21362
21472
  } else {
21363
21473
  const deadline = new AbortController();
@@ -21445,12 +21555,15 @@ function createRemoteMemoryPipeline(options) {
21445
21555
  twin_opt_in: false
21446
21556
  };
21447
21557
  };
21448
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
21558
+ const write = async (memory, snapshot) => {
21559
+ await assertWrite();
21560
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
21561
+ };
21449
21562
  const port = {
21450
21563
  prepare,
21451
21564
  get,
21452
21565
  getFresh: async (id) => {
21453
- const current = await persistence.get(id);
21566
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
21454
21567
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
21455
21568
  },
21456
21569
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -21461,7 +21574,8 @@ function createRemoteMemoryPipeline(options) {
21461
21574
  embedding_model: embeddingModel,
21462
21575
  strict_model: true,
21463
21576
  include_deleted: true,
21464
- deleted_only: true
21577
+ deleted_only: true,
21578
+ signal
21465
21579
  });
21466
21580
  rejectUndecryptable(window2.failed);
21467
21581
  return remember(window2.items);
@@ -21517,14 +21631,18 @@ function createRemoteMemoryPipeline(options) {
21517
21631
  if (!target || !newer || id === successor) return false;
21518
21632
  const old = snapshots.get(id);
21519
21633
  const next = snapshots.get(successor);
21634
+ await assertWrite();
21520
21635
  remember(
21521
- await persistence.putMany([
21522
- {
21523
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
21524
- expectedVersion: old
21525
- },
21526
- { memory: next.memory, expectedVersion: next }
21527
- ])
21636
+ await persistence.putMany(
21637
+ [
21638
+ {
21639
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
21640
+ expectedVersion: old
21641
+ },
21642
+ { memory: next.memory, expectedVersion: next }
21643
+ ],
21644
+ signal
21645
+ )
21528
21646
  );
21529
21647
  return true;
21530
21648
  },
@@ -21533,18 +21651,22 @@ function createRemoteMemoryPipeline(options) {
21533
21651
  if (!target) return { created: null, retired: false };
21534
21652
  const snapshot = snapshots.get(targetId);
21535
21653
  const successor = createRow(input);
21654
+ await assertWrite();
21536
21655
  const rows = remember(
21537
- await persistence.putMany([
21538
- { memory: successor, expectedVersion: 0 },
21539
- {
21540
- memory: {
21541
- ...snapshot.memory,
21542
- superseded_by: successor.memory_id,
21543
- superseded_at: Date.now()
21544
- },
21545
- expectedVersion: snapshot
21546
- }
21547
- ])
21656
+ await persistence.putMany(
21657
+ [
21658
+ { memory: successor, expectedVersion: 0 },
21659
+ {
21660
+ memory: {
21661
+ ...snapshot.memory,
21662
+ superseded_by: successor.memory_id,
21663
+ superseded_at: Date.now()
21664
+ },
21665
+ expectedVersion: snapshot
21666
+ }
21667
+ ],
21668
+ signal
21669
+ )
21548
21670
  );
21549
21671
  return { created: rows[0], retired: true };
21550
21672
  }
@@ -21579,7 +21701,7 @@ function createRemoteMemoryPipeline(options) {
21579
21701
  factSource,
21580
21702
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
21581
21703
  retain: async (content, retainOptions) => {
21582
- const { port } = operation("retain");
21704
+ const { port } = operation("retain", embeddingOptions, retainOptions);
21583
21705
  return retainWithPersistence(
21584
21706
  content,
21585
21707
  {
@@ -22241,16 +22363,21 @@ function createRemoteMemoryStore(options) {
22241
22363
  const record2 = await persistence.get(id);
22242
22364
  return record2 && !record2.memory.is_deleted ? record2 : null;
22243
22365
  };
22244
- const mutateLocked = async (id, change, edit) => {
22245
- const record2 = await live(id);
22366
+ const mutateLocked = async (id, change, edit, writeOptions) => {
22367
+ writeOptions?.signal?.throwIfAborted();
22368
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
22246
22369
  if (!record2) return null;
22370
+ if (record2.memory.is_deleted) return null;
22371
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
22372
+ throw new Error("Memory source is no longer eligible");
22373
+ writeOptions?.signal?.throwIfAborted();
22247
22374
  const next = edit(structuredClone(record2.memory));
22248
22375
  if (!next) return null;
22249
- const saved = await persistence.put(next, record2);
22376
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
22250
22377
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
22251
22378
  return saved;
22252
22379
  };
22253
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
22380
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
22254
22381
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
22255
22382
  const mutateMany = async (ids, change, edit) => {
22256
22383
  const saved = /* @__PURE__ */ new Map();
@@ -22763,13 +22890,21 @@ function createRemoteMemoryStore(options) {
22763
22890
  }))
22764
22891
  );
22765
22892
  },
22766
- addTopics: async (memoryId, topics) => {
22893
+ addTopics: async (memoryId, topics, writeOptions) => {
22767
22894
  const added = await withKnownKinds(topicInputs(topics, "auto"));
22768
- const saved = await mutate(memoryId, "edit", (memory) => ({
22769
- ...memory,
22770
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
22771
- topics_updated_at: Date.now()
22772
- }));
22895
+ const saved = await mutate(
22896
+ memoryId,
22897
+ "edit",
22898
+ (memory) => {
22899
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
22900
+ return {
22901
+ ...memory,
22902
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
22903
+ topics_updated_at: Date.now()
22904
+ };
22905
+ },
22906
+ writeOptions
22907
+ );
22773
22908
  if (!saved) return [];
22774
22909
  const now = /* @__PURE__ */ new Date();
22775
22910
  return mergeTopics([], added).map(
@@ -23622,6 +23757,7 @@ function createDurableAutoExtractor(options) {
23622
23757
  };
23623
23758
  const folderId = options.folderId ?? null;
23624
23759
  let disposed = false;
23760
+ let activeBatch = null;
23625
23761
  let running = false;
23626
23762
  let wakeRequested = false;
23627
23763
  let timer;
@@ -23658,14 +23794,18 @@ function createDurableAutoExtractor(options) {
23658
23794
  }, delay);
23659
23795
  }
23660
23796
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
23797
+ if (disposed) throw new Error("Extraction session was disposed");
23661
23798
  let watchdog;
23662
23799
  let cancelled = false;
23800
+ const controller = new AbortController();
23801
+ activeBatch = controller;
23663
23802
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
23664
23803
  try {
23665
23804
  return await Promise.race([
23666
23805
  new Promise((resolve, reject) => {
23667
23806
  const worker = createAutoExtractor({
23668
23807
  ...options,
23808
+ signal: controller.signal,
23669
23809
  retainCtx: {
23670
23810
  ...options.retainCtx,
23671
23811
  vaultCtx: {
@@ -23694,6 +23834,8 @@ function createDurableAutoExtractor(options) {
23694
23834
  ]);
23695
23835
  } finally {
23696
23836
  cancelled = true;
23837
+ controller.abort();
23838
+ if (activeBatch === controller) activeBatch = null;
23697
23839
  if (watchdog !== void 0) clearTimeout(watchdog);
23698
23840
  }
23699
23841
  }
@@ -23705,6 +23847,7 @@ function createDurableAutoExtractor(options) {
23705
23847
  for (const message of loaded)
23706
23848
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
23707
23849
  await database.write(async () => {
23850
+ if (disposed) return;
23708
23851
  const current = idsOf(job);
23709
23852
  const acknowledged = ids.filter((id) => current.includes(id));
23710
23853
  if (!acknowledged.length) return;
@@ -23877,6 +24020,7 @@ function createDurableAutoExtractor(options) {
23877
24020
  jobScope,
23878
24021
  jobFolderId
23879
24022
  );
24023
+ if (disposed) throw new Error("Extraction session was disposed");
23880
24024
  options.onTurnComplete?.(result);
23881
24025
  return result;
23882
24026
  };
@@ -23913,6 +24057,7 @@ function createDurableAutoExtractor(options) {
23913
24057
  );
23914
24058
  scheduleAfterSuccess = true;
23915
24059
  } catch (error) {
24060
+ if (disposed) break;
23916
24061
  attempts.set(job.id, attempted + 1);
23917
24062
  retry = true;
23918
24063
  reportError(error, conversationId);
@@ -23935,6 +24080,14 @@ function createDurableAutoExtractor(options) {
23935
24080
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
23936
24081
  }
23937
24082
  }
24083
+ function dispose() {
24084
+ disposed = true;
24085
+ activeBatch?.abort(options.signal?.reason);
24086
+ if (timer !== void 0) clearTimeout(timer);
24087
+ options.signal?.removeEventListener("abort", dispose);
24088
+ }
24089
+ if (options.signal?.aborted) dispose();
24090
+ else options.signal?.addEventListener("abort", dispose, { once: true });
23938
24091
  schedule(0);
23939
24092
  return {
23940
24093
  processTurn(messages, conversationId) {
@@ -24014,10 +24167,7 @@ function createDurableAutoExtractor(options) {
24014
24167
  return true;
24015
24168
  },
24016
24169
  isProcessing: () => running,
24017
- dispose() {
24018
- disposed = true;
24019
- if (timer !== void 0) clearTimeout(timer);
24020
- }
24170
+ dispose
24021
24171
  };
24022
24172
  }
24023
24173
 
@@ -27289,6 +27439,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
27289
27439
  var NO_CONVERSATION_KEY = "__no_conversation__";
27290
27440
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
27291
27441
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
27442
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
27292
27443
  var conversationRedactors = /* @__PURE__ */ new Map();
27293
27444
  var clientToolFilterCache = /* @__PURE__ */ new Map();
27294
27445
  function getConversationRedactor(conversationId, nerDetector) {
@@ -27433,7 +27584,7 @@ function useChatStorage(options) {
27433
27584
  [resolvedPiiRedaction]
27434
27585
  );
27435
27586
  const maskEmbeddingInput = isPiiRedactor(resolvedPiiRedaction) ? maskForEmbedding : void 0;
27436
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
27587
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
27437
27588
  const messagesCollection = (0, import_react5.useMemo)(() => database.get("history"), [database]);
27438
27589
  const conversationsCollection = (0, import_react5.useMemo)(
27439
27590
  () => database.get("conversations"),
@@ -27682,6 +27833,7 @@ function useChatStorage(options) {
27682
27833
  }, [vaultEmbeddingCache]);
27683
27834
  const retainVaultMemory = (0, import_react5.useCallback)(
27684
27835
  async (input) => {
27836
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27685
27837
  if (memoryStore) {
27686
27838
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
27687
27839
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -27727,6 +27879,7 @@ function useChatStorage(options) {
27727
27879
  );
27728
27880
  const createMemoryVaultTool2 = (0, import_react5.useCallback)(
27729
27881
  (options2) => {
27882
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27730
27883
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
27731
27884
  return createMemoryVaultTool(
27732
27885
  memoryStore ?? vaultCtx,
@@ -27737,6 +27890,7 @@ function useChatStorage(options) {
27737
27890
  );
27738
27891
  const createRecallTool2 = (0, import_react5.useCallback)(
27739
27892
  (toolOptions, callbacks) => {
27893
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27740
27894
  if (!getToken && !memoryStore) {
27741
27895
  throw new Error("getToken is required for recall tool");
27742
27896
  }
@@ -27782,7 +27936,7 @@ function useChatStorage(options) {
27782
27936
  );
27783
27937
  const recallFn = (0, import_react5.useCallback)(
27784
27938
  async (query, options2) => {
27785
- if (!getToken && !memoryStore) {
27939
+ if (memoryStore === null || !getToken && !memoryStore) {
27786
27940
  return {
27787
27941
  memories: [],
27788
27942
  usedBudget: options2?.budget ?? "low",
@@ -27831,6 +27985,7 @@ function useChatStorage(options) {
27831
27985
  );
27832
27986
  const getVaultMemories = (0, import_react5.useCallback)(
27833
27987
  (options2) => {
27988
+ if (memoryStore === null) return Promise.resolve([]);
27834
27989
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
27835
27990
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
27836
27991
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -27841,7 +27996,8 @@ function useChatStorage(options) {
27841
27996
  [vaultCtx, memoryStore]
27842
27997
  );
27843
27998
  const deleteVaultMemory = (0, import_react5.useCallback)(
27844
- (id) => {
27999
+ async (id) => {
28000
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27845
28001
  return memoryStore ? memoryStore.delete(id) : deleteVaultMemoryOp(vaultCtx, id);
27846
28002
  },
27847
28003
  [vaultCtx, memoryStore]