diff --git a/lib/coding-agent/__tests__/getCodingAgentPRState.test.ts b/lib/coding-agent/__tests__/getCodingAgentPRState.test.ts index ab64107a..57872584 100644 --- a/lib/coding-agent/__tests__/getCodingAgentPRState.test.ts +++ b/lib/coding-agent/__tests__/getCodingAgentPRState.test.ts @@ -25,7 +25,6 @@ describe("getCodingAgentPRState", () => { it("returns parsed state when key exists", async () => { const state = { status: "pr_created", - snapshotId: "snap_abc", branch: "agent/fix-bug", repo: "recoupable/api", prs: [ diff --git a/lib/coding-agent/__tests__/handleCodingAgentCallback.test.ts b/lib/coding-agent/__tests__/handleCodingAgentCallback.test.ts index ebc0e519..e60da345 100644 --- a/lib/coding-agent/__tests__/handleCodingAgentCallback.test.ts +++ b/lib/coding-agent/__tests__/handleCodingAgentCallback.test.ts @@ -89,7 +89,7 @@ describe("handleCodingAgentCallback", () => { threadId: "slack:C123:1234567890.123456", status: "pr_created", branch: "agent/fix-bug-1234", - snapshotId: "snap_abc", + prs: [ { repo: "recoupable/recoup-api", @@ -113,7 +113,7 @@ describe("handleCodingAgentCallback", () => { threadId: "slack:C123:1234567890.123456", status: "pr_created", branch: "agent/fix-bug-1234", - snapshotId: "snap_abc", + stdout: "Created branch agent/fix-bug-1234\nModified 3 files in api/\n", prs: [ { @@ -199,7 +199,6 @@ describe("handleCodingAgentCallback", () => { const body = { threadId: "slack:C123:1234567890.123456", status: "updated", - snapshotId: "snap_new", }; const request = makeRequest(body); @@ -207,7 +206,7 @@ describe("handleCodingAgentCallback", () => { expect(response.status).toBe(200); expect(mockSetState).toHaveBeenCalledWith( - expect.objectContaining({ status: "pr_created", snapshotId: "snap_new" }), + expect.objectContaining({ status: "pr_created" }), ); expect(mockPost).toHaveBeenCalledWith(expect.objectContaining({ card: expect.anything() })); }); diff --git a/lib/coding-agent/__tests__/handleGitHubWebhook.test.ts b/lib/coding-agent/__tests__/handleGitHubWebhook.test.ts index 5e059f4e..47cf48cd 100644 --- a/lib/coding-agent/__tests__/handleGitHubWebhook.test.ts +++ b/lib/coding-agent/__tests__/handleGitHubWebhook.test.ts @@ -45,6 +45,12 @@ const BASE_PAYLOAD = { }, }; +/** + * + * @param body + * @param event + * @param signature + */ function makeRequest(body: unknown, event = "issue_comment", signature = "valid") { return { text: () => Promise.resolve(JSON.stringify(body)), @@ -144,7 +150,7 @@ describe("handleGitHubWebhook", () => { } as Response); mockGetPRState.mockResolvedValue({ status: "pr_created", - snapshotId: "snap_abc", + branch: "agent/fix-bug", repo: "recoupable/tasks", prs: [{ repo: "recoupable/tasks", number: 66, url: "url", baseBranch: "main" }], @@ -161,7 +167,7 @@ describe("handleGitHubWebhook", () => { expect(mockTriggerUpdatePR).toHaveBeenCalledWith( expect.objectContaining({ feedback: "make the button blue", - snapshotId: "snap_abc", + branch: "agent/fix-bug", repo: "recoupable/tasks", }), @@ -191,7 +197,7 @@ describe("handleGitHubWebhook", () => { }; mockGetPRState.mockResolvedValue({ status: "pr_created", - snapshotId: "snap_xyz", + branch: "feature/my-branch", repo: "recoupable/api", prs: [{ repo: "recoupable/api", number: 266, url: "url", baseBranch: "test" }], @@ -208,7 +214,7 @@ describe("handleGitHubWebhook", () => { expect(mockTriggerUpdatePR).toHaveBeenCalledWith( expect.objectContaining({ feedback: "fix the typo", - snapshotId: "snap_xyz", + branch: "feature/my-branch", repo: "recoupable/api", }), @@ -233,7 +239,7 @@ describe("handleGitHubWebhook", () => { }; const originalState = { status: "pr_created" as const, - snapshotId: "snap_abc", + branch: "agent/fix-bug", repo: "recoupable/tasks", prs: [{ repo: "recoupable/tasks", number: 99, url: "url", baseBranch: "main" }], diff --git a/lib/coding-agent/__tests__/handleMergeSuccess.test.ts b/lib/coding-agent/__tests__/handleMergeSuccess.test.ts index a5f99df9..546e9550 100644 --- a/lib/coding-agent/__tests__/handleMergeSuccess.test.ts +++ b/lib/coding-agent/__tests__/handleMergeSuccess.test.ts @@ -5,33 +5,21 @@ vi.mock("../prState", () => ({ deleteCodingAgentPRState: (...args: unknown[]) => mockDeletePRState(...args), })); -const mockUpsertAccountSnapshot = vi.fn(); -vi.mock("@/lib/supabase/account_snapshots/upsertAccountSnapshot", () => ({ - upsertAccountSnapshot: (...args: unknown[]) => mockUpsertAccountSnapshot(...args), -})); - const { handleMergeSuccess } = await import("../handleMergeSuccess"); beforeEach(() => { vi.clearAllMocks(); - mockUpsertAccountSnapshot.mockResolvedValue({ data: {}, error: null }); }); describe("handleMergeSuccess", () => { - it("deletes PR state and persists snapshot", async () => { + it("deletes PR state", async () => { await handleMergeSuccess({ status: "pr_created", branch: "agent/fix-bug", - snapshotId: "snap_abc123", prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }], }); expect(mockDeletePRState).toHaveBeenCalledWith("recoupable/api", "agent/fix-bug"); - expect(mockUpsertAccountSnapshot).toHaveBeenCalledWith({ - account_id: "04e3aba9-c130-4fb8-8b92-34e95d43e66b", - snapshot_id: "snap_abc123", - expires_at: expect.any(String), - }); }); it("deletes PR state for all repos when PRs span multiple repos", async () => { @@ -50,38 +38,6 @@ describe("handleMergeSuccess", () => { expect(mockDeletePRState).toHaveBeenCalledWith("recoupable/chat", "agent/fix-bug"); }); - it("skips snapshot persistence when snapshotId is not in state", async () => { - await handleMergeSuccess({ - status: "pr_created", - branch: "agent/fix-bug", - prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }], - }); - - expect(mockDeletePRState).toHaveBeenCalledWith("recoupable/api", "agent/fix-bug"); - expect(mockUpsertAccountSnapshot).not.toHaveBeenCalled(); - }); - - it("logs error but does not throw when snapshot persistence fails", async () => { - mockUpsertAccountSnapshot.mockResolvedValue({ - data: null, - error: { message: "db error", code: "500" }, - }); - const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - - await handleMergeSuccess({ - status: "pr_created", - branch: "agent/fix-bug", - snapshotId: "snap_abc123", - prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }], - }); - - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("failed to persist snapshot"), - expect.anything(), - ); - consoleSpy.mockRestore(); - }); - it("does not throw when deleteCodingAgentPRState throws", async () => { mockDeletePRState.mockRejectedValue(new Error("Redis connection failed")); const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); @@ -90,7 +46,6 @@ describe("handleMergeSuccess", () => { handleMergeSuccess({ status: "pr_created", branch: "agent/fix-bug", - snapshotId: "snap_abc123", prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }], }), ).resolves.toBeUndefined(); diff --git a/lib/coding-agent/__tests__/handlePRCreated.test.ts b/lib/coding-agent/__tests__/handlePRCreated.test.ts index 928e9e15..1b7dac69 100644 --- a/lib/coding-agent/__tests__/handlePRCreated.test.ts +++ b/lib/coding-agent/__tests__/handlePRCreated.test.ts @@ -30,7 +30,7 @@ describe("handlePRCreated", () => { threadId: "slack:C123:ts", status: "pr_created", branch: "agent/fix-bug", - snapshotId: "snap_abc", + prs: [ { repo: "recoupable/api", @@ -57,7 +57,7 @@ describe("handlePRCreated", () => { expect.objectContaining({ status: "pr_created", branch: "agent/fix-bug", - snapshotId: "snap_abc", + }), ); @@ -66,7 +66,7 @@ describe("handlePRCreated", () => { "agent/fix-bug", expect.objectContaining({ status: "pr_created", - snapshotId: "snap_abc", + branch: "agent/fix-bug", repo: "recoupable/api", }), diff --git a/lib/coding-agent/__tests__/handlers.test.ts b/lib/coding-agent/__tests__/handlers.test.ts index b21ebfe2..89cb57e0 100644 --- a/lib/coding-agent/__tests__/handlers.test.ts +++ b/lib/coding-agent/__tests__/handlers.test.ts @@ -87,7 +87,7 @@ describe("registerOnNewMention", () => { state: Promise.resolve({ status: "pr_created", prompt: "original prompt", - snapshotId: "snap_abc", + branch: "agent/fix-bug", prs: [ { @@ -113,7 +113,7 @@ describe("registerOnNewMention", () => { expect(mockTriggerUpdatePR).toHaveBeenCalledWith( expect.objectContaining({ feedback: "remove the Project Structure changes", - snapshotId: "snap_abc", + branch: "agent/fix-bug", repo: "recoupable/tasks", }), @@ -130,7 +130,6 @@ describe("registerOnNewMention", () => { const { getCodingAgentPRState } = await import("../prState"); vi.mocked(getCodingAgentPRState).mockResolvedValue({ status: "pr_created", - snapshotId: "snap_abc", branch: "agent/fix-bug", repo: "recoupable/api", prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }], @@ -159,7 +158,7 @@ describe("registerOnNewMention", () => { expect(mockTriggerUpdatePR).toHaveBeenCalledWith( expect.objectContaining({ feedback: "make the button blue", - snapshotId: "snap_abc", + branch: "agent/fix-bug", repo: "recoupable/api", }), diff --git a/lib/coding-agent/__tests__/onMergeAction.test.ts b/lib/coding-agent/__tests__/onMergeAction.test.ts index 56f25c32..5f84824e 100644 --- a/lib/coding-agent/__tests__/onMergeAction.test.ts +++ b/lib/coding-agent/__tests__/onMergeAction.test.ts @@ -10,23 +10,12 @@ vi.mock("../handleMergeSuccess", () => ({ handleMergeSuccess: (...args: unknown[]) => mockHandleMergeSuccess(...args), })); -const mockDeletePRState = vi.fn(); -vi.mock("../prState", () => ({ - deleteCodingAgentPRState: (...args: unknown[]) => mockDeletePRState(...args), -})); - -const mockUpsertAccountSnapshot = vi.fn(); -vi.mock("@/lib/supabase/account_snapshots/upsertAccountSnapshot", () => ({ - upsertAccountSnapshot: (...args: unknown[]) => mockUpsertAccountSnapshot(...args), -})); - const { registerOnMergeAction } = await import("../handlers/onMergeAction"); beforeEach(() => { vi.clearAllMocks(); process.env.GITHUB_TOKEN = "ghp_test"; mockHandleMergeSuccess.mockResolvedValue(undefined); - mockUpsertAccountSnapshot.mockResolvedValue({ data: {}, error: null }); }); /** @@ -73,7 +62,7 @@ describe("registerOnMergeAction", () => { status: "pr_created", prompt: "fix bug", branch: "agent/fix-bug", - snapshotId: "snap_abc123", + prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }], }), post: vi.fn(), @@ -85,7 +74,7 @@ describe("registerOnMergeAction", () => { expect(mockMergeGithubPR).toHaveBeenCalledWith("recoupable/api", 42, "ghp_test"); expect(mockThread.setState).toHaveBeenCalledWith({ status: "merged", prs: [] }); expect(mockHandleMergeSuccess).toHaveBeenCalledWith( - expect.objectContaining({ branch: "agent/fix-bug", snapshotId: "snap_abc123" }), + expect.objectContaining({ branch: "agent/fix-bug" }), ); expect(mockThread.post).toHaveBeenCalledWith("✅ recoupable/api#42 merged."); }); @@ -165,41 +154,4 @@ describe("registerOnMergeAction", () => { ); }); - it("merge button click triggers snapshot upsert in Supabase via handleMergeSuccess", async () => { - // Use the real handleMergeSuccess instead of the mock to verify the full chain - const { handleMergeSuccess: realHandleMergeSuccess } = - await vi.importActual("../handleMergeSuccess"); - mockHandleMergeSuccess.mockImplementation(realHandleMergeSuccess); - mockMergeGithubPR.mockResolvedValue({ ok: true }); - - const bot = createMockBot(); - registerOnMergeAction(bot); - const handler = bot.onAction.mock.calls[0][0]; - - const state = { - status: "pr_created" as const, - prompt: "fix bug", - branch: "agent/fix-bug", - snapshotId: "snap_abc123", - prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }], - }; - - const mockThread = { - state: Promise.resolve(state), - post: vi.fn(), - setState: vi.fn(), - }; - - await handler({ thread: mockThread, actionId: "merge_pr:recoupable/api#42" }); - - // Verify the full chain: merge → handleMergeSuccess → upsertAccountSnapshot - expect(mockMergeGithubPR).toHaveBeenCalledWith("recoupable/api", 42, "ghp_test"); - expect(mockDeletePRState).toHaveBeenCalledWith("recoupable/api", "agent/fix-bug"); - expect(mockUpsertAccountSnapshot).toHaveBeenCalledWith( - expect.objectContaining({ - snapshot_id: "snap_abc123", - expires_at: expect.any(String), - }), - ); - }); }); diff --git a/lib/coding-agent/__tests__/onSubscribedMessage.test.ts b/lib/coding-agent/__tests__/onSubscribedMessage.test.ts index 78a772e9..94ffdc25 100644 --- a/lib/coding-agent/__tests__/onSubscribedMessage.test.ts +++ b/lib/coding-agent/__tests__/onSubscribedMessage.test.ts @@ -42,7 +42,6 @@ describe("registerOnSubscribedMessage", () => { state: Promise.resolve({ status: "pr_created", prompt: "fix bug", - snapshotId: "snap_abc", branch: "agent/fix-bug", prs: [{ repo: "recoupable/api", number: 1, url: "url", baseBranch: "test" }], }), @@ -61,7 +60,6 @@ describe("registerOnSubscribedMessage", () => { expect(triggerUpdatePR).toHaveBeenCalledWith( expect.objectContaining({ feedback: "make the button blue", - snapshotId: "snap_abc", repo: "recoupable/api", }), ); diff --git a/lib/coding-agent/__tests__/resolvePRState.test.ts b/lib/coding-agent/__tests__/resolvePRState.test.ts index 308ef700..5c1a1c53 100644 --- a/lib/coding-agent/__tests__/resolvePRState.test.ts +++ b/lib/coding-agent/__tests__/resolvePRState.test.ts @@ -31,7 +31,7 @@ describe("resolvePRState", () => { status: "pr_created", prompt: "fix bug", branch: "agent/fix", - snapshotId: "snap_1", + prs: [{ repo: "recoupable/api", number: 1, url: "url", baseBranch: "test" }], }; const thread = createMockThread(threadState); @@ -45,7 +45,7 @@ describe("resolvePRState", () => { const thread = createMockThread(null); const prState = { status: "pr_created", - snapshotId: "snap_1", + branch: "agent/fix", repo: "recoupable/api", prs: [{ repo: "recoupable/api", number: 1, url: "url", baseBranch: "test" }], @@ -59,7 +59,7 @@ describe("resolvePRState", () => { status: "pr_created", prompt: "", branch: "agent/fix", - snapshotId: "snap_1", + prs: prState.prs, }); }); diff --git a/lib/coding-agent/__tests__/setCodingAgentPRState.test.ts b/lib/coding-agent/__tests__/setCodingAgentPRState.test.ts index b7141d1b..201a34ed 100644 --- a/lib/coding-agent/__tests__/setCodingAgentPRState.test.ts +++ b/lib/coding-agent/__tests__/setCodingAgentPRState.test.ts @@ -18,7 +18,6 @@ describe("setCodingAgentPRState", () => { it("stores serialized state in Redis", async () => { const state = { status: "pr_created" as const, - snapshotId: "snap_abc", branch: "agent/fix-bug", repo: "recoupable/api", prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }], diff --git a/lib/coding-agent/__tests__/validateCodingAgentCallback.test.ts b/lib/coding-agent/__tests__/validateCodingAgentCallback.test.ts index 525aa4ff..aeb721fe 100644 --- a/lib/coding-agent/__tests__/validateCodingAgentCallback.test.ts +++ b/lib/coding-agent/__tests__/validateCodingAgentCallback.test.ts @@ -9,7 +9,7 @@ describe("validateCodingAgentCallback", () => { threadId: "slack:C123:1234567890.123456", status: "pr_created", branch: "agent/fix-bug-1234", - snapshotId: "snap_abc123", + prs: [ { repo: "recoupable/recoup-api", @@ -44,11 +44,10 @@ describe("validateCodingAgentCallback", () => { expect(result).not.toBeInstanceOf(NextResponse); }); - it("accepts updated status with new snapshotId", () => { + it("accepts updated status", () => { const body = { threadId: "slack:C123:1234567890.123456", status: "updated", - snapshotId: "snap_new456", }; const result = validateCodingAgentCallback(body); expect(result).not.toBeInstanceOf(NextResponse); diff --git a/lib/coding-agent/handleCodingAgentCallback.ts b/lib/coding-agent/handleCodingAgentCallback.ts index e4cabda1..60ea47fb 100644 --- a/lib/coding-agent/handleCodingAgentCallback.ts +++ b/lib/coding-agent/handleCodingAgentCallback.ts @@ -65,7 +65,7 @@ export async function handleCodingAgentCallback(request: Request): Promise { try { @@ -14,21 +13,6 @@ export async function handleMergeSuccess(state: CodingAgentThreadState): Promise const repos = [...new Set(state.prs.map(pr => pr.repo))]; await Promise.all(repos.map(repo => deleteCodingAgentPRState(repo, state.branch!))); } - - if (state.snapshotId) { - const snapshotResult = await upsertAccountSnapshot({ - account_id: RECOUP_ORG_ID, - snapshot_id: state.snapshotId, - expires_at: new Date(Date.now() + SNAPSHOT_EXPIRY_MS).toISOString(), - }); - - if (snapshotResult.error) { - console.error( - `[coding-agent] failed to persist snapshot for ${RECOUP_ORG_ID}:`, - snapshotResult.error, - ); - } - } } catch (error) { console.error("[coding-agent] post-merge cleanup failed:", error); } diff --git a/lib/coding-agent/handlePRCreated.ts b/lib/coding-agent/handlePRCreated.ts index 52bee268..c1792f7d 100644 --- a/lib/coding-agent/handlePRCreated.ts +++ b/lib/coding-agent/handlePRCreated.ts @@ -21,14 +21,12 @@ export async function handlePRCreated(threadId: string, body: CodingAgentCallbac await thread.setState({ status: "pr_created", branch: body.branch, - snapshotId: body.snapshotId, prs, }); if (body.branch && prs.length) { await setCodingAgentPRState(prs[0].repo, body.branch, { status: "pr_created", - snapshotId: body.snapshotId, branch: body.branch, repo: prs[0].repo, prs, diff --git a/lib/coding-agent/handlers/handleFeedback.ts b/lib/coding-agent/handlers/handleFeedback.ts index bfecd3eb..c125bbea 100644 --- a/lib/coding-agent/handlers/handleFeedback.ts +++ b/lib/coding-agent/handlers/handleFeedback.ts @@ -22,11 +22,10 @@ export async function handleFeedback( return true; } - if (state?.status === "pr_created" && state.snapshotId && state.branch && state.prs?.length) { + if (state?.status === "pr_created" && state.branch && state.prs?.length) { await thread.setState({ status: "updating" }); await setCodingAgentPRState(state.prs[0].repo, state.branch, { status: "updating", - snapshotId: state.snapshotId, branch: state.branch, repo: state.prs[0].repo, prs: state.prs, @@ -34,7 +33,6 @@ export async function handleFeedback( const handle = await triggerUpdatePR({ feedback: messageText, - snapshotId: state.snapshotId, branch: state.branch, repo: state.prs[0].repo, callbackThreadId: thread.id, diff --git a/lib/coding-agent/prState/types.ts b/lib/coding-agent/prState/types.ts index 84fc8c16..d8913a66 100644 --- a/lib/coding-agent/prState/types.ts +++ b/lib/coding-agent/prState/types.ts @@ -2,7 +2,6 @@ import type { CodingAgentPR } from "../types"; export interface CodingAgentPRState { status: "running" | "pr_created" | "updating" | "merged" | "failed" | "no_changes"; - snapshotId?: string; branch: string; repo: string; prs?: CodingAgentPR[]; diff --git a/lib/coding-agent/resolvePRState.ts b/lib/coding-agent/resolvePRState.ts index 78f8bb2d..7c9d9b26 100644 --- a/lib/coding-agent/resolvePRState.ts +++ b/lib/coding-agent/resolvePRState.ts @@ -30,7 +30,6 @@ export async function resolvePRState( status: prState.status, prompt: "", branch: prState.branch, - snapshotId: prState.snapshotId, prs: prState.prs, }; } diff --git a/lib/coding-agent/types.ts b/lib/coding-agent/types.ts index 76bb6461..e2f05a68 100644 --- a/lib/coding-agent/types.ts +++ b/lib/coding-agent/types.ts @@ -9,7 +9,6 @@ export interface CodingAgentThreadState { /** Platform-agnostic thread identifier (Slack or WhatsApp thread ID). */ threadId?: string; branch?: string; - snapshotId?: string; prs?: CodingAgentPR[]; } diff --git a/lib/coding-agent/validateCodingAgentCallback.ts b/lib/coding-agent/validateCodingAgentCallback.ts index 57989f04..f3878162 100644 --- a/lib/coding-agent/validateCodingAgentCallback.ts +++ b/lib/coding-agent/validateCodingAgentCallback.ts @@ -13,7 +13,6 @@ export const codingAgentCallbackSchema = z.object({ threadId: z.string({ message: "threadId is required" }).min(1, "threadId cannot be empty"), status: z.enum(["pr_created", "no_changes", "failed", "updated"]), branch: z.string().optional(), - snapshotId: z.string().optional(), prs: z.array(codingAgentPRSchema).optional(), message: z.string().optional(), stdout: z.string().optional(), diff --git a/lib/sandbox/__tests__/createSandbox.test.ts b/lib/sandbox/__tests__/createSandbox.test.ts index 1530afad..02ca4074 100644 --- a/lib/sandbox/__tests__/createSandbox.test.ts +++ b/lib/sandbox/__tests__/createSandbox.test.ts @@ -4,7 +4,7 @@ import { createSandbox } from "../createSandbox"; import { Sandbox } from "@vercel/sandbox"; const mockSandbox = { - sandboxId: "sbx_test123", + name: "sbx_test123", status: "running", timeout: 1800000, createdAt: new Date("2024-01-01T00:00:00Z"), @@ -38,12 +38,14 @@ describe("createSandbox", () => { }); }); - it("creates sandbox from snapshot when source is provided", async () => { - await createSandbox({ source: { type: "snapshot", snapshotId: "snap_abc123" } }); + it("creates sandbox with name when provided", async () => { + await createSandbox({ name: "acc_123" }); expect(Sandbox.create).toHaveBeenCalledWith({ - source: { type: "snapshot", snapshotId: "snap_abc123" }, + resources: { vcpus: 4 }, timeout: 1800000, + runtime: "node22", + name: "acc_123", }); }); diff --git a/lib/sandbox/__tests__/createSandboxFromSnapshot.test.ts b/lib/sandbox/__tests__/createSandboxFromSnapshot.test.ts index 887d136c..91aec1f6 100644 --- a/lib/sandbox/__tests__/createSandboxFromSnapshot.test.ts +++ b/lib/sandbox/__tests__/createSandboxFromSnapshot.test.ts @@ -3,7 +3,6 @@ import type { Sandbox } from "@vercel/sandbox"; import { createSandboxFromSnapshot } from "../createSandboxFromSnapshot"; -const mockSelectAccountSnapshots = vi.fn(); const mockInsertAccountSandbox = vi.fn(); const mockCreateSandbox = vi.fn(); @@ -11,17 +10,13 @@ vi.mock("@/lib/sandbox/createSandbox", () => ({ createSandbox: (...args: unknown[]) => mockCreateSandbox(...args), })); -vi.mock("@/lib/supabase/account_snapshots/selectAccountSnapshots", () => ({ - selectAccountSnapshots: (...args: unknown[]) => mockSelectAccountSnapshots(...args), -})); - vi.mock("@/lib/supabase/account_sandboxes/insertAccountSandbox", () => ({ insertAccountSandbox: (...args: unknown[]) => mockInsertAccountSandbox(...args), })); describe("createSandboxFromSnapshot", () => { const mockSandbox = { - sandboxId: "sbx_new", + name: "sbx_new", status: "running", runCommand: vi.fn(), } as unknown as Sandbox; @@ -43,29 +38,13 @@ describe("createSandboxFromSnapshot", () => { }); }); - it("creates from snapshot when available", async () => { - mockSelectAccountSnapshots.mockResolvedValue([ - { snapshot_id: "snap_abc", account_id: "acc_1" }, - ]); - - await createSandboxFromSnapshot("acc_1"); - - expect(mockCreateSandbox).toHaveBeenCalledWith({ - source: { type: "snapshot", snapshotId: "snap_abc" }, - }); - }); - - it("creates fresh sandbox when no snapshot exists", async () => { - mockSelectAccountSnapshots.mockResolvedValue([]); - + it("creates sandbox with name set to accountId", async () => { await createSandboxFromSnapshot("acc_1"); - expect(mockCreateSandbox).toHaveBeenCalledWith({}); + expect(mockCreateSandbox).toHaveBeenCalledWith({ name: "acc_1" }); }); it("inserts account_sandbox record", async () => { - mockSelectAccountSnapshots.mockResolvedValue([]); - await createSandboxFromSnapshot("acc_1"); expect(mockInsertAccountSandbox).toHaveBeenCalledWith({ @@ -74,19 +53,7 @@ describe("createSandboxFromSnapshot", () => { }); }); - it("returns { sandbox, fromSnapshot: true } when snapshot exists", async () => { - mockSelectAccountSnapshots.mockResolvedValue([ - { snapshot_id: "snap_abc", account_id: "acc_1" }, - ]); - - const result = await createSandboxFromSnapshot("acc_1"); - - expect(result).toEqual({ sandbox: mockSandbox, fromSnapshot: true }); - }); - - it("returns { sandbox, fromSnapshot: false } when no snapshot", async () => { - mockSelectAccountSnapshots.mockResolvedValue([]); - + it("returns { sandbox, fromSnapshot: false }", async () => { const result = await createSandboxFromSnapshot("acc_1"); expect(result).toEqual({ sandbox: mockSandbox, fromSnapshot: false }); diff --git a/lib/sandbox/__tests__/getActiveSandbox.test.ts b/lib/sandbox/__tests__/getActiveSandbox.test.ts index 58133707..0f6ca2e6 100644 --- a/lib/sandbox/__tests__/getActiveSandbox.test.ts +++ b/lib/sandbox/__tests__/getActiveSandbox.test.ts @@ -24,7 +24,7 @@ describe("getActiveSandbox", () => { mockSelectAccountSandboxes.mockResolvedValue([{ sandbox_id: "sbx_123", account_id: "acc_1" }]); const mockSandbox = { - sandboxId: "sbx_123", + name: "sbx_123", status: "running", runCommand: vi.fn(), }; @@ -35,7 +35,7 @@ describe("getActiveSandbox", () => { expect(mockSelectAccountSandboxes).toHaveBeenCalledWith({ accountIds: ["acc_1"], }); - expect(Sandbox.get).toHaveBeenCalledWith({ sandboxId: "sbx_123" }); + expect(Sandbox.get).toHaveBeenCalledWith({ name: "sbx_123" }); expect(result).toBe(mockSandbox); }); @@ -54,7 +54,7 @@ describe("getActiveSandbox", () => { ]); const mockSandbox = { - sandboxId: "sbx_stopped", + name: "sbx_stopped", status: "stopped", }; vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as unknown as Sandbox); diff --git a/lib/sandbox/__tests__/getOrCreateSandbox.test.ts b/lib/sandbox/__tests__/getOrCreateSandbox.test.ts index 56e641bc..063b8792 100644 --- a/lib/sandbox/__tests__/getOrCreateSandbox.test.ts +++ b/lib/sandbox/__tests__/getOrCreateSandbox.test.ts @@ -21,7 +21,7 @@ describe("getOrCreateSandbox", () => { it("returns existing sandbox with created=false and fromSnapshot=true", async () => { const mockSandbox = { - sandboxId: "sbx_existing", + name: "sbx_existing", status: "running", } as unknown as Sandbox; @@ -40,7 +40,7 @@ describe("getOrCreateSandbox", () => { it("creates new sandbox from snapshot with created=true, fromSnapshot=true", async () => { const mockSandbox = { - sandboxId: "sbx_new", + name: "sbx_new", status: "running", } as unknown as Sandbox; @@ -63,7 +63,7 @@ describe("getOrCreateSandbox", () => { it("creates fresh sandbox with created=true, fromSnapshot=false", async () => { const mockSandbox = { - sandboxId: "sbx_fresh", + name: "sbx_fresh", status: "running", } as unknown as Sandbox; diff --git a/lib/sandbox/__tests__/getSandboxStatus.test.ts b/lib/sandbox/__tests__/getSandboxStatus.test.ts index 3f84615a..94903236 100644 --- a/lib/sandbox/__tests__/getSandboxStatus.test.ts +++ b/lib/sandbox/__tests__/getSandboxStatus.test.ts @@ -16,7 +16,7 @@ describe("getSandboxStatus", () => { it("returns sandbox status when sandbox exists", async () => { const mockSandbox = { - sandboxId: "sbx_123", + name: "sbx_123", status: "running", timeout: 600000, createdAt: new Date("2024-01-01T00:00:00.000Z"), @@ -25,7 +25,7 @@ describe("getSandboxStatus", () => { const result = await getSandboxStatus("sbx_123"); - expect(Sandbox.get).toHaveBeenCalledWith({ sandboxId: "sbx_123" }); + expect(Sandbox.get).toHaveBeenCalledWith({ name: "sbx_123" }); expect(result).toEqual({ sandboxId: "sbx_123", sandboxStatus: "running", @@ -52,7 +52,7 @@ describe("getSandboxStatus", () => { it("handles stopped sandbox status", async () => { const mockSandbox = { - sandboxId: "sbx_stopped", + name: "sbx_stopped", status: "stopped", timeout: 0, createdAt: new Date("2024-01-01T00:00:00.000Z"), @@ -71,7 +71,7 @@ describe("getSandboxStatus", () => { it("handles pending sandbox status", async () => { const mockSandbox = { - sandboxId: "sbx_pending", + name: "sbx_pending", status: "pending", timeout: 600000, createdAt: new Date("2024-01-01T00:00:00.000Z"), diff --git a/lib/sandbox/__tests__/getSandboxesHandler.test.ts b/lib/sandbox/__tests__/getSandboxesHandler.test.ts index 6c6121f1..e74ead27 100644 --- a/lib/sandbox/__tests__/getSandboxesHandler.test.ts +++ b/lib/sandbox/__tests__/getSandboxesHandler.test.ts @@ -75,7 +75,6 @@ describe("getSandboxesHandler", () => { expect(json).toEqual({ status: "success", sandboxes: [], - snapshot_id: null, github_repo: null, filetree: null, }); @@ -115,7 +114,6 @@ describe("getSandboxesHandler", () => { createdAt: "2024-01-01T00:00:00.000Z", }, ], - snapshot_id: null, github_repo: null, filetree: null, }); @@ -259,8 +257,8 @@ describe("getSandboxesHandler", () => { expect(maxStartIndex).toBeLessThan(minEndIndex); }); - describe("snapshot_id and github_repo fields", () => { - it("returns snapshot_id and github_repo when account has a snapshot", async () => { + describe("github_repo field", () => { + it("returns github_repo when account has a snapshot", async () => { vi.mocked(validateGetSandboxesRequest).mockResolvedValue({ accountIds: ["acc_123"], }); @@ -280,11 +278,10 @@ describe("getSandboxesHandler", () => { expect(response.status).toBe(200); const json = await response.json(); - expect(json.snapshot_id).toBe("snap_abc123"); expect(json.github_repo).toBe("https://github.com/user/repo"); }); - it("returns null for snapshot_id and github_repo when account has no snapshot", async () => { + it("returns null for github_repo when account has no snapshot", async () => { vi.mocked(validateGetSandboxesRequest).mockResolvedValue({ accountIds: ["acc_123"], }); @@ -296,7 +293,6 @@ describe("getSandboxesHandler", () => { expect(response.status).toBe(200); const json = await response.json(); - expect(json.snapshot_id).toBeNull(); expect(json.github_repo).toBeNull(); }); @@ -320,7 +316,6 @@ describe("getSandboxesHandler", () => { expect(response.status).toBe(200); const json = await response.json(); - expect(json.snapshot_id).toBe("snap_abc123"); expect(json.github_repo).toBeNull(); }); @@ -344,7 +339,6 @@ describe("getSandboxesHandler", () => { expect(response.status).toBe(200); const json = await response.json(); - expect(json.snapshot_id).toBe("snap_org_abc"); expect(json.github_repo).toBe("https://github.com/org/repo"); expect(selectAccountSnapshots).toHaveBeenCalledWith("org_123"); }); diff --git a/lib/sandbox/__tests__/updateSnapshotPatchHandler.test.ts b/lib/sandbox/__tests__/updateSnapshotPatchHandler.test.ts index 39af95ab..3b44e389 100644 --- a/lib/sandbox/__tests__/updateSnapshotPatchHandler.test.ts +++ b/lib/sandbox/__tests__/updateSnapshotPatchHandler.test.ts @@ -46,102 +46,9 @@ describe("updateSnapshotPatchHandler", () => { expect(response.status).toBe(401); }); - it("returns 200 with full row on successful upsert", async () => { + it("upserts with github_repo when github_repo is provided", async () => { vi.mocked(validateSnapshotPatchBody).mockResolvedValue({ accountId: "acc_123", - orgId: null, - authToken: "token", - snapshotId: "snap_abc123", - }); - vi.mocked(upsertAccountSnapshot).mockResolvedValue({ - data: { - account_id: "acc_123", - snapshot_id: "snap_abc123", - expires_at: "2025-01-01T00:00:00.000Z", - created_at: "2024-01-01T00:00:00.000Z", - github_repo: null, - }, - error: null, - }); - - const request = createMockRequest(); - const response = await updateSnapshotPatchHandler(request); - - expect(response.status).toBe(200); - const json = await response.json(); - expect(json).toEqual({ - account_id: "acc_123", - snapshot_id: "snap_abc123", - expires_at: "2025-01-01T00:00:00.000Z", - created_at: "2024-01-01T00:00:00.000Z", - github_repo: null, - }); - }); - - it("calls upsertAccountSnapshot with correct accountId and snapshotId", async () => { - vi.mocked(validateSnapshotPatchBody).mockResolvedValue({ - accountId: "acc_456", - orgId: "org_789", - authToken: "token", - snapshotId: "snap_xyz", - }); - vi.mocked(upsertAccountSnapshot).mockResolvedValue({ - data: { - account_id: "acc_456", - snapshot_id: "snap_xyz", - expires_at: "2025-01-01T00:00:00.000Z", - created_at: "2024-01-01T00:00:00.000Z", - }, - error: null, - }); - - const request = createMockRequest(); - await updateSnapshotPatchHandler(request); - - expect(upsertAccountSnapshot).toHaveBeenCalledWith( - expect.objectContaining({ - account_id: "acc_456", - snapshot_id: "snap_xyz", - expires_at: expect.any(String), - }), - ); - }); - - it("forwards githubRepo to upsertAccountSnapshot when provided", async () => { - vi.mocked(validateSnapshotPatchBody).mockResolvedValue({ - accountId: "acc_123", - orgId: null, - authToken: "token", - snapshotId: "snap_abc123", - githubRepo: "https://github.com/org/repo", - }); - vi.mocked(upsertAccountSnapshot).mockResolvedValue({ - data: { - account_id: "acc_123", - snapshot_id: "snap_abc123", - expires_at: "2025-01-01T00:00:00.000Z", - created_at: "2024-01-01T00:00:00.000Z", - }, - error: null, - }); - - const request = createMockRequest(); - await updateSnapshotPatchHandler(request); - - expect(upsertAccountSnapshot).toHaveBeenCalledWith( - expect.objectContaining({ - account_id: "acc_123", - snapshot_id: "snap_abc123", - github_repo: "https://github.com/org/repo", - }), - ); - }); - - it("upserts with github_repo when only github_repo is provided", async () => { - vi.mocked(validateSnapshotPatchBody).mockResolvedValue({ - accountId: "acc_123", - orgId: null, - authToken: "token", githubRepo: "https://github.com/org/repo", }); vi.mocked(upsertAccountSnapshot).mockResolvedValue({ @@ -170,8 +77,6 @@ describe("updateSnapshotPatchHandler", () => { it("returns current row when no fields to update", async () => { vi.mocked(validateSnapshotPatchBody).mockResolvedValue({ accountId: "acc_123", - orgId: null, - authToken: "token", }); vi.mocked(selectAccountSnapshots).mockResolvedValue([ { @@ -202,9 +107,7 @@ describe("updateSnapshotPatchHandler", () => { it("returns 400 when upsertAccountSnapshot returns error", async () => { vi.mocked(validateSnapshotPatchBody).mockResolvedValue({ accountId: "acc_123", - orgId: null, - authToken: "token", - snapshotId: "snap_abc123", + githubRepo: "https://github.com/org/repo", }); vi.mocked(upsertAccountSnapshot).mockResolvedValue({ data: null, @@ -218,16 +121,14 @@ describe("updateSnapshotPatchHandler", () => { const json = await response.json(); expect(json).toEqual({ status: "error", - error: "Failed to update snapshot", + error: "Failed to update sandbox metadata", }); }); it("returns 400 when upsertAccountSnapshot throws error", async () => { vi.mocked(validateSnapshotPatchBody).mockResolvedValue({ accountId: "acc_123", - orgId: null, - authToken: "token", - snapshotId: "snap_abc123", + githubRepo: "https://github.com/org/repo", }); vi.mocked(upsertAccountSnapshot).mockRejectedValue(new Error("Unexpected error")); diff --git a/lib/sandbox/__tests__/validateSnapshotPatchBody.test.ts b/lib/sandbox/__tests__/validateSnapshotPatchBody.test.ts index e1b2bfea..de4a795f 100644 --- a/lib/sandbox/__tests__/validateSnapshotPatchBody.test.ts +++ b/lib/sandbox/__tests__/validateSnapshotPatchBody.test.ts @@ -30,7 +30,7 @@ describe("validateSnapshotPatchBody", () => { }); it("returns 401 when auth fails", async () => { - vi.mocked(safeParseJson).mockResolvedValue({ snapshotId: "snap_abc123" }); + vi.mocked(safeParseJson).mockResolvedValue({ github_repo: "https://github.com/org/repo" }); vi.mocked(validateAuthContext).mockResolvedValue( NextResponse.json({ error: "Unauthorized" }, { status: 401 }), ); @@ -42,23 +42,6 @@ describe("validateSnapshotPatchBody", () => { expect((result as NextResponse).status).toBe(401); }); - it("returns validated body when snapshotId is provided", async () => { - vi.mocked(safeParseJson).mockResolvedValue({ snapshotId: "snap_abc123" }); - vi.mocked(validateAuthContext).mockResolvedValue({ - accountId: "acc_123", - orgId: "org_456", - authToken: "token", - }); - - const request = createMockRequest(); - const result = await validateSnapshotPatchBody(request); - - expect(result).toEqual({ - accountId: "acc_123", - snapshotId: "snap_abc123", - }); - }); - it("returns validated body when only github_repo is provided", async () => { vi.mocked(safeParseJson).mockResolvedValue({ github_repo: "https://github.com/org/repo", @@ -78,7 +61,7 @@ describe("validateSnapshotPatchBody", () => { }); }); - it("returns validated body when neither snapshotId nor github_repo is provided", async () => { + it("returns validated body when no github_repo is provided", async () => { vi.mocked(safeParseJson).mockResolvedValue({}); vi.mocked(validateAuthContext).mockResolvedValue({ accountId: "acc_123", @@ -94,20 +77,10 @@ describe("validateSnapshotPatchBody", () => { }); }); - it("returns error response when snapshotId is empty string", async () => { - vi.mocked(safeParseJson).mockResolvedValue({ snapshotId: "" }); - - const request = createMockRequest(); - const result = await validateSnapshotPatchBody(request); - - expect(result).toBeInstanceOf(NextResponse); - expect((result as NextResponse).status).toBe(400); - }); - it("passes account_id to validateAuthContext when provided", async () => { const targetAccountId = "550e8400-e29b-41d4-a716-446655440000"; vi.mocked(safeParseJson).mockResolvedValue({ - snapshotId: "snap_abc123", + github_repo: "https://github.com/org/repo", account_id: targetAccountId, }); vi.mocked(validateAuthContext).mockResolvedValue({ @@ -124,14 +97,14 @@ describe("validateSnapshotPatchBody", () => { }); expect(result).toEqual({ accountId: targetAccountId, - snapshotId: "snap_abc123", + githubRepo: "https://github.com/org/repo", }); }); it("returns 403 when account_id override is denied", async () => { const unauthorizedAccountId = "660e8400-e29b-41d4-a716-446655440001"; vi.mocked(safeParseJson).mockResolvedValue({ - snapshotId: "snap_abc123", + github_repo: "https://github.com/org/repo", account_id: unauthorizedAccountId, }); vi.mocked(validateAuthContext).mockResolvedValue( @@ -145,30 +118,9 @@ describe("validateSnapshotPatchBody", () => { expect((result as NextResponse).status).toBe(403); }); - it("returns validated body with github_repo when provided", async () => { - vi.mocked(safeParseJson).mockResolvedValue({ - snapshotId: "snap_abc123", - github_repo: "https://github.com/org/repo", - }); - vi.mocked(validateAuthContext).mockResolvedValue({ - accountId: "acc_123", - orgId: "org_456", - authToken: "token", - }); - - const request = createMockRequest(); - const result = await validateSnapshotPatchBody(request); - - expect(result).toEqual({ - accountId: "acc_123", - snapshotId: "snap_abc123", - githubRepo: "https://github.com/org/repo", - }); - }); - it("returns error when account_id is not a valid UUID", async () => { vi.mocked(safeParseJson).mockResolvedValue({ - snapshotId: "snap_abc123", + github_repo: "https://github.com/org/repo", account_id: "not-a-uuid", }); diff --git a/lib/sandbox/createSandbox.ts b/lib/sandbox/createSandbox.ts index 16d0da3f..632ae2a0 100644 --- a/lib/sandbox/createSandbox.ts +++ b/lib/sandbox/createSandbox.ts @@ -2,7 +2,7 @@ import ms from "ms"; import { Sandbox } from "@vercel/sandbox"; export interface SandboxCreatedResponse { - sandboxId: Sandbox["sandboxId"]; + sandboxId: string; sandboxStatus: Sandbox["status"]; timeout: Sandbox["timeout"]; createdAt: string; @@ -26,35 +26,26 @@ const DEFAULT_RUNTIME = "node22"; * The sandbox is left running so that prompts can be executed via the prompt_sandbox tool. * Accepts the same parameters as Sandbox.create from @vercel/sandbox. * - * @param params - Sandbox creation parameters (source, timeout, resources, runtime, ports) + * Uses the name parameter for persistent sandbox identification when provided. + * + * @param params - Sandbox creation parameters (name, timeout, resources, runtime, ports) * @returns The sandbox creation response * @throws Error if sandbox creation fails */ export async function createSandbox( params: CreateSandboxParams = {}, ): Promise { - const hasSnapshotSource = - params.source && "type" in params.source && params.source.type === "snapshot"; - - // Pass params directly to SDK - it handles all the type variants - const sandbox = await Sandbox.create( - hasSnapshotSource - ? { - ...params, - timeout: params.timeout ?? DEFAULT_TIMEOUT, - } - : { - resources: { vcpus: DEFAULT_VCPUS }, - timeout: params.timeout ?? DEFAULT_TIMEOUT, - runtime: DEFAULT_RUNTIME, - ...params, - }, - ); + const sandbox = await Sandbox.create({ + resources: { vcpus: DEFAULT_VCPUS }, + timeout: DEFAULT_TIMEOUT, + runtime: DEFAULT_RUNTIME, + ...params, + }); return { sandbox, response: { - sandboxId: sandbox.sandboxId, + sandboxId: sandbox.name, sandboxStatus: sandbox.status, timeout: sandbox.timeout, createdAt: sandbox.createdAt.toISOString(), diff --git a/lib/sandbox/createSandboxFromSnapshot.ts b/lib/sandbox/createSandboxFromSnapshot.ts index 98310a1f..3cf9c9b0 100644 --- a/lib/sandbox/createSandboxFromSnapshot.ts +++ b/lib/sandbox/createSandboxFromSnapshot.ts @@ -1,6 +1,5 @@ import type { Sandbox } from "@vercel/sandbox"; import { createSandbox } from "@/lib/sandbox/createSandbox"; -import { selectAccountSnapshots } from "@/lib/supabase/account_snapshots/selectAccountSnapshots"; import { insertAccountSandbox } from "@/lib/supabase/account_sandboxes/insertAccountSandbox"; export interface CreateSandboxFromSnapshotResult { @@ -9,26 +8,24 @@ export interface CreateSandboxFromSnapshotResult { } /** - * Creates a new sandbox from the account's latest snapshot (or fresh if none) + * Creates a new sandbox for an account using name-based persistence * and records it in the database. * + * Uses the Vercel Sandbox names feature with accountId as the name, + * replacing the previous snapshotId-based approach. + * * @param accountId - The account ID to create a sandbox for - * @returns The created Sandbox instance and whether it was created from a snapshot + * @returns The created Sandbox instance (fromSnapshot is always false with name-based approach) */ export async function createSandboxFromSnapshot( accountId: string, ): Promise { - const snapshots = await selectAccountSnapshots(accountId); - const snapshotId = snapshots[0]?.snapshot_id; - - const { sandbox, response } = await createSandbox( - snapshotId ? { source: { type: "snapshot", snapshotId } } : {}, - ); + const { sandbox, response } = await createSandbox({ name: accountId }); await insertAccountSandbox({ account_id: accountId, sandbox_id: response.sandboxId, }); - return { sandbox, fromSnapshot: !!snapshotId }; + return { sandbox, fromSnapshot: false }; } diff --git a/lib/sandbox/getActiveSandbox.ts b/lib/sandbox/getActiveSandbox.ts index faa220ad..f47dec4f 100644 --- a/lib/sandbox/getActiveSandbox.ts +++ b/lib/sandbox/getActiveSandbox.ts @@ -19,7 +19,7 @@ export async function getActiveSandbox(accountId: string): Promise { try { - const sandbox = await Sandbox.get({ sandboxId }); + const sandbox = await Sandbox.get({ name: sandboxId }); return { - sandboxId: sandbox.sandboxId, + sandboxId: sandbox.name, sandboxStatus: sandbox.status, timeout: sandbox.timeout, createdAt: sandbox.createdAt.toISOString(), diff --git a/lib/sandbox/getSandboxesHandler.ts b/lib/sandbox/getSandboxesHandler.ts index 618e6e3b..37f36b33 100644 --- a/lib/sandbox/getSandboxesHandler.ts +++ b/lib/sandbox/getSandboxesHandler.ts @@ -36,8 +36,7 @@ export async function getSandboxesHandler(request: NextRequest): Promise { const { accountId, prompt } = input; - // Get account's most recent snapshot if available - const accountSnapshots = await selectAccountSnapshots(accountId); - const snapshotId = accountSnapshots[0]?.snapshot_id; - - // Create sandbox (from snapshot if valid, otherwise fresh) - const { response: result } = await createSandbox( - snapshotId ? { source: { type: "snapshot", snapshotId } } : {}, - ); + // Create sandbox using name-based persistence (replaces snapshotId approach) + const { response: result } = await createSandbox({ name: accountId }); await insertAccountSandbox({ account_id: accountId, diff --git a/lib/sandbox/updateSnapshotPatchHandler.ts b/lib/sandbox/updateSnapshotPatchHandler.ts index 4d5da44d..5a2d08b4 100644 --- a/lib/sandbox/updateSnapshotPatchHandler.ts +++ b/lib/sandbox/updateSnapshotPatchHandler.ts @@ -3,13 +3,12 @@ import { NextResponse } from "next/server"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; import { validateSnapshotPatchBody } from "@/lib/sandbox/validateSnapshotPatchBody"; import { upsertAccountSnapshot } from "@/lib/supabase/account_snapshots/upsertAccountSnapshot"; -import { SNAPSHOT_EXPIRY_MS } from "@/lib/const"; import { selectAccountSnapshots } from "@/lib/supabase/account_snapshots/selectAccountSnapshots"; /** - * Handler for PATCH /api/sandboxes/snapshot. + * Handler for PATCH /api/sandboxes. * - * Updates the snapshot ID and/or github_repo for an account. + * Updates the github_repo for an account's sandbox metadata. * Requires authentication via x-api-key header or Authorization Bearer token. * * @param request - The request object @@ -21,7 +20,7 @@ export async function updateSnapshotPatchHandler(request: NextRequest): Promise< return validated; } - if (!validated.snapshotId && !validated.githubRepo) { + if (!validated.githubRepo) { const rows = await selectAccountSnapshots(validated.accountId); return NextResponse.json(rows[0] ?? null, { status: 200, headers: getCorsHeaders() }); } @@ -29,23 +28,19 @@ export async function updateSnapshotPatchHandler(request: NextRequest): Promise< try { const result = await upsertAccountSnapshot({ account_id: validated.accountId, - ...(validated.snapshotId && { - snapshot_id: validated.snapshotId, - expires_at: new Date(Date.now() + SNAPSHOT_EXPIRY_MS).toISOString(), - }), - ...(validated.githubRepo && { github_repo: validated.githubRepo }), + github_repo: validated.githubRepo, }); if (result.error || !result.data) { return NextResponse.json( - { status: "error", error: "Failed to update snapshot" }, + { status: "error", error: "Failed to update sandbox metadata" }, { status: 400, headers: getCorsHeaders() }, ); } return NextResponse.json(result.data, { status: 200, headers: getCorsHeaders() }); } catch (error) { - const message = error instanceof Error ? error.message : "Failed to update snapshot"; + const message = error instanceof Error ? error.message : "Failed to update sandbox metadata"; return NextResponse.json( { status: "error", error: message }, { status: 400, headers: getCorsHeaders() }, diff --git a/lib/sandbox/validateSnapshotPatchBody.ts b/lib/sandbox/validateSnapshotPatchBody.ts index c31c75aa..4d152650 100644 --- a/lib/sandbox/validateSnapshotPatchBody.ts +++ b/lib/sandbox/validateSnapshotPatchBody.ts @@ -6,7 +6,6 @@ import { safeParseJson } from "@/lib/networking/safeParseJson"; import { z } from "zod"; export const snapshotPatchBodySchema = z.object({ - snapshotId: z.string().min(1, "snapshotId cannot be empty").optional(), account_id: z.string().uuid("account_id must be a valid UUID").optional(), github_repo: z.string().url("github_repo must be a valid URL").optional(), }); @@ -14,14 +13,12 @@ export const snapshotPatchBodySchema = z.object({ export type SnapshotPatchBody = { /** The account ID to update */ accountId: string; - /** The snapshot ID to set */ - snapshotId?: string; /** The GitHub repository URL to associate with the sandbox */ githubRepo?: string; }; /** - * Validates auth and request body for PATCH /api/sandboxes/snapshot. + * Validates auth and request body for PATCH /api/sandboxes. * Handles authentication via x-api-key or Authorization bearer token, * body validation, and optional account_id override for organization API keys. * @@ -49,7 +46,7 @@ export async function validateSnapshotPatchBody( ); } - const { snapshotId, account_id: targetAccountId, github_repo: githubRepo } = result.data; + const { account_id: targetAccountId, github_repo: githubRepo } = result.data; const authResult = await validateAuthContext(request, { accountId: targetAccountId, @@ -61,7 +58,6 @@ export async function validateSnapshotPatchBody( return { accountId: authResult.accountId, - ...(snapshotId && { snapshotId }), ...(githubRepo && { githubRepo }), }; } diff --git a/lib/trigger/triggerUpdatePR.ts b/lib/trigger/triggerUpdatePR.ts index f989b576..cfbd398e 100644 --- a/lib/trigger/triggerUpdatePR.ts +++ b/lib/trigger/triggerUpdatePR.ts @@ -2,17 +2,16 @@ import { tasks } from "@trigger.dev/sdk"; type UpdatePRPayload = { feedback: string; - snapshotId: string; branch: string; repo: string; callbackThreadId: string; }; /** - * Triggers the update-pr task to resume a sandbox from snapshot, + * Triggers the update-pr task to resume a named sandbox, * apply feedback via the AI agent, and push updates to existing PRs. * - * @param payload - The task payload with feedback, snapshot, branch, and PR info + * @param payload - The task payload with feedback, branch, and PR info * @returns The task handle with runId */ export async function triggerUpdatePR(payload: UpdatePRPayload) {