@mastra/memory 1.39.0-alpha.2 → 1.39.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/docs/references/reference-memory-settled.md +13 -5
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.d.ts +4 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/processors/index.cjs +1 -1
  9. package/dist/processors/index.js +1 -1
  10. package/dist/processors/observational-memory/extraction-runner.d.ts.map +1 -1
  11. package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
  12. package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
  13. package/dist/processors/observational-memory/observation-turn/step.d.ts +2 -0
  14. package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
  15. package/dist/processors/observational-memory/observation-turn/turn.d.ts +13 -0
  16. package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
  17. package/dist/processors/observational-memory/observational-memory.d.ts +17 -3
  18. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  19. package/dist/processors/observational-memory/observer-runner.d.ts.map +1 -1
  20. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  21. package/dist/processors/observational-memory/reflector-runner.d.ts +3 -0
  22. package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
  23. package/dist/processors/observational-memory/retry.d.ts +28 -1
  24. package/dist/processors/observational-memory/retry.d.ts.map +1 -1
  25. package/dist/{src-BDhP0_OK.js → src--zkaCof1.js} +224 -44
  26. package/dist/{src-BDhP0_OK.js.map → src--zkaCof1.js.map} +1 -1
  27. package/dist/{src-CJiR-m2F.cjs → src-BGgY5kgl.cjs} +224 -44
  28. package/dist/{src-CJiR-m2F.cjs.map → src-BGgY5kgl.cjs.map} +1 -1
  29. package/package.json +4 -3
@@ -18685,7 +18685,15 @@ const RETRY_CONFIG = {
18685
18685
  /** Cap on per-attempt delay (ms). */
18686
18686
  maxDelayMs: 12e4,
18687
18687
  /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
18688
- jitter: .2
18688
+ jitter: .2,
18689
+ /**
18690
+ * How long one observer, reflector, or extraction model call may go without streaming any
18691
+ * output before it is aborted and retried. Without it a provider that accepts the request but
18692
+ * never answers stalls the call forever, and the retry schedule above never gets a chance to run.
18693
+ * It measures inactivity, not total duration, so a long reply that keeps streaming is never cut
18694
+ * off. It is generous because reasoning models can think silently before their first token.
18695
+ */
18696
+ modelCallIdleTimeoutMs: 3e5
18689
18697
  };
18690
18698
  const TRANSIENT_MESSAGE_SUBSTRINGS = [
18691
18699
  "terminated",
@@ -18704,6 +18712,19 @@ const TRANSIENT_MESSAGE_SUBSTRINGS = [
18704
18712
  ];
18705
18713
  const INCOMPLETE_FINISH_REASONS = /* @__PURE__ */ new Set(["other", "unknown"]);
18706
18714
  /**
18715
+ * A model call that streamed nothing for `RETRY_CONFIG.modelCallIdleTimeoutMs`. Retryable, and
18716
+ * deliberately not an AbortError so `withRetry` doesn't treat it as cancellation.
18717
+ *
18718
+ * @internal
18719
+ */
18720
+ var OmModelCallTimeoutError = class extends Error {
18721
+ isRetryable = true;
18722
+ constructor(label, idleTimeoutMs) {
18723
+ super(`${label} call produced no output for ${idleTimeoutMs}ms`);
18724
+ this.name = "OmModelCallTimeoutError";
18725
+ }
18726
+ };
18727
+ /**
18707
18728
  * A model reply that ended before the model finished. Carries the AI SDK's
18708
18729
  * `isRetryable` flag, which `isTransientLLMError` and core's error processors
18709
18730
  * already recognize.
@@ -18846,6 +18867,51 @@ function sleep(ms, abortSignal) {
18846
18867
  });
18847
18868
  }
18848
18869
  /**
18870
+ * Run one attempt, failing with a retryable `OmModelCallTimeoutError` if it
18871
+ * goes `idleTimeoutMs` without calling `onActivity`. `fn` gets a signal that
18872
+ * aborts on timeout or on the caller's signal, so the underlying request is
18873
+ * cancelled too.
18874
+ */
18875
+ async function attemptWithIdleTimeout(fn, label, idleTimeoutMs, abortSignal) {
18876
+ const controller = new AbortController();
18877
+ let rejectOnAbort;
18878
+ const callerAborted = new Promise((_, reject) => {
18879
+ rejectOnAbort = reject;
18880
+ });
18881
+ const onAbort = () => {
18882
+ controller.abort(abortSignal?.reason);
18883
+ rejectOnAbort(abortSignal?.reason ?? /* @__PURE__ */ new Error("The operation was aborted."));
18884
+ };
18885
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
18886
+ let timer;
18887
+ let rejectOnIdle;
18888
+ const idle = new Promise((_, reject) => {
18889
+ rejectOnIdle = reject;
18890
+ });
18891
+ const armIdleTimer = () => {
18892
+ clearTimeout(timer);
18893
+ timer = setTimeout(() => {
18894
+ const error = new OmModelCallTimeoutError(label, idleTimeoutMs);
18895
+ rejectOnIdle(error);
18896
+ controller.abort(error);
18897
+ }, idleTimeoutMs);
18898
+ };
18899
+ const onActivity = () => {
18900
+ if (!controller.signal.aborted) armIdleTimer();
18901
+ };
18902
+ armIdleTimer();
18903
+ try {
18904
+ return await Promise.race([
18905
+ fn(controller.signal, onActivity),
18906
+ idle,
18907
+ callerAborted
18908
+ ]);
18909
+ } finally {
18910
+ clearTimeout(timer);
18911
+ abortSignal?.removeEventListener("abort", onAbort);
18912
+ }
18913
+ }
18914
+ /**
18849
18915
  * Run `fn` with retries on transient transport-class errors.
18850
18916
  *
18851
18917
  * Non-transient errors (auth, validation, schema, etc.) are rethrown
@@ -18854,14 +18920,16 @@ function sleep(ms, abortSignal) {
18854
18920
  * @internal
18855
18921
  */
18856
18922
  async function withRetry(fn, opts) {
18857
- const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries } = opts;
18923
+ const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries, idleTimeoutMs } = opts;
18858
18924
  let attempt = 0;
18859
18925
  while (true) {
18860
18926
  if (abortSignal?.aborted) throw new Error("The operation was aborted.");
18861
18927
  try {
18862
- return await fn();
18928
+ if (idleTimeoutMs === void 0) return await fn(abortSignal ?? new AbortController().signal, () => {});
18929
+ return await attemptWithIdleTimeout(fn, label, idleTimeoutMs, abortSignal);
18863
18930
  } catch (error) {
18864
- if (hasAbortInChain(error) || abortSignal?.aborted) throw error;
18931
+ if (abortSignal?.aborted) throw error;
18932
+ if (!(error instanceof OmModelCallTimeoutError) && hasAbortInChain(error)) throw error;
18865
18933
  if (attempt >= maxRetries || !isTransientLLMError(error)) {
18866
18934
  if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
18867
18935
  throw error;
@@ -18981,19 +19049,21 @@ If a prior value is still applicable and carry-forward is enabled, return that p
18981
19049
  ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values\n\n${priorLines.join("\n\n")}` : ""}`;
18982
19050
  const values = {};
18983
19051
  const failures = [];
18984
- const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry(() => streamOnce(jsonPromptInjection), {
19052
+ const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry((attemptSignal, onActivity) => streamOnce(jsonPromptInjection, attemptSignal, onActivity), {
18985
19053
  label: `om-${opts.source}-structured-extraction`,
18986
19054
  abortSignal: opts.abortSignal,
18987
- maxRetries: 1
19055
+ maxRetries: 1,
19056
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs
18988
19057
  });
18989
- const streamOnce = async (jsonPromptInjection) => {
19058
+ const streamOnce = async (jsonPromptInjection, abortSignal, onActivity) => {
18990
19059
  const object = await (await opts.agent.stream(prompt, {
18991
19060
  structuredOutput: {
18992
19061
  schema,
18993
19062
  ...jsonPromptInjection ? { jsonPromptInjection } : {}
18994
19063
  },
18995
19064
  ...opts.memory ? { memory: opts.memory } : {},
18996
- ...opts.abortSignal ? { abortSignal: opts.abortSignal } : {},
19065
+ abortSignal,
19066
+ onChunk: onActivity,
18997
19067
  ...opts.requestContext ? { requestContext: opts.requestContext } : {},
18998
19068
  ...opts.observabilityContext
18999
19069
  })).object;
@@ -21761,7 +21831,7 @@ var ObserverRunner = class {
21761
21831
  timeZone: options?.timeZone
21762
21832
  })];
21763
21833
  const doGenerate = async () => {
21764
- return withRetry(() => withOmTracingSpan({
21834
+ return withRetry((attemptSignal, onActivity) => withOmTracingSpan({
21765
21835
  phase: "observer",
21766
21836
  model: resolvedModel.model,
21767
21837
  inputTokens,
@@ -21784,7 +21854,8 @@ var ObserverRunner = class {
21784
21854
  modelSettings: { ...this.observationConfig.modelSettings },
21785
21855
  providerOptions: this.observationConfig.providerOptions,
21786
21856
  ...attemptMemory ? { memory: attemptMemory } : {},
21787
- ...abortSignal ? { abortSignal } : {},
21857
+ abortSignal: attemptSignal,
21858
+ onChunk: onActivity,
21788
21859
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
21789
21860
  ...childObservabilityContext
21790
21861
  })).getFullOutput(), "OM observer");
@@ -21806,6 +21877,7 @@ var ObserverRunner = class {
21806
21877
  }), {
21807
21878
  label: "observer",
21808
21879
  abortSignal,
21880
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
21809
21881
  maxRetries: this.observationConfig.maxRetries
21810
21882
  });
21811
21883
  };
@@ -21974,7 +22046,7 @@ var ObserverRunner = class {
21974
22046
  timeZone
21975
22047
  })];
21976
22048
  const doGenerate = async () => {
21977
- return withRetry(() => withOmTracingSpan({
22049
+ return withRetry((attemptSignal, onActivity) => withOmTracingSpan({
21978
22050
  phase: "observer-multi-thread",
21979
22051
  model: resolvedModel.model,
21980
22052
  inputTokens,
@@ -21995,7 +22067,8 @@ var ObserverRunner = class {
21995
22067
  maxSteps: 1,
21996
22068
  modelSettings: { ...this.observationConfig.modelSettings },
21997
22069
  providerOptions: this.observationConfig.providerOptions,
21998
- ...abortSignal ? { abortSignal } : {},
22070
+ abortSignal: attemptSignal,
22071
+ onChunk: onActivity,
21999
22072
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
22000
22073
  ...childObservabilityContext
22001
22074
  })).getFullOutput(), "OM multi-thread observer");
@@ -22019,6 +22092,7 @@ var ObserverRunner = class {
22019
22092
  }), {
22020
22093
  label: "observer-multi-thread",
22021
22094
  abortSignal,
22095
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
22022
22096
  maxRetries: this.observationConfig.maxRetries
22023
22097
  });
22024
22098
  };
@@ -28598,6 +28672,7 @@ var ObservationStep = class {
28598
28672
  let observerExchange;
28599
28673
  const step0Messages = this.stepNumber === 0 ? getObservableMessages(messageList) : [];
28600
28674
  if (this.stepNumber === 0) {
28675
+ this.turn.inputMessageIds = messageList.get.input.db().map((msg) => msg.id);
28601
28676
  const activation = await om.activate({
28602
28677
  threadId,
28603
28678
  resourceId,
@@ -28699,19 +28774,36 @@ var ObservationStep = class {
28699
28774
  observabilityContext: this.turn.observabilityContext
28700
28775
  }).catch((err) => {
28701
28776
  omDebug(`[OM:buffer] fire-and-forget buffer failed: ${err?.message}`);
28702
- }));
28777
+ }), {
28778
+ threadId,
28779
+ resourceId
28780
+ });
28703
28781
  buffered = true;
28704
28782
  }
28705
28783
  }
28706
28784
  const bandDefersObservation = statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate;
28707
28785
  const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls && !bandDefersObservation;
28708
- /** In-flight message ids the step-0 cleanup must never remove from live context. */
28709
- let step0PreserveIds;
28786
+ /**
28787
+ * In-flight message ids cleanup must never remove from live context: the turn's own
28788
+ * request, plus whatever the model has not read yet (the pending messages at step 0, the
28789
+ * previous step's output — e.g. a tool result — at step > 0).
28790
+ */
28791
+ let inFlightIds;
28710
28792
  if (this.stepNumber > 0 || willObserveNow) {
28711
28793
  if (this.stepNumber > 0) {
28712
28794
  const newInput = messageList.clear.input.db();
28713
28795
  const newOutput = messageList.clear.response.db();
28714
28796
  const messagesToSave = [...newInput, ...newOutput];
28797
+ const drainedIds = new Set(messagesToSave.map((msg) => msg.id));
28798
+ const updatedEarlier = messageList.get.all.db().filter((msg) => !drainedIds.has(msg.id) && this.turn.drainedPartKeys.has(msg.id));
28799
+ const drainedKeys = /* @__PURE__ */ new Map();
28800
+ const unreadIds = [...messagesToSave, ...updatedEarlier].filter((msg) => {
28801
+ const keys = getPartKeys(msg);
28802
+ const seen = this.turn.drainedPartKeys.get(msg.id);
28803
+ drainedKeys.set(msg.id, /* @__PURE__ */ new Set([...seen ?? [], ...keys]));
28804
+ return !seen || keys.some((key) => !seen.has(key));
28805
+ }).map((msg) => msg.id);
28806
+ inFlightIds = [.../* @__PURE__ */ new Set([...this.turn.inputMessageIds, ...unreadIds])];
28715
28807
  if (messagesToSave.length > 0) {
28716
28808
  for (const msg of messagesToSave) messageList.add(msg, "memory");
28717
28809
  try {
@@ -28722,10 +28814,11 @@ var ObservationStep = class {
28722
28814
  throw error;
28723
28815
  }
28724
28816
  }
28817
+ for (const [id, keys] of drainedKeys) this.turn.drainedPartKeys.set(id, keys);
28725
28818
  } else {
28726
28819
  const pending = [...messageList.get.input.db(), ...messageList.get.response.db()];
28727
28820
  if (pending.length > 0) await om.persistMessages(pending, threadId, resourceId);
28728
- step0PreserveIds = pending.map((msg) => msg.id);
28821
+ inFlightIds = pending.map((msg) => msg.id);
28729
28822
  }
28730
28823
  if (this.stepNumber === 0 && willObserveNow && this.turn.responseMessageId) {
28731
28824
  const seed = {
@@ -28759,7 +28852,7 @@ var ObservationStep = class {
28759
28852
  messages: messageList,
28760
28853
  observedMessageIds: observedIds,
28761
28854
  retentionFloor: minRemaining,
28762
- preserveMessageIds: step0PreserveIds
28855
+ preserveMessageIds: [...inFlightIds ?? [], ...this.changedSinceDrain(messageList)]
28763
28856
  });
28764
28857
  if (statusSnapshot.asyncObservationEnabled) await om.resetBufferingState({
28765
28858
  threadId,
@@ -28787,7 +28880,7 @@ var ObservationStep = class {
28787
28880
  });
28788
28881
  if (!didThresholdCleanup) {
28789
28882
  const fallbackCursor = this.turn.record.threadId ? (0, _mastra_core_memory.getThreadOMMetadata)((await om.getStorage().getThreadById({ threadId: this.turn.record.threadId }))?.metadata)?.lastObservedMessageCursor : void 0;
28790
- const pendingMessageIds = new Set([...messageList.get.input.db(), ...messageList.get.response.db()].map((msg) => msg.id).filter(Boolean));
28883
+ const pendingMessageIds = new Set([...messageList.get.input.db(), ...messageList.get.response.db()].map((msg) => msg.id).concat(inFlightIds ?? [], this.turn.inputMessageIds, this.changedSinceDrain(messageList)).filter(Boolean));
28791
28884
  filterObservedMessages({
28792
28885
  messageList,
28793
28886
  record: this.turn.record,
@@ -28816,6 +28909,13 @@ var ObservationStep = class {
28816
28909
  this._prepared = true;
28817
28910
  return this._context;
28818
28911
  }
28912
+ /** Ids of already-drained messages that now hold parts the model has not been sent. */
28913
+ changedSinceDrain(messageList) {
28914
+ return messageList.get.all.db().filter((msg) => {
28915
+ const seen = this.turn.drainedPartKeys.get(msg.id);
28916
+ return seen !== void 0 && getPartKeys(msg).some((key) => !seen.has(key));
28917
+ }).map((msg) => msg.id);
28918
+ }
28819
28919
  /**
28820
28920
  * Run the full threshold observation pipeline:
28821
28921
  * waitForBuffering → re-check → activate buffered chunks → reflect → observe
@@ -28988,6 +29088,49 @@ function restoreDrainedMessages(messageList, drained, source) {
28988
29088
  const ids = drained.map((msg) => msg.id).filter((id) => !stillQueued.has(id));
28989
29089
  for (const msg of messageList.removeByIds(ids)) messageList.add(msg, source, { merge: false });
28990
29090
  }
29091
+ /**
29092
+ * Identity keys of a message's non-marker parts. Tool parts are keyed by call id, state and result,
29093
+ * so a result merged in place into an existing tool-invocation part yields a new key. Bookkeeping
29094
+ * fields that change when a part is re-stamped are not part of the identity.
29095
+ */
29096
+ function getPartKeys(msg) {
29097
+ const seen = /* @__PURE__ */ new Map();
29098
+ return (msg.content.parts ?? []).flatMap((part) => {
29099
+ if (part.type.startsWith("data-om-")) return [];
29100
+ let identity;
29101
+ if (part.type === "tool-invocation") identity = toolPartIdentity(part.toolInvocation);
29102
+ else {
29103
+ const { providerMetadata: _providerMetadata, createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = part;
29104
+ identity = JSON.stringify(rest);
29105
+ }
29106
+ const ordinal = seen.get(identity) ?? 0;
29107
+ seen.set(identity, ordinal + 1);
29108
+ return [`${identity}#${ordinal}`];
29109
+ });
29110
+ }
29111
+ /**
29112
+ * Serializing a tool result on every step is quadratic in turn length (each step rescans every drained
29113
+ * message), so the identity is cached per invocation object and reused while its state and result
29114
+ * reference are unchanged. A result merged in place replaces that reference and is re-serialized.
29115
+ */
29116
+ const toolPartIdentityCache = /* @__PURE__ */ new WeakMap();
29117
+ function toolPartIdentity(invocation) {
29118
+ const result = invocation.state === "result" ? invocation.result : null;
29119
+ const cached = toolPartIdentityCache.get(invocation);
29120
+ if (cached && cached.state === invocation.state && cached.result === result) return cached.identity;
29121
+ const identity = JSON.stringify([
29122
+ "tool-invocation",
29123
+ invocation.toolCallId,
29124
+ invocation.state,
29125
+ result
29126
+ ]);
29127
+ toolPartIdentityCache.set(invocation, {
29128
+ state: invocation.state,
29129
+ result,
29130
+ identity
29131
+ });
29132
+ return identity;
29133
+ }
28991
29134
  //#endregion
28992
29135
  //#region src/processors/observational-memory/observation-turn/turn.ts
28993
29136
  /**
@@ -29021,6 +29164,19 @@ var ObservationTurn = class {
29021
29164
  _ended = false;
29022
29165
  /** Generation count at turn start — used to detect if reflection happened during the turn. */
29023
29166
  _generationCountAtStart = -1;
29167
+ /**
29168
+ * Ids of the messages this turn was started with (the user's request). Mid-turn
29169
+ * observation may record them, but they stay in the live prompt until the turn ends.
29170
+ * @internal Set by step 0.
29171
+ */
29172
+ inputMessageIds = [];
29173
+ /**
29174
+ * Fingerprints of the non-marker parts each message had when a step last drained it. A message
29175
+ * is unread step output only if it gained a part the model has not seen — including a tool result
29176
+ * merged in place into an existing tool-invocation part — not when it only gained OM markers.
29177
+ * @internal
29178
+ */
29179
+ drainedPartKeys = /* @__PURE__ */ new Map();
29024
29180
  /** Memory context provider — set via start(). Used by steps for beforeBuffer persistence. */
29025
29181
  memory;
29026
29182
  /** Optional stream writer for emitting markers. */
@@ -29174,7 +29330,10 @@ var ObservationTurn = class {
29174
29330
  skipMinimumTokenCheck: true
29175
29331
  }).catch((err) => {
29176
29332
  omDebug(`[OM:turn.end] idle buffer failed: ${err?.message}`);
29177
- }));
29333
+ }), {
29334
+ threadId: this.threadId,
29335
+ resourceId: this.resourceId
29336
+ });
29178
29337
  }
29179
29338
  return { record: this._record };
29180
29339
  }
@@ -29618,6 +29777,7 @@ var ReflectorRunner = class {
29618
29777
  * number per active thread/resource key and live for the runner's lifetime.
29619
29778
  */
29620
29779
  syncReflectionSuppression = /* @__PURE__ */ new Map();
29780
+ trackBackgroundWork;
29621
29781
  constructor(opts) {
29622
29782
  this.reflectionConfig = opts.reflectionConfig;
29623
29783
  this.observationConfig = opts.observationConfig;
@@ -29634,6 +29794,7 @@ var ReflectorRunner = class {
29634
29794
  this.memory = opts.memory;
29635
29795
  this.onReflectionCommitted = opts.onReflectionCommitted;
29636
29796
  this.hooks = opts.hooks;
29797
+ this.trackBackgroundWork = opts.trackBackgroundWork;
29637
29798
  }
29638
29799
  __registerMastra(mastra) {
29639
29800
  this.mastra = mastra;
@@ -29730,7 +29891,7 @@ var ReflectorRunner = class {
29730
29891
  const prompt = buildReflectorPrompt(observations, manualPrompt, currentLevel, skipContinuationHints, activeExtractors, priorExtractedValues);
29731
29892
  omDebug(`[OM:callReflector] ${isRetry ? `retry #${attemptNumber - 1}` : "first attempt"}: level=${currentLevel}, originalTokens=${originalTokens}, targetThreshold=${targetThreshold}, promptLen=${prompt.length}, skipContinuationHints=${skipContinuationHints}`);
29732
29893
  let chunkCount = 0;
29733
- const result = await withRetry(() => withOmTracingSpan({
29894
+ const result = await withRetry((attemptSignal, onActivity) => withOmTracingSpan({
29734
29895
  phase: "reflector",
29735
29896
  model: resolvedModel.model,
29736
29897
  inputTokens: originalTokens,
@@ -29754,17 +29915,19 @@ var ReflectorRunner = class {
29754
29915
  modelSettings: { ...this.reflectionConfig.modelSettings },
29755
29916
  providerOptions: this.reflectionConfig.providerOptions,
29756
29917
  ...attemptMemory ? { memory: attemptMemory } : {},
29757
- ...abortSignal ? { abortSignal } : {},
29918
+ abortSignal: attemptSignal,
29758
29919
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
29759
29920
  ...childObservabilityContext,
29921
+ onChunk(chunk) {
29922
+ onActivity();
29923
+ if (attemptNumber !== 1) return;
29924
+ chunkCount++;
29925
+ if (chunkCount === 1 || chunkCount % 50 === 0) {
29926
+ const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
29927
+ omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
29928
+ }
29929
+ },
29760
29930
  ...attemptNumber === 1 ? {
29761
- onChunk(chunk) {
29762
- chunkCount++;
29763
- if (chunkCount === 1 || chunkCount % 50 === 0) {
29764
- const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
29765
- omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
29766
- }
29767
- },
29768
29931
  onFinish(event) {
29769
29932
  omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
29770
29933
  },
@@ -29784,6 +29947,7 @@ var ReflectorRunner = class {
29784
29947
  }), {
29785
29948
  label: "reflector",
29786
29949
  abortSignal,
29950
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
29787
29951
  maxRetries: this.reflectionConfig.maxRetries
29788
29952
  });
29789
29953
  omDebug(`[OM:callReflector] attempt #${attemptNumber} returned: textLen=${result.text?.length}, textPreview="${result.text?.slice(0, 120)}...", inputTokens=${result.usage?.inputTokens ?? result.totalUsage?.inputTokens}, outputTokens=${result.usage?.outputTokens ?? result.totalUsage?.outputTokens}`);
@@ -29891,10 +30055,11 @@ var ReflectorRunner = class {
29891
30055
  if (this.buffering.isAsyncBufferingInProgress(bufferKey)) return;
29892
30056
  BufferingCoordinator.lastBufferedBoundary.set(bufferKey, observationTokens);
29893
30057
  registerOp(record.id, "bufferingReflection");
29894
- this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
30058
+ const flagSet = this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
29895
30059
  omError("[OM] Failed to set buffering reflection flag", err);
29896
30060
  });
29897
30061
  const asyncOp = (async () => {
30062
+ await flagSet;
29898
30063
  let outcome;
29899
30064
  let reflectionError;
29900
30065
  try {
@@ -29935,12 +30100,13 @@ var ReflectorRunner = class {
29935
30100
  }
29936
30101
  BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
29937
30102
  unregisterOp(record.id, "bufferingReflection");
29938
- this.storage.setBufferingReflectionFlag(record.id, false).catch((err) => {
30103
+ await this.storage.setBufferingReflectionFlag(record.id, false).catch((err) => {
29939
30104
  omError("[OM] Failed to clear buffering reflection flag", err);
29940
30105
  });
29941
30106
  }
29942
30107
  })();
29943
30108
  BufferingCoordinator.asyncBufferingOps.set(bufferKey, asyncOp);
30109
+ this.trackBackgroundWork?.(asyncOp, lockKey);
29944
30110
  }
29945
30111
  /**
29946
30112
  * Perform async buffered reflection — reflects observations and stores to bufferedReflection.
@@ -30968,32 +31134,44 @@ var ObservationalMemory = class ObservationalMemory {
30968
31134
  * Tracked so callers can join them via `settled()` before closing a storage
30969
31135
  * connection they own — otherwise a background cycle's tail statements race the close.
30970
31136
  */
30971
- pendingBackgroundWork = /* @__PURE__ */ new Set();
31137
+ pendingBackgroundWork = /* @__PURE__ */ new Map();
30972
31138
  /**
30973
31139
  * Register fire-and-forget background work so `settled()` can join it.
31140
+ * `scope` is the conversation the work belongs to (thread/resource ids, or a lock key from
31141
+ * `buffering.getLockKey`),
31142
+ * so a scoped `settled()` can wait for one conversation without waiting for every other.
30974
31143
  * Returns the original promise so callers keep their own rejection handling.
30975
31144
  */
30976
- trackBackgroundWork(work) {
31145
+ trackBackgroundWork(work, scope) {
31146
+ const lockKey = typeof scope === "string" ? scope : this.buffering.getLockKey(scope.threadId, scope.resourceId);
30977
31147
  const tracked = work.catch(() => {}).finally(() => {
30978
31148
  this.pendingBackgroundWork.delete(tracked);
30979
31149
  });
30980
- this.pendingBackgroundWork.add(tracked);
31150
+ this.pendingBackgroundWork.set(tracked, lockKey);
30981
31151
  return work;
30982
31152
  }
30983
31153
  /**
30984
- * Resolve once all background observational-memory work started so far has finished.
31154
+ * Resolve once background observational-memory work started so far has finished.
31155
+ *
31156
+ * Pass `threadId`/`resourceId` to wait only for that conversation's work. An engine shared by
31157
+ * many busy threads may never be fully idle, so an unscoped wait can last indefinitely there.
31158
+ * A scope without the id this engine keys work on (`resourceId` for resource scope, `threadId`
31159
+ * for thread scope) can't be matched to its work, so it falls back to waiting for all work.
30985
31160
  *
30986
31161
  * Background cycles enqueue further background work (a buffered observation can
30987
31162
  * trigger a reflection, which runs nested agent streams), so this drains repeatedly
30988
31163
  * until nothing is left rather than joining a single snapshot.
30989
31164
  */
30990
- async settled() {
31165
+ async settled(scope) {
31166
+ const lockKey = (this.scope === "resource" ? scope?.resourceId : scope?.threadId) ? this.buffering.getLockKey(scope.threadId, scope.resourceId) : void 0;
31167
+ const pending = () => [...this.pendingBackgroundWork].filter(([, key]) => lockKey === void 0 || key === lockKey).map(([w]) => w);
30991
31168
  const maxDrainRounds = 100;
30992
31169
  for (let round = 0; round < maxDrainRounds; round++) {
30993
- if (this.pendingBackgroundWork.size === 0) return;
30994
- await Promise.allSettled([...this.pendingBackgroundWork]);
31170
+ const work = pending();
31171
+ if (work.length === 0) return;
31172
+ await Promise.allSettled(work);
30995
31173
  }
30996
- if (this.pendingBackgroundWork.size === 0) return;
31174
+ if (pending().length === 0) return;
30997
31175
  omError(`[OM:settled] background work still pending after ${maxDrainRounds} drain rounds; giving up waiting`);
30998
31176
  }
30999
31177
  /**
@@ -31149,7 +31327,8 @@ var ObservationalMemory = class ObservationalMemory {
31149
31327
  mastra: config.mastra,
31150
31328
  memory: this.memory,
31151
31329
  onReflectionCommitted: config.onReflectionCommitted,
31152
- hooks: this.hooks
31330
+ hooks: this.hooks,
31331
+ trackBackgroundWork: (work, lockKey) => this.trackBackgroundWork(work, lockKey)
31153
31332
  });
31154
31333
  this.validateBufferConfig();
31155
31334
  if (this.scope === "resource" && !hasWarnedResourceScopeDeprecation) {
@@ -32005,11 +32184,12 @@ ${formattedMessages}
32005
32184
  }).finally(() => {
32006
32185
  if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === asyncOp) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
32007
32186
  unregisterOp(record.id, "bufferingObservation");
32008
- runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
32187
+ return runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
32009
32188
  omError("[OM] Failed to clear buffering observation flag", err);
32010
32189
  });
32011
32190
  });
32012
32191
  BufferingCoordinator.asyncBufferingOps.set(bufferKey, asyncOp);
32192
+ return { op: asyncOp };
32013
32193
  }
32014
32194
  /**
32015
32195
  * Internal method that waits for existing buffering operation and then runs new buffering.
@@ -32109,7 +32289,7 @@ ${formattedMessages}
32109
32289
  if (!this.buffering.isAsyncObservationEnabled()) return false;
32110
32290
  const lockKey = this.buffering.getLockKey(opts.threadId, opts.resourceId);
32111
32291
  const shouldTrigger = this.buffering.shouldTriggerAsyncObservation(opts.pendingTokens, lockKey, opts.record, this.storage, opts.threshold);
32112
- if (shouldTrigger) this.trackBackgroundWork(this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext, opts.observabilityContext));
32292
+ if (shouldTrigger) this.trackBackgroundWork(this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext, opts.observabilityContext).then(({ op }) => op), lockKey);
32113
32293
  return shouldTrigger;
32114
32294
  }
32115
32295
  isMessageList(value) {
@@ -34161,9 +34341,9 @@ var Memory = class Memory extends _mastra_core_memory.MastraMemory {
34161
34341
  * await store.close();
34162
34342
  * ```
34163
34343
  */
34164
- async settled() {
34344
+ async settled(scope) {
34165
34345
  await this.pendingVectorCleanup;
34166
- await (this._omEngine ? await this._omEngine : this._omEngineInstance)?.settled();
34346
+ await (this._omEngine ? await this._omEngine : this._omEngineInstance)?.settled(scope);
34167
34347
  await this.pendingVectorCleanup;
34168
34348
  }
34169
34349
  /** The shared ObservationalMemory engine. Lazily created on first access. */
@@ -36757,4 +36937,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
36757
36937
  }
36758
36938
  });
36759
36939
 
36760
- //# sourceMappingURL=src-CJiR-m2F.cjs.map
36940
+ //# sourceMappingURL=src-BGgY5kgl.cjs.map