fix(sandbox): resume leases with their effective provider configuration
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
3f7cbb4f97
commit
d619dd85b8
|
|
@ -7,6 +7,12 @@ publication. Repeated error cleanup keeps the original failure visible. A later
|
|||
authorized run recovers unsaved edits from the retained sandbox before loading
|
||||
incoming shared files; it does not rewrite the failed run as successful.
|
||||
|
||||
Reusable sandbox resume resolves provider configuration from the recorded lease,
|
||||
as workspace operations and cleanup do. This preserves provider-selected defaults
|
||||
such as Daytona's region when the environment leaves them unspecified. Existing
|
||||
configuration and identity checks still reject incompatible reuse; stopping and
|
||||
resuming a compatible lease must reopen the same account-scoped provider handle.
|
||||
|
||||
The deployed acceptance entry point is `pnpm test:e2e:work-folders:deployed`.
|
||||
Set `PAPERCLIP_DEPLOYED_STACK_MANIFEST` to a JSON manifest matching
|
||||
`tests/runner-e2e/deployed-stack.ts`, `PAPERCLIP_DEPLOYED_STACK_AUTH` to a private
|
||||
|
|
|
|||
|
|
@ -668,6 +668,61 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
|||
expect(retry.lease.metadata?.sandboxLeaseAcquisition).toEqual({ outcome: "resumed" });
|
||||
});
|
||||
|
||||
it.each(["codex_local", "paperclip_runner"])("resumes %s in the provider region recorded by the lease", async (adapterType) => {
|
||||
const seeded = await seedReusablePluginSandboxLease(adapterType);
|
||||
// The environment leaves region selection to the provider. The provider
|
||||
// records its resolved region, which execute/realize/release already use.
|
||||
expect(seeded.environment.config).not.toHaveProperty("target");
|
||||
await environmentService(db).updateLeaseMetadata(seeded.reusableLease.id, {
|
||||
...seeded.reusableLease.metadata,
|
||||
target: "us",
|
||||
});
|
||||
const closedRegions = new Set<string | undefined>();
|
||||
const workerManager = {
|
||||
isRunning: vi.fn(() => true),
|
||||
getWorker: vi.fn(() => ({ supportedMethods: ["environmentResumeLease", "environmentReleaseLease", "environmentDestroyLease"] })),
|
||||
call: vi.fn(async (_pluginId: string, method: string, params: { config: { target?: string } }) => {
|
||||
if (method === "environmentReleaseLease") {
|
||||
closedRegions.add(params.config.target);
|
||||
return;
|
||||
}
|
||||
if (method === "environmentResumeLease") {
|
||||
closedRegions.delete(params.config.target);
|
||||
return { providerLeaseId: seeded.reusableLease.providerLeaseId, metadata: {
|
||||
provider: "fake-plugin", image: "fake:test", timeoutMs: 1234, reuseLease: true, target: "us",
|
||||
} };
|
||||
}
|
||||
if (method === "environmentRealizeWorkspace") {
|
||||
if (closedRegions.has(params.config.target)) throw new Error("Sandbox lease is no longer active");
|
||||
return { cwd: "/workspace" };
|
||||
}
|
||||
throw new Error(`Unexpected provider operation: ${method}`);
|
||||
}),
|
||||
} as unknown as PluginWorkerManager;
|
||||
const runtime = environmentRuntimeService(db, { pluginWorkerManager: workerManager });
|
||||
await runtime.acquireRunLease({
|
||||
companyId: seeded.companyId, environment: seeded.environment, agentId: seeded.agentId,
|
||||
heartbeatRunId: seeded.runId, issueId: null, adapterType,
|
||||
persistedExecutionWorkspace: { id: seeded.executionWorkspaceId, mode: "shared_workspace" },
|
||||
});
|
||||
await runtime.releaseRunLeases(seeded.runId, "released", undefined, "stop_and_retain");
|
||||
expect(closedRegions).toEqual(new Set(["us"]));
|
||||
const nextRunId = randomUUID();
|
||||
await db.insert(heartbeatRuns).values({ id: nextRunId, companyId: seeded.companyId, agentId: seeded.agentId, status: "running" });
|
||||
const acquired = await runtime.acquireRunLease({
|
||||
companyId: seeded.companyId, environment: seeded.environment, agentId: seeded.agentId,
|
||||
heartbeatRunId: nextRunId, issueId: null, adapterType,
|
||||
persistedExecutionWorkspace: { id: seeded.executionWorkspaceId, mode: "shared_workspace" },
|
||||
});
|
||||
expect(acquired.lease.providerLeaseId).toBe(seeded.reusableLease.providerLeaseId);
|
||||
await expect(runtime.realizeWorkspace({
|
||||
environment: seeded.environment, lease: acquired.lease,
|
||||
workspace: { localPath: "/workspace", mode: "shared_workspace" },
|
||||
})).resolves.toMatchObject({ cwd: "/workspace" });
|
||||
expect(workerManager.call).toHaveBeenCalledWith(seeded.pluginId, "environmentResumeLease",
|
||||
expect.objectContaining({ config: expect.objectContaining({ target: "us" }) }), expect.any(Number));
|
||||
});
|
||||
|
||||
it("keeps an existing task's legacy sync contract after its sandbox expires, without affecting new tasks", async () => {
|
||||
const seeded = await seedReusablePluginSandboxLease("codex_local");
|
||||
const taskId = randomUUID();
|
||||
|
|
|
|||
|
|
@ -2033,9 +2033,19 @@ function createSandboxEnvironmentDriver(
|
|||
reusableLease = await claimReusableLeaseBeforeResume(reusableLease, input);
|
||||
if (!reusableLease.providerLeaseId) throw new Error("Reusable sandbox claim lost its provider identity");
|
||||
try {
|
||||
// Use the same effective provider configuration as execute and
|
||||
// release. Providers can resolve an omitted region at creation;
|
||||
// resuming with the original default would open a different
|
||||
// account-scoped handle from the one that cleanup closed.
|
||||
const resumeConfig = await resolvePluginSandboxRuntimeConfig({
|
||||
environment: input.environment,
|
||||
lease: reusableLease,
|
||||
provider: parsed.config.provider,
|
||||
});
|
||||
const resumeWorkerConfig = stripSandboxProviderEnvelope(resumeConfig as SandboxEnvironmentConfig);
|
||||
const resumeDeadline = Date.now() + 60_000;
|
||||
const configuredResumeTimeoutMs =
|
||||
resolvePluginSandboxRpcTimeoutMs(workerConfig) ?? 60_000;
|
||||
resolvePluginSandboxRpcTimeoutMs(resumeWorkerConfig) ?? 60_000;
|
||||
let retryDelayMs = 250;
|
||||
let resumed: PluginEnvironmentLease;
|
||||
while (true) {
|
||||
|
|
@ -2048,7 +2058,7 @@ function createSandboxEnvironmentDriver(
|
|||
companyId: input.companyId,
|
||||
environmentId: input.environment.id,
|
||||
issueId: input.issueId,
|
||||
config: workerConfig,
|
||||
config: resumeWorkerConfig,
|
||||
providerLeaseId: reusableLease.providerLeaseId,
|
||||
leaseMetadata: reusableLease.metadata ?? undefined,
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue