diff --git a/packages/adapters/pi-local/src/server/execute.remote.test.ts b/packages/adapters/pi-local/src/server/execute.remote.test.ts index 09f9485d3e..7d9a02e67e 100644 --- a/packages/adapters/pi-local/src/server/execute.remote.test.ts +++ b/packages/adapters/pi-local/src/server/execute.remote.test.ts @@ -135,6 +135,9 @@ describe("pi remote execution", () => { const options = processSpy.mock.calls[0][4]; expect(options.env.HOME).toBe(workFolderHome ?? runtimeRootDir); expect(options.env.PAPERCLIP_WORKSPACE_CWD).toBe("/home/daytona/repos/main"); + // Pi consumes piped stdin before starting its turn, even with a prompt argument. + // An empty input asks the sandbox transport to deliver EOF instead of an open pipe. + expect(options.stdin).toBe(""); } finally { vi.restoreAllMocks(); } diff --git a/packages/adapters/pi-local/src/server/execute.ts b/packages/adapters/pi-local/src/server/execute.ts index 4aca7d918c..b357d2f220 100644 --- a/packages/adapters/pi-local/src/server/execute.ts +++ b/packages/adapters/pi-local/src/server/execute.ts @@ -723,6 +723,9 @@ export async function execute(ctx: AdapterExecutionContext): Promise { code: "PROCESS_NOT_FOUND", statusCode: 404, }); + it("redirects empty stdin so a session command receives EOF", async () => { + process.env.DAYTONA_API_KEY = "host-key"; + const sandbox = createMockSandbox(); + mockGet.mockResolvedValue(sandbox); + + await plugin.definition.onEnvironmentExecute?.(sessionExecParams({ stdin: "" })); + + expect(sandbox.fs.uploadFile).toHaveBeenCalledWith( + Buffer.alloc(0), expect.stringMatching(/^\/tmp\/paperclip-stdin-/), 1, + ); + const [, params] = sandbox.process.executeSessionCommand.mock.calls[0]!; + expect(params.command).toMatch(/< '\/tmp\/paperclip-stdin-/); + expect(sandbox.fs.deleteFile).toHaveBeenCalledWith(expect.stringMatching(/^\/tmp\/paperclip-stdin-/)); + }); + it("replaces a disappeared session only when dispatch was rejected", async () => { process.env.DAYTONA_API_KEY = "host-key"; const sandbox = createMockSandbox(); @@ -2272,7 +2287,7 @@ describe("Daytona sandbox provider plugin", () => { expect(mockGet).toHaveBeenCalledTimes(1); }); - it("stages stdin in the sandbox filesystem when execution needs redirected input", async () => { + it.each(["input payload", ""])("stages redirected stdin, including EOF-only input (%j)", async (stdin) => { process.env.DAYTONA_API_KEY = "host-key"; const sandbox = createMockSandbox(); mockGet.mockResolvedValue(sandbox); @@ -2290,12 +2305,12 @@ describe("Daytona sandbox provider plugin", () => { command: "cat", args: [], cwd: "/workspace", - stdin: "input payload", + stdin, timeoutMs: 1000, }); expect(sandbox.fs.uploadFile).toHaveBeenCalledWith( - Buffer.from("input payload", "utf8"), + Buffer.from(stdin, "utf8"), expect.stringMatching(/^\/tmp\/paperclip-stdin-/), 1, );