@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.
@@ -10495,6 +10495,11 @@ function parseTopics(value) {
10495
10495
  }
10496
10496
 
10497
10497
  // src/lib/db/entities/operations.ts
10498
+ async function assertCanWrite(ctx) {
10499
+ if (ctx.canWrite && !await ctx.canWrite()) {
10500
+ throw new Error("Memory source is no longer eligible");
10501
+ }
10502
+ }
10498
10503
  function entityToStored(e) {
10499
10504
  return {
10500
10505
  uniqueId: e.id,
@@ -10635,6 +10640,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
10635
10640
  let skipped = false;
10636
10641
  let entities = [];
10637
10642
  await ctx.database.write(async () => {
10643
+ await assertCanWrite(ctx);
10638
10644
  const {
10639
10645
  entities: byName,
10640
10646
  operations: entityOps,
@@ -10710,6 +10716,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
10710
10716
  let skipped = false;
10711
10717
  let entities = [];
10712
10718
  await ctx.database.write(async () => {
10719
+ await assertCanWrite(ctx);
10713
10720
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
10714
10721
  if (options.guarded && autoLinkBlocked(vaultRow)) {
10715
10722
  skipped = true;
@@ -10770,6 +10777,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
10770
10777
  const links = await ctx.memoryEntityCollection.query(Q5.where("memory_id", Q5.oneOf(Array.from(memoryIds)))).fetch();
10771
10778
  if (links.length === 0) return;
10772
10779
  await ctx.database.write(async () => {
10780
+ await assertCanWrite(ctx);
10773
10781
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
10774
10782
  });
10775
10783
  }
@@ -10778,6 +10786,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
10778
10786
  const links = await ctx.memoryEntityCollection.query(Q5.where("user_id", userId)).fetch();
10779
10787
  if (links.length === 0) return;
10780
10788
  await ctx.database.write(async () => {
10789
+ await assertCanWrite(ctx);
10781
10790
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
10782
10791
  });
10783
10792
  }
@@ -10797,6 +10806,7 @@ async function backfillMemoryEntityUserIdsOp(ctx, vaultMemoryCollection) {
10797
10806
  }
10798
10807
  if (toUpdate.length === 0) return 0;
10799
10808
  await ctx.database.write(async () => {
10809
+ await assertCanWrite(ctx);
10800
10810
  await ctx.database.batch(
10801
10811
  ...toUpdate.map(({ link, userId }) => link.prepareUpdate((r) => r._setRaw("user_id", userId)))
10802
10812
  );
@@ -13118,6 +13128,17 @@ async function decryptVaultMemoryFields(memory, address, signMessage, embeddedWa
13118
13128
  };
13119
13129
  }
13120
13130
 
13131
+ // src/lib/db/memoryVault/sourceChunkIds.ts
13132
+ var MAX_SOURCE_CHUNK_IDS_BYTES = 16 << 10;
13133
+ var utf8 = new TextEncoder();
13134
+ function serializeSourceChunkIds(ids) {
13135
+ const sizes = ids.map((id) => utf8.encode(JSON.stringify(id)).length + 1);
13136
+ let total = sizes.reduce((sum, size) => sum + size, 1);
13137
+ let start = 0;
13138
+ while (total > MAX_SOURCE_CHUNK_IDS_BYTES && start < ids.length) total -= sizes[start++];
13139
+ return JSON.stringify(ids.slice(start));
13140
+ }
13141
+
13121
13142
  // src/lib/db/memoryVault/types.ts
13122
13143
  var MEMORY_KINDS = [
13123
13144
  "display_name",
@@ -13190,6 +13211,11 @@ function visibilityConditions(requested) {
13190
13211
  function isKinded(record2) {
13191
13212
  return record2.kind !== null && record2.kind !== void 0;
13192
13213
  }
13214
+ async function assertCanWrite2(ctx) {
13215
+ if (ctx.canWrite && !await ctx.canWrite()) {
13216
+ throw new Error("Memory source is no longer eligible");
13217
+ }
13218
+ }
13193
13219
  function assertValidKindLevel(kind, level) {
13194
13220
  const hasKind = kind !== null && kind !== void 0;
13195
13221
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -13265,15 +13291,15 @@ async function mapInBatches(items, fn) {
13265
13291
  return results;
13266
13292
  }
13267
13293
  function vaultMemoryToStoredRaw(memory) {
13268
- let sourceChunkIds = null;
13294
+ let sourceChunkIds2 = null;
13269
13295
  if (memory.sourceChunkIds) {
13270
13296
  try {
13271
13297
  const parsed = JSON.parse(memory.sourceChunkIds);
13272
13298
  if (Array.isArray(parsed)) {
13273
- sourceChunkIds = parsed.filter((s) => typeof s === "string");
13299
+ sourceChunkIds2 = parsed.filter((s) => typeof s === "string");
13274
13300
  }
13275
13301
  } catch {
13276
- sourceChunkIds = null;
13302
+ sourceChunkIds2 = null;
13277
13303
  }
13278
13304
  }
13279
13305
  return {
@@ -13287,7 +13313,7 @@ function vaultMemoryToStoredRaw(memory) {
13287
13313
  userId: memory.userId ?? null,
13288
13314
  embedding: memory.embedding ?? null,
13289
13315
  embeddingModel: memory.embeddingModel ?? null,
13290
- sourceChunkIds,
13316
+ sourceChunkIds: sourceChunkIds2,
13291
13317
  proofCount: memory.proofCount ?? null,
13292
13318
  source: memory.source ?? null,
13293
13319
  media: parseMedia(memory.media),
@@ -13336,7 +13362,7 @@ function populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedK
13336
13362
  record2._setRaw("embedding_model", opts.embeddingModel ?? null);
13337
13363
  }
13338
13364
  if (opts.sourceChunkIds !== void 0) {
13339
- record2._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
13365
+ record2._setRaw("source_chunk_ids", serializeSourceChunkIds(opts.sourceChunkIds));
13340
13366
  }
13341
13367
  record2._setRaw("proof_count", opts.proofCount ?? 1);
13342
13368
  record2._setRaw("source", opts.source ?? "manual");
@@ -13370,8 +13396,7 @@ async function createVaultMemoryOp(ctx, opts) {
13370
13396
  ctx.embeddedWalletSigner
13371
13397
  ) : opts.content;
13372
13398
  const created = await ctx.database.write(async () => {
13373
- if (ctx.canWrite && !await ctx.canWrite())
13374
- throw new Error("Memory source is no longer eligible");
13399
+ await assertCanWrite2(ctx);
13375
13400
  return ctx.vaultMemoryCollection.create(
13376
13401
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
13377
13402
  );
@@ -13390,8 +13415,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
13390
13415
  ) : opts.content;
13391
13416
  let createdRecord = null;
13392
13417
  await ctx.database.write(async () => {
13393
- if (ctx.canWrite && !await ctx.canWrite())
13394
- throw new Error("Memory source is no longer eligible");
13418
+ await assertCanWrite2(ctx);
13395
13419
  let target;
13396
13420
  try {
13397
13421
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -13483,8 +13507,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
13483
13507
  })
13484
13508
  );
13485
13509
  const created = await ctx.database.write(async () => {
13486
- if (ctx.canWrite && !await ctx.canWrite())
13487
- throw new Error("Memory source is no longer eligible");
13510
+ await assertCanWrite2(ctx);
13488
13511
  const prepared = optionsArray.map(
13489
13512
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
13490
13513
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -13514,16 +13537,16 @@ async function getVaultMemoryOp(ctx, id) {
13514
13537
  }
13515
13538
  }
13516
13539
  function vaultMemoryRawToStoredRaw(raw) {
13517
- let sourceChunkIds = null;
13540
+ let sourceChunkIds2 = null;
13518
13541
  const rawChunks = raw.source_chunk_ids;
13519
13542
  if (typeof rawChunks === "string" && rawChunks) {
13520
13543
  try {
13521
13544
  const parsed = JSON.parse(rawChunks);
13522
13545
  if (Array.isArray(parsed)) {
13523
- sourceChunkIds = parsed.filter((s) => typeof s === "string");
13546
+ sourceChunkIds2 = parsed.filter((s) => typeof s === "string");
13524
13547
  }
13525
13548
  } catch {
13526
- sourceChunkIds = null;
13549
+ sourceChunkIds2 = null;
13527
13550
  }
13528
13551
  }
13529
13552
  return {
@@ -13537,7 +13560,7 @@ function vaultMemoryRawToStoredRaw(raw) {
13537
13560
  userId: raw.user_id ?? null,
13538
13561
  embedding: raw.embedding ?? null,
13539
13562
  embeddingModel: raw.embedding_model ?? null,
13540
- sourceChunkIds,
13563
+ sourceChunkIds: sourceChunkIds2,
13541
13564
  proofCount: raw.proof_count ?? null,
13542
13565
  source: raw.source ?? null,
13543
13566
  eventTimeStart: raw.event_time_start ?? null,
@@ -13789,8 +13812,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
13789
13812
  const record2 = probe;
13790
13813
  const originalUpdatedAt = record2.updatedAt.getTime();
13791
13814
  await ctx.database.write(async () => {
13792
- if (ctx.canWrite && !await ctx.canWrite())
13793
- throw new Error("Memory source is no longer eligible");
13815
+ await assertCanWrite2(ctx);
13794
13816
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
13795
13817
  stale = true;
13796
13818
  return;
@@ -13834,7 +13856,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
13834
13856
  if (opts.sourceChunkIds !== void 0) {
13835
13857
  r._setRaw(
13836
13858
  "source_chunk_ids",
13837
- JSON.stringify(
13859
+ serializeSourceChunkIds(
13838
13860
  opts.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observedSources, ...opts.sourceChunkIds])] : opts.sourceChunkIds
13839
13861
  )
13840
13862
  );
@@ -13901,6 +13923,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
13901
13923
  let stale = false;
13902
13924
  const originalUpdatedAt = record2.updatedAt.getTime();
13903
13925
  await ctx.database.write(async (writer) => {
13926
+ await assertCanWrite2(ctx);
13904
13927
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13905
13928
  stale = true;
13906
13929
  return;
@@ -13942,6 +13965,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
13942
13965
  const originalUpdatedAt = record2.updatedAt.getTime();
13943
13966
  let cleared = false;
13944
13967
  await ctx.database.write(async () => {
13968
+ await assertCanWrite2(ctx);
13945
13969
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
13946
13970
  cleared = true;
13947
13971
  await record2.update((r) => {
@@ -13965,6 +13989,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
13965
13989
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
13966
13990
  let stale = false;
13967
13991
  await ctx.database.write(async () => {
13992
+ await assertCanWrite2(ctx);
13968
13993
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13969
13994
  stale = true;
13970
13995
  return;
@@ -13993,6 +14018,7 @@ async function deleteVaultMemoryOp(ctx, id) {
13993
14018
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
13994
14019
  let stale = false;
13995
14020
  await ctx.database.write(async () => {
14021
+ await assertCanWrite2(ctx);
13996
14022
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
13997
14023
  stale = true;
13998
14024
  return;
@@ -14030,8 +14056,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
14030
14056
  }
14031
14057
  let stale = false;
14032
14058
  await ctx.database.write(async () => {
14033
- if (ctx.canWrite && !await ctx.canWrite())
14034
- throw new Error("Memory source is no longer eligible");
14059
+ await assertCanWrite2(ctx);
14035
14060
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
14036
14061
  stale = true;
14037
14062
  return;
@@ -14193,6 +14218,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
14193
14218
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14194
14219
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14195
14220
  await ctx.database.write(async () => {
14221
+ await assertCanWrite2(ctx);
14196
14222
  const records = [];
14197
14223
  for (const id of chunkIds) {
14198
14224
  try {
@@ -14226,6 +14252,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
14226
14252
  for (const id of Array.from(new Set(memoryIds))) {
14227
14253
  try {
14228
14254
  await ctx.database.write(async (writer) => {
14255
+ await assertCanWrite2(ctx);
14229
14256
  let record2;
14230
14257
  try {
14231
14258
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -14256,6 +14283,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
14256
14283
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
14257
14284
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
14258
14285
  await ctx.database.write(async () => {
14286
+ await assertCanWrite2(ctx);
14259
14287
  const writes = [];
14260
14288
  for (const id of chunkIds) {
14261
14289
  let record2;
@@ -14288,6 +14316,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
14288
14316
  const records = await ctx.vaultMemoryCollection.query(Q8.where("user_id", userId), Q8.where("is_deleted", false)).fetch();
14289
14317
  if (records.length === 0) return 0;
14290
14318
  await ctx.database.write(async () => {
14319
+ await assertCanWrite2(ctx);
14291
14320
  const prepared = records.map(
14292
14321
  (record2) => record2.prepareUpdate((r) => {
14293
14322
  r._setRaw("is_deleted", true);
@@ -14342,6 +14371,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
14342
14371
  let stale = false;
14343
14372
  const archivedAtValue = opts?.now ?? Date.now();
14344
14373
  await ctx.database.write(async () => {
14374
+ await assertCanWrite2(ctx);
14345
14375
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
14346
14376
  stale = true;
14347
14377
  return;
@@ -14365,6 +14395,7 @@ async function restoreVaultMemoryOp(ctx, id) {
14365
14395
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
14366
14396
  let stale = false;
14367
14397
  await ctx.database.write(async () => {
14398
+ await assertCanWrite2(ctx);
14368
14399
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
14369
14400
  stale = true;
14370
14401
  return;
@@ -14385,6 +14416,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
14385
14416
  const now = opts.now ?? Date.now();
14386
14417
  let stale = false;
14387
14418
  await ctx.database.write(async () => {
14419
+ await assertCanWrite2(ctx);
14388
14420
  const archivedAt = record2.archivedAt;
14389
14421
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
14390
14422
  stale = true;
@@ -14424,6 +14456,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
14424
14456
  }
14425
14457
  let written = false;
14426
14458
  await ctx.database.write(async () => {
14459
+ await assertCanWrite2(ctx);
14427
14460
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
14428
14461
  const originalUpdatedAt = record2.updatedAt.getTime();
14429
14462
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -18393,6 +18426,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
18393
18426
  ).map((c) => c.candidate);
18394
18427
  }
18395
18428
  async function extractAndRetain(messages, retainCtx, options) {
18429
+ const { memoryStore, signal } = options;
18430
+ if (memoryStore === null) throw new Error("Memory store is not ready");
18431
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
18432
+ throw new Error("Remote memory extraction does not support folders");
18433
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
18434
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
18435
+ signal?.throwIfAborted();
18396
18436
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
18397
18437
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
18398
18438
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -18426,6 +18466,7 @@ async function extractAndRetain(messages, retainCtx, options) {
18426
18466
  }
18427
18467
  });
18428
18468
  const extractMs = Date.now() - tExtract;
18469
+ signal?.throwIfAborted();
18429
18470
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
18430
18471
  const log = getLogger();
18431
18472
  const screened = screenCandidatesForInjection(filtered);
@@ -18473,16 +18514,33 @@ async function extractAndRetain(messages, retainCtx, options) {
18473
18514
  const results = [];
18474
18515
  const quarantinedInfo = [];
18475
18516
  let failedWrites = 0;
18517
+ let auditRows;
18476
18518
  const tRetain = Date.now();
18477
18519
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
18478
18520
  try {
18521
+ signal?.throwIfAborted();
18479
18522
  if (isQuarantined) {
18480
- const existingId = await findQuarantinedDuplicateOp(
18481
- retainCtx.vaultCtx,
18482
- candidate.content,
18483
- candidate.sourceMessageIds,
18484
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
18485
- ).catch(() => null);
18523
+ let existingId;
18524
+ if (memoryStore) {
18525
+ auditRows ?? (auditRows = memoryStore.list({
18526
+ scopes: [options.scope ?? "private"],
18527
+ includeArchived: true,
18528
+ includeQuarantined: true,
18529
+ includeSuperseded: true
18530
+ }));
18531
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
18532
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
18533
+ existingId = (await auditRows).find(
18534
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
18535
+ )?.uniqueId ?? null;
18536
+ } else {
18537
+ existingId = await findQuarantinedDuplicateOp(
18538
+ retainCtx.vaultCtx,
18539
+ candidate.content,
18540
+ candidate.sourceMessageIds,
18541
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
18542
+ ).catch(() => null);
18543
+ }
18486
18544
  if (existingId) {
18487
18545
  quarantinedInfo.push({
18488
18546
  candidate,
@@ -18493,18 +18551,26 @@ async function extractAndRetain(messages, retainCtx, options) {
18493
18551
  continue;
18494
18552
  }
18495
18553
  }
18496
- const result = await retain(candidate.content, retainCtx, {
18554
+ const retainOptions = {
18497
18555
  source: "auto-extracted",
18498
18556
  sourceChunkIds: candidate.sourceMessageIds,
18499
18557
  respectTombstones: true,
18500
18558
  ...options.scope !== void 0 && { scope: options.scope },
18501
- ...options.folderId !== void 0 && { folderId: options.folderId },
18502
18559
  ...consolidateOptions !== void 0 && { consolidateOptions },
18503
18560
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
18504
18561
  factType: candidate.type,
18505
18562
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
18563
+ };
18564
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
18565
+ ...retainOptions,
18566
+ canWrite: retainCtx.vaultCtx.canWrite,
18567
+ signal
18568
+ }) : await retain(candidate.content, retainCtx, {
18569
+ ...retainOptions,
18570
+ ...options.folderId !== void 0 && { folderId: options.folderId }
18506
18571
  });
18507
18572
  if (isQuarantined) {
18573
+ auditRows = void 0;
18508
18574
  const info = {
18509
18575
  candidate,
18510
18576
  memoryId: result.memoryId,
@@ -18523,9 +18589,16 @@ async function extractAndRetain(messages, retainCtx, options) {
18523
18589
  }
18524
18590
  succeededCandidates.push(candidate);
18525
18591
  results.push(result);
18526
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
18592
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
18527
18593
  try {
18528
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
18594
+ signal?.throwIfAborted();
18595
+ if (memoryStore) {
18596
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
18597
+ unlessUserManaged: true,
18598
+ canWrite: retainCtx.vaultCtx.canWrite,
18599
+ signal
18600
+ });
18601
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
18529
18602
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
18530
18603
  unlessTopicsUserManaged: true
18531
18604
  });
@@ -18773,7 +18846,7 @@ function createAutoExtractor(options) {
18773
18846
  };
18774
18847
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
18775
18848
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
18776
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18849
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
18777
18850
  getLogger().warn(
18778
18851
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
18779
18852
  );
@@ -18818,6 +18891,8 @@ function createAutoExtractor(options) {
18818
18891
  try {
18819
18892
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
18820
18893
  extract,
18894
+ memoryStore: options.memoryStore,
18895
+ signal: options.signal,
18821
18896
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
18822
18897
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
18823
18898
  ...options.scope !== void 0 && { scope: options.scope },
@@ -20930,6 +21005,7 @@ function createLocalMemoryStore(options) {
20930
21005
  database,
20931
21006
  entityCollection: database.get("entity"),
20932
21007
  memoryEntityCollection: database.get("memory_entity"),
21008
+ canWrite: options.canWrite,
20933
21009
  ...userId !== void 0 && { userId },
20934
21010
  ...options.allowUnscopedRows !== void 0 && {
20935
21011
  allowUnscopedRows: options.allowUnscopedRows
@@ -20941,10 +21017,21 @@ function createLocalMemoryStore(options) {
20941
21017
  walletAddress: options.walletAddress,
20942
21018
  signMessage: options.signMessage,
20943
21019
  embeddedWalletSigner: options.embeddedWalletSigner,
21020
+ canWrite: options.canWrite,
20944
21021
  ...userId !== void 0 && { userId },
20945
21022
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
20946
21023
  entityCtx
20947
21024
  };
21025
+ const assertWrite = async (writeOptions) => {
21026
+ writeOptions?.signal?.throwIfAborted();
21027
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
21028
+ throw new Error("Memory source is no longer eligible");
21029
+ }
21030
+ if (options.canWrite && !await options.canWrite()) {
21031
+ throw new Error("Memory source is no longer eligible");
21032
+ }
21033
+ writeOptions?.signal?.throwIfAborted();
21034
+ };
20948
21035
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
20949
21036
  const ownedBy = userId !== void 0 ? [Q15.where("user_id", userId)] : [];
20950
21037
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [Q15.or(Q15.where("user_id", userId), Q15.where("user_id", null))] : [Q15.where("user_id", userId)];
@@ -21056,9 +21143,25 @@ function createLocalMemoryStore(options) {
21056
21143
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
21057
21144
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
21058
21145
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
21059
- addTopics: (memoryId, topics) => database.write(async (writer) => {
21146
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
21147
+ await assertWrite(writeOptions);
21060
21148
  const owned = await vaultCtx.vaultMemoryCollection.query(Q15.where("id", memoryId), Q15.where("is_deleted", false), ...ownedBy).fetchCount();
21061
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
21149
+ return owned > 0 ? writer.callWriter(
21150
+ () => linkMemoryEntitiesOp(
21151
+ {
21152
+ ...entityCtx,
21153
+ canWrite: async () => {
21154
+ await assertWrite(writeOptions);
21155
+ return true;
21156
+ }
21157
+ },
21158
+ memoryId,
21159
+ topics,
21160
+ {
21161
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
21162
+ }
21163
+ )
21164
+ ) : [];
21062
21165
  }),
21063
21166
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
21064
21167
  recall: (query, recallOptions) => recall(
@@ -21068,7 +21171,17 @@ function createLocalMemoryStore(options) {
21068
21171
  ),
21069
21172
  retain: (content, retainOptions) => retain(
21070
21173
  content,
21071
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
21174
+ {
21175
+ vaultCtx: {
21176
+ ...vaultCtx,
21177
+ canWrite: async () => {
21178
+ await assertWrite(retainOptions);
21179
+ return true;
21180
+ }
21181
+ },
21182
+ embeddingOptions: options.embeddingOptions,
21183
+ vaultCache
21184
+ },
21072
21185
  retainOptions
21073
21186
  ),
21074
21187
  factSource: {
@@ -21172,11 +21285,15 @@ function memoryCipher(canonical) {
21172
21285
  };
21173
21286
  }
21174
21287
  async function reencryptMemoryField(value, canonical, fallbacks = []) {
21175
- const { encrypt } = memoryCipher(canonical);
21176
- if (!value.startsWith("enc:")) return encrypt(value);
21288
+ return memoryCipher(canonical).encrypt(
21289
+ await decryptMemoryField(value, [canonical, ...fallbacks])
21290
+ );
21291
+ }
21292
+ async function decryptMemoryField(value, rings) {
21293
+ if (!value.startsWith("enc:")) return value;
21177
21294
  if (!isEncrypted(value))
21178
21295
  throw new MemoryKeyError("Field has an encryption prefix but an invalid payload");
21179
- return encrypt(await decryptWith(value, [canonical, ...fallbacks]));
21296
+ return decryptWith(value, rings);
21180
21297
  }
21181
21298
 
21182
21299
  // src/lib/memory/store/remotePersistence.ts
@@ -21714,7 +21831,14 @@ function stored(row) {
21714
21831
  function createRemoteMemoryPipeline(options) {
21715
21832
  const { persistence, embeddingOptions } = options;
21716
21833
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
21717
- const operation = (mode, queryOptions = embeddingOptions) => {
21834
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
21835
+ const signal = writeOptions?.signal;
21836
+ const assertWrite = async () => {
21837
+ signal?.throwIfAborted();
21838
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
21839
+ throw new Error("Memory source is no longer eligible");
21840
+ signal?.throwIfAborted();
21841
+ };
21718
21842
  const rejectUndecryptable = (failed) => {
21719
21843
  if (!failed.length) return;
21720
21844
  if (mode === "retain")
@@ -21732,7 +21856,7 @@ function createRemoteMemoryPipeline(options) {
21732
21856
  return items.map((item) => stored(item.memory));
21733
21857
  };
21734
21858
  const get = async (id) => {
21735
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
21859
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
21736
21860
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
21737
21861
  snapshots.set(id, snapshot);
21738
21862
  return stored(snapshot.memory);
@@ -21782,7 +21906,7 @@ function createRemoteMemoryPipeline(options) {
21782
21906
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
21783
21907
  }
21784
21908
  const forceIds = [...forced];
21785
- const windowOptions = (i, signal) => ({
21909
+ const windowOptions = (i, signal2) => ({
21786
21910
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
21787
21911
  force_ids: i === 0 ? forceIds : [],
21788
21912
  ...search.scopes && { scopes: search.scopes },
@@ -21790,13 +21914,15 @@ function createRemoteMemoryPipeline(options) {
21790
21914
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
21791
21915
  include_archived: search.includeArchived,
21792
21916
  embedding_model: model,
21793
- ...signal && { signal }
21917
+ ...signal2 && { signal: signal2 }
21794
21918
  });
21795
21919
  const vectors = [queryEmbedding, ...facetEmbeddings];
21796
21920
  let windows;
21797
21921
  if (mode === "retain") {
21798
21922
  windows = await Promise.all(
21799
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
21923
+ vectors.map(
21924
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
21925
+ )
21800
21926
  );
21801
21927
  } else {
21802
21928
  const deadline = new AbortController();
@@ -21869,7 +21995,7 @@ function createRemoteMemoryPipeline(options) {
21869
21995
  embedding: storedVector(JSON.parse(input.embedding)),
21870
21996
  embedding_model: input.embeddingModel
21871
21997
  },
21872
- source_chunk_ids: JSON.stringify(input.sourceChunkIds ?? []),
21998
+ source_chunk_ids: serializeSourceChunkIds(input.sourceChunkIds ?? []),
21873
21999
  proof_count: input.proofCount ?? 1,
21874
22000
  source: input.source ?? "manual",
21875
22001
  ...input.eventTime && {
@@ -21884,12 +22010,15 @@ function createRemoteMemoryPipeline(options) {
21884
22010
  twin_opt_in: false
21885
22011
  };
21886
22012
  };
21887
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
22013
+ const write = async (memory, snapshot) => {
22014
+ await assertWrite();
22015
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
22016
+ };
21888
22017
  const port = {
21889
22018
  prepare,
21890
22019
  get,
21891
22020
  getFresh: async (id) => {
21892
- const current = await persistence.get(id);
22021
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
21893
22022
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
21894
22023
  },
21895
22024
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -21900,7 +22029,8 @@ function createRemoteMemoryPipeline(options) {
21900
22029
  embedding_model: embeddingModel,
21901
22030
  strict_model: true,
21902
22031
  include_deleted: true,
21903
- deleted_only: true
22032
+ deleted_only: true,
22033
+ signal
21904
22034
  });
21905
22035
  rejectUndecryptable(window2.failed);
21906
22036
  return remember(window2.items);
@@ -21928,7 +22058,7 @@ function createRemoteMemoryPipeline(options) {
21928
22058
  const observed = sourceIds(memory.source_chunk_ids);
21929
22059
  const replay = !!patch.observationSourceIds?.length && patch.observationSourceIds.every((id2) => observed.includes(id2));
21930
22060
  if (patch.sourceChunkIds !== void 0)
21931
- memory.source_chunk_ids = JSON.stringify(
22061
+ memory.source_chunk_ids = serializeSourceChunkIds(
21932
22062
  patch.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observed, ...patch.sourceChunkIds])] : patch.sourceChunkIds
21933
22063
  );
21934
22064
  if (!replay) {
@@ -21956,14 +22086,18 @@ function createRemoteMemoryPipeline(options) {
21956
22086
  if (!target || !newer || id === successor) return false;
21957
22087
  const old = snapshots.get(id);
21958
22088
  const next = snapshots.get(successor);
22089
+ await assertWrite();
21959
22090
  remember(
21960
- await persistence.putMany([
21961
- {
21962
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
21963
- expectedVersion: old
21964
- },
21965
- { memory: next.memory, expectedVersion: next }
21966
- ])
22091
+ await persistence.putMany(
22092
+ [
22093
+ {
22094
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
22095
+ expectedVersion: old
22096
+ },
22097
+ { memory: next.memory, expectedVersion: next }
22098
+ ],
22099
+ signal
22100
+ )
21967
22101
  );
21968
22102
  return true;
21969
22103
  },
@@ -21972,18 +22106,22 @@ function createRemoteMemoryPipeline(options) {
21972
22106
  if (!target) return { created: null, retired: false };
21973
22107
  const snapshot = snapshots.get(targetId);
21974
22108
  const successor = createRow(input);
22109
+ await assertWrite();
21975
22110
  const rows = remember(
21976
- await persistence.putMany([
21977
- { memory: successor, expectedVersion: 0 },
21978
- {
21979
- memory: {
21980
- ...snapshot.memory,
21981
- superseded_by: successor.memory_id,
21982
- superseded_at: Date.now()
21983
- },
21984
- expectedVersion: snapshot
21985
- }
21986
- ])
22111
+ await persistence.putMany(
22112
+ [
22113
+ { memory: successor, expectedVersion: 0 },
22114
+ {
22115
+ memory: {
22116
+ ...snapshot.memory,
22117
+ superseded_by: successor.memory_id,
22118
+ superseded_at: Date.now()
22119
+ },
22120
+ expectedVersion: snapshot
22121
+ }
22122
+ ],
22123
+ signal
22124
+ )
21987
22125
  );
21988
22126
  return { created: rows[0], retired: true };
21989
22127
  }
@@ -22018,7 +22156,7 @@ function createRemoteMemoryPipeline(options) {
22018
22156
  factSource,
22019
22157
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
22020
22158
  retain: async (content, retainOptions) => {
22021
- const { port } = operation("retain");
22159
+ const { port } = operation("retain", embeddingOptions, retainOptions);
22022
22160
  return retainWithPersistence(
22023
22161
  content,
22024
22162
  {
@@ -22035,6 +22173,8 @@ function createRemoteMemoryPipeline(options) {
22035
22173
 
22036
22174
  // src/lib/memory/store/remoteMigration.ts
22037
22175
  var IMPORT_BATCH = 50;
22176
+ var EMBED_BATCH = 100;
22177
+ var LOCAL_VECTOR_DIMS = 4096;
22038
22178
  var VERIFY_BATCH = 1e3;
22039
22179
  var QUERY_IDS = 1e3;
22040
22180
  var QUERY_PAGE = 200;
@@ -22060,7 +22200,7 @@ var SHORT_FIELDS = [
22060
22200
  "visibility",
22061
22201
  "geohash"
22062
22202
  ];
22063
- var utf8 = new TextEncoder();
22203
+ var utf82 = new TextEncoder();
22064
22204
  var METADATA_FIELDS = [
22065
22205
  "scope",
22066
22206
  "kind",
@@ -22111,17 +22251,34 @@ function revision(raw) {
22111
22251
  last_observed_at: num(raw.last_observed_at) ?? null
22112
22252
  };
22113
22253
  }
22114
- function embedding(raw) {
22254
+ function embedding(raw, model) {
22115
22255
  const value = text13(raw.embedding);
22116
22256
  if (!value) return void 0;
22117
22257
  try {
22118
- return storedVector(JSON.parse(value));
22258
+ const vector = JSON.parse(value);
22259
+ const stored2 = text13(raw.embedding_model);
22260
+ if (stored2 === void 0 ? vector.length !== LOCAL_VECTOR_DIMS : stored2 !== model)
22261
+ return void 0;
22262
+ return storedVector(vector);
22263
+ } catch {
22264
+ return void 0;
22265
+ }
22266
+ }
22267
+ function stringArray(value) {
22268
+ try {
22269
+ const parsed = JSON.parse(value);
22270
+ return Array.isArray(parsed) && parsed.every((id) => typeof id === "string") ? parsed : void 0;
22119
22271
  } catch {
22120
22272
  return void 0;
22121
22273
  }
22122
22274
  }
22275
+ function sourceChunkIds(raw) {
22276
+ const value = text13(raw.source_chunk_ids);
22277
+ const ids = value && stringArray(value);
22278
+ return ids ? serializeSourceChunkIds(ids) : value;
22279
+ }
22123
22280
  function bytes(value) {
22124
- return value === void 0 ? 0 : utf8.encode(value).length;
22281
+ return value === void 0 ? 0 : utf82.encode(value).length;
22125
22282
  }
22126
22283
  function fitsNearby(row) {
22127
22284
  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);
@@ -22130,16 +22287,16 @@ function invalid(raw) {
22130
22287
  const id = raw.id;
22131
22288
  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;
22132
22289
  }
22133
- function metadata(raw) {
22134
- const vector = embedding(raw);
22290
+ function metadata(raw, model) {
22291
+ const vector = embedding(raw, model);
22135
22292
  return {
22136
22293
  scope: raw.scope,
22137
22294
  kind: text13(raw.kind),
22138
22295
  level: text13(raw.level),
22139
22296
  folder_id: text13(raw.folder_id),
22140
22297
  is_deleted: bool(raw.is_deleted) ?? false,
22141
- ...vector && { embedding: vector, embedding_model: text13(raw.embedding_model) },
22142
- source_chunk_ids: text13(raw.source_chunk_ids),
22298
+ ...vector && { embedding: vector, embedding_model: model },
22299
+ source_chunk_ids: sourceChunkIds(raw),
22143
22300
  proof_count: num(raw.proof_count),
22144
22301
  source: text13(raw.source),
22145
22302
  event_time_start: num(raw.event_time_start),
@@ -22168,30 +22325,63 @@ function defined(value) {
22168
22325
  Object.entries(value).filter(([, field10]) => field10 !== void 0)
22169
22326
  );
22170
22327
  }
22171
- async function remoteRow(raw, canonical, fallbacks, updatedAt) {
22172
- const content = await reencryptMemoryField(
22173
- typeof raw.content === "string" ? raw.content : "",
22174
- canonical,
22175
- fallbacks
22176
- );
22177
- const kindValue = text13(raw.kind_value);
22178
- const encryptedKindValue = kindValue ? await reencryptMemoryField(kindValue, canonical, fallbacks) : void 0;
22328
+ async function prepareRow(raw, canonical, rings, model, updatedAt) {
22329
+ const deleted = bool(raw.is_deleted) ?? false;
22330
+ const { encrypt } = memoryCipher(canonical);
22331
+ const localKindValue = text13(raw.kind_value);
22332
+ let plaintext;
22333
+ let kindValue;
22334
+ try {
22335
+ plaintext = await decryptMemoryField(typeof raw.content === "string" ? raw.content : "", rings);
22336
+ kindValue = localKindValue && await decryptMemoryField(localKindValue, rings);
22337
+ } catch (error) {
22338
+ if (!deleted || !(error instanceof MemoryKeyError)) throw error;
22339
+ plaintext = void 0;
22340
+ kindValue = void 0;
22341
+ }
22179
22342
  const row = {
22180
- ...defined({ ...metadata(raw), kind_value: encryptedKindValue }),
22343
+ ...defined({
22344
+ ...metadata(raw, model),
22345
+ kind_value: kindValue === void 0 ? void 0 : await encrypt(kindValue)
22346
+ }),
22181
22347
  memory_id: raw.id,
22182
- content,
22348
+ content: await encrypt(plaintext ?? ""),
22183
22349
  scope: raw.scope,
22184
22350
  created_at: raw.created_at,
22185
22351
  updated_at: updatedAt,
22186
- is_deleted: bool(raw.is_deleted) ?? false
22352
+ is_deleted: deleted
22187
22353
  };
22188
- return fitsNearby(row) ? row : null;
22354
+ return { row, plaintext };
22189
22355
  }
22190
- function sameMetadata(local, remote) {
22191
- const mine = metadata(local);
22356
+ async function embedMissing(prepared, options, model, signal) {
22357
+ const needed = prepared.filter((p) => !p.row.embedding && p.plaintext);
22358
+ for (const batch of chunks(needed, EMBED_BATCH)) {
22359
+ if (signal?.aborted) throw aborted(signal);
22360
+ try {
22361
+ const vectors = await generateEmbeddings(
22362
+ batch.map((p) => p.plaintext),
22363
+ { ...options, taskType: "memory_retain_embed" }
22364
+ );
22365
+ if (vectors.length !== batch.length) throw new Error("Incomplete migration embeddings");
22366
+ const stored2 = vectors.map(storedVector);
22367
+ batch.forEach((p, i) => {
22368
+ p.row.embedding = stored2[i];
22369
+ p.row.embedding_model = model;
22370
+ });
22371
+ } catch (error) {
22372
+ getLogger().warn(
22373
+ `[memory/migration] Uploading ${batch.length} row(s) without a vector: embedding failed`,
22374
+ error
22375
+ );
22376
+ }
22377
+ }
22378
+ }
22379
+ function sameMetadata(local, remote, model) {
22380
+ const mine = metadata(local, model);
22192
22381
  return METADATA_FIELDS.every((field10) => {
22193
22382
  const a = mine[field10] ?? null;
22194
22383
  const b = remote[field10] ?? null;
22384
+ if (field10 === "embedding_model" && a === null) return true;
22195
22385
  if (field10 === "visibility") return (a ?? "private") === (b ?? "private");
22196
22386
  if (field10 === "is_deleted" || field10 === "topics_user_managed" || field10 === "twin_opt_in")
22197
22387
  return (a ?? false) === (b ?? false);
@@ -22287,7 +22477,8 @@ async function backfillTopics(ctx) {
22287
22477
  }
22288
22478
  async function migrateMemoriesToRemote(options) {
22289
22479
  const { canonical, signal } = options;
22290
- const fallbacks = options.fallbacks ?? [];
22480
+ const rings = [canonical, ...options.fallbacks ?? []];
22481
+ const model = options.embeddingOptions?.model ?? DEFAULT_API_EMBEDDING_MODEL;
22291
22482
  const request = createNearbyRequest(options);
22292
22483
  const account = await request("/account", {}, signal);
22293
22484
  if (typeof account.key_id === "string" && account.key_id !== canonical.keyId)
@@ -22298,8 +22489,13 @@ async function migrateMemoriesToRemote(options) {
22298
22489
  );
22299
22490
  const active = account.state === "active";
22300
22491
  let claimed = account.key_id === canonical.keyId;
22492
+ if (!active) await createDecaySweeper({ vaultCtx: options.vaultCtx }).runSweep();
22301
22493
  await backfillTopics(options.vaultCtx);
22302
22494
  const skipped = /* @__PURE__ */ new Map();
22495
+ const reembedded = /* @__PURE__ */ new Set();
22496
+ const unembedded = /* @__PURE__ */ new Set();
22497
+ const placeholders = /* @__PURE__ */ new Set();
22498
+ const truncated = /* @__PURE__ */ new Set();
22303
22499
  let imported = 0;
22304
22500
  for (let pass = 0; ; pass++) {
22305
22501
  const rows = await getAllVaultMemoryRowsOp(options.vaultCtx);
@@ -22315,9 +22511,10 @@ async function migrateMemoriesToRemote(options) {
22315
22511
  candidates.map((raw) => raw.id),
22316
22512
  signal
22317
22513
  );
22514
+ const needsVector = (raw, server) => !!options.embeddingOptions && !embedding(raw, model) && server.embedding_model !== model;
22318
22515
  for (const raw of candidates) {
22319
22516
  const server = remote.get(raw.id);
22320
- if (server && server.updated_at === raw.updated_at && !sameMetadata(raw, server))
22517
+ if (server && server.updated_at === raw.updated_at && (!sameMetadata(raw, server, model) || needsVector(raw, server)))
22321
22518
  pending.set(raw.id, raw.updated_at + 1);
22322
22519
  }
22323
22520
  }
@@ -22326,7 +22523,11 @@ async function migrateMemoriesToRemote(options) {
22326
22523
  state,
22327
22524
  total: rows.length,
22328
22525
  imported,
22329
- skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason }))
22526
+ skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason })),
22527
+ reembedded: reembedded.size,
22528
+ unembedded: unembedded.size,
22529
+ placeholderTombstones: placeholders.size,
22530
+ truncatedSourceChunkIds: truncated.size
22330
22531
  });
22331
22532
  if (skipped.size > 0 && !options.allowSkipped) return result("migrating");
22332
22533
  if (!claimed) await importBatch(request, canonical.keyId, [], signal);
@@ -22344,19 +22545,35 @@ async function migrateMemoriesToRemote(options) {
22344
22545
  409,
22345
22546
  "migration_incomplete"
22346
22547
  );
22347
- const upload = [];
22548
+ const prepared = [];
22348
22549
  for (const raw of candidates) {
22349
22550
  const updatedAt = pending.get(raw.id);
22350
22551
  if (updatedAt === void 0) continue;
22351
22552
  try {
22352
- const row = await remoteRow(raw, canonical, fallbacks, updatedAt);
22353
- if (row) upload.push(row);
22354
- else skipped.set(raw.id, "invalid");
22553
+ prepared.push(await prepareRow(raw, canonical, rings, model, updatedAt));
22355
22554
  } catch (error) {
22356
22555
  if (!(error instanceof MemoryKeyError)) throw error;
22357
22556
  skipped.set(raw.id, "unreadable");
22358
22557
  }
22359
22558
  }
22559
+ const usable = new Set(prepared.filter((p) => p.row.embedding).map((p) => p.row.memory_id));
22560
+ if (options.embeddingOptions)
22561
+ await embedMissing(prepared, options.embeddingOptions, model, signal);
22562
+ const local = new Map(candidates.map((raw) => [raw.id, raw]));
22563
+ const upload = [];
22564
+ for (const { row, plaintext } of prepared) {
22565
+ const id = row.memory_id;
22566
+ if (!fitsNearby(row)) {
22567
+ skipped.set(id, "invalid");
22568
+ continue;
22569
+ }
22570
+ upload.push(row);
22571
+ const track = (set, on) => on ? set.add(id) : set.delete(id);
22572
+ track(reembedded, !!row.embedding && !usable.has(id));
22573
+ track(unembedded, !row.embedding);
22574
+ track(placeholders, plaintext === void 0);
22575
+ track(truncated, row.source_chunk_ids !== text13(local.get(id).source_chunk_ids));
22576
+ }
22360
22577
  let sent = 0;
22361
22578
  options.onProgress?.({ imported: sent, toImport: upload.length });
22362
22579
  for (const batch of chunks(upload, IMPORT_BATCH)) {
@@ -22600,16 +22817,21 @@ function createRemoteMemoryStore(options) {
22600
22817
  const record2 = await persistence.get(id);
22601
22818
  return record2 && !record2.memory.is_deleted ? record2 : null;
22602
22819
  };
22603
- const mutateLocked = async (id, change, edit) => {
22604
- const record2 = await live(id);
22820
+ const mutateLocked = async (id, change, edit, writeOptions) => {
22821
+ writeOptions?.signal?.throwIfAborted();
22822
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
22605
22823
  if (!record2) return null;
22824
+ if (record2.memory.is_deleted) return null;
22825
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
22826
+ throw new Error("Memory source is no longer eligible");
22827
+ writeOptions?.signal?.throwIfAborted();
22606
22828
  const next = edit(structuredClone(record2.memory));
22607
22829
  if (!next) return null;
22608
- const saved = await persistence.put(next, record2);
22830
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
22609
22831
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
22610
22832
  return saved;
22611
22833
  };
22612
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
22834
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
22613
22835
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
22614
22836
  const mutateMany = async (ids, change, edit) => {
22615
22837
  const saved = /* @__PURE__ */ new Map();
@@ -23122,13 +23344,21 @@ function createRemoteMemoryStore(options) {
23122
23344
  }))
23123
23345
  );
23124
23346
  },
23125
- addTopics: async (memoryId, topics) => {
23347
+ addTopics: async (memoryId, topics, writeOptions) => {
23126
23348
  const added = await withKnownKinds(topicInputs(topics, "auto"));
23127
- const saved = await mutate(memoryId, "edit", (memory) => ({
23128
- ...memory,
23129
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
23130
- topics_updated_at: Date.now()
23131
- }));
23349
+ const saved = await mutate(
23350
+ memoryId,
23351
+ "edit",
23352
+ (memory) => {
23353
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
23354
+ return {
23355
+ ...memory,
23356
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
23357
+ topics_updated_at: Date.now()
23358
+ };
23359
+ },
23360
+ writeOptions
23361
+ );
23132
23362
  if (!saved) return [];
23133
23363
  const now = /* @__PURE__ */ new Date();
23134
23364
  return mergeTopics([], added).map(
@@ -23979,6 +24209,7 @@ function createDurableAutoExtractor(options) {
23979
24209
  };
23980
24210
  const folderId = options.folderId ?? null;
23981
24211
  let disposed = false;
24212
+ let activeBatch = null;
23982
24213
  let running = false;
23983
24214
  let wakeRequested = false;
23984
24215
  let timer;
@@ -24015,14 +24246,18 @@ function createDurableAutoExtractor(options) {
24015
24246
  }, delay);
24016
24247
  }
24017
24248
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
24249
+ if (disposed) throw new Error("Extraction session was disposed");
24018
24250
  let watchdog;
24019
24251
  let cancelled = false;
24252
+ const controller = new AbortController();
24253
+ activeBatch = controller;
24020
24254
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
24021
24255
  try {
24022
24256
  return await Promise.race([
24023
24257
  new Promise((resolve, reject) => {
24024
24258
  const worker = createAutoExtractor({
24025
24259
  ...options,
24260
+ signal: controller.signal,
24026
24261
  retainCtx: {
24027
24262
  ...options.retainCtx,
24028
24263
  vaultCtx: {
@@ -24051,6 +24286,8 @@ function createDurableAutoExtractor(options) {
24051
24286
  ]);
24052
24287
  } finally {
24053
24288
  cancelled = true;
24289
+ controller.abort();
24290
+ if (activeBatch === controller) activeBatch = null;
24054
24291
  if (watchdog !== void 0) clearTimeout(watchdog);
24055
24292
  }
24056
24293
  }
@@ -24062,6 +24299,7 @@ function createDurableAutoExtractor(options) {
24062
24299
  for (const message of loaded)
24063
24300
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
24064
24301
  await database.write(async () => {
24302
+ if (disposed) return;
24065
24303
  const current = idsOf(job);
24066
24304
  const acknowledged = ids.filter((id) => current.includes(id));
24067
24305
  if (!acknowledged.length) return;
@@ -24234,6 +24472,7 @@ function createDurableAutoExtractor(options) {
24234
24472
  jobScope,
24235
24473
  jobFolderId
24236
24474
  );
24475
+ if (disposed) throw new Error("Extraction session was disposed");
24237
24476
  options.onTurnComplete?.(result);
24238
24477
  return result;
24239
24478
  };
@@ -24270,6 +24509,7 @@ function createDurableAutoExtractor(options) {
24270
24509
  );
24271
24510
  scheduleAfterSuccess = true;
24272
24511
  } catch (error) {
24512
+ if (disposed) break;
24273
24513
  attempts.set(job.id, attempted + 1);
24274
24514
  retry = true;
24275
24515
  reportError(error, conversationId);
@@ -24292,6 +24532,14 @@ function createDurableAutoExtractor(options) {
24292
24532
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
24293
24533
  }
24294
24534
  }
24535
+ function dispose() {
24536
+ disposed = true;
24537
+ activeBatch?.abort(options.signal?.reason);
24538
+ if (timer !== void 0) clearTimeout(timer);
24539
+ options.signal?.removeEventListener("abort", dispose);
24540
+ }
24541
+ if (options.signal?.aborted) dispose();
24542
+ else options.signal?.addEventListener("abort", dispose, { once: true });
24295
24543
  schedule(0);
24296
24544
  return {
24297
24545
  processTurn(messages, conversationId) {
@@ -24371,10 +24619,7 @@ function createDurableAutoExtractor(options) {
24371
24619
  return true;
24372
24620
  },
24373
24621
  isProcessing: () => running,
24374
- dispose() {
24375
- disposed = true;
24376
- if (timer !== void 0) clearTimeout(timer);
24377
- }
24622
+ dispose
24378
24623
  };
24379
24624
  }
24380
24625
 
@@ -30636,6 +30881,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
30636
30881
  var NO_CONVERSATION_KEY = "__no_conversation__";
30637
30882
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
30638
30883
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
30884
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
30639
30885
  var conversationRedactors = /* @__PURE__ */ new Map();
30640
30886
  function getConversationRedactor(conversationId, nerDetector) {
30641
30887
  const key = conversationId ?? NO_CONVERSATION_KEY;
@@ -30726,7 +30972,7 @@ function useChatStorage(options) {
30726
30972
  () => ({ getToken, baseUrl, model: embeddingModel, maskInput: maskEmbeddingInput }),
30727
30973
  [getToken, baseUrl, embeddingModel, maskEmbeddingInput]
30728
30974
  );
30729
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
30975
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
30730
30976
  const blobManagerRef = useRef4(new BlobUrlManager());
30731
30977
  useEffect4(() => {
30732
30978
  const manager = blobManagerRef.current;
@@ -31003,6 +31249,7 @@ function useChatStorage(options) {
31003
31249
  );
31004
31250
  const retainVaultMemory = useCallback5(
31005
31251
  async (input) => {
31252
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31006
31253
  if (memoryStore) {
31007
31254
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
31008
31255
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -31034,6 +31281,7 @@ function useChatStorage(options) {
31034
31281
  );
31035
31282
  const createMemoryVaultTool2 = useCallback5(
31036
31283
  (options2) => {
31284
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31037
31285
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
31038
31286
  const embOpts = getToken ? vaultEmbeddingOptions : void 0;
31039
31287
  return createMemoryVaultTool(
@@ -31055,6 +31303,7 @@ function useChatStorage(options) {
31055
31303
  );
31056
31304
  const getVaultMemories = useCallback5(
31057
31305
  (options2) => {
31306
+ if (memoryStore === null) return Promise.resolve([]);
31058
31307
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
31059
31308
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
31060
31309
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -31066,6 +31315,7 @@ function useChatStorage(options) {
31066
31315
  );
31067
31316
  const createVaultMemory = useCallback5(
31068
31317
  async (content, scope) => {
31318
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31069
31319
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
31070
31320
  if (memoryStore) return memoryStore.create({ content, scope });
31071
31321
  const result = await createVaultMemoryOp(vaultCtx, { content, scope });
@@ -31087,6 +31337,7 @@ function useChatStorage(options) {
31087
31337
  );
31088
31338
  const updateVaultMemory = useCallback5(
31089
31339
  async (id, content, scope) => {
31340
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31090
31341
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
31091
31342
  if (memoryStore) return memoryStore.update(id, { content, scope });
31092
31343
  const existing = await getVaultMemoryOp(vaultCtx, id);
@@ -31112,6 +31363,7 @@ function useChatStorage(options) {
31112
31363
  );
31113
31364
  const deleteVaultMemory = useCallback5(
31114
31365
  async (id) => {
31366
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31115
31367
  if (memoryStore) return memoryStore.delete(id);
31116
31368
  const existing = await getVaultMemoryOp(vaultCtx, id);
31117
31369
  const result = await deleteVaultMemoryOp(vaultCtx, id);
@@ -31125,7 +31377,7 @@ function useChatStorage(options) {
31125
31377
  const chunkVectorCacheRef = useRef4(createChunkVectorCache());
31126
31378
  const clientToolEmbeddingsCacheRef = useRef4(/* @__PURE__ */ new Map());
31127
31379
  useEffect4(() => {
31128
- if (!getToken || memoryStore) return;
31380
+ if (!getToken || memoryStore !== void 0) return;
31129
31381
  void (async () => {
31130
31382
  try {
31131
31383
  await preEmbedVaultMemories(vaultCtx, vaultEmbeddingOptions, vaultEmbeddingCache);
@@ -31141,6 +31393,7 @@ function useChatStorage(options) {
31141
31393
  }, [vaultEmbeddingCache]);
31142
31394
  const createMemoryVaultSearchTool2 = useCallback5(
31143
31395
  (searchOptions) => {
31396
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31144
31397
  if (!getToken && !memoryStore) {
31145
31398
  throw new Error("getToken is required for memory vault search tool");
31146
31399
  }
@@ -31156,6 +31409,7 @@ function useChatStorage(options) {
31156
31409
  );
31157
31410
  const createRecallTool2 = useCallback5(
31158
31411
  (toolOptions, callbacks) => {
31412
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
31159
31413
  if (!getToken && !memoryStore) {
31160
31414
  throw new Error("getToken is required for recall tool");
31161
31415
  }
@@ -31195,6 +31449,7 @@ function useChatStorage(options) {
31195
31449
  );
31196
31450
  const searchVaultMemoriesFn = useCallback5(
31197
31451
  async (query, searchOptions) => {
31452
+ if (memoryStore === null) return [];
31198
31453
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
31199
31454
  if (memoryStore) return (await memoryStore.factSource.search(query, searchOptions)).results;
31200
31455
  if (!getToken) return [];
@@ -31210,7 +31465,7 @@ function useChatStorage(options) {
31210
31465
  );
31211
31466
  const recallFn = useCallback5(
31212
31467
  async (query, options2) => {
31213
- if (!getToken && !memoryStore) {
31468
+ if (memoryStore === null || !getToken && !memoryStore) {
31214
31469
  return {
31215
31470
  memories: [],
31216
31471
  usedBudget: options2?.budget ?? "low",