@anuma/sdk 6.21.1 → 6.23.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.
Files changed (32) hide show
  1. package/dist/expo/{chunk-CIMZZ7GQ.mjs → chunk-6BULYLRY.mjs} +92 -8
  2. package/dist/expo/index.cjs +761 -162
  3. package/dist/expo/index.d.mts +151 -9
  4. package/dist/expo/index.d.ts +151 -9
  5. package/dist/expo/index.mjs +516 -14
  6. package/dist/{react/recall-7Q3V337H.mjs → expo/recall-CUTJWQDQ.mjs} +1 -1
  7. package/dist/memory/context.cjs +183 -0
  8. package/dist/memory/context.d.mts +936 -0
  9. package/dist/memory/context.d.ts +936 -0
  10. package/dist/memory/context.mjs +157 -0
  11. package/dist/react/{chunk-AVYB5IXC.mjs → chunk-GPLMMMZN.mjs} +1 -1
  12. package/dist/react/{chunk-JRVAUZHM.mjs → chunk-K4N52PSK.mjs} +93 -9
  13. package/dist/react/{consolidate-URHGJT7I.mjs → consolidate-XKS6IZFC.mjs} +2 -2
  14. package/dist/react/index.cjs +876 -270
  15. package/dist/react/index.d.mts +209 -9
  16. package/dist/react/index.d.ts +209 -9
  17. package/dist/react/index.mjs +569 -65
  18. package/dist/{expo/recall-FQQGE6A2.mjs → react/recall-LFHIWONV.mjs} +2 -2
  19. package/dist/server/{chunk-4DSDATYG.mjs → chunk-FEDRFVHD.mjs} +182 -98
  20. package/dist/server/index.cjs +815 -216
  21. package/dist/server/index.d.mts +150 -5
  22. package/dist/server/index.d.ts +150 -5
  23. package/dist/server/index.mjs +509 -7
  24. package/dist/server/{recall-HA4UX4BB.mjs → recall-Z2WJGXOL.mjs} +1 -1
  25. package/dist/telemetry/index.d.mts +7 -1
  26. package/dist/telemetry/index.d.ts +7 -1
  27. package/dist/tools/index.cjs +347 -1
  28. package/dist/tools/index.d.mts +167 -1
  29. package/dist/tools/index.d.ts +167 -1
  30. package/dist/tools/index.mjs +343 -1
  31. package/package.json +8 -1
  32. package/dist/react/{chunk-WUG6GTHO.mjs → chunk-MHZB6KIX.mjs} +8 -8
@@ -11,6 +11,7 @@ import {
11
11
  ENTITY_FANOUT,
12
12
  ENTITY_KINDS,
13
13
  EncryptionKeyMissingError,
14
+ ExtractionJob,
14
15
  LRUCache,
15
16
  MAX_HOPS,
16
17
  NODE_BUDGET,
@@ -52,6 +53,7 @@ import {
52
53
  decryptJsonField,
53
54
  deleteAllVaultMemoriesForUserOp,
54
55
  deleteConversationOp,
56
+ deleteMessageOp,
55
57
  deleteVaultMemoryOp,
56
58
  eagerEmbedContent,
57
59
  embedAllMessages,
@@ -133,7 +135,7 @@ import {
133
135
  upsertMessageOp,
134
136
  useEncryption,
135
137
  vaultMemoryToStored
136
- } from "./chunk-CIMZZ7GQ.mjs";
138
+ } from "./chunk-6BULYLRY.mjs";
137
139
  import {
138
140
  PII_PATTERNS,
139
141
  PiiRedactor,
@@ -5802,7 +5804,8 @@ function classifyDecay(m, now, policy) {
5802
5804
  if (m.eventTimeEnd < now - resolved.pastEventGraceMs) return "archive";
5803
5805
  if (m.eventTimeEnd >= now) return "keep";
5804
5806
  }
5805
- if (now - m.updatedAt > ttlForType(m.factType, resolved)) return "archive";
5807
+ const observedAt = Number.isFinite(m.lastObservedAt) ? m.lastObservedAt : m.updatedAt;
5808
+ if (now - Math.max(m.updatedAt, observedAt) > ttlForType(m.factType, resolved)) return "archive";
5806
5809
  return "keep";
5807
5810
  }
5808
5811
 
@@ -6707,6 +6710,7 @@ async function retain(content, ctx, options = {}) {
6707
6710
  const updated = await updateVaultMemoryOp(ctx.vaultCtx, targetId, {
6708
6711
  content: existing.content,
6709
6712
  proofCountIncrement: 1,
6713
+ observationSourceIds: options.sourceChunkIds,
6710
6714
  sourceChunkIds: mergedSourceIds,
6711
6715
  // resurrect encodes the decay gate: ACTIVE target → { preserveUpdatedAt:
6712
6716
  // true } (main's normal re-observation path — bump proof_count without
@@ -6950,6 +6954,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
6950
6954
  const updated = await updateVaultMemoryOp(ctx.vaultCtx, decision.targetId, {
6951
6955
  content: existing.content,
6952
6956
  proofCountIncrement: 1,
6957
+ observationSourceIds: options.sourceChunkIds,
6953
6958
  sourceChunkIds: mergedSourceIds,
6954
6959
  // ACTIVE target → { preserveUpdatedAt: true } (main's normal re-observation
6955
6960
  // path); ARCHIVED (non-superseded, non-deleted) → { restore: true } and no
@@ -6999,6 +7004,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
6999
7004
  const updated = await updateVaultMemoryOp(ctx.vaultCtx, decision.targetId, {
7000
7005
  content: decision.content,
7001
7006
  proofCountIncrement: 1,
7007
+ observationSourceIds: options.sourceChunkIds,
7002
7008
  sourceChunkIds: mergedSourceIds,
7003
7009
  embedding: JSON.stringify(newEmbedding),
7004
7010
  embeddingModel: consolidatedModel,
@@ -7635,7 +7641,7 @@ function createAutoExtractor(options) {
7635
7641
  onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
7636
7642
  }
7637
7643
  });
7638
- if (outcome !== "empty-after-retry") {
7644
+ if (outcome !== "empty-after-retry" && failedCount === 0) {
7639
7645
  const advancedTo = window[window.length - 1].id;
7640
7646
  stateFor(conversationId).watermark = advancedTo;
7641
7647
  persistCursor(conversationId, messages, advancedTo, contiguous);
@@ -7790,6 +7796,7 @@ function toDecayInput(c) {
7790
7796
  eventTimeEnd: c.eventTimeEnd,
7791
7797
  eventTimeKind: c.eventTimeKind,
7792
7798
  updatedAt: c.updatedAt,
7799
+ lastObservedAt: c.lastObservedAt,
7793
7800
  archivedAt: c.archivedAt,
7794
7801
  source: c.source,
7795
7802
  trustTier: c.trustTier
@@ -7871,7 +7878,8 @@ function createDecaySweeper(options) {
7871
7878
  for (const c of toArchive) {
7872
7879
  const ok = await archiveVaultMemoryOp(vaultCtx, c.uniqueId, {
7873
7880
  now,
7874
- expectedUpdatedAt: c.updatedAt
7881
+ expectedUpdatedAt: c.updatedAt,
7882
+ expectedLastObservedAt: c.lastObservedAt ?? null
7875
7883
  });
7876
7884
  if (ok) archived++;
7877
7885
  }
@@ -8116,6 +8124,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
8116
8124
  budget: defaultBudget,
8117
8125
  ...toolOptions?.minScore !== void 0 && { minScore: toolOptions.minScore },
8118
8126
  ...toolOptions?.scopes && { scopes: toolOptions.scopes },
8127
+ ...toolOptions?.memoryIds !== void 0 && { memoryIds: toolOptions.memoryIds },
8119
8128
  ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId },
8120
8129
  ...toolOptions?.excludeConversationId && {
8121
8130
  excludeConversationId: toolOptions.excludeConversationId
@@ -9305,6 +9314,462 @@ function createMessageSourceResolver(ctx) {
9305
9314
  };
9306
9315
  }
9307
9316
 
9317
+ // src/lib/memory/context.ts
9318
+ function shouldRecallMemory(query) {
9319
+ const normalized = query.trim().toLowerCase().replace(/[.!?。!?¡¿]+$/u, "").replace(/^[¡¿]+/u, "");
9320
+ return normalized.length > 0 && !/^(hi|hi there|hello|hey|yes|no|yep|nope|sure|k|ok|okay|thanks|thank you|thank you very much|thanks a lot|cool|great|got it|sounds good|hola|buenos días|buenas|sí|si|gracias|muchas gracias|vale|bonjour|salut|oui|non|merci|merci beaucoup|d'accord|hallo|guten tag|ja|nein|danke|danke schön|alles klar|olá|obrigado|obrigada|ciao|grazie|sì|你好|谢谢|好的|こんにちは|ありがとう|안녕하세요|감사합니다)$/u.test(
9321
+ normalized
9322
+ );
9323
+ }
9324
+ async function assembleMemoryContext(options) {
9325
+ const { query, recall: recall2, loadFacts, loadSessionRefs } = options;
9326
+ const memoryIds = options.memoryIds ?? options.recallOptions?.memoryIds;
9327
+ const ranked = shouldRecallMemory(query);
9328
+ const degraded = [];
9329
+ const guard = async (lane, run, fallback) => {
9330
+ try {
9331
+ return await run();
9332
+ } catch (error) {
9333
+ degraded.push(lane);
9334
+ try {
9335
+ options.onDegraded?.(lane, error);
9336
+ } catch {
9337
+ }
9338
+ return fallback;
9339
+ }
9340
+ };
9341
+ const scoped = memoryIds !== void 0 || options.recallOptions?.scopes !== void 0 || options.recallOptions?.folderId !== void 0;
9342
+ const allowed = memoryIds !== void 0 ? new Set(memoryIds) : void 0;
9343
+ const filters = {
9344
+ ...options.recallOptions?.scopes && { scopes: options.recallOptions.scopes },
9345
+ ...options.recallOptions?.folderId !== void 0 && {
9346
+ folderId: options.recallOptions.folderId
9347
+ }
9348
+ };
9349
+ const active = (m) => !m.isDeleted && !m.archivedAt && !m.supersededBy && m.trustTier !== "quarantined" && (!allowed || allowed.has(m.uniqueId));
9350
+ const asItem = (m, lane, score = 1) => ({
9351
+ id: m.uniqueId,
9352
+ content: m.content,
9353
+ score,
9354
+ lane,
9355
+ sourceIds: m.sourceChunkIds ?? []
9356
+ });
9357
+ const empty = {
9358
+ memories: [],
9359
+ usedBudget: options.recallOptions?.budget ?? "low",
9360
+ reranked: false,
9361
+ candidateCount: 0
9362
+ };
9363
+ const [facts, episodes, profile, session] = await Promise.all([
9364
+ ranked ? guard(
9365
+ "fact",
9366
+ () => recall2(query, { ...options.recallOptions, types: ["fact"], limit: 20, memoryIds }),
9367
+ empty
9368
+ ) : empty,
9369
+ ranked && !scoped && options.includeEpisodes !== false ? guard(
9370
+ "episode",
9371
+ () => recall2(query, {
9372
+ ...options.recallOptions,
9373
+ types: ["chunk"],
9374
+ limit: 4,
9375
+ minScore: 0.5,
9376
+ onDiagnostics: void 0
9377
+ }),
9378
+ empty
9379
+ ) : empty,
9380
+ loadFacts ? guard(
9381
+ "profile",
9382
+ async () => {
9383
+ const stable = await loadFacts({
9384
+ ...filters,
9385
+ memoryIds,
9386
+ factTypes: ["identity", "preference", "constraint", "relationship"],
9387
+ limit: 8
9388
+ });
9389
+ const dynamic = await loadFacts({
9390
+ ...filters,
9391
+ memoryIds,
9392
+ factTypes: ["ongoing_context", "plan"],
9393
+ limit: 4
9394
+ });
9395
+ return [...stable, ...dynamic].filter(active).map((m) => asItem(m, "profile"));
9396
+ },
9397
+ []
9398
+ ) : [],
9399
+ loadFacts && loadSessionRefs ? guard(
9400
+ "session",
9401
+ async () => {
9402
+ const refs = (await loadSessionRefs()).filter((r) => !allowed || allowed.has(r.memoryId)).sort((a, b) => b.score - a.score).slice(0, 10);
9403
+ if (!refs.length) return [];
9404
+ const rows = await loadFacts({ ...filters, memoryIds: refs.map((r) => r.memoryId) });
9405
+ const byId = new Map(rows.filter(active).map((m) => [m.uniqueId, m]));
9406
+ return refs.flatMap((ref) => {
9407
+ const m = byId.get(ref.memoryId);
9408
+ return m ? [asItem(m, "session", ref.score)] : [];
9409
+ });
9410
+ },
9411
+ []
9412
+ ) : []
9413
+ ]);
9414
+ const toItems = (result, lane) => result.memories.filter((m) => !allowed || allowed.has(m.id)).map((m) => ({
9415
+ id: m.id,
9416
+ content: m.content,
9417
+ score: m.score,
9418
+ lane,
9419
+ ...lane === "fact" && { recalled: true },
9420
+ sourceIds: m.sourceChunkIds ?? (m.messageId ? [m.messageId] : [])
9421
+ }));
9422
+ const recalledFacts = toItems(facts, "fact");
9423
+ const recalledScores = /* @__PURE__ */ new Map();
9424
+ const recalledContentScores = /* @__PURE__ */ new Map();
9425
+ for (const item of recalledFacts) {
9426
+ recalledScores.set(item.id, Math.max(recalledScores.get(item.id) ?? -Infinity, item.score));
9427
+ const content = item.content.trim();
9428
+ recalledContentScores.set(
9429
+ content,
9430
+ Math.max(recalledContentScores.get(content) ?? -Infinity, item.score)
9431
+ );
9432
+ }
9433
+ const rankedProfile = profile.map((item) => {
9434
+ const score = Math.max(
9435
+ recalledScores.get(item.id) ?? -Infinity,
9436
+ recalledContentScores.get(item.content.trim()) ?? -Infinity
9437
+ );
9438
+ return score === -Infinity ? item : { ...item, score, recalled: true };
9439
+ });
9440
+ const lanes = [
9441
+ [rankedProfile, 2e3],
9442
+ [recalledFacts, 6e3],
9443
+ [toItems(episodes, "episode"), 3e3],
9444
+ [session, 1e3]
9445
+ ];
9446
+ const items = [];
9447
+ const seenIds = /* @__PURE__ */ new Set();
9448
+ const seenContent = /* @__PURE__ */ new Set();
9449
+ let remaining = Math.max(0, options.maxChars ?? 12e3);
9450
+ let truncated = false;
9451
+ for (const [candidates, laneBudget] of lanes) {
9452
+ let laneRemaining = Math.min(laneBudget, remaining);
9453
+ for (const item of candidates) {
9454
+ const content = item.content?.trim();
9455
+ const key = `${item.lane === "episode" ? "episode" : "fact"}:${item.id}`;
9456
+ if (!content || seenIds.has(key) || seenContent.has(content)) continue;
9457
+ if (content.length > laneRemaining) {
9458
+ truncated = true;
9459
+ continue;
9460
+ }
9461
+ items.push({ ...item, content });
9462
+ seenIds.add(key);
9463
+ seenContent.add(content);
9464
+ remaining -= content.length;
9465
+ laneRemaining -= content.length;
9466
+ }
9467
+ }
9468
+ return { items, ranked, rankedCount: recalledFacts.length, degraded, truncated };
9469
+ }
9470
+
9471
+ // src/lib/memory/durableExtraction.ts
9472
+ import { Q as Q4 } from "@nozbe/watermelondb";
9473
+ var MAX_JOBS_PER_PASS = 3;
9474
+ var MAX_ATTEMPTS = 3;
9475
+ function idsOf(job) {
9476
+ return JSON.parse(String(job._getRaw("message_ids")));
9477
+ }
9478
+ function seqOf(job) {
9479
+ const raw = job._getRaw("watermark_seq");
9480
+ return typeof raw === "number" && raw > 0 ? raw : void 0;
9481
+ }
9482
+ function createDurableAutoExtractor(options) {
9483
+ const vault = options.retainCtx.vaultCtx;
9484
+ const database = vault.database;
9485
+ const owner = vault.userId ?? vault.walletAddress;
9486
+ if (!owner) throw new Error("Durable extraction requires a user or wallet owner");
9487
+ const jobs = database.get(ExtractionJob.table);
9488
+ const storage = {
9489
+ database,
9490
+ messagesCollection: database.get("history"),
9491
+ conversationsCollection: database.get("conversations"),
9492
+ walletAddress: vault.walletAddress,
9493
+ signMessage: vault.signMessage,
9494
+ embeddedWalletSigner: vault.embeddedWalletSigner
9495
+ };
9496
+ const folderId = options.folderId ?? null;
9497
+ let disposed = false;
9498
+ let running = false;
9499
+ let wakeRequested = false;
9500
+ let timer;
9501
+ let writes = Promise.resolve();
9502
+ let storeFailures = 0;
9503
+ const attempts = /* @__PURE__ */ new Map();
9504
+ const reportError = (error, conversationId) => {
9505
+ try {
9506
+ options.onError?.(error instanceof Error ? error : new Error(String(error)), conversationId);
9507
+ } catch {
9508
+ }
9509
+ };
9510
+ const currentScope = () => {
9511
+ try {
9512
+ const value = typeof options.scope === "function" ? options.scope() : options.scope;
9513
+ return value ?? "private";
9514
+ } catch (error) {
9515
+ reportError(error);
9516
+ return "private";
9517
+ }
9518
+ };
9519
+ function schedule(delay) {
9520
+ if (disposed) return;
9521
+ if (running || timer !== void 0) {
9522
+ if (running) wakeRequested = true;
9523
+ return;
9524
+ }
9525
+ timer = setTimeout(() => {
9526
+ timer = void 0;
9527
+ void drain();
9528
+ }, delay);
9529
+ }
9530
+ async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
9531
+ let watchdog;
9532
+ try {
9533
+ return await Promise.race([
9534
+ new Promise((resolve, reject) => {
9535
+ const worker = createAutoExtractor({
9536
+ ...options,
9537
+ retainCtx: {
9538
+ ...options.retainCtx,
9539
+ vaultCtx: {
9540
+ ...vault,
9541
+ canWrite: async () => !disposed && await storage.conversationsCollection.query(Q4.where("conversation_id", conversationId), Q4.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(Q4.where("conversation_id", conversationId), Q4.where("id", Q4.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed
9542
+ }
9543
+ },
9544
+ // Never publish a private queued observation after a mode flip.
9545
+ scope: jobScope,
9546
+ folderId: jobFolderId,
9547
+ cursorStore: void 0,
9548
+ windowSize: 20,
9549
+ maxWindowSize: 20,
9550
+ onTurnComplete: resolve,
9551
+ onError: reject
9552
+ });
9553
+ worker.processTurn(messages, conversationId);
9554
+ worker.dispose();
9555
+ }),
9556
+ new Promise((_resolve, reject) => {
9557
+ watchdog = setTimeout(
9558
+ () => reject(new Error("Extraction did not settle in time; retained for retry")),
9559
+ options.batchTimeoutMs ?? 18e4
9560
+ );
9561
+ })
9562
+ ]);
9563
+ } finally {
9564
+ if (watchdog !== void 0) clearTimeout(watchdog);
9565
+ }
9566
+ }
9567
+ async function drain() {
9568
+ if (disposed || running) return;
9569
+ running = true;
9570
+ wakeRequested = false;
9571
+ let retry = false;
9572
+ let scheduleAfterSuccess = false;
9573
+ try {
9574
+ await writes;
9575
+ const pending = await jobs.query(Q4.where("owner_key", owner), Q4.where("message_ids", Q4.notEq("[]"))).fetch();
9576
+ storeFailures = 0;
9577
+ let processed = 0;
9578
+ for (const job of pending) {
9579
+ if (disposed) break;
9580
+ const attempted = attempts.get(job.id) ?? 0;
9581
+ if (attempted >= MAX_ATTEMPTS) continue;
9582
+ if (processed >= MAX_JOBS_PER_PASS) {
9583
+ scheduleAfterSuccess = true;
9584
+ break;
9585
+ }
9586
+ processed++;
9587
+ const conversationId = String(job._getRaw("conversation_id"));
9588
+ try {
9589
+ const conversations = await storage.conversationsCollection.query(Q4.where("conversation_id", conversationId), Q4.where("is_deleted", false)).fetchCount();
9590
+ if (!conversations) {
9591
+ await database.write(() => job.destroyPermanently());
9592
+ continue;
9593
+ }
9594
+ const ids = idsOf(job).slice(0, 20);
9595
+ const loaded = await Promise.all(ids.map((id) => getMessageOp(storage, id)));
9596
+ const missing = ids.filter((_id, index) => loaded[index] === null);
9597
+ if (missing.length) {
9598
+ if (attempted < MAX_ATTEMPTS - 1)
9599
+ throw new Error("Source messages not yet available; retained for retry");
9600
+ const dropped = new Set(missing);
9601
+ await database.write(
9602
+ () => job.update(
9603
+ (r) => r._setRaw(
9604
+ "message_ids",
9605
+ JSON.stringify(idsOf(job).filter((id) => !dropped.has(id)))
9606
+ )
9607
+ )
9608
+ );
9609
+ attempts.delete(job.id);
9610
+ scheduleAfterSuccess = true;
9611
+ reportError(
9612
+ new Error(
9613
+ `Dropped ${missing.length} unresolvable source id(s) to unblock extraction`
9614
+ ),
9615
+ conversationId
9616
+ );
9617
+ continue;
9618
+ }
9619
+ if (loaded.some(
9620
+ (message) => message && (message.decryptionStatus || isEncrypted(message.content))
9621
+ )) {
9622
+ throw new Error("Source messages are locked; retained for retry");
9623
+ }
9624
+ const messages = loaded.flatMap(
9625
+ (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
9626
+ );
9627
+ let success = messages.length === 0;
9628
+ if (messages.length) {
9629
+ const result = await extractBatch(
9630
+ messages,
9631
+ conversationId,
9632
+ ids,
9633
+ currentScope() === "private" ? "private" : String(job._getRaw("scope")),
9634
+ job._getRaw("folder_id") ?? null
9635
+ );
9636
+ success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
9637
+ options.onTurnComplete?.(result);
9638
+ }
9639
+ if (!success) throw new Error("Extraction batch incomplete; retained for retry");
9640
+ attempts.delete(job.id);
9641
+ const sequences = /* @__PURE__ */ new Map();
9642
+ for (const message of loaded)
9643
+ if (message && message.messageId > 0)
9644
+ sequences.set(message.uniqueId, message.messageId);
9645
+ await database.write(async () => {
9646
+ const current = idsOf(job);
9647
+ const acknowledged = ids.filter((id) => current.includes(id));
9648
+ if (!acknowledged.length) return;
9649
+ const done = new Set(acknowledged);
9650
+ const remaining = current.filter((id) => !done.has(id));
9651
+ if (remaining.length) {
9652
+ await job.update(
9653
+ (r) => r._setRaw("message_ids", JSON.stringify([...acknowledged.slice(-2), ...remaining]))
9654
+ );
9655
+ } else await job.update((r) => r._setRaw("message_ids", "[]"));
9656
+ const newest = acknowledged[acknowledged.length - 1];
9657
+ await job.update((r) => {
9658
+ r._setRaw("watermark", newest);
9659
+ const sequence = sequences.get(newest);
9660
+ if (sequence !== void 0) {
9661
+ const prior = seqOf(job);
9662
+ r._setRaw(
9663
+ "watermark_seq",
9664
+ prior === void 0 ? sequence : Math.max(prior, sequence)
9665
+ );
9666
+ }
9667
+ });
9668
+ });
9669
+ scheduleAfterSuccess = true;
9670
+ } catch (error) {
9671
+ attempts.set(job.id, attempted + 1);
9672
+ retry = true;
9673
+ reportError(error, conversationId);
9674
+ }
9675
+ }
9676
+ } catch (error) {
9677
+ retry = ++storeFailures < 3;
9678
+ reportError(error);
9679
+ } finally {
9680
+ running = false;
9681
+ const woken = wakeRequested;
9682
+ wakeRequested = false;
9683
+ if (retry || scheduleAfterSuccess || woken)
9684
+ schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
9685
+ }
9686
+ }
9687
+ schedule(0);
9688
+ return {
9689
+ processTurn(messages, conversationId) {
9690
+ if (disposed || !conversationId || !messages.length) return false;
9691
+ const ids = messages.map((m) => m.id);
9692
+ const scope = currentScope();
9693
+ writes = writes.then(
9694
+ () => database.write(async () => {
9695
+ const existing = await jobs.query(Q4.where("owner_key", owner), Q4.where("conversation_id", conversationId)).fetch();
9696
+ const job = existing.find(
9697
+ (candidate) => candidate._getRaw("scope") === scope && (candidate._getRaw("folder_id") ?? null) === folderId
9698
+ );
9699
+ const positions = new Map(ids.map((id, index) => [id, index]));
9700
+ const rows = await storage.messagesCollection.query(Q4.where("id", Q4.oneOf(ids))).fetch();
9701
+ const sequences = /* @__PURE__ */ new Map();
9702
+ for (const row of rows) {
9703
+ const sequence = Number(row._getRaw("message_id"));
9704
+ if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
9705
+ }
9706
+ let boundary = -1;
9707
+ let observedSeq = 0;
9708
+ const foreignSources = /* @__PURE__ */ new Set();
9709
+ for (const prior of existing) {
9710
+ const sources = [...idsOf(prior), String(prior._getRaw("watermark") ?? "")];
9711
+ for (const id of sources) {
9712
+ boundary = Math.max(boundary, positions.get(id) ?? -1);
9713
+ if (prior !== job) foreignSources.add(id);
9714
+ }
9715
+ observedSeq = Math.max(observedSeq, seqOf(prior) ?? 0);
9716
+ for (const id of idsOf(prior))
9717
+ observedSeq = Math.max(observedSeq, sequences.get(id) ?? 0);
9718
+ }
9719
+ if (observedSeq > 0)
9720
+ ids.forEach((id, index) => {
9721
+ const sequence = sequences.get(id);
9722
+ if (sequence !== void 0 && sequence <= observedSeq)
9723
+ boundary = Math.max(boundary, index);
9724
+ });
9725
+ if (!existing.length && options.cursorStore) {
9726
+ try {
9727
+ const cursor = options.cursorStore.get(conversationId);
9728
+ if (cursor) boundary = Math.max(boundary, positions.get(cursor) ?? -1);
9729
+ } catch (error) {
9730
+ reportError(error, conversationId);
9731
+ }
9732
+ }
9733
+ if (boundary < 0 && existing.some((prior) => idsOf(prior).length > 0 || seqOf(prior) !== void 0)) {
9734
+ reportError(
9735
+ new Error("Extraction boundary unresolved; skipped rather than re-observing"),
9736
+ conversationId
9737
+ );
9738
+ return;
9739
+ }
9740
+ const start = boundary >= 0 ? boundary + 1 : Math.max(0, ids.length - Math.max(1, options.windowSize ?? 6));
9741
+ if (start >= ids.length) return;
9742
+ const ownWatermark = job?._getRaw("watermark");
9743
+ const atOwnWatermark = !!job && boundary >= 0 && (ids[boundary] === ownWatermark || sequences.get(ids[boundary]) !== void 0 && sequences.get(ids[boundary]) === seqOf(job));
9744
+ const overlapStart = atOwnWatermark ? Math.max(0, boundary - 1) : start;
9745
+ const fresh = ids.slice(overlapStart).filter((id) => !foreignSources.has(id));
9746
+ if (!fresh.length) return;
9747
+ if (job) {
9748
+ await job.update(
9749
+ (r) => r._setRaw("message_ids", JSON.stringify([.../* @__PURE__ */ new Set([...idsOf(job), ...fresh])]))
9750
+ );
9751
+ attempts.delete(job.id);
9752
+ } else {
9753
+ await jobs.create((r) => {
9754
+ r._setRaw("owner_key", owner);
9755
+ r._setRaw("conversation_id", conversationId);
9756
+ r._setRaw("scope", scope);
9757
+ r._setRaw("folder_id", folderId);
9758
+ r._setRaw("message_ids", JSON.stringify([...new Set(fresh)]));
9759
+ });
9760
+ }
9761
+ })
9762
+ ).then(() => schedule(options.debounceMs ?? 2e4)).catch((error) => reportError(error, conversationId));
9763
+ return true;
9764
+ },
9765
+ isProcessing: () => running,
9766
+ dispose() {
9767
+ disposed = true;
9768
+ if (timer !== void 0) clearTimeout(timer);
9769
+ }
9770
+ };
9771
+ }
9772
+
9308
9773
  // src/lib/memoryEngine/quantization.ts
9309
9774
  var INT8_MAX = 127;
9310
9775
  function quantizeEmbedding(v) {
@@ -12706,10 +13171,25 @@ __decorateClass([
12706
13171
  ], VaultFolder.prototype, "context", 2);
12707
13172
 
12708
13173
  // src/lib/db/schema.ts
12709
- var SDK_SCHEMA_VERSION = 45;
13174
+ var SDK_SCHEMA_VERSION = 46;
12710
13175
  var sdkSchema = appSchema2({
12711
13176
  version: SDK_SCHEMA_VERSION,
12712
13177
  tables: [
13178
+ tableSchema2({
13179
+ name: "memory_extraction_jobs",
13180
+ columns: [
13181
+ { name: "owner_key", type: "string", isIndexed: true },
13182
+ { name: "conversation_id", type: "string", isIndexed: true },
13183
+ { name: "scope", type: "string" },
13184
+ { name: "message_ids", type: "string" },
13185
+ { name: "watermark", type: "string", isOptional: true },
13186
+ // Deletion-proof anchor: history.message_id of the newest acknowledged
13187
+ // source. `watermark` alone is an id, and deleting that message erased
13188
+ // the anchor — see the column note in extractionJobs/models.ts.
13189
+ { name: "watermark_seq", type: "number", isOptional: true },
13190
+ { name: "folder_id", type: "string", isOptional: true }
13191
+ ]
13192
+ }),
12713
13193
  // Chat storage tables
12714
13194
  tableSchema2({
12715
13195
  name: "history",
@@ -13714,10 +14194,28 @@ var sdkMigrations = schemaMigrations2({
13714
14194
  columns: [{ name: "media", type: "string", isOptional: true }]
13715
14195
  })
13716
14196
  ]
14197
+ },
14198
+ {
14199
+ toVersion: 46,
14200
+ steps: [
14201
+ createTable({
14202
+ name: "memory_extraction_jobs",
14203
+ columns: [
14204
+ { name: "owner_key", type: "string", isIndexed: true },
14205
+ { name: "conversation_id", type: "string", isIndexed: true },
14206
+ { name: "scope", type: "string" },
14207
+ { name: "message_ids", type: "string" },
14208
+ { name: "watermark", type: "string", isOptional: true },
14209
+ { name: "watermark_seq", type: "number", isOptional: true },
14210
+ { name: "folder_id", type: "string", isOptional: true }
14211
+ ]
14212
+ })
14213
+ ]
13717
14214
  }
13718
14215
  ]
13719
14216
  });
13720
14217
  var sdkModelClasses = [
14218
+ ExtractionJob,
13721
14219
  Message,
13722
14220
  Conversation,
13723
14221
  ConversationSummary,
@@ -13895,7 +14393,7 @@ var DatabaseManager = class {
13895
14393
  };
13896
14394
 
13897
14395
  // src/lib/db/conversationMemory/operations.ts
13898
- import { Q as Q4 } from "@nozbe/watermelondb";
14396
+ import { Q as Q5 } from "@nozbe/watermelondb";
13899
14397
  var MAX_PER_CONVERSATION = 200;
13900
14398
  function conversationMemoryToStored(row) {
13901
14399
  return {
@@ -13910,7 +14408,7 @@ async function addConversationMemoriesOp(ctx, conversationId, items) {
13910
14408
  if (!conversationId || items.length === 0) return;
13911
14409
  const now = Date.now();
13912
14410
  await ctx.database.write(async () => {
13913
- const existing = await ctx.conversationMemoryCollection.query(Q4.where("conversation_id", conversationId), Q4.sortBy("created_at", Q4.asc)).fetch();
14411
+ const existing = await ctx.conversationMemoryCollection.query(Q5.where("conversation_id", conversationId), Q5.sortBy("created_at", Q5.asc)).fetch();
13914
14412
  const seen = new Set(existing.map((r) => r.memoryId));
13915
14413
  const batchSeen = /* @__PURE__ */ new Set();
13916
14414
  const deduped = [];
@@ -13945,20 +14443,20 @@ async function addConversationMemoriesOp(ctx, conversationId, items) {
13945
14443
  }
13946
14444
  async function getConversationMemoriesOp(ctx, conversationId) {
13947
14445
  if (!conversationId) return [];
13948
- const rows = await ctx.conversationMemoryCollection.query(Q4.where("conversation_id", conversationId), Q4.sortBy("created_at", Q4.asc)).fetch();
14446
+ const rows = await ctx.conversationMemoryCollection.query(Q5.where("conversation_id", conversationId), Q5.sortBy("created_at", Q5.asc)).fetch();
13949
14447
  return rows.map(conversationMemoryToStored);
13950
14448
  }
13951
14449
  async function clearConversationMemoriesOp(ctx, conversationId) {
13952
14450
  if (!conversationId) return;
13953
14451
  await ctx.database.write(async () => {
13954
- const rows = await ctx.conversationMemoryCollection.query(Q4.where("conversation_id", conversationId)).fetch();
14452
+ const rows = await ctx.conversationMemoryCollection.query(Q5.where("conversation_id", conversationId)).fetch();
13955
14453
  if (rows.length === 0) return;
13956
14454
  await ctx.database.batch(...rows.map((r) => r.prepareDestroyPermanently()));
13957
14455
  });
13958
14456
  }
13959
14457
 
13960
14458
  // src/lib/db/vaultFolders/operations.ts
13961
- import { Q as Q5 } from "@nozbe/watermelondb";
14459
+ import { Q as Q6 } from "@nozbe/watermelondb";
13962
14460
  function folderToStored(folder) {
13963
14461
  return {
13964
14462
  uniqueId: folder.id,
@@ -13983,7 +14481,7 @@ async function createVaultFolderOp(ctx, opts) {
13983
14481
  return folderToStored(created);
13984
14482
  }
13985
14483
  async function getAllVaultFoldersOp(ctx) {
13986
- const results = await ctx.vaultFolderCollection.query(Q5.where("is_deleted", false), Q5.sortBy("created_at", Q5.desc)).fetch();
14484
+ const results = await ctx.vaultFolderCollection.query(Q6.where("is_deleted", false), Q6.sortBy("created_at", Q6.desc)).fetch();
13987
14485
  return results.map(folderToStored);
13988
14486
  }
13989
14487
  async function updateVaultFolderOp(ctx, id, opts) {
@@ -13992,7 +14490,7 @@ async function updateVaultFolderOp(ctx, id, opts) {
13992
14490
  if (record.isDeleted) return null;
13993
14491
  const scopeChanged = opts.scope !== void 0 && opts.scope !== record.scope;
13994
14492
  const updated = await ctx.database.write(async () => {
13995
- const memories = scopeChanged ? await ctx.vaultMemoryCollection.query(Q5.where("folder_id", id), Q5.where("is_deleted", false)).fetch() : [];
14493
+ const memories = scopeChanged ? await ctx.vaultMemoryCollection.query(Q6.where("folder_id", id), Q6.where("is_deleted", false)).fetch() : [];
13996
14494
  const updates = [];
13997
14495
  updates.push(
13998
14496
  record.prepareUpdate((r) => {
@@ -14020,7 +14518,7 @@ async function deleteVaultFolderOp(ctx, id) {
14020
14518
  const record = await ctx.vaultFolderCollection.find(id);
14021
14519
  if (record.isDeleted) return false;
14022
14520
  await ctx.database.write(async () => {
14023
- const memories = await ctx.vaultMemoryCollection.query(Q5.where("folder_id", id), Q5.where("is_deleted", false)).fetch();
14521
+ const memories = await ctx.vaultMemoryCollection.query(Q6.where("folder_id", id), Q6.where("is_deleted", false)).fetch();
14024
14522
  const preparedMemories = memories.map(
14025
14523
  (memory) => memory.prepareUpdate((r) => {
14026
14524
  r._setRaw("folder_id", null);
@@ -14089,7 +14587,7 @@ async function updateVaultFolderContextOp(ctx, id, context) {
14089
14587
  }
14090
14588
  }
14091
14589
  async function getVaultFolderMemoryCountOp(ctx, folderId) {
14092
- return await ctx.vaultMemoryCollection.query(Q5.where("folder_id", folderId), Q5.where("is_deleted", false)).fetchCount();
14590
+ return await ctx.vaultMemoryCollection.query(Q6.where("folder_id", folderId), Q6.where("is_deleted", false)).fetchCount();
14093
14591
  }
14094
14592
 
14095
14593
  // src/lib/db/vaultFolders/defaults.ts
@@ -15097,6 +15595,7 @@ export {
15097
15595
  WalletPoller,
15098
15596
  addConversationMemoriesOp,
15099
15597
  archiveVaultMemoryOp,
15598
+ assembleMemoryContext,
15100
15599
  backfillMemoryTopicsOp,
15101
15600
  buildNotionAuthUrl,
15102
15601
  capHopsForDensity,
@@ -15129,6 +15628,7 @@ export {
15129
15628
  createChunkVectorCache,
15130
15629
  createCryptoPricePreProcessor,
15131
15630
  createDecaySweeper,
15631
+ createDurableAutoExtractor,
15132
15632
  createLlmDecayClassifier,
15133
15633
  createLlmNeighborRefiner,
15134
15634
  createMemoryEngineTool,
@@ -15149,6 +15649,7 @@ export {
15149
15649
  decodeChunkVector,
15150
15650
  decryptConversationTitle,
15151
15651
  deleteAllVaultMemoriesForUserOp,
15652
+ deleteMessageOp,
15152
15653
  deleteVaultFolderOp,
15153
15654
  deleteVaultMemoryOp,
15154
15655
  dequantizeEmbedding,
@@ -15221,6 +15722,7 @@ export {
15221
15722
  setLogger,
15222
15723
  setMemoryVisibilityOp,
15223
15724
  shouldChunkMessage,
15725
+ shouldRecallMemory,
15224
15726
  stampTopicsExtractedAtOp,
15225
15727
  streamCancelPath,
15226
15728
  streamReplayPath,