Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
SandboxRunCodeResult,
} from "@cheatcode/sandbox-contracts";
import type { SandboxConsoleSnapshot } from "@cheatcode/types/api";
import { encodeBase64 } from "../sandbox-support";
import { sandboxExecProcessName } from "./project-sandbox-audit";
import { WORKSPACE_DIR } from "./project-sandbox-content-support";
import { recordSandboxUsageBestEffort } from "./project-sandbox-metering";
Expand Down Expand Up @@ -56,8 +55,6 @@ import {
import type { SandboxRuntime } from "./project-sandbox-runtime-handle";

const DEFAULT_EXEC_TIMEOUT_MS = 60_000;
const RUN_CODE_ENV_CHUNK_CHARACTERS = 24_000;
const RUN_CODE_ENV_PREFIX = "CHEATCODE_RUN_CODE_";

export interface ProjectSandboxStatus {
healthy: boolean;
Expand Down Expand Up @@ -149,11 +146,11 @@ async function runCode(
input: ProjectRunCodeInput,
): Promise<SandboxRunCodeResult> {
const parsed = ProjectRunCodeInputSchema.parse(input);
const encodedChunks = chunkRunCode(encodeBase64(new TextEncoder().encode(parsed.code)));
const result = await executeCommand(context.runtime, {
command: runCodeCommand(parsed.language, encodedChunks.length),
const result = await executeCode(context.runtime, {
code: parsed.code,
cwd: parsed.cwd ?? WORKSPACE_DIR,
env: runCodeEnvironment(parsed.env, encodedChunks),
env: parsed.env,
language: parsed.language,
timeoutMs: parsed.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS,
});
return {
Expand All @@ -164,6 +161,38 @@ async function runCode(
};
}

interface ExecutableCode {
code: string;
cwd: string;
env: Record<string, string> | undefined;
language: "javascript" | "python";
timeoutMs: number;
}

async function executeCode(
runtime: ProcessRuntime,
input: ExecutableCode,
): Promise<SandboxExecResult> {
const startedAt = Date.now();
const processName = input.language === "python" ? "python3" : "node";
const id = await runtime.ensureSandbox();
const env = projectPackageEnvironment(input.cwd, input.env);
try {
const completed = await runtime.client().runCode(id, {
code: codeWithWorkingDirectory(input.language, input.cwd, input.code),
language: input.language,
timeout: timeoutSeconds(input.timeoutMs),
...(env === undefined ? {} : { env }),
});
const result = execResult(processName, completed, startedAt);
await recordExecAudit(runtime, [processName], input.cwd, result, completed.exitCode, startedAt);
await recordSandboxUsageBestEffort(await runtime.meteringContext());
return result;
} catch (error) {
throw runtime.toUpstreamError(error, "Sandbox code execution failed.");
}
}

async function exec(runtime: ProcessRuntime, input: ProjectExecInput): Promise<SandboxExecResult> {
const parsed = ProjectExecInputSchema.parse(input);
return executeCommand(runtime, {
Expand Down Expand Up @@ -211,32 +240,16 @@ async function executeCommand(
}
}

function chunkRunCode(encoded: string): string[] {
const chunks: string[] = [];
for (let offset = 0; offset < encoded.length; offset += RUN_CODE_ENV_CHUNK_CHARACTERS) {
chunks.push(encoded.slice(offset, offset + RUN_CODE_ENV_CHUNK_CHARACTERS));
}
return chunks;
}

function runCodeCommand(language: "javascript" | "python", chunkCount: number): string[] {
const inputs = Array.from(
{ length: chunkCount },
(_, index) => `"$${RUN_CODE_ENV_PREFIX}${index}"`,
).join(" ");
const interpreter = language === "python" ? "python3 -" : "node --input-type=module";
return ["sh", "-c", `printf %s ${inputs} | base64 -d | ${interpreter}`];
}

function runCodeEnvironment(
requested: Record<string, string> | undefined,
chunks: readonly string[],
): Record<string, string> {
const environment = { ...requested };
for (const [index, chunk] of chunks.entries()) {
environment[`${RUN_CODE_ENV_PREFIX}${index}`] = chunk;
function codeWithWorkingDirectory(
language: "javascript" | "python",
cwd: string,
code: string,
): string {
const serializedCwd = JSON.stringify(cwd);
if (language === "python") {
return `import os\nos.chdir(${serializedCwd})\nexec(compile(${JSON.stringify(code)}, "<cheatcode>", "exec"))`;
}
return environment;
return `process.chdir(${serializedCwd});\n${code}`;
}

function packageManagerPolicyResult(
Expand Down
4 changes: 3 additions & 1 deletion packages/agent-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ workspace-backed file, shell, document, chart, or artifact work resolves the thr
project lazily when durable project storage is actually needed. Code tools expose `/workspace`
as a virtual project root and remap path references inside argv, shell payloads, and inline code
to the canonical project folder. Projectless calculations and environment probes run from `/tmp`,
so a weaker model cannot accidentally leave durable files outside a project.
so a weaker model cannot accidentally leave durable files outside a project. The bounded Daytona
REST adapter maps request-scoped command environment variables to the provider's `envs` wire field;
generated code can therefore cross the process boundary without entering argv or persistent files.

## Code Checks

Expand Down
24 changes: 23 additions & 1 deletion packages/agent-core/src/tools/code/daytona-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ interface ExecuteParams {
timeout?: number;
}

interface CodeRunParams {
code: string;
env?: Record<string, string>;
language: "javascript" | "python";
/** seconds */
timeout?: number;
}

// ---------------------------------------------------------------------------
// Client
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -360,7 +368,7 @@ export class DaytonaClient {
async execute(id: string, params: ExecuteParams): Promise<DaytonaExecuteResponse> {
const body: Record<string, unknown> = { command: params.command };
if (params.cwd !== undefined) body["cwd"] = params.cwd;
if (params.env !== undefined) body["env"] = params.env;
if (params.env !== undefined) body["envs"] = params.env;
if (params.timeout !== undefined) body["timeout"] = params.timeout;
const json = await this.toolbox("POST", id, "/process/execute", {
body,
Expand All @@ -369,6 +377,20 @@ export class DaytonaClient {
return ExecuteResponseSchema.parse(json);
}

async runCode(id: string, params: CodeRunParams): Promise<DaytonaExecuteResponse> {
const body: Record<string, unknown> = {
code: params.code,
language: params.language,
};
if (params.env !== undefined) body["envs"] = params.env;
if (params.timeout !== undefined) body["timeout"] = params.timeout;
const json = await this.toolbox("POST", id, "/process/code-run", {
body,
timeoutMs: (params.timeout ?? 600) * 1_000 + DAYTONA_EXEC_OVERHEAD_MS,
});
return ExecuteResponseSchema.parse(json);
}

async createSession(id: string, sessionId: string): Promise<void> {
await this.toolbox("POST", id, "/process/session", {
body: { sessionId },
Expand Down