diff --git a/apps/agent-worker/README.md b/apps/agent-worker/README.md index 8ebee8ef..2b378520 100644 --- a/apps/agent-worker/README.md +++ b/apps/agent-worker/README.md @@ -40,9 +40,11 @@ the project path and content digest. R2 stores immutable bytes under the existin small current/version namespace records and mirrors the current version to `/workspace//uploads/` on the user's persistent Daytona volume before exposing it. An exact replay is idempotent; uploading new bytes at the same path creates a retained version and -updates the working copy. 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. +updates the working copy. First-run app scaffolding preserves the `uploads/` directory, and restored +template projects reuse their persistent dependency installation instead of rebuilding the +workspace. 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 diff --git a/apps/agent-worker/src/durable-objects/agent-run-app-builder.ts b/apps/agent-worker/src/durable-objects/agent-run-app-builder.ts index 3f333bc8..9ebec791 100644 --- a/apps/agent-worker/src/durable-objects/agent-run-app-builder.ts +++ b/apps/agent-worker/src/durable-objects/agent-run-app-builder.ts @@ -160,11 +160,7 @@ export async function runAppBuilder( if (await hasImportedAppWorkspace(sandbox, workspace.dir)) { return restoreImportedWorkspace(workspaceOptions); } - const shouldBootstrap = !(await hasExistingAppBuilderWorkspace( - sandbox, - workspace.dir, - workspace.mobile, - )); + const shouldBootstrap = !(await hasExistingAppBuilderWorkspace(sandbox, workspace.dir)); if (shouldBootstrap && input.importRepoUrl) { return importRepoWorkspace({ ...workspaceOptions, repoUrl: input.importRepoUrl }); } @@ -200,13 +196,15 @@ async function prepareTemplateWorkspace( setRunStage(mobile ? "Preparing the Expo workspace." : "Preparing the Next.js workspace."); if (!shouldBootstrap) { setRunStage("Restoring the app workspace."); - await installAppBuilderDependencies(sandbox, logger, workspace.dir, mobile); + if (!(await hasInstalledAppBuilderDependencies(sandbox, workspace.dir))) { + await installAppBuilderDependencies(sandbox, logger, workspace.dir, mobile); + } if (mobile) { await ensureExpoWebSupport(sandbox, workspace.dir); } return; } - await resetAppBuilderDirectory(sandbox, workspace.dir); + await resetTemplateAppBuilderDirectory(sandbox, workspace.dir); throwIfRunCanceled(options.abortSignal); if (mobile) { await scaffoldExpoApp(sandbox, logger, workspace.dir); @@ -583,12 +581,28 @@ async function startAppBuilderDevServer( async function hasExistingAppBuilderWorkspace( sandbox: ProjectSandboxStub, dir: string, - mobile: boolean, ): Promise { - const appDir = mobile ? "app" : "src/app"; + const appDirs = ["src/app", "app"]; + const result = await executeShellTerminal( + { + command: + `test -f ${dir}/package.json && ` + + `(test -d ${dir}/${appDirs[0]} || test -d ${dir}/${appDirs[1]})`, + cwd: "/workspace", + timeoutMs: 10_000, + }, + { sandbox }, + ); + return result.success; +} + +async function hasInstalledAppBuilderDependencies( + sandbox: ProjectSandboxStub, + dir: string, +): Promise { const result = await executeShellTerminal( { - command: `test -f ${dir}/package.json && test -d ${dir}/${appDir}`, + command: `test -d ${dir}/node_modules/.pnpm`, cwd: "/workspace", timeoutMs: 10_000, }, @@ -604,6 +618,36 @@ async function resetAppBuilderDirectory(sandbox: ProjectSandboxStub, dir: string ); } +async function resetTemplateAppBuilderDirectory( + sandbox: ProjectSandboxStub, + dir: string, +): Promise { + await executeShellExec( + { + command: [ + "find", + dir, + "-mindepth", + "1", + "-maxdepth", + "1", + "!", + "-name", + "uploads", + "-exec", + "rm", + "-rf", + "--", + "{}", + "+", + ], + cwd: "/workspace", + timeoutMs: 120_000, + }, + { sandbox }, + ); +} + async function clearBuildCache( sandbox: ProjectSandboxStub, dir: string,