Skip to content

Commit cade5ac

Browse files
authored
fix(agent): repair incomplete app dependencies (#82)
## Summary - validates the runtime-specific dependency links before reusing a persistent app workspace - repairs interrupted pnpm installs instead of accepting a partial `node_modules` tree - gives mobile offline installation the same five-minute recovery window as the network fallback ## Context Post-deploy QA proved uploaded-file persistence and agent reads. A mobile QA workspace damaged by the pre-fix destructive bootstrap still had `node_modules/.pnpm` but lacked Expo web links, so the old coarse restore check incorrectly treated it as complete. ## Decisions | Decision | Choice | Reasoning | |---|---|---| | Restore readiness | Check concrete runtime dependencies | A package-store directory alone does not prove usable symlinks | | Recovery | Re-run the pinned install when links are incomplete | The lockfile and snapshot store remain the clean source of truth | | Timeout | Five minutes for mobile offline installs | Daytona volume I/O can exceed the previous two-minute window | ## Verification - [x] agent-worker lint, typecheck, and production dry-run build — 23/23 tasks passed - [x] production upload/read flow passed in two newly created projects - [ ] verify the damaged mobile QA workspace self-repairs after deploy No database change or migration is included. No Linear issue or plan document is associated with this production QA fix.
1 parent ef86735 commit cade5ac

3 files changed

Lines changed: 18 additions & 9 deletions

File tree

apps/agent-worker/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ small current/version namespace records and mirrors the current version to
4141
`/workspace/<workspaceSlug>/uploads/` on the user's persistent Daytona volume before exposing it.
4242
An exact replay is idempotent; uploading new bytes at the same path creates a retained version and
4343
updates the working copy. First-run app scaffolding preserves the `uploads/` directory, and restored
44-
template projects reuse their persistent dependency installation instead of rebuilding the
45-
workspace. Project deletion removes the namespace during fenced workspace cleanup and the existing
46-
resource-deletion prefix sweep removes every immutable object. Account deletion clears both through
47-
the existing account state and R2 lifecycle phases.
44+
template projects reuse a complete persistent dependency installation or repair an interrupted one
45+
instead of rebuilding the workspace. Project deletion removes the namespace during fenced workspace
46+
cleanup and the existing resource-deletion prefix sweep removes every immutable object. Account
47+
deletion clears both through the existing account state and R2 lifecycle phases.
4848

4949
Run creation validates the gateway payload with the shared `CreateRunSchema` from
5050
`packages/types` before selecting the run-scoped `AgentRun` Durable Object. The

apps/agent-worker/src/durable-objects/agent-run-app-builder-scaffold.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,13 @@ export async function installAppBuilderDependencies(
8484
dir: string,
8585
mobile = false,
8686
): Promise<void> {
87-
const networkTimeoutMs = mobile ? 300_000 : 120_000;
87+
const installTimeoutMs = mobile ? 300_000 : 120_000;
8888
try {
8989
await executeShellExec(
9090
{
9191
command: ["pnpm", "install", "--frozen-lockfile", "--offline"],
9292
cwd: dir,
93-
timeoutMs: 120_000,
93+
timeoutMs: installTimeoutMs,
9494
},
9595
{ sandbox },
9696
);
@@ -111,7 +111,7 @@ export async function installAppBuilderDependencies(
111111
"4",
112112
],
113113
cwd: dir,
114-
timeoutMs: networkTimeoutMs,
114+
timeoutMs: installTimeoutMs,
115115
},
116116
{ sandbox },
117117
);

apps/agent-worker/src/durable-objects/agent-run-app-builder.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ async function prepareTemplateWorkspace(
196196
setRunStage(mobile ? "Preparing the Expo workspace." : "Preparing the Next.js workspace.");
197197
if (!shouldBootstrap) {
198198
setRunStage("Restoring the app workspace.");
199-
if (!(await hasInstalledAppBuilderDependencies(sandbox, workspace.dir))) {
199+
if (!(await hasInstalledAppBuilderDependencies(sandbox, workspace.dir, mobile))) {
200200
await installAppBuilderDependencies(sandbox, logger, workspace.dir, mobile);
201201
}
202202
if (mobile) {
@@ -599,10 +599,19 @@ async function hasExistingAppBuilderWorkspace(
599599
async function hasInstalledAppBuilderDependencies(
600600
sandbox: ProjectSandboxStub,
601601
dir: string,
602+
mobile: boolean,
602603
): Promise<boolean> {
604+
const requiredPaths = mobile
605+
? [
606+
"node_modules/.pnpm",
607+
"node_modules/@expo/metro-runtime",
608+
"node_modules/react-dom",
609+
"node_modules/react-native-web",
610+
]
611+
: ["node_modules/.pnpm", "node_modules/next", "node_modules/react", "node_modules/react-dom"];
603612
const result = await executeShellTerminal(
604613
{
605-
command: `test -d ${dir}/node_modules/.pnpm`,
614+
command: requiredPaths.map((path) => `test -d ${dir}/${path}`).join(" && "),
606615
cwd: "/workspace",
607616
timeoutMs: 10_000,
608617
},

0 commit comments

Comments
 (0)