From ae14bed75cf6d9108f369f18fc51d1f688294467 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 17:27:11 -0600 Subject: [PATCH 01/11] add opensrc --- .gitignore | 3 +++ tsconfig.json | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ba0b02e8..6f2518da 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,6 @@ logs/ .DS_Store Thumbs.db .pnpm-store + +# opensrc - source code for packages +opensrc/ diff --git a/tsconfig.json b/tsconfig.json index e7ff3a26..b1356b2f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,6 +36,7 @@ ".next/dev/types/**/*.ts" ], "exclude": [ - "node_modules" + "node_modules", + "opensrc" ] } From 9a9c937a5aefa98e45e36ce82b50541c92a0ad8b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 17:57:13 -0600 Subject: [PATCH 02/11] add agent-browser --- app/[owner]/[repo]/page.tsx | 2 + app/api/tasks/route.ts | 5 + app/new/[owner]/[repo]/page.tsx | 2 + app/page.tsx | 2 + components/app-layout.tsx | 1 + components/home-page-content.tsx | 6 + components/task-form.tsx | 35 +++++- lib/db/migrations/0021_lovely_lizard.sql | 1 + lib/db/migrations/meta/_journal.json | 7 ++ lib/db/schema.ts | 3 + lib/sandbox/creation.ts | 154 +++++++++++++++++++++++ lib/sandbox/types.ts | 1 + lib/utils/cookies.ts | 25 ++++ 13 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 lib/db/migrations/0021_lovely_lizard.sql diff --git a/app/[owner]/[repo]/page.tsx b/app/[owner]/[repo]/page.tsx index 96d3bbaf..6ddfae36 100644 --- a/app/[owner]/[repo]/page.tsx +++ b/app/[owner]/[repo]/page.tsx @@ -17,6 +17,7 @@ export default async function OwnerRepoPage({ params }: OwnerRepoPageProps) { const cookieStore = await cookies() const installDependencies = cookieStore.get('install-dependencies')?.value === 'true' const keepAlive = cookieStore.get('keep-alive')?.value === 'true' + const enableBrowser = cookieStore.get('enable-browser')?.value === 'true' const session = await getServerSession() @@ -33,6 +34,7 @@ export default async function OwnerRepoPage({ params }: OwnerRepoPageProps) { initialInstallDependencies={installDependencies} initialMaxDuration={maxDuration} initialKeepAlive={keepAlive} + initialEnableBrowser={enableBrowser} maxSandboxDuration={maxSandboxDuration} user={session?.user ?? null} initialStars={stars} diff --git a/app/api/tasks/route.ts b/app/api/tasks/route.ts index 45e82d89..2473fef3 100644 --- a/app/api/tasks/route.ts +++ b/app/api/tasks/route.ts @@ -231,6 +231,7 @@ export async function POST(request: NextRequest) { validatedData.selectedModel, validatedData.installDependencies || false, validatedData.keepAlive || false, + validatedData.enableBrowser || false, userApiKeys, userGithubToken, githubUser, @@ -257,6 +258,7 @@ async function processTaskWithTimeout( selectedModel?: string, installDependencies: boolean = false, keepAlive: boolean = false, + enableBrowser: boolean = false, apiKeys?: { OPENAI_API_KEY?: string GEMINI_API_KEY?: string @@ -301,6 +303,7 @@ async function processTaskWithTimeout( selectedModel, installDependencies, keepAlive, + enableBrowser, apiKeys, githubToken, githubUser, @@ -369,6 +372,7 @@ async function processTask( selectedModel?: string, installDependencies: boolean = false, keepAlive: boolean = false, + enableBrowser: boolean = false, apiKeys?: { OPENAI_API_KEY?: string GEMINI_API_KEY?: string @@ -458,6 +462,7 @@ async function processTask( selectedModel, installDependencies, keepAlive, + enableBrowser, preDeterminedBranchName: aiBranchName || undefined, onProgress: async (progress: number, message: string) => { // Use real-time logger for progress updates diff --git a/app/new/[owner]/[repo]/page.tsx b/app/new/[owner]/[repo]/page.tsx index ce3b1daf..94f85ed1 100644 --- a/app/new/[owner]/[repo]/page.tsx +++ b/app/new/[owner]/[repo]/page.tsx @@ -17,6 +17,7 @@ export default async function NewRepoPage({ params }: NewRepoPageProps) { const cookieStore = await cookies() const installDependencies = cookieStore.get('install-dependencies')?.value === 'true' const keepAlive = cookieStore.get('keep-alive')?.value === 'true' + const enableBrowser = cookieStore.get('enable-browser')?.value === 'true' const session = await getServerSession() @@ -33,6 +34,7 @@ export default async function NewRepoPage({ params }: NewRepoPageProps) { initialInstallDependencies={installDependencies} initialMaxDuration={maxDuration} initialKeepAlive={keepAlive} + initialEnableBrowser={enableBrowser} maxSandboxDuration={maxSandboxDuration} user={session?.user ?? null} initialStars={stars} diff --git a/app/page.tsx b/app/page.tsx index 137899a3..13a27da9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -10,6 +10,7 @@ export default async function Home() { const selectedRepo = cookieStore.get('selected-repo')?.value || '' const installDependencies = cookieStore.get('install-dependencies')?.value === 'true' const keepAlive = cookieStore.get('keep-alive')?.value === 'true' + const enableBrowser = cookieStore.get('enable-browser')?.value === 'true' const session = await getServerSession() @@ -26,6 +27,7 @@ export default async function Home() { initialInstallDependencies={installDependencies} initialMaxDuration={maxDuration} initialKeepAlive={keepAlive} + initialEnableBrowser={enableBrowser} maxSandboxDuration={maxSandboxDuration} user={session?.user ?? null} initialStars={stars} diff --git a/components/app-layout.tsx b/components/app-layout.tsx index 991c27d9..7782f8a0 100644 --- a/components/app-layout.tsx +++ b/components/app-layout.tsx @@ -227,6 +227,7 @@ export function AppLayout({ children, initialSidebarWidth, initialSidebarOpen, i installDependencies: taskData.installDependencies, maxDuration: taskData.maxDuration, keepAlive: false, + enableBrowser: false, status: 'pending', progress: 0, logs: [], diff --git a/components/home-page-content.tsx b/components/home-page-content.tsx index 72843b26..07f87cc9 100644 --- a/components/home-page-content.tsx +++ b/components/home-page-content.tsx @@ -24,6 +24,7 @@ interface HomePageContentProps { initialInstallDependencies?: boolean initialMaxDuration?: number initialKeepAlive?: boolean + initialEnableBrowser?: boolean maxSandboxDuration?: number user?: Session['user'] | null initialStars?: number @@ -35,6 +36,7 @@ export function HomePageContent({ initialInstallDependencies = false, initialMaxDuration = 300, initialKeepAlive = false, + initialEnableBrowser = false, maxSandboxDuration = 300, user = null, initialStars = 1200, @@ -130,6 +132,7 @@ export function HomePageContent({ installDependencies: boolean maxDuration: number keepAlive: boolean + enableBrowser: boolean }) => { // Check if user is authenticated if (!user) { @@ -183,6 +186,7 @@ export function HomePageContent({ installDependencies: data.installDependencies, maxDuration: data.maxDuration, keepAlive: data.keepAlive, + enableBrowser: data.enableBrowser, } }) @@ -256,6 +260,7 @@ export function HomePageContent({ installDependencies: data.installDependencies, maxDuration: data.maxDuration, keepAlive: data.keepAlive, + enableBrowser: data.enableBrowser, } }) @@ -366,6 +371,7 @@ export function HomePageContent({ initialInstallDependencies={initialInstallDependencies} initialMaxDuration={initialMaxDuration} initialKeepAlive={initialKeepAlive} + initialEnableBrowser={initialEnableBrowser} maxSandboxDuration={maxSandboxDuration} /> diff --git a/components/task-form.tsx b/components/task-form.tsx index 4567f987..9d639464 100644 --- a/components/task-form.tsx +++ b/components/task-form.tsx @@ -15,9 +15,9 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' -import { Loader2, ArrowUp, Settings, X, Cable, Users } from 'lucide-react' +import { Loader2, ArrowUp, Settings, X, Cable, Users, Globe } from 'lucide-react' import { Claude, Codex, Copilot, Cursor, Gemini, OpenCode } from '@/components/logos' -import { setInstallDependencies, setMaxDuration, setKeepAlive } from '@/lib/utils/cookies' +import { setInstallDependencies, setMaxDuration, setKeepAlive, setEnableBrowser } from '@/lib/utils/cookies' import { useConnectors } from '@/components/connectors-provider' import { ConnectorDialog } from '@/components/connectors/manage-connectors' import { toast } from 'sonner' @@ -46,6 +46,7 @@ interface TaskFormProps { installDependencies: boolean maxDuration: number keepAlive: boolean + enableBrowser: boolean }) => void isSubmitting: boolean selectedOwner: string @@ -53,6 +54,7 @@ interface TaskFormProps { initialInstallDependencies?: boolean initialMaxDuration?: number initialKeepAlive?: boolean + initialEnableBrowser?: boolean maxSandboxDuration?: number } @@ -164,6 +166,7 @@ export function TaskForm({ initialInstallDependencies = false, initialMaxDuration = 300, initialKeepAlive = false, + initialEnableBrowser = false, maxSandboxDuration = 300, }: TaskFormProps) { const [prompt, setPrompt] = useAtom(taskPromptAtom) @@ -178,6 +181,7 @@ export function TaskForm({ const [installDependencies, setInstallDependenciesState] = useState(initialInstallDependencies) const [maxDuration, setMaxDurationState] = useState(initialMaxDuration) const [keepAlive, setKeepAliveState] = useState(initialKeepAlive) + const [enableBrowser, setEnableBrowserState] = useState(initialEnableBrowser) const [showMcpServersDialog, setShowMcpServersDialog] = useState(false) // Connectors state @@ -202,6 +206,11 @@ export function TaskForm({ setKeepAlive(value) } + const updateEnableBrowser = (value: boolean) => { + setEnableBrowserState(value) + setEnableBrowser(value) + } + // Handle keyboard events in textarea const handleTextareaKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { @@ -339,6 +348,7 @@ export function TaskForm({ installDependencies, maxDuration, keepAlive, + enableBrowser, }) return } @@ -383,6 +393,7 @@ export function TaskForm({ installDependencies, maxDuration, keepAlive, + enableBrowser, }) } @@ -605,6 +616,26 @@ export function TaskForm({ {/* Buttons */}
+ + + + + +

Browser Automation {enableBrowser ? '(On)' : '(Off)'}

+
+
+
+ +
+ setTryAgainEnableBrowser(!!checked)} + /> + +
From d8f95de1862c83eb5d682fa5f35a2defb3e3fa61 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 18:22:38 -0600 Subject: [PATCH 05/11] better status --- components/task-chat.tsx | 54 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/components/task-chat.tsx b/components/task-chat.tsx index 0e405a04..6eaffa81 100644 --- a/components/task-chat.tsx +++ b/components/task-chat.tsx @@ -1144,7 +1144,7 @@ export function TaskChat({ taskId, task }: TaskChatProps) { ) })} - {/* Show "Awaiting response..." or "Awaiting response..." if task is processing and latest message is from user without response */} + {/* Show sandbox setup progress or "Awaiting response..." if task is processing and latest message is from user without response */} {(task.status === 'processing' || task.status === 'pending') && displayMessages.length > 0 && (() => { @@ -1154,13 +1154,61 @@ export function TaskChat({ taskId, task }: TaskChatProps) { // Check if this is the first user message (sandbox initialization) const userMessages = displayMessages.filter((m) => m.role === 'user') const isFirstMessage = userMessages.length === 1 - const placeholderText = isFirstMessage ? 'Awaiting response...' : 'Awaiting response...' + // Get the latest logs to show progress (filter out server logs) + const setupLogs = (task.logs || []) + .filter((log) => !log.message.startsWith('[SERVER]')) + .slice(-8) // Show last 8 logs + + // If first message and we have logs, show sandbox setup progress + if (isFirstMessage && setupLogs.length > 0) { + return ( +
+
+
+
+ + Setting up sandbox... +
+
+ {setupLogs.map((log, idx) => { + const isLatest = idx === setupLogs.length - 1 + return ( +
+ {log.message} +
+ ) + })} +
+
+ {formatDuration(lastMessage.createdAt)} +
+
+
+
+ ) + } + + // Otherwise show simple awaiting response return (
-
{placeholderText}
+
+ + Awaiting response... +
{formatDuration(lastMessage.createdAt)}
From 35cdc1ca553f9dbb50dfe9554e9043f2198538ed Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 18:22:48 -0600 Subject: [PATCH 06/11] format --- components/task-chat.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/task-chat.tsx b/components/task-chat.tsx index 6eaffa81..bff2042a 100644 --- a/components/task-chat.tsx +++ b/components/task-chat.tsx @@ -1156,9 +1156,7 @@ export function TaskChat({ taskId, task }: TaskChatProps) { const isFirstMessage = userMessages.length === 1 // Get the latest logs to show progress (filter out server logs) - const setupLogs = (task.logs || []) - .filter((log) => !log.message.startsWith('[SERVER]')) - .slice(-8) // Show last 8 logs + const setupLogs = (task.logs || []).filter((log) => !log.message.startsWith('[SERVER]')).slice(-8) // Show last 8 logs // If first message and we have logs, show sandbox setup progress if (isFirstMessage && setupLogs.length > 0) { From 61899950b854e86ce678ccfe9dbd47c9b9ccf064 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 18:27:32 -0600 Subject: [PATCH 07/11] add other skills --- AGENTS.md | 23 ++++ lib/sandbox/creation.ts | 255 +++++++++++++++++++++++++--------------- 2 files changed, 182 insertions(+), 96 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 51e82079..b215e26a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -272,3 +272,26 @@ If you need to log information for debugging purposes: **Remember: When in doubt, use a static string. No exceptions.** + + + +## Source Code Reference + +Source code for dependencies is available in `opensrc/` for deeper understanding of implementation details. + +See `opensrc/sources.json` for the list of available packages and their versions. + +Use this source code when you need to understand how a package works internally, not just its types/interface. + +### Fetching Additional Source Code + +To fetch source code for a package or repository you need to understand, run: + +```bash +npx opensrc # npm package (e.g., npx opensrc zod) +npx opensrc pypi: # Python package (e.g., npx opensrc pypi:requests) +npx opensrc crates: # Rust crate (e.g., npx opensrc crates:serde) +npx opensrc / # GitHub repo (e.g., npx opensrc vercel/ai) +``` + + \ No newline at end of file diff --git a/lib/sandbox/creation.ts b/lib/sandbox/creation.ts index d2e8d1af..47eadbb1 100644 --- a/lib/sandbox/creation.ts +++ b/lib/sandbox/creation.ts @@ -577,131 +577,194 @@ fi await logger.info('Chromium downloaded successfully for agent-browser') } - // Create the agent-browser skill file for Claude + // Create the agent-browser skill file based on the selected agent await logger.info('Creating agent-browser skill for coding agent...') - const skillContent = `--- -description: Browser automation skill using agent-browser CLI -invocation: auto + const agentType = config.selectedAgent || 'claude' + + // Skill content with YAML front matter (Claude format) + const claudeSkillContent = `--- +name: agent-browser +description: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages. +allowed-tools: Bash(agent-browser:*) --- -# agent-browser Skill +# Browser Automation with agent-browser + +## Quick start + +\`\`\`bash +agent-browser open # Navigate to page +agent-browser snapshot -i # Get interactive elements with refs +agent-browser click @e1 # Click element by ref +agent-browser fill @e2 "text" # Fill input by ref +agent-browser close # Close browser +\`\`\` + +## Core workflow + +1. Navigate: \`agent-browser open \` +2. Snapshot: \`agent-browser snapshot -i\` (returns elements with refs like \`@e1\`, \`@e2\`) +3. Interact using refs from the snapshot +4. Re-snapshot after navigation or significant DOM changes + +## Commands -You have access to the \`agent-browser\` CLI tool for headless browser automation. +### Navigation +\`\`\`bash +agent-browser open # Navigate to URL +agent-browser back # Go back +agent-browser forward # Go forward +agent-browser reload # Reload page +agent-browser close # Close browser +\`\`\` -## Workflow +### Snapshot (page analysis) +\`\`\`bash +agent-browser snapshot # Full accessibility tree +agent-browser snapshot -i # Interactive elements only (recommended) +agent-browser snapshot -c # Compact output +\`\`\` -1. **Open a page**: \`agent-browser open \` -2. **Get element refs**: \`agent-browser snapshot -i\` (returns accessibility tree with @refs) -3. **Interact using refs**: \`agent-browser click @e2\` or \`agent-browser fill @e3 "text"\` -4. **Get information**: \`agent-browser get text @e1\` -5. **Screenshot**: \`agent-browser screenshot\` +### Interactions (use @refs from snapshot) +\`\`\`bash +agent-browser click @e1 # Click +agent-browser fill @e2 "text" # Clear and type +agent-browser type @e2 "text" # Type without clearing +agent-browser press Enter # Press key +agent-browser hover @e1 # Hover +agent-browser check @e1 # Check checkbox +agent-browser select @e1 "value" # Select dropdown option +agent-browser scroll down 500 # Scroll page +agent-browser upload @e1 file.pdf # Upload files +\`\`\` -## Core Commands +### Get information +\`\`\`bash +agent-browser get text @e1 # Get element text +agent-browser get value @e1 # Get input value +agent-browser get title # Get page title +agent-browser get url # Get current URL +\`\`\` +### Screenshots \`\`\`bash -# Navigation -agent-browser open # Navigate to URL -agent-browser back # Go back -agent-browser forward # Go forward -agent-browser reload # Reload page - -# Get element references (IMPORTANT: use this to find elements) -agent-browser snapshot # Full accessibility tree with @refs -agent-browser snapshot -i # Interactive elements only (recommended) -agent-browser snapshot -c # Compact (remove empty elements) - -# Interact with elements (use @ref from snapshot) -agent-browser click @e2 # Click element by ref -agent-browser dblclick @e2 # Double-click -agent-browser type @e3 "hello" # Type text (appends) -agent-browser fill @e3 "hello" # Clear and fill (replaces) -agent-browser hover @e2 # Hover element -agent-browser focus @e2 # Focus element -agent-browser check @e2 # Check checkbox -agent-browser uncheck @e2 # Uncheck checkbox -agent-browser select @e2 "option" # Select dropdown option - -# Keyboard -agent-browser press Enter # Press key -agent-browser press Control+a # Key combo - -# Get information -agent-browser get text @e1 # Get text content -agent-browser get html @e1 # Get HTML -agent-browser get value @e1 # Get input value -agent-browser get title # Get page title -agent-browser get url # Get current URL - -# Check state -agent-browser is visible @e1 # Check if visible -agent-browser is enabled @e1 # Check if enabled -agent-browser is checked @e1 # Check if checked - -# Screenshots -agent-browser screenshot # Take screenshot -agent-browser screenshot --full # Full page screenshot -agent-browser screenshot output.png - -# Scrolling -agent-browser scroll down 500 # Scroll down 500px -agent-browser scroll up 500 # Scroll up 500px -agent-browser scrollintoview @e2 # Scroll element into view - -# Wait -agent-browser wait @e2 # Wait for element -agent-browser wait 2000 # Wait 2 seconds - -# JavaScript -agent-browser eval "document.title" +agent-browser screenshot # Screenshot to stdout +agent-browser screenshot path.png # Save to file +agent-browser screenshot --full # Full page \`\`\` -## Example: Login Flow +### Wait +\`\`\`bash +agent-browser wait @e1 # Wait for element +agent-browser wait 2000 # Wait milliseconds +\`\`\` +## Example: Form submission \`\`\`bash -agent-browser open https://example.com/login +agent-browser open https://example.com/form agent-browser snapshot -i -# Output shows: @e1 [textbox] "Email", @e2 [textbox] "Password", @e3 [button] "Sign In" agent-browser fill @e1 "user@example.com" agent-browser fill @e2 "password123" agent-browser click @e3 agent-browser wait 2000 -agent-browser screenshot login-result.png +agent-browser snapshot -i # Check result \`\`\` +` -## Example: Scrape Content + // Generic instructions content (for agents without skill file support) + const genericInstructions = `You have access to the agent-browser CLI for browser automation. -\`\`\`bash -agent-browser open https://news.ycombinator.com -agent-browser snapshot -i -agent-browser get text @e1 -\`\`\` +Quick start: +- agent-browser open # Navigate to page +- agent-browser snapshot -i # Get interactive elements with refs +- agent-browser click @e1 # Click element by ref +- agent-browser fill @e2 "text" # Fill input by ref -## Tips +Workflow: open URL -> snapshot -i -> interact with @refs -> re-snapshot after changes -- Always use \`snapshot -i\` first to get element references -- Use @refs (like @e1, @e2) from snapshot output to target elements -- Use \`--json\` flag for machine-readable output -- Use \`--headed\` to see the browser window for debugging -` +Key commands: open, snapshot -i, click, fill, type, press, get text/value/title/url, screenshot, wait` - // Create the skill file in user home directory (not project dir) - const skillDir = '/home/vercel-sandbox/.claude/skills/agent-browser' - const createSkillDir = await runCommandInSandbox(sandbox, 'mkdir', ['-p', skillDir]) - if (createSkillDir.success) { - // Write the skill file using heredoc - const writeSkillCmd = `cat > ${skillDir}/SKILL.md << 'SKILL_EOF' -${skillContent} + let skillInstalled = false + + if (agentType === 'claude') { + // Claude: Use .claude/skills directory + const skillDir = '/home/vercel-sandbox/.claude/skills/agent-browser' + const createSkillDir = await runCommandInSandbox(sandbox, 'mkdir', ['-p', skillDir]) + if (createSkillDir.success) { + const writeSkillCmd = `cat > ${skillDir}/SKILL.md << 'SKILL_EOF' +${claudeSkillContent} +SKILL_EOF` + const writeSkill = await runCommandInSandbox(sandbox, 'sh', ['-c', writeSkillCmd]) + skillInstalled = writeSkill.success + } + } else if (agentType === 'gemini') { + // Gemini: Use .gemini directory with AGENTS.md + const geminiDir = '/home/vercel-sandbox/.gemini' + const createDir = await runCommandInSandbox(sandbox, 'mkdir', ['-p', geminiDir]) + if (createDir.success) { + const writeCmd = `cat > ${geminiDir}/AGENTS.md << 'SKILL_EOF' +# Browser Automation Skill + +${genericInstructions} SKILL_EOF` - const writeSkill = await runCommandInSandbox(sandbox, 'sh', ['-c', writeSkillCmd]) + const writeSkill = await runCommandInSandbox(sandbox, 'sh', ['-c', writeCmd]) + skillInstalled = writeSkill.success + } + } else if (agentType === 'cursor') { + // Cursor: Use .cursor/rules directory + const cursorDir = '/home/vercel-sandbox/.cursor/rules' + const createDir = await runCommandInSandbox(sandbox, 'mkdir', ['-p', cursorDir]) + if (createDir.success) { + const writeCmd = `cat > ${cursorDir}/agent-browser.mdc << 'SKILL_EOF' +--- +description: Browser automation with agent-browser CLI +globs: ["**/*"] +alwaysApply: true +--- - if (writeSkill.success) { - await logger.info('agent-browser skill created successfully') - } else { - await logger.info('Warning: Failed to create agent-browser skill file') +${genericInstructions} +SKILL_EOF` + const writeSkill = await runCommandInSandbox(sandbox, 'sh', ['-c', writeCmd]) + skillInstalled = writeSkill.success + } + } else if (agentType === 'codex') { + // Codex: Use AGENTS.md in home directory + const writeCmd = `cat > /home/vercel-sandbox/AGENTS.md << 'SKILL_EOF' +# Browser Automation + +${genericInstructions} +SKILL_EOF` + const writeSkill = await runCommandInSandbox(sandbox, 'sh', ['-c', writeCmd]) + skillInstalled = writeSkill.success + } else if (agentType === 'copilot') { + // Copilot: Use .github/copilot-instructions.md + const ghDir = '/home/vercel-sandbox/.github' + const createDir = await runCommandInSandbox(sandbox, 'mkdir', ['-p', ghDir]) + if (createDir.success) { + const writeCmd = `cat > ${ghDir}/copilot-instructions.md << 'SKILL_EOF' +# Browser Automation + +${genericInstructions} +SKILL_EOF` + const writeSkill = await runCommandInSandbox(sandbox, 'sh', ['-c', writeCmd]) + skillInstalled = writeSkill.success } + } else if (agentType === 'opencode') { + // OpenCode: Use AGENTS.md in home directory + const writeCmd = `cat > /home/vercel-sandbox/AGENTS.md << 'SKILL_EOF' +# Browser Automation + +${genericInstructions} +SKILL_EOF` + const writeSkill = await runCommandInSandbox(sandbox, 'sh', ['-c', writeCmd]) + skillInstalled = writeSkill.success + } + + if (skillInstalled) { + await logger.info('agent-browser skill created successfully') } else { - await logger.info('Warning: Failed to create skill directory') + await logger.info('Warning: Failed to create agent-browser skill file') } } } From e473aac2c2d80c2cc170d49ad135b1b5c3f6b870 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 18:37:08 -0600 Subject: [PATCH 08/11] better hint --- components/task-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/task-form.tsx b/components/task-form.tsx index 9d639464..ac2216f8 100644 --- a/components/task-form.tsx +++ b/components/task-form.tsx @@ -632,7 +632,7 @@ export function TaskForm({ -

Browser Automation {enableBrowser ? '(On)' : '(Off)'}

+

Agent Browser

From 37a29138d298f5f0b841581902c6aac500c7f8a9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 18:48:06 -0600 Subject: [PATCH 09/11] fix claude model names --- components/home-page-header.tsx | 2 +- components/repo-issues.tsx | 15 +++++++-------- components/repo-pull-requests.tsx | 15 +++++++-------- components/revert-commit-dialog.tsx | 15 +++++++-------- components/task-details.tsx | 28 +++++++++++++--------------- components/task-form.tsx | 16 +++++++--------- components/task-sidebar.tsx | 13 ++++++------- components/tasks-list-client.tsx | 13 ++++++------- lib/sandbox/agents/claude.ts | 4 ++-- 9 files changed, 56 insertions(+), 65 deletions(-) diff --git a/components/home-page-header.tsx b/components/home-page-header.tsx index 2f547343..b6529d80 100644 --- a/components/home-page-header.tsx +++ b/components/home-page-header.tsx @@ -158,7 +158,7 @@ export function HomePageHeader({ prompt: 'Work on this repository', repoUrl: repoUrl, selectedAgent: localStorage.getItem('last-selected-agent') || 'claude', - selectedModel: localStorage.getItem('last-selected-model-claude') || 'claude-sonnet-4-5-20250929', + selectedModel: localStorage.getItem('last-selected-model-claude') || 'claude-sonnet-4-5', installDependencies: true, maxDuration: 300, keepAlive: false, diff --git a/components/repo-issues.tsx b/components/repo-issues.tsx index 75b1dfff..39559185 100644 --- a/components/repo-issues.tsx +++ b/components/repo-issues.tsx @@ -39,10 +39,9 @@ const CODING_AGENTS = [ const AGENT_MODELS = { claude: [ - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], codex: [ { value: 'openai/gpt-5.1', label: 'GPT-5.1' }, @@ -81,14 +80,14 @@ const AGENT_MODELS = { { value: 'gpt-5-mini', label: 'GPT-5 mini' }, { value: 'gpt-5-nano', label: 'GPT-5 nano' }, { value: 'gpt-4.1', label: 'GPT-4.1' }, - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], } as const const DEFAULT_MODELS = { - claude: 'claude-sonnet-4-5-20250929', + claude: 'claude-sonnet-4-5', codex: 'openai/gpt-5.1', copilot: 'claude-sonnet-4.5', cursor: 'auto', diff --git a/components/repo-pull-requests.tsx b/components/repo-pull-requests.tsx index b8868199..a751ae6c 100644 --- a/components/repo-pull-requests.tsx +++ b/components/repo-pull-requests.tsx @@ -47,10 +47,9 @@ const CODING_AGENTS = [ const AGENT_MODELS = { claude: [ - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], codex: [ { value: 'openai/gpt-5.1', label: 'GPT-5.1' }, @@ -89,14 +88,14 @@ const AGENT_MODELS = { { value: 'gpt-5-mini', label: 'GPT-5 mini' }, { value: 'gpt-5-nano', label: 'GPT-5 nano' }, { value: 'gpt-4.1', label: 'GPT-4.1' }, - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], } as const const DEFAULT_MODELS = { - claude: 'claude-sonnet-4-5-20250929', + claude: 'claude-sonnet-4-5', codex: 'openai/gpt-5.1', copilot: 'claude-sonnet-4.5', cursor: 'auto', diff --git a/components/revert-commit-dialog.tsx b/components/revert-commit-dialog.tsx index 32285e95..702cf528 100644 --- a/components/revert-commit-dialog.tsx +++ b/components/revert-commit-dialog.tsx @@ -61,10 +61,9 @@ const CODING_AGENTS = [ const AGENT_MODELS = { claude: [ - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], codex: [ { value: 'openai/gpt-5', label: 'GPT-5' }, @@ -99,14 +98,14 @@ const AGENT_MODELS = { { value: 'gpt-5-mini', label: 'GPT-5 Mini' }, { value: 'gpt-5-nano', label: 'GPT-5 Nano' }, { value: 'gpt-4.1', label: 'GPT-4.1' }, - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], } as const const DEFAULT_MODELS = { - claude: 'claude-sonnet-4-5-20250929', + claude: 'claude-sonnet-4-5', codex: 'openai/gpt-5.1', copilot: 'claude-sonnet-4.5', cursor: 'auto', diff --git a/components/task-details.tsx b/components/task-details.tsx index ab6a84c1..9a6bbed6 100644 --- a/components/task-details.tsx +++ b/components/task-details.tsx @@ -110,10 +110,9 @@ const CODING_AGENTS = [ const AGENT_MODELS = { claude: [ - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], codex: [ { value: 'openai/gpt-5.1', label: 'GPT-5.1' }, @@ -152,14 +151,14 @@ const AGENT_MODELS = { { value: 'gpt-5-mini', label: 'GPT-5 Mini' }, { value: 'gpt-5-nano', label: 'GPT-5 Nano' }, { value: 'gpt-4.1', label: 'GPT-4.1' }, - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], } as const const DEFAULT_MODELS = { - claude: 'claude-sonnet-4-5-20250929', + claude: 'claude-sonnet-4-5', codex: 'openai/gpt-5.1', copilot: 'claude-sonnet-4.5', cursor: 'auto', @@ -681,10 +680,9 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps // Model mappings for all agents const AGENT_MODELS: Record> = { claude: [ - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], codex: [ { value: 'openai/gpt-5', label: 'GPT-5' }, @@ -719,9 +717,9 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps { value: 'gpt-5-mini', label: 'GPT-5 mini' }, { value: 'gpt-5-nano', label: 'GPT-5 nano' }, { value: 'gpt-4.1', label: 'GPT-4.1' }, - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], } diff --git a/components/task-form.tsx b/components/task-form.tsx index ac2216f8..375f9e95 100644 --- a/components/task-form.tsx +++ b/components/task-form.tsx @@ -72,11 +72,9 @@ const CODING_AGENTS = [ // Model options for each agent const AGENT_MODELS = { claude: [ - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-opus-4-5-20250201', label: 'Opus 4.5' }, - { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], codex: [ { value: 'openai/gpt-5.1', label: 'GPT-5.1' }, @@ -116,15 +114,15 @@ const AGENT_MODELS = { { value: 'gpt-5-mini', label: 'GPT-5 mini' }, { value: 'gpt-5-nano', label: 'GPT-5 nano' }, { value: 'gpt-4.1', label: 'GPT-4.1' }, - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], } as const // Default models for each agent const DEFAULT_MODELS = { - claude: 'claude-sonnet-4-5-20250929', + claude: 'claude-sonnet-4-5', codex: 'openai/gpt-5.1', copilot: 'claude-sonnet-4.5', cursor: 'auto', diff --git a/components/task-sidebar.tsx b/components/task-sidebar.tsx index 6f06f58e..0e978efa 100644 --- a/components/task-sidebar.tsx +++ b/components/task-sidebar.tsx @@ -32,10 +32,9 @@ import { githubConnectionAtom } from '@/lib/atoms/github-connection' // Model mappings for human-friendly names const AGENT_MODELS = { claude: [ - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], codex: [ { value: 'openai/gpt-5', label: 'GPT-5' }, @@ -70,9 +69,9 @@ const AGENT_MODELS = { { value: 'gpt-5-mini', label: 'GPT-5 Mini' }, { value: 'gpt-5-nano', label: 'GPT-5 Nano' }, { value: 'gpt-4.1', label: 'GPT-4.1' }, - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], } as const diff --git a/components/tasks-list-client.tsx b/components/tasks-list-client.tsx index 9a4f8f00..f03df4d2 100644 --- a/components/tasks-list-client.tsx +++ b/components/tasks-list-client.tsx @@ -39,10 +39,9 @@ interface TasksListClientProps { // Model mappings for human-friendly names const AGENT_MODELS = { claude: [ - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], codex: [ { value: 'openai/gpt-5.1', label: 'GPT-5.1' }, @@ -81,9 +80,9 @@ const AGENT_MODELS = { { value: 'gpt-5-mini', label: 'GPT-5 Mini' }, { value: 'gpt-5-nano', label: 'GPT-5 Nano' }, { value: 'gpt-4.1', label: 'GPT-4.1' }, - { value: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' }, - { value: 'claude-sonnet-4-20250514', label: 'Sonnet 4' }, - { value: 'claude-opus-4-1-20250805', label: 'Opus 4.1' }, + { value: 'claude-sonnet-4-5', label: 'Sonnet 4.5' }, + { value: 'claude-opus-4-5', label: 'Opus 4.5' }, + { value: 'claude-haiku-4-5', label: 'Haiku 4.5' }, ], } as const diff --git a/lib/sandbox/agents/claude.ts b/lib/sandbox/agents/claude.ts index 83093b82..41a24ca1 100644 --- a/lib/sandbox/agents/claude.ts +++ b/lib/sandbox/agents/claude.ts @@ -145,7 +145,7 @@ export async function installClaudeCLI( } } - const modelToUse = selectedModel || 'claude-sonnet-4-5-20250929' + const modelToUse = selectedModel || 'claude-sonnet-4-5' const configFileCmd = `mkdir -p $HOME/.config/claude && cat > $HOME/.config/claude/config.json << 'EOF' { "api_key": "${process.env.ANTHROPIC_API_KEY}", @@ -244,7 +244,7 @@ export async function executeClaudeInSandbox( } // Log what we're trying to do - const modelToUse = selectedModel || 'claude-sonnet-4-5-20250929' + const modelToUse = selectedModel || 'claude-sonnet-4-5' if (logger) { await logger.info( `Attempting to execute Claude CLI with model ${modelToUse} and instruction: ${instruction.substring(0, 100)}...`, From af8250012fa35e943a012acb4330e8f08ebb5a01 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 19:14:13 -0600 Subject: [PATCH 10/11] fixes --- components/task-details.tsx | 84 +++++++++++++++++++++++++++---------- lib/hooks/use-task.ts | 22 ++++++++++ lib/sandbox/creation.ts | 12 +++--- 3 files changed, 90 insertions(+), 28 deletions(-) diff --git a/components/task-details.tsx b/components/task-details.tsx index 9a6bbed6..9fbe7abe 100644 --- a/components/task-details.tsx +++ b/components/task-details.tsx @@ -1928,8 +1928,23 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps + {task.sandboxUrl && ( + <> + window.open(task.sandboxUrl!, '_blank')}> + Open in New Tab + + { + navigator.clipboard.writeText(task.sandboxUrl!) + }} + > + Copy URL + + + )} {task.keepAlive && ( <> + {task.sandboxUrl && } {sandboxHealth === 'stopped' || !task.sandboxUrl ? ( {isStartingSandbox ? ( @@ -1956,16 +1971,19 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps )} {sandboxHealth === 'running' && ( - - {isRestartingDevServer ? ( - <> - - Restarting... - - ) : ( - 'Restart Dev Server' - )} - + <> + {(task.sandboxUrl || task.keepAlive) && } + + {isRestartingDevServer ? ( + <> + + Restarting... + + ) : ( + 'Restart Dev Server' + )} + + )} @@ -2160,8 +2178,23 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps + {task.sandboxUrl && ( + <> + window.open(task.sandboxUrl!, '_blank')}> + Open in New Tab + + { + navigator.clipboard.writeText(task.sandboxUrl!) + }} + > + Copy URL + + + )} {task.keepAlive && ( <> + {task.sandboxUrl && } {task.sandboxUrl ? ( {isStoppingSandbox ? ( @@ -2187,19 +2220,24 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps )} )} - - {isRestartingDevServer ? ( - <> - - Restarting... - - ) : ( - 'Restart Dev Server' - )} - + {task.sandboxUrl && ( + <> + {task.keepAlive && } + + {isRestartingDevServer ? ( + <> + + Restarting... + + ) : ( + 'Restart Dev Server' + )} + + + )}
diff --git a/lib/hooks/use-task.ts b/lib/hooks/use-task.ts index 51102a4a..854b857a 100644 --- a/lib/hooks/use-task.ts +++ b/lib/hooks/use-task.ts @@ -9,6 +9,7 @@ export function useTask(taskId: string) { const [error, setError] = useState(null) const attemptCountRef = useRef(0) const hasFoundTaskRef = useRef(false) + const pendingSandboxRefetchRef = useRef(false) const fetchTask = useCallback(async () => { let errorOccurred = false @@ -81,5 +82,26 @@ export function useTask(taskId: string) { } }, [fetchTask, isLoading]) + // Watch for sandbox URL becoming available - trigger immediate refetch when logs indicate it's ready + useEffect(() => { + if (!task || task.sandboxUrl) { + pendingSandboxRefetchRef.current = false + return + } + + // Check if logs indicate dev server is running but we don't have sandboxUrl yet + const logs = task.logs || [] + const hasDevServerLog = logs.some( + (log) => + log.message === 'Development server is running' || log.message === 'Development server started' + ) + + if (hasDevServerLog && !pendingSandboxRefetchRef.current) { + pendingSandboxRefetchRef.current = true + // Trigger an immediate refetch to get the sandboxUrl + setTimeout(() => fetchTask(), 500) + } + }, [task, fetchTask]) + return { task, isLoading, error, refetch: fetchTask } } diff --git a/lib/sandbox/creation.ts b/lib/sandbox/creation.ts index 47eadbb1..2902a782 100644 --- a/lib/sandbox/creation.ts +++ b/lib/sandbox/creation.ts @@ -1,4 +1,5 @@ import { Sandbox } from '@vercel/sandbox' +import { Writable } from 'stream' import { validateEnvironmentVariables, createAuthenticatedRepoUrl } from './config' import { runCommandInSandbox, runInProject, PROJECT_DIR } from './commands' import { generateId } from '@/lib/utils/id' @@ -331,7 +332,9 @@ export async function createSandbox(config: SandboxConfig, logger: TaskLogger): const packageJsonRead = await runInProject(sandbox, 'cat', ['package.json']) if (packageJsonRead.success && packageJsonRead.output) { try { - const packageJson = JSON.parse(packageJsonRead.output) + // Trim the output to remove any extra whitespace/newlines + const packageJsonContent = packageJsonRead.output.trim() + const packageJson = JSON.parse(packageJsonContent) const hasDevScript = packageJson?.scripts?.dev // Detect Vite projects (use port 5173) @@ -416,9 +419,6 @@ fi // Start dev server in detached mode (runs in background) with log capture const fullDevCommand = devArgs.length > 0 ? `${devCommand} ${devArgs.join(' ')}` : devCommand - // Import Writable for stream capture - const { Writable } = await import('stream') - const captureServerStdout = new Writable({ write(chunk: Buffer | string, _encoding: BufferEncoding, callback: (error?: Error | null) => void) { const lines = chunk @@ -461,7 +461,9 @@ fi await logger.info('Development server is running') } } catch (parseError) { - // If package.json parsing fails, just continue without starting dev server + // If package.json parsing fails, log the error details and continue without starting dev server + const errorMessage = parseError instanceof Error ? parseError.message : 'Unknown error' + console.error('Failed to parse package.json:', errorMessage) await logger.info('Could not parse package.json, skipping auto-start of dev server') } } From 93ba054b2a4d7899ec89b7c63a55356df93db663 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Jan 2026 19:21:00 -0600 Subject: [PATCH 11/11] format --- components/task-details.tsx | 5 +---- lib/hooks/use-task.ts | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/task-details.tsx b/components/task-details.tsx index 9fbe7abe..246a0066 100644 --- a/components/task-details.tsx +++ b/components/task-details.tsx @@ -2223,10 +2223,7 @@ export function TaskDetails({ task, maxSandboxDuration = 300 }: TaskDetailsProps {task.sandboxUrl && ( <> {task.keepAlive && } - + {isRestartingDevServer ? ( <> diff --git a/lib/hooks/use-task.ts b/lib/hooks/use-task.ts index 854b857a..557a810b 100644 --- a/lib/hooks/use-task.ts +++ b/lib/hooks/use-task.ts @@ -92,8 +92,7 @@ export function useTask(taskId: string) { // Check if logs indicate dev server is running but we don't have sandboxUrl yet const logs = task.logs || [] const hasDevServerLog = logs.some( - (log) => - log.message === 'Development server is running' || log.message === 'Development server started' + (log) => log.message === 'Development server is running' || log.message === 'Development server started', ) if (hasDevServerLog && !pendingSandboxRefetchRef.current) {