feat: project durable agent conversations

This commit is contained in:
-Puter
2026-07-29 08:19:17 +05:30
parent 18eb150d7d
commit 3ffa1cfc7c
22 changed files with 800 additions and 309 deletions

View File

@@ -20,17 +20,6 @@ const markProcessingRef = makeFunctionReference<
{ turnId: string; attempt: number; leaseOwner: string },
boolean
>("conversationMessages:markProcessing");
const completeTurnRef = makeFunctionReference<
"mutation",
{
turnId: string;
attempt: number;
leaseOwner: string;
submissionId: string;
text: string;
},
boolean
>("conversationMessages:completeTurn");
const failTurnRef = makeFunctionReference<
"mutation",
{
@@ -137,7 +126,7 @@ describe("conversationMessages", () => {
).rejects.toThrow(/Organization membership required/u);
});
test("fences stale turn attempts from overwriting a retry", async () => {
test("fences stale dispatch failures after admission", async () => {
const t = newTest();
const organization = await ensureOrg(t, identityA);
const sent = await t
@@ -146,7 +135,7 @@ describe("conversationMessages", () => {
clientRequestId: "request-fenced",
images: [],
organizationId: organization._id,
rawText: "Keep only the current response",
rawText: "Keep only the admitted submission",
});
expect(
@@ -156,46 +145,27 @@ describe("conversationMessages", () => {
turnId: sent.turnId,
})
).toBe(true);
await t.run(async (ctx) => {
await ctx.db.patch(sent.turnId, {
leaseExpiresAt: undefined,
leaseOwner: undefined,
status: "running",
submissionId: "submission-1",
});
});
expect(
await t.mutation(failTurnRef, {
attempt: 1,
error: "retry",
error: "lost 202",
leaseOwner: "worker-1",
retry: true,
turnId: sent.turnId,
})
).toBe(true);
expect(
await t.mutation(completeTurnRef, {
attempt: 1,
leaseOwner: "worker-1",
submissionId: "stale",
text: "stale response",
turnId: sent.turnId,
})
).toBe(false);
expect(
await t.mutation(markProcessingRef, {
attempt: 2,
leaseOwner: "worker-2",
turnId: sent.turnId,
})
).toBe(true);
expect(
await t.mutation(completeTurnRef, {
attempt: 2,
leaseOwner: "worker-2",
submissionId: "current",
text: "current response",
turnId: sent.turnId,
})
).toBe(true);
const messages = await t
.withIdentity(identityA)
.query(api.conversationMessages.listForCurrentOrganization, {
organizationId: organization._id,
});
expect(messages[1]?.rawText).toBe("current response");
expect(await t.run((ctx) => ctx.db.get(sent.turnId))).toMatchObject({
status: "running",
submissionId: "submission-1",
});
});
});