@anuma/sdk 7.0.3 → 7.0.4-next.20261015010856

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.
@@ -2158,8 +2158,9 @@ async function updateConversationPinnedOp(ctx, id, pinned) {
2158
2158
  }
2159
2159
  return false;
2160
2160
  }
2161
- async function getMessagesOp(ctx, convId) {
2162
- const results = await ctx.messagesCollection.query(Q.where("conversation_id", convId), Q.sortBy("message_id", Q.asc)).unsafeFetchRaw();
2161
+ async function getMessagesOp(ctx, convId, opts) {
2162
+ const fetched = await ctx.messagesCollection.query(Q.where("conversation_id", convId), Q.sortBy("message_id", Q.asc)).unsafeFetchRaw();
2163
+ const results = opts?.rawFilter ? fetched.filter(opts.rawFilter) : fetched;
2163
2164
  return Promise.all(
2164
2165
  results.map(
2165
2166
  (raw) => messageRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
@@ -4306,6 +4307,37 @@ async function chunkAndEmbedMessage(ctx, messageId, options) {
4306
4307
  }));
4307
4308
  return updateMessageChunksOp(ctx, messageId, messageChunks, embeddingModel);
4308
4309
  }
4310
+ var MIN_SEALED_EMBEDDING_HEX = 256;
4311
+ function rawHasEmbedding(value) {
4312
+ if (Array.isArray(value)) return value.length > 0;
4313
+ if (typeof value !== "string" || value.length === 0) return false;
4314
+ if (isEncrypted(value)) {
4315
+ return value.length - value.indexOf(":", 4) - 1 >= MIN_SEALED_EMBEDDING_HEX;
4316
+ }
4317
+ try {
4318
+ const parsed = JSON.parse(value);
4319
+ return Array.isArray(parsed) && parsed.length > 0;
4320
+ } catch {
4321
+ return false;
4322
+ }
4323
+ }
4324
+ function sealedVersion(value) {
4325
+ if (typeof value !== "string" || !isEncrypted(value)) return null;
4326
+ return value.startsWith("enc:v2:") ? "v2" : "v3";
4327
+ }
4328
+ function mayNeedChunkEmbedding(raw, embeddingModel, filter) {
4329
+ const role = raw.role;
4330
+ if (role === "system") return false;
4331
+ if (filter?.roles && !filter.roles.includes(role ?? "")) return false;
4332
+ if (isNonEmbeddableOrigin(raw.origin, filter?.reembedDiscarded)) {
4333
+ return false;
4334
+ }
4335
+ const model = raw.embedding_model;
4336
+ if (typeof model === "string" && model !== embeddingModel) return true;
4337
+ if (rawHasEmbedding(raw.chunks)) return false;
4338
+ if (rawHasEmbedding(raw.vector) && !filter?.rechunkExisting) return false;
4339
+ return true;
4340
+ }
4309
4341
  async function chunkAndEmbedAllMessages(ctx, options, filter) {
4310
4342
  const embeddingModel = options.model ?? DEFAULT_API_EMBEDDING_MODEL;
4311
4343
  const { chunkSize = DEFAULT_CHUNK_SIZE } = options;
@@ -4318,11 +4350,31 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4318
4350
  let considered = 0;
4319
4351
  let sealedRowsSeen = 0;
4320
4352
  let rowsSeen = 0;
4353
+ const keptSealed = /* @__PURE__ */ new Map();
4354
+ const candidatesStillSealed = /* @__PURE__ */ new Map();
4355
+ const droppedSealed = /* @__PURE__ */ new Map();
4356
+ const rawFilter = (raw) => {
4357
+ rowsSeen++;
4358
+ const keep = mayNeedChunkEmbedding(raw, embeddingModel, filter);
4359
+ const version = sealedVersion(raw.content);
4360
+ if (!version) return keep;
4361
+ if (keep) {
4362
+ keptSealed.set(version, (keptSealed.get(version) ?? 0) + 1);
4363
+ } else {
4364
+ const dropped = droppedSealed.get(version);
4365
+ if (dropped) dropped.count++;
4366
+ else droppedSealed.set(version, { count: 1, probeId: raw.id });
4367
+ }
4368
+ return keep;
4369
+ };
4321
4370
  for (const conv of targetConversations) {
4322
- const messages = await getMessagesOp(ctx, conv.conversationId);
4371
+ const messages = await getMessagesOp(ctx, conv.conversationId, { rawFilter });
4323
4372
  for (const message of messages) {
4324
- rowsSeen++;
4325
- if (isEncrypted(message.content)) sealedRowsSeen++;
4373
+ const stillSealed = sealedVersion(message.content);
4374
+ if (stillSealed) {
4375
+ sealedRowsSeen++;
4376
+ candidatesStillSealed.set(stillSealed, (candidatesStillSealed.get(stillSealed) ?? 0) + 1);
4377
+ }
4326
4378
  const isStale = message.embeddingModel !== void 0 && message.embeddingModel !== null && message.embeddingModel !== embeddingModel;
4327
4379
  if (message.chunks && message.chunks.length > 0 && !isStale) continue;
4328
4380
  const hasVector = message.vector && message.vector.length > 0;
@@ -4335,6 +4387,7 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4335
4387
  stillEncrypted++;
4336
4388
  continue;
4337
4389
  }
4390
+ if (isToolResultsRow(message)) continue;
4338
4391
  if (message.content.length < minLength) continue;
4339
4392
  if (shouldChunkMessage(message.content, chunkSize)) {
4340
4393
  longMessages.push({
@@ -4346,6 +4399,12 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
4346
4399
  }
4347
4400
  }
4348
4401
  }
4402
+ for (const [version, dropped] of droppedSealed) {
4403
+ const keyOpenedACandidate = (keptSealed.get(version) ?? 0) > (candidatesStillSealed.get(version) ?? 0);
4404
+ if (keyOpenedACandidate) continue;
4405
+ const probe = await getMessageOp(ctx, dropped.probeId);
4406
+ if (!probe || isEncrypted(probe.content)) sealedRowsSeen += dropped.count;
4407
+ }
4349
4408
  if (stillEncrypted > 0 || sealedRowsSeen > 0) {
4350
4409
  getLogger().error(
4351
4410
  "memoryEngine: messages still encrypted (key unavailable?) \u2014 excluded from embedding",
@@ -11050,6 +11109,11 @@ import { Q as Q5 } from "@nozbe/watermelondb";
11050
11109
 
11051
11110
  // src/lib/db/entities/operations.ts
11052
11111
  import { Q as Q4 } from "@nozbe/watermelondb";
11112
+ async function assertCanWrite(ctx) {
11113
+ if (ctx.canWrite && !await ctx.canWrite()) {
11114
+ throw new Error("Memory source is no longer eligible");
11115
+ }
11116
+ }
11053
11117
  function entityToStored(e) {
11054
11118
  return {
11055
11119
  uniqueId: e.id,
@@ -11190,6 +11254,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
11190
11254
  let skipped = false;
11191
11255
  let entities = [];
11192
11256
  await ctx.database.write(async () => {
11257
+ await assertCanWrite(ctx);
11193
11258
  const {
11194
11259
  entities: byName,
11195
11260
  operations: entityOps,
@@ -11265,6 +11330,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
11265
11330
  let skipped = false;
11266
11331
  let entities = [];
11267
11332
  await ctx.database.write(async () => {
11333
+ await assertCanWrite(ctx);
11268
11334
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
11269
11335
  if (options.guarded && autoLinkBlocked(vaultRow)) {
11270
11336
  skipped = true;
@@ -11325,6 +11391,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
11325
11391
  const links = await ctx.memoryEntityCollection.query(Q4.where("memory_id", Q4.oneOf(Array.from(memoryIds)))).fetch();
11326
11392
  if (links.length === 0) return;
11327
11393
  await ctx.database.write(async () => {
11394
+ await assertCanWrite(ctx);
11328
11395
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
11329
11396
  });
11330
11397
  }
@@ -11333,6 +11400,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
11333
11400
  const links = await ctx.memoryEntityCollection.query(Q4.where("user_id", userId)).fetch();
11334
11401
  if (links.length === 0) return;
11335
11402
  await ctx.database.write(async () => {
11403
+ await assertCanWrite(ctx);
11336
11404
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
11337
11405
  });
11338
11406
  }
@@ -11496,6 +11564,11 @@ function visibilityConditions(requested) {
11496
11564
  function isKinded(record2) {
11497
11565
  return record2.kind !== null && record2.kind !== void 0;
11498
11566
  }
11567
+ async function assertCanWrite2(ctx) {
11568
+ if (ctx.canWrite && !await ctx.canWrite()) {
11569
+ throw new Error("Memory source is no longer eligible");
11570
+ }
11571
+ }
11499
11572
  function assertValidKindLevel(kind, level) {
11500
11573
  const hasKind = kind !== null && kind !== void 0;
11501
11574
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -11676,8 +11749,7 @@ async function createVaultMemoryOp(ctx, opts) {
11676
11749
  ctx.embeddedWalletSigner
11677
11750
  ) : opts.content;
11678
11751
  const created = await ctx.database.write(async () => {
11679
- if (ctx.canWrite && !await ctx.canWrite())
11680
- throw new Error("Memory source is no longer eligible");
11752
+ await assertCanWrite2(ctx);
11681
11753
  return ctx.vaultMemoryCollection.create(
11682
11754
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
11683
11755
  );
@@ -11696,8 +11768,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
11696
11768
  ) : opts.content;
11697
11769
  let createdRecord = null;
11698
11770
  await ctx.database.write(async () => {
11699
- if (ctx.canWrite && !await ctx.canWrite())
11700
- throw new Error("Memory source is no longer eligible");
11771
+ await assertCanWrite2(ctx);
11701
11772
  let target;
11702
11773
  try {
11703
11774
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -11789,8 +11860,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
11789
11860
  })
11790
11861
  );
11791
11862
  const created = await ctx.database.write(async () => {
11792
- if (ctx.canWrite && !await ctx.canWrite())
11793
- throw new Error("Memory source is no longer eligible");
11863
+ await assertCanWrite2(ctx);
11794
11864
  const prepared = optionsArray.map(
11795
11865
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
11796
11866
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -12095,8 +12165,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
12095
12165
  const record2 = probe;
12096
12166
  const originalUpdatedAt = record2.updatedAt.getTime();
12097
12167
  await ctx.database.write(async () => {
12098
- if (ctx.canWrite && !await ctx.canWrite())
12099
- throw new Error("Memory source is no longer eligible");
12168
+ await assertCanWrite2(ctx);
12100
12169
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
12101
12170
  stale = true;
12102
12171
  return;
@@ -12207,6 +12276,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
12207
12276
  let stale = false;
12208
12277
  const originalUpdatedAt = record2.updatedAt.getTime();
12209
12278
  await ctx.database.write(async (writer) => {
12279
+ await assertCanWrite2(ctx);
12210
12280
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12211
12281
  stale = true;
12212
12282
  return;
@@ -12248,6 +12318,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
12248
12318
  const originalUpdatedAt = record2.updatedAt.getTime();
12249
12319
  let cleared = false;
12250
12320
  await ctx.database.write(async () => {
12321
+ await assertCanWrite2(ctx);
12251
12322
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
12252
12323
  cleared = true;
12253
12324
  await record2.update((r) => {
@@ -12271,6 +12342,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
12271
12342
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
12272
12343
  let stale = false;
12273
12344
  await ctx.database.write(async () => {
12345
+ await assertCanWrite2(ctx);
12274
12346
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12275
12347
  stale = true;
12276
12348
  return;
@@ -12299,6 +12371,7 @@ async function deleteVaultMemoryOp(ctx, id) {
12299
12371
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
12300
12372
  let stale = false;
12301
12373
  await ctx.database.write(async () => {
12374
+ await assertCanWrite2(ctx);
12302
12375
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12303
12376
  stale = true;
12304
12377
  return;
@@ -12336,8 +12409,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
12336
12409
  }
12337
12410
  let stale = false;
12338
12411
  await ctx.database.write(async () => {
12339
- if (ctx.canWrite && !await ctx.canWrite())
12340
- throw new Error("Memory source is no longer eligible");
12412
+ await assertCanWrite2(ctx);
12341
12413
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
12342
12414
  stale = true;
12343
12415
  return;
@@ -12499,6 +12571,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
12499
12571
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
12500
12572
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
12501
12573
  await ctx.database.write(async () => {
12574
+ await assertCanWrite2(ctx);
12502
12575
  const records = [];
12503
12576
  for (const id of chunkIds) {
12504
12577
  try {
@@ -12532,6 +12605,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
12532
12605
  for (const id of Array.from(new Set(memoryIds))) {
12533
12606
  try {
12534
12607
  await ctx.database.write(async (writer) => {
12608
+ await assertCanWrite2(ctx);
12535
12609
  let record2;
12536
12610
  try {
12537
12611
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -12562,6 +12636,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
12562
12636
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
12563
12637
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
12564
12638
  await ctx.database.write(async () => {
12639
+ await assertCanWrite2(ctx);
12565
12640
  const writes = [];
12566
12641
  for (const id of chunkIds) {
12567
12642
  let record2;
@@ -12594,6 +12669,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
12594
12669
  const records = await ctx.vaultMemoryCollection.query(Q5.where("user_id", userId), Q5.where("is_deleted", false)).fetch();
12595
12670
  if (records.length === 0) return 0;
12596
12671
  await ctx.database.write(async () => {
12672
+ await assertCanWrite2(ctx);
12597
12673
  const prepared = records.map(
12598
12674
  (record2) => record2.prepareUpdate((r) => {
12599
12675
  r._setRaw("is_deleted", true);
@@ -12648,6 +12724,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
12648
12724
  let stale = false;
12649
12725
  const archivedAtValue = opts?.now ?? Date.now();
12650
12726
  await ctx.database.write(async () => {
12727
+ await assertCanWrite2(ctx);
12651
12728
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
12652
12729
  stale = true;
12653
12730
  return;
@@ -12671,6 +12748,7 @@ async function restoreVaultMemoryOp(ctx, id) {
12671
12748
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
12672
12749
  let stale = false;
12673
12750
  await ctx.database.write(async () => {
12751
+ await assertCanWrite2(ctx);
12674
12752
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12675
12753
  stale = true;
12676
12754
  return;
@@ -12691,6 +12769,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
12691
12769
  const now = opts.now ?? Date.now();
12692
12770
  let stale = false;
12693
12771
  await ctx.database.write(async () => {
12772
+ await assertCanWrite2(ctx);
12694
12773
  const archivedAt = record2.archivedAt;
12695
12774
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
12696
12775
  stale = true;
@@ -12730,6 +12809,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
12730
12809
  }
12731
12810
  let written = false;
12732
12811
  await ctx.database.write(async () => {
12812
+ await assertCanWrite2(ctx);
12733
12813
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
12734
12814
  const originalUpdatedAt = record2.updatedAt.getTime();
12735
12815
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -16242,6 +16322,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
16242
16322
  ).map((c) => c.candidate);
16243
16323
  }
16244
16324
  async function extractAndRetain(messages, retainCtx, options) {
16325
+ const { memoryStore, signal } = options;
16326
+ if (memoryStore === null) throw new Error("Memory store is not ready");
16327
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
16328
+ throw new Error("Remote memory extraction does not support folders");
16329
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
16330
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
16331
+ signal?.throwIfAborted();
16245
16332
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
16246
16333
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
16247
16334
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -16275,6 +16362,7 @@ async function extractAndRetain(messages, retainCtx, options) {
16275
16362
  }
16276
16363
  });
16277
16364
  const extractMs = Date.now() - tExtract;
16365
+ signal?.throwIfAborted();
16278
16366
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
16279
16367
  const log = getLogger();
16280
16368
  const screened = screenCandidatesForInjection(filtered);
@@ -16322,16 +16410,33 @@ async function extractAndRetain(messages, retainCtx, options) {
16322
16410
  const results = [];
16323
16411
  const quarantinedInfo = [];
16324
16412
  let failedWrites = 0;
16413
+ let auditRows;
16325
16414
  const tRetain = Date.now();
16326
16415
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
16327
16416
  try {
16417
+ signal?.throwIfAborted();
16328
16418
  if (isQuarantined) {
16329
- const existingId = await findQuarantinedDuplicateOp(
16330
- retainCtx.vaultCtx,
16331
- candidate.content,
16332
- candidate.sourceMessageIds,
16333
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
16334
- ).catch(() => null);
16419
+ let existingId;
16420
+ if (memoryStore) {
16421
+ auditRows ?? (auditRows = memoryStore.list({
16422
+ scopes: [options.scope ?? "private"],
16423
+ includeArchived: true,
16424
+ includeQuarantined: true,
16425
+ includeSuperseded: true
16426
+ }));
16427
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
16428
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
16429
+ existingId = (await auditRows).find(
16430
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
16431
+ )?.uniqueId ?? null;
16432
+ } else {
16433
+ existingId = await findQuarantinedDuplicateOp(
16434
+ retainCtx.vaultCtx,
16435
+ candidate.content,
16436
+ candidate.sourceMessageIds,
16437
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
16438
+ ).catch(() => null);
16439
+ }
16335
16440
  if (existingId) {
16336
16441
  quarantinedInfo.push({
16337
16442
  candidate,
@@ -16342,18 +16447,26 @@ async function extractAndRetain(messages, retainCtx, options) {
16342
16447
  continue;
16343
16448
  }
16344
16449
  }
16345
- const result = await retain(candidate.content, retainCtx, {
16450
+ const retainOptions = {
16346
16451
  source: "auto-extracted",
16347
16452
  sourceChunkIds: candidate.sourceMessageIds,
16348
16453
  respectTombstones: true,
16349
16454
  ...options.scope !== void 0 && { scope: options.scope },
16350
- ...options.folderId !== void 0 && { folderId: options.folderId },
16351
16455
  ...consolidateOptions !== void 0 && { consolidateOptions },
16352
16456
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
16353
16457
  factType: candidate.type,
16354
16458
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
16459
+ };
16460
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
16461
+ ...retainOptions,
16462
+ canWrite: retainCtx.vaultCtx.canWrite,
16463
+ signal
16464
+ }) : await retain(candidate.content, retainCtx, {
16465
+ ...retainOptions,
16466
+ ...options.folderId !== void 0 && { folderId: options.folderId }
16355
16467
  });
16356
16468
  if (isQuarantined) {
16469
+ auditRows = void 0;
16357
16470
  const info = {
16358
16471
  candidate,
16359
16472
  memoryId: result.memoryId,
@@ -16372,9 +16485,16 @@ async function extractAndRetain(messages, retainCtx, options) {
16372
16485
  }
16373
16486
  succeededCandidates.push(candidate);
16374
16487
  results.push(result);
16375
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
16488
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
16376
16489
  try {
16377
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
16490
+ signal?.throwIfAborted();
16491
+ if (memoryStore) {
16492
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
16493
+ unlessUserManaged: true,
16494
+ canWrite: retainCtx.vaultCtx.canWrite,
16495
+ signal
16496
+ });
16497
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
16378
16498
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
16379
16499
  unlessTopicsUserManaged: true
16380
16500
  });
@@ -16622,7 +16742,7 @@ function createAutoExtractor(options) {
16622
16742
  };
16623
16743
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
16624
16744
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
16625
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
16745
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
16626
16746
  getLogger().warn(
16627
16747
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
16628
16748
  );
@@ -16667,6 +16787,8 @@ function createAutoExtractor(options) {
16667
16787
  try {
16668
16788
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
16669
16789
  extract,
16790
+ memoryStore: options.memoryStore,
16791
+ signal: options.signal,
16670
16792
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
16671
16793
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
16672
16794
  ...options.scope !== void 0 && { scope: options.scope },
@@ -18749,6 +18871,7 @@ function createLocalMemoryStore(options) {
18749
18871
  database,
18750
18872
  entityCollection: database.get("entity"),
18751
18873
  memoryEntityCollection: database.get("memory_entity"),
18874
+ canWrite: options.canWrite,
18752
18875
  ...userId !== void 0 && { userId },
18753
18876
  ...options.allowUnscopedRows !== void 0 && {
18754
18877
  allowUnscopedRows: options.allowUnscopedRows
@@ -18760,10 +18883,21 @@ function createLocalMemoryStore(options) {
18760
18883
  walletAddress: options.walletAddress,
18761
18884
  signMessage: options.signMessage,
18762
18885
  embeddedWalletSigner: options.embeddedWalletSigner,
18886
+ canWrite: options.canWrite,
18763
18887
  ...userId !== void 0 && { userId },
18764
18888
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
18765
18889
  entityCtx
18766
18890
  };
18891
+ const assertWrite = async (writeOptions) => {
18892
+ writeOptions?.signal?.throwIfAborted();
18893
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
18894
+ throw new Error("Memory source is no longer eligible");
18895
+ }
18896
+ if (options.canWrite && !await options.canWrite()) {
18897
+ throw new Error("Memory source is no longer eligible");
18898
+ }
18899
+ writeOptions?.signal?.throwIfAborted();
18900
+ };
18767
18901
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
18768
18902
  const ownedBy = userId !== void 0 ? [Q8.where("user_id", userId)] : [];
18769
18903
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [Q8.or(Q8.where("user_id", userId), Q8.where("user_id", null))] : [Q8.where("user_id", userId)];
@@ -18875,9 +19009,25 @@ function createLocalMemoryStore(options) {
18875
19009
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
18876
19010
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
18877
19011
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
18878
- addTopics: (memoryId, topics) => database.write(async (writer) => {
19012
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
19013
+ await assertWrite(writeOptions);
18879
19014
  const owned = await vaultCtx.vaultMemoryCollection.query(Q8.where("id", memoryId), Q8.where("is_deleted", false), ...ownedBy).fetchCount();
18880
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
19015
+ return owned > 0 ? writer.callWriter(
19016
+ () => linkMemoryEntitiesOp(
19017
+ {
19018
+ ...entityCtx,
19019
+ canWrite: async () => {
19020
+ await assertWrite(writeOptions);
19021
+ return true;
19022
+ }
19023
+ },
19024
+ memoryId,
19025
+ topics,
19026
+ {
19027
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
19028
+ }
19029
+ )
19030
+ ) : [];
18881
19031
  }),
18882
19032
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
18883
19033
  recall: (query, recallOptions) => recall(
@@ -18887,7 +19037,17 @@ function createLocalMemoryStore(options) {
18887
19037
  ),
18888
19038
  retain: (content, retainOptions) => retain(
18889
19039
  content,
18890
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
19040
+ {
19041
+ vaultCtx: {
19042
+ ...vaultCtx,
19043
+ canWrite: async () => {
19044
+ await assertWrite(retainOptions);
19045
+ return true;
19046
+ }
19047
+ },
19048
+ embeddingOptions: options.embeddingOptions,
19049
+ vaultCache
19050
+ },
18891
19051
  retainOptions
18892
19052
  ),
18893
19053
  factSource: {
@@ -19524,7 +19684,14 @@ function stored(row) {
19524
19684
  function createRemoteMemoryPipeline(options) {
19525
19685
  const { persistence, embeddingOptions } = options;
19526
19686
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
19527
- const operation = (mode) => {
19687
+ const operation = (mode, writeOptions) => {
19688
+ const signal = writeOptions?.signal;
19689
+ const assertWrite = async () => {
19690
+ signal?.throwIfAborted();
19691
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
19692
+ throw new Error("Memory source is no longer eligible");
19693
+ signal?.throwIfAborted();
19694
+ };
19528
19695
  const rejectUndecryptable = (failed) => {
19529
19696
  if (!failed.length) return;
19530
19697
  if (mode === "retain")
@@ -19542,7 +19709,7 @@ function createRemoteMemoryPipeline(options) {
19542
19709
  return items.map((item) => stored(item.memory));
19543
19710
  };
19544
19711
  const get = async (id) => {
19545
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
19712
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
19546
19713
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
19547
19714
  snapshots.set(id, snapshot);
19548
19715
  return stored(snapshot.memory);
@@ -19588,7 +19755,7 @@ function createRemoteMemoryPipeline(options) {
19588
19755
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
19589
19756
  }
19590
19757
  const forceIds = [...forced];
19591
- const windowOptions = (i, signal) => ({
19758
+ const windowOptions = (i, signal2) => ({
19592
19759
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
19593
19760
  force_ids: i === 0 ? forceIds : [],
19594
19761
  ...search.scopes && { scopes: search.scopes },
@@ -19596,13 +19763,15 @@ function createRemoteMemoryPipeline(options) {
19596
19763
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
19597
19764
  include_archived: search.includeArchived,
19598
19765
  embedding_model: model,
19599
- ...signal && { signal }
19766
+ ...signal2 && { signal: signal2 }
19600
19767
  });
19601
19768
  const vectors = [queryEmbedding, ...facetEmbeddings];
19602
19769
  let windows;
19603
19770
  if (mode === "retain") {
19604
19771
  windows = await Promise.all(
19605
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
19772
+ vectors.map(
19773
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
19774
+ )
19606
19775
  );
19607
19776
  } else {
19608
19777
  const deadline = new AbortController();
@@ -19690,12 +19859,15 @@ function createRemoteMemoryPipeline(options) {
19690
19859
  twin_opt_in: false
19691
19860
  };
19692
19861
  };
19693
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
19862
+ const write = async (memory, snapshot) => {
19863
+ await assertWrite();
19864
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
19865
+ };
19694
19866
  const port = {
19695
19867
  prepare,
19696
19868
  get,
19697
19869
  getFresh: async (id) => {
19698
- const current = await persistence.get(id);
19870
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
19699
19871
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
19700
19872
  },
19701
19873
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -19706,7 +19878,8 @@ function createRemoteMemoryPipeline(options) {
19706
19878
  embedding_model: embeddingModel,
19707
19879
  strict_model: true,
19708
19880
  include_deleted: true,
19709
- deleted_only: true
19881
+ deleted_only: true,
19882
+ signal
19710
19883
  });
19711
19884
  rejectUndecryptable(window2.failed);
19712
19885
  return remember(window2.items);
@@ -19762,14 +19935,18 @@ function createRemoteMemoryPipeline(options) {
19762
19935
  if (!target || !newer || id === successor) return false;
19763
19936
  const old = snapshots.get(id);
19764
19937
  const next = snapshots.get(successor);
19938
+ await assertWrite();
19765
19939
  remember(
19766
- await persistence.putMany([
19767
- {
19768
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
19769
- expectedVersion: old
19770
- },
19771
- { memory: next.memory, expectedVersion: next }
19772
- ])
19940
+ await persistence.putMany(
19941
+ [
19942
+ {
19943
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
19944
+ expectedVersion: old
19945
+ },
19946
+ { memory: next.memory, expectedVersion: next }
19947
+ ],
19948
+ signal
19949
+ )
19773
19950
  );
19774
19951
  return true;
19775
19952
  },
@@ -19778,18 +19955,22 @@ function createRemoteMemoryPipeline(options) {
19778
19955
  if (!target) return { created: null, retired: false };
19779
19956
  const snapshot = snapshots.get(targetId);
19780
19957
  const successor = createRow(input);
19958
+ await assertWrite();
19781
19959
  const rows = remember(
19782
- await persistence.putMany([
19783
- { memory: successor, expectedVersion: 0 },
19784
- {
19785
- memory: {
19786
- ...snapshot.memory,
19787
- superseded_by: successor.memory_id,
19788
- superseded_at: Date.now()
19789
- },
19790
- expectedVersion: snapshot
19791
- }
19792
- ])
19960
+ await persistence.putMany(
19961
+ [
19962
+ { memory: successor, expectedVersion: 0 },
19963
+ {
19964
+ memory: {
19965
+ ...snapshot.memory,
19966
+ superseded_by: successor.memory_id,
19967
+ superseded_at: Date.now()
19968
+ },
19969
+ expectedVersion: snapshot
19970
+ }
19971
+ ],
19972
+ signal
19973
+ )
19793
19974
  );
19794
19975
  return { created: rows[0], retired: true };
19795
19976
  }
@@ -19822,7 +20003,7 @@ function createRemoteMemoryPipeline(options) {
19822
20003
  factSource,
19823
20004
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
19824
20005
  retain: async (content, retainOptions) => {
19825
- const { port } = operation("retain");
20006
+ const { port } = operation("retain", retainOptions);
19826
20007
  return retainWithPersistence(
19827
20008
  content,
19828
20009
  {
@@ -20404,16 +20585,21 @@ function createRemoteMemoryStore(options) {
20404
20585
  const record2 = await persistence.get(id);
20405
20586
  return record2 && !record2.memory.is_deleted ? record2 : null;
20406
20587
  };
20407
- const mutateLocked = async (id, change, edit) => {
20408
- const record2 = await live(id);
20588
+ const mutateLocked = async (id, change, edit, writeOptions) => {
20589
+ writeOptions?.signal?.throwIfAborted();
20590
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
20409
20591
  if (!record2) return null;
20592
+ if (record2.memory.is_deleted) return null;
20593
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
20594
+ throw new Error("Memory source is no longer eligible");
20595
+ writeOptions?.signal?.throwIfAborted();
20410
20596
  const next = edit(structuredClone(record2.memory));
20411
20597
  if (!next) return null;
20412
- const saved = await persistence.put(next, record2);
20598
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
20413
20599
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
20414
20600
  return saved;
20415
20601
  };
20416
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
20602
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
20417
20603
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
20418
20604
  const mutateMany = async (ids, change, edit) => {
20419
20605
  const saved = /* @__PURE__ */ new Map();
@@ -20926,13 +21112,21 @@ function createRemoteMemoryStore(options) {
20926
21112
  }))
20927
21113
  );
20928
21114
  },
20929
- addTopics: async (memoryId, topics) => {
21115
+ addTopics: async (memoryId, topics, writeOptions) => {
20930
21116
  const added = await withKnownKinds(topicInputs(topics, "auto"));
20931
- const saved = await mutate(memoryId, "edit", (memory) => ({
20932
- ...memory,
20933
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
20934
- topics_updated_at: Date.now()
20935
- }));
21117
+ const saved = await mutate(
21118
+ memoryId,
21119
+ "edit",
21120
+ (memory) => {
21121
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
21122
+ return {
21123
+ ...memory,
21124
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
21125
+ topics_updated_at: Date.now()
21126
+ };
21127
+ },
21128
+ writeOptions
21129
+ );
20936
21130
  if (!saved) return [];
20937
21131
  const now = /* @__PURE__ */ new Date();
20938
21132
  return mergeTopics([], added).map(
@@ -21783,6 +21977,7 @@ function createDurableAutoExtractor(options) {
21783
21977
  };
21784
21978
  const folderId = options.folderId ?? null;
21785
21979
  let disposed = false;
21980
+ let activeBatch = null;
21786
21981
  let running = false;
21787
21982
  let wakeRequested = false;
21788
21983
  let timer;
@@ -21819,14 +22014,18 @@ function createDurableAutoExtractor(options) {
21819
22014
  }, delay);
21820
22015
  }
21821
22016
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
22017
+ if (disposed) throw new Error("Extraction session was disposed");
21822
22018
  let watchdog;
21823
22019
  let cancelled = false;
22020
+ const controller = new AbortController();
22021
+ activeBatch = controller;
21824
22022
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
21825
22023
  try {
21826
22024
  return await Promise.race([
21827
22025
  new Promise((resolve, reject) => {
21828
22026
  const worker = createAutoExtractor({
21829
22027
  ...options,
22028
+ signal: controller.signal,
21830
22029
  retainCtx: {
21831
22030
  ...options.retainCtx,
21832
22031
  vaultCtx: {
@@ -21855,6 +22054,8 @@ function createDurableAutoExtractor(options) {
21855
22054
  ]);
21856
22055
  } finally {
21857
22056
  cancelled = true;
22057
+ controller.abort();
22058
+ if (activeBatch === controller) activeBatch = null;
21858
22059
  if (watchdog !== void 0) clearTimeout(watchdog);
21859
22060
  }
21860
22061
  }
@@ -21866,6 +22067,7 @@ function createDurableAutoExtractor(options) {
21866
22067
  for (const message of loaded)
21867
22068
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
21868
22069
  await database.write(async () => {
22070
+ if (disposed) return;
21869
22071
  const current = idsOf(job);
21870
22072
  const acknowledged = ids.filter((id) => current.includes(id));
21871
22073
  if (!acknowledged.length) return;
@@ -22038,6 +22240,7 @@ function createDurableAutoExtractor(options) {
22038
22240
  jobScope,
22039
22241
  jobFolderId
22040
22242
  );
22243
+ if (disposed) throw new Error("Extraction session was disposed");
22041
22244
  options.onTurnComplete?.(result);
22042
22245
  return result;
22043
22246
  };
@@ -22074,6 +22277,7 @@ function createDurableAutoExtractor(options) {
22074
22277
  );
22075
22278
  scheduleAfterSuccess = true;
22076
22279
  } catch (error) {
22280
+ if (disposed) break;
22077
22281
  attempts.set(job.id, attempted + 1);
22078
22282
  retry = true;
22079
22283
  reportError(error, conversationId);
@@ -22096,6 +22300,14 @@ function createDurableAutoExtractor(options) {
22096
22300
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
22097
22301
  }
22098
22302
  }
22303
+ function dispose() {
22304
+ disposed = true;
22305
+ activeBatch?.abort(options.signal?.reason);
22306
+ if (timer !== void 0) clearTimeout(timer);
22307
+ options.signal?.removeEventListener("abort", dispose);
22308
+ }
22309
+ if (options.signal?.aborted) dispose();
22310
+ else options.signal?.addEventListener("abort", dispose, { once: true });
22099
22311
  schedule(0);
22100
22312
  return {
22101
22313
  processTurn(messages, conversationId) {
@@ -22175,10 +22387,7 @@ function createDurableAutoExtractor(options) {
22175
22387
  return true;
22176
22388
  },
22177
22389
  isProcessing: () => running,
22178
- dispose() {
22179
- disposed = true;
22180
- if (timer !== void 0) clearTimeout(timer);
22181
- }
22390
+ dispose
22182
22391
  };
22183
22392
  }
22184
22393
 
@@ -25427,6 +25636,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
25427
25636
  var NO_CONVERSATION_KEY = "__no_conversation__";
25428
25637
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
25429
25638
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
25639
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
25430
25640
  var conversationRedactors = /* @__PURE__ */ new Map();
25431
25641
  var clientToolFilterCache = /* @__PURE__ */ new Map();
25432
25642
  function getConversationRedactor(conversationId, nerDetector) {
@@ -25571,7 +25781,7 @@ function useChatStorage(options) {
25571
25781
  [resolvedPiiRedaction]
25572
25782
  );
25573
25783
  const maskEmbeddingInput = isPiiRedactor(resolvedPiiRedaction) ? maskForEmbedding : void 0;
25574
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
25784
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
25575
25785
  const messagesCollection = useMemo(() => database.get("history"), [database]);
25576
25786
  const conversationsCollection = useMemo(
25577
25787
  () => database.get("conversations"),
@@ -25820,6 +26030,7 @@ function useChatStorage(options) {
25820
26030
  }, [vaultEmbeddingCache]);
25821
26031
  const retainVaultMemory = useCallback4(
25822
26032
  async (input) => {
26033
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25823
26034
  if (memoryStore) {
25824
26035
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
25825
26036
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -25865,6 +26076,7 @@ function useChatStorage(options) {
25865
26076
  );
25866
26077
  const createMemoryVaultTool2 = useCallback4(
25867
26078
  (options2) => {
26079
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25868
26080
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
25869
26081
  return createMemoryVaultTool(
25870
26082
  memoryStore ?? vaultCtx,
@@ -25875,6 +26087,7 @@ function useChatStorage(options) {
25875
26087
  );
25876
26088
  const createRecallTool2 = useCallback4(
25877
26089
  (toolOptions, callbacks) => {
26090
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25878
26091
  if (!getToken && !memoryStore) {
25879
26092
  throw new Error("getToken is required for recall tool");
25880
26093
  }
@@ -25920,7 +26133,7 @@ function useChatStorage(options) {
25920
26133
  );
25921
26134
  const recallFn = useCallback4(
25922
26135
  async (query, options2) => {
25923
- if (!getToken && !memoryStore) {
26136
+ if (memoryStore === null || !getToken && !memoryStore) {
25924
26137
  return {
25925
26138
  memories: [],
25926
26139
  usedBudget: options2?.budget ?? "low",
@@ -25969,6 +26182,7 @@ function useChatStorage(options) {
25969
26182
  );
25970
26183
  const getVaultMemories = useCallback4(
25971
26184
  (options2) => {
26185
+ if (memoryStore === null) return Promise.resolve([]);
25972
26186
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
25973
26187
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
25974
26188
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -25979,7 +26193,8 @@ function useChatStorage(options) {
25979
26193
  [vaultCtx, memoryStore]
25980
26194
  );
25981
26195
  const deleteVaultMemory = useCallback4(
25982
- (id) => {
26196
+ async (id) => {
26197
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25983
26198
  return memoryStore ? memoryStore.delete(id) : deleteVaultMemoryOp(vaultCtx, id);
25984
26199
  },
25985
26200
  [vaultCtx, memoryStore]