@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.
- package/dist/_types/@internal_core/dist/memory/index.d.ts +102 -0
- package/dist/_types/@internal_core/package.json +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-vectors-convex.md +1 -3
- package/dist/index.cjs +370 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +371 -162
- package/dist/index.js.map +1 -1
- package/dist/schema.cjs +1 -0
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.ts +3 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/schema.js.map +1 -1
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/observational-memory.d.ts +26 -1
- package/dist/server/observational-memory.d.ts.map +1 -1
- package/dist/server/run-fencing.d.ts +14 -0
- package/dist/server/run-fencing.d.ts.map +1 -0
- package/dist/server/storage.d.ts +2 -2
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/{server-D3SG5LQf.js → server-B8RSiOmu.js} +454 -56
- package/dist/server-B8RSiOmu.js.map +1 -0
- package/dist/{server-D_scBUwi.cjs → server-Bcgr5CcA.cjs} +454 -56
- package/dist/server-Bcgr5CcA.cjs.map +1 -0
- package/dist/storage/client.d.ts +3 -3
- package/dist/storage/client.d.ts.map +1 -1
- package/dist/storage/db/index.d.ts +33 -6
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +20 -7
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +16 -5
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/run-fencing.d.ts +28 -0
- package/dist/storage/run-fencing.d.ts.map +1 -0
- package/dist/storage/types.d.ts +114 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/package.json +5 -4
- package/dist/server-D3SG5LQf.js.map +0 -1
- 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
|
|
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
|
|
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 {
|
|
868
|
+
return {
|
|
869
|
+
ok: true,
|
|
870
|
+
result: { applied: true }
|
|
871
|
+
};
|
|
636
872
|
}
|
|
637
873
|
case "omAppendBufferedChunk": {
|
|
638
|
-
const doc =
|
|
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
|
-
|
|
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
|
|
651
|
-
|
|
652
|
-
if (
|
|
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:
|
|
902
|
+
result: {
|
|
903
|
+
...EMPTY_SWAP_RESULT,
|
|
904
|
+
retired: true
|
|
905
|
+
}
|
|
655
906
|
};
|
|
656
|
-
const
|
|
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 =
|
|
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
|
|
726
|
-
|
|
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
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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:
|
|
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 = (0, convex_server.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 = (0, convex_server.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":
|
|
@@ -1924,4 +2322,4 @@ Object.defineProperty(exports, "mastraStorage", {
|
|
|
1924
2322
|
}
|
|
1925
2323
|
});
|
|
1926
2324
|
|
|
1927
|
-
//# sourceMappingURL=server-
|
|
2325
|
+
//# sourceMappingURL=server-Bcgr5CcA.cjs.map
|