@anuma/sdk 6.43.0 → 6.44.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.
@@ -20,7 +20,7 @@ import {
20
20
  taskTypeHeader,
21
21
  validateEndpointOverride,
22
22
  withInternalFlowMarker
23
- } from "./chunk-RQ3B2IYV.mjs";
23
+ } from "./chunk-AF37N5Z6.mjs";
24
24
  import {
25
25
  BASE_URL,
26
26
  MCP_R2_DOMAIN,
@@ -3738,7 +3738,10 @@ async function embedOne(text14, options, outer) {
3738
3738
  const cached = cache.get(text14);
3739
3739
  if (cached) return Array.from(cached);
3740
3740
  }
3741
- const headers = await resolveAuthHeaders(options, outer);
3741
+ const headers = {
3742
+ ...await resolveAuthHeaders(options, outer),
3743
+ ...taskTypeHeader(options.taskType)
3744
+ };
3742
3745
  const response = await withEmbeddingRetry(
3743
3746
  () => withDeadline(
3744
3747
  (signal) => postApiV1Embeddings({
@@ -3833,7 +3836,10 @@ async function embedMany(texts, options, outer) {
3833
3836
  if (uncachedTexts.length === 0) {
3834
3837
  return results;
3835
3838
  }
3836
- const headers = await resolveAuthHeaders(options, outer);
3839
+ const headers = {
3840
+ ...await resolveAuthHeaders(options, outer),
3841
+ ...taskTypeHeader(options.taskType)
3842
+ };
3837
3843
  if (outer?.aborted) throw outer.reason;
3838
3844
  const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
3839
3845
  let newEmbeddings;
@@ -13541,6 +13547,50 @@ function applyMMR(candidates, k, lambda = 0.5) {
13541
13547
  return selected;
13542
13548
  }
13543
13549
 
13550
+ // src/lib/memory/recallTelemetry.ts
13551
+ function notifyMemoryCallback(callback, value) {
13552
+ try {
13553
+ callback?.(value);
13554
+ } catch {
13555
+ return;
13556
+ }
13557
+ }
13558
+ function factRecallDiagnostics(scores, elapsedMs, options = {}) {
13559
+ return {
13560
+ usedBudget: "low",
13561
+ reranked: false,
13562
+ candidateCount: options.candidateCount ?? scores.length,
13563
+ ...options.vaultSize !== void 0 && { vaultSize: options.vaultSize },
13564
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
13565
+ ...options.vaultRowsDecrypted !== void 0 && {
13566
+ vaultRowsDecrypted: options.vaultRowsDecrypted
13567
+ },
13568
+ ...options.vaultRowsEmbedded !== void 0 && {
13569
+ vaultRowsEmbedded: options.vaultRowsEmbedded
13570
+ },
13571
+ factCount: scores.length,
13572
+ chunkCount: 0,
13573
+ admittedCount: scores.length,
13574
+ topScore: scores.length ? Math.max(...scores) : -1,
13575
+ lowestAdmittedScore: scores.length ? Math.min(...scores) : -1,
13576
+ minScoreApplied: options.minScore ?? -1,
13577
+ truncated: options.truncated ?? false,
13578
+ graphLaneCount: 0,
13579
+ temporalLaneCount: 0,
13580
+ emptyReason: scores.length ? "" : options.vaultSize === 0 ? "vault-empty" : "no-candidates",
13581
+ timings: {
13582
+ total: elapsedMs,
13583
+ prep: 0,
13584
+ factLane: elapsedMs,
13585
+ rerank: 0,
13586
+ queryEmbed: options.queryEmbedMs ?? 0,
13587
+ chunkLane: 0,
13588
+ fuse: 0
13589
+ },
13590
+ degraded: options.embeddingsUnavailable ? ["embeddings-unavailable"] : []
13591
+ };
13592
+ }
13593
+
13544
13594
  // src/lib/memory/recency.ts
13545
13595
  var DAY_MS2 = 1e3 * 60 * 60 * 24;
13546
13596
  var DEFAULT_PER_YEAR_DECAY = 0.2;
@@ -14230,6 +14280,7 @@ function rankVaultMemories(_query, queryEmbedding, items, options) {
14230
14280
  }
14231
14281
  filtered.sort((a, b) => b.similarity - a.similarity);
14232
14282
  }
14283
+ notifyMemoryCallback(options?.onCandidateCount, filtered.length);
14233
14284
  const itemById = new Map(items.map((i) => [i.id, i]));
14234
14285
  return filtered.slice(0, limit).map((r) => resultFromItem(itemById.get(r.uniqueId), r.similarity));
14235
14286
  }
@@ -15212,6 +15263,7 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
15212
15263
  const results = rankVaultMemories(query, queryEmbedding, embeddedItems, {
15213
15264
  limit,
15214
15265
  minSimilarity,
15266
+ onCandidateCount: searchOptions?.onCandidateCount,
15215
15267
  ...searchOptions?.supersessionBoost !== void 0 && {
15216
15268
  supersessionBoost: searchOptions.supersessionBoost
15217
15269
  },
@@ -15312,16 +15364,20 @@ var DEFAULT_SCOPE = "private";
15312
15364
  var DEFAULT_CONSOLIDATE_THRESHOLD = 0.55;
15313
15365
  var DEFAULT_CONSOLIDATE_TOP_K = 20;
15314
15366
  async function retain(content, ctx, options = {}) {
15367
+ const embeddingOptions = {
15368
+ ...ctx.embeddingOptions,
15369
+ taskType: "memory_retain_embed"
15370
+ };
15315
15371
  return retainWithPersistence(
15316
15372
  content,
15317
15373
  {
15318
- embeddingOptions: ctx.embeddingOptions,
15374
+ embeddingOptions,
15319
15375
  vaultCache: ctx.vaultCache,
15320
15376
  persistence: {
15321
15377
  prepare: (query, searchOptions) => prepareVaultCandidates(
15322
15378
  query,
15323
15379
  ctx.vaultCtx,
15324
- ctx.embeddingOptions,
15380
+ embeddingOptions,
15325
15381
  ctx.vaultCache,
15326
15382
  searchOptions
15327
15383
  ),
@@ -15605,7 +15661,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
15605
15661
  content: m.content,
15606
15662
  similarity: m.similarity
15607
15663
  }));
15608
- const { consolidateMemory: doConsolidate } = await import("./consolidate-7WCBP5J7.mjs");
15664
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-TOITBD6W.mjs");
15609
15665
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
15610
15666
  if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
15611
15667
  if (decision.action === "supersede" && decision.content) {
@@ -16469,7 +16525,17 @@ function parseVerdict(parsed) {
16469
16525
  }
16470
16526
 
16471
16527
  // src/lib/memory/decayWorker.ts
16472
- var EMPTY_RESULT = { archived: 0, deleted: 0, scanned: 0 };
16528
+ function emptyResult() {
16529
+ return {
16530
+ archived: 0,
16531
+ deleted: 0,
16532
+ scanned: 0,
16533
+ ruleDriven: { archived: 0, deleted: 0 },
16534
+ classifierDriven: { archived: 0, deleted: 0 },
16535
+ classifierCalls: 0,
16536
+ classifierCapHits: 0
16537
+ };
16538
+ }
16473
16539
  var DEFAULT_MAX_CLASSIFIER_CALLS_PER_SWEEP = 20;
16474
16540
  function resolveNow(now) {
16475
16541
  if (typeof now === "function") return now();
@@ -16508,42 +16574,45 @@ function createDecaySweeper(options) {
16508
16574
  let disposed = false;
16509
16575
  async function verdictFor(input, now, sweep) {
16510
16576
  const ruleVerdict = classifyDecay(input, now, policy);
16511
- if (ruleVerdict !== "keep") return ruleVerdict;
16512
- if (!classifier || !isBorderline(input)) return ruleVerdict;
16577
+ const rule = { verdict: ruleVerdict, source: "ruleDriven" };
16578
+ if (ruleVerdict !== "keep") return rule;
16579
+ if (!classifier || !isBorderline(input)) return rule;
16513
16580
  if (input.id) {
16514
16581
  const cached = classifierCache.get(input.id);
16515
- if (cached && cached.activityAt === lastActivityAt(input)) return cached.verdict;
16582
+ if (cached && cached.activityAt === lastActivityAt(input))
16583
+ return { verdict: cached.verdict, source: "classifierDriven" };
16516
16584
  }
16517
16585
  if (sweep.classifierCalls >= maxClassifierCalls) {
16586
+ sweep.classifierCapHits++;
16518
16587
  if (!sweep.ceilingLogged) {
16519
16588
  sweep.ceilingLogged = true;
16520
16589
  getLogger().warn(
16521
16590
  `[memory/decay] classifier per-sweep ceiling (${maxClassifierCalls}) reached; remaining borderline rows use the rule verdict this sweep`
16522
16591
  );
16523
16592
  }
16524
- return ruleVerdict;
16593
+ return rule;
16525
16594
  }
16526
16595
  sweep.classifierCalls++;
16527
16596
  try {
16528
16597
  const verdict = await classifier.classify(input, ruleVerdict, now);
16529
16598
  if (input.id) classifierCache.set(input.id, { activityAt: lastActivityAt(input), verdict });
16530
- return verdict;
16599
+ return { verdict, source: "classifierDriven" };
16531
16600
  } catch (err) {
16532
16601
  getLogger().warn(
16533
16602
  `[memory/decay] classifier failed; falling back to rule verdict: ${err instanceof Error ? err.message : String(err)}`
16534
16603
  );
16535
- return ruleVerdict;
16604
+ return rule;
16536
16605
  }
16537
16606
  }
16538
16607
  async function runSweep() {
16539
- if (disposed) return { ...EMPTY_RESULT };
16608
+ if (disposed) return emptyResult();
16540
16609
  const now = resolveNow(options.now);
16541
16610
  let candidates;
16542
16611
  try {
16543
16612
  candidates = await getDecayCandidatesRawOp(vaultCtx);
16544
16613
  } catch (err) {
16545
16614
  onError?.(err instanceof Error ? err : new Error(String(err)));
16546
- return { ...EMPTY_RESULT };
16615
+ return emptyResult();
16547
16616
  }
16548
16617
  if (classifierCache.size > 0) {
16549
16618
  const liveIds = new Set(candidates.map((c) => c.uniqueId));
@@ -16551,33 +16620,40 @@ function createDecaySweeper(options) {
16551
16620
  if (!liveIds.has(id)) classifierCache.delete(id);
16552
16621
  }
16553
16622
  }
16554
- const sweep = { classifierCalls: 0, ceilingLogged: false };
16623
+ const sweep = { classifierCalls: 0, classifierCapHits: 0, ceilingLogged: false };
16555
16624
  const toArchive = [];
16556
16625
  const toDelete = [];
16557
16626
  for (const c of candidates) {
16558
- const verdict = await verdictFor(toDecayInput(c), now, sweep);
16559
- if (verdict === "archive") toArchive.push(c);
16560
- else if (verdict === "delete") toDelete.push(c);
16561
- }
16562
- let archived = 0;
16563
- let deleted = 0;
16627
+ const { verdict, source } = await verdictFor(toDecayInput(c), now, sweep);
16628
+ if (verdict === "archive") toArchive.push({ candidate: c, source });
16629
+ else if (verdict === "delete") toDelete.push({ candidate: c, source });
16630
+ }
16631
+ const result = emptyResult();
16632
+ result.scanned = candidates.length;
16633
+ result.classifierCalls = sweep.classifierCalls;
16634
+ result.classifierCapHits = sweep.classifierCapHits;
16564
16635
  try {
16565
- for (const c of toArchive) {
16636
+ for (const { candidate: c, source } of toArchive) {
16566
16637
  const ok = await archiveVaultMemoryOp(vaultCtx, c.uniqueId, {
16567
16638
  now,
16568
16639
  expectedUpdatedAt: c.updatedAt,
16569
16640
  expectedLastObservedAt: c.lastObservedAt ?? null
16570
16641
  });
16571
- if (ok) archived++;
16642
+ if (ok) {
16643
+ result.archived++;
16644
+ result[source].archived++;
16645
+ }
16572
16646
  }
16573
- for (const c of toDelete) {
16647
+ for (const { candidate: c, source } of toDelete) {
16574
16648
  const ok = await hardDeleteDecayedOp(vaultCtx, c.uniqueId, { hardDeleteWindowMs, now });
16575
- if (ok) deleted++;
16649
+ if (ok) {
16650
+ result.deleted++;
16651
+ result[source].deleted++;
16652
+ }
16576
16653
  }
16577
16654
  } catch (err) {
16578
16655
  onError?.(err instanceof Error ? err : new Error(String(err)));
16579
16656
  }
16580
- const result = { archived, deleted, scanned: candidates.length };
16581
16657
  onSwept?.(result);
16582
16658
  return result;
16583
16659
  }
@@ -17175,6 +17251,10 @@ async function recall(query, ctx, options = {}) {
17175
17251
  const flags = flagsForBudget(usedBudget);
17176
17252
  const normalizedFacets = normalizeSubQueries(options.subQueries);
17177
17253
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
17254
+ const embeddingOptions = {
17255
+ ...ctx.embeddingOptions,
17256
+ taskType: "memory_recall_embed"
17257
+ };
17178
17258
  const t0 = nowMs2();
17179
17259
  let prepMs = 0;
17180
17260
  let factLaneMs = 0;
@@ -17270,7 +17350,7 @@ async function recall(query, ctx, options = {}) {
17270
17350
  let sharedEmbedMs = 0;
17271
17351
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
17272
17352
  needsChunkEmbedding ? generateEmbedding(query, {
17273
- ...ctx.embeddingOptions,
17353
+ ...embeddingOptions,
17274
17354
  totalTimeoutMs: queryEmbedTotalTimeoutMs
17275
17355
  }).finally(() => {
17276
17356
  sharedEmbedMs = nowMs2() - prepStart;
@@ -17328,7 +17408,7 @@ async function recall(query, ctx, options = {}) {
17328
17408
  } = await (ctx.factSource?.search.bind(ctx.factSource) ?? ((searchQuery, searchOptions) => searchVaultMemoriesWithSize(
17329
17409
  searchQuery,
17330
17410
  ctx.vaultCtx,
17331
- ctx.embeddingOptions,
17411
+ embeddingOptions,
17332
17412
  ctx.vaultCache,
17333
17413
  searchOptions
17334
17414
  )))(query, {
@@ -17694,7 +17774,7 @@ function isDumpQuery(query) {
17694
17774
  async function listRecentFacts(ctx, limit, toolOptions) {
17695
17775
  const store = toolOptions?.memoryStore;
17696
17776
  const vaultCtx = ctx.vaultCtx;
17697
- if (!vaultCtx && !store) return [];
17777
+ if (!vaultCtx && !store) return { memories: [], truncated: false };
17698
17778
  const read = (ids) => store ? store.list({ memoryIds: ids }) : getVaultMemoriesByIdsOp(vaultCtx, ids);
17699
17779
  const filters = {
17700
17780
  ...toolOptions?.scopes && { scopes: toolOptions.scopes },
@@ -17705,34 +17785,47 @@ async function listRecentFacts(ctx, limit, toolOptions) {
17705
17785
  ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId }
17706
17786
  });
17707
17787
  const readable = [];
17788
+ const readLimit = limit + 1;
17789
+ let batchLimit = limit;
17708
17790
  let start = 0;
17709
- for (let size = limit; readable.length < limit; size *= 2) {
17791
+ let projected = 0;
17792
+ let exhausted = false;
17793
+ for (let size = readLimit; readable.length < readLimit; size *= 2) {
17710
17794
  const ordered = await project(size);
17711
- for (let i = start; i < ordered.length && readable.length < limit; i += limit) {
17712
- const batch = ordered.slice(i, i + limit).map((p) => p.uniqueId);
17795
+ projected = ordered.length;
17796
+ exhausted = !store || ordered.length < size;
17797
+ for (let i = start; i < ordered.length && readable.length < readLimit; ) {
17798
+ const batch = ordered.slice(i, i + batchLimit).map((p) => p.uniqueId);
17713
17799
  const byId = new Map((await read(batch)).map((m) => [m.uniqueId, m]));
17800
+ const previousCount = readable.length;
17714
17801
  for (const id of batch) {
17715
17802
  const m = byId.get(id);
17716
17803
  if (m && !isEncrypted(m.content)) readable.push(m);
17717
- if (readable.length >= limit) break;
17804
+ if (readable.length >= readLimit) break;
17718
17805
  }
17806
+ batchLimit = readable.length > previousCount ? Math.min(limit, readLimit - readable.length) : limit;
17807
+ i += batch.length;
17719
17808
  }
17720
- if (!store || ordered.length < size) break;
17809
+ if (exhausted) break;
17721
17810
  start = ordered.length;
17722
17811
  }
17723
- return readable.map((m) => ({
17724
- id: m.uniqueId,
17725
- kind: "fact",
17726
- content: m.content,
17727
- score: 0,
17728
- folderId: m.folderId,
17729
- eventTimeStart: m.eventTimeStart,
17730
- eventTimeEnd: m.eventTimeEnd,
17731
- eventTimeKind: m.eventTimeKind,
17732
- factType: m.factType,
17733
- createdAt: m.createdAt,
17734
- updatedAt: m.updatedAt
17735
- }));
17812
+ return {
17813
+ memories: readable.slice(0, limit).map((m) => ({
17814
+ id: m.uniqueId,
17815
+ kind: "fact",
17816
+ content: m.content,
17817
+ score: 0,
17818
+ folderId: m.folderId,
17819
+ eventTimeStart: m.eventTimeStart,
17820
+ eventTimeEnd: m.eventTimeEnd,
17821
+ eventTimeKind: m.eventTimeKind,
17822
+ factType: m.factType,
17823
+ createdAt: m.createdAt,
17824
+ updatedAt: m.updatedAt
17825
+ })),
17826
+ ...exhausted && { vaultSize: projected },
17827
+ truncated: readable.length > limit
17828
+ };
17736
17829
  }
17737
17830
  function createRecallTool(ctx, toolOptions, callbacks) {
17738
17831
  if (toolOptions?.memoryStore && toolOptions.folderId !== void 0)
@@ -17771,7 +17864,11 @@ function createRecallTool(ctx, toolOptions, callbacks) {
17771
17864
  },
17772
17865
  executor: async (args) => {
17773
17866
  if (typeof args.query !== "string" || args.query.length === 0) {
17774
- throw new Error("recall_memory: `query` is required and must be a non-empty string.");
17867
+ const error = new Error(
17868
+ "recall_memory: `query` is required and must be a non-empty string."
17869
+ );
17870
+ notifyMemoryCallback(callbacks?.onError, error);
17871
+ throw error;
17775
17872
  }
17776
17873
  const query = args.query;
17777
17874
  const now = Date.now();
@@ -17799,10 +17896,17 @@ function createRecallTool(ctx, toolOptions, callbacks) {
17799
17896
  RECALL_MAX_MEMORIES_PER_CONVERSATION - conversationMemories
17800
17897
  );
17801
17898
  const effectiveLimit = Math.min(requestLimit, remainingTurnBudget, remainingConvBudget);
17899
+ const truncatedByBudget = effectiveLimit < requestLimit;
17900
+ if (truncatedByBudget) {
17901
+ notifyMemoryCallback(callbacks?.onTruncated, {
17902
+ requestedLimit: requestLimit,
17903
+ effectiveLimit,
17904
+ truncatedByBudget
17905
+ });
17906
+ }
17802
17907
  if (effectiveLimit <= 0) {
17803
17908
  return VOLUME_LIMIT_NOTICE;
17804
17909
  }
17805
- const truncatedByBudget = effectiveLimit < requestLimit;
17806
17910
  turnMemories += effectiveLimit;
17807
17911
  conversationMemories += effectiveLimit;
17808
17912
  let surfaced = 0;
@@ -17811,7 +17915,27 @@ function createRecallTool(ctx, toolOptions, callbacks) {
17811
17915
  const wantsRecent = args.sort === "recent" && defaultTypes.includes("fact") && (ctx.vaultCtx !== void 0 || toolOptions?.memoryStore !== void 0);
17812
17916
  let result;
17813
17917
  if (wantsRecent) {
17814
- result = { memories: await listRecentFacts(ctx, effectiveLimit, toolOptions) };
17918
+ const start = Date.now();
17919
+ const recent = await listRecentFacts(ctx, effectiveLimit, toolOptions);
17920
+ result = recent;
17921
+ notifyMemoryCallback(callbacks?.onDiagnostics, {
17922
+ ...factRecallDiagnostics(
17923
+ result.memories.map((m) => m.score),
17924
+ Date.now() - start,
17925
+ {
17926
+ truncated: recent.truncated,
17927
+ ...recent.vaultSize !== void 0 && { vaultSize: recent.vaultSize }
17928
+ }
17929
+ ),
17930
+ truncatedByBudget
17931
+ });
17932
+ if (recent.truncated && !truncatedByBudget) {
17933
+ notifyMemoryCallback(callbacks?.onTruncated, {
17934
+ requestedLimit: requestLimit,
17935
+ effectiveLimit,
17936
+ truncatedByBudget
17937
+ });
17938
+ }
17815
17939
  } else {
17816
17940
  let subQueries;
17817
17941
  if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
@@ -17823,6 +17947,14 @@ function createRecallTool(ctx, toolOptions, callbacks) {
17823
17947
  const recallOpts = {
17824
17948
  onDiagnostics: (d) => {
17825
17949
  recallDegraded = d.degraded;
17950
+ notifyMemoryCallback(callbacks?.onDiagnostics, { ...d, truncatedByBudget });
17951
+ if (d.truncated && !truncatedByBudget) {
17952
+ notifyMemoryCallback(callbacks?.onTruncated, {
17953
+ requestedLimit: requestLimit,
17954
+ effectiveLimit,
17955
+ truncatedByBudget
17956
+ });
17957
+ }
17826
17958
  },
17827
17959
  types: defaultTypes,
17828
17960
  limit: effectiveLimit,
@@ -17866,6 +17998,7 @@ ${TRUNCATION_NOTICE}` : formatted;
17866
17998
  const message = error instanceof Error ? error.message : "Unknown error";
17867
17999
  const wrapped = new Error(`recall_memory: search failed \u2014 ${message}`);
17868
18000
  wrapped.cause = error;
18001
+ notifyMemoryCallback(callbacks?.onError, wrapped);
17869
18002
  throw wrapped;
17870
18003
  } finally {
17871
18004
  const unused = effectiveLimit - surfaced;
@@ -18099,6 +18232,18 @@ When a memory refers to one of these, reuse the EXACT existing name (same spelli
18099
18232
  ` : "";
18100
18233
  for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
18101
18234
  const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
18235
+ const start = Date.now();
18236
+ let attempts = 0;
18237
+ let failure;
18238
+ const reportBatch = (unansweredCount) => {
18239
+ notifyMemoryCallback(options.onBatch, {
18240
+ memoryIds: batch.map((m) => m.id),
18241
+ attempts,
18242
+ durationMs: Date.now() - start,
18243
+ unansweredCount,
18244
+ ...failure && { failure }
18245
+ });
18246
+ };
18102
18247
  const rows = [];
18103
18248
  for (const m of batch) {
18104
18249
  const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
@@ -18120,6 +18265,12 @@ ${listing}
18120
18265
 
18121
18266
  List each memory's named entities.`,
18122
18267
  tag: "memory/topics",
18268
+ onAttempt: (attempt) => {
18269
+ attempts = attempt.attempt;
18270
+ },
18271
+ onFailure: (value) => {
18272
+ failure = value;
18273
+ },
18123
18274
  extra: { max_completion_tokens: MAX_COMPLETION_TOKENS },
18124
18275
  ...options.fetchFn && { fetchFn: options.fetchFn },
18125
18276
  ...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
@@ -18128,6 +18279,7 @@ List each memory's named entities.`,
18128
18279
  ...options.backoffMs && { backoffMs: options.backoffMs }
18129
18280
  });
18130
18281
  if (parsed === null) {
18282
+ reportBatch(batch.length);
18131
18283
  getLogger().warn(
18132
18284
  `[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
18133
18285
  );
@@ -18136,6 +18288,7 @@ List each memory's named entities.`,
18136
18288
  const validIds = new Set(batch.map((m) => m.id));
18137
18289
  const byId = parseTopicResponse(parsed, validIds);
18138
18290
  if (byId === null) {
18291
+ reportBatch(batch.length);
18139
18292
  getLogger().warn(
18140
18293
  `[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
18141
18294
  );
@@ -18148,6 +18301,7 @@ List each memory's named entities.`,
18148
18301
  }
18149
18302
  out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
18150
18303
  }
18304
+ reportBatch(batch.filter((m) => !byId.has(m.id)).length);
18151
18305
  }
18152
18306
  return out;
18153
18307
  }
@@ -18186,6 +18340,7 @@ function isDegradedTopicSkip(reason) {
18186
18340
  return DEGRADED_TOPIC_SKIP[reason] === true;
18187
18341
  }
18188
18342
  async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
18343
+ const start = Date.now();
18189
18344
  const entityCtx = ctx.entityCtx;
18190
18345
  if (!entityCtx) {
18191
18346
  throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
@@ -18226,7 +18381,18 @@ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
18226
18381
  skip(id, "unreadable");
18227
18382
  }
18228
18383
  }
18229
- const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
18384
+ const batches = [];
18385
+ const llmFailures = /* @__PURE__ */ new Map();
18386
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, {
18387
+ ...options,
18388
+ onBatch: (batch) => {
18389
+ batches.push(batch);
18390
+ if (batch.failure) {
18391
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
18392
+ }
18393
+ notifyMemoryCallback(options.onBatch, batch);
18394
+ }
18395
+ });
18230
18396
  const toStamp = [];
18231
18397
  for (const input of inputs) {
18232
18398
  const entities = entitiesByMemory.get(input.id);
@@ -18257,7 +18423,16 @@ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
18257
18423
  entitiesByMemory.delete(id);
18258
18424
  }
18259
18425
  }
18260
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
18426
+ return {
18427
+ entitiesByMemory,
18428
+ stampedIds,
18429
+ skippedIds,
18430
+ skippedReasons,
18431
+ batchCount: batches.length,
18432
+ durationMs: Date.now() - start,
18433
+ batches,
18434
+ llmFailures
18435
+ };
18261
18436
  }
18262
18437
 
18263
18438
  // src/lib/memory/store/local.ts
@@ -18412,7 +18587,7 @@ function createLocalMemoryStore(options) {
18412
18587
  search: (query, searchOptions) => searchVaultMemoriesWithSize(
18413
18588
  query,
18414
18589
  vaultCtx,
18415
- options.embeddingOptions,
18590
+ { ...options.embeddingOptions, taskType: "memory_recall_embed" },
18416
18591
  vaultCache,
18417
18592
  searchOptions
18418
18593
  ),
@@ -19074,6 +19249,7 @@ function createRemoteMemoryPipeline(options) {
19074
19249
  const budgetMs = search.queryEmbedTotalTimeoutMs ?? 8e3;
19075
19250
  const embed = (texts) => generateEmbeddings(texts, {
19076
19251
  ...embeddingOptions,
19252
+ taskType: mode === "retain" ? "memory_retain_embed" : "memory_recall_embed",
19077
19253
  totalTimeoutMs: budgetMs
19078
19254
  });
19079
19255
  const embedFacets = async () => {
@@ -19322,7 +19498,7 @@ function createRemoteMemoryPipeline(options) {
19322
19498
  const ranked = await rankPreparedVaultCandidates(
19323
19499
  text14,
19324
19500
  prepared,
19325
- embeddingOptions,
19501
+ { ...embeddingOptions, taskType: "memory_recall_embed" },
19326
19502
  searchOptions
19327
19503
  );
19328
19504
  return {
@@ -19344,7 +19520,7 @@ function createRemoteMemoryPipeline(options) {
19344
19520
  content,
19345
19521
  {
19346
19522
  persistence: port,
19347
- embeddingOptions,
19523
+ embeddingOptions: { ...embeddingOptions, taskType: "memory_retain_embed" },
19348
19524
  vaultCache: /* @__PURE__ */ new Map(),
19349
19525
  normalizeEmbedding: storedVector
19350
19526
  },
@@ -19720,6 +19896,7 @@ var MAX_BATCH = 50;
19720
19896
  var MAX_QUERY_IDS = 1e3;
19721
19897
  var MAX_QUERY_TOPICS = 100;
19722
19898
  var MAX_QUERY_FILTER = 20;
19899
+ var GRAPH_LANE_ROWS = 256;
19723
19900
  var RemoteMemoryPartialCreateError = class extends Error {
19724
19901
  constructor(created, cause) {
19725
19902
  super(`createMany stored ${created.length} memories before a batch failed`);
@@ -19815,7 +19992,11 @@ function createRemoteMemoryStore(options) {
19815
19992
  const { persistence, embeddingOptions } = options;
19816
19993
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
19817
19994
  const pollIntervalMs = options.pollIntervalMs ?? 6e4;
19818
- const pipeline = createRemoteMemoryPipeline(options);
19995
+ const pipeline = createRemoteMemoryPipeline({
19996
+ ...options,
19997
+ graphRanking: options.graphRanking ?? ((query, _traverse, recallOptions) => topicGraphRanking(query, recallOptions)),
19998
+ temporalRanking: options.temporalRanking ?? (() => Promise.resolve([]))
19999
+ });
19819
20000
  const subscribers = /* @__PURE__ */ new Set();
19820
20001
  const seen = /* @__PURE__ */ new Map();
19821
20002
  let timer;
@@ -19847,6 +20028,36 @@ function createRemoteMemoryStore(options) {
19847
20028
  const decrypted = (options2) => persistence.query(options2);
19848
20029
  const metadata2 = (options2) => persistence.queryMetadata(options2);
19849
20030
  const enumerate = (filters, max = Infinity) => pages(decrypted, filters, max);
20031
+ const memoriesByTopics = async (names) => {
20032
+ const wanted = new Set(names.map(normalizeEntityName).filter(Boolean));
20033
+ const out = /* @__PURE__ */ new Map();
20034
+ const pages2 = await Promise.all(
20035
+ chunks2([...wanted], MAX_QUERY_TOPICS).map((topics) => enumerate({ topics }))
20036
+ );
20037
+ for (const { memory } of pages2.flat()) {
20038
+ const matched = [...canonicalTopics(memory)].filter((name) => wanted.has(name));
20039
+ if (matched.length) out.set(memory.memory_id, new Set(matched));
20040
+ }
20041
+ return out;
20042
+ };
20043
+ const topicGraphRanking = async (query, recallOptions = {}) => {
20044
+ const wanted = new Set(extractQueryEntities(query).map(normalizeEntityName).filter(Boolean));
20045
+ if (wanted.size === 0 || recallOptions.memoryIds?.length === 0) return [];
20046
+ const rows = await pages(
20047
+ metadata2,
20048
+ {
20049
+ topics: [...wanted].slice(0, MAX_QUERY_TOPICS),
20050
+ ...recallOptions.scopes && { scopes: recallOptions.scopes },
20051
+ ...recallOptions.factTypes && { fact_types: recallOptions.factTypes },
20052
+ ...recallOptions.memoryIds && { memory_ids: recallOptions.memoryIds }
20053
+ },
20054
+ GRAPH_LANE_ROWS
20055
+ );
20056
+ return rows.map(({ memory }) => ({
20057
+ memoryId: memory.memory_id,
20058
+ matched: [...canonicalTopics(memory)].filter((name) => wanted.has(name)).length
20059
+ })).filter((row) => row.matched > 0).sort((a, b) => b.matched - a.matched).slice(0, NODE_BUDGET).map((row) => row.memoryId);
20060
+ };
19850
20061
  const enumerateIds = async (read, ids, filters) => (await Promise.all(
19851
20062
  chunks2([...new Set(ids)], MAX_QUERY_IDS).map(
19852
20063
  (memory_ids) => pages(read, { ...filters, memory_ids })
@@ -20072,7 +20283,8 @@ function createRemoteMemoryStore(options) {
20072
20283
  };
20073
20284
  },
20074
20285
  extractTopics: async (memoryIds, extractOptions) => {
20075
- const extractedAt = extractOptions.now ?? Date.now();
20286
+ const start = Date.now();
20287
+ const extractedAt = extractOptions.now ?? start;
20076
20288
  const skippedIds = [];
20077
20289
  const skippedReasons = /* @__PURE__ */ new Map();
20078
20290
  const skip = (id, reason) => {
@@ -20090,7 +20302,18 @@ function createRemoteMemoryStore(options) {
20090
20302
  else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
20091
20303
  else inputs.push({ id, content: memory.content });
20092
20304
  }
20093
- const entitiesByMemory = await extractEntitiesForMemories(inputs, extractOptions);
20305
+ const batches = [];
20306
+ const llmFailures = /* @__PURE__ */ new Map();
20307
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, {
20308
+ ...extractOptions,
20309
+ onBatch: (batch) => {
20310
+ batches.push(batch);
20311
+ if (batch.failure) {
20312
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
20313
+ }
20314
+ notifyMemoryCallback(extractOptions.onBatch, batch);
20315
+ }
20316
+ });
20094
20317
  const answered = /* @__PURE__ */ new Map();
20095
20318
  for (const input of inputs) {
20096
20319
  const entities = entitiesByMemory.get(input.id);
@@ -20139,7 +20362,16 @@ function createRemoteMemoryStore(options) {
20139
20362
  getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
20140
20363
  for (const id of answered.keys()) fail(id, "link-failed");
20141
20364
  }
20142
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
20365
+ return {
20366
+ entitiesByMemory,
20367
+ stampedIds,
20368
+ skippedIds,
20369
+ skippedReasons,
20370
+ batchCount: batches.length,
20371
+ durationMs: Date.now() - start,
20372
+ batches,
20373
+ llmFailures
20374
+ };
20143
20375
  },
20144
20376
  stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
20145
20377
  const ids = [...new Set(memoryIds)];
@@ -20266,18 +20498,7 @@ function createRemoteMemoryStore(options) {
20266
20498
  listArchived: async () => (await enumerate({ archived_only: true, order: "archived", include_embeddings: true })).map(
20267
20499
  (record2) => stored(record2.memory)
20268
20500
  ),
20269
- memoriesByTopics: async (names) => {
20270
- const wanted = new Set(names.map(normalizeEntityName).filter(Boolean));
20271
- const out = /* @__PURE__ */ new Map();
20272
- const pages2 = await Promise.all(
20273
- chunks2([...wanted], MAX_QUERY_TOPICS).map((topics) => enumerate({ topics }))
20274
- );
20275
- for (const { memory } of pages2.flat()) {
20276
- const matched = [...canonicalTopics(memory)].filter((name) => wanted.has(name));
20277
- if (matched.length) out.set(memory.memory_id, new Set(matched));
20278
- }
20279
- return out;
20280
- },
20501
+ memoriesByTopics,
20281
20502
  topicsByMemories: async (memoryIds) => {
20282
20503
  const out = /* @__PURE__ */ new Map();
20283
20504
  for (const { memory } of await enumerateIds(decrypted, memoryIds, {
@@ -22150,9 +22371,13 @@ function formatVaultHits(hits) {
22150
22371
  return hits.map((h, i) => `[${i + 1}] (id: ${h.id}, similarity: ${h.score.toFixed(2)})
22151
22372
  ${h.content}`).join("\n\n");
22152
22373
  }
22153
- function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOptions) {
22374
+ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOptions, callbacks) {
22154
22375
  const limit = searchOptions?.limit ?? 5;
22155
22376
  const minSimilarity = searchOptions?.minSimilarity ?? 0.1;
22377
+ const recallEmbeddingOptions = {
22378
+ ...embeddingOptions,
22379
+ taskType: "memory_recall_embed"
22380
+ };
22156
22381
  const tuningForward = {
22157
22382
  ...searchOptions?.rerankTopN !== void 0 && { rerankTopN: searchOptions.rerankTopN },
22158
22383
  ...searchOptions?.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
@@ -22206,6 +22431,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22206
22431
  const requestLimit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.floor(rawLimit), 1), RECALL_MAX_LIMIT) : limit;
22207
22432
  const argFolderId = args.folder_id;
22208
22433
  if (!query || typeof query !== "string") {
22434
+ notifyMemoryCallback(callbacks?.onError, new Error("A search query is required."));
22209
22435
  return "Error: A search query is required.";
22210
22436
  }
22211
22437
  try {
@@ -22213,7 +22439,11 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22213
22439
  const budget = wantsDecompose ? "high" : searchOptions?.rerank ? "mid" : "low";
22214
22440
  const folderId = searchOptions?.folderId ?? argFolderId;
22215
22441
  if (searchOptions?.useFusion === false) {
22442
+ let candidateCount;
22216
22443
  const legacyOptions = {
22444
+ onCandidateCount: (count2) => {
22445
+ candidateCount = count2;
22446
+ },
22217
22447
  limit: requestLimit,
22218
22448
  minSimilarity,
22219
22449
  useFusion: false,
@@ -22221,13 +22451,38 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22221
22451
  ...folderId !== void 0 && { folderId },
22222
22452
  ...searchOptions?.scopes && { scopes: searchOptions.scopes }
22223
22453
  };
22224
- const { results: legacy, embeddingsUnavailable } = "retain" in vaultCtx ? await vaultCtx.factSource.search(query, legacyOptions) : await searchVaultMemoriesWithSize(
22454
+ const start = Date.now();
22455
+ const outcome2 = "retain" in vaultCtx ? await vaultCtx.factSource.search(query, legacyOptions) : await searchVaultMemoriesWithSize(
22225
22456
  query,
22226
22457
  vaultCtx,
22227
- embeddingOptions,
22458
+ recallEmbeddingOptions,
22228
22459
  cache,
22229
22460
  legacyOptions
22230
22461
  );
22462
+ const { results: legacy, embeddingsUnavailable, vaultSize } = outcome2;
22463
+ const diagnostics = factRecallDiagnostics(
22464
+ legacy.map((m) => m.similarity),
22465
+ Date.now() - start,
22466
+ {
22467
+ vaultSize,
22468
+ minScore: minSimilarity,
22469
+ embeddingsUnavailable,
22470
+ candidateCount,
22471
+ truncated: candidateCount !== void 0 && candidateCount > requestLimit,
22472
+ decryptLast: outcome2.decryptLast,
22473
+ vaultRowsDecrypted: outcome2.rowsDecrypted,
22474
+ vaultRowsEmbedded: outcome2.rowsEmbedded,
22475
+ queryEmbedMs: outcome2.queryEmbedMs
22476
+ }
22477
+ );
22478
+ notifyMemoryCallback(callbacks?.onDiagnostics, diagnostics);
22479
+ if (diagnostics.truncated) {
22480
+ notifyMemoryCallback(callbacks?.onTruncated, {
22481
+ requestedLimit: requestLimit,
22482
+ effectiveLimit: requestLimit,
22483
+ truncatedByBudget: false
22484
+ });
22485
+ }
22231
22486
  if (legacy.length === 0) {
22232
22487
  return embeddingsUnavailable ? EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL : "No relevant memories found in the vault.";
22233
22488
  }
@@ -22249,6 +22504,14 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22249
22504
  {
22250
22505
  onDiagnostics: (d) => {
22251
22506
  recallDegraded = d.degraded;
22507
+ notifyMemoryCallback(callbacks?.onDiagnostics, d);
22508
+ if (d.truncated) {
22509
+ notifyMemoryCallback(callbacks?.onTruncated, {
22510
+ requestedLimit: requestLimit,
22511
+ effectiveLimit: requestLimit,
22512
+ truncatedByBudget: false
22513
+ });
22514
+ }
22252
22515
  },
22253
22516
  types: ["fact"],
22254
22517
  limit: requestLimit,
@@ -22281,6 +22544,10 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
22281
22544
 
22282
22545
  ${formatted}`;
22283
22546
  } catch (error) {
22547
+ notifyMemoryCallback(
22548
+ callbacks?.onError,
22549
+ error instanceof Error ? error : new Error(String(error))
22550
+ );
22284
22551
  const message = error instanceof Error ? error.message : "Unknown error";
22285
22552
  return `Error searching vault: ${message}`;
22286
22553
  }
@@ -23746,6 +24013,15 @@ function useChat(options) {
23746
24013
  detachControllerRef.current = null;
23747
24014
  pendingResumeRef.current = null;
23748
24015
  }, [resumable, fireCancel]);
24016
+ const cancel = useCallback3(
24017
+ (handle) => {
24018
+ if (resumable) fireCancel(handle);
24019
+ if (pendingResumeRef.current?.inferenceId === handle.inferenceId) {
24020
+ pendingResumeRef.current = null;
24021
+ }
24022
+ },
24023
+ [resumable, fireCancel]
24024
+ );
23749
24025
  const detach = useCallback3(() => {
23750
24026
  if (detachControllerRef.current) {
23751
24027
  detachControllerRef.current.abort();
@@ -24004,6 +24280,7 @@ function useChat(options) {
24004
24280
  isLoading,
24005
24281
  sendMessage,
24006
24282
  stop,
24283
+ cancel,
24007
24284
  detach,
24008
24285
  resumeStream: resumeStream2
24009
24286
  };
@@ -25403,6 +25680,7 @@ function useChatStorage(options) {
25403
25680
  isLoading,
25404
25681
  sendMessage: baseSendMessage,
25405
25682
  stop: baseStop,
25683
+ cancel: baseCancel,
25406
25684
  detach,
25407
25685
  resumeStream: baseResumeStream
25408
25686
  } = useChat({
@@ -25426,6 +25704,9 @@ function useChatStorage(options) {
25426
25704
  });
25427
25705
  const pendingResumeRef = useRef4(null);
25428
25706
  const isResumingRef = useRef4(false);
25707
+ const activeResumeRef = useRef4(null);
25708
+ const retirementRef = useRef4(null);
25709
+ const retirementStopRequestedRef = useRef4(false);
25429
25710
  const createConversation = useCallback4(
25430
25711
  async (opts) => {
25431
25712
  const { result, queued } = await writeOrQueue(
@@ -25625,6 +25906,143 @@ function useChatStorage(options) {
25625
25906
  activeToolSetsRef.current = activeToolSets;
25626
25907
  const getTokenRef = useRef4(getToken);
25627
25908
  getTokenRef.current = getToken;
25909
+ const finalizeResumedRow = useCallback4(
25910
+ async (ctx, data, wasStopped, responseDuration, viaQueue = false) => {
25911
+ const { content, thinking } = extractAssistantText(data);
25912
+ let parentMessageId = ctx.userMessageUniqueId;
25913
+ let knownIds = ctx.knownToolCallEventIds;
25914
+ if (ctx.convId && (!knownIds || !parentMessageId)) {
25915
+ let storedMessages = null;
25916
+ try {
25917
+ storedMessages = await getMessages(ctx.convId);
25918
+ } catch {
25919
+ }
25920
+ if (storedMessages) {
25921
+ if (!knownIds) {
25922
+ knownIds = /* @__PURE__ */ new Set();
25923
+ for (const msg of storedMessages) {
25924
+ if (msg.uniqueId === ctx.assistantUniqueId) continue;
25925
+ if (msg.toolCallEvents) {
25926
+ for (const evt of msg.toolCallEvents) {
25927
+ if (evt.id) knownIds.add(evt.id);
25928
+ }
25929
+ }
25930
+ }
25931
+ }
25932
+ if (!parentMessageId) {
25933
+ for (let i = storedMessages.length - 1; i >= 0; i--) {
25934
+ if (storedMessages[i].uniqueId !== ctx.assistantUniqueId) {
25935
+ parentMessageId = storedMessages[i].uniqueId;
25936
+ break;
25937
+ }
25938
+ }
25939
+ }
25940
+ }
25941
+ }
25942
+ const allToolCallEvents = getToolCallEvents(data);
25943
+ const currentTurnToolCallEvents = knownIds ? allToolCallEvents?.filter(
25944
+ (evt) => evt.id !== void 0 && evt.id !== null && !knownIds.has(evt.id)
25945
+ ) : allToolCallEvents;
25946
+ const baseSources = ctx.sources ?? [];
25947
+ const seenSourceUrls = new Set(
25948
+ baseSources.map((s) => s.url).filter((url) => !!url)
25949
+ );
25950
+ const toolEventSources = extractSourcesFromToolCallEvents(currentTurnToolCallEvents);
25951
+ const sources = [
25952
+ ...baseSources,
25953
+ ...toolEventSources.filter((s) => !s.url || !seenSourceUrls.has(s.url))
25954
+ ].filter((source) => !source.url?.includes(MCP_R2_DOMAIN));
25955
+ const rowOpts = {
25956
+ conversationId: ctx.convId,
25957
+ role: "assistant",
25958
+ content,
25959
+ model: data.model || ctx.model || "",
25960
+ imageModel: ctx.imageModel || getImageModel(data),
25961
+ usage: convertUsageToStored(data),
25962
+ responseDuration,
25963
+ sources,
25964
+ thoughtProcess: finalizeThoughtProcess(ctx.thoughtProcess),
25965
+ thinking,
25966
+ wasStopped,
25967
+ parentMessageId,
25968
+ toolCallEvents: currentTurnToolCallEvents && currentTurnToolCallEvents.length > 0 ? currentTurnToolCallEvents : void 0,
25969
+ uniqueId: ctx.assistantUniqueId
25970
+ };
25971
+ if (!viaQueue) return upsertMessageOp(storageCtx, rowOpts);
25972
+ const { result } = await writeOrQueue(
25973
+ "createMessage",
25974
+ rowOpts,
25975
+ () => upsertMessageOp(storageCtx, rowOpts),
25976
+ () => makeSyntheticStoredMessage(rowOpts),
25977
+ ctx.userMessageQueueId ? [ctx.userMessageQueueId] : []
25978
+ );
25979
+ return result;
25980
+ },
25981
+ [storageCtx, getMessages, writeOrQueue]
25982
+ );
25983
+ const retirePendingTurn = useCallback4(async () => {
25984
+ const visible = activeResumeRef.current;
25985
+ if (visible && !visible.headless) {
25986
+ baseStop();
25987
+ await visible.settled;
25988
+ }
25989
+ if (retirementRef.current) return retirementRef.current;
25990
+ const active = activeResumeRef.current;
25991
+ if (active && !active.headless) return "Resume already in progress";
25992
+ const pending = pendingResumeRef.current;
25993
+ if (!pending) return null;
25994
+ if (!pending.userMessageUniqueId) {
25995
+ if (active?.context === pending) return null;
25996
+ if (!pending.partialData) {
25997
+ pendingResumeRef.current = null;
25998
+ return null;
25999
+ }
26000
+ }
26001
+ if (active?.context === pending) return "Resume already in progress";
26002
+ pendingResumeRef.current = null;
26003
+ retirementStopRequestedRef.current = false;
26004
+ const retirement = Promise.resolve().then(async () => {
26005
+ try {
26006
+ const data = pending.partialData;
26007
+ if (data && pending.assistantUniqueId) {
26008
+ const { content, thinking } = extractAssistantText(data);
26009
+ if (content || thinking || (getToolCallEvents(data)?.length ?? 0) > 0) {
26010
+ if (walletAddress && signMessage && !hasEncryptionKey(walletAddress)) {
26011
+ try {
26012
+ await requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner);
26013
+ } catch {
26014
+ }
26015
+ }
26016
+ await finalizeResumedRow(
26017
+ pending,
26018
+ data,
26019
+ true,
26020
+ (Date.now() - pending.startTime) / 1e3,
26021
+ true
26022
+ );
26023
+ }
26024
+ }
26025
+ if (pending.handle) baseCancel(pending.handle);
26026
+ return null;
26027
+ } catch (err) {
26028
+ pendingResumeRef.current = pending;
26029
+ if (retirementStopRequestedRef.current && pending.handle) {
26030
+ baseCancel(pending.handle);
26031
+ pendingResumeRef.current = { ...pending, handle: null };
26032
+ }
26033
+ return err instanceof Error ? err.message : "Failed to store detached message";
26034
+ }
26035
+ });
26036
+ retirementRef.current = retirement;
26037
+ try {
26038
+ return await retirement;
26039
+ } finally {
26040
+ if (retirementRef.current === retirement) {
26041
+ retirementRef.current = null;
26042
+ retirementStopRequestedRef.current = false;
26043
+ }
26044
+ }
26045
+ }, [baseCancel, baseStop, finalizeResumedRow, walletAddress, signMessage, embeddedWalletSigner]);
25628
26046
  const sendMessage = useCallback4(
25629
26047
  async (args) => {
25630
26048
  const {
@@ -25676,7 +26094,11 @@ function useChatStorage(options) {
25676
26094
  maskForCall: (text14) => redactor ? redactor.maskText(text14) : text14
25677
26095
  };
25678
26096
  };
25679
- pendingResumeRef.current = null;
26097
+ const retirementError = await retirePendingTurn();
26098
+ if (retirementError) {
26099
+ if (!skipStorage) return { data: null, error: retirementError };
26100
+ getLogger().warn("[useChatStorage] detached partial not saved:", retirementError);
26101
+ }
25680
26102
  const effectiveAssistantUniqueId = assistantUniqueId ?? (resumable ? `msg_${uuidv77()}` : void 0);
25681
26103
  const embedToolText = (text14, mask, masked, token) => {
25682
26104
  const opts = {
@@ -26200,6 +26622,7 @@ function useChatStorage(options) {
26200
26622
  handle: detachedResult.resume ?? null,
26201
26623
  convId,
26202
26624
  userMessageUniqueId: storedUserMessage.uniqueId,
26625
+ userMessageQueueId: userMsgQueueId,
26203
26626
  assistantUniqueId: rowId,
26204
26627
  model,
26205
26628
  imageModel,
@@ -26426,6 +26849,7 @@ function useChatStorage(options) {
26426
26849
  getMessages,
26427
26850
  storageCtx,
26428
26851
  baseSendMessage,
26852
+ retirePendingTurn,
26429
26853
  embedMessageAsync,
26430
26854
  minContentLength,
26431
26855
  walletAddress,
@@ -26440,72 +26864,9 @@ function useChatStorage(options) {
26440
26864
  onPiiRedacted
26441
26865
  ]
26442
26866
  );
26443
- const finalizeResumedRow = useCallback4(
26444
- async (ctx, data, wasStopped, responseDuration) => {
26445
- const { content, thinking } = extractAssistantText(data);
26446
- let parentMessageId = ctx.userMessageUniqueId;
26447
- let knownIds = ctx.knownToolCallEventIds;
26448
- if (ctx.convId && (!knownIds || !parentMessageId)) {
26449
- let storedMessages = null;
26450
- try {
26451
- storedMessages = await getMessages(ctx.convId);
26452
- } catch {
26453
- }
26454
- if (storedMessages) {
26455
- if (!knownIds) {
26456
- knownIds = /* @__PURE__ */ new Set();
26457
- for (const msg of storedMessages) {
26458
- if (msg.toolCallEvents) {
26459
- for (const evt of msg.toolCallEvents) {
26460
- if (evt.id) knownIds.add(evt.id);
26461
- }
26462
- }
26463
- }
26464
- }
26465
- if (!parentMessageId) {
26466
- for (let i = storedMessages.length - 1; i >= 0; i--) {
26467
- if (storedMessages[i].uniqueId !== ctx.assistantUniqueId) {
26468
- parentMessageId = storedMessages[i].uniqueId;
26469
- break;
26470
- }
26471
- }
26472
- }
26473
- }
26474
- }
26475
- const allToolCallEvents = getToolCallEvents(data);
26476
- const currentTurnToolCallEvents = knownIds ? allToolCallEvents?.filter(
26477
- (evt) => evt.id !== void 0 && evt.id !== null && !knownIds.has(evt.id)
26478
- ) : allToolCallEvents;
26479
- const baseSources = ctx.sources ?? [];
26480
- const seenSourceUrls = new Set(
26481
- baseSources.map((s) => s.url).filter((url) => !!url)
26482
- );
26483
- const toolEventSources = extractSourcesFromToolCallEvents(currentTurnToolCallEvents);
26484
- const sources = [
26485
- ...baseSources,
26486
- ...toolEventSources.filter((s) => !s.url || !seenSourceUrls.has(s.url))
26487
- ].filter((source) => !source.url?.includes(MCP_R2_DOMAIN));
26488
- return upsertMessageOp(storageCtx, {
26489
- conversationId: ctx.convId,
26490
- role: "assistant",
26491
- content,
26492
- model: data.model || ctx.model || "",
26493
- imageModel: ctx.imageModel || getImageModel(data),
26494
- usage: convertUsageToStored(data),
26495
- responseDuration,
26496
- sources,
26497
- thoughtProcess: finalizeThoughtProcess(ctx.thoughtProcess),
26498
- thinking,
26499
- wasStopped,
26500
- parentMessageId,
26501
- uniqueId: ctx.assistantUniqueId
26502
- });
26503
- },
26504
- [storageCtx, getMessages]
26505
- );
26506
26867
  const resumeStream2 = useCallback4(
26507
26868
  async (handleOverride, opts) => {
26508
- if (isResumingRef.current) {
26869
+ if (isResumingRef.current || retirementRef.current) {
26509
26870
  return { data: null, error: "Resume already in progress", assistantMessage: null };
26510
26871
  }
26511
26872
  const pending = pendingResumeRef.current;
@@ -26534,6 +26895,7 @@ function useChatStorage(options) {
26534
26895
  if (pendingResumeRef.current === rctx) pendingResumeRef.current = null;
26535
26896
  };
26536
26897
  const safeFinalize = async (data, wasStopped, responseDuration) => {
26898
+ rctx.partialData = data;
26537
26899
  try {
26538
26900
  return { message: await finalizeResumedRow(rctx, data, wasStopped, responseDuration) };
26539
26901
  } catch (writeErr) {
@@ -26543,6 +26905,13 @@ function useChatStorage(options) {
26543
26905
  }
26544
26906
  };
26545
26907
  isResumingRef.current = true;
26908
+ let markSettled = () => {
26909
+ };
26910
+ const settled = new Promise((resolve) => {
26911
+ markSettled = resolve;
26912
+ });
26913
+ const activeResume = { context: rctx, headless: opts?.headless === true, settled };
26914
+ activeResumeRef.current = activeResume;
26546
26915
  try {
26547
26916
  const result = await baseResumeStream(handle, { headless: opts?.headless });
26548
26917
  const responseDuration = (Date.now() - rctx.startTime) / 1e3;
@@ -26619,11 +26988,17 @@ function useChatStorage(options) {
26619
26988
  throw err;
26620
26989
  } finally {
26621
26990
  isResumingRef.current = false;
26991
+ if (activeResumeRef.current === activeResume) activeResumeRef.current = null;
26992
+ markSettled();
26622
26993
  }
26623
26994
  },
26624
26995
  [baseResumeStream, currentConversationId, finalizeResumedRow, embedMessageAsync]
26625
26996
  );
26626
26997
  const stop = useCallback4(() => {
26998
+ if (retirementRef.current) {
26999
+ retirementStopRequestedRef.current = true;
27000
+ return;
27001
+ }
26627
27002
  const pending = pendingResumeRef.current;
26628
27003
  if (pending && pending.assistantUniqueId && !isResumingRef.current) {
26629
27004
  const finalize = pending;