@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.
@@ -11131,6 +11131,11 @@ import { Q as Q5 } from "@nozbe/watermelondb";
11131
11131
 
11132
11132
  // src/lib/db/entities/operations.ts
11133
11133
  import { Q as Q4 } from "@nozbe/watermelondb";
11134
+ async function assertCanWrite(ctx) {
11135
+ if (ctx.canWrite && !await ctx.canWrite()) {
11136
+ throw new Error("Memory source is no longer eligible");
11137
+ }
11138
+ }
11134
11139
  function entityToStored(e) {
11135
11140
  return {
11136
11141
  uniqueId: e.id,
@@ -11271,6 +11276,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
11271
11276
  let skipped = false;
11272
11277
  let entities = [];
11273
11278
  await ctx.database.write(async () => {
11279
+ await assertCanWrite(ctx);
11274
11280
  const {
11275
11281
  entities: byName,
11276
11282
  operations: entityOps,
@@ -11346,6 +11352,7 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
11346
11352
  let skipped = false;
11347
11353
  let entities = [];
11348
11354
  await ctx.database.write(async () => {
11355
+ await assertCanWrite(ctx);
11349
11356
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
11350
11357
  if (options.guarded && autoLinkBlocked(vaultRow)) {
11351
11358
  skipped = true;
@@ -11406,6 +11413,7 @@ async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
11406
11413
  const links = await ctx.memoryEntityCollection.query(Q4.where("memory_id", Q4.oneOf(Array.from(memoryIds)))).fetch();
11407
11414
  if (links.length === 0) return;
11408
11415
  await ctx.database.write(async () => {
11416
+ await assertCanWrite(ctx);
11409
11417
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
11410
11418
  });
11411
11419
  }
@@ -11414,6 +11422,7 @@ async function unlinkAllMemoryEntitiesForUserOp(ctx, userId) {
11414
11422
  const links = await ctx.memoryEntityCollection.query(Q4.where("user_id", userId)).fetch();
11415
11423
  if (links.length === 0) return;
11416
11424
  await ctx.database.write(async () => {
11425
+ await assertCanWrite(ctx);
11417
11426
  await ctx.database.batch(...links.map((l) => l.prepareDestroyPermanently()));
11418
11427
  });
11419
11428
  }
@@ -11505,6 +11514,17 @@ async function decryptVaultMemoryFields(memory, address, signMessage, embeddedWa
11505
11514
  };
11506
11515
  }
11507
11516
 
11517
+ // src/lib/db/memoryVault/sourceChunkIds.ts
11518
+ var MAX_SOURCE_CHUNK_IDS_BYTES = 16 << 10;
11519
+ var utf8 = new TextEncoder();
11520
+ function serializeSourceChunkIds(ids) {
11521
+ const sizes = ids.map((id) => utf8.encode(JSON.stringify(id)).length + 1);
11522
+ let total = sizes.reduce((sum, size) => sum + size, 1);
11523
+ let start = 0;
11524
+ while (total > MAX_SOURCE_CHUNK_IDS_BYTES && start < ids.length) total -= sizes[start++];
11525
+ return JSON.stringify(ids.slice(start));
11526
+ }
11527
+
11508
11528
  // src/lib/db/memoryVault/types.ts
11509
11529
  var MEMORY_KINDS = [
11510
11530
  "display_name",
@@ -11577,6 +11597,11 @@ function visibilityConditions(requested) {
11577
11597
  function isKinded(record2) {
11578
11598
  return record2.kind !== null && record2.kind !== void 0;
11579
11599
  }
11600
+ async function assertCanWrite2(ctx) {
11601
+ if (ctx.canWrite && !await ctx.canWrite()) {
11602
+ throw new Error("Memory source is no longer eligible");
11603
+ }
11604
+ }
11580
11605
  function assertValidKindLevel(kind, level) {
11581
11606
  const hasKind = kind !== null && kind !== void 0;
11582
11607
  if (hasKind && !MEMORY_KINDS.includes(kind)) {
@@ -11652,15 +11677,15 @@ async function mapInBatches(items, fn) {
11652
11677
  return results;
11653
11678
  }
11654
11679
  function vaultMemoryToStoredRaw(memory) {
11655
- let sourceChunkIds = null;
11680
+ let sourceChunkIds2 = null;
11656
11681
  if (memory.sourceChunkIds) {
11657
11682
  try {
11658
11683
  const parsed = JSON.parse(memory.sourceChunkIds);
11659
11684
  if (Array.isArray(parsed)) {
11660
- sourceChunkIds = parsed.filter((s) => typeof s === "string");
11685
+ sourceChunkIds2 = parsed.filter((s) => typeof s === "string");
11661
11686
  }
11662
11687
  } catch {
11663
- sourceChunkIds = null;
11688
+ sourceChunkIds2 = null;
11664
11689
  }
11665
11690
  }
11666
11691
  return {
@@ -11674,7 +11699,7 @@ function vaultMemoryToStoredRaw(memory) {
11674
11699
  userId: memory.userId ?? null,
11675
11700
  embedding: memory.embedding ?? null,
11676
11701
  embeddingModel: memory.embeddingModel ?? null,
11677
- sourceChunkIds,
11702
+ sourceChunkIds: sourceChunkIds2,
11678
11703
  proofCount: memory.proofCount ?? null,
11679
11704
  source: memory.source ?? null,
11680
11705
  media: parseMedia(memory.media),
@@ -11723,7 +11748,7 @@ function populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedK
11723
11748
  record2._setRaw("embedding_model", opts.embeddingModel ?? null);
11724
11749
  }
11725
11750
  if (opts.sourceChunkIds !== void 0) {
11726
- record2._setRaw("source_chunk_ids", JSON.stringify(opts.sourceChunkIds));
11751
+ record2._setRaw("source_chunk_ids", serializeSourceChunkIds(opts.sourceChunkIds));
11727
11752
  }
11728
11753
  record2._setRaw("proof_count", opts.proofCount ?? 1);
11729
11754
  record2._setRaw("source", opts.source ?? "manual");
@@ -11757,8 +11782,7 @@ async function createVaultMemoryOp(ctx, opts) {
11757
11782
  ctx.embeddedWalletSigner
11758
11783
  ) : opts.content;
11759
11784
  const created = await ctx.database.write(async () => {
11760
- if (ctx.canWrite && !await ctx.canWrite())
11761
- throw new Error("Memory source is no longer eligible");
11785
+ await assertCanWrite2(ctx);
11762
11786
  return ctx.vaultMemoryCollection.create(
11763
11787
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContent, encryptedKindValue)
11764
11788
  );
@@ -11777,8 +11801,7 @@ async function createSupersedingMemoryOp(ctx, opts, targetId) {
11777
11801
  ) : opts.content;
11778
11802
  let createdRecord = null;
11779
11803
  await ctx.database.write(async () => {
11780
- if (ctx.canWrite && !await ctx.canWrite())
11781
- throw new Error("Memory source is no longer eligible");
11804
+ await assertCanWrite2(ctx);
11782
11805
  let target;
11783
11806
  try {
11784
11807
  target = await ctx.vaultMemoryCollection.find(targetId);
@@ -11870,8 +11893,7 @@ async function createVaultMemoriesBatchOp(ctx, optionsArray) {
11870
11893
  })
11871
11894
  );
11872
11895
  const created = await ctx.database.write(async () => {
11873
- if (ctx.canWrite && !await ctx.canWrite())
11874
- throw new Error("Memory source is no longer eligible");
11896
+ await assertCanWrite2(ctx);
11875
11897
  const prepared = optionsArray.map(
11876
11898
  (opts, i) => ctx.vaultMemoryCollection.prepareCreate(
11877
11899
  (record2) => populateNewVaultMemory(record2, ctx, opts, encryptedContents[i], encryptedKindValues[i])
@@ -11901,16 +11923,16 @@ async function getVaultMemoryOp(ctx, id) {
11901
11923
  }
11902
11924
  }
11903
11925
  function vaultMemoryRawToStoredRaw(raw) {
11904
- let sourceChunkIds = null;
11926
+ let sourceChunkIds2 = null;
11905
11927
  const rawChunks = raw.source_chunk_ids;
11906
11928
  if (typeof rawChunks === "string" && rawChunks) {
11907
11929
  try {
11908
11930
  const parsed = JSON.parse(rawChunks);
11909
11931
  if (Array.isArray(parsed)) {
11910
- sourceChunkIds = parsed.filter((s) => typeof s === "string");
11932
+ sourceChunkIds2 = parsed.filter((s) => typeof s === "string");
11911
11933
  }
11912
11934
  } catch {
11913
- sourceChunkIds = null;
11935
+ sourceChunkIds2 = null;
11914
11936
  }
11915
11937
  }
11916
11938
  return {
@@ -11924,7 +11946,7 @@ function vaultMemoryRawToStoredRaw(raw) {
11924
11946
  userId: raw.user_id ?? null,
11925
11947
  embedding: raw.embedding ?? null,
11926
11948
  embeddingModel: raw.embedding_model ?? null,
11927
- sourceChunkIds,
11949
+ sourceChunkIds: sourceChunkIds2,
11928
11950
  proofCount: raw.proof_count ?? null,
11929
11951
  source: raw.source ?? null,
11930
11952
  eventTimeStart: raw.event_time_start ?? null,
@@ -12176,8 +12198,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
12176
12198
  const record2 = probe;
12177
12199
  const originalUpdatedAt = record2.updatedAt.getTime();
12178
12200
  await ctx.database.write(async () => {
12179
- if (ctx.canWrite && !await ctx.canWrite())
12180
- throw new Error("Memory source is no longer eligible");
12201
+ await assertCanWrite2(ctx);
12181
12202
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
12182
12203
  stale = true;
12183
12204
  return;
@@ -12221,7 +12242,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
12221
12242
  if (opts.sourceChunkIds !== void 0) {
12222
12243
  r._setRaw(
12223
12244
  "source_chunk_ids",
12224
- JSON.stringify(
12245
+ serializeSourceChunkIds(
12225
12246
  opts.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observedSources, ...opts.sourceChunkIds])] : opts.sourceChunkIds
12226
12247
  )
12227
12248
  );
@@ -12288,6 +12309,7 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
12288
12309
  let stale = false;
12289
12310
  const originalUpdatedAt = record2.updatedAt.getTime();
12290
12311
  await ctx.database.write(async (writer) => {
12312
+ await assertCanWrite2(ctx);
12291
12313
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12292
12314
  stale = true;
12293
12315
  return;
@@ -12329,6 +12351,7 @@ async function clearMemoryTopicsOverrideOp(ctx, memoryId, options) {
12329
12351
  const originalUpdatedAt = record2.updatedAt.getTime();
12330
12352
  let cleared = false;
12331
12353
  await ctx.database.write(async () => {
12354
+ await assertCanWrite2(ctx);
12332
12355
  if (options?.unlessTopicsRecorded && parseTopics(record2.topics) !== null) return;
12333
12356
  cleared = true;
12334
12357
  await record2.update((r) => {
@@ -12352,6 +12375,7 @@ async function setMemoryVisibilityOp(ctx, id, opts) {
12352
12375
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return null;
12353
12376
  let stale = false;
12354
12377
  await ctx.database.write(async () => {
12378
+ await assertCanWrite2(ctx);
12355
12379
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12356
12380
  stale = true;
12357
12381
  return;
@@ -12380,6 +12404,7 @@ async function deleteVaultMemoryOp(ctx, id) {
12380
12404
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
12381
12405
  let stale = false;
12382
12406
  await ctx.database.write(async () => {
12407
+ await assertCanWrite2(ctx);
12383
12408
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12384
12409
  stale = true;
12385
12410
  return;
@@ -12417,8 +12442,7 @@ async function supersedeVaultMemoryOp(ctx, id, supersededById) {
12417
12442
  }
12418
12443
  let stale = false;
12419
12444
  await ctx.database.write(async () => {
12420
- if (ctx.canWrite && !await ctx.canWrite())
12421
- throw new Error("Memory source is no longer eligible");
12445
+ await assertCanWrite2(ctx);
12422
12446
  if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2)) {
12423
12447
  stale = true;
12424
12448
  return;
@@ -12580,6 +12604,7 @@ async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt, version = T
12580
12604
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
12581
12605
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
12582
12606
  await ctx.database.write(async () => {
12607
+ await assertCanWrite2(ctx);
12583
12608
  const records = [];
12584
12609
  for (const id of chunkIds) {
12585
12610
  try {
@@ -12613,6 +12638,7 @@ async function relinkMemoryTopicsOp(ctx, memoryIds) {
12613
12638
  for (const id of Array.from(new Set(memoryIds))) {
12614
12639
  try {
12615
12640
  await ctx.database.write(async (writer) => {
12641
+ await assertCanWrite2(ctx);
12616
12642
  let record2;
12617
12643
  try {
12618
12644
  record2 = await ctx.vaultMemoryCollection.find(id);
@@ -12643,6 +12669,7 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
12643
12669
  for (let i = 0; i < uniqueIds.length; i += CHUNK) {
12644
12670
  const chunkIds = uniqueIds.slice(i, i + CHUNK);
12645
12671
  await ctx.database.write(async () => {
12672
+ await assertCanWrite2(ctx);
12646
12673
  const writes = [];
12647
12674
  for (const id of chunkIds) {
12648
12675
  let record2;
@@ -12675,6 +12702,7 @@ async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
12675
12702
  const records = await ctx.vaultMemoryCollection.query(Q5.where("user_id", userId), Q5.where("is_deleted", false)).fetch();
12676
12703
  if (records.length === 0) return 0;
12677
12704
  await ctx.database.write(async () => {
12705
+ await assertCanWrite2(ctx);
12678
12706
  const prepared = records.map(
12679
12707
  (record2) => record2.prepareUpdate((r) => {
12680
12708
  r._setRaw("is_deleted", true);
@@ -12729,6 +12757,7 @@ async function archiveVaultMemoryOp(ctx, id, opts) {
12729
12757
  let stale = false;
12730
12758
  const archivedAtValue = opts?.now ?? Date.now();
12731
12759
  await ctx.database.write(async () => {
12760
+ await assertCanWrite2(ctx);
12732
12761
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || record2.archivedAt !== null) {
12733
12762
  stale = true;
12734
12763
  return;
@@ -12752,6 +12781,7 @@ async function restoreVaultMemoryOp(ctx, id) {
12752
12781
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return false;
12753
12782
  let stale = false;
12754
12783
  await ctx.database.write(async () => {
12784
+ await assertCanWrite2(ctx);
12755
12785
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) {
12756
12786
  stale = true;
12757
12787
  return;
@@ -12772,6 +12802,7 @@ async function hardDeleteDecayedOp(ctx, id, opts) {
12772
12802
  const now = opts.now ?? Date.now();
12773
12803
  let stale = false;
12774
12804
  await ctx.database.write(async () => {
12805
+ await assertCanWrite2(ctx);
12775
12806
  const archivedAt = record2.archivedAt;
12776
12807
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2) || archivedAt === null || now - archivedAt <= opts.hardDeleteWindowMs) {
12777
12808
  stale = true;
@@ -12811,6 +12842,7 @@ async function updateVaultMemoryEmbeddingOp(ctx, id, embedding2, embeddingModel,
12811
12842
  }
12812
12843
  let written = false;
12813
12844
  await ctx.database.write(async () => {
12845
+ await assertCanWrite2(ctx);
12814
12846
  if (record2.isDeleted || !isOwnedByCtxUser(ctx, record2)) return;
12815
12847
  const originalUpdatedAt = record2.updatedAt.getTime();
12816
12848
  if (originalUpdatedAt !== readUpdatedAt || record2._getRaw("content") !== readStoredContent)
@@ -16323,6 +16355,13 @@ function restoreCandidates(candidates, redactor, ownNames) {
16323
16355
  ).map((c) => c.candidate);
16324
16356
  }
16325
16357
  async function extractAndRetain(messages, retainCtx, options) {
16358
+ const { memoryStore, signal } = options;
16359
+ if (memoryStore === null) throw new Error("Memory store is not ready");
16360
+ if (memoryStore && options.folderId !== void 0 && options.folderId !== null)
16361
+ throw new Error("Remote memory extraction does not support folders");
16362
+ if (memoryStore && options.extract.piiRedaction && !memoryStore.masksEmbeddingInput)
16363
+ throw new Error("PII-redacted extraction requires a memory store that masks embedding inputs");
16364
+ signal?.throwIfAborted();
16326
16365
  const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
16327
16366
  const resolvedConsolidatePii = options.consolidateOptions?.piiRedaction ?? options.extract.piiRedaction;
16328
16367
  const consolidateOptions = options.consolidateOptions !== void 0 ? {
@@ -16356,6 +16395,7 @@ async function extractAndRetain(messages, retainCtx, options) {
16356
16395
  }
16357
16396
  });
16358
16397
  const extractMs = Date.now() - tExtract;
16398
+ signal?.throwIfAborted();
16359
16399
  const filtered = candidates.filter((c) => c.confidence >= minConfidence);
16360
16400
  const log = getLogger();
16361
16401
  const screened = screenCandidatesForInjection(filtered);
@@ -16403,16 +16443,33 @@ async function extractAndRetain(messages, retainCtx, options) {
16403
16443
  const results = [];
16404
16444
  const quarantinedInfo = [];
16405
16445
  let failedWrites = 0;
16446
+ let auditRows;
16406
16447
  const tRetain = Date.now();
16407
16448
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
16408
16449
  try {
16450
+ signal?.throwIfAborted();
16409
16451
  if (isQuarantined) {
16410
- const existingId = await findQuarantinedDuplicateOp(
16411
- retainCtx.vaultCtx,
16412
- candidate.content,
16413
- candidate.sourceMessageIds,
16414
- { scope: options.scope ?? "private", folderId: options.folderId ?? null }
16415
- ).catch(() => null);
16452
+ let existingId;
16453
+ if (memoryStore) {
16454
+ auditRows ?? (auditRows = memoryStore.list({
16455
+ scopes: [options.scope ?? "private"],
16456
+ includeArchived: true,
16457
+ includeQuarantined: true,
16458
+ includeSuperseded: true
16459
+ }));
16460
+ const sourceKey = [...new Set(candidate.sourceMessageIds)].sort().join("\0");
16461
+ const normalize = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
16462
+ existingId = (await auditRows).find(
16463
+ (row) => row.trustTier === "quarantined" && row.scope === (options.scope ?? "private") && [...new Set(row.sourceChunkIds ?? [])].sort().join("\0") === sourceKey && normalize(row.content) === normalize(candidate.content)
16464
+ )?.uniqueId ?? null;
16465
+ } else {
16466
+ existingId = await findQuarantinedDuplicateOp(
16467
+ retainCtx.vaultCtx,
16468
+ candidate.content,
16469
+ candidate.sourceMessageIds,
16470
+ { scope: options.scope ?? "private", folderId: options.folderId ?? null }
16471
+ ).catch(() => null);
16472
+ }
16416
16473
  if (existingId) {
16417
16474
  quarantinedInfo.push({
16418
16475
  candidate,
@@ -16423,18 +16480,26 @@ async function extractAndRetain(messages, retainCtx, options) {
16423
16480
  continue;
16424
16481
  }
16425
16482
  }
16426
- const result = await retain(candidate.content, retainCtx, {
16483
+ const retainOptions = {
16427
16484
  source: "auto-extracted",
16428
16485
  sourceChunkIds: candidate.sourceMessageIds,
16429
16486
  respectTombstones: true,
16430
16487
  ...options.scope !== void 0 && { scope: options.scope },
16431
- ...options.folderId !== void 0 && { folderId: options.folderId },
16432
16488
  ...consolidateOptions !== void 0 && { consolidateOptions },
16433
16489
  ...candidate.eventTime !== null && { eventTime: candidate.eventTime },
16434
16490
  factType: candidate.type,
16435
16491
  ...isQuarantined && { trustTier: "quarantined", enableAutoMerge: false }
16492
+ };
16493
+ const result = memoryStore ? await memoryStore.retain(candidate.content, {
16494
+ ...retainOptions,
16495
+ canWrite: retainCtx.vaultCtx.canWrite,
16496
+ signal
16497
+ }) : await retain(candidate.content, retainCtx, {
16498
+ ...retainOptions,
16499
+ ...options.folderId !== void 0 && { folderId: options.folderId }
16436
16500
  });
16437
16501
  if (isQuarantined) {
16502
+ auditRows = void 0;
16438
16503
  const info = {
16439
16504
  candidate,
16440
16505
  memoryId: result.memoryId,
@@ -16453,9 +16518,16 @@ async function extractAndRetain(messages, retainCtx, options) {
16453
16518
  }
16454
16519
  succeededCandidates.push(candidate);
16455
16520
  results.push(result);
16456
- if (result.action !== "suppressed" && options.entityCtx && candidate.entities.length > 0) {
16521
+ if (result.action !== "suppressed" && candidate.entities.length > 0) {
16457
16522
  try {
16458
- if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
16523
+ signal?.throwIfAborted();
16524
+ if (memoryStore) {
16525
+ await memoryStore.addTopics(result.memoryId, candidate.entities, {
16526
+ unlessUserManaged: true,
16527
+ canWrite: retainCtx.vaultCtx.canWrite,
16528
+ signal
16529
+ });
16530
+ } else if (options.entityCtx && !await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
16459
16531
  await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
16460
16532
  unlessTopicsUserManaged: true
16461
16533
  });
@@ -16703,7 +16775,7 @@ function createAutoExtractor(options) {
16703
16775
  };
16704
16776
  const consolidateOptions = options.consolidate ? { ...options.consolidate, ...subPassAuth(options.consolidate.baseUrl) } : void 0;
16705
16777
  const injectionClassifierOptions = options.injectionClassifier ? { ...options.injectionClassifier, ...subPassAuth(options.injectionClassifier.baseUrl) } : void 0;
16706
- if (options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
16778
+ if (!options.memoryStore && options.retainCtx.vaultCtx.entityCtx && !options.entityCtx) {
16707
16779
  getLogger().warn(
16708
16780
  "[memory/extract] retainCtx.vaultCtx.entityCtx is set but extractor was created without `entityCtx` \u2014 W5 graph lane will receive no writes"
16709
16781
  );
@@ -16748,6 +16820,8 @@ function createAutoExtractor(options) {
16748
16820
  try {
16749
16821
  const { candidates, results, failedCount, outcome: outcome2, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
16750
16822
  extract,
16823
+ memoryStore: options.memoryStore,
16824
+ signal: options.signal,
16751
16825
  ...options.minConfidence !== void 0 && { minConfidence: options.minConfidence },
16752
16826
  ...options.entityCtx !== void 0 && { entityCtx: options.entityCtx },
16753
16827
  ...options.scope !== void 0 && { scope: options.scope },
@@ -18834,6 +18908,7 @@ function createLocalMemoryStore(options) {
18834
18908
  database,
18835
18909
  entityCollection: database.get("entity"),
18836
18910
  memoryEntityCollection: database.get("memory_entity"),
18911
+ canWrite: options.canWrite,
18837
18912
  ...userId !== void 0 && { userId },
18838
18913
  ...options.allowUnscopedRows !== void 0 && {
18839
18914
  allowUnscopedRows: options.allowUnscopedRows
@@ -18845,10 +18920,21 @@ function createLocalMemoryStore(options) {
18845
18920
  walletAddress: options.walletAddress,
18846
18921
  signMessage: options.signMessage,
18847
18922
  embeddedWalletSigner: options.embeddedWalletSigner,
18923
+ canWrite: options.canWrite,
18848
18924
  ...userId !== void 0 && { userId },
18849
18925
  ...options.singleTenant !== void 0 && { singleTenant: options.singleTenant },
18850
18926
  entityCtx
18851
18927
  };
18928
+ const assertWrite = async (writeOptions) => {
18929
+ writeOptions?.signal?.throwIfAborted();
18930
+ if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
18931
+ throw new Error("Memory source is no longer eligible");
18932
+ }
18933
+ if (options.canWrite && !await options.canWrite()) {
18934
+ throw new Error("Memory source is no longer eligible");
18935
+ }
18936
+ writeOptions?.signal?.throwIfAborted();
18937
+ };
18852
18938
  const vaultCache = options.vaultCache ?? createVaultEmbeddingCache();
18853
18939
  const ownedBy = userId !== void 0 ? [Q8.where("user_id", userId)] : [];
18854
18940
  const linksOwnedBy = userId === void 0 ? [] : options.allowUnscopedRows ? [Q8.or(Q8.where("user_id", userId), Q8.where("user_id", null))] : [Q8.where("user_id", userId)];
@@ -18960,9 +19046,25 @@ function createLocalMemoryStore(options) {
18960
19046
  archive: (id) => archiveVaultMemoryOp(vaultCtx, id),
18961
19047
  restore: (id) => restoreVaultMemoryOp(vaultCtx, id),
18962
19048
  setTopics: (memoryId, topics) => setMemoryEntitiesOp(vaultCtx, memoryId, topics),
18963
- addTopics: (memoryId, topics) => database.write(async (writer) => {
19049
+ addTopics: (memoryId, topics, writeOptions) => database.write(async (writer) => {
19050
+ await assertWrite(writeOptions);
18964
19051
  const owned = await vaultCtx.vaultMemoryCollection.query(Q8.where("id", memoryId), Q8.where("is_deleted", false), ...ownedBy).fetchCount();
18965
- return owned > 0 ? writer.callWriter(() => linkMemoryEntitiesOp(entityCtx, memoryId, topics)) : [];
19052
+ return owned > 0 ? writer.callWriter(
19053
+ () => linkMemoryEntitiesOp(
19054
+ {
19055
+ ...entityCtx,
19056
+ canWrite: async () => {
19057
+ await assertWrite(writeOptions);
19058
+ return true;
19059
+ }
19060
+ },
19061
+ memoryId,
19062
+ topics,
19063
+ {
19064
+ unlessTopicsUserManaged: writeOptions?.unlessUserManaged
19065
+ }
19066
+ )
19067
+ ) : [];
18966
19068
  }),
18967
19069
  setVisibility: (id, visibility, visibilityOptions) => setMemoryVisibilityOp(vaultCtx, id, { visibility, ...visibilityOptions }),
18968
19070
  recall: (query, recallOptions) => recall(
@@ -18972,7 +19074,17 @@ function createLocalMemoryStore(options) {
18972
19074
  ),
18973
19075
  retain: (content, retainOptions) => retain(
18974
19076
  content,
18975
- { vaultCtx, embeddingOptions: options.embeddingOptions, vaultCache },
19077
+ {
19078
+ vaultCtx: {
19079
+ ...vaultCtx,
19080
+ canWrite: async () => {
19081
+ await assertWrite(retainOptions);
19082
+ return true;
19083
+ }
19084
+ },
19085
+ embeddingOptions: options.embeddingOptions,
19086
+ vaultCache
19087
+ },
18976
19088
  retainOptions
18977
19089
  ),
18978
19090
  factSource: {
@@ -19076,11 +19188,15 @@ function memoryCipher(canonical) {
19076
19188
  };
19077
19189
  }
19078
19190
  async function reencryptMemoryField(value, canonical, fallbacks = []) {
19079
- const { encrypt } = memoryCipher(canonical);
19080
- if (!value.startsWith("enc:")) return encrypt(value);
19191
+ return memoryCipher(canonical).encrypt(
19192
+ await decryptMemoryField(value, [canonical, ...fallbacks])
19193
+ );
19194
+ }
19195
+ async function decryptMemoryField(value, rings) {
19196
+ if (!value.startsWith("enc:")) return value;
19081
19197
  if (!isEncrypted(value))
19082
19198
  throw new MemoryKeyError("Field has an encryption prefix but an invalid payload");
19083
- return encrypt(await decryptWith(value, [canonical, ...fallbacks]));
19199
+ return decryptWith(value, rings);
19084
19200
  }
19085
19201
 
19086
19202
  // src/lib/memory/store/remotePersistence.ts
@@ -19618,7 +19734,14 @@ function stored(row) {
19618
19734
  function createRemoteMemoryPipeline(options) {
19619
19735
  const { persistence, embeddingOptions } = options;
19620
19736
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
19621
- const operation = (mode, queryOptions = embeddingOptions) => {
19737
+ const operation = (mode, queryOptions = embeddingOptions, writeOptions) => {
19738
+ const signal = writeOptions?.signal;
19739
+ const assertWrite = async () => {
19740
+ signal?.throwIfAborted();
19741
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
19742
+ throw new Error("Memory source is no longer eligible");
19743
+ signal?.throwIfAborted();
19744
+ };
19622
19745
  const rejectUndecryptable = (failed) => {
19623
19746
  if (!failed.length) return;
19624
19747
  if (mode === "retain")
@@ -19636,7 +19759,7 @@ function createRemoteMemoryPipeline(options) {
19636
19759
  return items.map((item) => stored(item.memory));
19637
19760
  };
19638
19761
  const get = async (id) => {
19639
- const snapshot = snapshots.get(id) ?? await persistence.get(id);
19762
+ const snapshot = snapshots.get(id) ?? await (signal ? persistence.get(id, signal) : persistence.get(id));
19640
19763
  if (!snapshot || snapshot.memory.is_deleted || snapshot.memory.superseded_by) return null;
19641
19764
  snapshots.set(id, snapshot);
19642
19765
  return stored(snapshot.memory);
@@ -19686,7 +19809,7 @@ function createRemoteMemoryPipeline(options) {
19686
19809
  if (forced.size < 100 && temporal[i] !== void 0) forced.add(temporal[i]);
19687
19810
  }
19688
19811
  const forceIds = [...forced];
19689
- const windowOptions = (i, signal) => ({
19812
+ const windowOptions = (i, signal2) => ({
19690
19813
  limit: Math.min(100, Math.max((search.limit ?? 8) * 3, 30)),
19691
19814
  force_ids: i === 0 ? forceIds : [],
19692
19815
  ...search.scopes && { scopes: search.scopes },
@@ -19694,13 +19817,15 @@ function createRemoteMemoryPipeline(options) {
19694
19817
  ...search.memoryIds !== void 0 && { memory_ids: search.memoryIds },
19695
19818
  include_archived: search.includeArchived,
19696
19819
  embedding_model: model,
19697
- ...signal && { signal }
19820
+ ...signal2 && { signal: signal2 }
19698
19821
  });
19699
19822
  const vectors = [queryEmbedding, ...facetEmbeddings];
19700
19823
  let windows;
19701
19824
  if (mode === "retain") {
19702
19825
  windows = await Promise.all(
19703
- vectors.map((embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i)))
19826
+ vectors.map(
19827
+ (embedding2, i) => persistence.candidateSet(embedding2, windowOptions(i, signal))
19828
+ )
19704
19829
  );
19705
19830
  } else {
19706
19831
  const deadline = new AbortController();
@@ -19773,7 +19898,7 @@ function createRemoteMemoryPipeline(options) {
19773
19898
  embedding: storedVector(JSON.parse(input.embedding)),
19774
19899
  embedding_model: input.embeddingModel
19775
19900
  },
19776
- source_chunk_ids: JSON.stringify(input.sourceChunkIds ?? []),
19901
+ source_chunk_ids: serializeSourceChunkIds(input.sourceChunkIds ?? []),
19777
19902
  proof_count: input.proofCount ?? 1,
19778
19903
  source: input.source ?? "manual",
19779
19904
  ...input.eventTime && {
@@ -19788,12 +19913,15 @@ function createRemoteMemoryPipeline(options) {
19788
19913
  twin_opt_in: false
19789
19914
  };
19790
19915
  };
19791
- const write = async (memory, snapshot) => remember([await persistence.put(memory, snapshot)])[0];
19916
+ const write = async (memory, snapshot) => {
19917
+ await assertWrite();
19918
+ return remember([await persistence.put(memory, snapshot, signal)])[0];
19919
+ };
19792
19920
  const port = {
19793
19921
  prepare,
19794
19922
  get,
19795
19923
  getFresh: async (id) => {
19796
- const current = await persistence.get(id);
19924
+ const current = await (signal ? persistence.get(id, signal) : persistence.get(id));
19797
19925
  return current && !current.memory.is_deleted && !current.memory.superseded_by ? stored(current.memory) : null;
19798
19926
  },
19799
19927
  tombstones: async (embedding2, embeddingModel, scope, folderId) => {
@@ -19804,7 +19932,8 @@ function createRemoteMemoryPipeline(options) {
19804
19932
  embedding_model: embeddingModel,
19805
19933
  strict_model: true,
19806
19934
  include_deleted: true,
19807
- deleted_only: true
19935
+ deleted_only: true,
19936
+ signal
19808
19937
  });
19809
19938
  rejectUndecryptable(window2.failed);
19810
19939
  return remember(window2.items);
@@ -19832,7 +19961,7 @@ function createRemoteMemoryPipeline(options) {
19832
19961
  const observed = sourceIds(memory.source_chunk_ids);
19833
19962
  const replay = !!patch.observationSourceIds?.length && patch.observationSourceIds.every((id2) => observed.includes(id2));
19834
19963
  if (patch.sourceChunkIds !== void 0)
19835
- memory.source_chunk_ids = JSON.stringify(
19964
+ memory.source_chunk_ids = serializeSourceChunkIds(
19836
19965
  patch.observationSourceIds?.length ? [.../* @__PURE__ */ new Set([...observed, ...patch.sourceChunkIds])] : patch.sourceChunkIds
19837
19966
  );
19838
19967
  if (!replay) {
@@ -19860,14 +19989,18 @@ function createRemoteMemoryPipeline(options) {
19860
19989
  if (!target || !newer || id === successor) return false;
19861
19990
  const old = snapshots.get(id);
19862
19991
  const next = snapshots.get(successor);
19992
+ await assertWrite();
19863
19993
  remember(
19864
- await persistence.putMany([
19865
- {
19866
- memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
19867
- expectedVersion: old
19868
- },
19869
- { memory: next.memory, expectedVersion: next }
19870
- ])
19994
+ await persistence.putMany(
19995
+ [
19996
+ {
19997
+ memory: { ...old.memory, superseded_by: successor, superseded_at: Date.now() },
19998
+ expectedVersion: old
19999
+ },
20000
+ { memory: next.memory, expectedVersion: next }
20001
+ ],
20002
+ signal
20003
+ )
19871
20004
  );
19872
20005
  return true;
19873
20006
  },
@@ -19876,18 +20009,22 @@ function createRemoteMemoryPipeline(options) {
19876
20009
  if (!target) return { created: null, retired: false };
19877
20010
  const snapshot = snapshots.get(targetId);
19878
20011
  const successor = createRow(input);
20012
+ await assertWrite();
19879
20013
  const rows = remember(
19880
- await persistence.putMany([
19881
- { memory: successor, expectedVersion: 0 },
19882
- {
19883
- memory: {
19884
- ...snapshot.memory,
19885
- superseded_by: successor.memory_id,
19886
- superseded_at: Date.now()
19887
- },
19888
- expectedVersion: snapshot
19889
- }
19890
- ])
20014
+ await persistence.putMany(
20015
+ [
20016
+ { memory: successor, expectedVersion: 0 },
20017
+ {
20018
+ memory: {
20019
+ ...snapshot.memory,
20020
+ superseded_by: successor.memory_id,
20021
+ superseded_at: Date.now()
20022
+ },
20023
+ expectedVersion: snapshot
20024
+ }
20025
+ ],
20026
+ signal
20027
+ )
19891
20028
  );
19892
20029
  return { created: rows[0], retired: true };
19893
20030
  }
@@ -19922,7 +20059,7 @@ function createRemoteMemoryPipeline(options) {
19922
20059
  factSource,
19923
20060
  recall: async (query, recallOptions = {}) => recall(query, { embeddingOptions, factSource }, { ...recallOptions, types: ["fact"] }),
19924
20061
  retain: async (content, retainOptions) => {
19925
- const { port } = operation("retain");
20062
+ const { port } = operation("retain", embeddingOptions, retainOptions);
19926
20063
  return retainWithPersistence(
19927
20064
  content,
19928
20065
  {
@@ -19939,6 +20076,8 @@ function createRemoteMemoryPipeline(options) {
19939
20076
 
19940
20077
  // src/lib/memory/store/remoteMigration.ts
19941
20078
  var IMPORT_BATCH = 50;
20079
+ var EMBED_BATCH = 100;
20080
+ var LOCAL_VECTOR_DIMS = 4096;
19942
20081
  var VERIFY_BATCH = 1e3;
19943
20082
  var QUERY_IDS = 1e3;
19944
20083
  var QUERY_PAGE = 200;
@@ -19964,7 +20103,7 @@ var SHORT_FIELDS = [
19964
20103
  "visibility",
19965
20104
  "geohash"
19966
20105
  ];
19967
- var utf8 = new TextEncoder();
20106
+ var utf82 = new TextEncoder();
19968
20107
  var METADATA_FIELDS = [
19969
20108
  "scope",
19970
20109
  "kind",
@@ -20015,17 +20154,34 @@ function revision(raw) {
20015
20154
  last_observed_at: num(raw.last_observed_at) ?? null
20016
20155
  };
20017
20156
  }
20018
- function embedding(raw) {
20157
+ function embedding(raw, model) {
20019
20158
  const value = text13(raw.embedding);
20020
20159
  if (!value) return void 0;
20021
20160
  try {
20022
- return storedVector(JSON.parse(value));
20161
+ const vector = JSON.parse(value);
20162
+ const stored2 = text13(raw.embedding_model);
20163
+ if (stored2 === void 0 ? vector.length !== LOCAL_VECTOR_DIMS : stored2 !== model)
20164
+ return void 0;
20165
+ return storedVector(vector);
20023
20166
  } catch {
20024
20167
  return void 0;
20025
20168
  }
20026
20169
  }
20170
+ function stringArray(value) {
20171
+ try {
20172
+ const parsed = JSON.parse(value);
20173
+ return Array.isArray(parsed) && parsed.every((id) => typeof id === "string") ? parsed : void 0;
20174
+ } catch {
20175
+ return void 0;
20176
+ }
20177
+ }
20178
+ function sourceChunkIds(raw) {
20179
+ const value = text13(raw.source_chunk_ids);
20180
+ const ids = value && stringArray(value);
20181
+ return ids ? serializeSourceChunkIds(ids) : value;
20182
+ }
20027
20183
  function bytes(value) {
20028
- return value === void 0 ? 0 : utf8.encode(value).length;
20184
+ return value === void 0 ? 0 : utf82.encode(value).length;
20029
20185
  }
20030
20186
  function fitsNearby(row) {
20031
20187
  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);
@@ -20034,16 +20190,16 @@ function invalid(raw) {
20034
20190
  const id = raw.id;
20035
20191
  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;
20036
20192
  }
20037
- function metadata(raw) {
20038
- const vector = embedding(raw);
20193
+ function metadata(raw, model) {
20194
+ const vector = embedding(raw, model);
20039
20195
  return {
20040
20196
  scope: raw.scope,
20041
20197
  kind: text13(raw.kind),
20042
20198
  level: text13(raw.level),
20043
20199
  folder_id: text13(raw.folder_id),
20044
20200
  is_deleted: bool(raw.is_deleted) ?? false,
20045
- ...vector && { embedding: vector, embedding_model: text13(raw.embedding_model) },
20046
- source_chunk_ids: text13(raw.source_chunk_ids),
20201
+ ...vector && { embedding: vector, embedding_model: model },
20202
+ source_chunk_ids: sourceChunkIds(raw),
20047
20203
  proof_count: num(raw.proof_count),
20048
20204
  source: text13(raw.source),
20049
20205
  event_time_start: num(raw.event_time_start),
@@ -20072,30 +20228,63 @@ function defined(value) {
20072
20228
  Object.entries(value).filter(([, field10]) => field10 !== void 0)
20073
20229
  );
20074
20230
  }
20075
- async function remoteRow(raw, canonical, fallbacks, updatedAt) {
20076
- const content = await reencryptMemoryField(
20077
- typeof raw.content === "string" ? raw.content : "",
20078
- canonical,
20079
- fallbacks
20080
- );
20081
- const kindValue = text13(raw.kind_value);
20082
- const encryptedKindValue = kindValue ? await reencryptMemoryField(kindValue, canonical, fallbacks) : void 0;
20231
+ async function prepareRow(raw, canonical, rings, model, updatedAt) {
20232
+ const deleted = bool(raw.is_deleted) ?? false;
20233
+ const { encrypt } = memoryCipher(canonical);
20234
+ const localKindValue = text13(raw.kind_value);
20235
+ let plaintext;
20236
+ let kindValue;
20237
+ try {
20238
+ plaintext = await decryptMemoryField(typeof raw.content === "string" ? raw.content : "", rings);
20239
+ kindValue = localKindValue && await decryptMemoryField(localKindValue, rings);
20240
+ } catch (error) {
20241
+ if (!deleted || !(error instanceof MemoryKeyError)) throw error;
20242
+ plaintext = void 0;
20243
+ kindValue = void 0;
20244
+ }
20083
20245
  const row = {
20084
- ...defined({ ...metadata(raw), kind_value: encryptedKindValue }),
20246
+ ...defined({
20247
+ ...metadata(raw, model),
20248
+ kind_value: kindValue === void 0 ? void 0 : await encrypt(kindValue)
20249
+ }),
20085
20250
  memory_id: raw.id,
20086
- content,
20251
+ content: await encrypt(plaintext ?? ""),
20087
20252
  scope: raw.scope,
20088
20253
  created_at: raw.created_at,
20089
20254
  updated_at: updatedAt,
20090
- is_deleted: bool(raw.is_deleted) ?? false
20255
+ is_deleted: deleted
20091
20256
  };
20092
- return fitsNearby(row) ? row : null;
20257
+ return { row, plaintext };
20093
20258
  }
20094
- function sameMetadata(local, remote) {
20095
- const mine = metadata(local);
20259
+ async function embedMissing(prepared, options, model, signal) {
20260
+ const needed = prepared.filter((p) => !p.row.embedding && p.plaintext);
20261
+ for (const batch of chunks(needed, EMBED_BATCH)) {
20262
+ if (signal?.aborted) throw aborted(signal);
20263
+ try {
20264
+ const vectors = await generateEmbeddings(
20265
+ batch.map((p) => p.plaintext),
20266
+ { ...options, taskType: "memory_retain_embed" }
20267
+ );
20268
+ if (vectors.length !== batch.length) throw new Error("Incomplete migration embeddings");
20269
+ const stored2 = vectors.map(storedVector);
20270
+ batch.forEach((p, i) => {
20271
+ p.row.embedding = stored2[i];
20272
+ p.row.embedding_model = model;
20273
+ });
20274
+ } catch (error) {
20275
+ getLogger().warn(
20276
+ `[memory/migration] Uploading ${batch.length} row(s) without a vector: embedding failed`,
20277
+ error
20278
+ );
20279
+ }
20280
+ }
20281
+ }
20282
+ function sameMetadata(local, remote, model) {
20283
+ const mine = metadata(local, model);
20096
20284
  return METADATA_FIELDS.every((field10) => {
20097
20285
  const a = mine[field10] ?? null;
20098
20286
  const b = remote[field10] ?? null;
20287
+ if (field10 === "embedding_model" && a === null) return true;
20099
20288
  if (field10 === "visibility") return (a ?? "private") === (b ?? "private");
20100
20289
  if (field10 === "is_deleted" || field10 === "topics_user_managed" || field10 === "twin_opt_in")
20101
20290
  return (a ?? false) === (b ?? false);
@@ -20191,7 +20380,8 @@ async function backfillTopics(ctx) {
20191
20380
  }
20192
20381
  async function migrateMemoriesToRemote(options) {
20193
20382
  const { canonical, signal } = options;
20194
- const fallbacks = options.fallbacks ?? [];
20383
+ const rings = [canonical, ...options.fallbacks ?? []];
20384
+ const model = options.embeddingOptions?.model ?? DEFAULT_API_EMBEDDING_MODEL;
20195
20385
  const request = createNearbyRequest(options);
20196
20386
  const account = await request("/account", {}, signal);
20197
20387
  if (typeof account.key_id === "string" && account.key_id !== canonical.keyId)
@@ -20202,8 +20392,13 @@ async function migrateMemoriesToRemote(options) {
20202
20392
  );
20203
20393
  const active = account.state === "active";
20204
20394
  let claimed = account.key_id === canonical.keyId;
20395
+ if (!active) await createDecaySweeper({ vaultCtx: options.vaultCtx }).runSweep();
20205
20396
  await backfillTopics(options.vaultCtx);
20206
20397
  const skipped = /* @__PURE__ */ new Map();
20398
+ const reembedded = /* @__PURE__ */ new Set();
20399
+ const unembedded = /* @__PURE__ */ new Set();
20400
+ const placeholders = /* @__PURE__ */ new Set();
20401
+ const truncated = /* @__PURE__ */ new Set();
20207
20402
  let imported = 0;
20208
20403
  for (let pass = 0; ; pass++) {
20209
20404
  const rows = await getAllVaultMemoryRowsOp(options.vaultCtx);
@@ -20219,9 +20414,10 @@ async function migrateMemoriesToRemote(options) {
20219
20414
  candidates.map((raw) => raw.id),
20220
20415
  signal
20221
20416
  );
20417
+ const needsVector = (raw, server) => !!options.embeddingOptions && !embedding(raw, model) && server.embedding_model !== model;
20222
20418
  for (const raw of candidates) {
20223
20419
  const server = remote.get(raw.id);
20224
- if (server && server.updated_at === raw.updated_at && !sameMetadata(raw, server))
20420
+ if (server && server.updated_at === raw.updated_at && (!sameMetadata(raw, server, model) || needsVector(raw, server)))
20225
20421
  pending.set(raw.id, raw.updated_at + 1);
20226
20422
  }
20227
20423
  }
@@ -20230,7 +20426,11 @@ async function migrateMemoriesToRemote(options) {
20230
20426
  state,
20231
20427
  total: rows.length,
20232
20428
  imported,
20233
- skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason }))
20429
+ skipped: [...skipped].map(([memoryId, reason]) => ({ memoryId, reason })),
20430
+ reembedded: reembedded.size,
20431
+ unembedded: unembedded.size,
20432
+ placeholderTombstones: placeholders.size,
20433
+ truncatedSourceChunkIds: truncated.size
20234
20434
  });
20235
20435
  if (skipped.size > 0 && !options.allowSkipped) return result("migrating");
20236
20436
  if (!claimed) await importBatch(request, canonical.keyId, [], signal);
@@ -20248,19 +20448,35 @@ async function migrateMemoriesToRemote(options) {
20248
20448
  409,
20249
20449
  "migration_incomplete"
20250
20450
  );
20251
- const upload = [];
20451
+ const prepared = [];
20252
20452
  for (const raw of candidates) {
20253
20453
  const updatedAt = pending.get(raw.id);
20254
20454
  if (updatedAt === void 0) continue;
20255
20455
  try {
20256
- const row = await remoteRow(raw, canonical, fallbacks, updatedAt);
20257
- if (row) upload.push(row);
20258
- else skipped.set(raw.id, "invalid");
20456
+ prepared.push(await prepareRow(raw, canonical, rings, model, updatedAt));
20259
20457
  } catch (error) {
20260
20458
  if (!(error instanceof MemoryKeyError)) throw error;
20261
20459
  skipped.set(raw.id, "unreadable");
20262
20460
  }
20263
20461
  }
20462
+ const usable = new Set(prepared.filter((p) => p.row.embedding).map((p) => p.row.memory_id));
20463
+ if (options.embeddingOptions)
20464
+ await embedMissing(prepared, options.embeddingOptions, model, signal);
20465
+ const local = new Map(candidates.map((raw) => [raw.id, raw]));
20466
+ const upload = [];
20467
+ for (const { row, plaintext } of prepared) {
20468
+ const id = row.memory_id;
20469
+ if (!fitsNearby(row)) {
20470
+ skipped.set(id, "invalid");
20471
+ continue;
20472
+ }
20473
+ upload.push(row);
20474
+ const track = (set, on) => on ? set.add(id) : set.delete(id);
20475
+ track(reembedded, !!row.embedding && !usable.has(id));
20476
+ track(unembedded, !row.embedding);
20477
+ track(placeholders, plaintext === void 0);
20478
+ track(truncated, row.source_chunk_ids !== text13(local.get(id).source_chunk_ids));
20479
+ }
20264
20480
  let sent = 0;
20265
20481
  options.onProgress?.({ imported: sent, toImport: upload.length });
20266
20482
  for (const batch of chunks(upload, IMPORT_BATCH)) {
@@ -20504,16 +20720,21 @@ function createRemoteMemoryStore(options) {
20504
20720
  const record2 = await persistence.get(id);
20505
20721
  return record2 && !record2.memory.is_deleted ? record2 : null;
20506
20722
  };
20507
- const mutateLocked = async (id, change, edit) => {
20508
- const record2 = await live(id);
20723
+ const mutateLocked = async (id, change, edit, writeOptions) => {
20724
+ writeOptions?.signal?.throwIfAborted();
20725
+ const record2 = writeOptions?.signal ? await persistence.get(id, writeOptions.signal) : await live(id);
20509
20726
  if (!record2) return null;
20727
+ if (record2.memory.is_deleted) return null;
20728
+ if (writeOptions?.canWrite && !await writeOptions.canWrite())
20729
+ throw new Error("Memory source is no longer eligible");
20730
+ writeOptions?.signal?.throwIfAborted();
20510
20731
  const next = edit(structuredClone(record2.memory));
20511
20732
  if (!next) return null;
20512
- const saved = await persistence.put(next, record2);
20733
+ const saved = writeOptions?.signal ? await persistence.put(next, record2, writeOptions.signal) : await persistence.put(next, record2);
20513
20734
  notify(typeof change === "function" ? change(record2.memory) : change, [saved]);
20514
20735
  return saved;
20515
20736
  };
20516
- const mutate = (id, change, edit) => serialized(id, () => mutateLocked(id, change, edit));
20737
+ const mutate = (id, change, edit, writeOptions) => serialized(id, () => mutateLocked(id, change, edit, writeOptions));
20517
20738
  const serializedAll = (ids, task) => [...new Set(ids)].sort().reduceRight((inner, id) => () => serialized(id, inner), task)();
20518
20739
  const mutateMany = async (ids, change, edit) => {
20519
20740
  const saved = /* @__PURE__ */ new Map();
@@ -21026,13 +21247,21 @@ function createRemoteMemoryStore(options) {
21026
21247
  }))
21027
21248
  );
21028
21249
  },
21029
- addTopics: async (memoryId, topics) => {
21250
+ addTopics: async (memoryId, topics, writeOptions) => {
21030
21251
  const added = await withKnownKinds(topicInputs(topics, "auto"));
21031
- const saved = await mutate(memoryId, "edit", (memory) => ({
21032
- ...memory,
21033
- topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
21034
- topics_updated_at: Date.now()
21035
- }));
21252
+ const saved = await mutate(
21253
+ memoryId,
21254
+ "edit",
21255
+ (memory) => {
21256
+ if (writeOptions?.unlessUserManaged && memory.topics_user_managed) return null;
21257
+ return {
21258
+ ...memory,
21259
+ topics: serializeTopics(mergeTopics(parseTopics(memory.topics) ?? [], added)),
21260
+ topics_updated_at: Date.now()
21261
+ };
21262
+ },
21263
+ writeOptions
21264
+ );
21036
21265
  if (!saved) return [];
21037
21266
  const now = /* @__PURE__ */ new Date();
21038
21267
  return mergeTopics([], added).map(
@@ -21883,6 +22112,7 @@ function createDurableAutoExtractor(options) {
21883
22112
  };
21884
22113
  const folderId = options.folderId ?? null;
21885
22114
  let disposed = false;
22115
+ let activeBatch = null;
21886
22116
  let running = false;
21887
22117
  let wakeRequested = false;
21888
22118
  let timer;
@@ -21919,14 +22149,18 @@ function createDurableAutoExtractor(options) {
21919
22149
  }, delay);
21920
22150
  }
21921
22151
  async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
22152
+ if (disposed) throw new Error("Extraction session was disposed");
21922
22153
  let watchdog;
21923
22154
  let cancelled = false;
22155
+ const controller = new AbortController();
22156
+ activeBatch = controller;
21924
22157
  const model = options.modelForScope?.(jobScope) ?? options.extract.model;
21925
22158
  try {
21926
22159
  return await Promise.race([
21927
22160
  new Promise((resolve, reject) => {
21928
22161
  const worker = createAutoExtractor({
21929
22162
  ...options,
22163
+ signal: controller.signal,
21930
22164
  retainCtx: {
21931
22165
  ...options.retainCtx,
21932
22166
  vaultCtx: {
@@ -21955,6 +22189,8 @@ function createDurableAutoExtractor(options) {
21955
22189
  ]);
21956
22190
  } finally {
21957
22191
  cancelled = true;
22192
+ controller.abort();
22193
+ if (activeBatch === controller) activeBatch = null;
21958
22194
  if (watchdog !== void 0) clearTimeout(watchdog);
21959
22195
  }
21960
22196
  }
@@ -21966,6 +22202,7 @@ function createDurableAutoExtractor(options) {
21966
22202
  for (const message of loaded)
21967
22203
  if (message && message.messageId > 0) sequences.set(message.uniqueId, message.messageId);
21968
22204
  await database.write(async () => {
22205
+ if (disposed) return;
21969
22206
  const current = idsOf(job);
21970
22207
  const acknowledged = ids.filter((id) => current.includes(id));
21971
22208
  if (!acknowledged.length) return;
@@ -22138,6 +22375,7 @@ function createDurableAutoExtractor(options) {
22138
22375
  jobScope,
22139
22376
  jobFolderId
22140
22377
  );
22378
+ if (disposed) throw new Error("Extraction session was disposed");
22141
22379
  options.onTurnComplete?.(result);
22142
22380
  return result;
22143
22381
  };
@@ -22174,6 +22412,7 @@ function createDurableAutoExtractor(options) {
22174
22412
  );
22175
22413
  scheduleAfterSuccess = true;
22176
22414
  } catch (error) {
22415
+ if (disposed) break;
22177
22416
  attempts.set(job.id, attempted + 1);
22178
22417
  retry = true;
22179
22418
  reportError(error, conversationId);
@@ -22196,6 +22435,14 @@ function createDurableAutoExtractor(options) {
22196
22435
  schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
22197
22436
  }
22198
22437
  }
22438
+ function dispose() {
22439
+ disposed = true;
22440
+ activeBatch?.abort(options.signal?.reason);
22441
+ if (timer !== void 0) clearTimeout(timer);
22442
+ options.signal?.removeEventListener("abort", dispose);
22443
+ }
22444
+ if (options.signal?.aborted) dispose();
22445
+ else options.signal?.addEventListener("abort", dispose, { once: true });
22199
22446
  schedule(0);
22200
22447
  return {
22201
22448
  processTurn(messages, conversationId) {
@@ -22275,10 +22522,7 @@ function createDurableAutoExtractor(options) {
22275
22522
  return true;
22276
22523
  },
22277
22524
  isProcessing: () => running,
22278
- dispose() {
22279
- disposed = true;
22280
- if (timer !== void 0) clearTimeout(timer);
22281
- }
22525
+ dispose
22282
22526
  };
22283
22527
  }
22284
22528
 
@@ -25527,6 +25771,7 @@ var CONVERSATION_REDACTOR_LIMIT = 50;
25527
25771
  var NO_CONVERSATION_KEY = "__no_conversation__";
25528
25772
  var NO_STORE_FOLDERS = "Folders are not supported with a memoryStore";
25529
25773
  var NO_STORE_MASK = "piiRedaction needs a memoryStore built with embeddingOptions.maskInput";
25774
+ var MEMORY_STORE_NOT_READY = "Memory store is not ready";
25530
25775
  var conversationRedactors = /* @__PURE__ */ new Map();
25531
25776
  var clientToolFilterCache = /* @__PURE__ */ new Map();
25532
25777
  function getConversationRedactor(conversationId, nerDetector) {
@@ -25671,7 +25916,7 @@ function useChatStorage(options) {
25671
25916
  [resolvedPiiRedaction]
25672
25917
  );
25673
25918
  const maskEmbeddingInput = isPiiRedactor(resolvedPiiRedaction) ? maskForEmbedding : void 0;
25674
- const storeMaskMissing = memoryStore !== void 0 && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
25919
+ const storeMaskMissing = memoryStore !== void 0 && memoryStore !== null && maskEmbeddingInput !== void 0 && !memoryStore.masksEmbeddingInput;
25675
25920
  const messagesCollection = useMemo(() => database.get("history"), [database]);
25676
25921
  const conversationsCollection = useMemo(
25677
25922
  () => database.get("conversations"),
@@ -25920,6 +26165,7 @@ function useChatStorage(options) {
25920
26165
  }, [vaultEmbeddingCache]);
25921
26166
  const retainVaultMemory = useCallback4(
25922
26167
  async (input) => {
26168
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25923
26169
  if (memoryStore) {
25924
26170
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
25925
26171
  if (input.folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -25965,6 +26211,7 @@ function useChatStorage(options) {
25965
26211
  );
25966
26212
  const createMemoryVaultTool2 = useCallback4(
25967
26213
  (options2) => {
26214
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25968
26215
  if (storeMaskMissing) throw new Error(NO_STORE_MASK);
25969
26216
  return createMemoryVaultTool(
25970
26217
  memoryStore ?? vaultCtx,
@@ -25975,6 +26222,7 @@ function useChatStorage(options) {
25975
26222
  );
25976
26223
  const createRecallTool2 = useCallback4(
25977
26224
  (toolOptions, callbacks) => {
26225
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
25978
26226
  if (!getToken && !memoryStore) {
25979
26227
  throw new Error("getToken is required for recall tool");
25980
26228
  }
@@ -26020,7 +26268,7 @@ function useChatStorage(options) {
26020
26268
  );
26021
26269
  const recallFn = useCallback4(
26022
26270
  async (query, options2) => {
26023
- if (!getToken && !memoryStore) {
26271
+ if (memoryStore === null || !getToken && !memoryStore) {
26024
26272
  return {
26025
26273
  memories: [],
26026
26274
  usedBudget: options2?.budget ?? "low",
@@ -26069,6 +26317,7 @@ function useChatStorage(options) {
26069
26317
  );
26070
26318
  const getVaultMemories = useCallback4(
26071
26319
  (options2) => {
26320
+ if (memoryStore === null) return Promise.resolve([]);
26072
26321
  if (!memoryStore) return getAllVaultMemoriesOp(vaultCtx, options2);
26073
26322
  const { folderId, levels, kinds, ...listOptions } = options2 ?? {};
26074
26323
  if (folderId !== void 0) throw new Error(NO_STORE_FOLDERS);
@@ -26079,7 +26328,8 @@ function useChatStorage(options) {
26079
26328
  [vaultCtx, memoryStore]
26080
26329
  );
26081
26330
  const deleteVaultMemory = useCallback4(
26082
- (id) => {
26331
+ async (id) => {
26332
+ if (memoryStore === null) throw new Error(MEMORY_STORE_NOT_READY);
26083
26333
  return memoryStore ? memoryStore.delete(id) : deleteVaultMemoryOp(vaultCtx, id);
26084
26334
  },
26085
26335
  [vaultCtx, memoryStore]