diff --git a/apps/web/README.md b/apps/web/README.md index ffbd9a63..64272e97 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -21,6 +21,9 @@ a compact `/uploads/...` reference after each successful save. `/` is exclusivel persistent project-file browser; `@` is exclusively the user-skill picker. The file browser reads durable project-file metadata and does not create or wake Daytona merely because the user opens it. +Computer preview wakeups rotate the preview session and reload the visible iframe +once after an actual sandbox/process recovery. Silent capability rotation keeps the +live iframe mounted so application state is preserved during ordinary use. ## Public exports diff --git a/apps/web/src/components/preview/use-ensure-preview-live.ts b/apps/web/src/components/preview/use-ensure-preview-live.ts index ff02b1d6..73b355c6 100644 --- a/apps/web/src/components/preview/use-ensure-preview-live.ts +++ b/apps/web/src/components/preview/use-ensure-preview-live.ts @@ -128,7 +128,12 @@ async function refreshPreviewSession( const controller = new AbortController(); runtime.wakeAbort = controller; runtime.waking = true; - if (showBooting) setPhase("booting"); + if (showBooting) { + // Unmount a stale cross-origin document before a real wake. The fresh handoff URL then performs + // its own cookie exchange on remount; silent capability rotation still preserves live SPA state. + clearPreviewUrls(deps); + setPhase("booting"); + } try { const outcome = await requestPreviewRefresh( { ...deps, threadId: deps.threadId }, diff --git a/packages/agent-core/README.md b/packages/agent-core/README.md index 16486432..51c2eb2d 100644 --- a/packages/agent-core/README.md +++ b/packages/agent-core/README.md @@ -30,7 +30,10 @@ Tools execute autonomously inside the active request context. Sandbox operations remain project-root confined, browser actions remain origin-bound, connected-app actions remain scoped to the user's active account, and secret-bearing input is validated before execution. Deterministic prepare/execute boundaries keep dynamic -ports and Git destinations stable between resolution and execution. +ports and Git destinations stable between resolution and execution. The managed +preview tool owns Computer-visible dev servers, remaps a requested port to the +project's allocated port when necessary, and is distinct from generic background +process tools so idle recovery always has a canonical process record. Browser-only runs use the account sandbox without materializing a persistent project; workspace-backed file, shell, document, chart, or artifact work resolves the thread's project lazily when durable project storage is actually needed. diff --git a/packages/agent-core/src/mastra/system-prompt.ts b/packages/agent-core/src/mastra/system-prompt.ts index 27ddd1fc..53e98282 100644 --- a/packages/agent-core/src/mastra/system-prompt.ts +++ b/packages/agent-core/src/mastra/system-prompt.ts @@ -165,7 +165,7 @@ A Linux sandbox is available when the task genuinely needs it. Ordinary conversa - Node.js 24 (node, npm, pnpm) and Python 3 (python3, pip3) — install anything else you need from the shell. - LibreOffice (headless) plus preinstalled Node libraries for deliverables: pptxgenjs (slides), docx, exceljs, @react-pdf/renderer, recharts, arquero. - A headed Chromium browser you drive to test what you build and to browse the web. -- A dev server you expose as a live preview on port 5173. +- A dev server you expose with code_start_dev_server. Request port 5173 normally; the tool owns the user-visible Computer preview and safely remaps it when another project already uses that port. Never launch a user-facing app with shell_exec, shell_terminal, or shell_start_process because those processes are not registered as the project preview and cannot recover after sandbox idle stops. Use the computer when the requested outcome needs it. Do the work there instead of describing work you could just do, and never claim you did something you didn't run.`, `## How you work @@ -202,7 +202,7 @@ Beyond these you also have browser, document-generation, data-analysis, web-rese const WEB_MODULE = `## Building web apps -Make the app real and complete: working features, real data flow, considered design. Default to a clean modern stack — React / Next.js. Ship something polished: sensible colour and type, responsive, mobile-first, no lorem ipsum, no dead buttons, no placeholder images. Write the files, install deps, and start the dev server early (code_start_dev_server on port 5173, --hostname 0.0.0.0) so you're always working against the running app. +Make the app real and complete: working features, real data flow, considered design. Default to a clean modern stack — React / Next.js. Ship something polished: sensible colour and type, responsive, mobile-first, no lorem ipsum, no dead buttons, no placeholder images. Write the files, install deps, and start the dev server early with code_start_dev_server (request port 5173 and bind to 0.0.0.0) so you're always working against the running app. Never substitute a shell process for the managed preview, even for a static server or when restarting an existing project. Verify it in the browser: open the app's INTERNAL address in the sandbox's headed Chromium — http://localhost: (e.g. http://localhost:5173), NOT the external preview link (your sandbox browser can't reach that) — then screenshot and read it (browser_open / browser_screenshot / browser_act / browser_observe / browser_extract, Stagehand LOCAL). Fix layout and console errors, re-check. If the browser can't load it at all, note you couldn't visually verify and go straight to your closing summary. The running app is shown to the user automatically in the Computer panel's Browser tab — never paste the preview URL.`; const MOBILE_MODULE = `## Building the mobile app diff --git a/packages/agent-core/src/mastra/tool-defs/code-tools.ts b/packages/agent-core/src/mastra/tool-defs/code-tools.ts index b4eeee41..f013c9cb 100644 --- a/packages/agent-core/src/mastra/tool-defs/code-tools.ts +++ b/packages/agent-core/src/mastra/tool-defs/code-tools.ts @@ -62,7 +62,7 @@ export const mastraRunCode = createTool({ export const mastraShellExec = createTool({ id: "shell_exec", description: - "Run a deterministic sandbox command in argv form. Omit cwd for projectless browser, skill-runtime, or environment-inspection commands. For any command that reads, creates, or changes persistent project files, set cwd to /workspace; that explicitly attaches the project and maps /workspace to its persistent folder.", + "Run a bounded deterministic sandbox command in argv form; never use it to launch a user-facing dev server or background service. Use code_start_dev_server for Computer previews. Omit cwd for projectless browser, skill-runtime, or environment-inspection commands. For any command that reads, creates, or changes persistent project files, set cwd to /workspace; that explicitly attaches the project and maps /workspace to its persistent folder.", inputSchema: ShellExecInputSchema, outputSchema: ShellExecOutputSchema, execute: async (input, context) => { @@ -79,7 +79,7 @@ export const mastraShellExec = createTool({ export const mastraShellStartProcess = createTool({ id: "shell_start_process", description: - "Start a long-running process under /workspace in the project sandbox with optional port readiness and restart policy.", + "Start a non-preview background process under /workspace with optional port readiness and restart policy. Never use this for a web or mobile app the user should see in Computer; code_start_dev_server is the only tool that registers and restores that preview.", inputSchema: ShellStartProcessInputSchema, outputSchema: ShellProcessOutputSchema, execute: async (input, context) => { @@ -104,7 +104,7 @@ export const mastraShellKillProcess = createTool({ export const mastraShellTerminal = createTool({ id: "shell_terminal", description: - "Run a short terminal-style command in /workspace. Prefer shell_exec for deterministic argv automation.", + "Run a short foreground terminal-style command in /workspace. Never start a dev server or background process here; use code_start_dev_server for Computer previews. Prefer shell_exec for deterministic argv automation.", inputSchema: ShellTerminalInputSchema, outputSchema: ShellExecOutputSchema, execute: async (input, context) => { @@ -215,7 +215,7 @@ export const mastraGitPush = createTool({ export const mastraStartDevServer = createTool({ id: "code_start_dev_server", description: - "Start a managed long-running dev server under /workspace. Returns only process readiness and the internal port; the user opens the authenticated preview from the Computer panel.", + "Start the managed web or mobile dev server shown in Computer. This is the only preview-registering server tool: it assigns the project's stable internal port, remaps the requested port in the command when needed, and restores the process after sandbox idle stops. Returns readiness and the actual internal port.", inputSchema: StartDevServerInputSchema, outputSchema: StartDevServerOutputSchema, execute: async (input, context) => { diff --git a/packages/agent-core/src/tools/code/preview.ts b/packages/agent-core/src/tools/code/preview.ts index 8591cc25..6f92145e 100644 --- a/packages/agent-core/src/tools/code/preview.ts +++ b/packages/agent-core/src/tools/code/preview.ts @@ -76,11 +76,12 @@ export async function prepareStartDevServer( const isExpo = isExpoStartCommand(parsedInput.command); const isMobile = isExpo || parsedInput.isMobile; const port = await allocateDevServerPort(runtimeContext, slug, isMobile); + const command = remapRequestedDevServerPort(parsedInput.command, parsedInput.port, port); return { mayUseNetwork: isExpo, port, process: { - command: isExpo ? expoWebCommand(port) : parsedInput.command, + command: isExpo ? expoWebCommand(port) : command, cwd, env: { ...parsedInput.env, PORT: String(port) }, isMobile, @@ -94,6 +95,22 @@ export async function prepareStartDevServer( }; } +/** + * The model supplies a familiar port such as 5173, while the shared user sandbox may allocate a + * different stable port for this project. Remap standalone occurrences in argv and shell payloads + * so explicit framework flags and positional ports agree with the readiness probe and preview URL. + */ +function remapRequestedDevServerPort( + command: readonly string[], + requestedPort: number, + allocatedPort: number, +): string[] { + if (requestedPort === allocatedPort) return [...command]; + const requestedPortToken = new RegExp(`(? argument.replace(requestedPortToken, allocatedPortToken)); +} + /** Executes only the fully resolved process plan. */ export async function executePreparedStartDevServer( prepared: PreparedStartDevServer,