@anuma/sdk 9.1.1 → 9.1.3

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.
@@ -2326,6 +2326,7 @@ async function getEncryptionKey(address, version = "v3") {
2326
2326
  if (cachedKey) {
2327
2327
  return cachedKey;
2328
2328
  }
2329
+ const startEpoch = sessionEpoch;
2329
2330
  const keyHex = getStoredKeyByVersion(address, version);
2330
2331
  if (!keyHex) {
2331
2332
  throw new EncryptionKeyMissingError(address, version);
@@ -2338,6 +2339,9 @@ async function getEncryptionKey(address, version = "v3") {
2338
2339
  false,
2339
2340
  ["encrypt", "decrypt"]
2340
2341
  );
2342
+ if (sessionEpoch !== startEpoch || getStoredKeyByVersion(address, version) !== keyHex) {
2343
+ throw new EncryptionKeyMissingError(address, version);
2344
+ }
2341
2345
  cryptoKeyCache.set(cacheKey, cryptoKey);
2342
2346
  return cryptoKey;
2343
2347
  }
@@ -14817,9 +14821,10 @@ async function getVaultRankingProjectionsOp(ctx, options) {
14817
14821
  }
14818
14822
  function baseVaultSql(ctx, options) {
14819
14823
  const clauses = [
14824
+ `"_status" is not 'deleted'`,
14820
14825
  '"is_deleted" = 0',
14821
14826
  ...options?.includeArchived ? [] : ['"archived_at" is null'],
14822
- `"trust_tier" is not 'quarantined'`,
14827
+ ...options?.includeQuarantined ? [] : [`"trust_tier" is not 'quarantined'`],
14823
14828
  '"superseded_by" is null'
14824
14829
  ];
14825
14830
  const args = [];
@@ -15305,8 +15310,17 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
15305
15310
  if (!entityCtx) {
15306
15311
  throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
15307
15312
  }
15308
- const conditions = [...baseVaultConditions(ctx), import_watermelondb21.Q.sortBy("created_at", import_watermelondb21.Q.desc)];
15309
- const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
15313
+ let rows;
15314
+ try {
15315
+ const base = baseVaultSql(ctx);
15316
+ const sql = `select "id", "topics", "topics_user_managed", "topics_extracted_at", "topics_extracted_version", "updated_at" from "memory_vault" where ${base.sql} order by "created_at" desc`;
15317
+ rows = await ctx.vaultMemoryCollection.query(import_watermelondb21.Q.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
15318
+ } catch (err) {
15319
+ getLogger().debug(
15320
+ "memoryVault: topic sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
15321
+ );
15322
+ rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), import_watermelondb21.Q.sortBy("created_at", import_watermelondb21.Q.desc)).unsafeFetchRaw();
15323
+ }
15310
15324
  const linkedNames = await linkedEntityNamesByMemory(
15311
15325
  entityCtx,
15312
15326
  rows.map((r) => r.id)
@@ -15365,10 +15379,19 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
15365
15379
  }
15366
15380
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
15367
15381
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
15368
- const pending = await mapInBatches(
15369
- limitedPendingRaw,
15370
- (raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
15371
- );
15382
+ const pending = [];
15383
+ for (let i = 0; i < limitedPendingRaw.length; i += 500) {
15384
+ const chunk = limitedPendingRaw.slice(i, i + 500);
15385
+ const memories = await getVaultMemoriesByIdsOp(
15386
+ ctx,
15387
+ chunk.map((raw) => raw.id)
15388
+ );
15389
+ const byId = new Map(memories.map((memory) => [memory.uniqueId, memory]));
15390
+ for (const raw of chunk) {
15391
+ const memory = byId.get(raw.id);
15392
+ if (memory) pending.push(memory);
15393
+ }
15394
+ }
15372
15395
  const applyCap = (ids) => cap !== void 0 ? ids.slice(0, cap) : ids;
15373
15396
  return {
15374
15397
  pending,
@@ -15515,10 +15538,20 @@ function assertVaultScopeForSweep(ctx) {
15515
15538
  }
15516
15539
  async function getDecayCandidatesRawOp(ctx) {
15517
15540
  assertVaultScopeForSweep(ctx);
15518
- const results = await ctx.vaultMemoryCollection.query(
15519
- ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
15520
- import_watermelondb21.Q.where("kind", null)
15521
- ).unsafeFetchRaw();
15541
+ let results;
15542
+ try {
15543
+ const base = baseVaultSql(ctx, { includeArchived: true, includeQuarantined: true });
15544
+ const sql = `select "id", "fact_type", "event_time_end", "event_time_kind", "updated_at", "last_observed_at", "archived_at", "source", "trust_tier" from "memory_vault" where ${base.sql} and "kind" is null`;
15545
+ results = await ctx.vaultMemoryCollection.query(import_watermelondb21.Q.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
15546
+ } catch (err) {
15547
+ getLogger().debug(
15548
+ "memoryVault: decay sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
15549
+ );
15550
+ results = await ctx.vaultMemoryCollection.query(
15551
+ ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
15552
+ import_watermelondb21.Q.where("kind", null)
15553
+ ).unsafeFetchRaw();
15554
+ }
15522
15555
  return results.map((raw) => ({
15523
15556
  uniqueId: raw.id,
15524
15557
  factType: raw.fact_type ?? null,
@@ -24986,6 +25019,18 @@ async function assembleMemoryContext(options) {
24986
25019
 
24987
25020
  // src/lib/memory/durableExtraction.ts
24988
25021
  var import_watermelondb25 = require("@nozbe/watermelondb");
25022
+ async function messagePositions(storage, ids) {
25023
+ try {
25024
+ return await storage.messagesCollection.query(
25025
+ import_watermelondb25.Q.unsafeSqlQuery(
25026
+ `select "id", "conversation_id", "message_id" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
25027
+ ids
25028
+ )
25029
+ ).unsafeFetchRaw();
25030
+ } catch {
25031
+ return await storage.messagesCollection.query(import_watermelondb25.Q.where("id", import_watermelondb25.Q.oneOf(ids))).unsafeFetchRaw();
25032
+ }
25033
+ }
24989
25034
  var MAX_JOBS_PER_PASS = 3;
24990
25035
  var MAX_ATTEMPTS = 3;
24991
25036
  var MAX_FAILED_SESSIONS = 3;
@@ -25554,9 +25599,9 @@ function createDurableAutoExtractor(options) {
25554
25599
  r._setRaw("watermark_seq", null);
25555
25600
  });
25556
25601
  }
25557
- const rows = fromSourceConversation ? [] : await storage.messagesCollection.query(import_watermelondb25.Q.where("id", import_watermelondb25.Q.oneOf(ids))).fetch();
25602
+ const rows = fromSourceConversation ? [] : await messagePositions(storage, ids);
25558
25603
  const foreignRows = new Set(
25559
- rows.filter((row) => row._getRaw("conversation_id") !== conversationId).map((row) => row.id)
25604
+ rows.filter((row) => row.conversation_id !== conversationId).map((row) => row.id)
25560
25605
  );
25561
25606
  const sequences = /* @__PURE__ */ new Map();
25562
25607
  if (fromSourceConversation) {
@@ -25566,7 +25611,7 @@ function createDurableAutoExtractor(options) {
25566
25611
  }
25567
25612
  for (const row of rows) {
25568
25613
  if (foreignRows.has(row.id)) continue;
25569
- const sequence = Number(row._getRaw("message_id"));
25614
+ const sequence = Number(row.message_id);
25570
25615
  if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
25571
25616
  }
25572
25617
  let boundary = -1;
@@ -6090,20 +6090,7 @@ interface DecayCandidateRaw {
6090
6090
  * not egress poison content — see the decay sweeper's `isBorderline`). */
6091
6091
  trustTier: string | null;
6092
6092
  }
6093
- /**
6094
- * Decay sweep candidate scan (PR2). Selects the plaintext columns
6095
- * `classifyDecay` (in `memory/decay`) needs via
6096
- * `unsafeFetchRaw` — NO Model per row (dodges the never-evicted RecordCache /
6097
- * web Pile-2 OOM history) and NO `content` read / decrypt (zero-knowledge).
6098
- *
6099
- * Includes archived AND quarantined rows (so archived→delete transitions and
6100
- * aged quarantined rows are seen) but excludes hard-deleted rows — the
6101
- * `baseVaultConditions` default keeps `is_deleted = false`. Excludes kinded
6102
- * (profile) memories, which never decay.
6103
- *
6104
- * Refuses to run on an unscoped multi-tenant context (see
6105
- * {@link assertVaultScopeForSweep}).
6106
- */
6093
+ /** Return the projected plaintext fields for the decay sweep within the required vault scope. */
6107
6094
  declare function getDecayCandidatesRawOp(ctx: VaultMemoryOperationsContext): Promise<DecayCandidateRaw[]>;
6108
6095
  /**
6109
6096
  * Archive a memory (decay soft state, PR2) — set `archived_at`. An archived row
@@ -6090,20 +6090,7 @@ interface DecayCandidateRaw {
6090
6090
  * not egress poison content — see the decay sweeper's `isBorderline`). */
6091
6091
  trustTier: string | null;
6092
6092
  }
6093
- /**
6094
- * Decay sweep candidate scan (PR2). Selects the plaintext columns
6095
- * `classifyDecay` (in `memory/decay`) needs via
6096
- * `unsafeFetchRaw` — NO Model per row (dodges the never-evicted RecordCache /
6097
- * web Pile-2 OOM history) and NO `content` read / decrypt (zero-knowledge).
6098
- *
6099
- * Includes archived AND quarantined rows (so archived→delete transitions and
6100
- * aged quarantined rows are seen) but excludes hard-deleted rows — the
6101
- * `baseVaultConditions` default keeps `is_deleted = false`. Excludes kinded
6102
- * (profile) memories, which never decay.
6103
- *
6104
- * Refuses to run on an unscoped multi-tenant context (see
6105
- * {@link assertVaultScopeForSweep}).
6106
- */
6093
+ /** Return the projected plaintext fields for the decay sweep within the required vault scope. */
6107
6094
  declare function getDecayCandidatesRawOp(ctx: VaultMemoryOperationsContext): Promise<DecayCandidateRaw[]>;
6108
6095
  /**
6109
6096
  * Archive a memory (decay soft state, PR2) — set `archived_at`. An archived row
@@ -729,6 +729,7 @@ async function getEncryptionKey(address, version = "v3") {
729
729
  if (cachedKey) {
730
730
  return cachedKey;
731
731
  }
732
+ const startEpoch = sessionEpoch;
732
733
  const keyHex = getStoredKeyByVersion(address, version);
733
734
  if (!keyHex) {
734
735
  throw new EncryptionKeyMissingError(address, version);
@@ -741,6 +742,9 @@ async function getEncryptionKey(address, version = "v3") {
741
742
  false,
742
743
  ["encrypt", "decrypt"]
743
744
  );
745
+ if (sessionEpoch !== startEpoch || getStoredKeyByVersion(address, version) !== keyHex) {
746
+ throw new EncryptionKeyMissingError(address, version);
747
+ }
744
748
  cryptoKeyCache.set(cacheKey, cryptoKey);
745
749
  return cryptoKey;
746
750
  }
@@ -13191,9 +13195,10 @@ async function getVaultRankingProjectionsOp(ctx, options) {
13191
13195
  }
13192
13196
  function baseVaultSql(ctx, options) {
13193
13197
  const clauses = [
13198
+ `"_status" is not 'deleted'`,
13194
13199
  '"is_deleted" = 0',
13195
13200
  ...options?.includeArchived ? [] : ['"archived_at" is null'],
13196
- `"trust_tier" is not 'quarantined'`,
13201
+ ...options?.includeQuarantined ? [] : [`"trust_tier" is not 'quarantined'`],
13197
13202
  '"superseded_by" is null'
13198
13203
  ];
13199
13204
  const args = [];
@@ -13679,8 +13684,17 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
13679
13684
  if (!entityCtx) {
13680
13685
  throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
13681
13686
  }
13682
- const conditions = [...baseVaultConditions(ctx), Q6.sortBy("created_at", Q6.desc)];
13683
- const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
13687
+ let rows;
13688
+ try {
13689
+ const base = baseVaultSql(ctx);
13690
+ const sql = `select "id", "topics", "topics_user_managed", "topics_extracted_at", "topics_extracted_version", "updated_at" from "memory_vault" where ${base.sql} order by "created_at" desc`;
13691
+ rows = await ctx.vaultMemoryCollection.query(Q6.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
13692
+ } catch (err) {
13693
+ getLogger().debug(
13694
+ "memoryVault: topic sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
13695
+ );
13696
+ rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), Q6.sortBy("created_at", Q6.desc)).unsafeFetchRaw();
13697
+ }
13684
13698
  const linkedNames = await linkedEntityNamesByMemory(
13685
13699
  entityCtx,
13686
13700
  rows.map((r) => r.id)
@@ -13739,10 +13753,19 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
13739
13753
  }
13740
13754
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
13741
13755
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
13742
- const pending = await mapInBatches(
13743
- limitedPendingRaw,
13744
- (raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
13745
- );
13756
+ const pending = [];
13757
+ for (let i = 0; i < limitedPendingRaw.length; i += 500) {
13758
+ const chunk = limitedPendingRaw.slice(i, i + 500);
13759
+ const memories = await getVaultMemoriesByIdsOp(
13760
+ ctx,
13761
+ chunk.map((raw) => raw.id)
13762
+ );
13763
+ const byId = new Map(memories.map((memory) => [memory.uniqueId, memory]));
13764
+ for (const raw of chunk) {
13765
+ const memory = byId.get(raw.id);
13766
+ if (memory) pending.push(memory);
13767
+ }
13768
+ }
13746
13769
  const applyCap = (ids) => cap !== void 0 ? ids.slice(0, cap) : ids;
13747
13770
  return {
13748
13771
  pending,
@@ -13889,10 +13912,20 @@ function assertVaultScopeForSweep(ctx) {
13889
13912
  }
13890
13913
  async function getDecayCandidatesRawOp(ctx) {
13891
13914
  assertVaultScopeForSweep(ctx);
13892
- const results = await ctx.vaultMemoryCollection.query(
13893
- ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
13894
- Q6.where("kind", null)
13895
- ).unsafeFetchRaw();
13915
+ let results;
13916
+ try {
13917
+ const base = baseVaultSql(ctx, { includeArchived: true, includeQuarantined: true });
13918
+ const sql = `select "id", "fact_type", "event_time_end", "event_time_kind", "updated_at", "last_observed_at", "archived_at", "source", "trust_tier" from "memory_vault" where ${base.sql} and "kind" is null`;
13919
+ results = await ctx.vaultMemoryCollection.query(Q6.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
13920
+ } catch (err) {
13921
+ getLogger().debug(
13922
+ "memoryVault: decay sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
13923
+ );
13924
+ results = await ctx.vaultMemoryCollection.query(
13925
+ ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
13926
+ Q6.where("kind", null)
13927
+ ).unsafeFetchRaw();
13928
+ }
13896
13929
  return results.map((raw) => ({
13897
13930
  uniqueId: raw.id,
13898
13931
  factType: raw.fact_type ?? null,
@@ -23308,6 +23341,18 @@ async function assembleMemoryContext(options) {
23308
23341
 
23309
23342
  // src/lib/memory/durableExtraction.ts
23310
23343
  import { Q as Q10 } from "@nozbe/watermelondb";
23344
+ async function messagePositions(storage, ids) {
23345
+ try {
23346
+ return await storage.messagesCollection.query(
23347
+ Q10.unsafeSqlQuery(
23348
+ `select "id", "conversation_id", "message_id" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
23349
+ ids
23350
+ )
23351
+ ).unsafeFetchRaw();
23352
+ } catch {
23353
+ return await storage.messagesCollection.query(Q10.where("id", Q10.oneOf(ids))).unsafeFetchRaw();
23354
+ }
23355
+ }
23311
23356
  var MAX_JOBS_PER_PASS = 3;
23312
23357
  var MAX_ATTEMPTS = 3;
23313
23358
  var MAX_FAILED_SESSIONS = 3;
@@ -23876,9 +23921,9 @@ function createDurableAutoExtractor(options) {
23876
23921
  r._setRaw("watermark_seq", null);
23877
23922
  });
23878
23923
  }
23879
- const rows = fromSourceConversation ? [] : await storage.messagesCollection.query(Q10.where("id", Q10.oneOf(ids))).fetch();
23924
+ const rows = fromSourceConversation ? [] : await messagePositions(storage, ids);
23880
23925
  const foreignRows = new Set(
23881
- rows.filter((row) => row._getRaw("conversation_id") !== conversationId).map((row) => row.id)
23926
+ rows.filter((row) => row.conversation_id !== conversationId).map((row) => row.id)
23882
23927
  );
23883
23928
  const sequences = /* @__PURE__ */ new Map();
23884
23929
  if (fromSourceConversation) {
@@ -23888,7 +23933,7 @@ function createDurableAutoExtractor(options) {
23888
23933
  }
23889
23934
  for (const row of rows) {
23890
23935
  if (foreignRows.has(row.id)) continue;
23891
- const sequence = Number(row._getRaw("message_id"));
23936
+ const sequence = Number(row.message_id);
23892
23937
  if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
23893
23938
  }
23894
23939
  let boundary = -1;
package/dist/index.cjs CHANGED
@@ -1404,6 +1404,7 @@ var EncryptionKeyMissingError = class extends Error {
1404
1404
  }
1405
1405
  };
1406
1406
  var encryptionKeyStore = /* @__PURE__ */ new Map();
1407
+ var sessionEpoch = 0;
1407
1408
  var cryptoKeyCache = /* @__PURE__ */ new Map();
1408
1409
  var clearAllEncryptionStateCallbacks = /* @__PURE__ */ new Set();
1409
1410
  function onClearAllEncryptionState(callback) {
@@ -1455,6 +1456,7 @@ async function getEncryptionKey(address, version = "v3") {
1455
1456
  if (cachedKey) {
1456
1457
  return cachedKey;
1457
1458
  }
1459
+ const startEpoch = sessionEpoch;
1458
1460
  const keyHex = getStoredKeyByVersion(address, version);
1459
1461
  if (!keyHex) {
1460
1462
  throw new EncryptionKeyMissingError(address, version);
@@ -1467,6 +1469,9 @@ async function getEncryptionKey(address, version = "v3") {
1467
1469
  false,
1468
1470
  ["encrypt", "decrypt"]
1469
1471
  );
1472
+ if (sessionEpoch !== startEpoch || getStoredKeyByVersion(address, version) !== keyHex) {
1473
+ throw new EncryptionKeyMissingError(address, version);
1474
+ }
1470
1475
  cryptoKeyCache.set(cacheKey, cryptoKey);
1471
1476
  return cryptoKey;
1472
1477
  }
@@ -2159,7 +2164,7 @@ async function createRemoteChatStore(options) {
2159
2164
  var LRU_CAPACITY = 256;
2160
2165
  var titleCache = /* @__PURE__ */ new Map();
2161
2166
  var pendingDecrypts = /* @__PURE__ */ new Map();
2162
- var sessionEpoch = 0;
2167
+ var sessionEpoch2 = 0;
2163
2168
  function buildCacheKey(address, encryptedTitle) {
2164
2169
  return `${address}:${encryptedTitle}`;
2165
2170
  }
@@ -2178,7 +2183,7 @@ function insertWithEviction(key, value) {
2178
2183
  function clearLazyTitleCache() {
2179
2184
  titleCache.clear();
2180
2185
  pendingDecrypts.clear();
2181
- sessionEpoch += 1;
2186
+ sessionEpoch2 += 1;
2182
2187
  }
2183
2188
  onClearAllEncryptionState(clearLazyTitleCache);
2184
2189
  async function decryptConversationTitle(encryptedTitle, address) {
@@ -2200,13 +2205,13 @@ async function decryptConversationTitle(encryptedTitle, address) {
2200
2205
  }
2201
2206
  const inFlight = pendingDecrypts.get(cacheKey);
2202
2207
  if (inFlight) return inFlight;
2203
- const epochAtStart = sessionEpoch;
2208
+ const epochAtStart = sessionEpoch2;
2204
2209
  const promise = (async () => {
2205
2210
  const plaintext = await decryptField(encryptedTitle, address);
2206
2211
  if (isEncrypted(plaintext)) {
2207
2212
  throw new Error("decryptConversationTitle: decryption failed (returned ciphertext)");
2208
2213
  }
2209
- if (sessionEpoch === epochAtStart) {
2214
+ if (sessionEpoch2 === epochAtStart) {
2210
2215
  insertWithEviction(cacheKey, plaintext);
2211
2216
  }
2212
2217
  return plaintext;
package/dist/index.mjs CHANGED
@@ -1201,6 +1201,7 @@ var EncryptionKeyMissingError = class extends Error {
1201
1201
  }
1202
1202
  };
1203
1203
  var encryptionKeyStore = /* @__PURE__ */ new Map();
1204
+ var sessionEpoch = 0;
1204
1205
  var cryptoKeyCache = /* @__PURE__ */ new Map();
1205
1206
  var clearAllEncryptionStateCallbacks = /* @__PURE__ */ new Set();
1206
1207
  function onClearAllEncryptionState(callback) {
@@ -1252,6 +1253,7 @@ async function getEncryptionKey(address, version = "v3") {
1252
1253
  if (cachedKey) {
1253
1254
  return cachedKey;
1254
1255
  }
1256
+ const startEpoch = sessionEpoch;
1255
1257
  const keyHex = getStoredKeyByVersion(address, version);
1256
1258
  if (!keyHex) {
1257
1259
  throw new EncryptionKeyMissingError(address, version);
@@ -1264,6 +1266,9 @@ async function getEncryptionKey(address, version = "v3") {
1264
1266
  false,
1265
1267
  ["encrypt", "decrypt"]
1266
1268
  );
1269
+ if (sessionEpoch !== startEpoch || getStoredKeyByVersion(address, version) !== keyHex) {
1270
+ throw new EncryptionKeyMissingError(address, version);
1271
+ }
1267
1272
  cryptoKeyCache.set(cacheKey, cryptoKey);
1268
1273
  return cryptoKey;
1269
1274
  }
@@ -1956,7 +1961,7 @@ async function createRemoteChatStore(options) {
1956
1961
  var LRU_CAPACITY = 256;
1957
1962
  var titleCache = /* @__PURE__ */ new Map();
1958
1963
  var pendingDecrypts = /* @__PURE__ */ new Map();
1959
- var sessionEpoch = 0;
1964
+ var sessionEpoch2 = 0;
1960
1965
  function buildCacheKey(address, encryptedTitle) {
1961
1966
  return `${address}:${encryptedTitle}`;
1962
1967
  }
@@ -1975,7 +1980,7 @@ function insertWithEviction(key, value) {
1975
1980
  function clearLazyTitleCache() {
1976
1981
  titleCache.clear();
1977
1982
  pendingDecrypts.clear();
1978
- sessionEpoch += 1;
1983
+ sessionEpoch2 += 1;
1979
1984
  }
1980
1985
  onClearAllEncryptionState(clearLazyTitleCache);
1981
1986
  async function decryptConversationTitle(encryptedTitle, address) {
@@ -1997,13 +2002,13 @@ async function decryptConversationTitle(encryptedTitle, address) {
1997
2002
  }
1998
2003
  const inFlight = pendingDecrypts.get(cacheKey);
1999
2004
  if (inFlight) return inFlight;
2000
- const epochAtStart = sessionEpoch;
2005
+ const epochAtStart = sessionEpoch2;
2001
2006
  const promise = (async () => {
2002
2007
  const plaintext = await decryptField(encryptedTitle, address);
2003
2008
  if (isEncrypted(plaintext)) {
2004
2009
  throw new Error("decryptConversationTitle: decryption failed (returned ciphertext)");
2005
2010
  }
2006
- if (sessionEpoch === epochAtStart) {
2011
+ if (sessionEpoch2 === epochAtStart) {
2007
2012
  insertWithEviction(cacheKey, plaintext);
2008
2013
  }
2009
2014
  return plaintext;
@@ -2093,6 +2093,7 @@ async function getEncryptionKey(address, version = "v3") {
2093
2093
  if (cachedKey) {
2094
2094
  return cachedKey;
2095
2095
  }
2096
+ const startEpoch = sessionEpoch;
2096
2097
  const keyHex = getStoredKeyByVersion(address, version);
2097
2098
  if (!keyHex) {
2098
2099
  throw new EncryptionKeyMissingError(address, version);
@@ -2105,6 +2106,9 @@ async function getEncryptionKey(address, version = "v3") {
2105
2106
  false,
2106
2107
  ["encrypt", "decrypt"]
2107
2108
  );
2109
+ if (sessionEpoch !== startEpoch || getStoredKeyByVersion(address, version) !== keyHex) {
2110
+ throw new EncryptionKeyMissingError(address, version);
2111
+ }
2108
2112
  cryptoKeyCache.set(cacheKey, cryptoKey);
2109
2113
  return cryptoKey;
2110
2114
  }
@@ -16634,9 +16638,10 @@ async function getVaultRankingProjectionsOp(ctx, options) {
16634
16638
  }
16635
16639
  function baseVaultSql(ctx, options) {
16636
16640
  const clauses = [
16641
+ `"_status" is not 'deleted'`,
16637
16642
  '"is_deleted" = 0',
16638
16643
  ...options?.includeArchived ? [] : ['"archived_at" is null'],
16639
- `"trust_tier" is not 'quarantined'`,
16644
+ ...options?.includeQuarantined ? [] : [`"trust_tier" is not 'quarantined'`],
16640
16645
  '"superseded_by" is null'
16641
16646
  ];
16642
16647
  const args = [];
@@ -17122,8 +17127,17 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
17122
17127
  if (!entityCtx) {
17123
17128
  throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
17124
17129
  }
17125
- const conditions = [...baseVaultConditions(ctx), import_watermelondb24.Q.sortBy("created_at", import_watermelondb24.Q.desc)];
17126
- const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
17130
+ let rows;
17131
+ try {
17132
+ const base = baseVaultSql(ctx);
17133
+ const sql = `select "id", "topics", "topics_user_managed", "topics_extracted_at", "topics_extracted_version", "updated_at" from "memory_vault" where ${base.sql} order by "created_at" desc`;
17134
+ rows = await ctx.vaultMemoryCollection.query(import_watermelondb24.Q.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
17135
+ } catch (err) {
17136
+ getLogger().debug(
17137
+ "memoryVault: topic sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
17138
+ );
17139
+ rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), import_watermelondb24.Q.sortBy("created_at", import_watermelondb24.Q.desc)).unsafeFetchRaw();
17140
+ }
17127
17141
  const linkedNames = await linkedEntityNamesByMemory(
17128
17142
  entityCtx,
17129
17143
  rows.map((r) => r.id)
@@ -17182,10 +17196,19 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
17182
17196
  }
17183
17197
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
17184
17198
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
17185
- const pending = await mapInBatches(
17186
- limitedPendingRaw,
17187
- (raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
17188
- );
17199
+ const pending = [];
17200
+ for (let i = 0; i < limitedPendingRaw.length; i += 500) {
17201
+ const chunk = limitedPendingRaw.slice(i, i + 500);
17202
+ const memories = await getVaultMemoriesByIdsOp(
17203
+ ctx,
17204
+ chunk.map((raw) => raw.id)
17205
+ );
17206
+ const byId = new Map(memories.map((memory) => [memory.uniqueId, memory]));
17207
+ for (const raw of chunk) {
17208
+ const memory = byId.get(raw.id);
17209
+ if (memory) pending.push(memory);
17210
+ }
17211
+ }
17189
17212
  const applyCap = (ids) => cap !== void 0 ? ids.slice(0, cap) : ids;
17190
17213
  return {
17191
17214
  pending,
@@ -17332,10 +17355,20 @@ function assertVaultScopeForSweep(ctx) {
17332
17355
  }
17333
17356
  async function getDecayCandidatesRawOp(ctx) {
17334
17357
  assertVaultScopeForSweep(ctx);
17335
- const results = await ctx.vaultMemoryCollection.query(
17336
- ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
17337
- import_watermelondb24.Q.where("kind", null)
17338
- ).unsafeFetchRaw();
17358
+ let results;
17359
+ try {
17360
+ const base = baseVaultSql(ctx, { includeArchived: true, includeQuarantined: true });
17361
+ const sql = `select "id", "fact_type", "event_time_end", "event_time_kind", "updated_at", "last_observed_at", "archived_at", "source", "trust_tier" from "memory_vault" where ${base.sql} and "kind" is null`;
17362
+ results = await ctx.vaultMemoryCollection.query(import_watermelondb24.Q.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
17363
+ } catch (err) {
17364
+ getLogger().debug(
17365
+ "memoryVault: decay sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
17366
+ );
17367
+ results = await ctx.vaultMemoryCollection.query(
17368
+ ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
17369
+ import_watermelondb24.Q.where("kind", null)
17370
+ ).unsafeFetchRaw();
17371
+ }
17339
17372
  return results.map((raw) => ({
17340
17373
  uniqueId: raw.id,
17341
17374
  factType: raw.fact_type ?? null,
@@ -27299,6 +27332,18 @@ async function assembleMemoryContext(options) {
27299
27332
 
27300
27333
  // src/lib/memory/durableExtraction.ts
27301
27334
  var import_watermelondb34 = require("@nozbe/watermelondb");
27335
+ async function messagePositions(storage, ids) {
27336
+ try {
27337
+ return await storage.messagesCollection.query(
27338
+ import_watermelondb34.Q.unsafeSqlQuery(
27339
+ `select "id", "conversation_id", "message_id" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
27340
+ ids
27341
+ )
27342
+ ).unsafeFetchRaw();
27343
+ } catch {
27344
+ return await storage.messagesCollection.query(import_watermelondb34.Q.where("id", import_watermelondb34.Q.oneOf(ids))).unsafeFetchRaw();
27345
+ }
27346
+ }
27302
27347
  var MAX_JOBS_PER_PASS = 3;
27303
27348
  var MAX_ATTEMPTS = 3;
27304
27349
  var MAX_FAILED_SESSIONS = 3;
@@ -27867,9 +27912,9 @@ function createDurableAutoExtractor(options) {
27867
27912
  r._setRaw("watermark_seq", null);
27868
27913
  });
27869
27914
  }
27870
- const rows = fromSourceConversation ? [] : await storage.messagesCollection.query(import_watermelondb34.Q.where("id", import_watermelondb34.Q.oneOf(ids))).fetch();
27915
+ const rows = fromSourceConversation ? [] : await messagePositions(storage, ids);
27871
27916
  const foreignRows = new Set(
27872
- rows.filter((row) => row._getRaw("conversation_id") !== conversationId).map((row) => row.id)
27917
+ rows.filter((row) => row.conversation_id !== conversationId).map((row) => row.id)
27873
27918
  );
27874
27919
  const sequences = /* @__PURE__ */ new Map();
27875
27920
  if (fromSourceConversation) {
@@ -27879,7 +27924,7 @@ function createDurableAutoExtractor(options) {
27879
27924
  }
27880
27925
  for (const row of rows) {
27881
27926
  if (foreignRows.has(row.id)) continue;
27882
- const sequence = Number(row._getRaw("message_id"));
27927
+ const sequence = Number(row.message_id);
27883
27928
  if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
27884
27929
  }
27885
27930
  let boundary = -1;
@@ -6695,20 +6695,7 @@ interface DecayCandidateRaw {
6695
6695
  * not egress poison content — see the decay sweeper's `isBorderline`). */
6696
6696
  trustTier: string | null;
6697
6697
  }
6698
- /**
6699
- * Decay sweep candidate scan (PR2). Selects the plaintext columns
6700
- * `classifyDecay` (in `memory/decay`) needs via
6701
- * `unsafeFetchRaw` — NO Model per row (dodges the never-evicted RecordCache /
6702
- * web Pile-2 OOM history) and NO `content` read / decrypt (zero-knowledge).
6703
- *
6704
- * Includes archived AND quarantined rows (so archived→delete transitions and
6705
- * aged quarantined rows are seen) but excludes hard-deleted rows — the
6706
- * `baseVaultConditions` default keeps `is_deleted = false`. Excludes kinded
6707
- * (profile) memories, which never decay.
6708
- *
6709
- * Refuses to run on an unscoped multi-tenant context (see
6710
- * {@link assertVaultScopeForSweep}).
6711
- */
6698
+ /** Return the projected plaintext fields for the decay sweep within the required vault scope. */
6712
6699
  declare function getDecayCandidatesRawOp(ctx: VaultMemoryOperationsContext): Promise<DecayCandidateRaw[]>;
6713
6700
  /**
6714
6701
  * Archive a memory (decay soft state, PR2) — set `archived_at`. An archived row
@@ -6695,20 +6695,7 @@ interface DecayCandidateRaw {
6695
6695
  * not egress poison content — see the decay sweeper's `isBorderline`). */
6696
6696
  trustTier: string | null;
6697
6697
  }
6698
- /**
6699
- * Decay sweep candidate scan (PR2). Selects the plaintext columns
6700
- * `classifyDecay` (in `memory/decay`) needs via
6701
- * `unsafeFetchRaw` — NO Model per row (dodges the never-evicted RecordCache /
6702
- * web Pile-2 OOM history) and NO `content` read / decrypt (zero-knowledge).
6703
- *
6704
- * Includes archived AND quarantined rows (so archived→delete transitions and
6705
- * aged quarantined rows are seen) but excludes hard-deleted rows — the
6706
- * `baseVaultConditions` default keeps `is_deleted = false`. Excludes kinded
6707
- * (profile) memories, which never decay.
6708
- *
6709
- * Refuses to run on an unscoped multi-tenant context (see
6710
- * {@link assertVaultScopeForSweep}).
6711
- */
6698
+ /** Return the projected plaintext fields for the decay sweep within the required vault scope. */
6712
6699
  declare function getDecayCandidatesRawOp(ctx: VaultMemoryOperationsContext): Promise<DecayCandidateRaw[]>;
6713
6700
  /**
6714
6701
  * Archive a memory (decay soft state, PR2) — set `archived_at`. An archived row
@@ -295,6 +295,7 @@ async function getEncryptionKey(address, version = "v3") {
295
295
  if (cachedKey) {
296
296
  return cachedKey;
297
297
  }
298
+ const startEpoch = sessionEpoch;
298
299
  const keyHex = getStoredKeyByVersion(address, version);
299
300
  if (!keyHex) {
300
301
  throw new EncryptionKeyMissingError(address, version);
@@ -307,6 +308,9 @@ async function getEncryptionKey(address, version = "v3") {
307
308
  false,
308
309
  ["encrypt", "decrypt"]
309
310
  );
311
+ if (sessionEpoch !== startEpoch || getStoredKeyByVersion(address, version) !== keyHex) {
312
+ throw new EncryptionKeyMissingError(address, version);
313
+ }
310
314
  cryptoKeyCache.set(cacheKey, cryptoKey);
311
315
  return cryptoKey;
312
316
  }
@@ -14803,9 +14807,10 @@ async function getVaultRankingProjectionsOp(ctx, options) {
14803
14807
  }
14804
14808
  function baseVaultSql(ctx, options) {
14805
14809
  const clauses = [
14810
+ `"_status" is not 'deleted'`,
14806
14811
  '"is_deleted" = 0',
14807
14812
  ...options?.includeArchived ? [] : ['"archived_at" is null'],
14808
- `"trust_tier" is not 'quarantined'`,
14813
+ ...options?.includeQuarantined ? [] : [`"trust_tier" is not 'quarantined'`],
14809
14814
  '"superseded_by" is null'
14810
14815
  ];
14811
14816
  const args = [];
@@ -15291,8 +15296,17 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
15291
15296
  if (!entityCtx) {
15292
15297
  throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
15293
15298
  }
15294
- const conditions = [...baseVaultConditions(ctx), Q9.sortBy("created_at", Q9.desc)];
15295
- const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
15299
+ let rows;
15300
+ try {
15301
+ const base = baseVaultSql(ctx);
15302
+ const sql = `select "id", "topics", "topics_user_managed", "topics_extracted_at", "topics_extracted_version", "updated_at" from "memory_vault" where ${base.sql} order by "created_at" desc`;
15303
+ rows = await ctx.vaultMemoryCollection.query(Q9.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
15304
+ } catch (err) {
15305
+ getLogger().debug(
15306
+ "memoryVault: topic sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
15307
+ );
15308
+ rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), Q9.sortBy("created_at", Q9.desc)).unsafeFetchRaw();
15309
+ }
15296
15310
  const linkedNames = await linkedEntityNamesByMemory(
15297
15311
  entityCtx,
15298
15312
  rows.map((r) => r.id)
@@ -15351,10 +15365,19 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
15351
15365
  }
15352
15366
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
15353
15367
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
15354
- const pending = await mapInBatches(
15355
- limitedPendingRaw,
15356
- (raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
15357
- );
15368
+ const pending = [];
15369
+ for (let i = 0; i < limitedPendingRaw.length; i += 500) {
15370
+ const chunk = limitedPendingRaw.slice(i, i + 500);
15371
+ const memories = await getVaultMemoriesByIdsOp(
15372
+ ctx,
15373
+ chunk.map((raw) => raw.id)
15374
+ );
15375
+ const byId = new Map(memories.map((memory) => [memory.uniqueId, memory]));
15376
+ for (const raw of chunk) {
15377
+ const memory = byId.get(raw.id);
15378
+ if (memory) pending.push(memory);
15379
+ }
15380
+ }
15358
15381
  const applyCap = (ids) => cap !== void 0 ? ids.slice(0, cap) : ids;
15359
15382
  return {
15360
15383
  pending,
@@ -15501,10 +15524,20 @@ function assertVaultScopeForSweep(ctx) {
15501
15524
  }
15502
15525
  async function getDecayCandidatesRawOp(ctx) {
15503
15526
  assertVaultScopeForSweep(ctx);
15504
- const results = await ctx.vaultMemoryCollection.query(
15505
- ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
15506
- Q9.where("kind", null)
15507
- ).unsafeFetchRaw();
15527
+ let results;
15528
+ try {
15529
+ const base = baseVaultSql(ctx, { includeArchived: true, includeQuarantined: true });
15530
+ const sql = `select "id", "fact_type", "event_time_end", "event_time_kind", "updated_at", "last_observed_at", "archived_at", "source", "trust_tier" from "memory_vault" where ${base.sql} and "kind" is null`;
15531
+ results = await ctx.vaultMemoryCollection.query(Q9.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
15532
+ } catch (err) {
15533
+ getLogger().debug(
15534
+ "memoryVault: decay sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
15535
+ );
15536
+ results = await ctx.vaultMemoryCollection.query(
15537
+ ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
15538
+ Q9.where("kind", null)
15539
+ ).unsafeFetchRaw();
15540
+ }
15508
15541
  return results.map((raw) => ({
15509
15542
  uniqueId: raw.id,
15510
15543
  factType: raw.fact_type ?? null,
@@ -25413,6 +25446,18 @@ async function assembleMemoryContext(options) {
25413
25446
 
25414
25447
  // src/lib/memory/durableExtraction.ts
25415
25448
  import { Q as Q17 } from "@nozbe/watermelondb";
25449
+ async function messagePositions(storage, ids) {
25450
+ try {
25451
+ return await storage.messagesCollection.query(
25452
+ Q17.unsafeSqlQuery(
25453
+ `select "id", "conversation_id", "message_id" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
25454
+ ids
25455
+ )
25456
+ ).unsafeFetchRaw();
25457
+ } catch {
25458
+ return await storage.messagesCollection.query(Q17.where("id", Q17.oneOf(ids))).unsafeFetchRaw();
25459
+ }
25460
+ }
25416
25461
  var MAX_JOBS_PER_PASS = 3;
25417
25462
  var MAX_ATTEMPTS = 3;
25418
25463
  var MAX_FAILED_SESSIONS = 3;
@@ -25981,9 +26026,9 @@ function createDurableAutoExtractor(options) {
25981
26026
  r._setRaw("watermark_seq", null);
25982
26027
  });
25983
26028
  }
25984
- const rows = fromSourceConversation ? [] : await storage.messagesCollection.query(Q17.where("id", Q17.oneOf(ids))).fetch();
26029
+ const rows = fromSourceConversation ? [] : await messagePositions(storage, ids);
25985
26030
  const foreignRows = new Set(
25986
- rows.filter((row) => row._getRaw("conversation_id") !== conversationId).map((row) => row.id)
26031
+ rows.filter((row) => row.conversation_id !== conversationId).map((row) => row.id)
25987
26032
  );
25988
26033
  const sequences = /* @__PURE__ */ new Map();
25989
26034
  if (fromSourceConversation) {
@@ -25993,7 +26038,7 @@ function createDurableAutoExtractor(options) {
25993
26038
  }
25994
26039
  for (const row of rows) {
25995
26040
  if (foreignRows.has(row.id)) continue;
25996
- const sequence = Number(row._getRaw("message_id"));
26041
+ const sequence = Number(row.message_id);
25997
26042
  if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
25998
26043
  }
25999
26044
  let boundary = -1;
@@ -17078,6 +17078,7 @@ async function getEncryptionKey(address, version = "v3") {
17078
17078
  if (cachedKey) {
17079
17079
  return cachedKey;
17080
17080
  }
17081
+ const startEpoch = sessionEpoch;
17081
17082
  const keyHex = getStoredKeyByVersion(address, version);
17082
17083
  if (!keyHex) {
17083
17084
  throw new EncryptionKeyMissingError(address, version);
@@ -17090,6 +17091,9 @@ async function getEncryptionKey(address, version = "v3") {
17090
17091
  false,
17091
17092
  ["encrypt", "decrypt"]
17092
17093
  );
17094
+ if (sessionEpoch !== startEpoch || getStoredKeyByVersion(address, version) !== keyHex) {
17095
+ throw new EncryptionKeyMissingError(address, version);
17096
+ }
17093
17097
  cryptoKeyCache.set(cacheKey, cryptoKey);
17094
17098
  return cryptoKey;
17095
17099
  }
@@ -28918,9 +28922,10 @@ async function getVaultRankingProjectionsOp(ctx, options) {
28918
28922
  }
28919
28923
  function baseVaultSql(ctx, options) {
28920
28924
  const clauses = [
28925
+ `"_status" is not 'deleted'`,
28921
28926
  '"is_deleted" = 0',
28922
28927
  ...options?.includeArchived ? [] : ['"archived_at" is null'],
28923
- `"trust_tier" is not 'quarantined'`,
28928
+ ...options?.includeQuarantined ? [] : [`"trust_tier" is not 'quarantined'`],
28924
28929
  '"superseded_by" is null'
28925
28930
  ];
28926
28931
  const args = [];
@@ -29406,8 +29411,17 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
29406
29411
  if (!entityCtx) {
29407
29412
  throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
29408
29413
  }
29409
- const conditions = [...baseVaultConditions(ctx), import_watermelondb19.Q.sortBy("created_at", import_watermelondb19.Q.desc)];
29410
- const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
29414
+ let rows;
29415
+ try {
29416
+ const base = baseVaultSql(ctx);
29417
+ const sql = `select "id", "topics", "topics_user_managed", "topics_extracted_at", "topics_extracted_version", "updated_at" from "memory_vault" where ${base.sql} order by "created_at" desc`;
29418
+ rows = await ctx.vaultMemoryCollection.query(import_watermelondb19.Q.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
29419
+ } catch (err) {
29420
+ getLogger().debug(
29421
+ "memoryVault: topic sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
29422
+ );
29423
+ rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), import_watermelondb19.Q.sortBy("created_at", import_watermelondb19.Q.desc)).unsafeFetchRaw();
29424
+ }
29411
29425
  const linkedNames = await linkedEntityNamesByMemory(
29412
29426
  entityCtx,
29413
29427
  rows.map((r) => r.id)
@@ -29466,10 +29480,19 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
29466
29480
  }
29467
29481
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
29468
29482
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
29469
- const pending = await mapInBatches(
29470
- limitedPendingRaw,
29471
- (raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
29472
- );
29483
+ const pending = [];
29484
+ for (let i = 0; i < limitedPendingRaw.length; i += 500) {
29485
+ const chunk = limitedPendingRaw.slice(i, i + 500);
29486
+ const memories = await getVaultMemoriesByIdsOp(
29487
+ ctx,
29488
+ chunk.map((raw) => raw.id)
29489
+ );
29490
+ const byId = new Map(memories.map((memory) => [memory.uniqueId, memory]));
29491
+ for (const raw of chunk) {
29492
+ const memory = byId.get(raw.id);
29493
+ if (memory) pending.push(memory);
29494
+ }
29495
+ }
29473
29496
  const applyCap = (ids) => cap !== void 0 ? ids.slice(0, cap) : ids;
29474
29497
  return {
29475
29498
  pending,
@@ -29616,10 +29639,20 @@ function assertVaultScopeForSweep(ctx) {
29616
29639
  }
29617
29640
  async function getDecayCandidatesRawOp(ctx) {
29618
29641
  assertVaultScopeForSweep(ctx);
29619
- const results = await ctx.vaultMemoryCollection.query(
29620
- ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
29621
- import_watermelondb19.Q.where("kind", null)
29622
- ).unsafeFetchRaw();
29642
+ let results;
29643
+ try {
29644
+ const base = baseVaultSql(ctx, { includeArchived: true, includeQuarantined: true });
29645
+ const sql = `select "id", "fact_type", "event_time_end", "event_time_kind", "updated_at", "last_observed_at", "archived_at", "source", "trust_tier" from "memory_vault" where ${base.sql} and "kind" is null`;
29646
+ results = await ctx.vaultMemoryCollection.query(import_watermelondb19.Q.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
29647
+ } catch (err) {
29648
+ getLogger().debug(
29649
+ "memoryVault: decay sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
29650
+ );
29651
+ results = await ctx.vaultMemoryCollection.query(
29652
+ ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
29653
+ import_watermelondb19.Q.where("kind", null)
29654
+ ).unsafeFetchRaw();
29655
+ }
29623
29656
  return results.map((raw) => ({
29624
29657
  uniqueId: raw.id,
29625
29658
  factType: raw.fact_type ?? null,
@@ -37112,6 +37145,18 @@ async function assembleMemoryContext(options) {
37112
37145
 
37113
37146
  // src/lib/memory/durableExtraction.ts
37114
37147
  var import_watermelondb25 = __toESM(require_watermelondb());
37148
+ async function messagePositions(storage, ids) {
37149
+ try {
37150
+ return await storage.messagesCollection.query(
37151
+ import_watermelondb25.Q.unsafeSqlQuery(
37152
+ `select "id", "conversation_id", "message_id" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
37153
+ ids
37154
+ )
37155
+ ).unsafeFetchRaw();
37156
+ } catch {
37157
+ return await storage.messagesCollection.query(import_watermelondb25.Q.where("id", import_watermelondb25.Q.oneOf(ids))).unsafeFetchRaw();
37158
+ }
37159
+ }
37115
37160
  var MAX_JOBS_PER_PASS = 3;
37116
37161
  var MAX_ATTEMPTS = 3;
37117
37162
  var MAX_FAILED_SESSIONS = 3;
@@ -37680,9 +37725,9 @@ function createDurableAutoExtractor(options) {
37680
37725
  r._setRaw("watermark_seq", null);
37681
37726
  });
37682
37727
  }
37683
- const rows = fromSourceConversation ? [] : await storage.messagesCollection.query(import_watermelondb25.Q.where("id", import_watermelondb25.Q.oneOf(ids))).fetch();
37728
+ const rows = fromSourceConversation ? [] : await messagePositions(storage, ids);
37684
37729
  const foreignRows = new Set(
37685
- rows.filter((row) => row._getRaw("conversation_id") !== conversationId).map((row) => row.id)
37730
+ rows.filter((row) => row.conversation_id !== conversationId).map((row) => row.id)
37686
37731
  );
37687
37732
  const sequences = /* @__PURE__ */ new Map();
37688
37733
  if (fromSourceConversation) {
@@ -37692,7 +37737,7 @@ function createDurableAutoExtractor(options) {
37692
37737
  }
37693
37738
  for (const row of rows) {
37694
37739
  if (foreignRows.has(row.id)) continue;
37695
- const sequence = Number(row._getRaw("message_id"));
37740
+ const sequence = Number(row.message_id);
37696
37741
  if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
37697
37742
  }
37698
37743
  let boundary = -1;
@@ -5666,20 +5666,7 @@ interface DecayCandidateRaw {
5666
5666
  * not egress poison content — see the decay sweeper's `isBorderline`). */
5667
5667
  trustTier: string | null;
5668
5668
  }
5669
- /**
5670
- * Decay sweep candidate scan (PR2). Selects the plaintext columns
5671
- * `classifyDecay` (in `memory/decay`) needs via
5672
- * `unsafeFetchRaw` — NO Model per row (dodges the never-evicted RecordCache /
5673
- * web Pile-2 OOM history) and NO `content` read / decrypt (zero-knowledge).
5674
- *
5675
- * Includes archived AND quarantined rows (so archived→delete transitions and
5676
- * aged quarantined rows are seen) but excludes hard-deleted rows — the
5677
- * `baseVaultConditions` default keeps `is_deleted = false`. Excludes kinded
5678
- * (profile) memories, which never decay.
5679
- *
5680
- * Refuses to run on an unscoped multi-tenant context (see
5681
- * {@link assertVaultScopeForSweep}).
5682
- */
5669
+ /** Return the projected plaintext fields for the decay sweep within the required vault scope. */
5683
5670
  declare function getDecayCandidatesRawOp(ctx: VaultMemoryOperationsContext): Promise<DecayCandidateRaw[]>;
5684
5671
  /**
5685
5672
  * Archive a memory (decay soft state, PR2) — set `archived_at`. An archived row
@@ -5666,20 +5666,7 @@ interface DecayCandidateRaw {
5666
5666
  * not egress poison content — see the decay sweeper's `isBorderline`). */
5667
5667
  trustTier: string | null;
5668
5668
  }
5669
- /**
5670
- * Decay sweep candidate scan (PR2). Selects the plaintext columns
5671
- * `classifyDecay` (in `memory/decay`) needs via
5672
- * `unsafeFetchRaw` — NO Model per row (dodges the never-evicted RecordCache /
5673
- * web Pile-2 OOM history) and NO `content` read / decrypt (zero-knowledge).
5674
- *
5675
- * Includes archived AND quarantined rows (so archived→delete transitions and
5676
- * aged quarantined rows are seen) but excludes hard-deleted rows — the
5677
- * `baseVaultConditions` default keeps `is_deleted = false`. Excludes kinded
5678
- * (profile) memories, which never decay.
5679
- *
5680
- * Refuses to run on an unscoped multi-tenant context (see
5681
- * {@link assertVaultScopeForSweep}).
5682
- */
5669
+ /** Return the projected plaintext fields for the decay sweep within the required vault scope. */
5683
5670
  declare function getDecayCandidatesRawOp(ctx: VaultMemoryOperationsContext): Promise<DecayCandidateRaw[]>;
5684
5671
  /**
5685
5672
  * Archive a memory (decay soft state, PR2) — set `archived_at`. An archived row
@@ -15407,6 +15407,7 @@ async function getEncryptionKey(address, version = "v3") {
15407
15407
  if (cachedKey) {
15408
15408
  return cachedKey;
15409
15409
  }
15410
+ const startEpoch = sessionEpoch;
15410
15411
  const keyHex = getStoredKeyByVersion(address, version);
15411
15412
  if (!keyHex) {
15412
15413
  throw new EncryptionKeyMissingError(address, version);
@@ -15419,6 +15420,9 @@ async function getEncryptionKey(address, version = "v3") {
15419
15420
  false,
15420
15421
  ["encrypt", "decrypt"]
15421
15422
  );
15423
+ if (sessionEpoch !== startEpoch || getStoredKeyByVersion(address, version) !== keyHex) {
15424
+ throw new EncryptionKeyMissingError(address, version);
15425
+ }
15422
15426
  cryptoKeyCache.set(cacheKey, cryptoKey);
15423
15427
  return cryptoKey;
15424
15428
  }
@@ -27209,9 +27213,10 @@ async function getVaultRankingProjectionsOp(ctx, options) {
27209
27213
  }
27210
27214
  function baseVaultSql(ctx, options) {
27211
27215
  const clauses = [
27216
+ `"_status" is not 'deleted'`,
27212
27217
  '"is_deleted" = 0',
27213
27218
  ...options?.includeArchived ? [] : ['"archived_at" is null'],
27214
- `"trust_tier" is not 'quarantined'`,
27219
+ ...options?.includeQuarantined ? [] : [`"trust_tier" is not 'quarantined'`],
27215
27220
  '"superseded_by" is null'
27216
27221
  ];
27217
27222
  const args = [];
@@ -27697,8 +27702,17 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
27697
27702
  if (!entityCtx) {
27698
27703
  throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
27699
27704
  }
27700
- const conditions = [...baseVaultConditions(ctx), import_watermelondb19.Q.sortBy("created_at", import_watermelondb19.Q.desc)];
27701
- const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
27705
+ let rows;
27706
+ try {
27707
+ const base = baseVaultSql(ctx);
27708
+ const sql = `select "id", "topics", "topics_user_managed", "topics_extracted_at", "topics_extracted_version", "updated_at" from "memory_vault" where ${base.sql} order by "created_at" desc`;
27709
+ rows = await ctx.vaultMemoryCollection.query(import_watermelondb19.Q.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
27710
+ } catch (err) {
27711
+ getLogger().debug(
27712
+ "memoryVault: topic sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
27713
+ );
27714
+ rows = await ctx.vaultMemoryCollection.query(...baseVaultConditions(ctx), import_watermelondb19.Q.sortBy("created_at", import_watermelondb19.Q.desc)).unsafeFetchRaw();
27715
+ }
27702
27716
  const linkedNames = await linkedEntityNamesByMemory(
27703
27717
  entityCtx,
27704
27718
  rows.map((r) => r.id)
@@ -27757,10 +27771,19 @@ async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
27757
27771
  }
27758
27772
  const orderedPendingRaw = [...stampedPendingRaw, ...pendingRaw];
27759
27773
  const limitedPendingRaw = cap !== void 0 ? orderedPendingRaw.slice(0, cap) : orderedPendingRaw;
27760
- const pending = await mapInBatches(
27761
- limitedPendingRaw,
27762
- (raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
27763
- );
27774
+ const pending = [];
27775
+ for (let i = 0; i < limitedPendingRaw.length; i += 500) {
27776
+ const chunk = limitedPendingRaw.slice(i, i + 500);
27777
+ const memories = await getVaultMemoriesByIdsOp(
27778
+ ctx,
27779
+ chunk.map((raw) => raw.id)
27780
+ );
27781
+ const byId = new Map(memories.map((memory) => [memory.uniqueId, memory]));
27782
+ for (const raw of chunk) {
27783
+ const memory = byId.get(raw.id);
27784
+ if (memory) pending.push(memory);
27785
+ }
27786
+ }
27764
27787
  const applyCap = (ids) => cap !== void 0 ? ids.slice(0, cap) : ids;
27765
27788
  return {
27766
27789
  pending,
@@ -27907,10 +27930,20 @@ function assertVaultScopeForSweep(ctx) {
27907
27930
  }
27908
27931
  async function getDecayCandidatesRawOp(ctx) {
27909
27932
  assertVaultScopeForSweep(ctx);
27910
- const results = await ctx.vaultMemoryCollection.query(
27911
- ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
27912
- import_watermelondb19.Q.where("kind", null)
27913
- ).unsafeFetchRaw();
27933
+ let results;
27934
+ try {
27935
+ const base = baseVaultSql(ctx, { includeArchived: true, includeQuarantined: true });
27936
+ const sql = `select "id", "fact_type", "event_time_end", "event_time_kind", "updated_at", "last_observed_at", "archived_at", "source", "trust_tier" from "memory_vault" where ${base.sql} and "kind" is null`;
27937
+ results = await ctx.vaultMemoryCollection.query(import_watermelondb19.Q.unsafeSqlQuery(sql, base.args)).unsafeFetchRaw();
27938
+ } catch (err) {
27939
+ getLogger().debug(
27940
+ "memoryVault: decay sweep SQL unavailable; use the adapter query: " + (err instanceof Error ? err.message : String(err))
27941
+ );
27942
+ results = await ctx.vaultMemoryCollection.query(
27943
+ ...baseVaultConditions(ctx, { includeArchived: true, includeQuarantined: true }),
27944
+ import_watermelondb19.Q.where("kind", null)
27945
+ ).unsafeFetchRaw();
27946
+ }
27914
27947
  return results.map((raw) => ({
27915
27948
  uniqueId: raw.id,
27916
27949
  factType: raw.fact_type ?? null,
@@ -35359,6 +35392,18 @@ async function assembleMemoryContext(options) {
35359
35392
 
35360
35393
  // src/lib/memory/durableExtraction.ts
35361
35394
  var import_watermelondb25 = __toESM(require_watermelondb());
35395
+ async function messagePositions(storage, ids) {
35396
+ try {
35397
+ return await storage.messagesCollection.query(
35398
+ import_watermelondb25.Q.unsafeSqlQuery(
35399
+ `select "id", "conversation_id", "message_id" from "history" where "_status" is not 'deleted' and "id" in (${ids.map(() => "?").join(",")})`,
35400
+ ids
35401
+ )
35402
+ ).unsafeFetchRaw();
35403
+ } catch {
35404
+ return await storage.messagesCollection.query(import_watermelondb25.Q.where("id", import_watermelondb25.Q.oneOf(ids))).unsafeFetchRaw();
35405
+ }
35406
+ }
35362
35407
  var MAX_JOBS_PER_PASS = 3;
35363
35408
  var MAX_ATTEMPTS = 3;
35364
35409
  var MAX_FAILED_SESSIONS = 3;
@@ -35927,9 +35972,9 @@ function createDurableAutoExtractor(options) {
35927
35972
  r._setRaw("watermark_seq", null);
35928
35973
  });
35929
35974
  }
35930
- const rows = fromSourceConversation ? [] : await storage.messagesCollection.query(import_watermelondb25.Q.where("id", import_watermelondb25.Q.oneOf(ids))).fetch();
35975
+ const rows = fromSourceConversation ? [] : await messagePositions(storage, ids);
35931
35976
  const foreignRows = new Set(
35932
- rows.filter((row) => row._getRaw("conversation_id") !== conversationId).map((row) => row.id)
35977
+ rows.filter((row) => row.conversation_id !== conversationId).map((row) => row.id)
35933
35978
  );
35934
35979
  const sequences = /* @__PURE__ */ new Map();
35935
35980
  if (fromSourceConversation) {
@@ -35939,7 +35984,7 @@ function createDurableAutoExtractor(options) {
35939
35984
  }
35940
35985
  for (const row of rows) {
35941
35986
  if (foreignRows.has(row.id)) continue;
35942
- const sequence = Number(row._getRaw("message_id"));
35987
+ const sequence = Number(row.message_id);
35943
35988
  if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
35944
35989
  }
35945
35990
  let boundary = -1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "9.1.1",
3
+ "version": "9.1.3",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",