@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
@@ -8,6 +8,7 @@ import {
8
8
  ENTITY_FANOUT,
9
9
  ENTITY_KINDS,
10
10
  EncryptionKeyMissingError,
11
+ ExtractionJob,
11
12
  LRUCache,
12
13
  MAX_HOPS,
13
14
  NODE_BUDGET,
@@ -54,6 +55,7 @@ import {
54
55
  decryptJsonField,
55
56
  deleteAllVaultMemoriesForUserOp,
56
57
  deleteConversationOp,
58
+ deleteMessageOp,
57
59
  deleteVaultMemoryOp,
58
60
  eagerEmbedContent,
59
61
  embedAllMessages,
@@ -141,7 +143,7 @@ import {
141
143
  updateVaultMemoryOp,
142
144
  upsertMessageOp,
143
145
  vaultMemoryToStored
144
- } from "./chunk-4DSDATYG.mjs";
146
+ } from "./chunk-FEDRFVHD.mjs";
145
147
  import {
146
148
  createStreamingDeAnonymizer,
147
149
  notifyConsolidationFallback,
@@ -330,7 +332,7 @@ var require_children = __commonJS({
330
332
  var _makeDecorator = _interopRequireDefault(require_makeDecorator());
331
333
  var _logError = _interopRequireDefault(require_logError());
332
334
  var _invariant = _interopRequireDefault(require_invariant());
333
- var Q7 = _interopRequireWildcard(require_QueryDescription());
335
+ var Q8 = _interopRequireWildcard(require_QueryDescription());
334
336
  function _getRequireWildcardCache(e) {
335
337
  if ("function" != typeof WeakMap) return null;
336
338
  var r = /* @__PURE__ */ new WeakMap(), t = /* @__PURE__ */ new WeakMap();
@@ -364,7 +366,7 @@ var require_children = __commonJS({
364
366
  var childCollection = model.collections.get(childTable);
365
367
  var association = model.constructor.associations[childTable];
366
368
  (0, _invariant.default)(association && "has_many" === association.type, "@children decorator used for a table that's not has_many");
367
- var query = childCollection.query(Q7.where(association.foreignKey, model.id));
369
+ var query = childCollection.query(Q8.where(association.foreignKey, model.id));
368
370
  that._childrenQueryCache[childTable] = query;
369
371
  return query;
370
372
  },
@@ -1334,10 +1336,25 @@ __decorateClass([
1334
1336
  ], VaultFolder.prototype, "context", 2);
1335
1337
 
1336
1338
  // src/lib/db/schema.ts
1337
- var SDK_SCHEMA_VERSION = 45;
1339
+ var SDK_SCHEMA_VERSION = 46;
1338
1340
  var sdkSchema = (0, import_watermelondb12.appSchema)({
1339
1341
  version: SDK_SCHEMA_VERSION,
1340
1342
  tables: [
1343
+ (0, import_watermelondb12.tableSchema)({
1344
+ name: "memory_extraction_jobs",
1345
+ columns: [
1346
+ { name: "owner_key", type: "string", isIndexed: true },
1347
+ { name: "conversation_id", type: "string", isIndexed: true },
1348
+ { name: "scope", type: "string" },
1349
+ { name: "message_ids", type: "string" },
1350
+ { name: "watermark", type: "string", isOptional: true },
1351
+ // Deletion-proof anchor: history.message_id of the newest acknowledged
1352
+ // source. `watermark` alone is an id, and deleting that message erased
1353
+ // the anchor — see the column note in extractionJobs/models.ts.
1354
+ { name: "watermark_seq", type: "number", isOptional: true },
1355
+ { name: "folder_id", type: "string", isOptional: true }
1356
+ ]
1357
+ }),
1341
1358
  // Chat storage tables
1342
1359
  (0, import_watermelondb12.tableSchema)({
1343
1360
  name: "history",
@@ -2342,10 +2359,28 @@ var sdkMigrations = (0, import_migrations.schemaMigrations)({
2342
2359
  columns: [{ name: "media", type: "string", isOptional: true }]
2343
2360
  })
2344
2361
  ]
2362
+ },
2363
+ {
2364
+ toVersion: 46,
2365
+ steps: [
2366
+ (0, import_migrations.createTable)({
2367
+ name: "memory_extraction_jobs",
2368
+ columns: [
2369
+ { name: "owner_key", type: "string", isIndexed: true },
2370
+ { name: "conversation_id", type: "string", isIndexed: true },
2371
+ { name: "scope", type: "string" },
2372
+ { name: "message_ids", type: "string" },
2373
+ { name: "watermark", type: "string", isOptional: true },
2374
+ { name: "watermark_seq", type: "number", isOptional: true },
2375
+ { name: "folder_id", type: "string", isOptional: true }
2376
+ ]
2377
+ })
2378
+ ]
2345
2379
  }
2346
2380
  ]
2347
2381
  });
2348
2382
  var sdkModelClasses = [
2383
+ ExtractionJob,
2349
2384
  Message,
2350
2385
  Conversation,
2351
2386
  ConversationSummary,
@@ -3696,7 +3731,8 @@ function classifyDecay(m, now, policy) {
3696
3731
  if (m.eventTimeEnd < now - resolved.pastEventGraceMs) return "archive";
3697
3732
  if (m.eventTimeEnd >= now) return "keep";
3698
3733
  }
3699
- if (now - m.updatedAt > ttlForType(m.factType, resolved)) return "archive";
3734
+ const observedAt = Number.isFinite(m.lastObservedAt) ? m.lastObservedAt : m.updatedAt;
3735
+ if (now - Math.max(m.updatedAt, observedAt) > ttlForType(m.factType, resolved)) return "archive";
3700
3736
  return "keep";
3701
3737
  }
3702
3738
 
@@ -5557,6 +5593,7 @@ async function retain(content, ctx, options = {}) {
5557
5593
  const updated = await updateVaultMemoryOp(ctx.vaultCtx, targetId, {
5558
5594
  content: existing.content,
5559
5595
  proofCountIncrement: 1,
5596
+ observationSourceIds: options.sourceChunkIds,
5560
5597
  sourceChunkIds: mergedSourceIds,
5561
5598
  // resurrect encodes the decay gate: ACTIVE target → { preserveUpdatedAt:
5562
5599
  // true } (main's normal re-observation path — bump proof_count without
@@ -5800,6 +5837,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
5800
5837
  const updated = await updateVaultMemoryOp(ctx.vaultCtx, decision.targetId, {
5801
5838
  content: existing.content,
5802
5839
  proofCountIncrement: 1,
5840
+ observationSourceIds: options.sourceChunkIds,
5803
5841
  sourceChunkIds: mergedSourceIds,
5804
5842
  // ACTIVE target → { preserveUpdatedAt: true } (main's normal re-observation
5805
5843
  // path); ARCHIVED (non-superseded, non-deleted) → { restore: true } and no
@@ -5849,6 +5887,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
5849
5887
  const updated = await updateVaultMemoryOp(ctx.vaultCtx, decision.targetId, {
5850
5888
  content: decision.content,
5851
5889
  proofCountIncrement: 1,
5890
+ observationSourceIds: options.sourceChunkIds,
5852
5891
  sourceChunkIds: mergedSourceIds,
5853
5892
  embedding: JSON.stringify(newEmbedding),
5854
5893
  embeddingModel: consolidatedModel,
@@ -6485,7 +6524,7 @@ function createAutoExtractor(options) {
6485
6524
  onQuarantined: (info) => options.onMemoryQuarantined?.({ ...info, conversationId })
6486
6525
  }
6487
6526
  });
6488
- if (outcome !== "empty-after-retry") {
6527
+ if (outcome !== "empty-after-retry" && failedCount === 0) {
6489
6528
  const advancedTo = window2[window2.length - 1].id;
6490
6529
  stateFor(conversationId).watermark = advancedTo;
6491
6530
  persistCursor(conversationId, messages, advancedTo, contiguous);
@@ -6640,6 +6679,7 @@ function toDecayInput(c) {
6640
6679
  eventTimeEnd: c.eventTimeEnd,
6641
6680
  eventTimeKind: c.eventTimeKind,
6642
6681
  updatedAt: c.updatedAt,
6682
+ lastObservedAt: c.lastObservedAt,
6643
6683
  archivedAt: c.archivedAt,
6644
6684
  source: c.source,
6645
6685
  trustTier: c.trustTier
@@ -6721,7 +6761,8 @@ function createDecaySweeper(options) {
6721
6761
  for (const c of toArchive) {
6722
6762
  const ok = await archiveVaultMemoryOp(vaultCtx, c.uniqueId, {
6723
6763
  now,
6724
- expectedUpdatedAt: c.updatedAt
6764
+ expectedUpdatedAt: c.updatedAt,
6765
+ expectedLastObservedAt: c.lastObservedAt ?? null
6725
6766
  });
6726
6767
  if (ok) archived++;
6727
6768
  }
@@ -6966,6 +7007,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
6966
7007
  budget: defaultBudget,
6967
7008
  ...toolOptions?.minScore !== void 0 && { minScore: toolOptions.minScore },
6968
7009
  ...toolOptions?.scopes && { scopes: toolOptions.scopes },
7010
+ ...toolOptions?.memoryIds !== void 0 && { memoryIds: toolOptions.memoryIds },
6969
7011
  ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId },
6970
7012
  ...toolOptions?.excludeConversationId && {
6971
7013
  excludeConversationId: toolOptions.excludeConversationId
@@ -8155,6 +8197,462 @@ function createMessageSourceResolver(ctx) {
8155
8197
  };
8156
8198
  }
8157
8199
 
8200
+ // src/lib/memory/context.ts
8201
+ function shouldRecallMemory(query) {
8202
+ const normalized = query.trim().toLowerCase().replace(/[.!?。!?¡¿]+$/u, "").replace(/^[¡¿]+/u, "");
8203
+ 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(
8204
+ normalized
8205
+ );
8206
+ }
8207
+ async function assembleMemoryContext(options) {
8208
+ const { query, recall: recall2, loadFacts, loadSessionRefs } = options;
8209
+ const memoryIds = options.memoryIds ?? options.recallOptions?.memoryIds;
8210
+ const ranked = shouldRecallMemory(query);
8211
+ const degraded = [];
8212
+ const guard = async (lane, run, fallback) => {
8213
+ try {
8214
+ return await run();
8215
+ } catch (error) {
8216
+ degraded.push(lane);
8217
+ try {
8218
+ options.onDegraded?.(lane, error);
8219
+ } catch {
8220
+ }
8221
+ return fallback;
8222
+ }
8223
+ };
8224
+ const scoped = memoryIds !== void 0 || options.recallOptions?.scopes !== void 0 || options.recallOptions?.folderId !== void 0;
8225
+ const allowed = memoryIds !== void 0 ? new Set(memoryIds) : void 0;
8226
+ const filters = {
8227
+ ...options.recallOptions?.scopes && { scopes: options.recallOptions.scopes },
8228
+ ...options.recallOptions?.folderId !== void 0 && {
8229
+ folderId: options.recallOptions.folderId
8230
+ }
8231
+ };
8232
+ const active = (m) => !m.isDeleted && !m.archivedAt && !m.supersededBy && m.trustTier !== "quarantined" && (!allowed || allowed.has(m.uniqueId));
8233
+ const asItem = (m, lane, score = 1) => ({
8234
+ id: m.uniqueId,
8235
+ content: m.content,
8236
+ score,
8237
+ lane,
8238
+ sourceIds: m.sourceChunkIds ?? []
8239
+ });
8240
+ const empty = {
8241
+ memories: [],
8242
+ usedBudget: options.recallOptions?.budget ?? "low",
8243
+ reranked: false,
8244
+ candidateCount: 0
8245
+ };
8246
+ const [facts, episodes, profile, session] = await Promise.all([
8247
+ ranked ? guard(
8248
+ "fact",
8249
+ () => recall2(query, { ...options.recallOptions, types: ["fact"], limit: 20, memoryIds }),
8250
+ empty
8251
+ ) : empty,
8252
+ ranked && !scoped && options.includeEpisodes !== false ? guard(
8253
+ "episode",
8254
+ () => recall2(query, {
8255
+ ...options.recallOptions,
8256
+ types: ["chunk"],
8257
+ limit: 4,
8258
+ minScore: 0.5,
8259
+ onDiagnostics: void 0
8260
+ }),
8261
+ empty
8262
+ ) : empty,
8263
+ loadFacts ? guard(
8264
+ "profile",
8265
+ async () => {
8266
+ const stable = await loadFacts({
8267
+ ...filters,
8268
+ memoryIds,
8269
+ factTypes: ["identity", "preference", "constraint", "relationship"],
8270
+ limit: 8
8271
+ });
8272
+ const dynamic = await loadFacts({
8273
+ ...filters,
8274
+ memoryIds,
8275
+ factTypes: ["ongoing_context", "plan"],
8276
+ limit: 4
8277
+ });
8278
+ return [...stable, ...dynamic].filter(active).map((m) => asItem(m, "profile"));
8279
+ },
8280
+ []
8281
+ ) : [],
8282
+ loadFacts && loadSessionRefs ? guard(
8283
+ "session",
8284
+ async () => {
8285
+ const refs = (await loadSessionRefs()).filter((r) => !allowed || allowed.has(r.memoryId)).sort((a, b) => b.score - a.score).slice(0, 10);
8286
+ if (!refs.length) return [];
8287
+ const rows = await loadFacts({ ...filters, memoryIds: refs.map((r) => r.memoryId) });
8288
+ const byId = new Map(rows.filter(active).map((m) => [m.uniqueId, m]));
8289
+ return refs.flatMap((ref) => {
8290
+ const m = byId.get(ref.memoryId);
8291
+ return m ? [asItem(m, "session", ref.score)] : [];
8292
+ });
8293
+ },
8294
+ []
8295
+ ) : []
8296
+ ]);
8297
+ const toItems = (result, lane) => result.memories.filter((m) => !allowed || allowed.has(m.id)).map((m) => ({
8298
+ id: m.id,
8299
+ content: m.content,
8300
+ score: m.score,
8301
+ lane,
8302
+ ...lane === "fact" && { recalled: true },
8303
+ sourceIds: m.sourceChunkIds ?? (m.messageId ? [m.messageId] : [])
8304
+ }));
8305
+ const recalledFacts = toItems(facts, "fact");
8306
+ const recalledScores = /* @__PURE__ */ new Map();
8307
+ const recalledContentScores = /* @__PURE__ */ new Map();
8308
+ for (const item of recalledFacts) {
8309
+ recalledScores.set(item.id, Math.max(recalledScores.get(item.id) ?? -Infinity, item.score));
8310
+ const content = item.content.trim();
8311
+ recalledContentScores.set(
8312
+ content,
8313
+ Math.max(recalledContentScores.get(content) ?? -Infinity, item.score)
8314
+ );
8315
+ }
8316
+ const rankedProfile = profile.map((item) => {
8317
+ const score = Math.max(
8318
+ recalledScores.get(item.id) ?? -Infinity,
8319
+ recalledContentScores.get(item.content.trim()) ?? -Infinity
8320
+ );
8321
+ return score === -Infinity ? item : { ...item, score, recalled: true };
8322
+ });
8323
+ const lanes = [
8324
+ [rankedProfile, 2e3],
8325
+ [recalledFacts, 6e3],
8326
+ [toItems(episodes, "episode"), 3e3],
8327
+ [session, 1e3]
8328
+ ];
8329
+ const items = [];
8330
+ const seenIds = /* @__PURE__ */ new Set();
8331
+ const seenContent = /* @__PURE__ */ new Set();
8332
+ let remaining = Math.max(0, options.maxChars ?? 12e3);
8333
+ let truncated = false;
8334
+ for (const [candidates, laneBudget] of lanes) {
8335
+ let laneRemaining = Math.min(laneBudget, remaining);
8336
+ for (const item of candidates) {
8337
+ const content = item.content?.trim();
8338
+ const key = `${item.lane === "episode" ? "episode" : "fact"}:${item.id}`;
8339
+ if (!content || seenIds.has(key) || seenContent.has(content)) continue;
8340
+ if (content.length > laneRemaining) {
8341
+ truncated = true;
8342
+ continue;
8343
+ }
8344
+ items.push({ ...item, content });
8345
+ seenIds.add(key);
8346
+ seenContent.add(content);
8347
+ remaining -= content.length;
8348
+ laneRemaining -= content.length;
8349
+ }
8350
+ }
8351
+ return { items, ranked, rankedCount: recalledFacts.length, degraded, truncated };
8352
+ }
8353
+
8354
+ // src/lib/memory/durableExtraction.ts
8355
+ var import_watermelondb20 = __toESM(require_watermelondb());
8356
+ var MAX_JOBS_PER_PASS = 3;
8357
+ var MAX_ATTEMPTS = 3;
8358
+ function idsOf(job) {
8359
+ return JSON.parse(String(job._getRaw("message_ids")));
8360
+ }
8361
+ function seqOf(job) {
8362
+ const raw = job._getRaw("watermark_seq");
8363
+ return typeof raw === "number" && raw > 0 ? raw : void 0;
8364
+ }
8365
+ function createDurableAutoExtractor(options) {
8366
+ const vault = options.retainCtx.vaultCtx;
8367
+ const database = vault.database;
8368
+ const owner = vault.userId ?? vault.walletAddress;
8369
+ if (!owner) throw new Error("Durable extraction requires a user or wallet owner");
8370
+ const jobs = database.get(ExtractionJob.table);
8371
+ const storage = {
8372
+ database,
8373
+ messagesCollection: database.get("history"),
8374
+ conversationsCollection: database.get("conversations"),
8375
+ walletAddress: vault.walletAddress,
8376
+ signMessage: vault.signMessage,
8377
+ embeddedWalletSigner: vault.embeddedWalletSigner
8378
+ };
8379
+ const folderId = options.folderId ?? null;
8380
+ let disposed = false;
8381
+ let running = false;
8382
+ let wakeRequested = false;
8383
+ let timer;
8384
+ let writes = Promise.resolve();
8385
+ let storeFailures = 0;
8386
+ const attempts = /* @__PURE__ */ new Map();
8387
+ const reportError = (error, conversationId) => {
8388
+ try {
8389
+ options.onError?.(error instanceof Error ? error : new Error(String(error)), conversationId);
8390
+ } catch {
8391
+ }
8392
+ };
8393
+ const currentScope = () => {
8394
+ try {
8395
+ const value = typeof options.scope === "function" ? options.scope() : options.scope;
8396
+ return value ?? "private";
8397
+ } catch (error) {
8398
+ reportError(error);
8399
+ return "private";
8400
+ }
8401
+ };
8402
+ function schedule(delay) {
8403
+ if (disposed) return;
8404
+ if (running || timer !== void 0) {
8405
+ if (running) wakeRequested = true;
8406
+ return;
8407
+ }
8408
+ timer = setTimeout(() => {
8409
+ timer = void 0;
8410
+ void drain();
8411
+ }, delay);
8412
+ }
8413
+ async function extractBatch(messages, conversationId, ids, jobScope, jobFolderId) {
8414
+ let watchdog;
8415
+ try {
8416
+ return await Promise.race([
8417
+ new Promise((resolve, reject) => {
8418
+ const worker = createAutoExtractor({
8419
+ ...options,
8420
+ retainCtx: {
8421
+ ...options.retainCtx,
8422
+ vaultCtx: {
8423
+ ...vault,
8424
+ canWrite: async () => !disposed && await storage.conversationsCollection.query(import_watermelondb20.Q.where("conversation_id", conversationId), import_watermelondb20.Q.where("is_deleted", false)).fetchCount() > 0 && await storage.messagesCollection.query(import_watermelondb20.Q.where("conversation_id", conversationId), import_watermelondb20.Q.where("id", import_watermelondb20.Q.oneOf(ids))).fetchCount() === ids.length && (!vault.canWrite || await vault.canWrite()) && !disposed
8425
+ }
8426
+ },
8427
+ // Never publish a private queued observation after a mode flip.
8428
+ scope: jobScope,
8429
+ folderId: jobFolderId,
8430
+ cursorStore: void 0,
8431
+ windowSize: 20,
8432
+ maxWindowSize: 20,
8433
+ onTurnComplete: resolve,
8434
+ onError: reject
8435
+ });
8436
+ worker.processTurn(messages, conversationId);
8437
+ worker.dispose();
8438
+ }),
8439
+ new Promise((_resolve, reject) => {
8440
+ watchdog = setTimeout(
8441
+ () => reject(new Error("Extraction did not settle in time; retained for retry")),
8442
+ options.batchTimeoutMs ?? 18e4
8443
+ );
8444
+ })
8445
+ ]);
8446
+ } finally {
8447
+ if (watchdog !== void 0) clearTimeout(watchdog);
8448
+ }
8449
+ }
8450
+ async function drain() {
8451
+ if (disposed || running) return;
8452
+ running = true;
8453
+ wakeRequested = false;
8454
+ let retry = false;
8455
+ let scheduleAfterSuccess = false;
8456
+ try {
8457
+ await writes;
8458
+ const pending = await jobs.query(import_watermelondb20.Q.where("owner_key", owner), import_watermelondb20.Q.where("message_ids", import_watermelondb20.Q.notEq("[]"))).fetch();
8459
+ storeFailures = 0;
8460
+ let processed = 0;
8461
+ for (const job of pending) {
8462
+ if (disposed) break;
8463
+ const attempted = attempts.get(job.id) ?? 0;
8464
+ if (attempted >= MAX_ATTEMPTS) continue;
8465
+ if (processed >= MAX_JOBS_PER_PASS) {
8466
+ scheduleAfterSuccess = true;
8467
+ break;
8468
+ }
8469
+ processed++;
8470
+ const conversationId = String(job._getRaw("conversation_id"));
8471
+ try {
8472
+ const conversations = await storage.conversationsCollection.query(import_watermelondb20.Q.where("conversation_id", conversationId), import_watermelondb20.Q.where("is_deleted", false)).fetchCount();
8473
+ if (!conversations) {
8474
+ await database.write(() => job.destroyPermanently());
8475
+ continue;
8476
+ }
8477
+ const ids = idsOf(job).slice(0, 20);
8478
+ const loaded = await Promise.all(ids.map((id) => getMessageOp(storage, id)));
8479
+ const missing = ids.filter((_id, index) => loaded[index] === null);
8480
+ if (missing.length) {
8481
+ if (attempted < MAX_ATTEMPTS - 1)
8482
+ throw new Error("Source messages not yet available; retained for retry");
8483
+ const dropped = new Set(missing);
8484
+ await database.write(
8485
+ () => job.update(
8486
+ (r) => r._setRaw(
8487
+ "message_ids",
8488
+ JSON.stringify(idsOf(job).filter((id) => !dropped.has(id)))
8489
+ )
8490
+ )
8491
+ );
8492
+ attempts.delete(job.id);
8493
+ scheduleAfterSuccess = true;
8494
+ reportError(
8495
+ new Error(
8496
+ `Dropped ${missing.length} unresolvable source id(s) to unblock extraction`
8497
+ ),
8498
+ conversationId
8499
+ );
8500
+ continue;
8501
+ }
8502
+ if (loaded.some(
8503
+ (message) => message && (message.decryptionStatus || isEncrypted(message.content))
8504
+ )) {
8505
+ throw new Error("Source messages are locked; retained for retry");
8506
+ }
8507
+ const messages = loaded.flatMap(
8508
+ (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
8509
+ );
8510
+ let success = messages.length === 0;
8511
+ if (messages.length) {
8512
+ const result = await extractBatch(
8513
+ messages,
8514
+ conversationId,
8515
+ ids,
8516
+ currentScope() === "private" ? "private" : String(job._getRaw("scope")),
8517
+ job._getRaw("folder_id") ?? null
8518
+ );
8519
+ success = result.failedCount === 0 && result.outcome !== "empty-after-retry";
8520
+ options.onTurnComplete?.(result);
8521
+ }
8522
+ if (!success) throw new Error("Extraction batch incomplete; retained for retry");
8523
+ attempts.delete(job.id);
8524
+ const sequences = /* @__PURE__ */ new Map();
8525
+ for (const message of loaded)
8526
+ if (message && message.messageId > 0)
8527
+ sequences.set(message.uniqueId, message.messageId);
8528
+ await database.write(async () => {
8529
+ const current = idsOf(job);
8530
+ const acknowledged = ids.filter((id) => current.includes(id));
8531
+ if (!acknowledged.length) return;
8532
+ const done = new Set(acknowledged);
8533
+ const remaining = current.filter((id) => !done.has(id));
8534
+ if (remaining.length) {
8535
+ await job.update(
8536
+ (r) => r._setRaw("message_ids", JSON.stringify([...acknowledged.slice(-2), ...remaining]))
8537
+ );
8538
+ } else await job.update((r) => r._setRaw("message_ids", "[]"));
8539
+ const newest = acknowledged[acknowledged.length - 1];
8540
+ await job.update((r) => {
8541
+ r._setRaw("watermark", newest);
8542
+ const sequence = sequences.get(newest);
8543
+ if (sequence !== void 0) {
8544
+ const prior = seqOf(job);
8545
+ r._setRaw(
8546
+ "watermark_seq",
8547
+ prior === void 0 ? sequence : Math.max(prior, sequence)
8548
+ );
8549
+ }
8550
+ });
8551
+ });
8552
+ scheduleAfterSuccess = true;
8553
+ } catch (error) {
8554
+ attempts.set(job.id, attempted + 1);
8555
+ retry = true;
8556
+ reportError(error, conversationId);
8557
+ }
8558
+ }
8559
+ } catch (error) {
8560
+ retry = ++storeFailures < 3;
8561
+ reportError(error);
8562
+ } finally {
8563
+ running = false;
8564
+ const woken = wakeRequested;
8565
+ wakeRequested = false;
8566
+ if (retry || scheduleAfterSuccess || woken)
8567
+ schedule(retry ? options.retryDelayMs ?? 3e4 : 0);
8568
+ }
8569
+ }
8570
+ schedule(0);
8571
+ return {
8572
+ processTurn(messages, conversationId) {
8573
+ if (disposed || !conversationId || !messages.length) return false;
8574
+ const ids = messages.map((m) => m.id);
8575
+ const scope = currentScope();
8576
+ writes = writes.then(
8577
+ () => database.write(async () => {
8578
+ const existing = await jobs.query(import_watermelondb20.Q.where("owner_key", owner), import_watermelondb20.Q.where("conversation_id", conversationId)).fetch();
8579
+ const job = existing.find(
8580
+ (candidate) => candidate._getRaw("scope") === scope && (candidate._getRaw("folder_id") ?? null) === folderId
8581
+ );
8582
+ const positions = new Map(ids.map((id, index) => [id, index]));
8583
+ const rows = await storage.messagesCollection.query(import_watermelondb20.Q.where("id", import_watermelondb20.Q.oneOf(ids))).fetch();
8584
+ const sequences = /* @__PURE__ */ new Map();
8585
+ for (const row of rows) {
8586
+ const sequence = Number(row._getRaw("message_id"));
8587
+ if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
8588
+ }
8589
+ let boundary = -1;
8590
+ let observedSeq = 0;
8591
+ const foreignSources = /* @__PURE__ */ new Set();
8592
+ for (const prior of existing) {
8593
+ const sources = [...idsOf(prior), String(prior._getRaw("watermark") ?? "")];
8594
+ for (const id of sources) {
8595
+ boundary = Math.max(boundary, positions.get(id) ?? -1);
8596
+ if (prior !== job) foreignSources.add(id);
8597
+ }
8598
+ observedSeq = Math.max(observedSeq, seqOf(prior) ?? 0);
8599
+ for (const id of idsOf(prior))
8600
+ observedSeq = Math.max(observedSeq, sequences.get(id) ?? 0);
8601
+ }
8602
+ if (observedSeq > 0)
8603
+ ids.forEach((id, index) => {
8604
+ const sequence = sequences.get(id);
8605
+ if (sequence !== void 0 && sequence <= observedSeq)
8606
+ boundary = Math.max(boundary, index);
8607
+ });
8608
+ if (!existing.length && options.cursorStore) {
8609
+ try {
8610
+ const cursor = options.cursorStore.get(conversationId);
8611
+ if (cursor) boundary = Math.max(boundary, positions.get(cursor) ?? -1);
8612
+ } catch (error) {
8613
+ reportError(error, conversationId);
8614
+ }
8615
+ }
8616
+ if (boundary < 0 && existing.some((prior) => idsOf(prior).length > 0 || seqOf(prior) !== void 0)) {
8617
+ reportError(
8618
+ new Error("Extraction boundary unresolved; skipped rather than re-observing"),
8619
+ conversationId
8620
+ );
8621
+ return;
8622
+ }
8623
+ const start = boundary >= 0 ? boundary + 1 : Math.max(0, ids.length - Math.max(1, options.windowSize ?? 6));
8624
+ if (start >= ids.length) return;
8625
+ const ownWatermark = job?._getRaw("watermark");
8626
+ const atOwnWatermark = !!job && boundary >= 0 && (ids[boundary] === ownWatermark || sequences.get(ids[boundary]) !== void 0 && sequences.get(ids[boundary]) === seqOf(job));
8627
+ const overlapStart = atOwnWatermark ? Math.max(0, boundary - 1) : start;
8628
+ const fresh = ids.slice(overlapStart).filter((id) => !foreignSources.has(id));
8629
+ if (!fresh.length) return;
8630
+ if (job) {
8631
+ await job.update(
8632
+ (r) => r._setRaw("message_ids", JSON.stringify([.../* @__PURE__ */ new Set([...idsOf(job), ...fresh])]))
8633
+ );
8634
+ attempts.delete(job.id);
8635
+ } else {
8636
+ await jobs.create((r) => {
8637
+ r._setRaw("owner_key", owner);
8638
+ r._setRaw("conversation_id", conversationId);
8639
+ r._setRaw("scope", scope);
8640
+ r._setRaw("folder_id", folderId);
8641
+ r._setRaw("message_ids", JSON.stringify([...new Set(fresh)]));
8642
+ });
8643
+ }
8644
+ })
8645
+ ).then(() => schedule(options.debounceMs ?? 2e4)).catch((error) => reportError(error, conversationId));
8646
+ return true;
8647
+ },
8648
+ isProcessing: () => running,
8649
+ dispose() {
8650
+ disposed = true;
8651
+ if (timer !== void 0) clearTimeout(timer);
8652
+ }
8653
+ };
8654
+ }
8655
+
8158
8656
  // src/tools/appBuilderPrompt.ts
8159
8657
  var APP_BUILDER_PROMPT = `App Builder tools (create_file, patch_file, and the rest described below) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should build anything.
8160
8658
 
@@ -13813,6 +14311,7 @@ export {
13813
14311
  activatedToolSetNames,
13814
14312
  analyzeStyle,
13815
14313
  archiveVaultMemoryOp,
14314
+ assembleMemoryContext,
13816
14315
  backfillMemoryTopicsOp,
13817
14316
  buildConnectorGuidance,
13818
14317
  buildDeniedToolsRider,
@@ -13847,6 +14346,7 @@ export {
13847
14346
  createConversationOp,
13848
14347
  createCryptoPricePreProcessor,
13849
14348
  createDecaySweeper,
14349
+ createDurableAutoExtractor,
13850
14350
  createLlmDecayClassifier,
13851
14351
  createLlmNeighborRefiner,
13852
14352
  createMediaBatchOp,
@@ -13880,6 +14380,7 @@ export {
13880
14380
  deleteMediaByConversationOp,
13881
14381
  deleteMediaByMessageOp,
13882
14382
  deleteMediaOp,
14383
+ deleteMessageOp,
13883
14384
  deleteModelPreferenceOp,
13884
14385
  deleteProjectOp,
13885
14386
  deleteUserPreferenceOp,
@@ -14004,6 +14505,7 @@ export {
14004
14505
  setUserPreferenceOp,
14005
14506
  shouldAnalyzeStyle,
14006
14507
  shouldChunkMessage,
14508
+ shouldRecallMemory,
14007
14509
  shouldRefreshTools,
14008
14510
  splitMessagesAtThreshold,
14009
14511
  stampTopicsExtractedAtOp,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-4DSDATYG.mjs";
3
+ } from "./chunk-FEDRFVHD.mjs";
4
4
  import "./chunk-YXL5VKCO.mjs";
5
5
  import "./chunk-3RPGXXPZ.mjs";
6
6
  export {
@@ -429,7 +429,13 @@ type RecallDegradation =
429
429
  | "graph-lane-failed"
430
430
  /** The W6 temporal side lane threw and was dropped. Same posture as the graph
431
431
  * lane: recall still returned, ranked without the temporal signal. */
432
- | "temporal-lane-failed";
432
+ | "temporal-lane-failed"
433
+ /** {@link RecallOptions.memoryIds} was set and `types` asked for `"chunk"`.
434
+ * Conversation excerpts have no equivalent membership filter, so running the
435
+ * chunk lane would escape the topic/folder scope — it is dropped instead.
436
+ * Without this signal a scoped caller asking for excerpts got an empty
437
+ * result indistinguishable from "the vault had no match". */
438
+ | "chunks-scope-restricted";
433
439
  /**
434
440
  * Why a recall returned nothing. `""` when it returned something, so the field
435
441
  * is always present and groupable rather than being absent on the healthy path.