Dev #11

Merged
cpfarhood merged 86 commits from dev into local 2026-05-12 00:02:32 +00:00
Showing only changes of commit cb6af7c2cc - Show all commits
@@ -1,5 +1,11 @@
import path from "node:path"; import path from "node:path";
import { CommandExitError, Sandbox, SandboxNotFoundError, TimeoutError } from "e2b"; import { randomUUID } from "node:crypto";
import {
CommandExitError,
Sandbox,
SandboxNotFoundError,
TimeoutError,
} from "e2b";
import { definePlugin } from "@paperclipai/plugin-sdk"; import { definePlugin } from "@paperclipai/plugin-sdk";
import type { import type {
PluginEnvironmentAcquireLeaseParams, PluginEnvironmentAcquireLeaseParams,
@@ -345,13 +351,38 @@ const plugin = definePlugin({
const config = parseDriverConfig(params.config); const config = parseDriverConfig(params.config);
const sandbox = await connectSandbox(config, params.lease.providerLeaseId); const sandbox = await connectSandbox(config, params.lease.providerLeaseId);
const command = buildCommandLine(params.command, params.args); const baseCommand = buildCommandLine(params.command, params.args);
if (params.stdin == null) { const timeoutMs = params.timeoutMs ?? config.timeoutMs;
// For commands with stdin, stage the payload to a temp file inside the
// sandbox and shell-redirect it. Streaming stdin via `sendStdin` raced
// with fast-failing commands (the process exits before the RPC lands),
// and the previous code awaited a foreground `run` before sending stdin
// at all, so the data was never delivered. The staged-file approach
// keeps execution synchronous, avoids the race, and is unaffected by
// whether the command exits in microseconds or minutes.
let stagedStdinPath: string | null = null;
if (params.stdin != null) {
stagedStdinPath = `/tmp/paperclip-stdin-${randomUUID()}`;
try {
await sandbox.files.write(stagedStdinPath, params.stdin);
} catch (error) {
// Best-effort cleanup in case the write partially succeeded; ignore
// remove failures so the original error is what propagates.
await sandbox.files.remove(stagedStdinPath).catch(() => undefined);
throw error;
}
}
const command = stagedStdinPath
? `${baseCommand} < ${shellQuote(stagedStdinPath)}`
: baseCommand;
try { try {
const result = await sandbox.commands.run(command, { const result = await sandbox.commands.run(command, {
cwd: params.cwd, cwd: params.cwd,
envs: params.env, envs: params.env,
timeoutMs: params.timeoutMs ?? config.timeoutMs, timeoutMs,
}) as Awaited<ReturnType<Sandbox["commands"]["run"]>> & { }) as Awaited<ReturnType<Sandbox["commands"]["run"]>> & {
exitCode: number; exitCode: number;
stdout: string; stdout: string;
@@ -365,59 +396,21 @@ const plugin = definePlugin({
}; };
} catch (error) { } catch (error) {
if (error instanceof CommandExitError) { if (error instanceof CommandExitError) {
const commandError = error as CommandExitError;
return { return {
exitCode: commandError.exitCode, exitCode: error.exitCode,
timedOut: false, timedOut: false,
stdout: commandError.stdout, stdout: error.stdout,
stderr: commandError.stderr, stderr: error.stderr,
}; };
} }
if (error instanceof TimeoutError) { if (error instanceof TimeoutError) {
return buildTimeoutExecuteResult(error); return buildTimeoutExecuteResult(error);
} }
throw error; throw error;
}
}
const started = await sandbox.commands.run(command, {
stdin: true,
cwd: params.cwd,
envs: params.env,
timeoutMs: params.timeoutMs ?? config.timeoutMs,
}) as Awaited<ReturnType<Sandbox["commands"]["run"]>> & {
pid: number;
exitCode: number;
stdout: string;
stderr: string;
};
try {
try {
await sandbox.commands.sendStdin(started.pid, params.stdin);
} finally { } finally {
await sandbox.commands.closeStdin(started.pid); if (stagedStdinPath) {
await sandbox.files.remove(stagedStdinPath).catch(() => undefined);
} }
return {
exitCode: started.exitCode,
timedOut: false,
stdout: started.stdout,
stderr: started.stderr,
};
} catch (error) {
if (error instanceof CommandExitError) {
const commandError = error as CommandExitError;
return {
exitCode: commandError.exitCode,
timedOut: false,
stdout: commandError.stdout,
stderr: commandError.stderr,
};
}
if (error instanceof TimeoutError) {
return buildTimeoutExecuteResult(error);
}
throw error;
} }
}, },
}); });