@anuma/sdk 7.0.6 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  }
@@ -13097,6 +13106,17 @@ async function decryptVaultMemoryFields(memory, address, signMessage, embeddedWa
13097
13106
  };
13098
13107
  }
13099
13108
 
13109
+ // src/lib/db/memoryVault/sourceChunkIds.ts
13110
+ var MAX_SOURCE_CHUNK_IDS_BYTES = 16 << 10;
13111
+ var utf8 = new TextEncoder();
13112
+ function serializeSourceChunkIds(ids) {
13113
+ const sizes = ids.map((id) => utf8.encode(JSON.stringify(id)).length + 1);
13114
+ let total = sizes.reduce((sum, size) => sum + size, 1);
13115
+ let start = 0;
13116
+ while (total > MAX_SOURCE_CHUNK_IDS_BYTES && start < ids.length) total -= sizes[start++];
13117
+ return JSON.stringify(ids.slice(start));
13118
+ }
13119
+
13100
13120
  // src/lib/db/memoryVault/types.ts
13101
13121
  var MEMORY_KINDS = [
13102
13122
  "display_name",
@@ -13169,6 +13189,11 @@ function visibilityConditions(requested) {
13169
13189
  function isKinded(record2) {
13170
13190
  return record2.kind !== null && record2.kind !== void 0;
13171
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
+ }
13172
13197
  function assertValidKindLevel(kind, level) {
13173
13198
  const hasKind = kind !== null && kind !== void 0;
13174
13199
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -13244,15 +13269,15 @@ async function mapInBatches(items, fn) {
13244
13269
  return results;
13245
13270
  }
13246
13271
  function vaultMemoryToStoredRaw(memory) {
13247
- let sourceChunkIds = null;
13272
+ let sourceChunkIds2 = null;
13248
13273
  if (memory.sourceChunkIds) {
13249
13274
  try {
13250
13275
  const parsed = JSON.parse(memory.sourceChunkIds);
13251
13276
  if (Array.isArray(parsed)) {
13252
- sourceChunkIds = parsed.filter((s) => typeof s === "string");
13277
+ sourceChunkIds2 = parsed.filter((s) => typeof s === "string");
13253
13278
  }
13254
13279
  } catch {
13255
- sourceChunkIds = null;
13280
+ sourceChunkIds2 = null;
13256
13281
  }
13257
13282
  }
13258
13283
  return {
@@ -13266,7 +13291,7 @@ function vaultMemoryToStoredRaw(memory) {
13266
13291
  userId: memory.userId ?? null,
13267
13292
  embedding: memory.embedding ?? null,
13268
13293
  embeddingModel: memory.embeddingModel ?? null,
13269
- sourceChunkIds,
13294
+ sourceChunkIds: sourceChunkIds2,
13270
13295
  proofCount: memory.proofCount ?? null,
13271
13296
  source: memory.source ?? null,
13272
13297
  media: parseMedia(memory.media),
@@ -13315,7 +13340,7 @@ function populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedK
13315
13340
  record2._setRaw("embedding_model", opts.embeddingModel ?? null);
13316
13341
  }
13317
13342
  if (opts.sourceChunkIds !== void 0) {
13318
- record2._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
13343
+ record2._setRaw("source_chunk_ids", serializeSourceChunkIds(opts.sourceChunkIds));
13319
13344
  }
13320
13345
  record2._setRaw("proof_count", opts.proofCount ?? 1);
13321
13346
  record2._setRaw("source", opts.source ?? "manual");
@@ -13349,8 +13374,7 @@ async function createVaultMemoryOp(ctx, opts) {
13349
13374
  ctx.embeddedWalletSigner
13350
13375
  ) : opts.content;
13351
13376
  const created = await ctx.database.write(async () => {
13352
- if (ctx.canWrite && !await ctx.canWrite())
13353
- throw new Error("Memory source is no longer eligible");
13377
+ await assertCanWrite2(ctx);
13354
13378
  return ctx.vaultMemoryCollection.create(
13355
13379
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
13356
13380
  );
@@ -13369,8 +13393,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
13369
13393
  ) : opts.content;
13370
13394
  let createdRecord = null;
13371
13395
  await ctx.database.write(async () => {
13372
- if (ctx.canWrite && !await ctx.canWrite())
13373
- throw new Error("Memory source is no longer eligible");
13396
+ await assertCanWrite2(ctx);
13374
13397
  let target;
13375
13398
  try {
13376
13399
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -13462,8 +13485,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
13462
13485
  })
13463
13486
  );
13464
13487
  const created = await ctx.database.write(async () => {
13465
- if (ctx.canWrite && !await ctx.canWrite())
13466
- throw new Error("Memory source is no longer eligible");
13488
+ await assertCanWrite2(ctx);
13467
13489
  const prepared = optionsArray.map(
13468
13490
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
13469
13491
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -13493,16 +13515,16 @@ async function getVaultMemoryOp(ctx, id) {
13493
13515
  }
13494
13516
  }
13495
13517
  function vaultMemoryRawToStoredRaw(raw) {
13496
- let sourceChunkIds = null;
13518
+ let sourceChunkIds2 = null;
13497
13519
  const rawChunks = raw.source_chunk_ids;
13498
13520
  if (typeof rawChunks === "string" && rawChunks) {
13499
13521
  try {
13500
13522
  const parsed = JSON.parse(rawChunks);
13501
13523
  if (Array.isArray(parsed)) {
13502
- sourceChunkIds = parsed.filter((s) => typeof s === "string");
13524
+ sourceChunkIds2 = parsed.filter((s) => typeof s === "string");
13503
13525
  }
13504
13526
  } catch {
13505
- sourceChunkIds = null;
13527
+ sourceChunkIds2 = null;
13506
13528
  }
13507
13529
  }
13508
13530
  return {
@@ -13516,7 +13538,7 @@ function vaultMemoryRawToStoredRaw(raw) {
13516
13538
  userId: raw.user_id ?? null,
13517
13539
  embedding: raw.embedding ?? null,
13518
13540
  embeddingModel: raw.embedding_model ?? null,
13519
- sourceChunkIds,
13541
+ sourceChunkIds: sourceChunkIds2,
13520
13542
  proofCount: raw.proof_count ?? null,
13521
13543
  source: raw.source ?? null,
13522
13544
  eventTimeStart: raw.event_time_start ?? null,
@@ -13768,8 +13790,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
13768
13790
  const record2 = probe;
13769
13791
  const originalUpdatedAt = record2.updatedAt.getTime();
13770
13792
  await ctx.database.write(async () => {
13771
- if (ctx.canWrite && !await ctx.canWrite())
13772
- throw new Error("Memory source is no longer eligible");
13793
+ await assertCanWrite2(ctx);
13773
13794
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
13774
13795
  stale = true;
13775
13796
  return;
@@ -13813,7 +13834,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
13813
13834
  if (opts.sourceChunkIds !== void 0) {
13814
13835
  r._setRaw(
13815
13836
  "source_chunk_ids",
13816
- JSON.stringify(
13837
+ serializeSourceChunkIds(
13817
13838
  opts.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observedSources, ...opts.sourceChunkIds])] : opts.sourceChunkIds
13818
13839
  )
13819
13840
  );
@@ -13880,6 +13901,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
13880
13901
  let stale = false;
13881
13902
  const originalUpdatedAt = record2.updatedAt.getTime();
13882
13903
  await ctx.database.write(async (writer) => {
13904
+ await assertCanWrite2(ctx);
13883
13905
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13884
13906
  stale = true;
13885
13907
  return;
@@ -13921,6 +13943,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
13921
13943
  const originalUpdatedAt = record2.updatedAt.getTime();
13922
13944
  let cleared = false;
13923
13945
  await ctx.database.write(async () => {
13946
+ await assertCanWrite2(ctx);
13924
13947
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
13925
13948
  cleared = true;
13926
13949
  await record2.update((r) => {
@@ -13944,6 +13967,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
13944
13967
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
13945
13968
  let stale = false;
13946
13969
  await ctx.database.write(async () => {
13970
+ await assertCanWrite2(ctx);
13947
13971
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13948
13972
  stale = true;
13949
13973
  return;
@@ -13972,6 +13996,7 @@ async function deleteVaultMemoryOp(ctx, id) {
13972
13996
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
13973
13997
  let stale = false;
13974
13998
  await ctx.database.write(async () => {
13999
+ await assertCanWrite2(ctx);
13975
14000
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13976
14001
  stale = true;
13977
14002
  return;
@@ -14009,8 +14034,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
14009
14034
  }
14010
14035
  let stale = false;
14011
14036
  await ctx.database.write(async () => {
14012
- if (ctx.canWrite && !await ctx.canWrite())
14013
- throw new Error("Memory source is no longer eligible");
14037
+ await assertCanWrite2(ctx);
14014
14038
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
14015
14039
  stale = true;
14016
14040
  return;
@@ -14172,6 +14196,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
14172
14196
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14173
14197
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14174
14198
  await ctx.database.write(async () => {
14199
+ await assertCanWrite2(ctx);
14175
14200
  const records = [];
14176
14201
  for (const id of chunkIds) {
14177
14202
  try {
@@ -14205,6 +14230,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
14205
14230
  for (const id of Array.from(new Set(memoryIds))) {
14206
14231
  try {
14207
14232
  await ctx.database.write(async (writer) => {
14233
+ await assertCanWrite2(ctx);
14208
14234
  let record2;
14209
14235
  try {
14210
14236
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -14235,6 +14261,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
14235
14261
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14236
14262
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14237
14263
  await ctx.database.write(async () => {
14264
+ await assertCanWrite2(ctx);
14238
14265
  const writes = [];
14239
14266
  for (const id of chunkIds) {
14240
14267
  let record2;
@@ -14267,6 +14294,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
14267
14294
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb20.Q.where("user_id", userId), import_watermelondb20.Q.where("is_deleted", false)).fetch();
14268
14295
  if (records.length === 0) return 0;
14269
14296
  await ctx.database.write(async () => {
14297
+ await assertCanWrite2(ctx);
14270
14298
  const prepared = records.map(
14271
14299
  (record2) => record2.prepareUpdate((r) => {
14272
14300
  r._setRaw("is_deleted", true);
@@ -14321,6 +14349,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
14321
14349
  let stale = false;
14322
14350
  const archivedAtValue = opts?.now ?? Date.now();
14323
14351
  await ctx.database.write(async () => {
14352
+ await assertCanWrite2(ctx);
14324
14353
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
14325
14354
  stale = true;
14326
14355
  return;
@@ -14344,6 +14373,7 @@ async function restoreVaultMemoryOp(ctx, id) {
14344
14373
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
14345
14374
  let stale = false;
14346
14375
  await ctx.database.write(async () => {
14376
+ await assertCanWrite2(ctx);
14347
14377
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
14348
14378
  stale = true;
14349
14379
  return;
@@ -14364,6 +14394,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
14364
14394
  const now = opts.now ?? Date.now();
14365
14395
  let stale = false;
14366
14396
  await ctx.database.write(async () => {
14397
+ await assertCanWrite2(ctx);
14367
14398
  const archivedAt = record2.archivedAt;
14368
14399
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
14369
14400
  stale = true;
@@ -14403,6 +14434,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
14403
14434
  }
14404
14435
  let written = false;
14405
14436
  await ctx.database.write(async () => {
14437
+ await assertCanWrite2(ctx);
14406
14438
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
14407
14439
  const originalUpdatedAt = record2.updatedAt.getTime();
14408
14440
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -17939,6 +17971,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
17939
17971
  ).map((c) => c.candidate);
17940
17972
  }
17941
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();
17942
17981
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
17943
17982
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
17944
17983
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -17972,6 +18011,7 @@ async function extractAndRetain(messages, retainCtx, options) {
17972
18011
  }
17973
18012
  });
17974
18013
  const extractMs = Date.now() - tExtract;
18014
+ signal?.throwIfAborted();
17975
18015
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
17976
18016
  const log = getLogger();
17977
18017
  const screened = screenCandidatesForInjection(filtered);
@@ -18019,16 +18059,33 @@ async function extractAndRetain(messages, retainCtx, options) {
18019
18059
  const results = [];
18020
18060
  const quarantinedInfo = [];
18021
18061
  let failedWrites = 0;
18062
+ let auditRows;
18022
18063
  const tRetain = Date.now();
18023
18064
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
18024
18065
  try {
18066
+ signal?.throwIfAborted();
18025
18067
  if (isQuarantined) {
18026
- const existingId = await findQuarantinedDuplicateOp(
18027
- retainCtx.vaultCtx,
18028
- candidate.content,
18029
- candidate.sourceMessageIds,
18030
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
18031
- ).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
+ }
18032
18089
  if (existingId) {
18033
18090
  quarantinedInfo.push({
18034
18091
  candidate,
@@ -18039,18 +18096,26 @@ async function extractAndRetain(messages, retainCtx, options) {
18039
18096
  continue;
18040
18097
  }
18041
18098
  }
18042
- const result = await retain(candidate.content, retainCtx, {
18099
+ const retainOptions = {
18043
18100
  source: "auto-extracted",
18044
18101
  sourceChunkIds: candidate.sourceMessageIds,
18045
18102
  respectTombstones: true,
18046
18103
  ...options.scope !== void 0 && { scope: options.scope },
18047
- ...options.folderId !== void 0 && { folderId: options.folderId },
18048
18104
  ...consolidateOptions !== void 0 && { consolidateOptions },
18049
18105
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
18050
18106
  factType: candidate.type,
18051
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 }
18052
18116
  });
18053
18117
  if (isQuarantined) {
18118
+ auditRows = void 0;
18054
18119
  const info = {
18055
18120
  candidate,
18056
18121
  memoryId: result.memoryId,
@@ -18069,9 +18134,16 @@ async function extractAndRetain(messages, retainCtx, options) {
18069
18134
  }
18070
18135
  succeededCandidates.push(candidate);
18071
18136
  results.push(result);
18072
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
18137
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
18073
18138
  try {
18074
- 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)) {
18075
18147
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
18076
18148
  unlessTopicsUserManaged: true
18077
18149
  });
@@ -18321,7 +18393,7 @@ function createAutoExtractor(options) {
18321
18393
  };
18322
18394
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
18323
18395
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
18324
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18396
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18325
18397
  getLogger().warn(
18326
18398
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
18327
18399
  );
@@ -18366,6 +18438,8 @@ function createAutoExtractor(options) {
18366
18438
  try {
18367
18439
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
18368
18440
  extract,
18441
+ memoryStore: options.memoryStore,
18442
+ signal: options.signal,
18369
18443
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
18370
18444
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
18371
18445
  ...options.scope !== void 0 && { scope: options.scope },
@@ -20471,6 +20545,7 @@ function createLocalMemoryStore(options) {
20471
20545
  database,
20472
20546
  entityCollection: database.get("entity"),
20473
20547
  memoryEntityCollection: database.get("memory_entity"),
20548
+ canWrite: options.canWrite,
20474
20549
  ...userId !== void 0 && { userId },
20475
20550
  ...options.allowUnscopedRows !== void 0 && {
20476
20551
  allowUnscopedRows: options.allowUnscopedRows
@@ -20482,10 +20557,21 @@ function createLocalMemoryStore(options) {
20482
20557
  walletAddress: options.walletAddress,
20483
20558
  signMessage: options.signMessage,
20484
20559
  embeddedWalletSigner: options.embeddedWalletSigner,
20560
+ canWrite: options.canWrite,
20485
20561
  ...userId !== void 0 && { userId },
20486
20562
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
20487
20563
  entityCtx
20488
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
+ };
20489
20575
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
20490
20576
  const ownedBy = userId !== void 0 ? [import_watermelondb23.Q.where("user_id", userId)] : [];
20491
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)];
@@ -20597,9 +20683,25 @@ function createLocalMemoryStore(options) {
20597
20683
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
20598
20684
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
20599
20685
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
20600
- addTopics: (memoryId, topics) => database.write(async (writer) => {
20686
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
20687
+ await assertWrite(writeOptions);
20601
20688
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb23.Q.where("id", memoryId), import_watermelondb23.Q.where("is_deleted", false), ...ownedBy).fetchCount();
20602
- 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
+ ) : [];
20603
20705
  }),
20604
20706
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
20605
20707
  recall: (query, recallOptions) => recall(
@@ -20609,7 +20711,17 @@ function createLocalMemoryStore(options) {
20609
20711
  ),
20610
20712
  retain: (content, retainOptions) => retain(
20611
20713
  content,
20612
- { 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
+ },
20613
20725
  retainOptions
20614
20726
  ),
20615
20727
  factSource: {
@@ -20713,13 +20825,20 @@ function memoryCipher(canonical) {
20713
20825
  };
20714
20826
  }
20715
20827
  async function reencryptMemoryField(value, canonical, fallbacks = []) {
20716
- const { encrypt } = memoryCipher(canonical);
20717
- if (!value.startsWith("enc:")) return encrypt(value);
20828
+ return memoryCipher(canonical).encrypt(
20829
+ await decryptMemoryField(value, [canonical, ...fallbacks])
20830
+ );
20831
+ }
20832
+ async function decryptMemoryField(value, rings) {
20833
+ if (!value.startsWith("enc:")) return value;
20718
20834
  if (!isEncrypted(value))
20719
20835
  throw new MemoryKeyError("Field has an encryption prefix but an invalid payload");
20720
- return encrypt(await decryptWith(value, [canonical, ...fallbacks]));
20836
+ return decryptWith(value, rings);
20721
20837
  }
20722
20838
 
20839
+ // src/lib/memory/store/remoteMigration.ts
20840
+ init_logger();
20841
+
20723
20842
  // src/lib/memory/store/remotePersistence.ts
20724
20843
  init_portalLlm();
20725
20844
  var RemoteMemoryError = class extends Error {
@@ -21257,7 +21376,14 @@ function stored(row) {
21257
21376
  function createRemoteMemoryPipeline(options) {
21258
21377
  const { persistence, embeddingOptions } = options;
21259
21378
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
21260
- 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
+ };
21261
21387
  const rejectUndecryptable = (failed) => {
21262
21388
  if (!failed.length) return;
21263
21389
  if (mode === "retain")
@@ -21275,7 +21401,7 @@ function createRemoteMemoryPipeline(options) {
21275
21401
  return items.map((item) => stored(item.memory));
21276
21402
  };
21277
21403
  const get = async (id) => {
21278
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
21404
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
21279
21405
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
21280
21406
  snapshots.set(id, snapshot);
21281
21407
  return stored(snapshot.memory);
@@ -21325,7 +21451,7 @@ function createRemoteMemoryPipeline(options) {
21325
21451
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
21326
21452
  }
21327
21453
  const forceIds = [...forced];
21328
- const windowOptions = (i, signal) => ({
21454
+ const windowOptions = (i, signal2) => ({
21329
21455
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
21330
21456
  force_ids: i === 0 ? forceIds : [],
21331
21457
  ...search.scopes && { scopes: search.scopes },
@@ -21333,13 +21459,15 @@ function createRemoteMemoryPipeline(options) {
21333
21459
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
21334
21460
  include_archived: search.includeArchived,
21335
21461
  embedding_model: model,
21336
- ...signal && { signal }
21462
+ ...signal2 && { signal: signal2 }
21337
21463
  });
21338
21464
  const vectors = [queryEmbedding, ...facetEmbeddings];
21339
21465
  let windows;
21340
21466
  if (mode === "retain") {
21341
21467
  windows = await Promise.all(
21342
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
21468
+ vectors.map(
21469
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
21470
+ )
21343
21471
  );
21344
21472
  } else {
21345
21473
  const deadline = new AbortController();
@@ -21412,7 +21540,7 @@ function createRemoteMemoryPipeline(options) {
21412
21540
  embedding: storedVector(JSON.parse(input.embedding)),
21413
21541
  embedding_model: input.embeddingModel
21414
21542
  },
21415
- source_chunk_ids: JSON.stringify(input.sourceChunkIds ?? []),
21543
+ source_chunk_ids: serializeSourceChunkIds(input.sourceChunkIds ?? []),
21416
21544
  proof_count: input.proofCount ?? 1,
21417
21545
  source: input.source ?? "manual",
21418
21546
  ...input.eventTime && {
@@ -21427,12 +21555,15 @@ function createRemoteMemoryPipeline(options) {
21427
21555
  twin_opt_in: false
21428
21556
  };
21429
21557
  };
21430
- 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
+ };
21431
21562
  const port = {
21432
21563
  prepare,
21433
21564
  get,
21434
21565
  getFresh: async (id) => {
21435
- const current = await persistence.get(id);
21566
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
21436
21567
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
21437
21568
  },
21438
21569
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -21443,7 +21574,8 @@ function createRemoteMemoryPipeline(options) {
21443
21574
  embedding_model: embeddingModel,
21444
21575
  strict_model: true,
21445
21576
  include_deleted: true,
21446
- deleted_only: true
21577
+ deleted_only: true,
21578
+ signal
21447
21579
  });
21448
21580
  rejectUndecryptable(window2.failed);
21449
21581
  return remember(window2.items);
@@ -21471,7 +21603,7 @@ function createRemoteMemoryPipeline(options) {
21471
21603
  const observed = sourceIds(memory.source_chunk_ids);
21472
21604
  const replay = !!patch.observationSourceIds?.length && patch.observationSourceIds.every((id2) => observed.includes(id2));
21473
21605
  if (patch.sourceChunkIds !== void 0)
21474
- memory.source_chunk_ids = JSON.stringify(
21606
+ memory.source_chunk_ids = serializeSourceChunkIds(
21475
21607
  patch.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observed, ...patch.sourceChunkIds])] : patch.sourceChunkIds
21476
21608
  );
21477
21609
  if (!replay) {
@@ -21499,14 +21631,18 @@ function createRemoteMemoryPipeline(options) {
21499
21631
  if (!target || !newer || id === successor) return false;
21500
21632
  const old = snapshots.get(id);
21501
21633
  const next = snapshots.get(successor);
21634
+ await assertWrite();
21502
21635
  remember(
21503
- await persistence.putMany([
21504
- {
21505
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
21506
- expectedVersion: old
21507
- },
21508
- { memory: next.memory, expectedVersion: next }
21509
- ])
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
+ )
21510
21646
  );
21511
21647
  return true;
21512
21648
  },
@@ -21515,18 +21651,22 @@ function createRemoteMemoryPipeline(options) {
21515
21651
  if (!target) return { created: null, retired: false };
21516
21652
  const snapshot = snapshots.get(targetId);
21517
21653
  const successor = createRow(input);
21654
+ await assertWrite();
21518
21655
  const rows = remember(
21519
- await persistence.putMany([
21520
- { memory: successor, expectedVersion: 0 },
21521
- {
21522
- memory: {
21523
- ...snapshot.memory,
21524
- superseded_by: successor.memory_id,
21525
- superseded_at: Date.now()
21526
- },
21527
- expectedVersion: snapshot
21528
- }
21529
- ])
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
+ )
21530
21670
  );
21531
21671
  return { created: rows[0], retired: true };
21532
21672
  }
@@ -21561,7 +21701,7 @@ function createRemoteMemoryPipeline(options) {
21561
21701
  factSource,
21562
21702
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
21563
21703
  retain: async (content, retainOptions) => {
21564
- const { port } = operation("retain");
21704
+ const { port } = operation("retain", embeddingOptions, retainOptions);
21565
21705
  return retainWithPersistence(
21566
21706
  content,
21567
21707
  {
@@ -21578,6 +21718,8 @@ function createRemoteMemoryPipeline(options) {
21578
21718
 
21579
21719
  // src/lib/memory/store/remoteMigration.ts
21580
21720
  var IMPORT_BATCH = 50;
21721
+ var EMBED_BATCH = 100;
21722
+ var LOCAL_VECTOR_DIMS = 4096;
21581
21723
  var VERIFY_BATCH = 1e3;
21582
21724
  var QUERY_IDS = 1e3;
21583
21725
  var QUERY_PAGE = 200;
@@ -21603,7 +21745,7 @@ var SHORT_FIELDS = [
21603
21745
  "visibility",
21604
21746
  "geohash"
21605
21747
  ];
21606
- var utf8 = new TextEncoder();
21748
+ var utf82 = new TextEncoder();
21607
21749
  var METADATA_FIELDS = [
21608
21750
  "scope",
21609
21751
  "kind",
@@ -21654,17 +21796,34 @@ function revision(raw) {
21654
21796
  last_observed_at: num(raw.last_observed_at) ?? null
21655
21797
  };
21656
21798
  }
21657
- function embedding(raw) {
21799
+ function embedding(raw, model) {
21658
21800
  const value = text13(raw.embedding);
21659
21801
  if (!value) return void 0;
21660
21802
  try {
21661
- return storedVector(JSON.parse(value));
21803
+ const vector = JSON.parse(value);
21804
+ const stored2 = text13(raw.embedding_model);
21805
+ if (stored2 === void 0 ? vector.length !== LOCAL_VECTOR_DIMS : stored2 !== model)
21806
+ return void 0;
21807
+ return storedVector(vector);
21662
21808
  } catch {
21663
21809
  return void 0;
21664
21810
  }
21665
21811
  }
21812
+ function stringArray(value) {
21813
+ try {
21814
+ const parsed = JSON.parse(value);
21815
+ return Array.isArray(parsed) && parsed.every((id) => typeof id === "string") ? parsed : void 0;
21816
+ } catch {
21817
+ return void 0;
21818
+ }
21819
+ }
21820
+ function sourceChunkIds(raw) {
21821
+ const value = text13(raw.source_chunk_ids);
21822
+ const ids = value && stringArray(value);
21823
+ return ids ? serializeSourceChunkIds(ids) : value;
21824
+ }
21666
21825
  function bytes(value) {
21667
- return value === void 0 ? 0 : utf8.encode(value).length;
21826
+ return value === void 0 ? 0 : utf82.encode(value).length;
21668
21827
  }
21669
21828
  function fitsNearby(row) {
21670
21829
  return bytes(row.memory_id) <= MAX_ID_BYTES && bytes(row.content) <= MAX_CONTENT_BYTES && bytes(row.kind_value) <= MAX_CONTENT_BYTES && TEXT_FIELDS.every((field10) => bytes(row[field10]) <= MAX_TEXT_BYTES) && SHORT_FIELDS.every((field10) => bytes(row[field10]) <= MAX_SHORT_BYTES);
@@ -21673,16 +21832,16 @@ function invalid(raw) {
21673
21832
  const id = raw.id;
21674
21833
  return typeof id !== "string" || id === "" || bytes(id) > MAX_ID_BYTES || !text13(raw.scope) || (num(raw.created_at) ?? 0) <= 0 || (num(raw.updated_at) ?? 0) <= 0;
21675
21834
  }
21676
- function metadata(raw) {
21677
- const vector = embedding(raw);
21835
+ function metadata(raw, model) {
21836
+ const vector = embedding(raw, model);
21678
21837
  return {
21679
21838
  scope: raw.scope,
21680
21839
  kind: text13(raw.kind),
21681
21840
  level: text13(raw.level),
21682
21841
  folder_id: text13(raw.folder_id),
21683
21842
  is_deleted: bool(raw.is_deleted) ?? false,
21684
- ...vector && { embedding: vector, embedding_model: text13(raw.embedding_model) },
21685
- source_chunk_ids: text13(raw.source_chunk_ids),
21843
+ ...vector && { embedding: vector, embedding_model: model },
21844
+ source_chunk_ids: sourceChunkIds(raw),
21686
21845
  proof_count: num(raw.proof_count),
21687
21846
  source: text13(raw.source),
21688
21847
  event_time_start: num(raw.event_time_start),
@@ -21711,30 +21870,63 @@ function defined(value) {
21711
21870
  Object.entries(value).filter(([, field10]) => field10 !== void 0)
21712
21871
  );
21713
21872
  }
21714
- async function remoteRow(raw, canonical, fallbacks, updatedAt) {
21715
- const content = await reencryptMemoryField(
21716
- typeof raw.content === "string" ? raw.content : "",
21717
- canonical,
21718
- fallbacks
21719
- );
21720
- const kindValue = text13(raw.kind_value);
21721
- const encryptedKindValue = kindValue ? await reencryptMemoryField(kindValue, canonical, fallbacks) : void 0;
21873
+ async function prepareRow(raw, canonical, rings, model, updatedAt) {
21874
+ const deleted = bool(raw.is_deleted) ?? false;
21875
+ const { encrypt } = memoryCipher(canonical);
21876
+ const localKindValue = text13(raw.kind_value);
21877
+ let plaintext;
21878
+ let kindValue;
21879
+ try {
21880
+ plaintext = await decryptMemoryField(typeof raw.content === "string" ? raw.content : "", rings);
21881
+ kindValue = localKindValue && await decryptMemoryField(localKindValue, rings);
21882
+ } catch (error) {
21883
+ if (!deleted || !(error instanceof MemoryKeyError)) throw error;
21884
+ plaintext = void 0;
21885
+ kindValue = void 0;
21886
+ }
21722
21887
  const row = {
21723
- ...defined({ ...metadata(raw), kind_value: encryptedKindValue }),
21888
+ ...defined({
21889
+ ...metadata(raw, model),
21890
+ kind_value: kindValue === void 0 ? void 0 : await encrypt(kindValue)
21891
+ }),
21724
21892
  memory_id: raw.id,
21725
- content,
21893
+ content: await encrypt(plaintext ?? ""),
21726
21894
  scope: raw.scope,
21727
21895
  created_at: raw.created_at,
21728
21896
  updated_at: updatedAt,
21729
- is_deleted: bool(raw.is_deleted) ?? false
21897
+ is_deleted: deleted
21730
21898
  };
21731
- return fitsNearby(row) ? row : null;
21899
+ return { row, plaintext };
21900
+ }
21901
+ async function embedMissing(prepared, options, model, signal) {
21902
+ const needed = prepared.filter((p) => !p.row.embedding && p.plaintext);
21903
+ for (const batch of chunks(needed, EMBED_BATCH)) {
21904
+ if (signal?.aborted) throw aborted(signal);
21905
+ try {
21906
+ const vectors = await generateEmbeddings(
21907
+ batch.map((p) => p.plaintext),
21908
+ { ...options, taskType: "memory_retain_embed" }
21909
+ );
21910
+ if (vectors.length !== batch.length) throw new Error("Incomplete migration embeddings");
21911
+ const stored2 = vectors.map(storedVector);
21912
+ batch.forEach((p, i) => {
21913
+ p.row.embedding = stored2[i];
21914
+ p.row.embedding_model = model;
21915
+ });
21916
+ } catch (error) {
21917
+ getLogger().warn(
21918
+ `[memory/migration] Uploading ${batch.length} row(s) without a vector: embedding failed`,
21919
+ error
21920
+ );
21921
+ }
21922
+ }
21732
21923
  }
21733
- function sameMetadata(local, remote) {
21734
- const mine = metadata(local);
21924
+ function sameMetadata(local, remote, model) {
21925
+ const mine = metadata(local, model);
21735
21926
  return METADATA_FIELDS.every((field10) => {
21736
21927
  const a = mine[field10] ?? null;
21737
21928
  const b = remote[field10] ?? null;
21929
+ if (field10 === "embedding_model" && a === null) return true;
21738
21930
  if (field10 === "visibility") return (a ?? "private") === (b ?? "private");
21739
21931
  if (field10 === "is_deleted" || field10 === "topics_user_managed" || field10 === "twin_opt_in")
21740
21932
  return (a ?? false) === (b ?? false);
@@ -21830,7 +22022,8 @@ async function backfillTopics(ctx) {
21830
22022
  }
21831
22023
  async function migrateMemoriesToRemote(options) {
21832
22024
  const { canonical, signal } = options;
21833
- const fallbacks = options.fallbacks ?? [];
22025
+ const rings = [canonical, ...options.fallbacks ?? []];
22026
+ const model = options.embeddingOptions?.model ?? DEFAULT_API_EMBEDDING_MODEL;
21834
22027
  const request = createNearbyRequest(options);
21835
22028
  const account = await request("/account", {}, signal);
21836
22029
  if (typeof account.key_id === "string" && account.key_id !== canonical.keyId)
@@ -21841,8 +22034,13 @@ async function migrateMemoriesToRemote(options) {
21841
22034
  );
21842
22035
  const active = account.state === "active";
21843
22036
  let claimed = account.key_id === canonical.keyId;
22037
+ if (!active) await createDecaySweeper({ vaultCtx: options.vaultCtx }).runSweep();
21844
22038
  await backfillTopics(options.vaultCtx);
21845
22039
  const skipped = /* @__PURE__ */ new Map();
22040
+ const reembedded = /* @__PURE__ */ new Set();
22041
+ const unembedded = /* @__PURE__ */ new Set();
22042
+ const placeholders = /* @__PURE__ */ new Set();
22043
+ const truncated = /* @__PURE__ */ new Set();
21846
22044
  let imported = 0;
21847
22045
  for (let pass = 0; ; pass++) {
21848
22046
  const rows = await getAllVaultMemoryRowsOp(options.vaultCtx);
@@ -21858,9 +22056,10 @@ async function migrateMemoriesToRemote(options) {
21858
22056
  candidates.map((raw) => raw.id),
21859
22057
  signal
21860
22058
  );
22059
+ const needsVector = (raw, server) => !!options.embeddingOptions && !embedding(raw, model) && server.embedding_model !== model;
21861
22060
  for (const raw of candidates) {
21862
22061
  const server = remote.get(raw.id);
21863
- if (server && server.updated_at === raw.updated_at && !sameMetadata(raw, server))
22062
+ if (server && server.updated_at === raw.updated_at && (!sameMetadata(raw, server, model) || needsVector(raw, server)))
21864
22063
  pending.set(raw.id, raw.updated_at + 1);
21865
22064
  }
21866
22065
  }
@@ -21869,7 +22068,11 @@ async function migrateMemoriesToRemote(options) {
21869
22068
  state,
21870
22069
  total: rows.length,
21871
22070
  imported,
21872
- skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason }))
22071
+ skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason })),
22072
+ reembedded: reembedded.size,
22073
+ unembedded: unembedded.size,
22074
+ placeholderTombstones: placeholders.size,
22075
+ truncatedSourceChunkIds: truncated.size
21873
22076
  });
21874
22077
  if (skipped.size > 0 && !options.allowSkipped) return result("migrating");
21875
22078
  if (!claimed) await importBatch(request, canonical.keyId, [], signal);
@@ -21887,19 +22090,35 @@ async function migrateMemoriesToRemote(options) {
21887
22090
  409,
21888
22091
  "migration_incomplete"
21889
22092
  );
21890
- const upload = [];
22093
+ const prepared = [];
21891
22094
  for (const raw of candidates) {
21892
22095
  const updatedAt = pending.get(raw.id);
21893
22096
  if (updatedAt === void 0) continue;
21894
22097
  try {
21895
- const row = await remoteRow(raw, canonical, fallbacks, updatedAt);
21896
- if (row) upload.push(row);
21897
- else skipped.set(raw.id, "invalid");
22098
+ prepared.push(await prepareRow(raw, canonical, rings, model, updatedAt));
21898
22099
  } catch (error) {
21899
22100
  if (!(error instanceof MemoryKeyError)) throw error;
21900
22101
  skipped.set(raw.id, "unreadable");
21901
22102
  }
21902
22103
  }
22104
+ const usable = new Set(prepared.filter((p) => p.row.embedding).map((p) => p.row.memory_id));
22105
+ if (options.embeddingOptions)
22106
+ await embedMissing(prepared, options.embeddingOptions, model, signal);
22107
+ const local = new Map(candidates.map((raw) => [raw.id, raw]));
22108
+ const upload = [];
22109
+ for (const { row, plaintext } of prepared) {
22110
+ const id = row.memory_id;
22111
+ if (!fitsNearby(row)) {
22112
+ skipped.set(id, "invalid");
22113
+ continue;
22114
+ }
22115
+ upload.push(row);
22116
+ const track = (set, on) => on ? set.add(id) : set.delete(id);
22117
+ track(reembedded, !!row.embedding && !usable.has(id));
22118
+ track(unembedded, !row.embedding);
22119
+ track(placeholders, plaintext === void 0);
22120
+ track(truncated, row.source_chunk_ids !== text13(local.get(id).source_chunk_ids));
22121
+ }
21903
22122
  let sent = 0;
21904
22123
  options.onProgress?.({ imported: sent, toImport: upload.length });
21905
22124
  for (const batch of chunks(upload, IMPORT_BATCH)) {
@@ -22144,16 +22363,21 @@ function createRemoteMemoryStore(options) {
22144
22363
  const record2 = await persistence.get(id);
22145
22364
  return record2 && !record2.memory.is_deleted ? record2 : null;
22146
22365
  };
22147
- const mutateLocked = async (id, change, edit) => {
22148
- 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);
22149
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();
22150
22374
  const next = edit(structuredClone(record2.memory));
22151
22375
  if (!next) return null;
22152
- const saved = await persistence.put(next, record2);
22376
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
22153
22377
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
22154
22378
  return saved;
22155
22379
  };
22156
- 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));
22157
22381
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
22158
22382
  const mutateMany = async (ids, change, edit) => {
22159
22383
  const saved = /* @__PURE__ */ new Map();
@@ -22666,13 +22890,21 @@ function createRemoteMemoryStore(options) {
22666
22890
  }))
22667
22891
  );
22668
22892
  },
22669
- addTopics: async (memoryId, topics) => {
22893
+ addTopics: async (memoryId, topics, writeOptions) => {
22670
22894
  const added = await withKnownKinds(topicInputs(topics, "auto"));
22671
- const saved = await mutate(memoryId, "edit", (memory) => ({
22672
- ...memory,
22673
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
22674
- topics_updated_at: Date.now()
22675
- }));
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
+ );
22676
22908
  if (!saved) return [];
22677
22909
  const now = /* @__PURE__ */ new Date();
22678
22910
  return mergeTopics([], added).map(
@@ -23525,6 +23757,7 @@ function createDurableAutoExtractor(options) {
23525
23757
  };
23526
23758
  const folderId = options.folderId ?? null;
23527
23759
  let disposed = false;
23760
+ let activeBatch = null;
23528
23761
  let running = false;
23529
23762
  let wakeRequested = false;
23530
23763
  let timer;
@@ -23561,14 +23794,18 @@ function createDurableAutoExtractor(options) {
23561
23794
  }, delay);
23562
23795
  }
23563
23796
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
23797
+ if (disposed) throw new Error("Extraction session was disposed");
23564
23798
  let watchdog;
23565
23799
  let cancelled = false;
23800
+ const controller = new AbortController();
23801
+ activeBatch = controller;
23566
23802
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
23567
23803
  try {
23568
23804
  return await Promise.race([
23569
23805
  new Promise((resolve, reject) => {
23570
23806
  const worker = createAutoExtractor({
23571
23807
  ...options,
23808
+ signal: controller.signal,
23572
23809
  retainCtx: {
23573
23810
  ...options.retainCtx,
23574
23811
  vaultCtx: {
@@ -23597,6 +23834,8 @@ function createDurableAutoExtractor(options) {
23597
23834
  ]);
23598
23835
  } finally {
23599
23836
  cancelled = true;
23837
+ controller.abort();
23838
+ if (activeBatch === controller) activeBatch = null;
23600
23839
  if (watchdog !== void 0) clearTimeout(watchdog);
23601
23840
  }
23602
23841
  }
@@ -23608,6 +23847,7 @@ function createDurableAutoExtractor(options) {
23608
23847
  for (const message of loaded)
23609
23848
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
23610
23849
  await database.write(async () => {
23850
+ if (disposed) return;
23611
23851
  const current = idsOf(job);
23612
23852
  const acknowledged = ids.filter((id) => current.includes(id));
23613
23853
  if (!acknowledged.length) return;
@@ -23780,6 +24020,7 @@ function createDurableAutoExtractor(options) {
23780
24020
  jobScope,
23781
24021
  jobFolderId
23782
24022
  );
24023
+ if (disposed) throw new Error("Extraction session was disposed");
23783
24024
  options.onTurnComplete?.(result);
23784
24025
  return result;
23785
24026
  };
@@ -23816,6 +24057,7 @@ function createDurableAutoExtractor(options) {
23816
24057
  );
23817
24058
  scheduleAfterSuccess = true;
23818
24059
  } catch (error) {
24060
+ if (disposed) break;
23819
24061
  attempts.set(job.id, attempted + 1);
23820
24062
  retry = true;
23821
24063
  reportError(error, conversationId);
@@ -23838,6 +24080,14 @@ function createDurableAutoExtractor(options) {
23838
24080
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
23839
24081
  }
23840
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 });
23841
24091
  schedule(0);
23842
24092
  return {
23843
24093
  processTurn(messages, conversationId) {
@@ -23917,10 +24167,7 @@ function createDurableAutoExtractor(options) {
23917
24167
  return true;
23918
24168
  },
23919
24169
  isProcessing: () => running,
23920
- dispose() {
23921
- disposed = true;
23922
- if (timer !== void 0) clearTimeout(timer);
23923
- }
24170
+ dispose
23924
24171
  };
23925
24172
  }
23926
24173
 
@@ -27192,6 +27439,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
27192
27439
  var NO_CONVERSATION_KEY = "__no_conversation__";
27193
27440
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
27194
27441
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
27442
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
27195
27443
  var conversationRedactors = /* @__PURE__ */ new Map();
27196
27444
  var clientToolFilterCache = /* @__PURE__ */ new Map();
27197
27445
  function getConversationRedactor(conversationId, nerDetector) {
@@ -27336,7 +27584,7 @@ function useChatStorage(options) {
27336
27584
  [resolvedPiiRedaction]
27337
27585
  );
27338
27586
  const maskEmbeddingInput = isPiiRedactor(resolvedPiiRedaction) ? maskForEmbedding : void 0;
27339
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
27587
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
27340
27588
  const messagesCollection = (0, import_react5.useMemo)(() => database.get("history"), [database]);
27341
27589
  const conversationsCollection = (0, import_react5.useMemo)(
27342
27590
  () => database.get("conversations"),
@@ -27585,6 +27833,7 @@ function useChatStorage(options) {
27585
27833
  }, [vaultEmbeddingCache]);
27586
27834
  const retainVaultMemory = (0, import_react5.useCallback)(
27587
27835
  async (input) => {
27836
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27588
27837
  if (memoryStore) {
27589
27838
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
27590
27839
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -27630,6 +27879,7 @@ function useChatStorage(options) {
27630
27879
  );
27631
27880
  const createMemoryVaultTool2 = (0, import_react5.useCallback)(
27632
27881
  (options2) => {
27882
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27633
27883
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
27634
27884
  return createMemoryVaultTool(
27635
27885
  memoryStore ?? vaultCtx,
@@ -27640,6 +27890,7 @@ function useChatStorage(options) {
27640
27890
  );
27641
27891
  const createRecallTool2 = (0, import_react5.useCallback)(
27642
27892
  (toolOptions, callbacks) => {
27893
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27643
27894
  if (!getToken && !memoryStore) {
27644
27895
  throw new Error("getToken is required for recall tool");
27645
27896
  }
@@ -27685,7 +27936,7 @@ function useChatStorage(options) {
27685
27936
  );
27686
27937
  const recallFn = (0, import_react5.useCallback)(
27687
27938
  async (query, options2) => {
27688
- if (!getToken && !memoryStore) {
27939
+ if (memoryStore === null || !getToken && !memoryStore) {
27689
27940
  return {
27690
27941
  memories: [],
27691
27942
  usedBudget: options2?.budget ?? "low",
@@ -27734,6 +27985,7 @@ function useChatStorage(options) {
27734
27985
  );
27735
27986
  const getVaultMemories = (0, import_react5.useCallback)(
27736
27987
  (options2) => {
27988
+ if (memoryStore === null) return Promise.resolve([]);
27737
27989
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
27738
27990
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
27739
27991
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -27744,7 +27996,8 @@ function useChatStorage(options) {
27744
27996
  [vaultCtx, memoryStore]
27745
27997
  );
27746
27998
  const deleteVaultMemory = (0, import_react5.useCallback)(
27747
- (id) => {
27999
+ async (id) => {
28000
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
27748
28001
  return memoryStore ? memoryStore.delete(id) : deleteVaultMemoryOp(vaultCtx, id);
27749
28002
  },
27750
28003
  [vaultCtx, memoryStore]