@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.
@@ -12292,6 +12292,11 @@ function parseTopics(value) {
12292
12292
  }
12293
12293
 
12294
12294
  // src/lib/db/entities/operations.ts
12295
+ async function assertCanWrite(ctx) {
12296
+ if (ctx.canWrite && !await ctx.canWrite()) {
12297
+ throw new Error("Memory source is no longer eligible");
12298
+ }
12299
+ }
12295
12300
  function entityToStored(e) {
12296
12301
  return {
12297
12302
  uniqueId: e.id,
@@ -12432,6 +12437,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
12432
12437
  let skipped = false;
12433
12438
  let entities = [];
12434
12439
  await ctx.database.write(async () => {
12440
+ await assertCanWrite(ctx);
12435
12441
  const {
12436
12442
  entities: byName,
12437
12443
  operations: entityOps,
@@ -12507,6 +12513,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
12507
12513
  let skipped = false;
12508
12514
  let entities = [];
12509
12515
  await ctx.database.write(async () => {
12516
+ await assertCanWrite(ctx);
12510
12517
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
12511
12518
  if (options.guarded && autoLinkBlocked(vaultRow)) {
12512
12519
  skipped = true;
@@ -12567,6 +12574,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
12567
12574
  const links = await ctx.memoryEntityCollection.query(import_watermelondb11.Q.where("memory_id", import_watermelondb11.Q.oneOf(Array.from(memoryIds)))).fetch();
12568
12575
  if (links.length === 0) return;
12569
12576
  await ctx.database.write(async () => {
12577
+ await assertCanWrite(ctx);
12570
12578
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
12571
12579
  });
12572
12580
  }
@@ -12575,6 +12583,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
12575
12583
  const links = await ctx.memoryEntityCollection.query(import_watermelondb11.Q.where("user_id", userId)).fetch();
12576
12584
  if (links.length === 0) return;
12577
12585
  await ctx.database.write(async () => {
12586
+ await assertCanWrite(ctx);
12578
12587
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
12579
12588
  });
12580
12589
  }
@@ -12594,6 +12603,7 @@ async function backfillMemoryEntityUserIdsOp(ctx, vaultMemoryCollection) {
12594
12603
  }
12595
12604
  if (toUpdate.length === 0) return 0;
12596
12605
  await ctx.database.write(async () => {
12606
+ await assertCanWrite(ctx);
12597
12607
  await ctx.database.batch(
12598
12608
  ...toUpdate.map(({ link, userId }) => link.prepareUpdate((r) => r._setRaw("user_id", userId)))
12599
12609
  );
@@ -14915,6 +14925,17 @@ async function decryptVaultMemoryFields(memory, address, signMessage, embeddedWa
14915
14925
  };
14916
14926
  }
14917
14927
 
14928
+ // src/lib/db/memoryVault/sourceChunkIds.ts
14929
+ var MAX_SOURCE_CHUNK_IDS_BYTES = 16 << 10;
14930
+ var utf8 = new TextEncoder();
14931
+ function serializeSourceChunkIds(ids) {
14932
+ const sizes = ids.map((id) => utf8.encode(JSON.stringify(id)).length + 1);
14933
+ let total = sizes.reduce((sum, size) => sum + size, 1);
14934
+ let start = 0;
14935
+ while (total > MAX_SOURCE_CHUNK_IDS_BYTES && start < ids.length) total -= sizes[start++];
14936
+ return JSON.stringify(ids.slice(start));
14937
+ }
14938
+
14918
14939
  // src/lib/db/memoryVault/types.ts
14919
14940
  var MEMORY_KINDS = [
14920
14941
  "display_name",
@@ -14987,6 +15008,11 @@ function visibilityConditions(requested) {
14987
15008
  function isKinded(record2) {
14988
15009
  return record2.kind !== null && record2.kind !== void 0;
14989
15010
  }
15011
+ async function assertCanWrite2(ctx) {
15012
+ if (ctx.canWrite && !await ctx.canWrite()) {
15013
+ throw new Error("Memory source is no longer eligible");
15014
+ }
15015
+ }
14990
15016
  function assertValidKindLevel(kind, level) {
14991
15017
  const hasKind = kind !== null && kind !== void 0;
14992
15018
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -15062,15 +15088,15 @@ async function mapInBatches(items, fn) {
15062
15088
  return results;
15063
15089
  }
15064
15090
  function vaultMemoryToStoredRaw(memory) {
15065
- let sourceChunkIds = null;
15091
+ let sourceChunkIds2 = null;
15066
15092
  if (memory.sourceChunkIds) {
15067
15093
  try {
15068
15094
  const parsed = JSON.parse(memory.sourceChunkIds);
15069
15095
  if (Array.isArray(parsed)) {
15070
- sourceChunkIds = parsed.filter((s) => typeof s === "string");
15096
+ sourceChunkIds2 = parsed.filter((s) => typeof s === "string");
15071
15097
  }
15072
15098
  } catch {
15073
- sourceChunkIds = null;
15099
+ sourceChunkIds2 = null;
15074
15100
  }
15075
15101
  }
15076
15102
  return {
@@ -15084,7 +15110,7 @@ function vaultMemoryToStoredRaw(memory) {
15084
15110
  userId: memory.userId ?? null,
15085
15111
  embedding: memory.embedding ?? null,
15086
15112
  embeddingModel: memory.embeddingModel ?? null,
15087
- sourceChunkIds,
15113
+ sourceChunkIds: sourceChunkIds2,
15088
15114
  proofCount: memory.proofCount ?? null,
15089
15115
  source: memory.source ?? null,
15090
15116
  media: parseMedia(memory.media),
@@ -15133,7 +15159,7 @@ function populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedK
15133
15159
  record2._setRaw("embedding_model", opts.embeddingModel ?? null);
15134
15160
  }
15135
15161
  if (opts.sourceChunkIds !== void 0) {
15136
- record2._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
15162
+ record2._setRaw("source_chunk_ids", serializeSourceChunkIds(opts.sourceChunkIds));
15137
15163
  }
15138
15164
  record2._setRaw("proof_count", opts.proofCount ?? 1);
15139
15165
  record2._setRaw("source", opts.source ?? "manual");
@@ -15167,8 +15193,7 @@ async function createVaultMemoryOp(ctx, opts) {
15167
15193
  ctx.embeddedWalletSigner
15168
15194
  ) : opts.content;
15169
15195
  const created = await ctx.database.write(async () => {
15170
- if (ctx.canWrite && !await ctx.canWrite())
15171
- throw new Error("Memory source is no longer eligible");
15196
+ await assertCanWrite2(ctx);
15172
15197
  return ctx.vaultMemoryCollection.create(
15173
15198
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
15174
15199
  );
@@ -15187,8 +15212,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
15187
15212
  ) : opts.content;
15188
15213
  let createdRecord = null;
15189
15214
  await ctx.database.write(async () => {
15190
- if (ctx.canWrite && !await ctx.canWrite())
15191
- throw new Error("Memory source is no longer eligible");
15215
+ await assertCanWrite2(ctx);
15192
15216
  let target;
15193
15217
  try {
15194
15218
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -15280,8 +15304,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
15280
15304
  })
15281
15305
  );
15282
15306
  const created = await ctx.database.write(async () => {
15283
- if (ctx.canWrite && !await ctx.canWrite())
15284
- throw new Error("Memory source is no longer eligible");
15307
+ await assertCanWrite2(ctx);
15285
15308
  const prepared = optionsArray.map(
15286
15309
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
15287
15310
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -15311,16 +15334,16 @@ async function getVaultMemoryOp(ctx, id) {
15311
15334
  }
15312
15335
  }
15313
15336
  function vaultMemoryRawToStoredRaw(raw) {
15314
- let sourceChunkIds = null;
15337
+ let sourceChunkIds2 = null;
15315
15338
  const rawChunks = raw.source_chunk_ids;
15316
15339
  if (typeof rawChunks === "string" && rawChunks) {
15317
15340
  try {
15318
15341
  const parsed = JSON.parse(rawChunks);
15319
15342
  if (Array.isArray(parsed)) {
15320
- sourceChunkIds = parsed.filter((s) => typeof s === "string");
15343
+ sourceChunkIds2 = parsed.filter((s) => typeof s === "string");
15321
15344
  }
15322
15345
  } catch {
15323
- sourceChunkIds = null;
15346
+ sourceChunkIds2 = null;
15324
15347
  }
15325
15348
  }
15326
15349
  return {
@@ -15334,7 +15357,7 @@ function vaultMemoryRawToStoredRaw(raw) {
15334
15357
  userId: raw.user_id ?? null,
15335
15358
  embedding: raw.embedding ?? null,
15336
15359
  embeddingModel: raw.embedding_model ?? null,
15337
- sourceChunkIds,
15360
+ sourceChunkIds: sourceChunkIds2,
15338
15361
  proofCount: raw.proof_count ?? null,
15339
15362
  source: raw.source ?? null,
15340
15363
  eventTimeStart: raw.event_time_start ?? null,
@@ -15586,8 +15609,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
15586
15609
  const record2 = probe;
15587
15610
  const originalUpdatedAt = record2.updatedAt.getTime();
15588
15611
  await ctx.database.write(async () => {
15589
- if (ctx.canWrite && !await ctx.canWrite())
15590
- throw new Error("Memory source is no longer eligible");
15612
+ await assertCanWrite2(ctx);
15591
15613
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
15592
15614
  stale = true;
15593
15615
  return;
@@ -15631,7 +15653,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
15631
15653
  if (opts.sourceChunkIds !== void 0) {
15632
15654
  r._setRaw(
15633
15655
  "source_chunk_ids",
15634
- JSON.stringify(
15656
+ serializeSourceChunkIds(
15635
15657
  opts.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observedSources, ...opts.sourceChunkIds])] : opts.sourceChunkIds
15636
15658
  )
15637
15659
  );
@@ -15698,6 +15720,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
15698
15720
  let stale = false;
15699
15721
  const originalUpdatedAt = record2.updatedAt.getTime();
15700
15722
  await ctx.database.write(async (writer) => {
15723
+ await assertCanWrite2(ctx);
15701
15724
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15702
15725
  stale = true;
15703
15726
  return;
@@ -15739,6 +15762,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
15739
15762
  const originalUpdatedAt = record2.updatedAt.getTime();
15740
15763
  let cleared = false;
15741
15764
  await ctx.database.write(async () => {
15765
+ await assertCanWrite2(ctx);
15742
15766
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
15743
15767
  cleared = true;
15744
15768
  await record2.update((r) => {
@@ -15762,6 +15786,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
15762
15786
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
15763
15787
  let stale = false;
15764
15788
  await ctx.database.write(async () => {
15789
+ await assertCanWrite2(ctx);
15765
15790
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15766
15791
  stale = true;
15767
15792
  return;
@@ -15790,6 +15815,7 @@ async function deleteVaultMemoryOp(ctx, id) {
15790
15815
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
15791
15816
  let stale = false;
15792
15817
  await ctx.database.write(async () => {
15818
+ await assertCanWrite2(ctx);
15793
15819
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
15794
15820
  stale = true;
15795
15821
  return;
@@ -15827,8 +15853,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
15827
15853
  }
15828
15854
  let stale = false;
15829
15855
  await ctx.database.write(async () => {
15830
- if (ctx.canWrite && !await ctx.canWrite())
15831
- throw new Error("Memory source is no longer eligible");
15856
+ await assertCanWrite2(ctx);
15832
15857
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
15833
15858
  stale = true;
15834
15859
  return;
@@ -15990,6 +16015,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
15990
16015
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
15991
16016
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
15992
16017
  await ctx.database.write(async () => {
16018
+ await assertCanWrite2(ctx);
15993
16019
  const records = [];
15994
16020
  for (const id of chunkIds) {
15995
16021
  try {
@@ -16023,6 +16049,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
16023
16049
  for (const id of Array.from(new Set(memoryIds))) {
16024
16050
  try {
16025
16051
  await ctx.database.write(async (writer) => {
16052
+ await assertCanWrite2(ctx);
16026
16053
  let record2;
16027
16054
  try {
16028
16055
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -16053,6 +16080,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
16053
16080
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
16054
16081
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
16055
16082
  await ctx.database.write(async () => {
16083
+ await assertCanWrite2(ctx);
16056
16084
  const writes = [];
16057
16085
  for (const id of chunkIds) {
16058
16086
  let record2;
@@ -16085,6 +16113,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
16085
16113
  const records = await ctx.vaultMemoryCollection.query(import_watermelondb23.Q.where("user_id", userId), import_watermelondb23.Q.where("is_deleted", false)).fetch();
16086
16114
  if (records.length === 0) return 0;
16087
16115
  await ctx.database.write(async () => {
16116
+ await assertCanWrite2(ctx);
16088
16117
  const prepared = records.map(
16089
16118
  (record2) => record2.prepareUpdate((r) => {
16090
16119
  r._setRaw("is_deleted", true);
@@ -16139,6 +16168,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
16139
16168
  let stale = false;
16140
16169
  const archivedAtValue = opts?.now ?? Date.now();
16141
16170
  await ctx.database.write(async () => {
16171
+ await assertCanWrite2(ctx);
16142
16172
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
16143
16173
  stale = true;
16144
16174
  return;
@@ -16162,6 +16192,7 @@ async function restoreVaultMemoryOp(ctx, id) {
16162
16192
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
16163
16193
  let stale = false;
16164
16194
  await ctx.database.write(async () => {
16195
+ await assertCanWrite2(ctx);
16165
16196
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
16166
16197
  stale = true;
16167
16198
  return;
@@ -16182,6 +16213,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
16182
16213
  const now = opts.now ?? Date.now();
16183
16214
  let stale = false;
16184
16215
  await ctx.database.write(async () => {
16216
+ await assertCanWrite2(ctx);
16185
16217
  const archivedAt = record2.archivedAt;
16186
16218
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
16187
16219
  stale = true;
@@ -16221,6 +16253,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
16221
16253
  }
16222
16254
  let written = false;
16223
16255
  await ctx.database.write(async () => {
16256
+ await assertCanWrite2(ctx);
16224
16257
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
16225
16258
  const originalUpdatedAt = record2.updatedAt.getTime();
16226
16259
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -20217,6 +20250,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
20217
20250
  ).map((c) => c.candidate);
20218
20251
  }
20219
20252
  async function extractAndRetain(messages, retainCtx, options) {
20253
+ const { memoryStore, signal } = options;
20254
+ if (memoryStore === null) throw new Error("Memory store is not ready");
20255
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
20256
+ throw new Error("Remote memory extraction does not support folders");
20257
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
20258
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
20259
+ signal?.throwIfAborted();
20220
20260
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
20221
20261
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
20222
20262
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -20250,6 +20290,7 @@ async function extractAndRetain(messages, retainCtx, options) {
20250
20290
  }
20251
20291
  });
20252
20292
  const extractMs = Date.now() - tExtract;
20293
+ signal?.throwIfAborted();
20253
20294
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
20254
20295
  const log = getLogger();
20255
20296
  const screened = screenCandidatesForInjection(filtered);
@@ -20297,16 +20338,33 @@ async function extractAndRetain(messages, retainCtx, options) {
20297
20338
  const results = [];
20298
20339
  const quarantinedInfo = [];
20299
20340
  let failedWrites = 0;
20341
+ let auditRows;
20300
20342
  const tRetain = Date.now();
20301
20343
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
20302
20344
  try {
20345
+ signal?.throwIfAborted();
20303
20346
  if (isQuarantined) {
20304
- const existingId = await findQuarantinedDuplicateOp(
20305
- retainCtx.vaultCtx,
20306
- candidate.content,
20307
- candidate.sourceMessageIds,
20308
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
20309
- ).catch(() => null);
20347
+ let existingId;
20348
+ if (memoryStore) {
20349
+ auditRows ?? (auditRows = memoryStore.list({
20350
+ scopes: [options.scope ?? "private"],
20351
+ includeArchived: true,
20352
+ includeQuarantined: true,
20353
+ includeSuperseded: true
20354
+ }));
20355
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
20356
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
20357
+ existingId = (await auditRows).find(
20358
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
20359
+ )?.uniqueId ?? null;
20360
+ } else {
20361
+ existingId = await findQuarantinedDuplicateOp(
20362
+ retainCtx.vaultCtx,
20363
+ candidate.content,
20364
+ candidate.sourceMessageIds,
20365
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
20366
+ ).catch(() => null);
20367
+ }
20310
20368
  if (existingId) {
20311
20369
  quarantinedInfo.push({
20312
20370
  candidate,
@@ -20317,18 +20375,26 @@ async function extractAndRetain(messages, retainCtx, options) {
20317
20375
  continue;
20318
20376
  }
20319
20377
  }
20320
- const result = await retain(candidate.content, retainCtx, {
20378
+ const retainOptions = {
20321
20379
  source: "auto-extracted",
20322
20380
  sourceChunkIds: candidate.sourceMessageIds,
20323
20381
  respectTombstones: true,
20324
20382
  ...options.scope !== void 0 && { scope: options.scope },
20325
- ...options.folderId !== void 0 && { folderId: options.folderId },
20326
20383
  ...consolidateOptions !== void 0 && { consolidateOptions },
20327
20384
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
20328
20385
  factType: candidate.type,
20329
20386
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
20387
+ };
20388
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
20389
+ ...retainOptions,
20390
+ canWrite: retainCtx.vaultCtx.canWrite,
20391
+ signal
20392
+ }) : await retain(candidate.content, retainCtx, {
20393
+ ...retainOptions,
20394
+ ...options.folderId !== void 0 && { folderId: options.folderId }
20330
20395
  });
20331
20396
  if (isQuarantined) {
20397
+ auditRows = void 0;
20332
20398
  const info = {
20333
20399
  candidate,
20334
20400
  memoryId: result.memoryId,
@@ -20347,9 +20413,16 @@ async function extractAndRetain(messages, retainCtx, options) {
20347
20413
  }
20348
20414
  succeededCandidates.push(candidate);
20349
20415
  results.push(result);
20350
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
20416
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
20351
20417
  try {
20352
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
20418
+ signal?.throwIfAborted();
20419
+ if (memoryStore) {
20420
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
20421
+ unlessUserManaged: true,
20422
+ canWrite: retainCtx.vaultCtx.canWrite,
20423
+ signal
20424
+ });
20425
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
20353
20426
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
20354
20427
  unlessTopicsUserManaged: true
20355
20428
  });
@@ -20599,7 +20672,7 @@ function createAutoExtractor(options) {
20599
20672
  };
20600
20673
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
20601
20674
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
20602
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
20675
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
20603
20676
  getLogger().warn(
20604
20677
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
20605
20678
  );
@@ -20644,6 +20717,8 @@ function createAutoExtractor(options) {
20644
20717
  try {
20645
20718
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
20646
20719
  extract,
20720
+ memoryStore: options.memoryStore,
20721
+ signal: options.signal,
20647
20722
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
20648
20723
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
20649
20724
  ...options.scope !== void 0 && { scope: options.scope },
@@ -22775,6 +22850,7 @@ function createLocalMemoryStore(options) {
22775
22850
  database,
22776
22851
  entityCollection: database.get("entity"),
22777
22852
  memoryEntityCollection: database.get("memory_entity"),
22853
+ canWrite: options.canWrite,
22778
22854
  ...userId !== void 0 && { userId },
22779
22855
  ...options.allowUnscopedRows !== void 0 && {
22780
22856
  allowUnscopedRows: options.allowUnscopedRows
@@ -22786,10 +22862,21 @@ function createLocalMemoryStore(options) {
22786
22862
  walletAddress: options.walletAddress,
22787
22863
  signMessage: options.signMessage,
22788
22864
  embeddedWalletSigner: options.embeddedWalletSigner,
22865
+ canWrite: options.canWrite,
22789
22866
  ...userId !== void 0 && { userId },
22790
22867
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
22791
22868
  entityCtx
22792
22869
  };
22870
+ const assertWrite = async (writeOptions) => {
22871
+ writeOptions?.signal?.throwIfAborted();
22872
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
22873
+ throw new Error("Memory source is no longer eligible");
22874
+ }
22875
+ if (options.canWrite && !await options.canWrite()) {
22876
+ throw new Error("Memory source is no longer eligible");
22877
+ }
22878
+ writeOptions?.signal?.throwIfAborted();
22879
+ };
22793
22880
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
22794
22881
  const ownedBy = userId !== void 0 ? [import_watermelondb32.Q.where("user_id", userId)] : [];
22795
22882
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [import_watermelondb32.Q.or(import_watermelondb32.Q.where("user_id", userId), import_watermelondb32.Q.where("user_id", null))] : [import_watermelondb32.Q.where("user_id", userId)];
@@ -22901,9 +22988,25 @@ function createLocalMemoryStore(options) {
22901
22988
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
22902
22989
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
22903
22990
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
22904
- addTopics: (memoryId, topics) => database.write(async (writer) => {
22991
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
22992
+ await assertWrite(writeOptions);
22905
22993
  const owned = await vaultCtx.vaultMemoryCollection.query(import_watermelondb32.Q.where("id", memoryId), import_watermelondb32.Q.where("is_deleted", false), ...ownedBy).fetchCount();
22906
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
22994
+ return owned > 0 ? writer.callWriter(
22995
+ () => linkMemoryEntitiesOp(
22996
+ {
22997
+ ...entityCtx,
22998
+ canWrite: async () => {
22999
+ await assertWrite(writeOptions);
23000
+ return true;
23001
+ }
23002
+ },
23003
+ memoryId,
23004
+ topics,
23005
+ {
23006
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
23007
+ }
23008
+ )
23009
+ ) : [];
22907
23010
  }),
22908
23011
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
22909
23012
  recall: (query, recallOptions) => recall(
@@ -22913,7 +23016,17 @@ function createLocalMemoryStore(options) {
22913
23016
  ),
22914
23017
  retain: (content, retainOptions) => retain(
22915
23018
  content,
22916
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
23019
+ {
23020
+ vaultCtx: {
23021
+ ...vaultCtx,
23022
+ canWrite: async () => {
23023
+ await assertWrite(retainOptions);
23024
+ return true;
23025
+ }
23026
+ },
23027
+ embeddingOptions: options.embeddingOptions,
23028
+ vaultCache
23029
+ },
22917
23030
  retainOptions
22918
23031
  ),
22919
23032
  factSource: {
@@ -23017,13 +23130,20 @@ function memoryCipher(canonical) {
23017
23130
  };
23018
23131
  }
23019
23132
  async function reencryptMemoryField(value, canonical, fallbacks = []) {
23020
- const { encrypt } = memoryCipher(canonical);
23021
- if (!value.startsWith("enc:")) return encrypt(value);
23133
+ return memoryCipher(canonical).encrypt(
23134
+ await decryptMemoryField(value, [canonical, ...fallbacks])
23135
+ );
23136
+ }
23137
+ async function decryptMemoryField(value, rings) {
23138
+ if (!value.startsWith("enc:")) return value;
23022
23139
  if (!isEncrypted(value))
23023
23140
  throw new MemoryKeyError("Field has an encryption prefix but an invalid payload");
23024
- return encrypt(await decryptWith(value, [canonical, ...fallbacks]));
23141
+ return decryptWith(value, rings);
23025
23142
  }
23026
23143
 
23144
+ // src/lib/memory/store/remoteMigration.ts
23145
+ init_logger();
23146
+
23027
23147
  // src/lib/memory/store/remotePersistence.ts
23028
23148
  init_portalLlm();
23029
23149
  var RemoteMemoryError = class extends Error {
@@ -23561,7 +23681,14 @@ function stored(row) {
23561
23681
  function createRemoteMemoryPipeline(options) {
23562
23682
  const { persistence, embeddingOptions } = options;
23563
23683
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
23564
- const operation = (mode, queryOptions = embeddingOptions) => {
23684
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
23685
+ const signal = writeOptions?.signal;
23686
+ const assertWrite = async () => {
23687
+ signal?.throwIfAborted();
23688
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
23689
+ throw new Error("Memory source is no longer eligible");
23690
+ signal?.throwIfAborted();
23691
+ };
23565
23692
  const rejectUndecryptable = (failed) => {
23566
23693
  if (!failed.length) return;
23567
23694
  if (mode === "retain")
@@ -23579,7 +23706,7 @@ function createRemoteMemoryPipeline(options) {
23579
23706
  return items.map((item) => stored(item.memory));
23580
23707
  };
23581
23708
  const get = async (id) => {
23582
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
23709
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
23583
23710
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
23584
23711
  snapshots.set(id, snapshot);
23585
23712
  return stored(snapshot.memory);
@@ -23629,7 +23756,7 @@ function createRemoteMemoryPipeline(options) {
23629
23756
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
23630
23757
  }
23631
23758
  const forceIds = [...forced];
23632
- const windowOptions = (i, signal) => ({
23759
+ const windowOptions = (i, signal2) => ({
23633
23760
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
23634
23761
  force_ids: i === 0 ? forceIds : [],
23635
23762
  ...search.scopes && { scopes: search.scopes },
@@ -23637,13 +23764,15 @@ function createRemoteMemoryPipeline(options) {
23637
23764
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
23638
23765
  include_archived: search.includeArchived,
23639
23766
  embedding_model: model,
23640
- ...signal && { signal }
23767
+ ...signal2 && { signal: signal2 }
23641
23768
  });
23642
23769
  const vectors = [queryEmbedding, ...facetEmbeddings];
23643
23770
  let windows;
23644
23771
  if (mode === "retain") {
23645
23772
  windows = await Promise.all(
23646
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
23773
+ vectors.map(
23774
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
23775
+ )
23647
23776
  );
23648
23777
  } else {
23649
23778
  const deadline = new AbortController();
@@ -23716,7 +23845,7 @@ function createRemoteMemoryPipeline(options) {
23716
23845
  embedding: storedVector(JSON.parse(input.embedding)),
23717
23846
  embedding_model: input.embeddingModel
23718
23847
  },
23719
- source_chunk_ids: JSON.stringify(input.sourceChunkIds ?? []),
23848
+ source_chunk_ids: serializeSourceChunkIds(input.sourceChunkIds ?? []),
23720
23849
  proof_count: input.proofCount ?? 1,
23721
23850
  source: input.source ?? "manual",
23722
23851
  ...input.eventTime && {
@@ -23731,12 +23860,15 @@ function createRemoteMemoryPipeline(options) {
23731
23860
  twin_opt_in: false
23732
23861
  };
23733
23862
  };
23734
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
23863
+ const write = async (memory, snapshot) => {
23864
+ await assertWrite();
23865
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
23866
+ };
23735
23867
  const port = {
23736
23868
  prepare,
23737
23869
  get,
23738
23870
  getFresh: async (id) => {
23739
- const current = await persistence.get(id);
23871
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
23740
23872
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
23741
23873
  },
23742
23874
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -23747,7 +23879,8 @@ function createRemoteMemoryPipeline(options) {
23747
23879
  embedding_model: embeddingModel,
23748
23880
  strict_model: true,
23749
23881
  include_deleted: true,
23750
- deleted_only: true
23882
+ deleted_only: true,
23883
+ signal
23751
23884
  });
23752
23885
  rejectUndecryptable(window2.failed);
23753
23886
  return remember(window2.items);
@@ -23775,7 +23908,7 @@ function createRemoteMemoryPipeline(options) {
23775
23908
  const observed = sourceIds(memory.source_chunk_ids);
23776
23909
  const replay = !!patch.observationSourceIds?.length && patch.observationSourceIds.every((id2) => observed.includes(id2));
23777
23910
  if (patch.sourceChunkIds !== void 0)
23778
- memory.source_chunk_ids = JSON.stringify(
23911
+ memory.source_chunk_ids = serializeSourceChunkIds(
23779
23912
  patch.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observed, ...patch.sourceChunkIds])] : patch.sourceChunkIds
23780
23913
  );
23781
23914
  if (!replay) {
@@ -23803,14 +23936,18 @@ function createRemoteMemoryPipeline(options) {
23803
23936
  if (!target || !newer || id === successor) return false;
23804
23937
  const old = snapshots.get(id);
23805
23938
  const next = snapshots.get(successor);
23939
+ await assertWrite();
23806
23940
  remember(
23807
- await persistence.putMany([
23808
- {
23809
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
23810
- expectedVersion: old
23811
- },
23812
- { memory: next.memory, expectedVersion: next }
23813
- ])
23941
+ await persistence.putMany(
23942
+ [
23943
+ {
23944
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
23945
+ expectedVersion: old
23946
+ },
23947
+ { memory: next.memory, expectedVersion: next }
23948
+ ],
23949
+ signal
23950
+ )
23814
23951
  );
23815
23952
  return true;
23816
23953
  },
@@ -23819,18 +23956,22 @@ function createRemoteMemoryPipeline(options) {
23819
23956
  if (!target) return { created: null, retired: false };
23820
23957
  const snapshot = snapshots.get(targetId);
23821
23958
  const successor = createRow(input);
23959
+ await assertWrite();
23822
23960
  const rows = remember(
23823
- await persistence.putMany([
23824
- { memory: successor, expectedVersion: 0 },
23825
- {
23826
- memory: {
23827
- ...snapshot.memory,
23828
- superseded_by: successor.memory_id,
23829
- superseded_at: Date.now()
23830
- },
23831
- expectedVersion: snapshot
23832
- }
23833
- ])
23961
+ await persistence.putMany(
23962
+ [
23963
+ { memory: successor, expectedVersion: 0 },
23964
+ {
23965
+ memory: {
23966
+ ...snapshot.memory,
23967
+ superseded_by: successor.memory_id,
23968
+ superseded_at: Date.now()
23969
+ },
23970
+ expectedVersion: snapshot
23971
+ }
23972
+ ],
23973
+ signal
23974
+ )
23834
23975
  );
23835
23976
  return { created: rows[0], retired: true };
23836
23977
  }
@@ -23865,7 +24006,7 @@ function createRemoteMemoryPipeline(options) {
23865
24006
  factSource,
23866
24007
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
23867
24008
  retain: async (content, retainOptions) => {
23868
- const { port } = operation("retain");
24009
+ const { port } = operation("retain", embeddingOptions, retainOptions);
23869
24010
  return retainWithPersistence(
23870
24011
  content,
23871
24012
  {
@@ -23882,6 +24023,8 @@ function createRemoteMemoryPipeline(options) {
23882
24023
 
23883
24024
  // src/lib/memory/store/remoteMigration.ts
23884
24025
  var IMPORT_BATCH = 50;
24026
+ var EMBED_BATCH = 100;
24027
+ var LOCAL_VECTOR_DIMS = 4096;
23885
24028
  var VERIFY_BATCH = 1e3;
23886
24029
  var QUERY_IDS = 1e3;
23887
24030
  var QUERY_PAGE = 200;
@@ -23907,7 +24050,7 @@ var SHORT_FIELDS = [
23907
24050
  "visibility",
23908
24051
  "geohash"
23909
24052
  ];
23910
- var utf8 = new TextEncoder();
24053
+ var utf82 = new TextEncoder();
23911
24054
  var METADATA_FIELDS = [
23912
24055
  "scope",
23913
24056
  "kind",
@@ -23958,17 +24101,34 @@ function revision(raw) {
23958
24101
  last_observed_at: num(raw.last_observed_at) ?? null
23959
24102
  };
23960
24103
  }
23961
- function embedding(raw) {
24104
+ function embedding(raw, model) {
23962
24105
  const value = text13(raw.embedding);
23963
24106
  if (!value) return void 0;
23964
24107
  try {
23965
- return storedVector(JSON.parse(value));
24108
+ const vector = JSON.parse(value);
24109
+ const stored2 = text13(raw.embedding_model);
24110
+ if (stored2 === void 0 ? vector.length !== LOCAL_VECTOR_DIMS : stored2 !== model)
24111
+ return void 0;
24112
+ return storedVector(vector);
24113
+ } catch {
24114
+ return void 0;
24115
+ }
24116
+ }
24117
+ function stringArray(value) {
24118
+ try {
24119
+ const parsed = JSON.parse(value);
24120
+ return Array.isArray(parsed) && parsed.every((id) => typeof id === "string") ? parsed : void 0;
23966
24121
  } catch {
23967
24122
  return void 0;
23968
24123
  }
23969
24124
  }
24125
+ function sourceChunkIds(raw) {
24126
+ const value = text13(raw.source_chunk_ids);
24127
+ const ids = value && stringArray(value);
24128
+ return ids ? serializeSourceChunkIds(ids) : value;
24129
+ }
23970
24130
  function bytes(value) {
23971
- return value === void 0 ? 0 : utf8.encode(value).length;
24131
+ return value === void 0 ? 0 : utf82.encode(value).length;
23972
24132
  }
23973
24133
  function fitsNearby(row) {
23974
24134
  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);
@@ -23977,16 +24137,16 @@ function invalid(raw) {
23977
24137
  const id = raw.id;
23978
24138
  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;
23979
24139
  }
23980
- function metadata(raw) {
23981
- const vector = embedding(raw);
24140
+ function metadata(raw, model) {
24141
+ const vector = embedding(raw, model);
23982
24142
  return {
23983
24143
  scope: raw.scope,
23984
24144
  kind: text13(raw.kind),
23985
24145
  level: text13(raw.level),
23986
24146
  folder_id: text13(raw.folder_id),
23987
24147
  is_deleted: bool(raw.is_deleted) ?? false,
23988
- ...vector && { embedding: vector, embedding_model: text13(raw.embedding_model) },
23989
- source_chunk_ids: text13(raw.source_chunk_ids),
24148
+ ...vector && { embedding: vector, embedding_model: model },
24149
+ source_chunk_ids: sourceChunkIds(raw),
23990
24150
  proof_count: num(raw.proof_count),
23991
24151
  source: text13(raw.source),
23992
24152
  event_time_start: num(raw.event_time_start),
@@ -24015,30 +24175,63 @@ function defined(value) {
24015
24175
  Object.entries(value).filter(([, field10]) => field10 !== void 0)
24016
24176
  );
24017
24177
  }
24018
- async function remoteRow(raw, canonical, fallbacks, updatedAt) {
24019
- const content = await reencryptMemoryField(
24020
- typeof raw.content === "string" ? raw.content : "",
24021
- canonical,
24022
- fallbacks
24023
- );
24024
- const kindValue = text13(raw.kind_value);
24025
- const encryptedKindValue = kindValue ? await reencryptMemoryField(kindValue, canonical, fallbacks) : void 0;
24178
+ async function prepareRow(raw, canonical, rings, model, updatedAt) {
24179
+ const deleted = bool(raw.is_deleted) ?? false;
24180
+ const { encrypt } = memoryCipher(canonical);
24181
+ const localKindValue = text13(raw.kind_value);
24182
+ let plaintext;
24183
+ let kindValue;
24184
+ try {
24185
+ plaintext = await decryptMemoryField(typeof raw.content === "string" ? raw.content : "", rings);
24186
+ kindValue = localKindValue && await decryptMemoryField(localKindValue, rings);
24187
+ } catch (error) {
24188
+ if (!deleted || !(error instanceof MemoryKeyError)) throw error;
24189
+ plaintext = void 0;
24190
+ kindValue = void 0;
24191
+ }
24026
24192
  const row = {
24027
- ...defined({ ...metadata(raw), kind_value: encryptedKindValue }),
24193
+ ...defined({
24194
+ ...metadata(raw, model),
24195
+ kind_value: kindValue === void 0 ? void 0 : await encrypt(kindValue)
24196
+ }),
24028
24197
  memory_id: raw.id,
24029
- content,
24198
+ content: await encrypt(plaintext ?? ""),
24030
24199
  scope: raw.scope,
24031
24200
  created_at: raw.created_at,
24032
24201
  updated_at: updatedAt,
24033
- is_deleted: bool(raw.is_deleted) ?? false
24202
+ is_deleted: deleted
24034
24203
  };
24035
- return fitsNearby(row) ? row : null;
24204
+ return { row, plaintext };
24205
+ }
24206
+ async function embedMissing(prepared, options, model, signal) {
24207
+ const needed = prepared.filter((p) => !p.row.embedding && p.plaintext);
24208
+ for (const batch of chunks(needed, EMBED_BATCH)) {
24209
+ if (signal?.aborted) throw aborted(signal);
24210
+ try {
24211
+ const vectors = await generateEmbeddings(
24212
+ batch.map((p) => p.plaintext),
24213
+ { ...options, taskType: "memory_retain_embed" }
24214
+ );
24215
+ if (vectors.length !== batch.length) throw new Error("Incomplete migration embeddings");
24216
+ const stored2 = vectors.map(storedVector);
24217
+ batch.forEach((p, i) => {
24218
+ p.row.embedding = stored2[i];
24219
+ p.row.embedding_model = model;
24220
+ });
24221
+ } catch (error) {
24222
+ getLogger().warn(
24223
+ `[memory/migration] Uploading ${batch.length} row(s) without a vector: embedding failed`,
24224
+ error
24225
+ );
24226
+ }
24227
+ }
24036
24228
  }
24037
- function sameMetadata(local, remote) {
24038
- const mine = metadata(local);
24229
+ function sameMetadata(local, remote, model) {
24230
+ const mine = metadata(local, model);
24039
24231
  return METADATA_FIELDS.every((field10) => {
24040
24232
  const a = mine[field10] ?? null;
24041
24233
  const b = remote[field10] ?? null;
24234
+ if (field10 === "embedding_model" && a === null) return true;
24042
24235
  if (field10 === "visibility") return (a ?? "private") === (b ?? "private");
24043
24236
  if (field10 === "is_deleted" || field10 === "topics_user_managed" || field10 === "twin_opt_in")
24044
24237
  return (a ?? false) === (b ?? false);
@@ -24134,7 +24327,8 @@ async function backfillTopics(ctx) {
24134
24327
  }
24135
24328
  async function migrateMemoriesToRemote(options) {
24136
24329
  const { canonical, signal } = options;
24137
- const fallbacks = options.fallbacks ?? [];
24330
+ const rings = [canonical, ...options.fallbacks ?? []];
24331
+ const model = options.embeddingOptions?.model ?? DEFAULT_API_EMBEDDING_MODEL;
24138
24332
  const request = createNearbyRequest(options);
24139
24333
  const account = await request("/account", {}, signal);
24140
24334
  if (typeof account.key_id === "string" && account.key_id !== canonical.keyId)
@@ -24145,8 +24339,13 @@ async function migrateMemoriesToRemote(options) {
24145
24339
  );
24146
24340
  const active = account.state === "active";
24147
24341
  let claimed = account.key_id === canonical.keyId;
24342
+ if (!active) await createDecaySweeper({ vaultCtx: options.vaultCtx }).runSweep();
24148
24343
  await backfillTopics(options.vaultCtx);
24149
24344
  const skipped = /* @__PURE__ */ new Map();
24345
+ const reembedded = /* @__PURE__ */ new Set();
24346
+ const unembedded = /* @__PURE__ */ new Set();
24347
+ const placeholders = /* @__PURE__ */ new Set();
24348
+ const truncated = /* @__PURE__ */ new Set();
24150
24349
  let imported = 0;
24151
24350
  for (let pass = 0; ; pass++) {
24152
24351
  const rows = await getAllVaultMemoryRowsOp(options.vaultCtx);
@@ -24162,9 +24361,10 @@ async function migrateMemoriesToRemote(options) {
24162
24361
  candidates.map((raw) => raw.id),
24163
24362
  signal
24164
24363
  );
24364
+ const needsVector = (raw, server) => !!options.embeddingOptions && !embedding(raw, model) && server.embedding_model !== model;
24165
24365
  for (const raw of candidates) {
24166
24366
  const server = remote.get(raw.id);
24167
- if (server && server.updated_at === raw.updated_at && !sameMetadata(raw, server))
24367
+ if (server && server.updated_at === raw.updated_at && (!sameMetadata(raw, server, model) || needsVector(raw, server)))
24168
24368
  pending.set(raw.id, raw.updated_at + 1);
24169
24369
  }
24170
24370
  }
@@ -24173,7 +24373,11 @@ async function migrateMemoriesToRemote(options) {
24173
24373
  state,
24174
24374
  total: rows.length,
24175
24375
  imported,
24176
- skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason }))
24376
+ skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason })),
24377
+ reembedded: reembedded.size,
24378
+ unembedded: unembedded.size,
24379
+ placeholderTombstones: placeholders.size,
24380
+ truncatedSourceChunkIds: truncated.size
24177
24381
  });
24178
24382
  if (skipped.size > 0 && !options.allowSkipped) return result("migrating");
24179
24383
  if (!claimed) await importBatch(request, canonical.keyId, [], signal);
@@ -24191,19 +24395,35 @@ async function migrateMemoriesToRemote(options) {
24191
24395
  409,
24192
24396
  "migration_incomplete"
24193
24397
  );
24194
- const upload = [];
24398
+ const prepared = [];
24195
24399
  for (const raw of candidates) {
24196
24400
  const updatedAt = pending.get(raw.id);
24197
24401
  if (updatedAt === void 0) continue;
24198
24402
  try {
24199
- const row = await remoteRow(raw, canonical, fallbacks, updatedAt);
24200
- if (row) upload.push(row);
24201
- else skipped.set(raw.id, "invalid");
24403
+ prepared.push(await prepareRow(raw, canonical, rings, model, updatedAt));
24202
24404
  } catch (error) {
24203
24405
  if (!(error instanceof MemoryKeyError)) throw error;
24204
24406
  skipped.set(raw.id, "unreadable");
24205
24407
  }
24206
24408
  }
24409
+ const usable = new Set(prepared.filter((p) => p.row.embedding).map((p) => p.row.memory_id));
24410
+ if (options.embeddingOptions)
24411
+ await embedMissing(prepared, options.embeddingOptions, model, signal);
24412
+ const local = new Map(candidates.map((raw) => [raw.id, raw]));
24413
+ const upload = [];
24414
+ for (const { row, plaintext } of prepared) {
24415
+ const id = row.memory_id;
24416
+ if (!fitsNearby(row)) {
24417
+ skipped.set(id, "invalid");
24418
+ continue;
24419
+ }
24420
+ upload.push(row);
24421
+ const track = (set, on) => on ? set.add(id) : set.delete(id);
24422
+ track(reembedded, !!row.embedding && !usable.has(id));
24423
+ track(unembedded, !row.embedding);
24424
+ track(placeholders, plaintext === void 0);
24425
+ track(truncated, row.source_chunk_ids !== text13(local.get(id).source_chunk_ids));
24426
+ }
24207
24427
  let sent = 0;
24208
24428
  options.onProgress?.({ imported: sent, toImport: upload.length });
24209
24429
  for (const batch of chunks(upload, IMPORT_BATCH)) {
@@ -24448,16 +24668,21 @@ function createRemoteMemoryStore(options) {
24448
24668
  const record2 = await persistence.get(id);
24449
24669
  return record2 && !record2.memory.is_deleted ? record2 : null;
24450
24670
  };
24451
- const mutateLocked = async (id, change, edit) => {
24452
- const record2 = await live(id);
24671
+ const mutateLocked = async (id, change, edit, writeOptions) => {
24672
+ writeOptions?.signal?.throwIfAborted();
24673
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
24453
24674
  if (!record2) return null;
24675
+ if (record2.memory.is_deleted) return null;
24676
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
24677
+ throw new Error("Memory source is no longer eligible");
24678
+ writeOptions?.signal?.throwIfAborted();
24454
24679
  const next = edit(structuredClone(record2.memory));
24455
24680
  if (!next) return null;
24456
- const saved = await persistence.put(next, record2);
24681
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
24457
24682
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
24458
24683
  return saved;
24459
24684
  };
24460
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
24685
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
24461
24686
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
24462
24687
  const mutateMany = async (ids, change, edit) => {
24463
24688
  const saved = /* @__PURE__ */ new Map();
@@ -24970,13 +25195,21 @@ function createRemoteMemoryStore(options) {
24970
25195
  }))
24971
25196
  );
24972
25197
  },
24973
- addTopics: async (memoryId, topics) => {
25198
+ addTopics: async (memoryId, topics, writeOptions) => {
24974
25199
  const added = await withKnownKinds(topicInputs(topics, "auto"));
24975
- const saved = await mutate(memoryId, "edit", (memory) => ({
24976
- ...memory,
24977
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
24978
- topics_updated_at: Date.now()
24979
- }));
25200
+ const saved = await mutate(
25201
+ memoryId,
25202
+ "edit",
25203
+ (memory) => {
25204
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
25205
+ return {
25206
+ ...memory,
25207
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
25208
+ topics_updated_at: Date.now()
25209
+ };
25210
+ },
25211
+ writeOptions
25212
+ );
24980
25213
  if (!saved) return [];
24981
25214
  const now = /* @__PURE__ */ new Date();
24982
25215
  return mergeTopics([], added).map(
@@ -25829,6 +26062,7 @@ function createDurableAutoExtractor(options) {
25829
26062
  };
25830
26063
  const folderId = options.folderId ?? null;
25831
26064
  let disposed = false;
26065
+ let activeBatch = null;
25832
26066
  let running = false;
25833
26067
  let wakeRequested = false;
25834
26068
  let timer;
@@ -25865,14 +26099,18 @@ function createDurableAutoExtractor(options) {
25865
26099
  }, delay);
25866
26100
  }
25867
26101
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
26102
+ if (disposed) throw new Error("Extraction session was disposed");
25868
26103
  let watchdog;
25869
26104
  let cancelled = false;
26105
+ const controller = new AbortController();
26106
+ activeBatch = controller;
25870
26107
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
25871
26108
  try {
25872
26109
  return await Promise.race([
25873
26110
  new Promise((resolve, reject) => {
25874
26111
  const worker = createAutoExtractor({
25875
26112
  ...options,
26113
+ signal: controller.signal,
25876
26114
  retainCtx: {
25877
26115
  ...options.retainCtx,
25878
26116
  vaultCtx: {
@@ -25901,6 +26139,8 @@ function createDurableAutoExtractor(options) {
25901
26139
  ]);
25902
26140
  } finally {
25903
26141
  cancelled = true;
26142
+ controller.abort();
26143
+ if (activeBatch === controller) activeBatch = null;
25904
26144
  if (watchdog !== void 0) clearTimeout(watchdog);
25905
26145
  }
25906
26146
  }
@@ -25912,6 +26152,7 @@ function createDurableAutoExtractor(options) {
25912
26152
  for (const message of loaded)
25913
26153
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
25914
26154
  await database.write(async () => {
26155
+ if (disposed) return;
25915
26156
  const current = idsOf(job);
25916
26157
  const acknowledged = ids.filter((id) => current.includes(id));
25917
26158
  if (!acknowledged.length) return;
@@ -26084,6 +26325,7 @@ function createDurableAutoExtractor(options) {
26084
26325
  jobScope,
26085
26326
  jobFolderId
26086
26327
  );
26328
+ if (disposed) throw new Error("Extraction session was disposed");
26087
26329
  options.onTurnComplete?.(result);
26088
26330
  return result;
26089
26331
  };
@@ -26120,6 +26362,7 @@ function createDurableAutoExtractor(options) {
26120
26362
  );
26121
26363
  scheduleAfterSuccess = true;
26122
26364
  } catch (error) {
26365
+ if (disposed) break;
26123
26366
  attempts.set(job.id, attempted + 1);
26124
26367
  retry = true;
26125
26368
  reportError(error, conversationId);
@@ -26142,6 +26385,14 @@ function createDurableAutoExtractor(options) {
26142
26385
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
26143
26386
  }
26144
26387
  }
26388
+ function dispose() {
26389
+ disposed = true;
26390
+ activeBatch?.abort(options.signal?.reason);
26391
+ if (timer !== void 0) clearTimeout(timer);
26392
+ options.signal?.removeEventListener("abort", dispose);
26393
+ }
26394
+ if (options.signal?.aborted) dispose();
26395
+ else options.signal?.addEventListener("abort", dispose, { once: true });
26145
26396
  schedule(0);
26146
26397
  return {
26147
26398
  processTurn(messages, conversationId) {
@@ -26221,10 +26472,7 @@ function createDurableAutoExtractor(options) {
26221
26472
  return true;
26222
26473
  },
26223
26474
  isProcessing: () => running,
26224
- dispose() {
26225
- disposed = true;
26226
- if (timer !== void 0) clearTimeout(timer);
26227
- }
26475
+ dispose
26228
26476
  };
26229
26477
  }
26230
26478
 
@@ -32495,6 +32743,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
32495
32743
  var NO_CONVERSATION_KEY = "__no_conversation__";
32496
32744
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
32497
32745
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
32746
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
32498
32747
  var conversationRedactors = /* @__PURE__ */ new Map();
32499
32748
  function getConversationRedactor(conversationId, nerDetector) {
32500
32749
  const key = conversationId ?? NO_CONVERSATION_KEY;
@@ -32585,7 +32834,7 @@ function useChatStorage(options) {
32585
32834
  () => ({ getToken, baseUrl, model: embeddingModel, maskInput: maskEmbeddingInput }),
32586
32835
  [getToken, baseUrl, embeddingModel, maskEmbeddingInput]
32587
32836
  );
32588
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
32837
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
32589
32838
  const blobManagerRef = (0, import_react6.useRef)(new BlobUrlManager());
32590
32839
  (0, import_react6.useEffect)(() => {
32591
32840
  const manager = blobManagerRef.current;
@@ -32862,6 +33111,7 @@ function useChatStorage(options) {
32862
33111
  );
32863
33112
  const retainVaultMemory = (0, import_react6.useCallback)(
32864
33113
  async (input) => {
33114
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32865
33115
  if (memoryStore) {
32866
33116
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32867
33117
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -32893,6 +33143,7 @@ function useChatStorage(options) {
32893
33143
  );
32894
33144
  const createMemoryVaultTool2 = (0, import_react6.useCallback)(
32895
33145
  (options2) => {
33146
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32896
33147
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32897
33148
  const embOpts = getToken ? vaultEmbeddingOptions : void 0;
32898
33149
  return createMemoryVaultTool(
@@ -32914,6 +33165,7 @@ function useChatStorage(options) {
32914
33165
  );
32915
33166
  const getVaultMemories = (0, import_react6.useCallback)(
32916
33167
  (options2) => {
33168
+ if (memoryStore === null) return Promise.resolve([]);
32917
33169
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
32918
33170
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
32919
33171
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -32925,6 +33177,7 @@ function useChatStorage(options) {
32925
33177
  );
32926
33178
  const createVaultMemory = (0, import_react6.useCallback)(
32927
33179
  async (content, scope) => {
33180
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32928
33181
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32929
33182
  if (memoryStore) return memoryStore.create({ content, scope });
32930
33183
  const result = await createVaultMemoryOp(vaultCtx, { content, scope });
@@ -32946,6 +33199,7 @@ function useChatStorage(options) {
32946
33199
  );
32947
33200
  const updateVaultMemory = (0, import_react6.useCallback)(
32948
33201
  async (id, content, scope) => {
33202
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32949
33203
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
32950
33204
  if (memoryStore) return memoryStore.update(id, { content, scope });
32951
33205
  const existing = await getVaultMemoryOp(vaultCtx, id);
@@ -32971,6 +33225,7 @@ function useChatStorage(options) {
32971
33225
  );
32972
33226
  const deleteVaultMemory = (0, import_react6.useCallback)(
32973
33227
  async (id) => {
33228
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
32974
33229
  if (memoryStore) return memoryStore.delete(id);
32975
33230
  const existing = await getVaultMemoryOp(vaultCtx, id);
32976
33231
  const result = await deleteVaultMemoryOp(vaultCtx, id);
@@ -32984,7 +33239,7 @@ function useChatStorage(options) {
32984
33239
  const chunkVectorCacheRef = (0, import_react6.useRef)(createChunkVectorCache());
32985
33240
  const clientToolEmbeddingsCacheRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
32986
33241
  (0, import_react6.useEffect)(() => {
32987
- if (!getToken || memoryStore) return;
33242
+ if (!getToken || memoryStore !== void 0) return;
32988
33243
  void (async () => {
32989
33244
  try {
32990
33245
  await preEmbedVaultMemories(vaultCtx, vaultEmbeddingOptions, vaultEmbeddingCache);
@@ -33000,6 +33255,7 @@ function useChatStorage(options) {
33000
33255
  }, [vaultEmbeddingCache]);
33001
33256
  const createMemoryVaultSearchTool2 = (0, import_react6.useCallback)(
33002
33257
  (searchOptions) => {
33258
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
33003
33259
  if (!getToken && !memoryStore) {
33004
33260
  throw new Error("getToken is required for memory vault search tool");
33005
33261
  }
@@ -33015,6 +33271,7 @@ function useChatStorage(options) {
33015
33271
  );
33016
33272
  const createRecallTool2 = (0, import_react6.useCallback)(
33017
33273
  (toolOptions, callbacks) => {
33274
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
33018
33275
  if (!getToken && !memoryStore) {
33019
33276
  throw new Error("getToken is required for recall tool");
33020
33277
  }
@@ -33054,6 +33311,7 @@ function useChatStorage(options) {
33054
33311
  );
33055
33312
  const searchVaultMemoriesFn = (0, import_react6.useCallback)(
33056
33313
  async (query, searchOptions) => {
33314
+ if (memoryStore === null) return [];
33057
33315
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
33058
33316
  if (memoryStore) return (await memoryStore.factSource.search(query, searchOptions)).results;
33059
33317
  if (!getToken) return [];
@@ -33069,7 +33327,7 @@ function useChatStorage(options) {
33069
33327
  );
33070
33328
  const recallFn = (0, import_react6.useCallback)(
33071
33329
  async (query, options2) => {
33072
- if (!getToken && !memoryStore) {
33330
+ if (memoryStore === null || !getToken && !memoryStore) {
33073
33331
  return {
33074
33332
  memories: [],
33075
33333
  usedBudget: options2?.budget ?? "low",