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
28 changes: 14 additions & 14 deletions apps/agent-worker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,17 @@ small current/version namespace records and mirrors the current version to
An exact replay is idempotent; uploading new bytes at the same path creates a retained version and
updates the working copy. First-run app scaffolding preserves the `uploads/` directory, and restored
template projects reuse a complete persistent dependency installation or repair an interrupted one
instead of rebuilding the workspace. The working copy is a read-only cache: every project-bound
run verifies its current file set before model access, restores missing, replaced, or modified files
from the checksum-verified R2 version, records the exact workspace materialization separately from
the immutable user-facing file metadata, and repeats that repair when the run exits. Daytona's
filesystem API assigns the sandbox runtime user and read-only modes after each toolbox upload. File
write/delete tools reject the reserved directory, while the system contract requires shell work to
copy an upload elsewhere before transforming it. Template scaffolding and repository imports both
retain the reserved directory. Project deletion removes the namespace during fenced workspace
cleanup and the existing resource-deletion prefix sweep removes every immutable object. Account
deletion clears both through the existing account state and R2 lifecycle phases.
instead of rebuilding the workspace. The working copy is a reserved cache: every project-bound run
verifies its current file set before model access, restores missing, replaced, or modified files from
the checksum-verified R2 version, records the exact workspace materialization separately from the
immutable user-facing file metadata, and repeats that repair when the run exits. File write/delete
tools reject the reserved directory, while the system contract requires shell work to copy an upload
elsewhere before transforming it. The mounted Daytona volume does not provide a portable
ownership/mode boundary, so application guards and R2 repair—not advisory FUSE permissions—enforce
the contract. Template scaffolding and repository imports both retain the reserved directory.
Project deletion removes the namespace during fenced workspace cleanup and the existing
resource-deletion prefix sweep removes every immutable object. Account deletion clears both through
the existing account state and R2 lifecycle phases.

Run creation validates the gateway payload with the shared `CreateRunSchema` from
`packages/types` before selecting the run-scoped `AgentRun` Durable Object. The
Expand Down Expand Up @@ -132,10 +133,9 @@ manifest avoids rewriting unchanged packages and limits cleanup to files previou
managed by that package, preserving local dependencies and generated output. Curated
default skills are immutable snapshot files under `/home/node/.cheatcode/default-skills/`.

ProjectSandbox also writes `/workspace/.cheatcode/runtime.json` as a generated,
mode-restricted projection of managed app-preview processes through Daytona's filesystem API.
The Durable Object process records remain authoritative; the file is only an inspectable runtime
manifest.
ProjectSandbox also writes `/workspace/.cheatcode/runtime.json` as a generated projection of
managed app-preview processes through Daytona's filesystem API. The Durable Object process records
remain authoritative; the file is only an inspectable runtime manifest.

Managed processes use required stable IDs and a maximum of 32 live metadata slots per user
sandbox. Reusing an ID atomically replaces that slot. At capacity, ProjectSandbox reconciles the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,9 @@ export abstract class ProjectSandboxProjectFiles extends ProjectSandboxProcesses
bytes: Uint8Array,
): Promise<Uint8Array> {
const uploadsPath = `${projectRoot}/uploads`;
await this.prepareUploadedFileWrite(sandboxId, uploadsPath);
await this.client().createFolder(sandboxId, uploadsPath);
await this.client().uploadFile(sandboxId, workspacePath, bytes);
const written = await this.downloadUploadedFile(sandboxId, workspacePath, bytes.byteLength);
await this.protectUploadedFile(sandboxId, uploadsPath, workspacePath);
return written;
return this.downloadUploadedFile(sandboxId, workspacePath, bytes.byteLength);
}

private async downloadUploadedFile(
Expand Down Expand Up @@ -477,32 +475,6 @@ export abstract class ProjectSandboxProjectFiles extends ProjectSandboxProcesses
});
}

private async prepareUploadedFileWrite(sandboxId: string, uploadsPath: string): Promise<void> {
await this.client().createFolder(sandboxId, uploadsPath, "0755");
await this.client().setFilePermissions(sandboxId, uploadsPath, {
group: "node",
mode: "0755",
owner: "node",
});
}

private async protectUploadedFile(
sandboxId: string,
uploadsPath: string,
workspacePath: string,
): Promise<void> {
await this.client().setFilePermissions(sandboxId, workspacePath, {
group: "node",
mode: "0444",
owner: "node",
});
await this.client().setFilePermissions(sandboxId, uploadsPath, {
group: "node",
mode: "0555",
owner: "node",
});
}

private async listProjectFileRecords(projectId: string): Promise<{ files: ProjectFile[] }> {
const records = await this.ctx.storage.list({
prefix: fileRecordProjectPrefix(projectId),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,11 @@ export async function writeSandboxRuntimeManifest(
): Promise<void> {
const manifest = buildSandboxRuntimeManifest(records);
await client.createFolder(sandboxId, RUNTIME_DIRECTORY, "0700");
await client.setFilePermissions(sandboxId, RUNTIME_DIRECTORY, {
group: "node",
mode: "0700",
owner: "node",
});
await client.uploadFile(
sandboxId,
SANDBOX_RUNTIME_MANIFEST_PATH,
new TextEncoder().encode(`${JSON.stringify(manifest, null, 2)}\n`),
);
await client.setFilePermissions(sandboxId, SANDBOX_RUNTIME_MANIFEST_PATH, {
group: "node",
mode: "0600",
owner: "node",
});
}

function buildSandboxRuntimeManifest(
Expand Down
6 changes: 2 additions & 4 deletions packages/tools-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ the first page, and file listings require the canonical RFC 3339 `modifiedAt` fi
accepting the deprecated `modTime` representation or fabricating timestamps.
The same REST-only client resolves/creates shared Daytona volumes, validates their current
provider shape and readiness, supplies `volumeId`/`mountPath`/`subpath` only at sandbox creation,
replaces complete sandbox label sets during release-scoped promotion, and uses Daytona's
filesystem-permissions endpoint when a toolbox upload must be reassigned to the sandbox runtime
user with an explicit mode. Snapshot replacement does not use the Daytona SDK or assume an
in-place snapshot mutation API.
and replaces complete sandbox label sets during release-scoped promotion. Snapshot replacement
does not use the Daytona SDK or assume an in-place snapshot mutation API.
When a run supplies `/workspace/<project>`, every file, shell, code-execution,
Git, and dev-server path is confined lexically to that project root; a bare
`/workspace` is remapped to it and sibling-project paths are rejected.
Expand Down
21 changes: 0 additions & 21 deletions packages/tools-code/src/daytona-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,6 @@ interface ExecuteParams {
timeout?: number;
}

interface DaytonaFilePermissions {
group?: string;
mode?: string;
owner?: string;
}

// ---------------------------------------------------------------------------
// Client
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -489,21 +483,6 @@ export class DaytonaClient {
);
}

async setFilePermissions(
id: string,
path: string,
permissions: DaytonaFilePermissions,
): Promise<void> {
const query = new URLSearchParams({ path });
if (permissions.owner !== undefined) query.set("owner", permissions.owner);
if (permissions.group !== undefined) query.set("group", permissions.group);
if (permissions.mode !== undefined) query.set("mode", permissions.mode);
if (query.size === 1) {
throw new TypeError("At least one file permission must be provided");
}
await this.toolbox("POST", id, `/files/permissions?${query.toString()}`);
}

async deleteFilePath(id: string, path: string, recursive: boolean): Promise<void> {
await this.toolbox(
"DELETE",
Expand Down