@mastra/mongodb 1.23.0-alpha.1 → 1.23.1-alpha.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.
package/dist/index.cjs CHANGED
@@ -5,11 +5,12 @@ let _mastra_core_storage = require("@mastra/core/storage");
5
5
  let _mastra_core_vector = require("@mastra/core/vector");
6
6
  let mongodb = require("mongodb");
7
7
  let _mastra_core_vector_filter = require("@mastra/core/vector/filter");
8
+ let crypto$1 = require("crypto");
8
9
  let _mastra_core_agent = require("@mastra/core/agent");
9
10
  let _mastra_core_evals = require("@mastra/core/evals");
10
11
  let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
11
12
  //#region package.json
12
- var version = "1.23.0-alpha.1";
13
+ var version = "1.23.1-alpha.0";
13
14
  //#endregion
14
15
  //#region src/vector/filter.ts
15
16
  /**
@@ -6495,6 +6496,71 @@ var MongoDBMCPServersStorage = class MongoDBMCPServersStorage extends _mastra_co
6495
6496
  }
6496
6497
  };
6497
6498
  //#endregion
6499
+ //#region ../../packages/_internal-core/dist/memory/index.js
6500
+ /**
6501
+ * A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
6502
+ * (every message it observed is already at or before the cursor) iff
6503
+ * `cursor >= chunk.lastObservedAt - 1ms`.
6504
+ */
6505
+ function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
6506
+ if (!cursor) return false;
6507
+ return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
6508
+ }
6509
+ /** The later of two cursors; `undefined` only when both are absent. */
6510
+ function maxObservationCursor(a, b) {
6511
+ if (!a) return b ? new Date(b) : void 0;
6512
+ if (!b) return new Date(a);
6513
+ return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
6514
+ }
6515
+ /**
6516
+ * Decide the new generation's text when a reflection built from `snapshot` commits against
6517
+ * the stored record.
6518
+ *
6519
+ * - Stored text equals the snapshot: the reflection replaces it.
6520
+ * - Stored text extends the snapshot (activation only appends): the appended tail is kept
6521
+ * after the reflection, and its tokens are added.
6522
+ * - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
6523
+ */
6524
+ function planReflectionGenerationText(input) {
6525
+ const stored = input.storedObservations ?? "";
6526
+ const snapshot = input.snapshotObservations ?? "";
6527
+ if (stored === snapshot) return {
6528
+ mode: "equal",
6529
+ observations: input.reflection,
6530
+ tokenCount: input.tokenCount
6531
+ };
6532
+ if (!stored.startsWith(snapshot)) return null;
6533
+ const tail = stored.slice(snapshot.length).trimStart();
6534
+ if (!tail.trim()) return {
6535
+ mode: "equal",
6536
+ observations: input.reflection,
6537
+ tokenCount: input.tokenCount
6538
+ };
6539
+ return {
6540
+ mode: "append",
6541
+ observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
6542
+ tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
6543
+ };
6544
+ }
6545
+ /**
6546
+ * Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
6547
+ * Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
6548
+ * write that was not applied throws instead of resolving as if it had committed.
6549
+ */
6550
+ function assertActiveObservationsApplied(result, id) {
6551
+ if (result.applied) return;
6552
+ throw new Error(result.reason === "retired" ? `Observational memory record ${id} was superseded by a newer generation; observations were not written` : `Observational memory record ${id} changed since the observations were composed; observations were not written`);
6553
+ }
6554
+ /**
6555
+ * Whether the stored text can still accept a reflection built from `snapshot`
6556
+ * (equal, or only appended to since).
6557
+ */
6558
+ function isAppendOnlySince(storedObservations, snapshotObservations) {
6559
+ const stored = storedObservations ?? "";
6560
+ const snapshot = snapshotObservations ?? "";
6561
+ return stored === snapshot || stored.startsWith(snapshot);
6562
+ }
6563
+ //#endregion
6498
6564
  //#region src/storage/domains/memory/index.ts
6499
6565
  /**
6500
6566
  * Local constant for the observational memory table name.
@@ -6502,6 +6568,98 @@ var MongoDBMCPServersStorage = class MongoDBMCPServersStorage extends _mastra_co
6502
6568
  * versions that don't export TABLE_OBSERVATIONAL_MEMORY.
6503
6569
  */
6504
6570
  const OM_TABLE = "mastra_observational_memory";
6571
+ /** Canonical head order: newest generation, then earliest createdAt, then lowest id. */
6572
+ const OM_HEAD_SORT = {
6573
+ generationCount: -1,
6574
+ createdAt: 1,
6575
+ id: 1
6576
+ };
6577
+ /** Max redirects when a write aimed at a retired record follows the head. */
6578
+ const OM_MAX_HEAD_HOPS = 3;
6579
+ /** Max read-decide-conditional-update rounds before giving up on a contended record. */
6580
+ const OM_MAX_CONDITIONAL_ATTEMPTS = 3;
6581
+ /** Document `_id` of a key's generation-0 record; see `initializeObservationalMemory`. */
6582
+ function getGeneration0DocumentId(lookupKey) {
6583
+ return `om0_${(0, crypto$1.createHash)("sha256").update(lookupKey).digest("hex").slice(0, 32)}`;
6584
+ }
6585
+ /**
6586
+ * The successor document of a fenced (frozen) document: a pure function of the old document's
6587
+ * final state and the rollover inputs, so concurrent roll-forwards insert identical documents.
6588
+ * Buffered chunks, the cursor, buffering markers, flags, and counters carry over; buffered
6589
+ * reflection state does not.
6590
+ */
6591
+ function buildSuccessorDocument(old, pending) {
6592
+ let activeObservations = pending.reflection;
6593
+ let observationTokenCount = pending.tokenCount;
6594
+ if (pending.mode === "append") {
6595
+ const tail = String(old.activeObservations ?? "").slice(pending.snapTextLength).trimStart();
6596
+ activeObservations = pending.reflection ? `${pending.reflection}\n\n${tail}` : tail;
6597
+ observationTokenCount = pending.tokenCount + Math.max(0, Number(old.observationTokenCount || 0) - pending.snapObservationTokenCount);
6598
+ }
6599
+ return {
6600
+ id: pending.newId,
6601
+ lookupKey: old.lookupKey,
6602
+ scope: old.scope,
6603
+ resourceId: old.resourceId,
6604
+ threadId: old.threadId ?? null,
6605
+ activeObservations,
6606
+ activeObservationsPendingUpdate: null,
6607
+ originType: "reflection",
6608
+ config: old.config ?? null,
6609
+ generationCount: Number(old.generationCount || 0) + 1,
6610
+ lastObservedAt: old.lastObservedAt ?? null,
6611
+ lastReflectionAt: pending.createdAt,
6612
+ pendingMessageTokens: old.pendingMessageTokens ?? 0,
6613
+ totalTokensObserved: old.totalTokensObserved ?? 0,
6614
+ observationTokenCount,
6615
+ bufferedObservationChunks: Array.isArray(old.bufferedObservationChunks) ? old.bufferedObservationChunks : [],
6616
+ isObserving: false,
6617
+ isReflecting: false,
6618
+ isBufferingObservation: Boolean(old.isBufferingObservation),
6619
+ isBufferingReflection: false,
6620
+ lastBufferedAtTokens: old.lastBufferedAtTokens ?? 0,
6621
+ lastBufferedAtTime: old.lastBufferedAtTime ?? null,
6622
+ observedTimezone: old.observedTimezone ?? null,
6623
+ metadata: old.metadata ?? null,
6624
+ supersededBy: null,
6625
+ createdAt: pending.createdAt,
6626
+ updatedAt: pending.createdAt
6627
+ };
6628
+ }
6629
+ /** Equality filters for `fields` as stored in `doc` (`$exists: false` for absent fields). */
6630
+ function matchStoredFields(doc, fields) {
6631
+ return Object.fromEntries(fields.map((f) => [f, doc[f] === void 0 ? { $exists: false } : doc[f]]));
6632
+ }
6633
+ function isDuplicateKeyError(error) {
6634
+ return error?.code === 11e3;
6635
+ }
6636
+ function omNotFound(operation, id) {
6637
+ return new _mastra_core_error.MastraError({
6638
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", operation, "NOT_FOUND"),
6639
+ text: `Observational memory record not found: ${id}`,
6640
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6641
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6642
+ details: { id }
6643
+ });
6644
+ }
6645
+ function omConflict(operation, id) {
6646
+ return new _mastra_core_error.MastraError({
6647
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", operation, "CONFLICT"),
6648
+ text: `Observational memory record ${id} kept changing during ${operation}; giving up after ${OM_MAX_CONDITIONAL_ATTEMPTS} attempts`,
6649
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6650
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6651
+ details: { id }
6652
+ });
6653
+ }
6654
+ function omNoLiveHead(id) {
6655
+ return new _mastra_core_error.MastraError({
6656
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "RESOLVE_OBSERVATIONAL_MEMORY_HEAD", "FAILED"),
6657
+ text: `Observational memory record ${id} is superseded but no live head was found`,
6658
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6659
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6660
+ details: { id }
6661
+ });
6662
+ }
6505
6663
  var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_storage.MemoryStorage {
6506
6664
  supportsPartialThreadUpdate = true;
6507
6665
  supportsObservationalMemory = true;
@@ -6549,6 +6707,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6549
6707
  async init() {
6550
6708
  await this.createDefaultIndexes();
6551
6709
  await this.createCustomIndexes();
6710
+ await this.#warnIfOMIdIndexMissing();
6711
+ await this.#maintainSupersededBy();
6552
6712
  }
6553
6713
  /**
6554
6714
  * Delete memory rows older than each table's `maxAge`, batched. Order is
@@ -7526,15 +7686,15 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7526
7686
  config: doc.config || {},
7527
7687
  metadata: doc.metadata || void 0,
7528
7688
  observedMessageIds: doc.observedMessageIds || void 0,
7529
- observedTimezone: doc.observedTimezone || void 0
7689
+ observedTimezone: doc.observedTimezone || void 0,
7690
+ supersededBy: doc.supersededBy ?? null
7530
7691
  };
7531
7692
  }
7532
7693
  async getObservationalMemory(threadId, resourceId) {
7533
7694
  try {
7534
- const lookupKey = this.getOMKey(threadId, resourceId);
7535
- const doc = await (await this.getCollection(OM_TABLE)).findOne({ lookupKey }, { sort: { generationCount: -1 } });
7536
- if (!doc) return null;
7537
- return this.parseOMDocument(doc);
7695
+ const collection = await this.getCollection(OM_TABLE);
7696
+ const doc = await this.#getHeadDoc(collection, this.getOMKey(threadId, resourceId));
7697
+ return doc ? this.parseOMDocument(doc) : null;
7538
7698
  } catch (error) {
7539
7699
  throw new _mastra_core_error.MastraError({
7540
7700
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "GET_OBSERVATIONAL_MEMORY", "FAILED"),
@@ -7597,60 +7757,55 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7597
7757
  }
7598
7758
  async initializeObservationalMemory(input) {
7599
7759
  try {
7760
+ const lookupKey = this.getOMKey(input.threadId, input.resourceId);
7761
+ const collection = await this.getCollection(OM_TABLE);
7762
+ const existing = await this.#getHeadDoc(collection, lookupKey);
7763
+ if (existing) return this.parseOMDocument(existing);
7600
7764
  const id = globalThis.crypto.randomUUID();
7601
7765
  const now = /* @__PURE__ */ new Date();
7602
- const lookupKey = this.getOMKey(input.threadId, input.resourceId);
7603
- const record = {
7604
- id,
7605
- scope: input.scope,
7606
- threadId: input.threadId,
7607
- resourceId: input.resourceId,
7608
- createdAt: now,
7609
- updatedAt: now,
7610
- lastObservedAt: void 0,
7611
- originType: "initial",
7612
- generationCount: 0,
7613
- activeObservations: "",
7614
- totalTokensObserved: 0,
7615
- observationTokenCount: 0,
7616
- pendingMessageTokens: 0,
7617
- isReflecting: false,
7618
- isObserving: false,
7619
- isBufferingObservation: false,
7620
- isBufferingReflection: false,
7621
- lastBufferedAtTokens: 0,
7622
- lastBufferedAtTime: null,
7623
- config: input.config,
7624
- observedTimezone: input.observedTimezone
7625
- };
7626
- await (await this.getCollection(OM_TABLE)).insertOne({
7627
- id,
7628
- lookupKey,
7629
- scope: input.scope,
7630
- resourceId: input.resourceId,
7631
- threadId: input.threadId || null,
7632
- activeObservations: "",
7633
- activeObservationsPendingUpdate: null,
7634
- originType: "initial",
7635
- config: input.config,
7636
- generationCount: 0,
7637
- lastObservedAt: null,
7638
- lastReflectionAt: null,
7639
- pendingMessageTokens: 0,
7640
- totalTokensObserved: 0,
7641
- observationTokenCount: 0,
7642
- isObserving: false,
7643
- isReflecting: false,
7644
- isBufferingObservation: false,
7645
- isBufferingReflection: false,
7646
- lastBufferedAtTokens: 0,
7647
- lastBufferedAtTime: null,
7648
- observedTimezone: input.observedTimezone || null,
7649
- createdAt: now,
7650
- updatedAt: now
7766
+ try {
7767
+ await collection.insertOne({
7768
+ _id: getGeneration0DocumentId(lookupKey),
7769
+ id,
7770
+ lookupKey,
7771
+ scope: input.scope,
7772
+ resourceId: input.resourceId,
7773
+ threadId: input.threadId || null,
7774
+ activeObservations: "",
7775
+ activeObservationsPendingUpdate: null,
7776
+ originType: "initial",
7777
+ config: input.config,
7778
+ generationCount: 0,
7779
+ lastObservedAt: null,
7780
+ lastReflectionAt: null,
7781
+ pendingMessageTokens: 0,
7782
+ totalTokensObserved: 0,
7783
+ observationTokenCount: 0,
7784
+ isObserving: false,
7785
+ isReflecting: false,
7786
+ isBufferingObservation: false,
7787
+ isBufferingReflection: false,
7788
+ lastBufferedAtTokens: 0,
7789
+ lastBufferedAtTime: null,
7790
+ observedTimezone: input.observedTimezone || null,
7791
+ supersededBy: null,
7792
+ createdAt: now,
7793
+ updatedAt: now
7794
+ });
7795
+ } catch (error) {
7796
+ if (!isDuplicateKeyError(error)) throw error;
7797
+ }
7798
+ const head = await this.#getHeadDoc(collection, lookupKey);
7799
+ if (!head) throw new _mastra_core_error.MastraError({
7800
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "INITIALIZE_OBSERVATIONAL_MEMORY", "NOT_FOUND"),
7801
+ text: `Observational memory record not found after initialization: ${id}`,
7802
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
7803
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
7804
+ details: { id }
7651
7805
  });
7652
- return record;
7806
+ return this.parseOMDocument(head);
7653
7807
  } catch (error) {
7808
+ if (error instanceof _mastra_core_error.MastraError) throw error;
7654
7809
  throw new _mastra_core_error.MastraError({
7655
7810
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "INITIALIZE_OBSERVATIONAL_MEMORY", "FAILED"),
7656
7811
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7695,6 +7850,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7695
7850
  lastBufferedAtTime: record.lastBufferedAtTime || null,
7696
7851
  observedTimezone: record.observedTimezone || null,
7697
7852
  metadata: record.metadata || null,
7853
+ supersededBy: record.supersededBy ?? null,
7698
7854
  createdAt: record.createdAt,
7699
7855
  updatedAt: record.updatedAt
7700
7856
  });
@@ -7712,28 +7868,40 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7712
7868
  }
7713
7869
  }
7714
7870
  async updateActiveObservations(input) {
7871
+ assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
7872
+ }
7873
+ async commitActiveObservations(input) {
7715
7874
  try {
7716
- const now = /* @__PURE__ */ new Date();
7717
7875
  const collection = await this.getCollection(OM_TABLE);
7718
7876
  const safeTokenCount = Number.isFinite(input.tokenCount) && input.tokenCount >= 0 ? input.tokenCount : 0;
7719
- const updateDoc = {
7720
- activeObservations: input.observations,
7721
- lastObservedAt: input.lastObservedAt,
7722
- pendingMessageTokens: 0,
7723
- observationTokenCount: safeTokenCount,
7724
- observedMessageIds: input.observedMessageIds ?? null,
7725
- updatedAt: now
7726
- };
7727
- if ((await collection.updateOne({ id: input.id }, {
7728
- $set: updateDoc,
7729
- $inc: { totalTokensObserved: safeTokenCount }
7730
- })).matchedCount === 0) throw new _mastra_core_error.MastraError({
7731
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"),
7732
- text: `Observational memory record not found: ${input.id}`,
7733
- domain: _mastra_core_error.ErrorDomain.STORAGE,
7734
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
7735
- details: { id: input.id }
7736
- });
7877
+ for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
7878
+ const doc = await collection.findOne({ id: input.id });
7879
+ if (!doc) throw omNotFound("UPDATE_ACTIVE_OBSERVATIONS", input.id);
7880
+ if (doc.supersededBy) return {
7881
+ applied: false,
7882
+ reason: "retired"
7883
+ };
7884
+ if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== (doc.activeObservations || "")) return {
7885
+ applied: false,
7886
+ reason: "conflict"
7887
+ };
7888
+ if ((await collection.updateOne({
7889
+ id: input.id,
7890
+ supersededBy: null,
7891
+ ...matchStoredFields(doc, ["activeObservations", "lastObservedAt"])
7892
+ }, {
7893
+ $set: {
7894
+ activeObservations: input.observations,
7895
+ lastObservedAt: maxObservationCursor(doc.lastObservedAt, input.lastObservedAt),
7896
+ pendingMessageTokens: 0,
7897
+ observationTokenCount: safeTokenCount,
7898
+ observedMessageIds: input.observedMessageIds ?? null,
7899
+ updatedAt: /* @__PURE__ */ new Date()
7900
+ },
7901
+ $inc: { totalTokensObserved: safeTokenCount }
7902
+ })).matchedCount === 1) return { applied: true };
7903
+ }
7904
+ throw omConflict("UPDATE_ACTIVE_OBSERVATIONS", input.id);
7737
7905
  } catch (error) {
7738
7906
  if (error instanceof _mastra_core_error.MastraError) throw error;
7739
7907
  throw new _mastra_core_error.MastraError({
@@ -7746,62 +7914,31 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7746
7914
  }
7747
7915
  async createReflectionGeneration(input) {
7748
7916
  try {
7749
- const id = globalThis.crypto.randomUUID();
7750
- const now = /* @__PURE__ */ new Date();
7751
- const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
7752
- const record = {
7753
- id,
7754
- scope: input.currentRecord.scope,
7755
- threadId: input.currentRecord.threadId,
7756
- resourceId: input.currentRecord.resourceId,
7757
- createdAt: now,
7758
- updatedAt: now,
7759
- lastObservedAt: input.currentRecord.lastObservedAt,
7760
- originType: "reflection",
7761
- generationCount: input.currentRecord.generationCount + 1,
7762
- activeObservations: input.reflection,
7763
- totalTokensObserved: input.currentRecord.totalTokensObserved,
7764
- observationTokenCount: input.tokenCount,
7765
- pendingMessageTokens: 0,
7766
- isReflecting: false,
7767
- isObserving: false,
7768
- isBufferingObservation: false,
7769
- isBufferingReflection: false,
7770
- lastBufferedAtTokens: 0,
7771
- lastBufferedAtTime: null,
7772
- config: input.currentRecord.config,
7773
- metadata: input.currentRecord.metadata,
7774
- observedTimezone: input.currentRecord.observedTimezone
7775
- };
7776
- await (await this.getCollection(OM_TABLE)).insertOne({
7777
- id,
7778
- lookupKey,
7779
- scope: record.scope,
7780
- resourceId: record.resourceId,
7781
- threadId: record.threadId || null,
7782
- activeObservations: input.reflection,
7783
- activeObservationsPendingUpdate: null,
7784
- originType: "reflection",
7785
- config: record.config,
7786
- generationCount: input.currentRecord.generationCount + 1,
7787
- lastObservedAt: record.lastObservedAt || null,
7788
- lastReflectionAt: now,
7789
- pendingMessageTokens: record.pendingMessageTokens,
7790
- totalTokensObserved: record.totalTokensObserved,
7791
- observationTokenCount: record.observationTokenCount,
7792
- isObserving: false,
7793
- isReflecting: false,
7794
- isBufferingObservation: false,
7795
- isBufferingReflection: false,
7796
- lastBufferedAtTokens: 0,
7797
- lastBufferedAtTime: null,
7798
- observedTimezone: record.observedTimezone || null,
7799
- createdAt: now,
7800
- updatedAt: now,
7801
- metadata: record.metadata || null
7917
+ const { currentRecord } = input;
7918
+ return await this.#rollOver(currentRecord, (doc) => {
7919
+ const stored = this.parseOMDocument(doc);
7920
+ const plan = planReflectionGenerationText({
7921
+ storedObservations: stored.activeObservations,
7922
+ storedObservationTokenCount: stored.observationTokenCount,
7923
+ snapshotObservations: currentRecord.activeObservations,
7924
+ snapshotObservationTokenCount: currentRecord.observationTokenCount,
7925
+ reflection: input.reflection,
7926
+ tokenCount: input.tokenCount
7927
+ });
7928
+ if (!plan) return null;
7929
+ return {
7930
+ newId: input.newRecordId ?? globalThis.crypto.randomUUID(),
7931
+ mode: plan.mode,
7932
+ reflection: input.reflection,
7933
+ tokenCount: input.tokenCount,
7934
+ snapTextLength: (currentRecord.activeObservations ?? "").length,
7935
+ snapObservationTokenCount: currentRecord.observationTokenCount ?? 0,
7936
+ clearBufferedReflection: false,
7937
+ createdAt: /* @__PURE__ */ new Date()
7938
+ };
7802
7939
  });
7803
- return record;
7804
7940
  } catch (error) {
7941
+ if (error instanceof _mastra_core_error.MastraError) throw error;
7805
7942
  throw new _mastra_core_error.MastraError({
7806
7943
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "CREATE_REFLECTION_GENERATION", "FAILED"),
7807
7944
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7868,23 +8005,12 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7868
8005
  }
7869
8006
  async setBufferingObservationFlag(id, isBuffering, lastBufferedAtTokens) {
7870
8007
  try {
7871
- const collection = await this.getCollection(OM_TABLE);
7872
8008
  const updateDoc = {
7873
8009
  isBufferingObservation: isBuffering,
7874
8010
  updatedAt: /* @__PURE__ */ new Date()
7875
8011
  };
7876
8012
  if (lastBufferedAtTokens !== void 0) updateDoc.lastBufferedAtTokens = lastBufferedAtTokens;
7877
- if ((await collection.updateOne({ id }, { $set: updateDoc })).matchedCount === 0) throw new _mastra_core_error.MastraError({
7878
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SET_BUFFERING_OBSERVATION_FLAG", "NOT_FOUND"),
7879
- text: `Observational memory record not found: ${id}`,
7880
- domain: _mastra_core_error.ErrorDomain.STORAGE,
7881
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
7882
- details: {
7883
- id,
7884
- isBuffering,
7885
- lastBufferedAtTokens: lastBufferedAtTokens ?? null
7886
- }
7887
- });
8013
+ await this.#updateLiveDoc(id, "SET_BUFFERING_OBSERVATION_FLAG", { $set: updateDoc });
7888
8014
  } catch (error) {
7889
8015
  if (error instanceof _mastra_core_error.MastraError) throw error;
7890
8016
  throw new _mastra_core_error.MastraError({
@@ -7955,19 +8081,10 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7955
8081
  }
7956
8082
  });
7957
8083
  try {
7958
- if ((await (await this.getCollection(OM_TABLE)).updateOne({ id }, { $set: {
8084
+ await this.#updateLiveDoc(id, "SET_PENDING_MESSAGE_TOKENS", { $set: {
7959
8085
  pendingMessageTokens: tokenCount,
7960
8086
  updatedAt: /* @__PURE__ */ new Date()
7961
- } })).matchedCount === 0) throw new _mastra_core_error.MastraError({
7962
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SET_PENDING_MESSAGE_TOKENS", "NOT_FOUND"),
7963
- text: `Observational memory record not found: ${id}`,
7964
- domain: _mastra_core_error.ErrorDomain.STORAGE,
7965
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
7966
- details: {
7967
- id,
7968
- tokenCount
7969
- }
7970
- });
8087
+ } });
7971
8088
  } catch (error) {
7972
8089
  if (error instanceof _mastra_core_error.MastraError) throw error;
7973
8090
  throw new _mastra_core_error.MastraError({
@@ -8009,6 +8126,9 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8009
8126
  }
8010
8127
  }
8011
8128
  async updateBufferedObservations(input) {
8129
+ await this.appendBufferedObservations(input);
8130
+ }
8131
+ async appendBufferedObservations(input) {
8012
8132
  try {
8013
8133
  const collection = await this.getCollection(OM_TABLE);
8014
8134
  const newChunk = {
@@ -8026,18 +8146,65 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8026
8146
  extractedValues: input.chunk.extractedValues,
8027
8147
  extractionFailures: input.chunk.extractionFailures
8028
8148
  };
8029
- const setStage = {
8030
- updatedAt: /* @__PURE__ */ new Date(),
8031
- bufferedObservationChunks: { $concatArrays: [{ $ifNull: ["$bufferedObservationChunks", []] }, [newChunk]] }
8032
- };
8033
- if (input.lastBufferedAtTime) setStage.lastBufferedAtTime = input.lastBufferedAtTime;
8034
- if ((await collection.updateOne({ id: input.id }, [{ $set: setStage }])).matchedCount === 0) throw new _mastra_core_error.MastraError({
8035
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"),
8036
- text: `Observational memory record not found: ${input.id}`,
8037
- domain: _mastra_core_error.ErrorDomain.STORAGE,
8038
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8039
- details: { id: input.id }
8040
- });
8149
+ const notCovered = { $or: [{ lastObservedAt: null }, { lastObservedAt: { $lt: /* @__PURE__ */ new Date(new Date(input.chunk.lastObservedAt).getTime() - 1) } }] };
8150
+ const lastBufferedAtTime = input.lastBufferedAtTime ? new Date(input.lastBufferedAtTime) : void 0;
8151
+ let targetId = input.id;
8152
+ let hops = 0;
8153
+ let retries = 0;
8154
+ for (;;) {
8155
+ if ((await collection.updateOne({
8156
+ id: targetId,
8157
+ supersededBy: null,
8158
+ "bufferedObservationChunks.cycleId": { $ne: input.chunk.cycleId },
8159
+ ...notCovered,
8160
+ $and: [{ $or: [
8161
+ { bufferedObservationChunks: { $exists: false } },
8162
+ { bufferedObservationChunks: null },
8163
+ { bufferedObservationChunks: { $type: "array" } }
8164
+ ] }]
8165
+ }, [{ $set: {
8166
+ bufferedObservationChunks: { $concatArrays: [{ $ifNull: ["$bufferedObservationChunks", []] }, { $literal: [newChunk] }] },
8167
+ ...lastBufferedAtTime ? { lastBufferedAtTime: { $max: ["$lastBufferedAtTime", { $literal: lastBufferedAtTime }] } } : {},
8168
+ updatedAt: { $literal: /* @__PURE__ */ new Date() }
8169
+ } }])).matchedCount === 1) return {
8170
+ persisted: true,
8171
+ recordId: targetId
8172
+ };
8173
+ const doc = await collection.findOne({ id: targetId });
8174
+ if (!doc) throw omNotFound("UPDATE_BUFFERED_OBSERVATIONS", targetId);
8175
+ if (doc.supersededBy) {
8176
+ const head = hops < OM_MAX_HEAD_HOPS ? await this.#getHeadDoc(collection, doc.lookupKey) : null;
8177
+ if (!head || head.supersededBy || head.id === targetId) throw omNoLiveHead(input.id);
8178
+ targetId = head.id;
8179
+ hops++;
8180
+ continue;
8181
+ }
8182
+ const stored = doc.bufferedObservationChunks;
8183
+ if (Array.isArray(stored) && stored.some((c) => c?.cycleId === input.chunk.cycleId)) return {
8184
+ persisted: false,
8185
+ recordId: targetId
8186
+ };
8187
+ if (isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, doc.lastObservedAt)) return {
8188
+ persisted: false,
8189
+ recordId: targetId
8190
+ };
8191
+ if (stored !== void 0 && stored !== null && !Array.isArray(stored)) {
8192
+ if ((await collection.updateOne({
8193
+ id: targetId,
8194
+ supersededBy: null,
8195
+ bufferedObservationChunks: stored,
8196
+ ...notCovered
8197
+ }, { $set: {
8198
+ bufferedObservationChunks: [newChunk],
8199
+ ...lastBufferedAtTime ? { lastBufferedAtTime: maxObservationCursor(doc.lastBufferedAtTime, lastBufferedAtTime) } : {},
8200
+ updatedAt: /* @__PURE__ */ new Date()
8201
+ } })).matchedCount === 1) return {
8202
+ persisted: true,
8203
+ recordId: targetId
8204
+ };
8205
+ }
8206
+ if (++retries >= OM_MAX_CONDITIONAL_ATTEMPTS) throw omConflict("UPDATE_BUFFERED_OBSERVATIONS", targetId);
8207
+ }
8041
8208
  } catch (error) {
8042
8209
  if (error instanceof _mastra_core_error.MastraError) throw error;
8043
8210
  throw new _mastra_core_error.MastraError({
@@ -8051,16 +8218,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8051
8218
  async swapBufferedToActive(input) {
8052
8219
  try {
8053
8220
  const collection = await this.getCollection(OM_TABLE);
8054
- const doc = await collection.findOne({ id: input.id });
8055
- if (!doc) throw new _mastra_core_error.MastraError({
8056
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SWAP_BUFFERED_TO_ACTIVE", "NOT_FOUND"),
8057
- text: `Observational memory record not found: ${input.id}`,
8058
- domain: _mastra_core_error.ErrorDomain.STORAGE,
8059
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8060
- details: { id: input.id }
8061
- });
8062
- const chunks = Array.isArray(doc.bufferedObservationChunks) ? doc.bufferedObservationChunks : [];
8063
- if (chunks.length === 0) return {
8221
+ const emptyResult = {
8064
8222
  chunksActivated: 0,
8065
8223
  messageTokensActivated: 0,
8066
8224
  observationTokensActivated: 0,
@@ -8068,95 +8226,107 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8068
8226
  activatedCycleIds: [],
8069
8227
  activatedMessageIds: []
8070
8228
  };
8071
- const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
8072
- const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
8073
- let cumulativeMessageTokens = 0;
8074
- let bestOverBoundary = 0;
8075
- let bestOverTokens = 0;
8076
- let bestUnderBoundary = 0;
8077
- let bestUnderTokens = 0;
8078
- for (let i = 0; i < chunks.length; i++) {
8079
- cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
8080
- const boundary = i + 1;
8081
- if (cumulativeMessageTokens >= targetMessageTokens) {
8082
- if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
8083
- bestOverBoundary = boundary;
8084
- bestOverTokens = cumulativeMessageTokens;
8229
+ for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
8230
+ const doc = await collection.findOne({ id: input.id });
8231
+ if (!doc) throw omNotFound("SWAP_BUFFERED_TO_ACTIVE", input.id);
8232
+ if (doc.supersededBy) return {
8233
+ ...emptyResult,
8234
+ retired: true
8235
+ };
8236
+ const persistedChunks = Array.isArray(doc.bufferedObservationChunks) ? doc.bufferedObservationChunks : [];
8237
+ const refreshedWeights = new Map((Array.isArray(input.bufferedChunks) ? input.bufferedChunks : []).map((c) => [c.id, c.messageTokens]));
8238
+ const chunks = persistedChunks.map((c) => refreshedWeights.has(c.id) ? {
8239
+ ...c,
8240
+ messageTokens: refreshedWeights.get(c.id)
8241
+ } : c);
8242
+ if (chunks.length === 0) return emptyResult;
8243
+ const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
8244
+ const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
8245
+ let cumulativeMessageTokens = 0;
8246
+ let bestOverBoundary = 0;
8247
+ let bestOverTokens = 0;
8248
+ let bestUnderBoundary = 0;
8249
+ let bestUnderTokens = 0;
8250
+ for (let i = 0; i < chunks.length; i++) {
8251
+ cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
8252
+ const boundary = i + 1;
8253
+ if (cumulativeMessageTokens >= targetMessageTokens) {
8254
+ if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
8255
+ bestOverBoundary = boundary;
8256
+ bestOverTokens = cumulativeMessageTokens;
8257
+ }
8258
+ } else if (cumulativeMessageTokens > bestUnderTokens) {
8259
+ bestUnderBoundary = boundary;
8260
+ bestUnderTokens = cumulativeMessageTokens;
8085
8261
  }
8086
- } else if (cumulativeMessageTokens > bestUnderTokens) {
8087
- bestUnderBoundary = boundary;
8088
- bestUnderTokens = cumulativeMessageTokens;
8089
8262
  }
8263
+ const maxOvershoot = retentionFloor * .95;
8264
+ const overshoot = bestOverTokens - targetMessageTokens;
8265
+ const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
8266
+ const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
8267
+ const minRemaining = Math.min(1e3, retentionFloor);
8268
+ let chunksToActivate;
8269
+ if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
8270
+ else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
8271
+ else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
8272
+ else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
8273
+ else chunksToActivate = 1;
8274
+ const activatedChunks = chunks.slice(0, chunksToActivate);
8275
+ const remainingChunks = persistedChunks.slice(chunksToActivate);
8276
+ const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
8277
+ const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
8278
+ const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
8279
+ const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
8280
+ const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
8281
+ const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
8282
+ const latestChunk = activatedChunks[activatedChunks.length - 1];
8283
+ const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
8284
+ const existingActive = doc.activeObservations || "";
8285
+ const existingTokenCount = Number(doc.observationTokenCount || 0);
8286
+ const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
8287
+ const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent;
8288
+ const newTokenCount = existingTokenCount + activatedTokens;
8289
+ const existingPending = Number(doc.pendingMessageTokens || 0);
8290
+ const newPending = Math.max(0, existingPending - activatedMessageTokens);
8291
+ if ((await collection.updateOne({
8292
+ id: input.id,
8293
+ supersededBy: null,
8294
+ ...matchStoredFields(doc, [
8295
+ "bufferedObservationChunks",
8296
+ "activeObservations",
8297
+ "observationTokenCount",
8298
+ "pendingMessageTokens",
8299
+ "lastObservedAt"
8300
+ ])
8301
+ }, { $set: {
8302
+ activeObservations: newActive,
8303
+ observationTokenCount: newTokenCount,
8304
+ pendingMessageTokens: newPending,
8305
+ bufferedObservationChunks: remainingChunks,
8306
+ lastObservedAt: maxObservationCursor(doc.lastObservedAt, lastObservedAt),
8307
+ updatedAt: /* @__PURE__ */ new Date()
8308
+ } })).matchedCount === 0) continue;
8309
+ const latestChunkHints = activatedChunks[activatedChunks.length - 1];
8310
+ return {
8311
+ chunksActivated: activatedChunks.length,
8312
+ messageTokensActivated: activatedMessageTokens,
8313
+ observationTokensActivated: activatedTokens,
8314
+ messagesActivated: activatedMessageCount,
8315
+ activatedCycleIds,
8316
+ activatedMessageIds,
8317
+ observations: activatedContent,
8318
+ perChunk: activatedChunks.map((c) => ({
8319
+ cycleId: c.cycleId ?? "",
8320
+ messageTokens: c.messageTokens ?? 0,
8321
+ observationTokens: c.tokenCount,
8322
+ messageCount: c.messageIds.length,
8323
+ observations: c.observations
8324
+ })),
8325
+ suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
8326
+ currentTask: latestChunkHints?.currentTask ?? void 0
8327
+ };
8090
8328
  }
8091
- const maxOvershoot = retentionFloor * .95;
8092
- const overshoot = bestOverTokens - targetMessageTokens;
8093
- const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
8094
- const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
8095
- const minRemaining = Math.min(1e3, retentionFloor);
8096
- let chunksToActivate;
8097
- if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
8098
- else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
8099
- else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
8100
- else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
8101
- else chunksToActivate = 1;
8102
- const activatedChunks = chunks.slice(0, chunksToActivate);
8103
- const remainingChunks = chunks.slice(chunksToActivate);
8104
- const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
8105
- const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
8106
- const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
8107
- const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
8108
- const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
8109
- const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
8110
- const latestChunk = activatedChunks[activatedChunks.length - 1];
8111
- const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
8112
- const existingActive = doc.activeObservations || "";
8113
- const existingTokenCount = Number(doc.observationTokenCount || 0);
8114
- const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
8115
- const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent;
8116
- const newTokenCount = existingTokenCount + activatedTokens;
8117
- const existingPending = Number(doc.pendingMessageTokens || 0);
8118
- const newPending = Math.max(0, existingPending - activatedMessageTokens);
8119
- if ((await collection.updateOne({
8120
- id: input.id,
8121
- bufferedObservationChunks: {
8122
- $exists: true,
8123
- $ne: null,
8124
- $not: { $size: 0 }
8125
- }
8126
- }, { $set: {
8127
- activeObservations: newActive,
8128
- observationTokenCount: newTokenCount,
8129
- pendingMessageTokens: newPending,
8130
- bufferedObservationChunks: remainingChunks,
8131
- lastObservedAt,
8132
- updatedAt: /* @__PURE__ */ new Date()
8133
- } })).modifiedCount === 0) return {
8134
- chunksActivated: 0,
8135
- messageTokensActivated: 0,
8136
- observationTokensActivated: 0,
8137
- messagesActivated: 0,
8138
- activatedCycleIds: [],
8139
- activatedMessageIds: []
8140
- };
8141
- const latestChunkHints = activatedChunks[activatedChunks.length - 1];
8142
- return {
8143
- chunksActivated: activatedChunks.length,
8144
- messageTokensActivated: activatedMessageTokens,
8145
- observationTokensActivated: activatedTokens,
8146
- messagesActivated: activatedMessageCount,
8147
- activatedCycleIds,
8148
- activatedMessageIds,
8149
- observations: activatedContent,
8150
- perChunk: activatedChunks.map((c) => ({
8151
- cycleId: c.cycleId ?? "",
8152
- messageTokens: c.messageTokens ?? 0,
8153
- observationTokens: c.tokenCount,
8154
- messageCount: c.messageIds.length,
8155
- observations: c.observations
8156
- })),
8157
- suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
8158
- currentTask: latestChunkHints?.currentTask ?? void 0
8159
- };
8329
+ throw omConflict("SWAP_BUFFERED_TO_ACTIVE", input.id);
8160
8330
  } catch (error) {
8161
8331
  if (error instanceof _mastra_core_error.MastraError) throw error;
8162
8332
  throw new _mastra_core_error.MastraError({
@@ -8209,39 +8379,33 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8209
8379
  }
8210
8380
  async swapBufferedReflectionToActive(input) {
8211
8381
  try {
8212
- const collection = await this.getCollection(OM_TABLE);
8213
- const doc = await collection.findOne({ id: input.currentRecord.id });
8214
- if (!doc) throw new _mastra_core_error.MastraError({
8215
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NOT_FOUND"),
8216
- text: `Observational memory record not found: ${input.currentRecord.id}`,
8217
- domain: _mastra_core_error.ErrorDomain.STORAGE,
8218
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8219
- details: { id: input.currentRecord.id }
8220
- });
8221
- const bufferedReflection = doc.bufferedReflection || "";
8222
- const reflectedLineCount = Number(doc.reflectedObservationLineCount || 0);
8223
- if (!bufferedReflection) throw new _mastra_core_error.MastraError({
8224
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
8225
- text: "No buffered reflection to swap",
8226
- domain: _mastra_core_error.ErrorDomain.STORAGE,
8227
- category: _mastra_core_error.ErrorCategory.USER,
8228
- details: { id: input.currentRecord.id }
8229
- });
8230
- const unreflectedContent = (doc.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
8231
- const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
8232
- const newRecord = await this.createReflectionGeneration({
8233
- currentRecord: input.currentRecord,
8234
- reflection: newObservations,
8235
- tokenCount: input.tokenCount
8382
+ const { currentRecord } = input;
8383
+ return await this.#rollOver(currentRecord, (doc) => {
8384
+ const stored = this.parseOMDocument(doc);
8385
+ const bufferedReflection = stored.bufferedReflection || "";
8386
+ if (!bufferedReflection) throw new _mastra_core_error.MastraError({
8387
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
8388
+ text: "No buffered reflection to swap",
8389
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
8390
+ category: _mastra_core_error.ErrorCategory.USER,
8391
+ details: { id: currentRecord.id }
8392
+ });
8393
+ if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return null;
8394
+ const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
8395
+ const unreflectedContent = (stored.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
8396
+ const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
8397
+ const tokenCount = input.tokenCount + Math.max(0, (stored.observationTokenCount ?? 0) - (currentRecord.observationTokenCount ?? 0));
8398
+ return {
8399
+ newId: input.newRecordId ?? globalThis.crypto.randomUUID(),
8400
+ mode: "equal",
8401
+ reflection: newObservations,
8402
+ tokenCount,
8403
+ snapTextLength: (stored.activeObservations ?? "").length,
8404
+ snapObservationTokenCount: stored.observationTokenCount ?? 0,
8405
+ clearBufferedReflection: true,
8406
+ createdAt: /* @__PURE__ */ new Date()
8407
+ };
8236
8408
  });
8237
- await collection.updateOne({ id: input.currentRecord.id }, { $set: {
8238
- bufferedReflection: null,
8239
- bufferedReflectionTokens: null,
8240
- bufferedReflectionInputTokens: null,
8241
- reflectedObservationLineCount: null,
8242
- updatedAt: /* @__PURE__ */ new Date()
8243
- } });
8244
- return newRecord;
8245
8409
  } catch (error) {
8246
8410
  if (error instanceof _mastra_core_error.MastraError) throw error;
8247
8411
  throw new _mastra_core_error.MastraError({
@@ -8252,6 +8416,168 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8252
8416
  }, error);
8253
8417
  }
8254
8418
  }
8419
+ /**
8420
+ * The head of a lookup key by the canonical order (`generationCount DESC, createdAt ASC, id ASC`).
8421
+ * A head that was fenced for rollover but whose successor insert never ran (crash, or still in
8422
+ * flight in another process) is rolled forward here, so every head read reaches the successor.
8423
+ */
8424
+ async #getHeadDoc(collection, lookupKey) {
8425
+ for (let hop = 0; hop <= OM_MAX_HEAD_HOPS; hop++) {
8426
+ const head = await collection.findOne({ lookupKey }, { sort: OM_HEAD_SORT });
8427
+ if (!head || !head.supersededBy || !head.pendingSuccessor) return head;
8428
+ await this.#completeRollover(collection, head, head.pendingSuccessor);
8429
+ }
8430
+ throw omNoLiveHead(lookupKey);
8431
+ }
8432
+ /**
8433
+ * A single-document update aimed at a live record. A retired (or concurrently fenced) id is
8434
+ * redirected to the head, at most `OM_MAX_HEAD_HOPS` times.
8435
+ */
8436
+ async #updateLiveDoc(id, operation, update) {
8437
+ const collection = await this.getCollection(OM_TABLE);
8438
+ let targetId = id;
8439
+ for (let hop = 0; hop <= OM_MAX_HEAD_HOPS; hop++) {
8440
+ if ((await collection.updateOne({
8441
+ id: targetId,
8442
+ supersededBy: null
8443
+ }, update)).matchedCount === 1) return targetId;
8444
+ const doc = await collection.findOne({ id: targetId }, { projection: {
8445
+ lookupKey: 1,
8446
+ supersededBy: 1
8447
+ } });
8448
+ if (!doc) throw omNotFound(operation, targetId);
8449
+ if (!doc.supersededBy) continue;
8450
+ const head = await this.#getHeadDoc(collection, doc.lookupKey);
8451
+ if (!head || head.supersededBy || head.id === targetId) throw omNoLiveHead(id);
8452
+ targetId = head.id;
8453
+ }
8454
+ throw omNoLiveHead(id);
8455
+ }
8456
+ /**
8457
+ * Roll the live record `currentRecord.id` over to a new generation.
8458
+ *
8459
+ * 1. Read the stored document and decide (`decide` returns the successor inputs, or `null`
8460
+ * when the snapshot is stale).
8461
+ * 2. Fence: one conditional update sets `supersededBy` and the small `pendingSuccessor`
8462
+ * payload, matching only if the fields the decision read are unchanged. From here the old
8463
+ * document is frozen — no lifecycle write matches it — so its pre-image is its final state.
8464
+ * 3. Insert the successor built purely from that pre-image, then clear the old document's
8465
+ * chunks (they moved) and `pendingSuccessor`.
8466
+ *
8467
+ * A crash between 2 and 3 is finished by the next head read (`#getHeadDoc`) or startup.
8468
+ */
8469
+ async #rollOver(currentRecord, decide) {
8470
+ const collection = await this.getCollection(OM_TABLE);
8471
+ for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
8472
+ const doc = await collection.findOne({ id: currentRecord.id });
8473
+ if (!doc) return currentRecord;
8474
+ if (doc.supersededBy) {
8475
+ const head = await this.#getHeadDoc(collection, doc.lookupKey);
8476
+ return head ? this.parseOMDocument(head) : currentRecord;
8477
+ }
8478
+ const pending = decide(doc);
8479
+ if (!pending) return this.parseOMDocument(doc);
8480
+ const frozen = await collection.findOneAndUpdate({
8481
+ id: doc.id,
8482
+ supersededBy: null,
8483
+ ...matchStoredFields(doc, [
8484
+ "activeObservations",
8485
+ "observationTokenCount",
8486
+ "bufferedReflection",
8487
+ "reflectedObservationLineCount"
8488
+ ])
8489
+ }, { $set: {
8490
+ supersededBy: pending.newId,
8491
+ pendingSuccessor: pending
8492
+ } }, { returnDocument: "before" });
8493
+ if (!frozen) continue;
8494
+ return this.parseOMDocument(await this.#completeRollover(collection, frozen, pending));
8495
+ }
8496
+ throw omConflict("CREATE_REFLECTION_GENERATION", currentRecord.id);
8497
+ }
8498
+ /** Insert the successor of a fenced document (idempotent) and clean up the old document. */
8499
+ async #completeRollover(collection, frozen, pending) {
8500
+ try {
8501
+ await collection.insertOne(buildSuccessorDocument(frozen, pending));
8502
+ } catch (error) {
8503
+ if (!isDuplicateKeyError(error)) throw error;
8504
+ }
8505
+ await collection.updateOne({
8506
+ id: frozen.id,
8507
+ supersededBy: pending.newId
8508
+ }, {
8509
+ $set: {
8510
+ bufferedObservationChunks: [],
8511
+ ...pending.clearBufferedReflection ? {
8512
+ bufferedReflection: null,
8513
+ bufferedReflectionTokens: null,
8514
+ bufferedReflectionInputTokens: null,
8515
+ reflectedObservationLineCount: null
8516
+ } : {},
8517
+ updatedAt: /* @__PURE__ */ new Date()
8518
+ },
8519
+ $unset: { pendingSuccessor: "" }
8520
+ });
8521
+ const successor = await collection.findOne({ id: pending.newId });
8522
+ if (!successor) throw omNotFound("CREATE_REFLECTION_GENERATION", pending.newId);
8523
+ return successor;
8524
+ }
8525
+ /**
8526
+ * Startup maintenance for the `supersededBy` marker:
8527
+ * - finish rollovers interrupted after the fence (insert the successor if missing, clean up);
8528
+ * - for keys with more than one live document (left by older adapter versions, which never set
8529
+ * `supersededBy`), retire every live document that sorts after the canonical head. The head's
8530
+ * ordering key is embedded in the filter, so a document created by a concurrent rollover
8531
+ * (a newer generation) never matches.
8532
+ */
8533
+ async #maintainSupersededBy() {
8534
+ const collection = await this.getCollection(OM_TABLE);
8535
+ const interrupted = await collection.find({
8536
+ supersededBy: { $nin: [null] },
8537
+ pendingSuccessor: { $exists: true }
8538
+ }).toArray();
8539
+ for (const doc of interrupted) await this.#completeRollover(collection, doc, doc.pendingSuccessor);
8540
+ const keys = await collection.aggregate([
8541
+ { $match: { supersededBy: null } },
8542
+ { $group: {
8543
+ _id: "$lookupKey",
8544
+ live: { $sum: 1 }
8545
+ } },
8546
+ { $match: { live: { $gt: 1 } } }
8547
+ ]).toArray();
8548
+ for (const { _id: lookupKey } of keys) {
8549
+ const head = await this.#getHeadDoc(collection, lookupKey);
8550
+ if (!head || head.supersededBy) continue;
8551
+ await collection.updateMany({
8552
+ lookupKey,
8553
+ supersededBy: null,
8554
+ $or: [
8555
+ { generationCount: { $lt: head.generationCount } },
8556
+ {
8557
+ generationCount: head.generationCount,
8558
+ createdAt: { $gt: head.createdAt }
8559
+ },
8560
+ {
8561
+ generationCount: head.generationCount,
8562
+ createdAt: head.createdAt,
8563
+ id: { $gt: head.id }
8564
+ }
8565
+ ]
8566
+ }, { $set: { supersededBy: head.id } });
8567
+ }
8568
+ }
8569
+ /**
8570
+ * Cross-process rollover and initialization rely on the unique `id` index (a duplicate insert
8571
+ * of the same successor or generation-0 id must fail). Warn once if it is missing, e.g. under
8572
+ * `skipDefaultIndexes`.
8573
+ */
8574
+ async #warnIfOMIdIndexMissing() {
8575
+ try {
8576
+ if (!(await (await this.getCollection(OM_TABLE)).listIndexes().toArray().catch((error) => error?.code === 26 ? [] : Promise.reject(error))).some((index) => index.unique && Object.keys(index.key ?? {}).length === 1 && index.key.id === 1)) this.logger?.warn?.(`MongoDB collection "${OM_TABLE}" has no unique index on { id: 1 }. Observational memory needs it to stay consistent when several processes write the same thread or resource. Create it with db.${OM_TABLE}.createIndex({ id: 1 }, { unique: true }).`);
8577
+ } catch (error) {
8578
+ this.logger?.warn?.(`Could not list indexes on MongoDB collection "${OM_TABLE}":`, error);
8579
+ }
8580
+ }
8255
8581
  };
8256
8582
  //#endregion
8257
8583
  //#region src/storage/domains/notifications/index.ts