import { describe, expect, it } from "vitest"; import { providerResourceDispositionForTerminalRun, resolveNativeSandboxLifecycle, resolveReusableSandboxLifecycle, } from "../services/heartbeat.js"; const reusableSandbox = { kind: "remote" as const, transport: "sandbox", reusableLeaseConfigured: true, effectiveCapabilities: { reusableLeases: true }, }; describe("paperclip_runner sandbox lifecycle", () => { it("keeps a warm reusable sandbox running", () => { expect( resolveNativeSandboxLifecycle({ adapterType: "paperclip_runner", lifecyclePolicy: { mode: "warm", idleTimeoutMs: 300_000 }, target: reusableSandbox, }), ).toEqual({ runnerProcess: "warm", sandboxResource: "keep_running", failoverBackup: "verified", }); }); it("keeps the same warm reusable sandbox for a legacy adapter", () => { expect( resolveReusableSandboxLifecycle({ lifecyclePolicy: { mode: "warm", idleTimeoutMs: 300_000 }, target: reusableSandbox, }), ).toEqual({ runnerProcess: "warm", sandboxResource: "keep_running", failoverBackup: "verified", }); }); it("stops and reuses a per-turn reusable sandbox", () => { expect( resolveNativeSandboxLifecycle({ adapterType: "paperclip_runner", lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, target: reusableSandbox, }), ).toEqual({ runnerProcess: "per_turn", sandboxResource: "stop_and_reuse", failoverBackup: "verified", }); }); it("destroys a per-turn disposable sandbox", () => { expect( resolveNativeSandboxLifecycle({ adapterType: "paperclip_runner", lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, target: { ...reusableSandbox, reusableLeaseConfigured: false, }, }), ).toEqual({ runnerProcess: "per_turn", sandboxResource: "destroy_after_turn", failoverBackup: "verified", }); }); it("rejects warm mode without an effective reusable-lease capability", () => { expect(() => resolveNativeSandboxLifecycle({ adapterType: "paperclip_runner", lifecyclePolicy: { mode: "warm", idleTimeoutMs: 300_000 }, target: { ...reusableSandbox, effectiveCapabilities: { reusableLeases: false }, }, }), ).toThrow("runner_warm_lifecycle_requires_reusable_provider_lease"); }); it("leaves legacy and non-sandbox targets untouched", () => { expect( resolveNativeSandboxLifecycle({ adapterType: "codex_local", lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, target: reusableSandbox, }), ).toBeNull(); expect( resolveNativeSandboxLifecycle({ adapterType: "paperclip_runner", lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, target: { kind: "local" }, }), ).toBeNull(); }); it("keeps a warm sandbox only after a successful turn", () => { expect( providerResourceDispositionForTerminalRun("keep_running", "succeeded"), ).toBe("keep_running"); expect( providerResourceDispositionForTerminalRun("keep_running", "failed"), ).toBe("stop_and_retain"); expect( providerResourceDispositionForTerminalRun("keep_running", "cancelled"), ).toBe("stop_and_retain"); expect( providerResourceDispositionForTerminalRun("keep_running", "timed_out"), ).toBe("stop_and_retain"); expect(providerResourceDispositionForTerminalRun("destroy", "failed")).toBe( "destroy", ); }); });