From 1e0245ca9a8297e1fd82c4cc20cfc653cdee1cf3 Mon Sep 17 00:00:00 2001 From: centdix Date: Sat, 21 Feb 2026 19:11:26 +0000 Subject: [PATCH] feat: add codex as agent choice in worktree creation dialog Add agent selector (Claude/Codex) to the create worktree dialog, orthogonal to the profile choice. Selection is persisted alongside the profile default. Backend builds the appropriate command per agent: codex uses --full-auto for sandbox, claude uses --dangerously-skip-permissions with --append-system-prompt. Co-Authored-By: Claude Opus 4.6 --- dev-dashboard/backend/src/server.ts | 9 ++-- dev-dashboard/backend/src/workmux.ts | 20 ++++++--- dev-dashboard/frontend/src/App.svelte | 6 +-- .../src/lib/CreateWorktreeDialog.svelte | 41 ++++++++++++++++--- dev-dashboard/frontend/src/lib/api.ts | 4 +- 5 files changed, 63 insertions(+), 17 deletions(-) diff --git a/dev-dashboard/backend/src/server.ts b/dev-dashboard/backend/src/server.ts index 0d4a9b8dea..bb299b9298 100644 --- a/dev-dashboard/backend/src/server.ts +++ b/dev-dashboard/backend/src/server.ts @@ -8,6 +8,7 @@ import { sendPrompt, readEnvLocal, type Profile, + type Agent, } from "./workmux"; import { reconcileForwarding, stopAll } from "./socat"; import { @@ -191,14 +192,16 @@ async function handleApi(req: Request, url: URL): Promise { // POST /api/worktrees if (parts[0] === "worktrees" && parts.length === 1 && method === "POST") { - const body = await req.json() as { branch?: string; prompt?: string; profile?: string }; + const body = await req.json() as { branch?: string; prompt?: string; profile?: string; agent?: string }; if (!body.branch) { return errorResponse("branch is required", 400); } const validProfiles = ["full", "agent-only", "agent-yolo"] as const; + const validAgents = ["claude", "codex"] as const; const profile = validProfiles.includes(body.profile as any) ? body.profile as Profile : "agent-only"; - console.log(`[worktree:add] branch=${body.branch} profile=${profile}${body.prompt ? ` prompt="${body.prompt.slice(0, 80)}"` : ""}`); - const result = await addWorktree(body.branch, { prompt: body.prompt, profile }); + const agent = validAgents.includes(body.agent as any) ? body.agent as Agent : "claude"; + console.log(`[worktree:add] branch=${body.branch} agent=${agent} profile=${profile}${body.prompt ? ` prompt="${body.prompt.slice(0, 80)}"` : ""}`); + const result = await addWorktree(body.branch, { prompt: body.prompt, profile, agent }); console.log(`[worktree:add] done branch=${body.branch}: ${result}`); return jsonResponse({ message: result }, 201); } diff --git a/dev-dashboard/backend/src/workmux.ts b/dev-dashboard/backend/src/workmux.ts index dc35a9c011..fe5e663e64 100644 --- a/dev-dashboard/backend/src/workmux.ts +++ b/dev-dashboard/backend/src/workmux.ts @@ -80,6 +80,7 @@ async function runChecked(args: string[]): Promise { } export type Profile = "full" | "agent-only" | "agent-yolo"; +export type Agent = "claude" | "codex"; export { readEnvLocal } from "./env"; @@ -99,9 +100,17 @@ function buildSystemPrompt(profile: Profile, env: Record): strin return lines.join(" "); } -function buildClaudeCmd(profile: Profile, env: Record): string { +function buildAgentCmd(profile: Profile, env: Record, agent: Agent): string { const prompt = buildSystemPrompt(profile, env); + if (agent === "codex") { + if (profile === "agent-yolo") { + return `workmux sandbox agent -- codex --full-auto`; + } + return "codex"; + } + + // Claude if (profile === "agent-yolo") { // Double-escape: outer single quotes for the host shell, // inner double quotes to survive workmux sandbox's sh -c @@ -114,9 +123,10 @@ function buildClaudeCmd(profile: Profile, env: Record): string { export async function addWorktree( branch: string, - opts?: { prompt?: string; profile?: Profile } + opts?: { prompt?: string; profile?: Profile; agent?: Agent } ): Promise { const profile = opts?.profile ?? "full"; + const agent = opts?.agent ?? "claude"; const args: string[] = ["workmux", "add", "-b"]; // -b = background (don't switch tmux) // Skip default pane commands for non-full profiles @@ -160,9 +170,9 @@ export async function addWorktree( Bun.spawnSync(["tmux", "kill-pane", "-t", `${windowTarget}.${paneIds[i]}`]); } // Build and send claude command with environment-aware system prompt - const claudeCmd = buildClaudeCmd(profile, env); - console.log(`[workmux] sending command to ${windowTarget}.0:\n${claudeCmd}`); - Bun.spawnSync(["tmux", "send-keys", "-t", `${windowTarget}.0`, claudeCmd, "Enter"]); + const agentCmd = buildAgentCmd(profile, env, agent); + console.log(`[workmux] sending command to ${windowTarget}.0:\n${agentCmd}`); + Bun.spawnSync(["tmux", "send-keys", "-t", `${windowTarget}.0`, agentCmd, "Enter"]); // Open a shell pane on the right (1/3 width) in the worktree dir Bun.spawnSync(["tmux", "split-window", "-h", "-t", `${windowTarget}.0`, "-l", "33%", "-c", wtDir]); // Keep focus on the agent pane (left) diff --git a/dev-dashboard/frontend/src/App.svelte b/dev-dashboard/frontend/src/App.svelte index e75ec33412..edbc6fe918 100644 --- a/dev-dashboard/frontend/src/App.svelte +++ b/dev-dashboard/frontend/src/App.svelte @@ -6,7 +6,7 @@ import ConfirmDialog from "./lib/ConfirmDialog.svelte"; import CreateWorktreeDialog from "./lib/CreateWorktreeDialog.svelte"; import type { WorktreeInfo } from "./lib/types"; - import type { Profile } from "./lib/api"; + import type { Profile, Agent } from "./lib/api"; import * as api from "./lib/api"; let worktrees = $state([]); @@ -40,11 +40,11 @@ return result; } - async function handleCreate(name: string, profile: Profile) { + async function handleCreate(name: string, profile: Profile, agent: Agent) { const branch = name || randomName(8); creating = true; try { - await api.createWorktree(branch, profile); + await api.createWorktree(branch, profile, agent); await api.openWorktree(branch); showCreateDialog = false; await refresh(); diff --git a/dev-dashboard/frontend/src/lib/CreateWorktreeDialog.svelte b/dev-dashboard/frontend/src/lib/CreateWorktreeDialog.svelte index 0e34d6ae41..ec0c3127f5 100644 --- a/dev-dashboard/frontend/src/lib/CreateWorktreeDialog.svelte +++ b/dev-dashboard/frontend/src/lib/CreateWorktreeDialog.svelte @@ -1,5 +1,10 @@