Spaces:
Paused
Paused
Download src/lib/server/agent/AgentGenerationStore.spec.ts from Mike0021/chat-ui-agent-demo: direct link, hf CLI and curl.
- Browser
- Download file 32.3 kB
-
https://huggingface.co/spaces/Mike0021/chat-ui-agent-demo/resolve/main/src/lib/server/agent/AgentGenerationStore.spec.ts
- Command line
-
hf download hf://spaces/Mike0021/chat-ui-agent-demo/src/lib/server/agent/AgentGenerationStore.spec.ts
-
curl -L -o AgentGenerationStore.spec.ts https://huggingface.co/spaces/Mike0021/chat-ui-agent-demo/resolve/main/src/lib/server/agent/AgentGenerationStore.spec.ts
32.3 kB
| import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; | |
| import { ObjectId } from "mongodb"; | |
| import { randomUUID } from "node:crypto"; | |
| import { collections, ready } from "$lib/server/database"; | |
| import { | |
| MessageUpdateStatus, | |
| MessageUpdateType, | |
| type MessageUpdate, | |
| } from "$lib/types/MessageUpdate"; | |
| import type { AgentPendingBatch, Generation } from "$lib/types/Generation"; | |
| import type { Workspace } from "$lib/types/Workspace"; | |
| import { | |
| claimWorkspaceOperation, | |
| releaseWorkspaceOperation, | |
| WORKSPACE_OPERATION_LEASE_MS, | |
| } from "./WorkspaceOperation"; | |
| import { recordGenerationStop } from "$lib/server/generation/stopGeneration"; | |
| import { | |
| AgentGenerationIncarnationMismatchError, | |
| AgentGenerationStore, | |
| AgentRelayLeaseLostError, | |
| } from "./AgentGenerationStore"; | |
| import { reapStaleGenerations } from "$lib/server/generation/reaper"; | |
| beforeAll(async () => { | |
| await ready; | |
| }); | |
| afterEach(async () => { | |
| vi.restoreAllMocks(); | |
| await Promise.all([ | |
| collections.conversations.deleteMany({}), | |
| collections.workspaces.deleteMany({}), | |
| collections.generations.deleteMany({}), | |
| collections.generationEvents.deleteMany({}), | |
| collections.abortedGenerations.deleteMany({}), | |
| ]); | |
| }); | |
| describe.sequential("AgentGenerationStore", () => { | |
| it("commits a replayed bridge sequence exactly once", async () => { | |
| const seeded = await seedAgentRun(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| const update: MessageUpdate = { type: MessageUpdateType.Stream, token: "once" }; | |
| const bound = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(bound?.agent).toMatchObject(sandboxIncarnation(seeded.workspace)); | |
| const first = await store.commitBridgeBatch(41, [update]); | |
| const replay = await store.commitBridgeBatch(41, [update]); | |
| expect(first.inserted).toBe(true); | |
| expect(replay.inserted).toBe(false); | |
| expect( | |
| await collections.generationEvents.countDocuments({ generationId: seeded.generationId }) | |
| ).toBe(1); | |
| const conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| const message = conversation?.messages.find((item) => item.id === seeded.messageId); | |
| expect(message?.content).toBe("once"); | |
| expect(message?.agentSequence).toBe(41); | |
| expect(message?.materializedSeq).toBe(1); | |
| expect( | |
| (await collections.workspaces.findOne({ _id: seeded.workspace._id }))?.piSession?.lastSequence | |
| ).toBe(41); | |
| }); | |
| it("binds once and refuses to acquire the same prompt for a replacement sandbox", async () => { | |
| const seeded = await seedAgentRun(); | |
| const original = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| await original.release(); | |
| await expect( | |
| AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| sandboxId: "sandbox-replacement", | |
| sandboxGeneration: "generation-replacement", | |
| }) | |
| ).rejects.toBeInstanceOf(AgentGenerationIncarnationMismatchError); | |
| const generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(generation?.agent).toMatchObject(sandboxIncarnation(seeded.workspace)); | |
| expect(generation?.agent?.relay).toBeUndefined(); | |
| expect(generation?.status).toBe("running"); | |
| }); | |
| it("continues after a durable provisioning event even when its heartbeat cursor lagged", async () => { | |
| const seeded = await seedAgentRun(); | |
| await collections.generationEvents.insertOne({ | |
| _id: new ObjectId(), | |
| generationId: seeded.generationId, | |
| seq: 1, | |
| event: { type: MessageUpdateType.AgentSandbox, state: "ready" }, | |
| createdAt: new Date(), | |
| }); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| const committed = await store.commitBridgeBatch(1, [ | |
| { type: MessageUpdateType.Stream, token: "after provisioning" }, | |
| ]); | |
| expect(committed.endSeq).toBe(2); | |
| expect( | |
| ( | |
| await collections.generationEvents | |
| .find({ generationId: seeded.generationId }) | |
| .sort({ seq: 1 }) | |
| .toArray() | |
| ).map((event) => event.event.type) | |
| ).toEqual([MessageUpdateType.AgentSandbox, MessageUpdateType.Stream]); | |
| const conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| const message = conversation?.messages.find((item) => item.id === seeded.messageId); | |
| expect(message?.materializedSeq).toBe(2); | |
| expect(message?.updates?.map((event) => event.type)).toEqual([ | |
| MessageUpdateType.AgentSandbox, | |
| MessageUpdateType.Stream, | |
| ]); | |
| }); | |
| it("tracks native approval requests as live generation state and clears them at terminal", async () => { | |
| const seeded = await seedAgentRun(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| await store.commitBridgeBatch(1, [ | |
| { | |
| type: MessageUpdateType.AgentApproval, | |
| requestId: "approval-live", | |
| method: "confirm", | |
| title: "Allow command?", | |
| }, | |
| ]); | |
| expect( | |
| (await collections.generations.findOne({ generationId: seeded.generationId }))?.agent | |
| ?.pendingApprovalIds | |
| ).toEqual(["approval-live"]); | |
| expect(await store.finish("completed")).toBe(true); | |
| expect( | |
| (await collections.generations.findOne({ generationId: seeded.generationId }))?.agent | |
| ?.pendingApprovalIds | |
| ).toBeUndefined(); | |
| }); | |
| it("keeps one exact relay authoritative through every login ownership handoff phase", async () => { | |
| const seeded = await seedAgentRun(); | |
| const userId = new ObjectId(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| await collections.workspaces.updateOne({ _id: seeded.workspace._id }, { $set: { userId } }); | |
| expect(await store.authoritativeOwner()).toEqual({ sessionId: seeded.workspace.sessionId }); | |
| await collections.generations.updateOne( | |
| { generationId: seeded.generationId }, | |
| { $set: { userId } } | |
| ); | |
| expect(await store.authoritativeOwner()).toEqual({ sessionId: seeded.workspace.sessionId }); | |
| await collections.conversations.updateOne( | |
| { _id: seeded.conversationId }, | |
| { $set: { userId }, $unset: { sessionId: "" } } | |
| ); | |
| expect(await store.authoritativeOwner()).toEqual({ userId }); | |
| await collections.workspaces.updateOne( | |
| { _id: seeded.workspace._id }, | |
| { $unset: { sessionId: "" } } | |
| ); | |
| await collections.generations.updateOne( | |
| { generationId: seeded.generationId }, | |
| { $unset: { sessionId: "" } } | |
| ); | |
| await store.assertPromptDispatch(seeded.workspace); | |
| expect(await store.authoritativeOwner()).toEqual({ userId }); | |
| await store.release(); | |
| }); | |
| it("cannot complete after an exact stop intent wins the relay terminal barrier", async () => { | |
| const seeded = await seedAgentRun(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| await store.commitBridgeBatch(1, [ | |
| { type: MessageUpdateType.Stream, token: "stopped-late-output" }, | |
| ]); | |
| expect( | |
| await recordGenerationStop({ | |
| conversationId: seeded.conversationId, | |
| generationId: seeded.generationId, | |
| seenContentLength: 4, | |
| }) | |
| ).toBe(true); | |
| expect(await store.finish("completed")).toBe(true); | |
| expect( | |
| await collections.generations.findOne({ generationId: seeded.generationId }) | |
| ).toMatchObject({ | |
| status: "interrupted", | |
| stopRequested: { seenContentLength: 4 }, | |
| }); | |
| const message = (await collections.conversations.findOne({ _id: seeded.conversationId })) | |
| ?.messages[0]; | |
| expect(message?.content).toBe("stop"); | |
| expect(message?.interrupted).toBe(true); | |
| }); | |
| it("finishes a crash-partial write-ahead batch without gaps or duplication", async () => { | |
| const seeded = await seedAgentRun(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| const events: MessageUpdate[] = [ | |
| { type: MessageUpdateType.Stream, token: "alpha " }, | |
| { type: MessageUpdateType.Stream, token: "beta" }, | |
| ]; | |
| await collections.generations.updateOne( | |
| { generationId: seeded.generationId }, | |
| { | |
| $set: { | |
| seq: 2, | |
| "agent.pending": { | |
| kind: "bridge", | |
| sourceSequence: 7, | |
| startSeq: 1, | |
| events, | |
| }, | |
| }, | |
| } | |
| ); | |
| await collections.generationEvents.insertOne({ | |
| _id: new ObjectId(), | |
| generationId: seeded.generationId, | |
| seq: 1, | |
| event: events[0], | |
| sourceSequence: 7, | |
| sourceIndex: 0, | |
| createdAt: new Date(), | |
| }); | |
| await store.resumePending(); | |
| const persisted = await collections.generationEvents | |
| .find({ generationId: seeded.generationId }) | |
| .sort({ seq: 1 }) | |
| .toArray(); | |
| expect(persisted.map((item) => item.seq)).toEqual([1, 2]); | |
| expect(persisted.map((item) => item.sourceIndex)).toEqual([0, 1]); | |
| const conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| expect(conversation?.messages.find((item) => item.id === seeded.messageId)?.content).toBe( | |
| "alpha beta" | |
| ); | |
| const generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(generation?.agent?.pending).toBeUndefined(); | |
| expect(generation?.agent?.sourceSequence).toBe(7); | |
| }); | |
| it("keeps the WAL pending when a relay crashes after message materialization", async () => { | |
| const seeded = await seedAgentRun(); | |
| let timestamp = 0; | |
| const now = () => new Date(timestamp); | |
| const first = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| owner: "crashing-relay", | |
| leaseMs: 10, | |
| now, | |
| }) | |
| ); | |
| timestamp = 5; | |
| const restoreMaterialization = failAfterNextPendingMaterialization(first); | |
| try { | |
| await expect( | |
| first.commitBridgeBatch(1, [ | |
| { type: MessageUpdateType.Stream, token: "materialized exactly once" }, | |
| ]) | |
| ).rejects.toThrow("simulated process crash after Agent WAL materialization"); | |
| } finally { | |
| restoreMaterialization(); | |
| } | |
| let generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(generation?.agent?.pending).toMatchObject({ | |
| kind: "bridge", | |
| sourceSequence: 1, | |
| }); | |
| expect(generation?.agent?.sourceSequence).toBe(0); | |
| expect(generation?.agent?.relay?.expiresAt).toEqual(new Date(15)); | |
| let workspace = await collections.workspaces.findOne({ _id: seeded.workspace._id }); | |
| expect(workspace?.execution?.expiresAt).toEqual(new Date(15)); | |
| let conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| expect(conversation?.messages[0]?.content).toBe("materialized exactly once"); | |
| await first.release(); | |
| const replacement = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| owner: "replacement-relay", | |
| leaseMs: 10, | |
| now, | |
| }) | |
| ); | |
| await replacement.resumePending(); | |
| generation = await collections.generations.findOne({ generationId: seeded.generationId }); | |
| expect(generation?.agent?.pending).toBeUndefined(); | |
| expect(generation?.agent?.sourceSequence).toBe(1); | |
| workspace = await collections.workspaces.findOne({ _id: seeded.workspace._id }); | |
| expect(workspace?.piSession?.lastSequence).toBe(1); | |
| conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| expect(conversation?.messages[0]?.content).toBe("materialized exactly once"); | |
| expect( | |
| await collections.generationEvents.countDocuments({ generationId: seeded.generationId }) | |
| ).toBe(1); | |
| }); | |
| it("recovers a sandbox-change crash after pending message materialization", async () => { | |
| const seeded = await seedAgentRun(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| const pending: AgentPendingBatch = { | |
| kind: "bridge", | |
| sourceSequence: 1, | |
| startSeq: 1, | |
| events: [{ type: MessageUpdateType.Stream, token: "terminal batch exactly once" }], | |
| }; | |
| await collections.generations.updateOne( | |
| { generationId: seeded.generationId }, | |
| { $set: { seq: 1, "agent.pending": pending, "agent.controlsOpen": true } } | |
| ); | |
| const originalUpdateOne = collections.conversations.updateOne.bind(collections.conversations); | |
| let crashed = false; | |
| const updateSpy = vi | |
| .spyOn(collections.conversations, "updateOne") | |
| .mockImplementation(async (filter, update, options) => { | |
| const result = await originalUpdateOne(filter, update, options); | |
| if (!crashed && "$set" in update && update.$set && "messages.$" in update.$set) { | |
| crashed = true; | |
| throw new Error("simulated sandbox terminalizer crash after materialization"); | |
| } | |
| return result; | |
| }); | |
| await expect( | |
| AgentGenerationStore.interruptForSandboxChange({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| sandboxId: "replacement-sandbox", | |
| sandboxGeneration: "replacement-generation", | |
| }) | |
| ).rejects.toThrow("simulated sandbox terminalizer crash after materialization"); | |
| let generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(generation).toMatchObject({ | |
| status: "running", | |
| agent: { | |
| controlsOpen: false, | |
| pending, | |
| terminalization: { kind: "sandbox-change" }, | |
| }, | |
| }); | |
| expect(generation?.agent?.relay).toBeUndefined(); | |
| expect( | |
| (await collections.conversations.findOne({ _id: seeded.conversationId }))?.messages[0] | |
| ?.content | |
| ).toBe("terminal batch exactly once"); | |
| expect( | |
| await collections.generationEvents.countDocuments({ generationId: seeded.generationId }) | |
| ).toBe(1); | |
| await expect(store.commitBridgeBatch(2, [])).rejects.toBeInstanceOf(AgentRelayLeaseLostError); | |
| expect( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ).toBeNull(); | |
| updateSpy.mockRestore(); | |
| await collections.generations.updateOne( | |
| { generationId: seeded.generationId }, | |
| { $set: { "agent.terminalization.expiresAt": new Date(0) } } | |
| ); | |
| expect( | |
| await AgentGenerationStore.interruptForSandboxChange({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| sandboxId: "replacement-sandbox", | |
| sandboxGeneration: "replacement-generation", | |
| }) | |
| ).toBe(true); | |
| generation = await collections.generations.findOne({ generationId: seeded.generationId }); | |
| expect(generation).toMatchObject({ | |
| status: "interrupted", | |
| agent: { sourceSequence: 1, controlsOpen: false }, | |
| }); | |
| expect(generation?.agent?.pending).toBeUndefined(); | |
| expect(generation?.agent?.terminalization).toBeUndefined(); | |
| expect( | |
| (await collections.conversations.findOne({ _id: seeded.conversationId }))?.messages[0] | |
| ).toMatchObject({ content: "terminal batch exactly once", interrupted: true }); | |
| expect( | |
| await collections.generationEvents.countDocuments({ generationId: seeded.generationId }) | |
| ).toBe(1); | |
| }); | |
| it("reaper resumes an expired post-terminal marker before clearing side effects", async () => { | |
| const seeded = await seedAgentRun(); | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }); | |
| const originalFindOne = collections.conversations.findOne.bind(collections.conversations); | |
| let crashed = false; | |
| const findSpy = vi | |
| .spyOn(collections.conversations, "findOne") | |
| .mockImplementation(async (filter, options) => { | |
| const generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| if (!crashed && generation?.status === "interrupted" && generation.agent?.terminalization) { | |
| crashed = true; | |
| throw new Error("simulated crash after terminal CAS"); | |
| } | |
| return originalFindOne(filter, options); | |
| }); | |
| await expect( | |
| AgentGenerationStore.interruptForSandboxChange({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| sandboxId: "replacement-sandbox", | |
| sandboxGeneration: "replacement-generation", | |
| }) | |
| ).rejects.toThrow("simulated crash after terminal CAS"); | |
| let generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(generation).toMatchObject({ | |
| status: "interrupted", | |
| agent: { terminalization: { kind: "sandbox-change" } }, | |
| }); | |
| expect( | |
| (await collections.conversations.findOne({ _id: seeded.conversationId }))?.messages[0] | |
| ?.interrupted | |
| ).not.toBe(true); | |
| expect( | |
| (await collections.workspaces.findOne({ _id: seeded.workspace._id }))?.execution | |
| ).toBeDefined(); | |
| findSpy.mockRestore(); | |
| await collections.generations.updateOne( | |
| { generationId: seeded.generationId }, | |
| { $set: { "agent.terminalization.expiresAt": new Date(0) } } | |
| ); | |
| await reapStaleGenerations(); | |
| generation = await collections.generations.findOne({ generationId: seeded.generationId }); | |
| expect(generation?.agent?.terminalization).toBeUndefined(); | |
| expect( | |
| (await collections.conversations.findOne({ _id: seeded.conversationId }))?.messages[0] | |
| ?.interrupted | |
| ).toBe(true); | |
| expect( | |
| (await collections.workspaces.findOne({ _id: seeded.workspace._id }))?.execution | |
| ).toBeUndefined(); | |
| }); | |
| it("does not materialize a captured WAL batch after its relay fence moves", async () => { | |
| const seeded = await seedAgentRun(); | |
| let timestamp = 0; | |
| const now = () => new Date(timestamp); | |
| const first = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| owner: "stale-relay", | |
| leaseMs: 10, | |
| now, | |
| }) | |
| ); | |
| const pending: AgentPendingBatch = { | |
| kind: "bridge", | |
| sourceSequence: 1, | |
| startSeq: 1, | |
| events: [{ type: MessageUpdateType.Stream, token: "authoritative output" }], | |
| }; | |
| await collections.generations.updateOne( | |
| { generationId: seeded.generationId }, | |
| { $set: { seq: 1, "agent.pending": pending } } | |
| ); | |
| const captured = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| if (!captured) throw new Error("Missing captured generation WAL"); | |
| timestamp = 11; | |
| const replacement = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| owner: "replacement-relay", | |
| leaseMs: 10, | |
| now, | |
| }) | |
| ); | |
| const capturedFlush = first as unknown as { | |
| flushPending(generation: Generation, batch: AgentPendingBatch): Promise<unknown>; | |
| }; | |
| await expect(capturedFlush.flushPending(captured, pending)).rejects.toBeInstanceOf( | |
| AgentRelayLeaseLostError | |
| ); | |
| let conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| expect(conversation?.messages[0]?.content).toBe(""); | |
| await replacement.resumePending(); | |
| conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| expect(conversation?.messages[0]?.content).toBe("authoritative output"); | |
| expect( | |
| await collections.generationEvents.countDocuments({ generationId: seeded.generationId }) | |
| ).toBe(1); | |
| }); | |
| it("fences an expired producer and lets only the replacement finish", async () => { | |
| const seeded = await seedAgentRun(); | |
| let timestamp = 0; | |
| const now = () => new Date(timestamp); | |
| const first = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| owner: "first", | |
| leaseMs: 10, | |
| now, | |
| }) | |
| ); | |
| timestamp = 11; | |
| const replacement = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| owner: "replacement", | |
| leaseMs: 10, | |
| now, | |
| }) | |
| ); | |
| await expect(first.renew()).rejects.toBeInstanceOf(AgentRelayLeaseLostError); | |
| await replacement.commitBridgeBatch(1, [ | |
| { type: MessageUpdateType.Stream, token: "replacement" }, | |
| ]); | |
| expect(await replacement.finish("completed")).toBe(true); | |
| expect( | |
| (await collections.generations.findOne({ generationId: seeded.generationId }))?.status | |
| ).toBe("completed"); | |
| }); | |
| it("never lets advisory pi stats lower authoritative proxy or sandbox spend", async () => { | |
| const seeded = await seedAgentRun(); | |
| await collections.workspaces.updateOne( | |
| { _id: seeded.workspace._id }, | |
| { | |
| $set: { | |
| spend: { | |
| accruedUsd: 12, | |
| inferenceUsd: 7, | |
| sandboxUsd: 5, | |
| lastCalculatedAt: new Date(), | |
| }, | |
| }, | |
| } | |
| ); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| await store.commitBridgeBatch(1, [ | |
| { | |
| type: MessageUpdateType.AgentStats, | |
| scope: "session", | |
| tokens: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0, total: 2 }, | |
| costUsd: 0.01, | |
| }, | |
| ]); | |
| const spend = (await collections.workspaces.findOne({ _id: seeded.workspace._id }))?.spend; | |
| expect(spend).toMatchObject({ accruedUsd: 12, inferenceUsd: 7, sandboxUsd: 5 }); | |
| }); | |
| it("does not advance the pi cursor on a replacement sandbox incarnation", async () => { | |
| const seeded = await seedAgentRun(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| await collections.workspaces.updateOne( | |
| { _id: seeded.workspace._id }, | |
| { | |
| $set: { | |
| "sandbox.id": "sandbox-replacement", | |
| "sandbox.generation": "generation-replacement", | |
| "piSession.lastSequence": 0, | |
| }, | |
| } | |
| ); | |
| await expect( | |
| store.commitBridgeBatch(41, [{ type: MessageUpdateType.Stream, token: "old incarnation" }]) | |
| ).rejects.toBeInstanceOf(AgentRelayLeaseLostError); | |
| const workspace = await collections.workspaces.findOne({ _id: seeded.workspace._id }); | |
| expect(workspace?.sandbox.id).toBe("sandbox-replacement"); | |
| expect(workspace?.piSession?.lastSequence).toBe(0); | |
| }); | |
| it("treats an exact workspace operation as contention instead of a sandbox mismatch", async () => { | |
| const seeded = await seedAgentRun(); | |
| const claimed = await claimWorkspaceOperation({ | |
| conversationId: seeded.conversationId, | |
| workspace: seeded.workspace, | |
| owner: { sessionId: seeded.workspace.sessionId }, | |
| kind: "configuration", | |
| action: "compact", | |
| }); | |
| expect( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ).toBeNull(); | |
| const generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(generation?.status).toBe("running"); | |
| expect(generation?.agent?.relay).toBeUndefined(); | |
| await releaseWorkspaceOperation( | |
| seeded.workspace._id, | |
| { sessionId: seeded.workspace.sessionId }, | |
| claimed.lease.id | |
| ); | |
| }); | |
| it("atomically reclaims orphaned operation leases on relay acquire and renew", async () => { | |
| const seeded = await seedAgentRun(); | |
| const orphanedOperation = (id: string) => ({ | |
| id, | |
| kind: "configuration" as const, | |
| action: "set-model", | |
| claimedAt: new Date(Date.now() - WORKSPACE_OPERATION_LEASE_MS - 1), | |
| workspaceUpdatedAt: seeded.workspace.updatedAt, | |
| }); | |
| await collections.workspaces.updateOne( | |
| { _id: seeded.workspace._id }, | |
| { $set: { operation: orphanedOperation("orphaned-acquire") } } | |
| ); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| expect( | |
| (await collections.workspaces.findOne({ _id: seeded.workspace._id }))?.operation | |
| ).toBeUndefined(); | |
| await collections.workspaces.updateOne( | |
| { _id: seeded.workspace._id }, | |
| { $set: { operation: orphanedOperation("orphaned-renew") } } | |
| ); | |
| await expect(store.renew()).resolves.toBeUndefined(); | |
| expect( | |
| (await collections.workspaces.findOne({ _id: seeded.workspace._id }))?.operation | |
| ).toBeUndefined(); | |
| await store.finish("completed"); | |
| }); | |
| it("keeps a live operation lease blocking relay renewal", async () => { | |
| const seeded = await seedAgentRun(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| await collections.workspaces.updateOne( | |
| { _id: seeded.workspace._id }, | |
| { | |
| $set: { | |
| operation: { | |
| id: "live-renew-operation", | |
| kind: "configuration", | |
| action: "set-policy", | |
| claimedAt: new Date(), | |
| workspaceUpdatedAt: seeded.workspace.updatedAt, | |
| }, | |
| }, | |
| } | |
| ); | |
| await expect(store.renew()).rejects.toBeInstanceOf(AgentRelayLeaseLostError); | |
| expect( | |
| (await collections.workspaces.findOne({ _id: seeded.workspace._id }))?.operation?.id | |
| ).toBe("live-renew-operation"); | |
| }); | |
| it("clears an unrecoverable pending WAL while preserving its terminal error", async () => { | |
| const seeded = await seedAgentRun(); | |
| const store = requireStore( | |
| await AgentGenerationStore.acquire({ | |
| generationId: seeded.generationId, | |
| workspaceId: seeded.workspace._id, | |
| ...sandboxIncarnation(seeded.workspace), | |
| }) | |
| ); | |
| await collections.generations.updateOne( | |
| { | |
| generationId: seeded.generationId, | |
| "agent.relay.owner": store.owner, | |
| "agent.relay.fence": store.fence, | |
| }, | |
| { | |
| $set: { | |
| "agent.controlsOpen": true, | |
| "agent.control": { | |
| id: "undeliverable-control", | |
| fingerprint: "fingerprint", | |
| expectedPromptId: "prompt", | |
| payload: { kind: "compact" }, | |
| requestedAt: new Date(), | |
| state: "pending", | |
| }, | |
| }, | |
| } | |
| ); | |
| await collections.workspaces.deleteOne({ _id: seeded.workspace._id }); | |
| await expect(store.finish("interrupted")).rejects.toBeInstanceOf(AgentRelayLeaseLostError); | |
| await collections.generations.updateOne( | |
| { | |
| generationId: seeded.generationId, | |
| "agent.relay.owner": store.owner, | |
| "agent.relay.fence": store.fence, | |
| }, | |
| { | |
| $set: { | |
| seq: 1, | |
| "agent.pending": { | |
| kind: "system", | |
| key: "unrecoverable-pending", | |
| startSeq: 1, | |
| events: [ | |
| { | |
| type: MessageUpdateType.Status, | |
| status: MessageUpdateStatus.Error, | |
| message: "historical terminal failure", | |
| }, | |
| ], | |
| }, | |
| }, | |
| } | |
| ); | |
| const restoreMaterialization = failAfterNextPendingMaterialization(store); | |
| try { | |
| await expect( | |
| store.finishUnrecoverable("interrupted", "Agent workspace is no longer available") | |
| ).rejects.toThrow("simulated process crash after Agent WAL materialization"); | |
| } finally { | |
| restoreMaterialization(); | |
| } | |
| let generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(generation?.status).toBe("running"); | |
| expect(generation?.agent?.pending).toMatchObject({ | |
| kind: "system", | |
| key: "unrecoverable-pending", | |
| }); | |
| let conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| expect(conversation?.messages[0]?.updates).toContainEqual({ | |
| type: MessageUpdateType.Status, | |
| status: MessageUpdateStatus.Error, | |
| message: "historical terminal failure", | |
| }); | |
| expect( | |
| await store.finishUnrecoverable("interrupted", "Agent workspace is no longer available") | |
| ).toBe(true); | |
| generation = await collections.generations.findOne({ | |
| generationId: seeded.generationId, | |
| }); | |
| expect(generation).toMatchObject({ | |
| status: "interrupted", | |
| error: "Agent workspace is no longer available", | |
| agent: { controlsOpen: false }, | |
| }); | |
| expect(generation?.agent?.relay).toBeUndefined(); | |
| expect(generation?.agent?.control).toBeUndefined(); | |
| expect(generation?.agent?.pending).toBeUndefined(); | |
| expect(generation?.agent?.systemKeys).toContain("unrecoverable-pending"); | |
| expect( | |
| await collections.generationEvents.countDocuments({ generationId: seeded.generationId }) | |
| ).toBe(1); | |
| conversation = await collections.conversations.findOne({ _id: seeded.conversationId }); | |
| const message = conversation?.messages.find((item) => item.id === seeded.messageId); | |
| expect(message?.interrupted).toBe(true); | |
| expect(message?.updates).toContainEqual({ | |
| type: MessageUpdateType.Status, | |
| status: MessageUpdateStatus.Error, | |
| message: "historical terminal failure", | |
| }); | |
| }); | |
| }); | |
| function requireStore(store: AgentGenerationStore | null): AgentGenerationStore { | |
| if (!store) throw new Error("Expected to acquire the Agent generation relay lease"); | |
| return store; | |
| } | |
| function failAfterNextPendingMaterialization(store: AgentGenerationStore): () => void { | |
| const internals = store as unknown as { | |
| applyPendingToMessage( | |
| generation: Generation, | |
| pending: AgentPendingBatch, | |
| endSeq: number | |
| ): Promise<void>; | |
| }; | |
| const applyPendingToMessage = internals.applyPendingToMessage.bind(store); | |
| internals.applyPendingToMessage = async (generation, pending, endSeq) => { | |
| await applyPendingToMessage(generation, pending, endSeq); | |
| throw new Error("simulated process crash after Agent WAL materialization"); | |
| }; | |
| return () => { | |
| internals.applyPendingToMessage = applyPendingToMessage; | |
| }; | |
| } | |
| function sandboxIncarnation(workspace: Workspace): { | |
| sandboxId: string; | |
| sandboxGeneration: string; | |
| } { | |
| if (!workspace.sandbox.id || !workspace.sandbox.generation) { | |
| throw new Error("Expected a complete sandbox incarnation"); | |
| } | |
| return { | |
| sandboxId: workspace.sandbox.id, | |
| sandboxGeneration: workspace.sandbox.generation, | |
| }; | |
| } | |
| async function seedAgentRun() { | |
| const now = new Date(); | |
| const conversationId = new ObjectId(); | |
| const workspaceId = new ObjectId(); | |
| const messageId = randomUUID(); | |
| const generationId = randomUUID(); | |
| const sandboxId = `sandbox-${randomUUID()}`; | |
| const workspace: Workspace = { | |
| _id: workspaceId, | |
| conversationId, | |
| sessionId: "agent-store-owner", | |
| repo: { kind: "blank" }, | |
| flavor: "cpu-basic", | |
| model: "fake/model", | |
| thinkingLevel: "medium", | |
| policy: "auto", | |
| sandbox: { | |
| status: "ready", | |
| generation: `generation-${randomUUID()}`, | |
| id: sandboxId, | |
| namespace: "tester", | |
| baseUrl: `https://${sandboxId}--49983.hf.jobs`, | |
| nonce: "nonce", | |
| image: "node:22", | |
| flavor: "cpu-basic", | |
| createdAt: now, | |
| }, | |
| piSession: { id: "pi-session", lastSequence: 0 }, | |
| spend: { accruedUsd: 0 }, | |
| createdAt: now, | |
| updatedAt: now, | |
| }; | |
| await collections.conversations.insertOne({ | |
| _id: conversationId, | |
| sessionId: workspace.sessionId, | |
| mode: "agent", | |
| workspaceId, | |
| messages: [ | |
| { | |
| id: messageId, | |
| from: "assistant", | |
| content: "", | |
| updates: [], | |
| generationId, | |
| createdAt: now, | |
| updatedAt: now, | |
| }, | |
| ], | |
| createdAt: now, | |
| updatedAt: now, | |
| } as never); | |
| await collections.workspaces.insertOne(workspace); | |
| await collections.generations.insertOne({ | |
| _id: new ObjectId(), | |
| generationId, | |
| conversationId, | |
| messageId, | |
| sessionId: workspace.sessionId, | |
| mode: "agent", | |
| workspaceId, | |
| agent: { sourceSequence: 0 }, | |
| status: "running", | |
| seq: 0, | |
| startedAt: now, | |
| lastHeartbeatAt: now, | |
| createdAt: now, | |
| updatedAt: now, | |
| } satisfies Generation); | |
| return { conversationId, messageId, generationId, workspace }; | |
| } | |