@mastra/convex 1.7.2 → 1.7.3-alpha.1

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 (42) hide show
  1. package/dist/_types/@internal_core/dist/memory/index.d.ts +102 -0
  2. package/dist/_types/@internal_core/package.json +1 -0
  3. package/dist/docs/SKILL.md +1 -1
  4. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  5. package/dist/docs/references/reference-vectors-convex.md +1 -3
  6. package/dist/index.cjs +370 -161
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +371 -162
  9. package/dist/index.js.map +1 -1
  10. package/dist/schema.cjs +1 -0
  11. package/dist/schema.cjs.map +1 -1
  12. package/dist/schema.d.ts +3 -1
  13. package/dist/schema.d.ts.map +1 -1
  14. package/dist/schema.js +1 -0
  15. package/dist/schema.js.map +1 -1
  16. package/dist/server/index.cjs +1 -1
  17. package/dist/server/index.js +1 -1
  18. package/dist/server/observational-memory.d.ts +26 -1
  19. package/dist/server/observational-memory.d.ts.map +1 -1
  20. package/dist/server/run-fencing.d.ts +14 -0
  21. package/dist/server/run-fencing.d.ts.map +1 -0
  22. package/dist/server/storage.d.ts +2 -2
  23. package/dist/server/storage.d.ts.map +1 -1
  24. package/dist/{server-D3SG5LQf.js → server-B8RSiOmu.js} +454 -56
  25. package/dist/server-B8RSiOmu.js.map +1 -0
  26. package/dist/{server-D_scBUwi.cjs → server-Bcgr5CcA.cjs} +454 -56
  27. package/dist/server-Bcgr5CcA.cjs.map +1 -0
  28. package/dist/storage/client.d.ts +3 -3
  29. package/dist/storage/client.d.ts.map +1 -1
  30. package/dist/storage/db/index.d.ts +33 -6
  31. package/dist/storage/db/index.d.ts.map +1 -1
  32. package/dist/storage/domains/memory/index.d.ts +20 -7
  33. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  34. package/dist/storage/domains/workflows/index.d.ts +16 -5
  35. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  36. package/dist/storage/run-fencing.d.ts +28 -0
  37. package/dist/storage/run-fencing.d.ts.map +1 -0
  38. package/dist/storage/types.d.ts +114 -1
  39. package/dist/storage/types.d.ts.map +1 -1
  40. package/package.json +5 -4
  41. package/dist/server-D3SG5LQf.js.map +0 -1
  42. package/dist/server-D_scBUwi.cjs.map +0 -1
@@ -550,6 +550,153 @@ function mergeReflectionWithUnreflected(activeObservations, bufferedReflection,
550
550
  const unreflectedContent = (activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
551
551
  return unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
552
552
  }
553
+ /**
554
+ * A buffered chunk stores `lastObservedAt = max message time + 1ms`; it is wholly covered by
555
+ * the cursor iff `cursor >= chunk.lastObservedAt - 1ms`.
556
+ */
557
+ function isChunkCoveredByCursor(chunkLastObservedAt, cursor) {
558
+ if (!cursor) return false;
559
+ return Date.parse(cursor) >= Date.parse(chunkLastObservedAt) - 1;
560
+ }
561
+ /** The later of two ISO cursors (`null` only when both are absent). */
562
+ function maxCursor(a, b) {
563
+ if (!a) return b ?? null;
564
+ if (!b) return a;
565
+ return Date.parse(b) > Date.parse(a) ? b : a;
566
+ }
567
+ /**
568
+ * New-generation text for a reflection built from `snapshot`: the reflection when the stored
569
+ * text equals the snapshot; the reflection plus the appended tail when the stored text only
570
+ * extends it; `null` (do not apply) after a non-append rewrite.
571
+ */
572
+ function planReflectionText(input) {
573
+ const stored = input.storedObservations ?? "";
574
+ const snapshot = input.snapshotObservations ?? "";
575
+ if (stored === snapshot) return {
576
+ observations: input.reflection,
577
+ tokenCount: input.tokenCount
578
+ };
579
+ if (!stored.startsWith(snapshot)) return null;
580
+ const tail = stored.slice(snapshot.length).trimStart();
581
+ if (!tail.trim()) return {
582
+ observations: input.reflection,
583
+ tokenCount: input.tokenCount
584
+ };
585
+ return {
586
+ observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
587
+ tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
588
+ };
589
+ }
590
+ /** Whether the stored text equals or only extends the snapshot. */
591
+ function isAppendOnly(storedObservations, snapshotObservations) {
592
+ const stored = storedObservations ?? "";
593
+ const snapshot = snapshotObservations ?? "";
594
+ return stored === snapshot || stored.startsWith(snapshot);
595
+ }
596
+ /** Canonical head order: generationCount DESC, createdAt ASC, id ASC. */
597
+ function sortsBeforeAsHead(a, b) {
598
+ if (a.generationCount !== b.generationCount) return a.generationCount > b.generationCount;
599
+ if (a.createdAt !== b.createdAt) return Date.parse(a.createdAt) < Date.parse(b.createdAt);
600
+ return a.id < b.id;
601
+ }
602
+ /**
603
+ * The canonical head of a lookup key. `by_lookup_key` orders ties by `_creationTime`, so when
604
+ * the top two share a generation all tied rows are read and the canonical order picks one.
605
+ */
606
+ async function getCanonicalHead(ctx, convexTable, lookupKey) {
607
+ const top = await ctx.db.query(convexTable).withIndex("by_lookup_key", (q) => q.eq("lookupKey", lookupKey)).order("desc").take(2);
608
+ if (top.length === 0) return null;
609
+ if (top.length === 1 || top[1].generationCount !== top[0].generationCount) return top[0];
610
+ return (await ctx.db.query(convexTable).withIndex("by_lookup_key", (q) => q.eq("lookupKey", lookupKey).eq("generationCount", top[0].generationCount)).collect()).reduce((best, doc) => sortsBeforeAsHead(doc, best) ? doc : best);
611
+ }
612
+ /**
613
+ * Liveness of the record a lifecycle write names. A record with `supersededBy` set is retired.
614
+ * A record that is unmarked but is not its key's canonical head (left by an older adapter
615
+ * version, which never set the marker) is marked superseded by the head here and treated as
616
+ * retired, so a write never lands on it.
617
+ */
618
+ async function resolveTarget(ctx, convexTable, id, now) {
619
+ const doc = await findRecordById(ctx, convexTable, id);
620
+ if (!doc) return { kind: "missing" };
621
+ const head = await getCanonicalHead(ctx, convexTable, doc.lookupKey);
622
+ if (doc.supersededBy) return {
623
+ kind: "retired",
624
+ doc,
625
+ head
626
+ };
627
+ if (head && head.id !== doc.id) {
628
+ await ctx.db.patch(doc._id, {
629
+ supersededBy: head.id,
630
+ updatedAt: now
631
+ });
632
+ return {
633
+ kind: "retired",
634
+ doc,
635
+ head
636
+ };
637
+ }
638
+ return {
639
+ kind: "live",
640
+ doc
641
+ };
642
+ }
643
+ /** The live record a write aimed at `id` lands on (the head when `id` is retired). Throws when missing. */
644
+ async function resolveWriteTarget(ctx, convexTable, id, now) {
645
+ const target = await resolveTarget(ctx, convexTable, id, now);
646
+ if (target.kind === "missing") return requireRecord(null, id);
647
+ if (target.kind === "live") return target.doc;
648
+ if (!target.head) throw new Error(`Observational memory record ${id} is superseded but no live head was found`);
649
+ return target.head;
650
+ }
651
+ /**
652
+ * Create the next generation from the stored (live) record and retire it in the same mutation.
653
+ * Buffered chunks move to the new generation; the cursor, buffering markers, flags, and
654
+ * counters carry over; buffered reflection state does not.
655
+ */
656
+ async function rollOver(ctx, convexTable, stored, args) {
657
+ const newRecord = {
658
+ id: args.newId,
659
+ lookupKey: stored.lookupKey,
660
+ scope: stored.scope,
661
+ resourceId: stored.resourceId ?? null,
662
+ threadId: stored.threadId ?? null,
663
+ activeObservations: args.observations,
664
+ activeObservationsPendingUpdate: null,
665
+ originType: "reflection",
666
+ config: stored.config,
667
+ generationCount: Number(stored.generationCount || 0) + 1,
668
+ lastObservedAt: stored.lastObservedAt ?? null,
669
+ lastReflectionAt: args.now,
670
+ pendingMessageTokens: Number(stored.pendingMessageTokens || 0),
671
+ totalTokensObserved: Number(stored.totalTokensObserved || 0),
672
+ observationTokenCount: args.tokenCount,
673
+ isObserving: false,
674
+ isReflecting: false,
675
+ bufferedObservationChunks: parseStoredChunks(stored.bufferedObservationChunks).length ? stored.bufferedObservationChunks : null,
676
+ isBufferingObservation: Boolean(stored.isBufferingObservation),
677
+ isBufferingReflection: false,
678
+ lastBufferedAtTokens: Number(stored.lastBufferedAtTokens || 0),
679
+ lastBufferedAtTime: stored.lastBufferedAtTime ?? null,
680
+ observedTimezone: stored.observedTimezone ?? null,
681
+ metadata: stored.metadata ?? null,
682
+ supersededBy: null,
683
+ createdAt: args.now,
684
+ updatedAt: args.now
685
+ };
686
+ await ctx.db.insert(convexTable, newRecord);
687
+ await ctx.db.patch(stored._id, {
688
+ supersededBy: args.newId,
689
+ bufferedObservationChunks: null,
690
+ ...args.clearBufferedReflection ? {
691
+ bufferedReflection: null,
692
+ bufferedReflectionTokens: null,
693
+ bufferedReflectionInputTokens: null,
694
+ reflectedObservationLineCount: null
695
+ } : {},
696
+ updatedAt: args.now
697
+ });
698
+ return newRecord;
699
+ }
553
700
  function isPlainObj(value) {
554
701
  return typeof value === "object" && value !== null && !Array.isArray(value);
555
702
  }
@@ -595,8 +742,78 @@ async function handleObservationalMemoryOperation(ctx, convexTable, request) {
595
742
  switch (request.op) {
596
743
  case "omGetLatest": return {
597
744
  ok: true,
598
- result: await ctx.db.query(convexTable).withIndex("by_lookup_key", (q) => q.eq("lookupKey", request.lookupKey)).order("desc").first() ?? null
745
+ result: await getCanonicalHead(ctx, convexTable, request.lookupKey)
599
746
  };
747
+ case "omInitialize": {
748
+ const existing = await getCanonicalHead(ctx, convexTable, request.record.lookupKey);
749
+ if (existing) return {
750
+ ok: true,
751
+ result: existing
752
+ };
753
+ await ctx.db.insert(convexTable, {
754
+ ...request.record,
755
+ supersededBy: null
756
+ });
757
+ return {
758
+ ok: true,
759
+ result: {
760
+ ...request.record,
761
+ supersededBy: null
762
+ }
763
+ };
764
+ }
765
+ case "omCreateReflectionGeneration": {
766
+ const { currentRecord, newId, reflection, tokenCount, now } = request;
767
+ const target = await resolveTarget(ctx, convexTable, currentRecord.id, now);
768
+ if (target.kind === "missing") return {
769
+ ok: true,
770
+ result: null
771
+ };
772
+ if (target.kind === "retired") return {
773
+ ok: true,
774
+ result: target.head
775
+ };
776
+ const stored = target.doc;
777
+ const plan = planReflectionText({
778
+ storedObservations: stored.activeObservations || "",
779
+ storedObservationTokenCount: Number(stored.observationTokenCount || 0),
780
+ snapshotObservations: currentRecord.activeObservations ?? "",
781
+ snapshotObservationTokenCount: currentRecord.observationTokenCount ?? 0,
782
+ reflection,
783
+ tokenCount
784
+ });
785
+ if (!plan) return {
786
+ ok: true,
787
+ result: stored
788
+ };
789
+ return {
790
+ ok: true,
791
+ result: await rollOver(ctx, convexTable, stored, {
792
+ newId,
793
+ observations: plan.observations,
794
+ tokenCount: plan.tokenCount,
795
+ now,
796
+ clearBufferedReflection: false
797
+ })
798
+ };
799
+ }
800
+ case "omSetPendingMessageTokens": {
801
+ const doc = await resolveWriteTarget(ctx, convexTable, request.id, request.updatedAt);
802
+ await ctx.db.patch(doc._id, {
803
+ pendingMessageTokens: request.tokenCount,
804
+ updatedAt: request.updatedAt
805
+ });
806
+ return { ok: true };
807
+ }
808
+ case "omSetBufferingObservationFlag": {
809
+ const doc = await resolveWriteTarget(ctx, convexTable, request.id, request.updatedAt);
810
+ await ctx.db.patch(doc._id, {
811
+ isBufferingObservation: request.isBuffering,
812
+ ...request.lastBufferedAtTokens !== void 0 ? { lastBufferedAtTokens: request.lastBufferedAtTokens } : {},
813
+ updatedAt: request.updatedAt
814
+ });
815
+ return { ok: true };
816
+ }
600
817
  case "omGetHistory": {
601
818
  let docs = request.recordId !== void 0 ? [await findRecordById(ctx, convexTable, request.recordId)].filter((doc) => doc?.lookupKey === request.lookupKey) : await ctx.db.query(convexTable).withIndex("by_lookup_key", (q) => {
602
819
  let range = q.eq("lookupKey", request.lookupKey);
@@ -621,39 +838,82 @@ async function handleObservationalMemoryOperation(ctx, convexTable, request) {
621
838
  };
622
839
  }
623
840
  case "omUpdateActive": {
624
- const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
841
+ const target = await resolveTarget(ctx, convexTable, request.id, request.updatedAt);
842
+ if (target.kind === "missing") requireRecord(null, request.id);
843
+ if (target.kind !== "live") return {
844
+ ok: true,
845
+ result: {
846
+ applied: false,
847
+ reason: "retired"
848
+ }
849
+ };
850
+ const doc = target.doc;
851
+ if (request.expectedActiveObservations !== void 0 && request.expectedActiveObservations !== (doc.activeObservations || "")) return {
852
+ ok: true,
853
+ result: {
854
+ applied: false,
855
+ reason: "conflict"
856
+ }
857
+ };
625
858
  const safeTokenCount = Number.isFinite(request.tokenCount) && request.tokenCount >= 0 ? request.tokenCount : 0;
626
859
  await ctx.db.patch(doc._id, {
627
860
  activeObservations: request.observations,
628
- lastObservedAt: request.lastObservedAt,
861
+ lastObservedAt: maxCursor(doc.lastObservedAt, request.lastObservedAt),
629
862
  pendingMessageTokens: 0,
630
863
  observationTokenCount: safeTokenCount,
631
864
  totalTokensObserved: Number(doc.totalTokensObserved || 0) + safeTokenCount,
632
865
  observedMessageIds: request.observedMessageIds,
633
866
  updatedAt: request.updatedAt
634
867
  });
635
- return { ok: true };
868
+ return {
869
+ ok: true,
870
+ result: { applied: true }
871
+ };
636
872
  }
637
873
  case "omAppendBufferedChunk": {
638
- const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
874
+ const doc = await resolveWriteTarget(ctx, convexTable, request.id, request.updatedAt);
639
875
  const chunks = parseStoredChunks(doc.bufferedObservationChunks);
876
+ if (chunks.some((chunk) => chunk.cycleId === request.chunk.cycleId) || isChunkCoveredByCursor(request.chunk.lastObservedAt, doc.lastObservedAt)) return {
877
+ ok: true,
878
+ result: {
879
+ persisted: false,
880
+ recordId: doc.id
881
+ }
882
+ };
640
883
  chunks.push(request.chunk);
641
- const patch = {
884
+ await ctx.db.patch(doc._id, {
642
885
  bufferedObservationChunks: JSON.stringify(chunks),
886
+ lastBufferedAtTime: maxCursor(doc.lastBufferedAtTime, request.lastBufferedAtTime),
643
887
  updatedAt: request.updatedAt
888
+ });
889
+ return {
890
+ ok: true,
891
+ result: {
892
+ persisted: true,
893
+ recordId: doc.id
894
+ }
644
895
  };
645
- if (request.lastBufferedAtTime) patch.lastBufferedAtTime = request.lastBufferedAtTime;
646
- await ctx.db.patch(doc._id, patch);
647
- return { ok: true };
648
896
  }
649
897
  case "omSwapBuffered": {
650
- const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
651
- const persistedChunks = parseStoredChunks(doc.bufferedObservationChunks);
652
- if (persistedChunks.length === 0) return {
898
+ const target = await resolveTarget(ctx, convexTable, request.id, request.now);
899
+ if (target.kind === "missing") requireRecord(null, request.id);
900
+ if (target.kind !== "live") return {
653
901
  ok: true,
654
- result: EMPTY_SWAP_RESULT
902
+ result: {
903
+ ...EMPTY_SWAP_RESULT,
904
+ retired: true
905
+ }
655
906
  };
656
- const chunks = Array.isArray(request.bufferedChunks) ? request.bufferedChunks : persistedChunks;
907
+ const doc = target.doc;
908
+ const refreshedWeights = new Map((request.bufferedChunks ?? []).map((chunk) => [chunk.id, chunk.messageTokens]));
909
+ const storedChunks = parseStoredChunks(doc.bufferedObservationChunks);
910
+ const chunks = storedChunks.map((chunk) => {
911
+ const weight = refreshedWeights.get(chunk.id);
912
+ return weight === void 0 ? chunk : {
913
+ ...chunk,
914
+ messageTokens: weight
915
+ };
916
+ });
657
917
  if (chunks.length === 0) return {
658
918
  ok: true,
659
919
  result: EMPTY_SWAP_RESULT
@@ -665,7 +925,7 @@ async function handleObservationalMemoryOperation(ctx, convexTable, request) {
665
925
  forceMaxActivation: request.forceMaxActivation
666
926
  });
667
927
  const activatedChunks = chunks.slice(0, chunksToActivate);
668
- const remainingChunks = chunks.slice(chunksToActivate);
928
+ const remainingChunks = storedChunks.slice(chunksToActivate);
669
929
  const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
670
930
  const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
671
931
  const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
@@ -682,7 +942,7 @@ async function handleObservationalMemoryOperation(ctx, convexTable, request) {
682
942
  observationTokenCount: Number(doc.observationTokenCount || 0) + activatedTokens,
683
943
  pendingMessageTokens: Math.max(0, Number(doc.pendingMessageTokens || 0) - activatedMessageTokens),
684
944
  bufferedObservationChunks: remainingChunks.length > 0 ? JSON.stringify(remainingChunks) : null,
685
- lastObservedAt,
945
+ lastObservedAt: maxCursor(doc.lastObservedAt, lastObservedAt),
686
946
  updatedAt: request.now
687
947
  });
688
948
  const latestChunkHints = activatedChunks[activatedChunks.length - 1];
@@ -722,48 +982,32 @@ async function handleObservationalMemoryOperation(ctx, convexTable, request) {
722
982
  }
723
983
  case "omSwapBufferedReflection": {
724
984
  const { currentRecord, newId, tokenCount, now } = request;
725
- const doc = requireRecord(await findRecordById(ctx, convexTable, currentRecord.id), currentRecord.id);
726
- const bufferedReflection = doc.bufferedReflection || "";
985
+ const target = await resolveTarget(ctx, convexTable, currentRecord.id, now);
986
+ if (target.kind === "missing") return {
987
+ ok: true,
988
+ result: null
989
+ };
990
+ if (target.kind === "retired") return {
991
+ ok: true,
992
+ result: target.head
993
+ };
994
+ const stored = target.doc;
995
+ const bufferedReflection = stored.bufferedReflection || "";
727
996
  if (!bufferedReflection) throw new Error("No buffered reflection to swap");
728
- const newObservations = mergeReflectionWithUnreflected(doc.activeObservations || "", bufferedReflection, Number(doc.reflectedObservationLineCount || 0));
729
- const newRecord = {
730
- id: newId,
731
- lookupKey: currentRecord.lookupKey,
732
- scope: currentRecord.scope,
733
- resourceId: currentRecord.resourceId,
734
- threadId: currentRecord.threadId,
735
- activeObservations: newObservations,
736
- activeObservationsPendingUpdate: null,
737
- originType: "reflection",
738
- config: currentRecord.config,
739
- generationCount: currentRecord.generationCount + 1,
740
- lastObservedAt: currentRecord.lastObservedAt,
741
- lastReflectionAt: now,
742
- pendingMessageTokens: 0,
743
- totalTokensObserved: currentRecord.totalTokensObserved,
744
- observationTokenCount: tokenCount,
745
- isObserving: false,
746
- isReflecting: false,
747
- isBufferingObservation: false,
748
- isBufferingReflection: false,
749
- lastBufferedAtTokens: 0,
750
- lastBufferedAtTime: null,
751
- observedTimezone: currentRecord.observedTimezone,
752
- metadata: currentRecord.metadata,
753
- createdAt: now,
754
- updatedAt: now
997
+ const storedObservations = stored.activeObservations || "";
998
+ if (!isAppendOnly(storedObservations, currentRecord.activeObservations ?? "")) return {
999
+ ok: true,
1000
+ result: stored
755
1001
  };
756
- await ctx.db.insert(convexTable, newRecord);
757
- await ctx.db.patch(doc._id, {
758
- bufferedReflection: null,
759
- bufferedReflectionTokens: null,
760
- bufferedReflectionInputTokens: null,
761
- reflectedObservationLineCount: null,
762
- updatedAt: now
763
- });
764
1002
  return {
765
1003
  ok: true,
766
- result: newRecord
1004
+ result: await rollOver(ctx, convexTable, stored, {
1005
+ newId,
1006
+ observations: mergeReflectionWithUnreflected(storedObservations, bufferedReflection, Number(stored.reflectedObservationLineCount || 0)),
1007
+ tokenCount: tokenCount + Math.max(0, Number(stored.observationTokenCount || 0) - (currentRecord.observationTokenCount ?? 0)),
1008
+ now,
1009
+ clearBufferedReflection: true
1010
+ })
767
1011
  };
768
1012
  }
769
1013
  case "omUpdateConfig": {
@@ -778,6 +1022,152 @@ async function handleObservationalMemoryOperation(ctx, convexTable, request) {
778
1022
  }
779
1023
  }
780
1024
  //#endregion
1025
+ //#region src/server/run-fencing.ts
1026
+ const RUN_FENCE_CONFLICT_CODE = "STORAGE_RUN_FENCE_CONFLICT";
1027
+ const RUN_CLAIM_OPS = /* @__PURE__ */ new Set([
1028
+ "claimRunOwnership",
1029
+ "renewRunOwnership",
1030
+ "releaseRunOwnership",
1031
+ "getRunOwnership",
1032
+ "raiseRunFence",
1033
+ "retireRunFence",
1034
+ "fenced"
1035
+ ]);
1036
+ const RUN_CLAIM_TABLES = /* @__PURE__ */ new Set(["mastra_workflow_run_owners", "mastra_memory_run_fences"]);
1037
+ const CLAIMS_TABLE = "mastra_documents";
1038
+ function isRunClaimRequest(request) {
1039
+ return RUN_CLAIM_OPS.has(request.op);
1040
+ }
1041
+ async function findClaim(ctx, table, runId) {
1042
+ return ctx.db.query(CLAIMS_TABLE).withIndex("by_table_primary", (q) => q.eq("table", table).eq("primaryKey", runId)).unique();
1043
+ }
1044
+ async function writeClaim(ctx, table, doc, claim) {
1045
+ const record = {
1046
+ id: claim.runId,
1047
+ ...claim
1048
+ };
1049
+ if (doc) await ctx.db.patch(doc._id, { record });
1050
+ else await ctx.db.insert(CLAIMS_TABLE, {
1051
+ table,
1052
+ primaryKey: claim.runId,
1053
+ record
1054
+ });
1055
+ return claim;
1056
+ }
1057
+ function holds(claim, fence) {
1058
+ return claim !== null && claim.generation === fence.generation && claim.ownerId === fence.ownerId;
1059
+ }
1060
+ function reply(applied, claim, now) {
1061
+ return {
1062
+ ok: true,
1063
+ result: {
1064
+ applied,
1065
+ claim,
1066
+ now
1067
+ }
1068
+ };
1069
+ }
1070
+ /**
1071
+ * Runs a run fencing operation. Each reads and writes the run's claim in the
1072
+ * calling mutation, so Convex serializes it against every other claim
1073
+ * operation and fenced write on the same run. Leases are measured on the
1074
+ * mutation's clock.
1075
+ *
1076
+ * `route` runs the request a fenced operation wraps.
1077
+ */
1078
+ async function handleRunClaimOperation(ctx, request, route) {
1079
+ if (!RUN_CLAIM_TABLES.has(request.tableName)) return {
1080
+ ok: false,
1081
+ error: `Unsupported run claim table ${request.tableName}`
1082
+ };
1083
+ const now = Date.now();
1084
+ switch (request.op) {
1085
+ case "claimRunOwnership": {
1086
+ const { tableName, runId, ownerId, leaseMs, force, expectedGeneration } = request;
1087
+ const doc = await findClaim(ctx, tableName, runId);
1088
+ const current = doc?.record ?? null;
1089
+ const leaseExpiresAt = now + leaseMs;
1090
+ if (!current) {
1091
+ if (expectedGeneration !== void 0 && expectedGeneration !== 0) return reply(false, null, now);
1092
+ return reply(true, await writeClaim(ctx, tableName, doc, {
1093
+ runId,
1094
+ generation: Math.max(1, now),
1095
+ ownerId,
1096
+ leaseExpiresAt,
1097
+ updatedAt: now
1098
+ }), now);
1099
+ }
1100
+ if (expectedGeneration !== void 0 && current.generation !== expectedGeneration) return reply(false, current, now);
1101
+ if (!force && current.leaseExpiresAt !== null && current.leaseExpiresAt > now) return reply(false, current, now);
1102
+ return reply(true, await writeClaim(ctx, tableName, doc, {
1103
+ runId,
1104
+ generation: current.generation + 1,
1105
+ ownerId,
1106
+ leaseExpiresAt,
1107
+ updatedAt: now
1108
+ }), now);
1109
+ }
1110
+ case "renewRunOwnership": {
1111
+ const { tableName, fence, leaseMs } = request;
1112
+ const doc = await findClaim(ctx, tableName, fence.runId);
1113
+ const current = doc?.record ?? null;
1114
+ if (!holds(current, fence) || current.leaseExpiresAt === null) return reply(false, current, now);
1115
+ return reply(true, await writeClaim(ctx, tableName, doc, {
1116
+ ...current,
1117
+ leaseExpiresAt: now + leaseMs,
1118
+ updatedAt: now
1119
+ }), now);
1120
+ }
1121
+ case "releaseRunOwnership": {
1122
+ const { tableName, fence } = request;
1123
+ const doc = await findClaim(ctx, tableName, fence.runId);
1124
+ const current = doc?.record ?? null;
1125
+ if (!holds(current, fence)) return reply(false, current, now);
1126
+ return reply(true, await writeClaim(ctx, tableName, doc, {
1127
+ ...current,
1128
+ leaseExpiresAt: null,
1129
+ updatedAt: now
1130
+ }), now);
1131
+ }
1132
+ case "getRunOwnership": return reply(true, (await findClaim(ctx, request.tableName, request.runId))?.record ?? null, now);
1133
+ case "raiseRunFence": {
1134
+ const { tableName, fence } = request;
1135
+ const doc = await findClaim(ctx, tableName, fence.runId);
1136
+ const current = doc?.record ?? null;
1137
+ if (!current || current.generation < fence.generation || holds(current, fence)) return reply(true, await writeClaim(ctx, tableName, doc, {
1138
+ ...fence,
1139
+ leaseExpiresAt: null,
1140
+ retiredAt: null
1141
+ }), now);
1142
+ return reply(false, current, now);
1143
+ }
1144
+ case "retireRunFence": {
1145
+ const { tableName, fence } = request;
1146
+ const doc = await findClaim(ctx, tableName, fence.runId);
1147
+ const current = doc?.record ?? null;
1148
+ if (!holds(current, fence)) return reply(false, current, now);
1149
+ return reply(true, await writeClaim(ctx, tableName, doc, {
1150
+ ...current,
1151
+ retiredAt: now
1152
+ }), now);
1153
+ }
1154
+ case "fenced": {
1155
+ const { tableName, fence, request: inner } = request;
1156
+ if (!holds((await findClaim(ctx, tableName, fence.runId))?.record ?? null, fence)) return {
1157
+ ok: false,
1158
+ error: `Run ${fence.runId} is no longer claimed by generation ${fence.generation} of ${fence.ownerId}`,
1159
+ code: RUN_FENCE_CONFLICT_CODE
1160
+ };
1161
+ if (!inner) return { ok: true };
1162
+ if (RUN_CLAIM_OPS.has(inner.op)) return {
1163
+ ok: false,
1164
+ error: `Unsupported fenced operation ${inner.op}`
1165
+ };
1166
+ return route(ctx, inner);
1167
+ }
1168
+ }
1169
+ }
1170
+ //#endregion
781
1171
  //#region src/server/workflow-snapshot.ts
782
1172
  const PENDING_MARKER_KEY = "__mastra_pending__";
783
1173
  function isPendingMarker(val) {
@@ -1059,6 +1449,10 @@ function resolveTable(tableName) {
1059
1449
  * Routes operations to the appropriate typed table.
1060
1450
  */
1061
1451
  const mastraStorage = mutationGeneric(async (ctx, request) => {
1452
+ if (isRunClaimRequest(request)) return handleRunClaimOperation(ctx, request, routeStorageRequest);
1453
+ return routeStorageRequest(ctx, request);
1454
+ });
1455
+ async function routeStorageRequest(ctx, request) {
1062
1456
  try {
1063
1457
  const { convexTable, isTyped } = resolveTable(request.tableName);
1064
1458
  if (request.tableName.startsWith(VECTOR_TABLE_PREFIX) && request.tableName !== TABLE_VECTOR_INDEXES) return await handleVectorOperation(ctx, request);
@@ -1078,7 +1472,7 @@ const mastraStorage = mutationGeneric(async (ctx, request) => {
1078
1472
  error: error.message
1079
1473
  };
1080
1474
  }
1081
- });
1475
+ }
1082
1476
  function parseStoredSnapshot(stored, runId) {
1083
1477
  if (typeof stored === "string") return JSON.parse(stored);
1084
1478
  return JSON.parse(JSON.stringify(stored ?? createEmptyWorkflowSnapshot(runId)));
@@ -1108,6 +1502,10 @@ async function handleTypedOperation(ctx, convexTable, request) {
1108
1502
  switch (request.op) {
1109
1503
  case "omGetLatest":
1110
1504
  case "omGetHistory":
1505
+ case "omInitialize":
1506
+ case "omCreateReflectionGeneration":
1507
+ case "omSetPendingMessageTokens":
1508
+ case "omSetBufferingObservationFlag":
1111
1509
  case "omUpdateActive":
1112
1510
  case "omAppendBufferedChunk":
1113
1511
  case "omSwapBuffered":
@@ -1895,4 +2293,4 @@ const mastraNativeVectorMutation = mutationGeneric({
1895
2293
  //#endregion
1896
2294
  export { mastraCache as a, mastraStorage as i, mastraNativeVectorMutation as n, mastraNativeVectorQuery as r, mastraNativeVectorAction as t };
1897
2295
 
1898
- //# sourceMappingURL=server-D3SG5LQf.js.map
2296
+ //# sourceMappingURL=server-B8RSiOmu.js.map