From ceb0b217190f467dc9abd1ee4a1f68cd0750f002 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 11 Sep 2026 16:06:44 -0500 Subject: [PATCH 1/2] test: wait for active Daytona transfers before teardown Co-Authored-By: Paperclip --- .../sandbox-providers/daytona/src/plugin.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/plugins/sandbox-providers/daytona/src/plugin.test.ts b/packages/plugins/sandbox-providers/daytona/src/plugin.test.ts index 84787dff01..a4699bcad0 100644 --- a/packages/plugins/sandbox-providers/daytona/src/plugin.test.ts +++ b/packages/plugins/sandbox-providers/daytona/src/plugin.test.ts @@ -4830,15 +4830,21 @@ describe("daytona native file-sync hooks", () => { // Hold the inbound upload and the outbound download open at the same time, so // the shared lease has two active sync calls when teardown starts. let releaseUpload!: () => void; + let uploadStarted!: () => void; + const uploadReady = new Promise((resolve) => { uploadStarted = resolve; }); sandbox.fs.uploadFiles.mockImplementation(async () => { await new Promise((resolve) => { releaseUpload = resolve; + uploadStarted(); }); }); let releaseDownload!: () => void; + let downloadStarted!: () => void; + const downloadReady = new Promise((resolve) => { downloadStarted = resolve; }); sandbox.fs.downloadFiles.mockImplementation(async (requests: Array<{ source: string; destination: string }>) => { await new Promise((resolve) => { releaseDownload = resolve; + downloadStarted(); }); return Promise.all( requests.map(async (request) => { @@ -4855,9 +4861,9 @@ describe("daytona native file-sync hooks", () => { const outboundCall = plugin.definition.onEnvironmentSyncOut?.( syncOutParams({ operationId: "out-active", sourcePath: `${REMOTE_DIR}/out.txt`, targetPath: outboundTarget }), ); - // Let both sync calls register on the activity gate and reach their hung - // transfer, so teardown sees a refCount of two. - await new Promise((resolve) => setTimeout(resolve, 0)); + // Wait for both actual transfers, rather than assuming one event-loop tick + // completes the asynchronous filesystem preparation on a busy runner. + await Promise.all([uploadReady, downloadReady]); const destroyCall = plugin.definition.onEnvironmentDestroyLease?.({ driverKey: "daytona", From 326c8b27827b9d63928af9765b75524ad4855614 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 11 Sep 2026 16:06:53 -0500 Subject: [PATCH 2/2] fix: keep supported ACPX profiles available in the Pi layer Move the already verified readiness fix to the layer that introduces these profiles. Co-Authored-By: Paperclip --- server/src/__tests__/adapter-registry.test.ts | 15 ++++++++++----- server/src/adapters/registry.ts | 12 +++++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/server/src/__tests__/adapter-registry.test.ts b/server/src/__tests__/adapter-registry.test.ts index d7cf38976a..f2e12d9849 100644 --- a/server/src/__tests__/adapter-registry.test.ts +++ b/server/src/__tests__/adapter-registry.test.ts @@ -279,6 +279,8 @@ describe("server adapter registry", () => { it.each([ ["claude", "claude-sonnet-5"], + ["codex", "gpt-5.6-sol"], + ["pi", "openrouter/deepseek/deepseek-v4-flash-0731"], ] as const)("does not claim runtime readiness from the remote ACPX %s platform alone", async (acpxAgent, model) => { const result = await requireServerAdapter("paperclip_runner").testEnvironment({ companyId: "company-1", @@ -315,20 +317,23 @@ describe("server adapter registry", () => { }); }); - it("accepts the qualified ACPX Pi profile", async () => { + it.each([ + ["codex", "gpt-5.6-sol"], + ["pi", "openrouter/deepseek/deepseek-v4-flash-0731"], + ])("accepts qualified ACPX %s without claiming installation readiness", async (acpxAgent, model) => { const result = await requireServerAdapter("paperclip_runner").testEnvironment({ companyId: "company-1", adapterType: "paperclip_runner", config: { provider: "acpx", - acpxAgent: "pi", - model: "openrouter/deepseek/deepseek-v4-flash-0731", + acpxAgent, + model, }, }); expect(result).toMatchObject({ - status: "pass", - checks: [{ code: "acpx_profile_qualified", level: "info" }], + status: "warn", + checks: [{ code: "acpx_runtime_unverified", level: "warn" }], }); }); it("wraps built-in npm runtime installs with the sandbox-aware install helper", () => { diff --git a/server/src/adapters/registry.ts b/server/src/adapters/registry.ts index 17a58cf7a1..f78c656a9b 100644 --- a/server/src/adapters/registry.ts +++ b/server/src/adapters/registry.ts @@ -405,7 +405,6 @@ const paperclipRunnerAdapter: ServerAdapterModule = { } if (profile.provider === "acpx") { try { - if (profile.acpxAgent !== "claude") throw new Error("Select Codex to use the native Codex runner."); const target = context.executionTarget; if (target?.kind === "remote") { const probe = await runAdapterExecutionTargetShellCommand( @@ -414,8 +413,8 @@ const paperclipRunnerAdapter: ServerAdapterModule = { ); if (probe.timedOut || probe.exitCode !== 0) throw new Error("Could not verify the remote ACPX runner platform."); const [os, arch] = probe.stdout.trim().split(/\s+/); - if (!((os === "Linux" && arch === "x86_64") || (os === "Darwin" && ["arm64", "x86_64"].includes(arch ?? "")))) { - throw new Error("ACPX Claude requires Linux x64 or macOS ARM64/x64."); + if (!((os === "Linux" && arch === "x86_64") || (profile.acpxAgent === "claude" && os === "Darwin" && ["arm64", "x86_64"].includes(arch ?? "")))) { + throw new Error(`ACPX ${profile.acpxAgent} requires Linux x64${profile.acpxAgent === "claude" ? " or macOS ARM64/x64" : ""}.`); } return { adapterType: "paperclip_runner", status: "warn" as const, testedAt: new Date().toISOString(), @@ -423,6 +422,13 @@ const paperclipRunnerAdapter: ServerAdapterModule = { message: "The remote platform is supported. Runtime package integrity and readiness must still be verified by the remote runner before launch." }], }; } + if (profile.acpxAgent !== "claude") { + return { + adapterType: "paperclip_runner", status: "warn" as const, testedAt: new Date().toISOString(), + checks: [{ code: "acpx_runtime_unverified", level: "warn" as const, + message: `The ACPX ${profile.acpxAgent} profile is qualified. Runtime package integrity, platform support, and readiness must still be verified by the runner before launch.` }], + }; + } const { probeAcpxClaudeInstallation } = await import("@paperclipai/paperclip-runner/live"); await probeAcpxClaudeInstallation(profile.model); return {