@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.
@@ -16391,13 +16391,13 @@ async function upsertEntitiesInWrite(ctx, entities) {
16391
16391
  }
16392
16392
  const unique = Array.from(kindByName.keys());
16393
16393
  const out = /* @__PURE__ */ new Map();
16394
- if (unique.length === 0) return { entities: out, operations: [] };
16394
+ if (unique.length === 0) return { entities: out, operations: [], kindBackfills: [] };
16395
16395
  const existing = await ctx.entityCollection.query(import_watermelondb2.Q.where("canonical_name", import_watermelondb2.Q.oneOf(unique))).fetch();
16396
16396
  const existingNames = new Set(existing.map((e) => e.canonicalName));
16397
- const updates = existing.filter((e) => {
16397
+ const kindBackfills = existing.filter((e) => {
16398
16398
  const incoming = kindByName.get(e.canonicalName);
16399
16399
  return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
16400
- });
16400
+ }).map((record) => ({ record, kind: kindByName.get(record.canonicalName) }));
16401
16401
  const missing = unique.filter((n) => !existingNames.has(n));
16402
16402
  const created = missing.map(
16403
16403
  (name) => ctx.entityCollection.prepareCreate((record) => {
@@ -16406,14 +16406,19 @@ async function upsertEntitiesInWrite(ctx, entities) {
16406
16406
  if (kind !== void 0) record._setRaw("kind", kind);
16407
16407
  })
16408
16408
  );
16409
- const updated = updates.map(
16410
- (e) => e.prepareUpdate((record) => {
16411
- record._setRaw("kind", kindByName.get(e.canonicalName));
16412
- })
16413
- );
16414
16409
  for (const e of existing) out.set(e.canonicalName, entityToStored(e));
16415
16410
  for (const record of created) out.set(record.canonicalName, entityToStored(record));
16416
- return { entities: out, operations: [...created, ...updated] };
16411
+ for (const { record, kind } of kindBackfills) {
16412
+ out.set(record.canonicalName, { ...entityToStored(record), kind });
16413
+ }
16414
+ return { entities: out, operations: created, kindBackfills };
16415
+ }
16416
+ function prepareKindBackfills(backfills) {
16417
+ return backfills.map(
16418
+ ({ record, kind }) => record.prepareUpdate((r) => {
16419
+ r._setRaw("kind", kind);
16420
+ })
16421
+ );
16417
16422
  }
16418
16423
  async function findVaultRowInWrite(ctx, memoryId) {
16419
16424
  try {
@@ -16463,10 +16468,12 @@ function topicsForEntities(entities, displayNames, source) {
16463
16468
  function toEntityObjects(entityInputs) {
16464
16469
  return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
16465
16470
  }
16466
- async function prepareMemoryTopicsUpdate(ctx, memoryId, linked, inputs, source) {
16471
+ async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
16467
16472
  const row = await findVaultRowInWrite(ctx, memoryId);
16468
- const write = resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
16469
- return write === null ? null : prepareTopicsUpdate(write.row, write.topics, Date.now());
16473
+ return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
16474
+ }
16475
+ function prepareMemoryTopicsUpdate(write) {
16476
+ return prepareTopicsUpdate(write.row, write.topics, Date.now());
16470
16477
  }
16471
16478
  async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
16472
16479
  if (entityInputs.length === 0) return [];
@@ -16475,15 +16482,16 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
16475
16482
  let skipped = false;
16476
16483
  let entities = [];
16477
16484
  await ctx.database.write(async () => {
16478
- const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
16479
- ctx,
16480
- normalized
16481
- );
16485
+ const {
16486
+ entities: byName,
16487
+ operations: entityOps,
16488
+ kindBackfills
16489
+ } = await upsertEntitiesInWrite(ctx, normalized);
16482
16490
  entities = Array.from(byName.values());
16483
16491
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
16484
16492
  if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
16485
- if (entityOps.length > 0) {
16486
- await ctx.database.batch(...entityOps);
16493
+ if (entityOps.length > 0 || kindBackfills.length > 0) {
16494
+ await ctx.database.batch(...entityOps, ...prepareKindBackfills(kindBackfills));
16487
16495
  }
16488
16496
  skipped = true;
16489
16497
  return;
@@ -16503,7 +16511,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
16503
16511
  displayNamesOf(normalized),
16504
16512
  options?.topicsSource ?? "auto"
16505
16513
  );
16506
- if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null) return;
16514
+ if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
16515
+ return;
16516
+ }
16507
16517
  const linkOps = toCreate.map(
16508
16518
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
16509
16519
  record._setRaw("memory_id", memoryId);
@@ -16513,6 +16523,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
16513
16523
  );
16514
16524
  await ctx.database.batch(
16515
16525
  ...entityOps,
16526
+ ...prepareKindBackfills(kindBackfills),
16516
16527
  ...linkOps,
16517
16528
  ...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
16518
16529
  );
@@ -16550,10 +16561,11 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
16550
16561
  skipped = true;
16551
16562
  return;
16552
16563
  }
16553
- const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
16554
- ctx,
16555
- normalized
16556
- );
16564
+ const {
16565
+ entities: byName,
16566
+ operations: entityOps,
16567
+ kindBackfills
16568
+ } = await upsertEntitiesInWrite(ctx, normalized);
16557
16569
  entities = Array.from(byName.values());
16558
16570
  const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb2.Q.where("memory_id", memoryId)).fetch();
16559
16571
  const keep = new Set(entities.map((e) => e.uniqueId));
@@ -16566,12 +16578,13 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
16566
16578
  displayNamesOf(normalized),
16567
16579
  options.topicsSource
16568
16580
  );
16569
- if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
16581
+ if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
16570
16582
  return;
16571
16583
  }
16572
16584
  const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
16573
16585
  await ctx.database.batch(
16574
16586
  ...entityOps,
16587
+ ...prepareKindBackfills(kindBackfills),
16575
16588
  ...toCreate.map(
16576
16589
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
16577
16590
  record._setRaw("memory_id", memoryId);
@@ -17622,27 +17635,36 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
17622
17635
  if (!entityCtx) {
17623
17636
  throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
17624
17637
  }
17638
+ const uniqueIds = Array.from(new Set(memoryIds));
17639
+ if (uniqueIds.length === 0) return [];
17625
17640
  const filled = [];
17626
- for (const id of Array.from(new Set(memoryIds))) {
17627
- let record;
17628
- try {
17629
- record = await ctx.vaultMemoryCollection.find(id);
17630
- } catch {
17631
- continue;
17632
- }
17633
- if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
17634
- if (parseTopics(record.topics) !== null) continue;
17635
- const links = await entityCtx.memoryEntityCollection.query(import_watermelondb3.Q.where("memory_id", id)).unsafeFetchRaw();
17636
- const entityIds = links.map((l) => String(l.entity_id));
17637
- if (entityIds.length === 0) continue;
17638
- const entities = await entityCtx.entityCollection.query(import_watermelondb3.Q.where("id", import_watermelondb3.Q.oneOf(entityIds))).fetch();
17639
- if (entities.length === 0) continue;
17640
- const source = record.topicsUserManaged ? "user" : "auto";
17641
+ const CHUNK = 500;
17642
+ for (let i = 0; i < uniqueIds.length; i += CHUNK) {
17643
+ const chunkIds = uniqueIds.slice(i, i + CHUNK);
17641
17644
  await ctx.database.write(async () => {
17642
- const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, id, entities, [], source);
17643
- if (!topicsOp) return;
17644
- await ctx.database.batch(topicsOp);
17645
- filled.push(id);
17645
+ const writes = [];
17646
+ for (const id of chunkIds) {
17647
+ let record;
17648
+ try {
17649
+ record = await ctx.vaultMemoryCollection.find(id);
17650
+ } catch {
17651
+ continue;
17652
+ }
17653
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
17654
+ if (parseTopics(record.topics) !== null) continue;
17655
+ const links = await entityCtx.memoryEntityCollection.query(import_watermelondb3.Q.where("memory_id", id)).unsafeFetchRaw();
17656
+ const entityIds = links.map((l) => String(l.entity_id));
17657
+ if (entityIds.length === 0) continue;
17658
+ const entities = await entityCtx.entityCollection.query(import_watermelondb3.Q.where("id", import_watermelondb3.Q.oneOf(entityIds))).fetch();
17659
+ if (entities.length === 0) continue;
17660
+ const source = record.topicsUserManaged ? "user" : "auto";
17661
+ const write = await resolveMemoryTopicsWrite(entityCtx, id, entities, [], source);
17662
+ if (write) writes.push(write);
17663
+ }
17664
+ if (writes.length === 0) return;
17665
+ const prepared = writes.map(prepareMemoryTopicsUpdate);
17666
+ await ctx.database.batch(...prepared);
17667
+ for (const write of writes) filled.push(write.row.id);
17646
17668
  });
17647
17669
  }
17648
17670
  return filled;
@@ -20856,7 +20878,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
20856
20878
  subQueries = decomp.subQueries;
20857
20879
  }
20858
20880
  }
20859
- const { recall: recall2 } = await import("./recall-RTW7T7KR.mjs");
20881
+ const { recall: recall2 } = await import("./recall-T7CNXTQ3.mjs");
20860
20882
  let recallDegraded = [];
20861
20883
  const result = await recall2(
20862
20884
  query,
@@ -17225,13 +17225,13 @@ async function upsertEntitiesInWrite(ctx, entities) {
17225
17225
  }
17226
17226
  const unique = Array.from(kindByName.keys());
17227
17227
  const out = /* @__PURE__ */ new Map();
17228
- if (unique.length === 0) return { entities: out, operations: [] };
17228
+ if (unique.length === 0) return { entities: out, operations: [], kindBackfills: [] };
17229
17229
  const existing = await ctx.entityCollection.query(import_watermelondb16.Q.where("canonical_name", import_watermelondb16.Q.oneOf(unique))).fetch();
17230
17230
  const existingNames = new Set(existing.map((e) => e.canonicalName));
17231
- const updates = existing.filter((e) => {
17231
+ const kindBackfills = existing.filter((e) => {
17232
17232
  const incoming = kindByName.get(e.canonicalName);
17233
17233
  return incoming !== void 0 && (e.kind === null || e.kind === void 0 || e.kind === "");
17234
- });
17234
+ }).map((record) => ({ record, kind: kindByName.get(record.canonicalName) }));
17235
17235
  const missing = unique.filter((n) => !existingNames.has(n));
17236
17236
  const created = missing.map(
17237
17237
  (name) => ctx.entityCollection.prepareCreate((record) => {
@@ -17240,14 +17240,19 @@ async function upsertEntitiesInWrite(ctx, entities) {
17240
17240
  if (kind !== void 0) record._setRaw("kind", kind);
17241
17241
  })
17242
17242
  );
17243
- const updated = updates.map(
17244
- (e) => e.prepareUpdate((record) => {
17245
- record._setRaw("kind", kindByName.get(e.canonicalName));
17246
- })
17247
- );
17248
17243
  for (const e of existing) out.set(e.canonicalName, entityToStored(e));
17249
17244
  for (const record of created) out.set(record.canonicalName, entityToStored(record));
17250
- return { entities: out, operations: [...created, ...updated] };
17245
+ for (const { record, kind } of kindBackfills) {
17246
+ out.set(record.canonicalName, { ...entityToStored(record), kind });
17247
+ }
17248
+ return { entities: out, operations: created, kindBackfills };
17249
+ }
17250
+ function prepareKindBackfills(backfills) {
17251
+ return backfills.map(
17252
+ ({ record, kind }) => record.prepareUpdate((r) => {
17253
+ r._setRaw("kind", kind);
17254
+ })
17255
+ );
17251
17256
  }
17252
17257
  async function findVaultRowInWrite(ctx, memoryId) {
17253
17258
  try {
@@ -17297,10 +17302,12 @@ function topicsForEntities(entities, displayNames, source) {
17297
17302
  function toEntityObjects(entityInputs) {
17298
17303
  return entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
17299
17304
  }
17300
- async function prepareMemoryTopicsUpdate(ctx, memoryId, linked, inputs, source) {
17305
+ async function resolveMemoryTopicsWrite(ctx, memoryId, linked, inputs, source) {
17301
17306
  const row = await findVaultRowInWrite(ctx, memoryId);
17302
- const write = resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
17303
- return write === null ? null : prepareTopicsUpdate(write.row, write.topics, Date.now());
17307
+ return resolveTopicsWrite(row, linked, displayNamesOf(toEntityObjects(inputs)), source);
17308
+ }
17309
+ function prepareMemoryTopicsUpdate(write) {
17310
+ return prepareTopicsUpdate(write.row, write.topics, Date.now());
17304
17311
  }
17305
17312
  async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
17306
17313
  if (entityInputs.length === 0) return [];
@@ -17309,15 +17316,16 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
17309
17316
  let skipped = false;
17310
17317
  let entities = [];
17311
17318
  await ctx.database.write(async () => {
17312
- const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
17313
- ctx,
17314
- normalized
17315
- );
17319
+ const {
17320
+ entities: byName,
17321
+ operations: entityOps,
17322
+ kindBackfills
17323
+ } = await upsertEntitiesInWrite(ctx, normalized);
17316
17324
  entities = Array.from(byName.values());
17317
17325
  const vaultRow = await findVaultRowInWrite(ctx, memoryId);
17318
17326
  if (options?.unlessTopicsUserManaged && autoLinkBlocked(vaultRow)) {
17319
- if (entityOps.length > 0) {
17320
- await ctx.database.batch(...entityOps);
17327
+ if (entityOps.length > 0 || kindBackfills.length > 0) {
17328
+ await ctx.database.batch(...entityOps, ...prepareKindBackfills(kindBackfills));
17321
17329
  }
17322
17330
  skipped = true;
17323
17331
  return;
@@ -17337,7 +17345,9 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
17337
17345
  displayNamesOf(normalized),
17338
17346
  options?.topicsSource ?? "auto"
17339
17347
  );
17340
- if (entityOps.length === 0 && toCreate.length === 0 && topicsWrite === null) return;
17348
+ if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && topicsWrite === null) {
17349
+ return;
17350
+ }
17341
17351
  const linkOps = toCreate.map(
17342
17352
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
17343
17353
  record._setRaw("memory_id", memoryId);
@@ -17347,6 +17357,7 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
17347
17357
  );
17348
17358
  await ctx.database.batch(
17349
17359
  ...entityOps,
17360
+ ...prepareKindBackfills(kindBackfills),
17350
17361
  ...linkOps,
17351
17362
  ...topicsWrite ? [prepareTopicsUpdate(topicsWrite.row, topicsWrite.topics, Date.now())] : []
17352
17363
  );
@@ -17384,10 +17395,11 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
17384
17395
  skipped = true;
17385
17396
  return;
17386
17397
  }
17387
- const { entities: byName, operations: entityOps } = await upsertEntitiesInWrite(
17388
- ctx,
17389
- normalized
17390
- );
17398
+ const {
17399
+ entities: byName,
17400
+ operations: entityOps,
17401
+ kindBackfills
17402
+ } = await upsertEntitiesInWrite(ctx, normalized);
17391
17403
  entities = Array.from(byName.values());
17392
17404
  const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb16.Q.where("memory_id", memoryId)).fetch();
17393
17405
  const keep = new Set(entities.map((e) => e.uniqueId));
@@ -17400,12 +17412,13 @@ async function replaceMemoryEntities(ctx, memoryId, entityInputs, options) {
17400
17412
  displayNamesOf(normalized),
17401
17413
  options.topicsSource
17402
17414
  );
17403
- if (entityOps.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
17415
+ if (entityOps.length === 0 && kindBackfills.length === 0 && toCreate.length === 0 && toDestroy.length === 0 && topicsWrite === null) {
17404
17416
  return;
17405
17417
  }
17406
17418
  const orphans = await findOrphanedEntities(ctx, memoryId, toDestroy);
17407
17419
  await ctx.database.batch(
17408
17420
  ...entityOps,
17421
+ ...prepareKindBackfills(kindBackfills),
17409
17422
  ...toCreate.map(
17410
17423
  (e) => ctx.memoryEntityCollection.prepareCreate((record) => {
17411
17424
  record._setRaw("memory_id", memoryId);
@@ -18471,27 +18484,36 @@ async function backfillMemoryTopicsOp(ctx, memoryIds) {
18471
18484
  if (!entityCtx) {
18472
18485
  throw new Error("backfillMemoryTopicsOp requires ctx.entityCtx");
18473
18486
  }
18487
+ const uniqueIds = Array.from(new Set(memoryIds));
18488
+ if (uniqueIds.length === 0) return [];
18474
18489
  const filled = [];
18475
- for (const id of Array.from(new Set(memoryIds))) {
18476
- let record;
18477
- try {
18478
- record = await ctx.vaultMemoryCollection.find(id);
18479
- } catch {
18480
- continue;
18481
- }
18482
- if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
18483
- if (parseTopics(record.topics) !== null) continue;
18484
- const links = await entityCtx.memoryEntityCollection.query(import_watermelondb17.Q.where("memory_id", id)).unsafeFetchRaw();
18485
- const entityIds = links.map((l) => String(l.entity_id));
18486
- if (entityIds.length === 0) continue;
18487
- const entities = await entityCtx.entityCollection.query(import_watermelondb17.Q.where("id", import_watermelondb17.Q.oneOf(entityIds))).fetch();
18488
- if (entities.length === 0) continue;
18489
- const source = record.topicsUserManaged ? "user" : "auto";
18490
+ const CHUNK = 500;
18491
+ for (let i = 0; i < uniqueIds.length; i += CHUNK) {
18492
+ const chunkIds = uniqueIds.slice(i, i + CHUNK);
18490
18493
  await ctx.database.write(async () => {
18491
- const topicsOp = await prepareMemoryTopicsUpdate(entityCtx, id, entities, [], source);
18492
- if (!topicsOp) return;
18493
- await ctx.database.batch(topicsOp);
18494
- filled.push(id);
18494
+ const writes = [];
18495
+ for (const id of chunkIds) {
18496
+ let record;
18497
+ try {
18498
+ record = await ctx.vaultMemoryCollection.find(id);
18499
+ } catch {
18500
+ continue;
18501
+ }
18502
+ if (record.isDeleted || !isOwnedByCtxUser(ctx, record)) continue;
18503
+ if (parseTopics(record.topics) !== null) continue;
18504
+ const links = await entityCtx.memoryEntityCollection.query(import_watermelondb17.Q.where("memory_id", id)).unsafeFetchRaw();
18505
+ const entityIds = links.map((l) => String(l.entity_id));
18506
+ if (entityIds.length === 0) continue;
18507
+ const entities = await entityCtx.entityCollection.query(import_watermelondb17.Q.where("id", import_watermelondb17.Q.oneOf(entityIds))).fetch();
18508
+ if (entities.length === 0) continue;
18509
+ const source = record.topicsUserManaged ? "user" : "auto";
18510
+ const write = await resolveMemoryTopicsWrite(entityCtx, id, entities, [], source);
18511
+ if (write) writes.push(write);
18512
+ }
18513
+ if (writes.length === 0) return;
18514
+ const prepared = writes.map(prepareMemoryTopicsUpdate);
18515
+ await ctx.database.batch(...prepared);
18516
+ for (const write of writes) filled.push(write.row.id);
18495
18517
  });
18496
18518
  }
18497
18519
  return filled;
@@ -30011,7 +30033,7 @@ function createAutoExtractor(options) {
30011
30033
  const t0 = Date.now();
30012
30034
  void (async () => {
30013
30035
  try {
30014
- const { candidates, results, failedCount, outcome } = await extractAndRetain(
30036
+ const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
30015
30037
  window2,
30016
30038
  retainCtx,
30017
30039
  {
@@ -30051,7 +30073,11 @@ function createAutoExtractor(options) {
30051
30073
  failedCount,
30052
30074
  durationMs: Date.now() - t0,
30053
30075
  conversationId,
30054
- outcome
30076
+ outcome,
30077
+ // Only set alongside `outcome: "empty-after-retry"`. Spread so the key
30078
+ // is absent rather than explicitly undefined on a healthy turn —
30079
+ // analytics backends store an explicit undefined as a real value.
30080
+ ...failure !== void 0 && { failure }
30055
30081
  });
30056
30082
  } catch (err) {
30057
30083
  options.onError?.(err instanceof Error ? err : new Error(String(err)), conversationId);
@@ -6615,6 +6615,14 @@ declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryI
6615
6615
  * row has: a curated memory's topics are recorded as `user`, everything else as
6616
6616
  * `auto`. Skips deleted, foreign-user, unlinked rows, and rows that already have
6617
6617
  * a record. Returns the ids filled.
6618
+ *
6619
+ * Runs as ONE writer over the whole (caller-bounded) list, in two phases:
6620
+ * resolve every row's write — all the awaits — and only then prepare and batch,
6621
+ * synchronously. Resolving and preparing per row inside its own writer is what
6622
+ * fired WatermelonDB's "wasn't sent to batch() synchronously" diagnostic once
6623
+ * per memory (~107 on a single dev launch, sdk#891); it also cost N writes.
6624
+ * Same treatment as {@link stampTopicsExtractedAtOp}, including its
6625
+ * transpilation hazard — the prepare pass MUST stay a `.map()`.
6618
6626
  */
6619
6627
  declare function backfillMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
6620
6628
  declare function deleteAllVaultMemoriesForUserOp(ctx: VaultMemoryOperationsContext, userId: string): Promise<number>;
@@ -7807,6 +7815,22 @@ interface TurnCompleteEvent {
7807
7815
  * previously indistinguishable (both surfaced as zero candidates).
7808
7816
  */
7809
7817
  outcome: ExtractOutcome;
7818
+ /**
7819
+ * Present only alongside `outcome: "empty-after-retry"` — WHICH failure ended
7820
+ * the turn (#888).
7821
+ *
7822
+ * `outcome` says extraction gave up; this says why, from a stable enum. The
7823
+ * distinction is the whole point: a 2026-08-11 audit measured ~63% of
7824
+ * production extraction turns ending in `empty-after-retry` and could not tell
7825
+ * from telemetry whether the cause was the freeloader 403 everyone assumed or
7826
+ * something else. It took a Prometheus cross-check to find the real one — the
7827
+ * portal returning HTTP 200 with an empty body, which it counts as a success.
7828
+ *
7829
+ * Forward `failure.reason` into your extraction analytics event; all three
7830
+ * fields are bounded (an enum, an HTTP status, a small attempt count) and none
7831
+ * carries content.
7832
+ */
7833
+ failure?: PortalLlmFailure;
7810
7834
  }
7811
7835
  /** @public */
7812
7836
  /**
@@ -6615,6 +6615,14 @@ declare function relinkMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryI
6615
6615
  * row has: a curated memory's topics are recorded as `user`, everything else as
6616
6616
  * `auto`. Skips deleted, foreign-user, unlinked rows, and rows that already have
6617
6617
  * a record. Returns the ids filled.
6618
+ *
6619
+ * Runs as ONE writer over the whole (caller-bounded) list, in two phases:
6620
+ * resolve every row's write — all the awaits — and only then prepare and batch,
6621
+ * synchronously. Resolving and preparing per row inside its own writer is what
6622
+ * fired WatermelonDB's "wasn't sent to batch() synchronously" diagnostic once
6623
+ * per memory (~107 on a single dev launch, sdk#891); it also cost N writes.
6624
+ * Same treatment as {@link stampTopicsExtractedAtOp}, including its
6625
+ * transpilation hazard — the prepare pass MUST stay a `.map()`.
6618
6626
  */
6619
6627
  declare function backfillMemoryTopicsOp(ctx: VaultMemoryOperationsContext, memoryIds: readonly string[]): Promise<string[]>;
6620
6628
  declare function deleteAllVaultMemoriesForUserOp(ctx: VaultMemoryOperationsContext, userId: string): Promise<number>;
@@ -7807,6 +7815,22 @@ interface TurnCompleteEvent {
7807
7815
  * previously indistinguishable (both surfaced as zero candidates).
7808
7816
  */
7809
7817
  outcome: ExtractOutcome;
7818
+ /**
7819
+ * Present only alongside `outcome: "empty-after-retry"` — WHICH failure ended
7820
+ * the turn (#888).
7821
+ *
7822
+ * `outcome` says extraction gave up; this says why, from a stable enum. The
7823
+ * distinction is the whole point: a 2026-08-11 audit measured ~63% of
7824
+ * production extraction turns ending in `empty-after-retry` and could not tell
7825
+ * from telemetry whether the cause was the freeloader 403 everyone assumed or
7826
+ * something else. It took a Prometheus cross-check to find the real one — the
7827
+ * portal returning HTTP 200 with an empty body, which it counts as a success.
7828
+ *
7829
+ * Forward `failure.reason` into your extraction analytics event; all three
7830
+ * fields are bounded (an enum, an HTTP status, a small attempt count) and none
7831
+ * carries content.
7832
+ */
7833
+ failure?: PortalLlmFailure;
7810
7834
  }
7811
7835
  /** @public */
7812
7836
  /**
@@ -141,7 +141,7 @@ import {
141
141
  updateVaultMemoryOp,
142
142
  upsertMessageOp,
143
143
  vaultMemoryToStored
144
- } from "./chunk-IP6PGI7H.mjs";
144
+ } from "./chunk-I56324FR.mjs";
145
145
  import {
146
146
  createStreamingDeAnonymizer,
147
147
  notifyConsolidationFallback,
@@ -6371,7 +6371,7 @@ function createAutoExtractor(options) {
6371
6371
  const t0 = Date.now();
6372
6372
  void (async () => {
6373
6373
  try {
6374
- const { candidates, results, failedCount, outcome } = await extractAndRetain(
6374
+ const { candidates, results, failedCount, outcome, failure } = await extractAndRetain(
6375
6375
  window2,
6376
6376
  retainCtx,
6377
6377
  {
@@ -6411,7 +6411,11 @@ function createAutoExtractor(options) {
6411
6411
  failedCount,
6412
6412
  durationMs: Date.now() - t0,
6413
6413
  conversationId,
6414
- outcome
6414
+ outcome,
6415
+ // Only set alongside `outcome: "empty-after-retry"`. Spread so the key
6416
+ // is absent rather than explicitly undefined on a healthy turn —
6417
+ // analytics backends store an explicit undefined as a real value.
6418
+ ...failure !== void 0 && { failure }
6415
6419
  });
6416
6420
  } catch (err) {
6417
6421
  options.onError?.(err instanceof Error ? err : new Error(String(err)), conversationId);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-IP6PGI7H.mjs";
3
+ } from "./chunk-I56324FR.mjs";
4
4
  import "./chunk-KF5OU4IN.mjs";
5
5
  import "./chunk-3RPGXXPZ.mjs";
6
6
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "6.8.2",
3
+ "version": "6.9.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",