@anuma/sdk 6.8.2 → 6.9.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.
@@ -2321,13 +2321,13 @@ async function upsertEntitiesInWrite(ctx, entities) {
2321
2321
  }
2322
2322
  const unique = Array.from(kindByName.keys());
2323
2323
  const out = /* @__PURE__ */ new Map();
2324
- if (unique.length === 0) return { entities: out, operations: [] };
2324
+ if (unique.length === 0) return { entities: out, operations: [], kindBackfills: [] };
2325
2325
  const existing = await ctx.entityCollection.query(Q2.where("canonical_name", Q2.oneOf(unique))).fetch();
2326
2326
  const existingNames = new Set(existing.map((e) => e.canonicalName));
2327
- const updates = existing.filter((e) => {
2327
+ const kindBackfills = existing.filter((e) => {
2328
2328
  const incoming = kindByName.get(e.canonicalName);
2329
2329
  return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
2330
- });
2330
+ }).map((record) => ({ record, kind: kindByName.get(record.canonicalName) }));
2331
2331
  const missing = unique.filter((n) => !existingNames.has(n));
2332
2332
  const created = missing.map(
2333
2333
  (name) => ctx.entityCollection.prepareCreate((record) => {
@@ -2336,14 +2336,19 @@ async function upsertEntitiesInWrite(ctx, entities) {
2336
2336
  if (kind !== void 0) record._setRaw("kind", kind);
2337
2337
  })
2338
2338
  );
2339
- const updated = updates.map(
2340
- (e) => e.prepareUpdate((record) => {
2341
- record._setRaw("kind", kindByName.get(e.canonicalName));
2342
- })
2343
- );
2344
2339
  for (const e of existing) out.set(e.canonicalName, entityToStored(e));
2345
2340
  for (const record of created) out.set(record.canonicalName, entityToStored(record));
2346
- return { entities: out, operations: [...created, ...updated] };
2341
+ for (const { record, kind } of kindBackfills) {
2342
+ out.set(record.canonicalName, { ...entityToStored(record), kind });
2343
+ }
2344
+ return { entities: out, operations: created, kindBackfills };
2345
+ }
2346
+ function prepareKindBackfills(backfills) {
2347
+ return backfills.map(
2348
+ ({ record, kind }) => record.prepareUpdate((r) => {
2349
+ r._setRaw("kind", kind);
2350
+ })
2351
+ );
2347
2352
  }
2348
2353
  async function findVaultRowInWrite(ctx, memoryId) {
2349
2354
  try {
@@ -2393,10 +2398,12 @@ function topicsForEntities(entities, displayNames, source) {
2393
2398
  function toEntityObjects(entityInputs) {
2394
2399
  return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
2395
2400
  }
2396
- async function prepareMemoryTopicsUpdate(ctx, memoryId, linked, inputs, source) {
2401
+ async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
2397
2402
  const row = await findVaultRowInWrite(ctx, memoryId);
2398
- const write = resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
2399
- return write === null ? null : prepareTopicsUpdate(write.row, write.topics, Date.now());
2403
+ return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
2404
+ }
2405
+ function prepareMemoryTopicsUpdate(write) {
2406
+ return prepareTopicsUpdate(write.row, write.topics, Date.now());
2400
2407
  }
2401
2408
  async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
2402
2409
  if (entityInputs.length === 0) return [];
@@ -2405,15 +2412,16 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
2405
2412
  let skipped = false;
2406
2413
  let entities = [];
2407
2414
  await ctx.database.write(async () => {
2408
- const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
2409
- ctx,
2410
- normalized
2411
- );
2415
+ const {
2416
+ entities: byName,
2417
+ operations: entityOps,
2418
+ kindBackfills
2419
+ } = await upsertEntitiesInWrite(ctx, normalized);
2412
2420
  entities = Array.from(byName.values());
2413
2421
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
2414
2422
  if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
2415
- if (entityOps.length > 0) {
2416
- await ctx.database.batch(...entityOps);
2423
+ if (entityOps.length > 0 || kindBackfills.length > 0) {
2424
+ await ctx.database.batch(...entityOps, ...prepareKindBackfills(kindBackfills));
2417
2425
  }
2418
2426
  skipped = true;
2419
2427
  return;
@@ -2433,7 +2441,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
2433
2441
  displayNamesOf(normalized),
2434
2442
  options?.topicsSource ?? "auto"
2435
2443
  );
2436
- if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null) return;
2444
+ if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
2445
+ return;
2446
+ }
2437
2447
  const linkOps = toCreate.map(
2438
2448
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
2439
2449
  record._setRaw("memory_id", memoryId);
@@ -2443,6 +2453,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
2443
2453
  );
2444
2454
  await ctx.database.batch(
2445
2455
  ...entityOps,
2456
+ ...prepareKindBackfills(kindBackfills),
2446
2457
  ...linkOps,
2447
2458
  ...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
2448
2459
  );
@@ -2480,10 +2491,11 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
2480
2491
  skipped = true;
2481
2492
  return;
2482
2493
  }
2483
- const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
2484
- ctx,
2485
- normalized
2486
- );
2494
+ const {
2495
+ entities: byName,
2496
+ operations: entityOps,
2497
+ kindBackfills
2498
+ } = await upsertEntitiesInWrite(ctx, normalized);
2487
2499
  entities = Array.from(byName.values());
2488
2500
  const existingLinks = await ctx.memoryEntityCollection.query(Q2.where("memory_id", memoryId)).fetch();
2489
2501
  const keep = new Set(entities.map((e) => e.uniqueId));
@@ -2496,12 +2508,13 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
2496
2508
  displayNamesOf(normalized),
2497
2509
  options.topicsSource
2498
2510
  );
2499
- if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
2511
+ if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
2500
2512
  return;
2501
2513
  }
2502
2514
  const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
2503
2515
  await ctx.database.batch(
2504
2516
  ...entityOps,
2517
+ ...prepareKindBackfills(kindBackfills),
2505
2518
  ...toCreate.map(
2506
2519
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
2507
2520
  record._setRaw("memory_id", memoryId);
@@ -3314,10 +3327,16 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
3314
3327
  const existing = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", memoryId)).fetch();
3315
3328
  const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
3316
3329
  if (staleLinks.length === 0 && entities.length > 0) return;
3317
- const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, memoryId, linked, entities, "user");
3330
+ const topicsWrite = await resolveMemoryTopicsWrite(
3331
+ entityCtx,
3332
+ memoryId,
3333
+ linked,
3334
+ entities,
3335
+ "user"
3336
+ );
3318
3337
  await ctx.database.batch(
3319
3338
  ...staleLinks.map((l) => l.prepareDestroyPermanently()),
3320
- ...topicsOp ? [topicsOp] : []
3339
+ ...topicsWrite ? [prepareMemoryTopicsUpdate(topicsWrite)] : []
3321
3340
  );
3322
3341
  });
3323
3342
  if (stale) return null;
@@ -3636,27 +3655,36 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
3636
3655
  if (!entityCtx) {
3637
3656
  throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
3638
3657
  }
3658
+ const uniqueIds = Array.from(new Set(memoryIds));
3659
+ if (uniqueIds.length === 0) return [];
3639
3660
  const filled = [];
3640
- for (const id of Array.from(new Set(memoryIds))) {
3641
- let record;
3642
- try {
3643
- record = await ctx.vaultMemoryCollection.find(id);
3644
- } catch {
3645
- continue;
3646
- }
3647
- if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
3648
- if (parseTopics(record.topics) !== null) continue;
3649
- const links = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", id)).unsafeFetchRaw();
3650
- const entityIds = links.map((l) => String(l.entity_id));
3651
- if (entityIds.length === 0) continue;
3652
- const entities = await entityCtx.entityCollection.query(Q3.where("id", Q3.oneOf(entityIds))).fetch();
3653
- if (entities.length === 0) continue;
3654
- const source = record.topicsUserManaged ? "user" : "auto";
3661
+ const CHUNK = 500;
3662
+ for (let i = 0; i < uniqueIds.length; i += CHUNK) {
3663
+ const chunkIds = uniqueIds.slice(i, i + CHUNK);
3655
3664
  await ctx.database.write(async () => {
3656
- const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, id, entities, [], source);
3657
- if (!topicsOp) return;
3658
- await ctx.database.batch(topicsOp);
3659
- filled.push(id);
3665
+ const writes = [];
3666
+ for (const id of chunkIds) {
3667
+ let record;
3668
+ try {
3669
+ record = await ctx.vaultMemoryCollection.find(id);
3670
+ } catch {
3671
+ continue;
3672
+ }
3673
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
3674
+ if (parseTopics(record.topics) !== null) continue;
3675
+ const links = await entityCtx.memoryEntityCollection.query(Q3.where("memory_id", id)).unsafeFetchRaw();
3676
+ const entityIds = links.map((l) => String(l.entity_id));
3677
+ if (entityIds.length === 0) continue;
3678
+ const entities = await entityCtx.entityCollection.query(Q3.where("id", Q3.oneOf(entityIds))).fetch();
3679
+ if (entities.length === 0) continue;
3680
+ const source = record.topicsUserManaged ? "user" : "auto";
3681
+ const write = await resolveMemoryTopicsWrite(entityCtx, id, entities, [], source);
3682
+ if (write) writes.push(write);
3683
+ }
3684
+ if (writes.length === 0) return;
3685
+ const prepared = writes.map(prepareMemoryTopicsUpdate);
3686
+ await ctx.database.batch(...prepared);
3687
+ for (const write of writes) filled.push(write.row.id);
3660
3688
  });
3661
3689
  }
3662
3690
  return filled;
@@ -7096,7 +7124,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
7096
7124
  subQueries = decomp.subQueries;
7097
7125
  }
7098
7126
  }
7099
- const { recall: recall2 } = await import("./recall-3I5GDJZE.mjs");
7127
+ const { recall: recall2 } = await import("./recall-FWILZSFH.mjs");
7100
7128
  let recallDegraded = [];
7101
7129
  const result = await recall2(
7102
7130
  query,
@@ -4888,13 +4888,13 @@ async function upsertEntitiesInWrite(ctx, entities) {
4888
4888
  }
4889
4889
  const unique = Array.from(kindByName.keys());
4890
4890
  const out = /* @__PURE__ */ new Map();
4891
- if (unique.length === 0) return { entities: out, operations: [] };
4891
+ if (unique.length === 0) return { entities: out, operations: [], kindBackfills: [] };
4892
4892
  const existing = await ctx.entityCollection.query(import_watermelondb9.Q.where("canonical_name", import_watermelondb9.Q.oneOf(unique))).fetch();
4893
4893
  const existingNames = new Set(existing.map((e) => e.canonicalName));
4894
- const updates = existing.filter((e) => {
4894
+ const kindBackfills = existing.filter((e) => {
4895
4895
  const incoming = kindByName.get(e.canonicalName);
4896
4896
  return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
4897
- });
4897
+ }).map((record) => ({ record, kind: kindByName.get(record.canonicalName) }));
4898
4898
  const missing = unique.filter((n) => !existingNames.has(n));
4899
4899
  const created = missing.map(
4900
4900
  (name) => ctx.entityCollection.prepareCreate((record) => {
@@ -4903,14 +4903,19 @@ async function upsertEntitiesInWrite(ctx, entities) {
4903
4903
  if (kind !== void 0) record._setRaw("kind", kind);
4904
4904
  })
4905
4905
  );
4906
- const updated = updates.map(
4907
- (e) => e.prepareUpdate((record) => {
4908
- record._setRaw("kind", kindByName.get(e.canonicalName));
4909
- })
4910
- );
4911
4906
  for (const e of existing) out.set(e.canonicalName, entityToStored(e));
4912
4907
  for (const record of created) out.set(record.canonicalName, entityToStored(record));
4913
- return { entities: out, operations: [...created, ...updated] };
4908
+ for (const { record, kind } of kindBackfills) {
4909
+ out.set(record.canonicalName, { ...entityToStored(record), kind });
4910
+ }
4911
+ return { entities: out, operations: created, kindBackfills };
4912
+ }
4913
+ function prepareKindBackfills(backfills) {
4914
+ return backfills.map(
4915
+ ({ record, kind }) => record.prepareUpdate((r) => {
4916
+ r._setRaw("kind", kind);
4917
+ })
4918
+ );
4914
4919
  }
4915
4920
  async function findVaultRowInWrite(ctx, memoryId) {
4916
4921
  try {
@@ -4960,10 +4965,12 @@ function topicsForEntities(entities, displayNames, source) {
4960
4965
  function toEntityObjects(entityInputs) {
4961
4966
  return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
4962
4967
  }
4963
- async function prepareMemoryTopicsUpdate(ctx, memoryId, linked, inputs, source) {
4968
+ async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
4964
4969
  const row = await findVaultRowInWrite(ctx, memoryId);
4965
- const write = resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
4966
- return write === null ? null : prepareTopicsUpdate(write.row, write.topics, Date.now());
4970
+ return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
4971
+ }
4972
+ function prepareMemoryTopicsUpdate(write) {
4973
+ return prepareTopicsUpdate(write.row, write.topics, Date.now());
4967
4974
  }
4968
4975
  async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
4969
4976
  if (entityInputs.length === 0) return [];
@@ -4972,15 +4979,16 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
4972
4979
  let skipped = false;
4973
4980
  let entities = [];
4974
4981
  await ctx.database.write(async () => {
4975
- const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
4976
- ctx,
4977
- normalized
4978
- );
4982
+ const {
4983
+ entities: byName,
4984
+ operations: entityOps,
4985
+ kindBackfills
4986
+ } = await upsertEntitiesInWrite(ctx, normalized);
4979
4987
  entities = Array.from(byName.values());
4980
4988
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
4981
4989
  if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
4982
- if (entityOps.length > 0) {
4983
- await ctx.database.batch(...entityOps);
4990
+ if (entityOps.length > 0 || kindBackfills.length > 0) {
4991
+ await ctx.database.batch(...entityOps, ...prepareKindBackfills(kindBackfills));
4984
4992
  }
4985
4993
  skipped = true;
4986
4994
  return;
@@ -5000,7 +5008,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
5000
5008
  displayNamesOf(normalized),
5001
5009
  options?.topicsSource ?? "auto"
5002
5010
  );
5003
- if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null) return;
5011
+ if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
5012
+ return;
5013
+ }
5004
5014
  const linkOps = toCreate.map(
5005
5015
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
5006
5016
  record._setRaw("memory_id", memoryId);
@@ -5010,6 +5020,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
5010
5020
  );
5011
5021
  await ctx.database.batch(
5012
5022
  ...entityOps,
5023
+ ...prepareKindBackfills(kindBackfills),
5013
5024
  ...linkOps,
5014
5025
  ...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
5015
5026
  );
@@ -5047,10 +5058,11 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
5047
5058
  skipped = true;
5048
5059
  return;
5049
5060
  }
5050
- const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
5051
- ctx,
5052
- normalized
5053
- );
5061
+ const {
5062
+ entities: byName,
5063
+ operations: entityOps,
5064
+ kindBackfills
5065
+ } = await upsertEntitiesInWrite(ctx, normalized);
5054
5066
  entities = Array.from(byName.values());
5055
5067
  const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb9.Q.where("memory_id", memoryId)).fetch();
5056
5068
  const keep = new Set(entities.map((e) => e.uniqueId));
@@ -5063,12 +5075,13 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
5063
5075
  displayNamesOf(normalized),
5064
5076
  options.topicsSource
5065
5077
  );
5066
- if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
5078
+ if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
5067
5079
  return;
5068
5080
  }
5069
5081
  const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
5070
5082
  await ctx.database.batch(
5071
5083
  ...entityOps,
5084
+ ...prepareKindBackfills(kindBackfills),
5072
5085
  ...toCreate.map(
5073
5086
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
5074
5087
  record._setRaw("memory_id", memoryId);
@@ -5897,10 +5910,16 @@ async function setMemoryEntitiesOp(ctx, memoryId, entities) {
5897
5910
  const existing = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", memoryId)).fetch();
5898
5911
  const staleLinks = existing.filter((l) => !keep.has(String(l.entityId)));
5899
5912
  if (staleLinks.length === 0 && entities.length > 0) return;
5900
- const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, memoryId, linked, entities, "user");
5913
+ const topicsWrite = await resolveMemoryTopicsWrite(
5914
+ entityCtx,
5915
+ memoryId,
5916
+ linked,
5917
+ entities,
5918
+ "user"
5919
+ );
5901
5920
  await ctx.database.batch(
5902
5921
  ...staleLinks.map((l) => l.prepareDestroyPermanently()),
5903
- ...topicsOp ? [topicsOp] : []
5922
+ ...topicsWrite ? [prepareMemoryTopicsUpdate(topicsWrite)] : []
5904
5923
  );
5905
5924
  });
5906
5925
  if (stale) return null;
@@ -6218,27 +6237,36 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
6218
6237
  if (!entityCtx) {
6219
6238
  throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
6220
6239
  }
6240
+ const uniqueIds = Array.from(new Set(memoryIds));
6241
+ if (uniqueIds.length === 0) return [];
6221
6242
  const filled = [];
6222
- for (const id of Array.from(new Set(memoryIds))) {
6223
- let record;
6224
- try {
6225
- record = await ctx.vaultMemoryCollection.find(id);
6226
- } catch {
6227
- continue;
6228
- }
6229
- if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
6230
- if (parseTopics(record.topics) !== null) continue;
6231
- const links = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", id)).unsafeFetchRaw();
6232
- const entityIds = links.map((l) => String(l.entity_id));
6233
- if (entityIds.length === 0) continue;
6234
- const entities = await entityCtx.entityCollection.query(import_watermelondb10.Q.where("id", import_watermelondb10.Q.oneOf(entityIds))).fetch();
6235
- if (entities.length === 0) continue;
6236
- const source = record.topicsUserManaged ? "user" : "auto";
6243
+ const CHUNK = 500;
6244
+ for (let i = 0; i < uniqueIds.length; i += CHUNK) {
6245
+ const chunkIds = uniqueIds.slice(i, i + CHUNK);
6237
6246
  await ctx.database.write(async () => {
6238
- const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, id, entities, [], source);
6239
- if (!topicsOp) return;
6240
- await ctx.database.batch(topicsOp);
6241
- filled.push(id);
6247
+ const writes = [];
6248
+ for (const id of chunkIds) {
6249
+ let record;
6250
+ try {
6251
+ record = await ctx.vaultMemoryCollection.find(id);
6252
+ } catch {
6253
+ continue;
6254
+ }
6255
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
6256
+ if (parseTopics(record.topics) !== null) continue;
6257
+ const links = await entityCtx.memoryEntityCollection.query(import_watermelondb10.Q.where("memory_id", id)).unsafeFetchRaw();
6258
+ const entityIds = links.map((l) => String(l.entity_id));
6259
+ if (entityIds.length === 0) continue;
6260
+ const entities = await entityCtx.entityCollection.query(import_watermelondb10.Q.where("id", import_watermelondb10.Q.oneOf(entityIds))).fetch();
6261
+ if (entities.length === 0) continue;
6262
+ const source = record.topicsUserManaged ? "user" : "auto";
6263
+ const write = await resolveMemoryTopicsWrite(entityCtx, id, entities, [], source);
6264
+ if (write) writes.push(write);
6265
+ }
6266
+ if (writes.length === 0) return;
6267
+ const prepared = writes.map(prepareMemoryTopicsUpdate);
6268
+ await ctx.database.batch(...prepared);
6269
+ for (const write of writes) filled.push(write.row.id);
6242
6270
  });
6243
6271
  }
6244
6272
  return filled;
@@ -18387,7 +18415,7 @@ function createAutoExtractor(options) {
18387
18415
  const t0 = Date.now();
18388
18416
  void (async () => {
18389
18417
  try {
18390
- const { candidates, results, failedCount, outcome } = await extractAndRetain(
18418
+ const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
18391
18419
  window2,
18392
18420
  retainCtx,
18393
18421
  {
@@ -18427,7 +18455,11 @@ function createAutoExtractor(options) {
18427
18455
  failedCount,
18428
18456
  durationMs: Date.now() - t0,
18429
18457
  conversationId,
18430
- outcome
18458
+ outcome,
18459
+ // Only set alongside `outcome: "empty-after-retry"`. Spread so the key
18460
+ // is absent rather than explicitly undefined on a healthy turn —
18461
+ // analytics backends store an explicit undefined as a real value.
18462
+ ...failure !== void 0 && { failure }
18431
18463
  });
18432
18464
  } catch (err) {
18433
18465
  options.onError?.(err instanceof Error ? err : new Error(String(err)), conversationId);
@@ -8114,6 +8114,14 @@ declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryI
8114
8114
  * row has: a curated memory's topics are recorded as `user`, everything else as
8115
8115
  * `auto`. Skips deleted, foreign-user, unlinked rows, and rows that already have
8116
8116
  * a record. Returns the ids filled.
8117
+ *
8118
+ * Runs as ONE writer over the whole (caller-bounded) list, in two phases:
8119
+ * resolve every row's write — all the awaits — and only then prepare and batch,
8120
+ * synchronously. Resolving and preparing per row inside its own writer is what
8121
+ * fired WatermelonDB's "wasn't sent to batch() synchronously" diagnostic once
8122
+ * per memory (~107 on a single dev launch, sdk#891); it also cost N writes.
8123
+ * Same treatment as {@link stampTopicsExtractedAtOp}, including its
8124
+ * transpilation hazard — the prepare pass MUST stay a `.map()`.
8117
8125
  */
8118
8126
  declare function backfillMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
8119
8127
  declare function deleteAllVaultMemoriesForUserOp(ctx: VaultMemoryOperationsContext, userId: string): Promise<number>;
@@ -8398,6 +8406,22 @@ interface TurnCompleteEvent {
8398
8406
  * previously indistinguishable (both surfaced as zero candidates).
8399
8407
  */
8400
8408
  outcome: ExtractOutcome;
8409
+ /**
8410
+ * Present only alongside `outcome: "empty-after-retry"` — WHICH failure ended
8411
+ * the turn (#888).
8412
+ *
8413
+ * `outcome` says extraction gave up; this says why, from a stable enum. The
8414
+ * distinction is the whole point: a 2026-08-11 audit measured ~63% of
8415
+ * production extraction turns ending in `empty-after-retry` and could not tell
8416
+ * from telemetry whether the cause was the freeloader 403 everyone assumed or
8417
+ * something else. It took a Prometheus cross-check to find the real one — the
8418
+ * portal returning HTTP 200 with an empty body, which it counts as a success.
8419
+ *
8420
+ * Forward `failure.reason` into your extraction analytics event; all three
8421
+ * fields are bounded (an enum, an HTTP status, a small attempt count) and none
8422
+ * carries content.
8423
+ */
8424
+ failure?: PortalLlmFailure;
8401
8425
  }
8402
8426
  /** @public */
8403
8427
  /**
@@ -8114,6 +8114,14 @@ declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryI
8114
8114
  * row has: a curated memory's topics are recorded as `user`, everything else as
8115
8115
  * `auto`. Skips deleted, foreign-user, unlinked rows, and rows that already have
8116
8116
  * a record. Returns the ids filled.
8117
+ *
8118
+ * Runs as ONE writer over the whole (caller-bounded) list, in two phases:
8119
+ * resolve every row's write — all the awaits — and only then prepare and batch,
8120
+ * synchronously. Resolving and preparing per row inside its own writer is what
8121
+ * fired WatermelonDB's "wasn't sent to batch() synchronously" diagnostic once
8122
+ * per memory (~107 on a single dev launch, sdk#891); it also cost N writes.
8123
+ * Same treatment as {@link stampTopicsExtractedAtOp}, including its
8124
+ * transpilation hazard — the prepare pass MUST stay a `.map()`.
8117
8125
  */
8118
8126
  declare function backfillMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
8119
8127
  declare function deleteAllVaultMemoriesForUserOp(ctx: VaultMemoryOperationsContext, userId: string): Promise<number>;
@@ -8398,6 +8406,22 @@ interface TurnCompleteEvent {
8398
8406
  * previously indistinguishable (both surfaced as zero candidates).
8399
8407
  */
8400
8408
  outcome: ExtractOutcome;
8409
+ /**
8410
+ * Present only alongside `outcome: "empty-after-retry"` — WHICH failure ended
8411
+ * the turn (#888).
8412
+ *
8413
+ * `outcome` says extraction gave up; this says why, from a stable enum. The
8414
+ * distinction is the whole point: a 2026-08-11 audit measured ~63% of
8415
+ * production extraction turns ending in `empty-after-retry` and could not tell
8416
+ * from telemetry whether the cause was the freeloader 403 everyone assumed or
8417
+ * something else. It took a Prometheus cross-check to find the real one — the
8418
+ * portal returning HTTP 200 with an empty body, which it counts as a success.
8419
+ *
8420
+ * Forward `failure.reason` into your extraction analytics event; all three
8421
+ * fields are bounded (an enum, an HTTP status, a small attempt count) and none
8422
+ * carries content.
8423
+ */
8424
+ failure?: PortalLlmFailure;
8401
8425
  }
8402
8426
  /** @public */
8403
8427
  /**
@@ -178,7 +178,7 @@ import {
178
178
  updateVaultMemoryOp,
179
179
  useEncryption,
180
180
  vaultMemoryToStored
181
- } from "./chunk-NPJSO5ES.mjs";
181
+ } from "./chunk-EJ65X4GI.mjs";
182
182
  import {
183
183
  PiiRedactor,
184
184
  createStreamingDeAnonymizer,
@@ -8357,7 +8357,7 @@ function createAutoExtractor(options) {
8357
8357
  const t0 = Date.now();
8358
8358
  void (async () => {
8359
8359
  try {
8360
- const { candidates, results, failedCount, outcome } = await extractAndRetain(
8360
+ const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
8361
8361
  window2,
8362
8362
  retainCtx,
8363
8363
  {
@@ -8397,7 +8397,11 @@ function createAutoExtractor(options) {
8397
8397
  failedCount,
8398
8398
  durationMs: Date.now() - t0,
8399
8399
  conversationId,
8400
- outcome
8400
+ outcome,
8401
+ // Only set alongside `outcome: "empty-after-retry"`. Spread so the key
8402
+ // is absent rather than explicitly undefined on a healthy turn —
8403
+ // analytics backends store an explicit undefined as a real value.
8404
+ ...failure !== void 0 && { failure }
8401
8405
  });
8402
8406
  } catch (err) {
8403
8407
  options.onError?.(err instanceof Error ? err : new Error(String(err)), conversationId);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-NPJSO5ES.mjs";
3
+ } from "./chunk-EJ65X4GI.mjs";
4
4
  import "./chunk-CJ32HO6Q.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {