Skip to content

Commit bbc91b8

Browse files
committed
fix(preview): honor allocated project ports
Remap requested dev-server ports to each project stable slot. Require managed preview tools and remount stale iframes only after real sandbox wakeups.
1 parent f16dbca commit bbc91b8

6 files changed

Lines changed: 37 additions & 9 deletions

File tree

apps/web/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ a compact `/uploads/...` reference after each successful save. `/` is exclusivel
2121
persistent project-file browser;
2222
`@` is exclusively the user-skill picker. The file browser reads durable project-file metadata and
2323
does not create or wake Daytona merely because the user opens it.
24+
Computer preview wakeups rotate the preview session and reload the visible iframe
25+
once after an actual sandbox/process recovery. Silent capability rotation keeps the
26+
live iframe mounted so application state is preserved during ordinary use.
2427

2528
## Public exports
2629

apps/web/src/components/preview/use-ensure-preview-live.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,12 @@ async function refreshPreviewSession(
128128
const controller = new AbortController();
129129
runtime.wakeAbort = controller;
130130
runtime.waking = true;
131-
if (showBooting) setPhase("booting");
131+
if (showBooting) {
132+
// Unmount a stale cross-origin document before a real wake. The fresh handoff URL then performs
133+
// its own cookie exchange on remount; silent capability rotation still preserves live SPA state.
134+
clearPreviewUrls(deps);
135+
setPhase("booting");
136+
}
132137
try {
133138
const outcome = await requestPreviewRefresh(
134139
{ ...deps, threadId: deps.threadId },

packages/agent-core/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ Tools execute autonomously inside the active request context. Sandbox operations
3030
remain project-root confined, browser actions remain origin-bound, connected-app
3131
actions remain scoped to the user's active account, and secret-bearing input is
3232
validated before execution. Deterministic prepare/execute boundaries keep dynamic
33-
ports and Git destinations stable between resolution and execution.
33+
ports and Git destinations stable between resolution and execution. The managed
34+
preview tool owns Computer-visible dev servers, remaps a requested port to the
35+
project's allocated port when necessary, and is distinct from generic background
36+
process tools so idle recovery always has a canonical process record.
3437
Browser-only runs use the account sandbox without materializing a persistent project;
3538
workspace-backed file, shell, document, chart, or artifact work resolves the thread's
3639
project lazily when durable project storage is actually needed.

packages/agent-core/src/mastra/system-prompt.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ A Linux sandbox is available when the task genuinely needs it. Ordinary conversa
165165
- Node.js 24 (node, npm, pnpm) and Python 3 (python3, pip3) — install anything else you need from the shell.
166166
- LibreOffice (headless) plus preinstalled Node libraries for deliverables: pptxgenjs (slides), docx, exceljs, @react-pdf/renderer, recharts, arquero.
167167
- A headed Chromium browser you drive to test what you build and to browse the web.
168-
- A dev server you expose as a live preview on port 5173.
168+
- 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.
169169
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.`,
170170

171171
`## How you work
@@ -202,7 +202,7 @@ Beyond these you also have browser, document-generation, data-analysis, web-rese
202202

203203
const WEB_MODULE = `## Building web apps
204204
205-
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.
205+
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.
206206
Verify it in the browser: open the app's INTERNAL address in the sandbox's headed Chromium — http://localhost:<port> (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.`;
207207

208208
const MOBILE_MODULE = `## Building the mobile app

packages/agent-core/src/mastra/tool-defs/code-tools.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const mastraRunCode = createTool({
6262
export const mastraShellExec = createTool({
6363
id: "shell_exec",
6464
description:
65-
"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.",
65+
"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.",
6666
inputSchema: ShellExecInputSchema,
6767
outputSchema: ShellExecOutputSchema,
6868
execute: async (input, context) => {
@@ -79,7 +79,7 @@ export const mastraShellExec = createTool({
7979
export const mastraShellStartProcess = createTool({
8080
id: "shell_start_process",
8181
description:
82-
"Start a long-running process under /workspace in the project sandbox with optional port readiness and restart policy.",
82+
"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.",
8383
inputSchema: ShellStartProcessInputSchema,
8484
outputSchema: ShellProcessOutputSchema,
8585
execute: async (input, context) => {
@@ -104,7 +104,7 @@ export const mastraShellKillProcess = createTool({
104104
export const mastraShellTerminal = createTool({
105105
id: "shell_terminal",
106106
description:
107-
"Run a short terminal-style command in /workspace. Prefer shell_exec for deterministic argv automation.",
107+
"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.",
108108
inputSchema: ShellTerminalInputSchema,
109109
outputSchema: ShellExecOutputSchema,
110110
execute: async (input, context) => {
@@ -215,7 +215,7 @@ export const mastraGitPush = createTool({
215215
export const mastraStartDevServer = createTool({
216216
id: "code_start_dev_server",
217217
description:
218-
"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.",
218+
"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.",
219219
inputSchema: StartDevServerInputSchema,
220220
outputSchema: StartDevServerOutputSchema,
221221
execute: async (input, context) => {

packages/agent-core/src/tools/code/preview.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ export async function prepareStartDevServer(
7676
const isExpo = isExpoStartCommand(parsedInput.command);
7777
const isMobile = isExpo || parsedInput.isMobile;
7878
const port = await allocateDevServerPort(runtimeContext, slug, isMobile);
79+
const command = remapRequestedDevServerPort(parsedInput.command, parsedInput.port, port);
7980
return {
8081
mayUseNetwork: isExpo,
8182
port,
8283
process: {
83-
command: isExpo ? expoWebCommand(port) : parsedInput.command,
84+
command: isExpo ? expoWebCommand(port) : command,
8485
cwd,
8586
env: { ...parsedInput.env, PORT: String(port) },
8687
isMobile,
@@ -94,6 +95,22 @@ export async function prepareStartDevServer(
9495
};
9596
}
9697

98+
/**
99+
* The model supplies a familiar port such as 5173, while the shared user sandbox may allocate a
100+
* different stable port for this project. Remap standalone occurrences in argv and shell payloads
101+
* so explicit framework flags and positional ports agree with the readiness probe and preview URL.
102+
*/
103+
function remapRequestedDevServerPort(
104+
command: readonly string[],
105+
requestedPort: number,
106+
allocatedPort: number,
107+
): string[] {
108+
if (requestedPort === allocatedPort) return [...command];
109+
const requestedPortToken = new RegExp(`(?<!\\d)${requestedPort}(?!\\d)`, "gu");
110+
const allocatedPortToken = String(allocatedPort);
111+
return command.map((argument) => argument.replace(requestedPortToken, allocatedPortToken));
112+
}
113+
97114
/** Executes only the fully resolved process plan. */
98115
export async function executePreparedStartDevServer(
99116
prepared: PreparedStartDevServer,

0 commit comments

Comments
 (0)