@anuma/sdk 7.3.0 → 7.3.2

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.
@@ -16828,7 +16828,7 @@ function onKeyAvailable(address, callback) {
16828
16828
  callbacks.add(callback);
16829
16829
  return () => {
16830
16830
  callbacks.delete(callback);
16831
- if (callbacks.size === 0) {
16831
+ if (callbacks.size === 0 && keyAvailableCallbacks.get(address) === callbacks) {
16832
16832
  keyAvailableCallbacks.delete(address);
16833
16833
  }
16834
16834
  };
@@ -27777,6 +27777,67 @@ init_logger();
27777
27777
  // src/lib/db/entities/operations.ts
27778
27778
  var import_watermelondb18 = __toESM(require_watermelondb());
27779
27779
 
27780
+ // src/lib/db/memoryVault/types.ts
27781
+ var MEMORY_KINDS = [
27782
+ "display_name",
27783
+ "birth_date",
27784
+ "bio",
27785
+ "occupation",
27786
+ "interest",
27787
+ "gender",
27788
+ "height_cm",
27789
+ "looking_for",
27790
+ "politics",
27791
+ "religion",
27792
+ "ethnicity",
27793
+ "smoking",
27794
+ "drinking",
27795
+ "exercise",
27796
+ "sexuality",
27797
+ "prompt"
27798
+ ];
27799
+ var MEMORY_LEVELS = ["private", "matching", "profile"];
27800
+ var MemoryLevelError = class extends Error {
27801
+ constructor(message) {
27802
+ super(message);
27803
+ this.name = "MemoryLevelError";
27804
+ }
27805
+ };
27806
+ var MemorySourceIneligibleError = class extends Error {
27807
+ constructor() {
27808
+ super("Memory source is no longer eligible");
27809
+ this.name = "MemorySourceIneligibleError";
27810
+ }
27811
+ };
27812
+ var PUBLISHED_SCOPES = /* @__PURE__ */ new Set(["shared", "public"]);
27813
+ function levelFromScope(scope) {
27814
+ return typeof scope === "string" && PUBLISHED_SCOPES.has(scope) ? "matching" : "private";
27815
+ }
27816
+ function scopeForLevel(level) {
27817
+ return level === "private" ? "private" : "shared";
27818
+ }
27819
+ function resolveMemoryLevel(level, scope) {
27820
+ return typeof level === "string" && MEMORY_LEVELS.includes(level) ? level : levelFromScope(scope);
27821
+ }
27822
+ function levelForScopeChange(current, nextScope) {
27823
+ const implied = levelFromScope(nextScope);
27824
+ return implied === "matching" && resolveMemoryLevel(current.level, current.scope) === "profile" ? "profile" : implied;
27825
+ }
27826
+ function parseMedia(value) {
27827
+ if (typeof value !== "string" || value === "") return null;
27828
+ try {
27829
+ const parsed = JSON.parse(value);
27830
+ if (!Array.isArray(parsed)) return null;
27831
+ const refs = parsed.filter((r) => typeof r === "object" && r !== null).map((r) => ({
27832
+ feedItemId: Number(r.feedItemId ?? r.feed_item_id),
27833
+ objectKey: r.objectKey ?? r.object_key ?? null
27834
+ })).filter((r) => Number.isFinite(r.feedItemId));
27835
+ return refs.length > 0 ? refs : null;
27836
+ } catch {
27837
+ return null;
27838
+ }
27839
+ }
27840
+
27780
27841
  // src/lib/db/entities/types.ts
27781
27842
  var ENTITY_KINDS = [
27782
27843
  "person",
@@ -27826,7 +27887,7 @@ function parseTopics(value) {
27826
27887
  // src/lib/db/entities/operations.ts
27827
27888
  async function assertCanWrite(ctx) {
27828
27889
  if (ctx.canWrite && !await ctx.canWrite()) {
27829
- throw new Error("Memory source is no longer eligible");
27890
+ throw new MemorySourceIneligibleError();
27830
27891
  }
27831
27892
  }
27832
27893
  function entityToStored(e) {
@@ -28219,61 +28280,6 @@ function serializeSourceChunkIds(ids) {
28219
28280
  return JSON.stringify(ids.slice(start));
28220
28281
  }
28221
28282
 
28222
- // src/lib/db/memoryVault/types.ts
28223
- var MEMORY_KINDS = [
28224
- "display_name",
28225
- "birth_date",
28226
- "bio",
28227
- "occupation",
28228
- "interest",
28229
- "gender",
28230
- "height_cm",
28231
- "looking_for",
28232
- "politics",
28233
- "religion",
28234
- "ethnicity",
28235
- "smoking",
28236
- "drinking",
28237
- "exercise",
28238
- "sexuality",
28239
- "prompt"
28240
- ];
28241
- var MEMORY_LEVELS = ["private", "matching", "profile"];
28242
- var MemoryLevelError = class extends Error {
28243
- constructor(message) {
28244
- super(message);
28245
- this.name = "MemoryLevelError";
28246
- }
28247
- };
28248
- var PUBLISHED_SCOPES = /* @__PURE__ */ new Set(["shared", "public"]);
28249
- function levelFromScope(scope) {
28250
- return typeof scope === "string" && PUBLISHED_SCOPES.has(scope) ? "matching" : "private";
28251
- }
28252
- function scopeForLevel(level) {
28253
- return level === "private" ? "private" : "shared";
28254
- }
28255
- function resolveMemoryLevel(level, scope) {
28256
- return typeof level === "string" && MEMORY_LEVELS.includes(level) ? level : levelFromScope(scope);
28257
- }
28258
- function levelForScopeChange(current, nextScope) {
28259
- const implied = levelFromScope(nextScope);
28260
- return implied === "matching" && resolveMemoryLevel(current.level, current.scope) === "profile" ? "profile" : implied;
28261
- }
28262
- function parseMedia(value) {
28263
- if (typeof value !== "string" || value === "") return null;
28264
- try {
28265
- const parsed = JSON.parse(value);
28266
- if (!Array.isArray(parsed)) return null;
28267
- const refs = parsed.filter((r) => typeof r === "object" && r !== null).map((r) => ({
28268
- feedItemId: Number(r.feedItemId ?? r.feed_item_id),
28269
- objectKey: r.objectKey ?? r.object_key ?? null
28270
- })).filter((r) => Number.isFinite(r.feedItemId));
28271
- return refs.length > 0 ? refs : null;
28272
- } catch {
28273
- return null;
28274
- }
28275
- }
28276
-
28277
28283
  // src/lib/db/memoryVault/operations.ts
28278
28284
  function visibilityOrPrivate(value) {
28279
28285
  return value === "public" ? value : "private";
@@ -28293,7 +28299,7 @@ function isKinded(record) {
28293
28299
  }
28294
28300
  async function assertCanWrite2(ctx) {
28295
28301
  if (ctx.canWrite && !await ctx.canWrite()) {
28296
- throw new Error("Memory source is no longer eligible");
28302
+ throw new MemorySourceIneligibleError();
28297
28303
  }
28298
28304
  }
28299
28305
  function assertValidKindLevel(kind, level) {
@@ -28970,7 +28976,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
28970
28976
  ctx.embeddedWalletSigner
28971
28977
  );
28972
28978
  } catch (err) {
28973
- if (err instanceof MemoryLevelError) throw err;
28979
+ if (err instanceof MemoryLevelError || err instanceof MemorySourceIneligibleError) throw err;
28974
28980
  return null;
28975
28981
  }
28976
28982
  }
@@ -32667,6 +32673,13 @@ var DEFAULT_AUTO_MERGE_THRESHOLD = 0.8;
32667
32673
  var DEFAULT_SCOPE = "private";
32668
32674
  var DEFAULT_CONSOLIDATE_THRESHOLD = 0.55;
32669
32675
  var DEFAULT_CONSOLIDATE_TOP_K = 20;
32676
+ var RetainError = class extends Error {
32677
+ constructor(reason, message) {
32678
+ super(message);
32679
+ this.reason = reason;
32680
+ this.name = "RetainError";
32681
+ }
32682
+ };
32670
32683
  async function retain(content, ctx, options = {}) {
32671
32684
  const embeddingOptions = {
32672
32685
  ...ctx.embeddingOptions,
@@ -32722,7 +32735,8 @@ async function retainWithPersistence(content, ctx, options = {}) {
32722
32735
  });
32723
32736
  prepared = withoutKindedRows(prepared);
32724
32737
  if (prepared.embeddingFailure || prepared.embeddingsUnavailable) {
32725
- throw new Error(
32738
+ throw new RetainError(
32739
+ "embeddings-unavailable",
32726
32740
  "retain: embeddings unavailable \u2014 refusing to auto-merge against an inert cosine lane, which would create a duplicate instead of merging into the existing memory. Retry when embeddings recover, or pass enableAutoMerge: false to force a create."
32727
32741
  );
32728
32742
  }
@@ -33092,7 +33106,10 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
33092
33106
  async function assertMergeTargetGoneOrThrow(ctx, targetId) {
33093
33107
  const stillExists = await (ctx.persistence.getFresh ? ctx.persistence.getFresh(targetId) : ctx.persistence.get(targetId));
33094
33108
  if (stillExists && (stillExists.kind === null || stillExists.kind === void 0)) {
33095
- throw new Error(`retain: merge into memory ${targetId} failed to persist`);
33109
+ throw new RetainError(
33110
+ "merge-not-persisted",
33111
+ `retain: merge into memory ${targetId} failed to persist`
33112
+ );
33096
33113
  }
33097
33114
  }
33098
33115
 
@@ -33250,6 +33267,12 @@ function restoreCandidates(candidates, redactor, ownNames) {
33250
33267
  (c) => c.candidate.content.length <= MAX_CONTENT_LENGTH && !c.unresolved && !isLowSignalContent(c.candidate.content, ownNames)
33251
33268
  ).map((c) => c.candidate);
33252
33269
  }
33270
+ function writeFailureReasonOf(err) {
33271
+ if (err instanceof MemorySourceIneligibleError) return "source-ineligible";
33272
+ if (err instanceof RetainError) return err.reason;
33273
+ if (err?.name === "AbortError") return "aborted";
33274
+ return "unknown";
33275
+ }
33253
33276
  async function extractAndRetain(messages, retainCtx, options) {
33254
33277
  const { memoryStore, signal } = options;
33255
33278
  if (memoryStore === null) throw new Error("Memory store is not ready");
@@ -33339,6 +33362,7 @@ async function extractAndRetain(messages, retainCtx, options) {
33339
33362
  const results = [];
33340
33363
  const quarantinedInfo = [];
33341
33364
  let failedWrites = 0;
33365
+ let writeFailureReason;
33342
33366
  let auditRows;
33343
33367
  const tRetain = Date.now();
33344
33368
  for (const { candidate, isQuarantined, reason, signature } of toRetain) {
@@ -33434,6 +33458,7 @@ async function extractAndRetain(messages, retainCtx, options) {
33434
33458
  }
33435
33459
  } catch (err) {
33436
33460
  failedWrites++;
33461
+ writeFailureReason ?? (writeFailureReason = writeFailureReasonOf(err));
33437
33462
  log.warn("[memory/extract] retain failed for one candidate", err);
33438
33463
  options.onCandidateFailed?.(candidate, err);
33439
33464
  }
@@ -33446,6 +33471,7 @@ async function extractAndRetain(messages, retainCtx, options) {
33446
33471
  candidates: succeededCandidates,
33447
33472
  results,
33448
33473
  failedCount: failedWrites,
33474
+ ...writeFailureReason !== void 0 && { writeFailureReason },
33449
33475
  outcome,
33450
33476
  quarantined: quarantinedInfo,
33451
33477
  funnel: {
@@ -33716,7 +33742,17 @@ function createAutoExtractor(options) {
33716
33742
  const t0 = Date.now();
33717
33743
  void (async () => {
33718
33744
  try {
33719
- const { candidates, results, failedCount, outcome, failure, funnel, timings, model } = await extractAndRetain(window2, retainCtx, {
33745
+ const {
33746
+ candidates,
33747
+ results,
33748
+ failedCount,
33749
+ writeFailureReason,
33750
+ outcome,
33751
+ failure,
33752
+ funnel,
33753
+ timings,
33754
+ model
33755
+ } = await extractAndRetain(window2, retainCtx, {
33720
33756
  extract,
33721
33757
  memoryStore: options.memoryStore,
33722
33758
  signal: options.signal,
@@ -33752,6 +33788,7 @@ function createAutoExtractor(options) {
33752
33788
  candidates,
33753
33789
  results,
33754
33790
  failedCount,
33791
+ ...writeFailureReason !== void 0 && { writeFailureReason },
33755
33792
  durationMs: Date.now() - t0,
33756
33793
  conversationId,
33757
33794
  outcome,
@@ -35883,10 +35920,10 @@ function createLocalMemoryStore(options) {
35883
35920
  const assertWrite = async (writeOptions) => {
35884
35921
  writeOptions?.signal?.throwIfAborted();
35885
35922
  if (writeOptions?.canWrite && !await writeOptions.canWrite()) {
35886
- throw new Error("Memory source is no longer eligible");
35923
+ throw new MemorySourceIneligibleError();
35887
35924
  }
35888
35925
  if (options.canWrite && !await options.canWrite()) {
35889
- throw new Error("Memory source is no longer eligible");
35926
+ throw new MemorySourceIneligibleError();
35890
35927
  }
35891
35928
  writeOptions?.signal?.throwIfAborted();
35892
35929
  };
@@ -36843,10 +36880,20 @@ function batchError(result) {
36843
36880
  var AccountFailureError = class extends Error {
36844
36881
  };
36845
36882
  var LockedSourcesError = class extends Error {
36846
- constructor(persistentIds) {
36847
- super("Source messages are locked; retained for retry");
36883
+ constructor(persistentIds, locked) {
36884
+ const counts = /* @__PURE__ */ new Map();
36885
+ for (const message of locked) {
36886
+ const version = /^enc:(v\d+):/.exec(message.content)?.[1] ?? "unknown";
36887
+ const key = `${message.decryptionStatus ?? "no_status"}:${version}`;
36888
+ counts.set(key, (counts.get(key) ?? 0) + 1);
36889
+ }
36890
+ const summary = [...counts].map(([key, count]) => `${key}=${count}`).join(", ");
36891
+ super(`Source messages are locked; retained for retry (${summary})`);
36848
36892
  this.persistentIds = persistentIds;
36849
36893
  }
36894
+ get awaitingKey() {
36895
+ return this.persistentIds.length === 0;
36896
+ }
36850
36897
  };
36851
36898
  function splitTurns(messages) {
36852
36899
  const turns = [];
@@ -36900,6 +36947,8 @@ function createDurableAutoExtractor(options) {
36900
36947
  const attempts = /* @__PURE__ */ new Map();
36901
36948
  const sessionFailures = /* @__PURE__ */ new Map();
36902
36949
  const skippedThisSession = /* @__PURE__ */ new Set();
36950
+ const awaitingKey = /* @__PURE__ */ new Set();
36951
+ let keyEpoch = 0;
36903
36952
  const clock = options.now ?? Date.now;
36904
36953
  const reportError = (error, conversationId) => {
36905
36954
  try {
@@ -37093,13 +37142,15 @@ function createDurableAutoExtractor(options) {
37093
37142
  for (const job of pending) {
37094
37143
  if (disposed) break;
37095
37144
  const attempted = attempts.get(job.id) ?? 0;
37096
- if (attempted >= MAX_ATTEMPTS || skippedThisSession.has(job.id)) continue;
37145
+ if (attempted >= MAX_ATTEMPTS || skippedThisSession.has(job.id) || awaitingKey.has(job.id))
37146
+ continue;
37097
37147
  if (processed >= MAX_JOBS_PER_PASS) {
37098
37148
  scheduleAfterSuccess = true;
37099
37149
  break;
37100
37150
  }
37101
37151
  processed++;
37102
37152
  const conversationId = String(job._getRaw("conversation_id"));
37153
+ const epochAtRead = keyEpoch;
37103
37154
  let ids = [];
37104
37155
  let loaded = [];
37105
37156
  try {
@@ -37133,16 +37184,39 @@ function createDurableAutoExtractor(options) {
37133
37184
  );
37134
37185
  continue;
37135
37186
  }
37136
- const locked = loaded.filter((message) => message && isEncrypted(message.content));
37187
+ const foreign = new Set(
37188
+ ids.filter((_id, index) => loaded[index]?.conversationId !== conversationId)
37189
+ );
37190
+ if (foreign.size) {
37191
+ await database.write(
37192
+ () => job.update(
37193
+ (r) => r._setRaw(
37194
+ "message_ids",
37195
+ JSON.stringify(idsOf(job).filter((id) => !foreign.has(id)))
37196
+ )
37197
+ )
37198
+ );
37199
+ attempts.delete(job.id);
37200
+ scheduleAfterSuccess = true;
37201
+ reportError(
37202
+ new Error(`Dropped ${foreign.size} source id(s) from another conversation`),
37203
+ conversationId
37204
+ );
37205
+ continue;
37206
+ }
37207
+ const locked = loaded.filter(
37208
+ (message) => !!message && isEncrypted(message.content)
37209
+ );
37137
37210
  if (locked.length) {
37138
37211
  throw new LockedSourcesError(
37139
37212
  locked.flatMap(
37140
37213
  (message) => message.decryptionStatus === "auth_mismatch" || message.decryptionStatus === "invalid_payload" ? [message.uniqueId] : []
37141
- )
37214
+ ),
37215
+ locked
37142
37216
  );
37143
37217
  }
37144
37218
  const messages = loaded.flatMap(
37145
- (m) => m && m.conversationId === conversationId && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
37219
+ (m) => m && (m.role === "user" || m.role === "assistant") ? [{ id: m.uniqueId, role: m.role, content: m.content }] : []
37146
37220
  );
37147
37221
  const jobScope = currentScope() === "private" ? "private" : String(job._getRaw("scope"));
37148
37222
  const jobFolderId = job._getRaw("folder_id") ?? null;
@@ -37192,6 +37266,12 @@ function createDurableAutoExtractor(options) {
37192
37266
  scheduleAfterSuccess = true;
37193
37267
  } catch (error) {
37194
37268
  if (disposed) break;
37269
+ if (error instanceof LockedSourcesError && error.awaitingKey) {
37270
+ reportError(error, conversationId);
37271
+ if (keyEpoch === epochAtRead) awaitingKey.add(job.id);
37272
+ else scheduleAfterSuccess = true;
37273
+ continue;
37274
+ }
37195
37275
  attempts.set(job.id, attempted + 1);
37196
37276
  retry = true;
37197
37277
  reportError(error, conversationId);
@@ -37219,7 +37299,18 @@ function createDurableAutoExtractor(options) {
37219
37299
  activeBatch?.abort(options.signal?.reason);
37220
37300
  if (timer !== void 0) clearTimeout(timer);
37221
37301
  options.signal?.removeEventListener("abort", dispose);
37302
+ unsubscribeKey?.();
37222
37303
  }
37304
+ const unsubscribeKey = vault.walletAddress ? onKeyAvailable(vault.walletAddress, () => {
37305
+ keyEpoch++;
37306
+ if (!awaitingKey.size) return;
37307
+ awaitingKey.clear();
37308
+ if (timer !== void 0) {
37309
+ clearTimeout(timer);
37310
+ timer = void 0;
37311
+ }
37312
+ schedule(0);
37313
+ }) : void 0;
37223
37314
  if (options.signal?.aborted) dispose();
37224
37315
  else options.signal?.addEventListener("abort", dispose, { once: true });
37225
37316
  schedule(0);
@@ -37235,9 +37326,31 @@ function createDurableAutoExtractor(options) {
37235
37326
  (candidate) => candidate._getRaw("scope") === scope && (candidate._getRaw("folder_id") ?? null) === folderId
37236
37327
  );
37237
37328
  const positions = new Map(ids.map((id, index) => [id, index]));
37329
+ const watermarks = existing.flatMap((prior) => {
37330
+ const watermark = prior._getRaw("watermark");
37331
+ return typeof watermark === "string" && watermark ? [watermark] : [];
37332
+ });
37333
+ if (watermarks.length) {
37334
+ const misplaced = new Set(
37335
+ await storage.messagesCollection.query(
37336
+ import_watermelondb25.Q.where("id", import_watermelondb25.Q.oneOf(watermarks)),
37337
+ import_watermelondb25.Q.where("conversation_id", import_watermelondb25.Q.notEq(conversationId))
37338
+ ).fetchIds()
37339
+ );
37340
+ for (const prior of existing)
37341
+ if (misplaced.has(String(prior._getRaw("watermark"))))
37342
+ await prior.update((r) => {
37343
+ r._setRaw("watermark", null);
37344
+ r._setRaw("watermark_seq", null);
37345
+ });
37346
+ }
37238
37347
  const rows = await storage.messagesCollection.query(import_watermelondb25.Q.where("id", import_watermelondb25.Q.oneOf(ids))).fetch();
37348
+ const foreignRows = new Set(
37349
+ rows.filter((row) => row._getRaw("conversation_id") !== conversationId).map((row) => row.id)
37350
+ );
37239
37351
  const sequences = /* @__PURE__ */ new Map();
37240
37352
  for (const row of rows) {
37353
+ if (foreignRows.has(row.id)) continue;
37241
37354
  const sequence = Number(row._getRaw("message_id"));
37242
37355
  if (Number.isFinite(sequence) && sequence > 0) sequences.set(row.id, sequence);
37243
37356
  }
@@ -37280,13 +37393,14 @@ function createDurableAutoExtractor(options) {
37280
37393
  const ownWatermark = job?._getRaw("watermark");
37281
37394
  const atOwnWatermark = !!job && boundary >= 0 && failedSessionsOf(job, String(ownWatermark)) < MAX_FAILED_SESSIONS && (ids[boundary] === ownWatermark || sequences.get(ids[boundary]) !== void 0 && sequences.get(ids[boundary]) === seqOf(job));
37282
37395
  const overlapStart = atOwnWatermark ? Math.max(0, boundary - 1) : start;
37283
- const fresh = ids.slice(overlapStart).filter((id) => !foreignSources.has(id));
37396
+ const fresh = ids.slice(overlapStart).filter((id) => !foreignSources.has(id) && !foreignRows.has(id));
37284
37397
  if (!fresh.length) return;
37285
37398
  if (job) {
37286
37399
  await job.update(
37287
37400
  (r) => r._setRaw("message_ids", JSON.stringify([.../* @__PURE__ */ new Set([...idsOf(job), ...fresh])]))
37288
37401
  );
37289
37402
  attempts.delete(job.id);
37403
+ awaitingKey.delete(job.id);
37290
37404
  } else {
37291
37405
  await jobs.create((r) => {
37292
37406
  r._setRaw("owner_key", owner);
@@ -4931,6 +4931,7 @@ declare function extractFacts(messages: AutoExtractMessage[], options: ExtractFa
4931
4931
  * look like `no-facts`.
4932
4932
  */
4933
4933
  type ExtractOutcome = "extracted" | "no-facts" | "empty-after-retry" | "dropped-after-redaction";
4934
+ type WriteFailureReason = "source-ineligible" | "embeddings-unavailable" | "merge-not-persisted" | "aborted" | "unknown";
4934
4935
  /**
4935
4936
  * Stage 2 — for each extracted candidate, call retain() with auto-merge
4936
4937
  * enabled. When `consolidateOptions` are wired, retain()'s consolidation pass
@@ -5001,6 +5002,7 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
5001
5002
  candidates: ExtractedCandidate[];
5002
5003
  results: RetainResult[];
5003
5004
  failedCount: number;
5005
+ writeFailureReason?: WriteFailureReason;
5004
5006
  outcome: ExtractOutcome;
5005
5007
  /**
5006
5008
  * Tier-0 security (PR3) — candidates quarantined by the injection screen and
@@ -9036,6 +9038,7 @@ interface TurnCompleteEvent {
9036
9038
  results: RetainResult[];
9037
9039
  /** Per-candidate retain() failures. `onError` only fires on pipeline throws. */
9038
9040
  failedCount: number;
9041
+ writeFailureReason?: WriteFailureReason;
9039
9042
  durationMs: number;
9040
9043
  conversationId?: string;
9041
9044
  /**
@@ -4931,6 +4931,7 @@ declare function extractFacts(messages: AutoExtractMessage[], options: ExtractFa
4931
4931
  * look like `no-facts`.
4932
4932
  */
4933
4933
  type ExtractOutcome = "extracted" | "no-facts" | "empty-after-retry" | "dropped-after-redaction";
4934
+ type WriteFailureReason = "source-ineligible" | "embeddings-unavailable" | "merge-not-persisted" | "aborted" | "unknown";
4934
4935
  /**
4935
4936
  * Stage 2 — for each extracted candidate, call retain() with auto-merge
4936
4937
  * enabled. When `consolidateOptions` are wired, retain()'s consolidation pass
@@ -5001,6 +5002,7 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
5001
5002
  candidates: ExtractedCandidate[];
5002
5003
  results: RetainResult[];
5003
5004
  failedCount: number;
5005
+ writeFailureReason?: WriteFailureReason;
5004
5006
  outcome: ExtractOutcome;
5005
5007
  /**
5006
5008
  * Tier-0 security (PR3) — candidates quarantined by the injection screen and
@@ -9036,6 +9038,7 @@ interface TurnCompleteEvent {
9036
9038
  results: RetainResult[];
9037
9039
  /** Per-candidate retain() failures. `onError` only fires on pipeline throws. */
9038
9040
  failedCount: number;
9041
+ writeFailureReason?: WriteFailureReason;
9039
9042
  durationMs: number;
9040
9043
  conversationId?: string;
9041
9044
  /**