@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
@@ -18664,7 +18664,15 @@ const RETRY_CONFIG = {
18664
18664
  /** Cap on per-attempt delay (ms). */
18665
18665
  maxDelayMs: 12e4,
18666
18666
  /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
18667
- jitter: .2
18667
+ jitter: .2,
18668
+ /**
18669
+ * How long one observer, reflector, or extraction model call may go without streaming any
18670
+ * output before it is aborted and retried. Without it a provider that accepts the request but
18671
+ * never answers stalls the call forever, and the retry schedule above never gets a chance to run.
18672
+ * It measures inactivity, not total duration, so a long reply that keeps streaming is never cut
18673
+ * off. It is generous because reasoning models can think silently before their first token.
18674
+ */
18675
+ modelCallIdleTimeoutMs: 3e5
18668
18676
  };
18669
18677
  const TRANSIENT_MESSAGE_SUBSTRINGS = [
18670
18678
  "terminated",
@@ -18683,6 +18691,19 @@ const TRANSIENT_MESSAGE_SUBSTRINGS = [
18683
18691
  ];
18684
18692
  const INCOMPLETE_FINISH_REASONS = /* @__PURE__ */ new Set(["other", "unknown"]);
18685
18693
  /**
18694
+ * A model call that streamed nothing for `RETRY_CONFIG.modelCallIdleTimeoutMs`. Retryable, and
18695
+ * deliberately not an AbortError so `withRetry` doesn't treat it as cancellation.
18696
+ *
18697
+ * @internal
18698
+ */
18699
+ var OmModelCallTimeoutError = class extends Error {
18700
+ isRetryable = true;
18701
+ constructor(label, idleTimeoutMs) {
18702
+ super(`${label} call produced no output for ${idleTimeoutMs}ms`);
18703
+ this.name = "OmModelCallTimeoutError";
18704
+ }
18705
+ };
18706
+ /**
18686
18707
  * A model reply that ended before the model finished. Carries the AI SDK's
18687
18708
  * `isRetryable` flag, which `isTransientLLMError` and core's error processors
18688
18709
  * already recognize.
@@ -18825,6 +18846,51 @@ function sleep(ms, abortSignal) {
18825
18846
  });
18826
18847
  }
18827
18848
  /**
18849
+ * Run one attempt, failing with a retryable `OmModelCallTimeoutError` if it
18850
+ * goes `idleTimeoutMs` without calling `onActivity`. `fn` gets a signal that
18851
+ * aborts on timeout or on the caller's signal, so the underlying request is
18852
+ * cancelled too.
18853
+ */
18854
+ async function attemptWithIdleTimeout(fn, label, idleTimeoutMs, abortSignal) {
18855
+ const controller = new AbortController();
18856
+ let rejectOnAbort;
18857
+ const callerAborted = new Promise((_, reject) => {
18858
+ rejectOnAbort = reject;
18859
+ });
18860
+ const onAbort = () => {
18861
+ controller.abort(abortSignal?.reason);
18862
+ rejectOnAbort(abortSignal?.reason ?? /* @__PURE__ */ new Error("The operation was aborted."));
18863
+ };
18864
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
18865
+ let timer;
18866
+ let rejectOnIdle;
18867
+ const idle = new Promise((_, reject) => {
18868
+ rejectOnIdle = reject;
18869
+ });
18870
+ const armIdleTimer = () => {
18871
+ clearTimeout(timer);
18872
+ timer = setTimeout(() => {
18873
+ const error = new OmModelCallTimeoutError(label, idleTimeoutMs);
18874
+ rejectOnIdle(error);
18875
+ controller.abort(error);
18876
+ }, idleTimeoutMs);
18877
+ };
18878
+ const onActivity = () => {
18879
+ if (!controller.signal.aborted) armIdleTimer();
18880
+ };
18881
+ armIdleTimer();
18882
+ try {
18883
+ return await Promise.race([
18884
+ fn(controller.signal, onActivity),
18885
+ idle,
18886
+ callerAborted
18887
+ ]);
18888
+ } finally {
18889
+ clearTimeout(timer);
18890
+ abortSignal?.removeEventListener("abort", onAbort);
18891
+ }
18892
+ }
18893
+ /**
18828
18894
  * Run `fn` with retries on transient transport-class errors.
18829
18895
  *
18830
18896
  * Non-transient errors (auth, validation, schema, etc.) are rethrown
@@ -18833,14 +18899,16 @@ function sleep(ms, abortSignal) {
18833
18899
  * @internal
18834
18900
  */
18835
18901
  async function withRetry(fn, opts) {
18836
- const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries } = opts;
18902
+ const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries, idleTimeoutMs } = opts;
18837
18903
  let attempt = 0;
18838
18904
  while (true) {
18839
18905
  if (abortSignal?.aborted) throw new Error("The operation was aborted.");
18840
18906
  try {
18841
- return await fn();
18907
+ if (idleTimeoutMs === void 0) return await fn(abortSignal ?? new AbortController().signal, () => {});
18908
+ return await attemptWithIdleTimeout(fn, label, idleTimeoutMs, abortSignal);
18842
18909
  } catch (error) {
18843
- if (hasAbortInChain(error) || abortSignal?.aborted) throw error;
18910
+ if (abortSignal?.aborted) throw error;
18911
+ if (!(error instanceof OmModelCallTimeoutError) && hasAbortInChain(error)) throw error;
18844
18912
  if (attempt >= maxRetries || !isTransientLLMError(error)) {
18845
18913
  if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
18846
18914
  throw error;
@@ -18960,19 +19028,21 @@ If a prior value is still applicable and carry-forward is enabled, return that p
18960
19028
  ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values\n\n${priorLines.join("\n\n")}` : ""}`;
18961
19029
  const values = {};
18962
19030
  const failures = [];
18963
- const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry(() => streamOnce(jsonPromptInjection), {
19031
+ const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry((attemptSignal, onActivity) => streamOnce(jsonPromptInjection, attemptSignal, onActivity), {
18964
19032
  label: `om-${opts.source}-structured-extraction`,
18965
19033
  abortSignal: opts.abortSignal,
18966
- maxRetries: 1
19034
+ maxRetries: 1,
19035
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs
18967
19036
  });
18968
- const streamOnce = async (jsonPromptInjection) => {
19037
+ const streamOnce = async (jsonPromptInjection, abortSignal, onActivity) => {
18969
19038
  const object = await (await opts.agent.stream(prompt, {
18970
19039
  structuredOutput: {
18971
19040
  schema,
18972
19041
  ...jsonPromptInjection ? { jsonPromptInjection } : {}
18973
19042
  },
18974
19043
  ...opts.memory ? { memory: opts.memory } : {},
18975
- ...opts.abortSignal ? { abortSignal: opts.abortSignal } : {},
19044
+ abortSignal,
19045
+ onChunk: onActivity,
18976
19046
  ...opts.requestContext ? { requestContext: opts.requestContext } : {},
18977
19047
  ...opts.observabilityContext
18978
19048
  })).object;
@@ -21740,7 +21810,7 @@ var ObserverRunner = class {
21740
21810
  timeZone: options?.timeZone
21741
21811
  })];
21742
21812
  const doGenerate = async () => {
21743
- return withRetry(() => withOmTracingSpan({
21813
+ return withRetry((attemptSignal, onActivity) => withOmTracingSpan({
21744
21814
  phase: "observer",
21745
21815
  model: resolvedModel.model,
21746
21816
  inputTokens,
@@ -21763,7 +21833,8 @@ var ObserverRunner = class {
21763
21833
  modelSettings: { ...this.observationConfig.modelSettings },
21764
21834
  providerOptions: this.observationConfig.providerOptions,
21765
21835
  ...attemptMemory ? { memory: attemptMemory } : {},
21766
- ...abortSignal ? { abortSignal } : {},
21836
+ abortSignal: attemptSignal,
21837
+ onChunk: onActivity,
21767
21838
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
21768
21839
  ...childObservabilityContext
21769
21840
  })).getFullOutput(), "OM observer");
@@ -21785,6 +21856,7 @@ var ObserverRunner = class {
21785
21856
  }), {
21786
21857
  label: "observer",
21787
21858
  abortSignal,
21859
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
21788
21860
  maxRetries: this.observationConfig.maxRetries
21789
21861
  });
21790
21862
  };
@@ -21953,7 +22025,7 @@ var ObserverRunner = class {
21953
22025
  timeZone
21954
22026
  })];
21955
22027
  const doGenerate = async () => {
21956
- return withRetry(() => withOmTracingSpan({
22028
+ return withRetry((attemptSignal, onActivity) => withOmTracingSpan({
21957
22029
  phase: "observer-multi-thread",
21958
22030
  model: resolvedModel.model,
21959
22031
  inputTokens,
@@ -21974,7 +22046,8 @@ var ObserverRunner = class {
21974
22046
  maxSteps: 1,
21975
22047
  modelSettings: { ...this.observationConfig.modelSettings },
21976
22048
  providerOptions: this.observationConfig.providerOptions,
21977
- ...abortSignal ? { abortSignal } : {},
22049
+ abortSignal: attemptSignal,
22050
+ onChunk: onActivity,
21978
22051
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
21979
22052
  ...childObservabilityContext
21980
22053
  })).getFullOutput(), "OM multi-thread observer");
@@ -21998,6 +22071,7 @@ var ObserverRunner = class {
21998
22071
  }), {
21999
22072
  label: "observer-multi-thread",
22000
22073
  abortSignal,
22074
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
22001
22075
  maxRetries: this.observationConfig.maxRetries
22002
22076
  });
22003
22077
  };
@@ -28577,6 +28651,7 @@ var ObservationStep = class {
28577
28651
  let observerExchange;
28578
28652
  const step0Messages = this.stepNumber === 0 ? getObservableMessages(messageList) : [];
28579
28653
  if (this.stepNumber === 0) {
28654
+ this.turn.inputMessageIds = messageList.get.input.db().map((msg) => msg.id);
28580
28655
  const activation = await om.activate({
28581
28656
  threadId,
28582
28657
  resourceId,
@@ -28678,19 +28753,36 @@ var ObservationStep = class {
28678
28753
  observabilityContext: this.turn.observabilityContext
28679
28754
  }).catch((err) => {
28680
28755
  omDebug(`[OM:buffer] fire-and-forget buffer failed: ${err?.message}`);
28681
- }));
28756
+ }), {
28757
+ threadId,
28758
+ resourceId
28759
+ });
28682
28760
  buffered = true;
28683
28761
  }
28684
28762
  }
28685
28763
  const bandDefersObservation = statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate;
28686
28764
  const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls && !bandDefersObservation;
28687
- /** In-flight message ids the step-0 cleanup must never remove from live context. */
28688
- let step0PreserveIds;
28765
+ /**
28766
+ * In-flight message ids cleanup must never remove from live context: the turn's own
28767
+ * request, plus whatever the model has not read yet (the pending messages at step 0, the
28768
+ * previous step's output — e.g. a tool result — at step > 0).
28769
+ */
28770
+ let inFlightIds;
28689
28771
  if (this.stepNumber > 0 || willObserveNow) {
28690
28772
  if (this.stepNumber > 0) {
28691
28773
  const newInput = messageList.clear.input.db();
28692
28774
  const newOutput = messageList.clear.response.db();
28693
28775
  const messagesToSave = [...newInput, ...newOutput];
28776
+ const drainedIds = new Set(messagesToSave.map((msg) => msg.id));
28777
+ const updatedEarlier = messageList.get.all.db().filter((msg) => !drainedIds.has(msg.id) && this.turn.drainedPartKeys.has(msg.id));
28778
+ const drainedKeys = /* @__PURE__ */ new Map();
28779
+ const unreadIds = [...messagesToSave, ...updatedEarlier].filter((msg) => {
28780
+ const keys = getPartKeys(msg);
28781
+ const seen = this.turn.drainedPartKeys.get(msg.id);
28782
+ drainedKeys.set(msg.id, /* @__PURE__ */ new Set([...seen ?? [], ...keys]));
28783
+ return !seen || keys.some((key) => !seen.has(key));
28784
+ }).map((msg) => msg.id);
28785
+ inFlightIds = [.../* @__PURE__ */ new Set([...this.turn.inputMessageIds, ...unreadIds])];
28694
28786
  if (messagesToSave.length > 0) {
28695
28787
  for (const msg of messagesToSave) messageList.add(msg, "memory");
28696
28788
  try {
@@ -28701,10 +28793,11 @@ var ObservationStep = class {
28701
28793
  throw error;
28702
28794
  }
28703
28795
  }
28796
+ for (const [id, keys] of drainedKeys) this.turn.drainedPartKeys.set(id, keys);
28704
28797
  } else {
28705
28798
  const pending = [...messageList.get.input.db(), ...messageList.get.response.db()];
28706
28799
  if (pending.length > 0) await om.persistMessages(pending, threadId, resourceId);
28707
- step0PreserveIds = pending.map((msg) => msg.id);
28800
+ inFlightIds = pending.map((msg) => msg.id);
28708
28801
  }
28709
28802
  if (this.stepNumber === 0 && willObserveNow && this.turn.responseMessageId) {
28710
28803
  const seed = {
@@ -28738,7 +28831,7 @@ var ObservationStep = class {
28738
28831
  messages: messageList,
28739
28832
  observedMessageIds: observedIds,
28740
28833
  retentionFloor: minRemaining,
28741
- preserveMessageIds: step0PreserveIds
28834
+ preserveMessageIds: [...inFlightIds ?? [], ...this.changedSinceDrain(messageList)]
28742
28835
  });
28743
28836
  if (statusSnapshot.asyncObservationEnabled) await om.resetBufferingState({
28744
28837
  threadId,
@@ -28766,7 +28859,7 @@ var ObservationStep = class {
28766
28859
  });
28767
28860
  if (!didThresholdCleanup) {
28768
28861
  const fallbackCursor = this.turn.record.threadId ? getThreadOMMetadata((await om.getStorage().getThreadById({ threadId: this.turn.record.threadId }))?.metadata)?.lastObservedMessageCursor : void 0;
28769
- const pendingMessageIds = new Set([...messageList.get.input.db(), ...messageList.get.response.db()].map((msg) => msg.id).filter(Boolean));
28862
+ 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));
28770
28863
  filterObservedMessages({
28771
28864
  messageList,
28772
28865
  record: this.turn.record,
@@ -28795,6 +28888,13 @@ var ObservationStep = class {
28795
28888
  this._prepared = true;
28796
28889
  return this._context;
28797
28890
  }
28891
+ /** Ids of already-drained messages that now hold parts the model has not been sent. */
28892
+ changedSinceDrain(messageList) {
28893
+ return messageList.get.all.db().filter((msg) => {
28894
+ const seen = this.turn.drainedPartKeys.get(msg.id);
28895
+ return seen !== void 0 && getPartKeys(msg).some((key) => !seen.has(key));
28896
+ }).map((msg) => msg.id);
28897
+ }
28798
28898
  /**
28799
28899
  * Run the full threshold observation pipeline:
28800
28900
  * waitForBuffering → re-check → activate buffered chunks → reflect → observe
@@ -28967,6 +29067,49 @@ function restoreDrainedMessages(messageList, drained, source) {
28967
29067
  const ids = drained.map((msg) => msg.id).filter((id) => !stillQueued.has(id));
28968
29068
  for (const msg of messageList.removeByIds(ids)) messageList.add(msg, source, { merge: false });
28969
29069
  }
29070
+ /**
29071
+ * Identity keys of a message's non-marker parts. Tool parts are keyed by call id, state and result,
29072
+ * so a result merged in place into an existing tool-invocation part yields a new key. Bookkeeping
29073
+ * fields that change when a part is re-stamped are not part of the identity.
29074
+ */
29075
+ function getPartKeys(msg) {
29076
+ const seen = /* @__PURE__ */ new Map();
29077
+ return (msg.content.parts ?? []).flatMap((part) => {
29078
+ if (part.type.startsWith("data-om-")) return [];
29079
+ let identity;
29080
+ if (part.type === "tool-invocation") identity = toolPartIdentity(part.toolInvocation);
29081
+ else {
29082
+ const { providerMetadata: _providerMetadata, createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = part;
29083
+ identity = JSON.stringify(rest);
29084
+ }
29085
+ const ordinal = seen.get(identity) ?? 0;
29086
+ seen.set(identity, ordinal + 1);
29087
+ return [`${identity}#${ordinal}`];
29088
+ });
29089
+ }
29090
+ /**
29091
+ * Serializing a tool result on every step is quadratic in turn length (each step rescans every drained
29092
+ * message), so the identity is cached per invocation object and reused while its state and result
29093
+ * reference are unchanged. A result merged in place replaces that reference and is re-serialized.
29094
+ */
29095
+ const toolPartIdentityCache = /* @__PURE__ */ new WeakMap();
29096
+ function toolPartIdentity(invocation) {
29097
+ const result = invocation.state === "result" ? invocation.result : null;
29098
+ const cached = toolPartIdentityCache.get(invocation);
29099
+ if (cached && cached.state === invocation.state && cached.result === result) return cached.identity;
29100
+ const identity = JSON.stringify([
29101
+ "tool-invocation",
29102
+ invocation.toolCallId,
29103
+ invocation.state,
29104
+ result
29105
+ ]);
29106
+ toolPartIdentityCache.set(invocation, {
29107
+ state: invocation.state,
29108
+ result,
29109
+ identity
29110
+ });
29111
+ return identity;
29112
+ }
28970
29113
  //#endregion
28971
29114
  //#region src/processors/observational-memory/observation-turn/turn.ts
28972
29115
  /**
@@ -29000,6 +29143,19 @@ var ObservationTurn = class {
29000
29143
  _ended = false;
29001
29144
  /** Generation count at turn start — used to detect if reflection happened during the turn. */
29002
29145
  _generationCountAtStart = -1;
29146
+ /**
29147
+ * Ids of the messages this turn was started with (the user's request). Mid-turn
29148
+ * observation may record them, but they stay in the live prompt until the turn ends.
29149
+ * @internal Set by step 0.
29150
+ */
29151
+ inputMessageIds = [];
29152
+ /**
29153
+ * Fingerprints of the non-marker parts each message had when a step last drained it. A message
29154
+ * is unread step output only if it gained a part the model has not seen — including a tool result
29155
+ * merged in place into an existing tool-invocation part — not when it only gained OM markers.
29156
+ * @internal
29157
+ */
29158
+ drainedPartKeys = /* @__PURE__ */ new Map();
29003
29159
  /** Memory context provider — set via start(). Used by steps for beforeBuffer persistence. */
29004
29160
  memory;
29005
29161
  /** Optional stream writer for emitting markers. */
@@ -29153,7 +29309,10 @@ var ObservationTurn = class {
29153
29309
  skipMinimumTokenCheck: true
29154
29310
  }).catch((err) => {
29155
29311
  omDebug(`[OM:turn.end] idle buffer failed: ${err?.message}`);
29156
- }));
29312
+ }), {
29313
+ threadId: this.threadId,
29314
+ resourceId: this.resourceId
29315
+ });
29157
29316
  }
29158
29317
  return { record: this._record };
29159
29318
  }
@@ -29597,6 +29756,7 @@ var ReflectorRunner = class {
29597
29756
  * number per active thread/resource key and live for the runner's lifetime.
29598
29757
  */
29599
29758
  syncReflectionSuppression = /* @__PURE__ */ new Map();
29759
+ trackBackgroundWork;
29600
29760
  constructor(opts) {
29601
29761
  this.reflectionConfig = opts.reflectionConfig;
29602
29762
  this.observationConfig = opts.observationConfig;
@@ -29613,6 +29773,7 @@ var ReflectorRunner = class {
29613
29773
  this.memory = opts.memory;
29614
29774
  this.onReflectionCommitted = opts.onReflectionCommitted;
29615
29775
  this.hooks = opts.hooks;
29776
+ this.trackBackgroundWork = opts.trackBackgroundWork;
29616
29777
  }
29617
29778
  __registerMastra(mastra) {
29618
29779
  this.mastra = mastra;
@@ -29709,7 +29870,7 @@ var ReflectorRunner = class {
29709
29870
  const prompt = buildReflectorPrompt(observations, manualPrompt, currentLevel, skipContinuationHints, activeExtractors, priorExtractedValues);
29710
29871
  omDebug(`[OM:callReflector] ${isRetry ? `retry #${attemptNumber - 1}` : "first attempt"}: level=${currentLevel}, originalTokens=${originalTokens}, targetThreshold=${targetThreshold}, promptLen=${prompt.length}, skipContinuationHints=${skipContinuationHints}`);
29711
29872
  let chunkCount = 0;
29712
- const result = await withRetry(() => withOmTracingSpan({
29873
+ const result = await withRetry((attemptSignal, onActivity) => withOmTracingSpan({
29713
29874
  phase: "reflector",
29714
29875
  model: resolvedModel.model,
29715
29876
  inputTokens: originalTokens,
@@ -29733,17 +29894,19 @@ var ReflectorRunner = class {
29733
29894
  modelSettings: { ...this.reflectionConfig.modelSettings },
29734
29895
  providerOptions: this.reflectionConfig.providerOptions,
29735
29896
  ...attemptMemory ? { memory: attemptMemory } : {},
29736
- ...abortSignal ? { abortSignal } : {},
29897
+ abortSignal: attemptSignal,
29737
29898
  ...internalRequestContext ? { requestContext: internalRequestContext } : {},
29738
29899
  ...childObservabilityContext,
29900
+ onChunk(chunk) {
29901
+ onActivity();
29902
+ if (attemptNumber !== 1) return;
29903
+ chunkCount++;
29904
+ if (chunkCount === 1 || chunkCount % 50 === 0) {
29905
+ const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
29906
+ omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
29907
+ }
29908
+ },
29739
29909
  ...attemptNumber === 1 ? {
29740
- onChunk(chunk) {
29741
- chunkCount++;
29742
- if (chunkCount === 1 || chunkCount % 50 === 0) {
29743
- const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
29744
- omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
29745
- }
29746
- },
29747
29910
  onFinish(event) {
29748
29911
  omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
29749
29912
  },
@@ -29763,6 +29926,7 @@ var ReflectorRunner = class {
29763
29926
  }), {
29764
29927
  label: "reflector",
29765
29928
  abortSignal,
29929
+ idleTimeoutMs: RETRY_CONFIG.modelCallIdleTimeoutMs,
29766
29930
  maxRetries: this.reflectionConfig.maxRetries
29767
29931
  });
29768
29932
  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}`);
@@ -29870,10 +30034,11 @@ var ReflectorRunner = class {
29870
30034
  if (this.buffering.isAsyncBufferingInProgress(bufferKey)) return;
29871
30035
  BufferingCoordinator.lastBufferedBoundary.set(bufferKey, observationTokens);
29872
30036
  registerOp(record.id, "bufferingReflection");
29873
- this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
30037
+ const flagSet = this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
29874
30038
  omError("[OM] Failed to set buffering reflection flag", err);
29875
30039
  });
29876
30040
  const asyncOp = (async () => {
30041
+ await flagSet;
29877
30042
  let outcome;
29878
30043
  let reflectionError;
29879
30044
  try {
@@ -29914,12 +30079,13 @@ var ReflectorRunner = class {
29914
30079
  }
29915
30080
  BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
29916
30081
  unregisterOp(record.id, "bufferingReflection");
29917
- this.storage.setBufferingReflectionFlag(record.id, false).catch((err) => {
30082
+ await this.storage.setBufferingReflectionFlag(record.id, false).catch((err) => {
29918
30083
  omError("[OM] Failed to clear buffering reflection flag", err);
29919
30084
  });
29920
30085
  }
29921
30086
  })();
29922
30087
  BufferingCoordinator.asyncBufferingOps.set(bufferKey, asyncOp);
30088
+ this.trackBackgroundWork?.(asyncOp, lockKey);
29923
30089
  }
29924
30090
  /**
29925
30091
  * Perform async buffered reflection — reflects observations and stores to bufferedReflection.
@@ -30947,32 +31113,44 @@ var ObservationalMemory = class ObservationalMemory {
30947
31113
  * Tracked so callers can join them via `settled()` before closing a storage
30948
31114
  * connection they own — otherwise a background cycle's tail statements race the close.
30949
31115
  */
30950
- pendingBackgroundWork = /* @__PURE__ */ new Set();
31116
+ pendingBackgroundWork = /* @__PURE__ */ new Map();
30951
31117
  /**
30952
31118
  * Register fire-and-forget background work so `settled()` can join it.
31119
+ * `scope` is the conversation the work belongs to (thread/resource ids, or a lock key from
31120
+ * `buffering.getLockKey`),
31121
+ * so a scoped `settled()` can wait for one conversation without waiting for every other.
30953
31122
  * Returns the original promise so callers keep their own rejection handling.
30954
31123
  */
30955
- trackBackgroundWork(work) {
31124
+ trackBackgroundWork(work, scope) {
31125
+ const lockKey = typeof scope === "string" ? scope : this.buffering.getLockKey(scope.threadId, scope.resourceId);
30956
31126
  const tracked = work.catch(() => {}).finally(() => {
30957
31127
  this.pendingBackgroundWork.delete(tracked);
30958
31128
  });
30959
- this.pendingBackgroundWork.add(tracked);
31129
+ this.pendingBackgroundWork.set(tracked, lockKey);
30960
31130
  return work;
30961
31131
  }
30962
31132
  /**
30963
- * Resolve once all background observational-memory work started so far has finished.
31133
+ * Resolve once background observational-memory work started so far has finished.
31134
+ *
31135
+ * Pass `threadId`/`resourceId` to wait only for that conversation's work. An engine shared by
31136
+ * many busy threads may never be fully idle, so an unscoped wait can last indefinitely there.
31137
+ * A scope without the id this engine keys work on (`resourceId` for resource scope, `threadId`
31138
+ * for thread scope) can't be matched to its work, so it falls back to waiting for all work.
30964
31139
  *
30965
31140
  * Background cycles enqueue further background work (a buffered observation can
30966
31141
  * trigger a reflection, which runs nested agent streams), so this drains repeatedly
30967
31142
  * until nothing is left rather than joining a single snapshot.
30968
31143
  */
30969
- async settled() {
31144
+ async settled(scope) {
31145
+ const lockKey = (this.scope === "resource" ? scope?.resourceId : scope?.threadId) ? this.buffering.getLockKey(scope.threadId, scope.resourceId) : void 0;
31146
+ const pending = () => [...this.pendingBackgroundWork].filter(([, key]) => lockKey === void 0 || key === lockKey).map(([w]) => w);
30970
31147
  const maxDrainRounds = 100;
30971
31148
  for (let round = 0; round < maxDrainRounds; round++) {
30972
- if (this.pendingBackgroundWork.size === 0) return;
30973
- await Promise.allSettled([...this.pendingBackgroundWork]);
31149
+ const work = pending();
31150
+ if (work.length === 0) return;
31151
+ await Promise.allSettled(work);
30974
31152
  }
30975
- if (this.pendingBackgroundWork.size === 0) return;
31153
+ if (pending().length === 0) return;
30976
31154
  omError(`[OM:settled] background work still pending after ${maxDrainRounds} drain rounds; giving up waiting`);
30977
31155
  }
30978
31156
  /**
@@ -31128,7 +31306,8 @@ var ObservationalMemory = class ObservationalMemory {
31128
31306
  mastra: config.mastra,
31129
31307
  memory: this.memory,
31130
31308
  onReflectionCommitted: config.onReflectionCommitted,
31131
- hooks: this.hooks
31309
+ hooks: this.hooks,
31310
+ trackBackgroundWork: (work, lockKey) => this.trackBackgroundWork(work, lockKey)
31132
31311
  });
31133
31312
  this.validateBufferConfig();
31134
31313
  if (this.scope === "resource" && !hasWarnedResourceScopeDeprecation) {
@@ -31984,11 +32163,12 @@ ${formattedMessages}
31984
32163
  }).finally(() => {
31985
32164
  if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === asyncOp) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
31986
32165
  unregisterOp(record.id, "bufferingObservation");
31987
- runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
32166
+ return runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
31988
32167
  omError("[OM] Failed to clear buffering observation flag", err);
31989
32168
  });
31990
32169
  });
31991
32170
  BufferingCoordinator.asyncBufferingOps.set(bufferKey, asyncOp);
32171
+ return { op: asyncOp };
31992
32172
  }
31993
32173
  /**
31994
32174
  * Internal method that waits for existing buffering operation and then runs new buffering.
@@ -32088,7 +32268,7 @@ ${formattedMessages}
32088
32268
  if (!this.buffering.isAsyncObservationEnabled()) return false;
32089
32269
  const lockKey = this.buffering.getLockKey(opts.threadId, opts.resourceId);
32090
32270
  const shouldTrigger = this.buffering.shouldTriggerAsyncObservation(opts.pendingTokens, lockKey, opts.record, this.storage, opts.threshold);
32091
- if (shouldTrigger) this.trackBackgroundWork(this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext, opts.observabilityContext));
32271
+ 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);
32092
32272
  return shouldTrigger;
32093
32273
  }
32094
32274
  isMessageList(value) {
@@ -34140,9 +34320,9 @@ var Memory = class Memory extends MastraMemory {
34140
34320
  * await store.close();
34141
34321
  * ```
34142
34322
  */
34143
- async settled() {
34323
+ async settled(scope) {
34144
34324
  await this.pendingVectorCleanup;
34145
- await (this._omEngine ? await this._omEngine : this._omEngineInstance)?.settled();
34325
+ await (this._omEngine ? await this._omEngine : this._omEngineInstance)?.settled(scope);
34146
34326
  await this.pendingVectorCleanup;
34147
34327
  }
34148
34328
  /** The shared ObservationalMemory engine. Lazily created on first access. */
@@ -36431,4 +36611,4 @@ Notes:
36431
36611
  //#endregion
36432
36612
  export { renderSubconsciousActivity as $, buildObserverSystemPrompt as A, OBSERVATION_CONTEXT_INSTRUCTIONS as B, WorkingMemoryExtractor as C, TokenCounter as D, summarizeConversation as E, parseObserverOutput as F, Subconscious as G, OBSERVATION_CONTEXT_PROMPT_THREAD as H, injectAnchorIds as I, SubconsciousRemindExtractor as J, KnowledgeSemanticIndexCoordinator as K, parseAnchorId as L, formatMessagesForObserver as M, hasCurrentTaskSection as N, OBSERVER_SYSTEM_PROMPT as O, optimizeObservationsForContext as P, publishSubconsciousActivity as Q, stripEphemeralAnchorIds as R, wrapInObservationGroup as S, SUMMARIZE_THREAD_DEFAULTS as T, OBSERVATION_CONTINUATION_HINT as U, OBSERVATION_CONTEXT_PROMPT as V, getObservationContextPrompt as W, SUBCONSCIOUS_ACTIVITY_STATE_ID as X, ModelByInputTokens as Y, buildSubconsciousActivitySnapshot as Z, deriveObservationGroupProvenance as _, extractWorkingMemoryContent as a, renderObservationGroupsForReflection as b, getObservationsAsOf as c, AUTO_MODEL_BY_PROVIDER as d, Extractor as et, resolveAutoModelId as f, combineObservationGroupRanges as g, WorkingMemoryStateProcessor as h, WorkingMemory as i, extractCurrentTask as j, buildObserverPrompt as k, ObservationalMemoryProcessor as l, WORKING_MEMORY_STATE_PROCESSOR_ID as m, MessageHistory$1 as n, extractWorkingMemoryTags as o, WORKING_MEMORY_STATE_ID as p, StaleKnowledgeSemanticIndexError as q, SemanticRecall as r, removeWorkingMemoryTags as s, Memory as t, ObservationalMemory as u, parseObservationGroups as v, deepMergeWorkingMemory as w, stripObservationGroups as x, reconcileObservationGroupsFromReflection as y, OBSERVATIONAL_MEMORY_DEFAULTS as z };
36433
36613
 
36434
- //# sourceMappingURL=src-BDhP0_OK.js.map
36614
+ //# sourceMappingURL=src--zkaCof1.js.map