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

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.
@@ -28598,6 +28598,7 @@ var ObservationStep = class {
28598
28598
  let observerExchange;
28599
28599
  const step0Messages = this.stepNumber === 0 ? getObservableMessages(messageList) : [];
28600
28600
  if (this.stepNumber === 0) {
28601
+ this.turn.inputMessageIds = messageList.get.input.db().map((msg) => msg.id);
28601
28602
  const activation = await om.activate({
28602
28603
  threadId,
28603
28604
  resourceId,
@@ -28705,13 +28706,27 @@ var ObservationStep = class {
28705
28706
  }
28706
28707
  const bandDefersObservation = statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate;
28707
28708
  const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls && !bandDefersObservation;
28708
- /** In-flight message ids the step-0 cleanup must never remove from live context. */
28709
- let step0PreserveIds;
28709
+ /**
28710
+ * In-flight message ids cleanup must never remove from live context: the turn's own
28711
+ * request, plus whatever the model has not read yet (the pending messages at step 0, the
28712
+ * previous step's output — e.g. a tool result — at step > 0).
28713
+ */
28714
+ let inFlightIds;
28710
28715
  if (this.stepNumber > 0 || willObserveNow) {
28711
28716
  if (this.stepNumber > 0) {
28712
28717
  const newInput = messageList.clear.input.db();
28713
28718
  const newOutput = messageList.clear.response.db();
28714
28719
  const messagesToSave = [...newInput, ...newOutput];
28720
+ const drainedIds = new Set(messagesToSave.map((msg) => msg.id));
28721
+ const updatedEarlier = messageList.get.all.db().filter((msg) => !drainedIds.has(msg.id) && this.turn.drainedPartKeys.has(msg.id));
28722
+ const drainedKeys = /* @__PURE__ */ new Map();
28723
+ const unreadIds = [...messagesToSave, ...updatedEarlier].filter((msg) => {
28724
+ const keys = getPartKeys(msg);
28725
+ const seen = this.turn.drainedPartKeys.get(msg.id);
28726
+ drainedKeys.set(msg.id, /* @__PURE__ */ new Set([...seen ?? [], ...keys]));
28727
+ return !seen || keys.some((key) => !seen.has(key));
28728
+ }).map((msg) => msg.id);
28729
+ inFlightIds = [.../* @__PURE__ */ new Set([...this.turn.inputMessageIds, ...unreadIds])];
28715
28730
  if (messagesToSave.length > 0) {
28716
28731
  for (const msg of messagesToSave) messageList.add(msg, "memory");
28717
28732
  try {
@@ -28722,10 +28737,11 @@ var ObservationStep = class {
28722
28737
  throw error;
28723
28738
  }
28724
28739
  }
28740
+ for (const [id, keys] of drainedKeys) this.turn.drainedPartKeys.set(id, keys);
28725
28741
  } else {
28726
28742
  const pending = [...messageList.get.input.db(), ...messageList.get.response.db()];
28727
28743
  if (pending.length > 0) await om.persistMessages(pending, threadId, resourceId);
28728
- step0PreserveIds = pending.map((msg) => msg.id);
28744
+ inFlightIds = pending.map((msg) => msg.id);
28729
28745
  }
28730
28746
  if (this.stepNumber === 0 && willObserveNow && this.turn.responseMessageId) {
28731
28747
  const seed = {
@@ -28759,7 +28775,7 @@ var ObservationStep = class {
28759
28775
  messages: messageList,
28760
28776
  observedMessageIds: observedIds,
28761
28777
  retentionFloor: minRemaining,
28762
- preserveMessageIds: step0PreserveIds
28778
+ preserveMessageIds: [...inFlightIds ?? [], ...this.changedSinceDrain(messageList)]
28763
28779
  });
28764
28780
  if (statusSnapshot.asyncObservationEnabled) await om.resetBufferingState({
28765
28781
  threadId,
@@ -28787,7 +28803,7 @@ var ObservationStep = class {
28787
28803
  });
28788
28804
  if (!didThresholdCleanup) {
28789
28805
  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));
28806
+ 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
28807
  filterObservedMessages({
28792
28808
  messageList,
28793
28809
  record: this.turn.record,
@@ -28816,6 +28832,13 @@ var ObservationStep = class {
28816
28832
  this._prepared = true;
28817
28833
  return this._context;
28818
28834
  }
28835
+ /** Ids of already-drained messages that now hold parts the model has not been sent. */
28836
+ changedSinceDrain(messageList) {
28837
+ return messageList.get.all.db().filter((msg) => {
28838
+ const seen = this.turn.drainedPartKeys.get(msg.id);
28839
+ return seen !== void 0 && getPartKeys(msg).some((key) => !seen.has(key));
28840
+ }).map((msg) => msg.id);
28841
+ }
28819
28842
  /**
28820
28843
  * Run the full threshold observation pipeline:
28821
28844
  * waitForBuffering → re-check → activate buffered chunks → reflect → observe
@@ -28988,6 +29011,49 @@ function restoreDrainedMessages(messageList, drained, source) {
28988
29011
  const ids = drained.map((msg) => msg.id).filter((id) => !stillQueued.has(id));
28989
29012
  for (const msg of messageList.removeByIds(ids)) messageList.add(msg, source, { merge: false });
28990
29013
  }
29014
+ /**
29015
+ * Identity keys of a message's non-marker parts. Tool parts are keyed by call id, state and result,
29016
+ * so a result merged in place into an existing tool-invocation part yields a new key. Bookkeeping
29017
+ * fields that change when a part is re-stamped are not part of the identity.
29018
+ */
29019
+ function getPartKeys(msg) {
29020
+ const seen = /* @__PURE__ */ new Map();
29021
+ return (msg.content.parts ?? []).flatMap((part) => {
29022
+ if (part.type.startsWith("data-om-")) return [];
29023
+ let identity;
29024
+ if (part.type === "tool-invocation") identity = toolPartIdentity(part.toolInvocation);
29025
+ else {
29026
+ const { providerMetadata: _providerMetadata, createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = part;
29027
+ identity = JSON.stringify(rest);
29028
+ }
29029
+ const ordinal = seen.get(identity) ?? 0;
29030
+ seen.set(identity, ordinal + 1);
29031
+ return [`${identity}#${ordinal}`];
29032
+ });
29033
+ }
29034
+ /**
29035
+ * Serializing a tool result on every step is quadratic in turn length (each step rescans every drained
29036
+ * message), so the identity is cached per invocation object and reused while its state and result
29037
+ * reference are unchanged. A result merged in place replaces that reference and is re-serialized.
29038
+ */
29039
+ const toolPartIdentityCache = /* @__PURE__ */ new WeakMap();
29040
+ function toolPartIdentity(invocation) {
29041
+ const result = invocation.state === "result" ? invocation.result : null;
29042
+ const cached = toolPartIdentityCache.get(invocation);
29043
+ if (cached && cached.state === invocation.state && cached.result === result) return cached.identity;
29044
+ const identity = JSON.stringify([
29045
+ "tool-invocation",
29046
+ invocation.toolCallId,
29047
+ invocation.state,
29048
+ result
29049
+ ]);
29050
+ toolPartIdentityCache.set(invocation, {
29051
+ state: invocation.state,
29052
+ result,
29053
+ identity
29054
+ });
29055
+ return identity;
29056
+ }
28991
29057
  //#endregion
28992
29058
  //#region src/processors/observational-memory/observation-turn/turn.ts
28993
29059
  /**
@@ -29021,6 +29087,19 @@ var ObservationTurn = class {
29021
29087
  _ended = false;
29022
29088
  /** Generation count at turn start — used to detect if reflection happened during the turn. */
29023
29089
  _generationCountAtStart = -1;
29090
+ /**
29091
+ * Ids of the messages this turn was started with (the user's request). Mid-turn
29092
+ * observation may record them, but they stay in the live prompt until the turn ends.
29093
+ * @internal Set by step 0.
29094
+ */
29095
+ inputMessageIds = [];
29096
+ /**
29097
+ * Fingerprints of the non-marker parts each message had when a step last drained it. A message
29098
+ * is unread step output only if it gained a part the model has not seen — including a tool result
29099
+ * merged in place into an existing tool-invocation part — not when it only gained OM markers.
29100
+ * @internal
29101
+ */
29102
+ drainedPartKeys = /* @__PURE__ */ new Map();
29024
29103
  /** Memory context provider — set via start(). Used by steps for beforeBuffer persistence. */
29025
29104
  memory;
29026
29105
  /** Optional stream writer for emitting markers. */
@@ -29618,6 +29697,7 @@ var ReflectorRunner = class {
29618
29697
  * number per active thread/resource key and live for the runner's lifetime.
29619
29698
  */
29620
29699
  syncReflectionSuppression = /* @__PURE__ */ new Map();
29700
+ trackBackgroundWork;
29621
29701
  constructor(opts) {
29622
29702
  this.reflectionConfig = opts.reflectionConfig;
29623
29703
  this.observationConfig = opts.observationConfig;
@@ -29634,6 +29714,7 @@ var ReflectorRunner = class {
29634
29714
  this.memory = opts.memory;
29635
29715
  this.onReflectionCommitted = opts.onReflectionCommitted;
29636
29716
  this.hooks = opts.hooks;
29717
+ this.trackBackgroundWork = opts.trackBackgroundWork;
29637
29718
  }
29638
29719
  __registerMastra(mastra) {
29639
29720
  this.mastra = mastra;
@@ -29891,10 +29972,11 @@ var ReflectorRunner = class {
29891
29972
  if (this.buffering.isAsyncBufferingInProgress(bufferKey)) return;
29892
29973
  BufferingCoordinator.lastBufferedBoundary.set(bufferKey, observationTokens);
29893
29974
  registerOp(record.id, "bufferingReflection");
29894
- this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
29975
+ const flagSet = this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
29895
29976
  omError("[OM] Failed to set buffering reflection flag", err);
29896
29977
  });
29897
29978
  const asyncOp = (async () => {
29979
+ await flagSet;
29898
29980
  let outcome;
29899
29981
  let reflectionError;
29900
29982
  try {
@@ -29935,12 +30017,13 @@ var ReflectorRunner = class {
29935
30017
  }
29936
30018
  BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
29937
30019
  unregisterOp(record.id, "bufferingReflection");
29938
- this.storage.setBufferingReflectionFlag(record.id, false).catch((err) => {
30020
+ await this.storage.setBufferingReflectionFlag(record.id, false).catch((err) => {
29939
30021
  omError("[OM] Failed to clear buffering reflection flag", err);
29940
30022
  });
29941
30023
  }
29942
30024
  })();
29943
30025
  BufferingCoordinator.asyncBufferingOps.set(bufferKey, asyncOp);
30026
+ this.trackBackgroundWork?.(asyncOp);
29944
30027
  }
29945
30028
  /**
29946
30029
  * Perform async buffered reflection — reflects observations and stores to bufferedReflection.
@@ -31149,7 +31232,8 @@ var ObservationalMemory = class ObservationalMemory {
31149
31232
  mastra: config.mastra,
31150
31233
  memory: this.memory,
31151
31234
  onReflectionCommitted: config.onReflectionCommitted,
31152
- hooks: this.hooks
31235
+ hooks: this.hooks,
31236
+ trackBackgroundWork: (work) => this.trackBackgroundWork(work)
31153
31237
  });
31154
31238
  this.validateBufferConfig();
31155
31239
  if (this.scope === "resource" && !hasWarnedResourceScopeDeprecation) {
@@ -36757,4 +36841,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
36757
36841
  }
36758
36842
  });
36759
36843
 
36760
- //# sourceMappingURL=src-CJiR-m2F.cjs.map
36844
+ //# sourceMappingURL=src-GHUZVCL8.cjs.map