From c26742063225a9c8d7c05f87336a608f38e1ffbf Mon Sep 17 00:00:00 2001 From: VenkataSakethDakuri <126963412+VenkataSakethDakuri@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:56:00 +0530 Subject: [PATCH] feat(projects): default worker and orchestrator agents to claude-code (#2321) Pre-fill both role agents with claude-code in the create-project dialog and project settings instead of requiring a selection; the required-agent validation gate is removed. Users can still change either agent at creation or later in settings. --- .../components/CreateProjectAgentSheet.tsx | 12 ++++---- .../components/ProjectSettingsForm.test.tsx | 28 +++++++++++++------ .../components/ProjectSettingsForm.tsx | 21 +++----------- frontend/src/renderer/lib/agent-options.ts | 4 +++ 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/frontend/src/renderer/components/CreateProjectAgentSheet.tsx b/frontend/src/renderer/components/CreateProjectAgentSheet.tsx index 0f9a75f18..76f6054ed 100644 --- a/frontend/src/renderer/components/CreateProjectAgentSheet.tsx +++ b/frontend/src/renderer/components/CreateProjectAgentSheet.tsx @@ -1,7 +1,7 @@ import * as Dialog from "@radix-ui/react-dialog"; import { X } from "lucide-react"; import { memo, useEffect, useState } from "react"; -import { AGENT_OPTIONS } from "../lib/agent-options"; +import { AGENT_OPTIONS, DEFAULT_PROJECT_AGENT } from "../lib/agent-options"; import { Button } from "./ui/button"; import { Label } from "./ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; @@ -28,14 +28,14 @@ export function CreateProjectAgentSheet({ open, path, }: CreateProjectAgentSheetProps) { - const [workerAgent, setWorkerAgent] = useState(""); - const [orchestratorAgent, setOrchestratorAgent] = useState(""); + const [workerAgent, setWorkerAgent] = useState(DEFAULT_PROJECT_AGENT); + const [orchestratorAgent, setOrchestratorAgent] = useState(DEFAULT_PROJECT_AGENT); const canSubmit = workerAgent !== "" && orchestratorAgent !== "" && !isCreating; useEffect(() => { if (!open) { - setWorkerAgent(""); - setOrchestratorAgent(""); + setWorkerAgent(DEFAULT_PROJECT_AGENT); + setOrchestratorAgent(DEFAULT_PROJECT_AGENT); } }, [open, path]); @@ -142,4 +142,4 @@ export const RequiredAgentField = memo(function RequiredAgentField({ ); -}); +}); \ No newline at end of file diff --git a/frontend/src/renderer/components/ProjectSettingsForm.test.tsx b/frontend/src/renderer/components/ProjectSettingsForm.test.tsx index 9ecb1cbaa..26776054d 100644 --- a/frontend/src/renderer/components/ProjectSettingsForm.test.tsx +++ b/frontend/src/renderer/components/ProjectSettingsForm.test.tsx @@ -170,7 +170,7 @@ describe("ProjectSettingsForm", () => { expect(screen.queryByText("Saved.")).not.toBeInTheDocument(); }); - it("requires worker and orchestrator agents for existing projects missing role config", async () => { + it("defaults worker and orchestrator to claude-code for projects missing role config", async () => { getMock.mockResolvedValue({ data: { status: "ok", @@ -189,15 +189,25 @@ describe("ProjectSettingsForm", () => { renderSettings(); - expect(await screen.findByText("Worker and orchestrator agents are required.")).toBeInTheDocument(); - expect(screen.getByRole("combobox", { name: "Default worker agent" })).toHaveTextContent("Select worker agent"); - expect(screen.getByRole("combobox", { name: "Default orchestrator agent" })).toHaveTextContent( - "Select orchestrator agent", - ); + const workerAgent = await screen.findByRole("combobox", { name: "Default worker agent" }); + const orchestratorAgent = screen.getByRole("combobox", { name: "Default orchestrator agent" }); + expect(workerAgent).toHaveTextContent("claude-code"); + expect(orchestratorAgent).toHaveTextContent("claude-code"); + expect(screen.queryByText("Worker and orchestrator agents are required.")).not.toBeInTheDocument(); await userEvent.click(screen.getByRole("button", { name: "Save changes" })); - expect(await screen.findAllByText("Worker and orchestrator agents are required.")).toHaveLength(2); - expect(putMock).not.toHaveBeenCalled(); + await waitFor(() => expect(putMock).toHaveBeenCalledTimes(1)); + expect(putMock).toHaveBeenCalledWith("/api/v1/projects/{id}/config", { + params: { path: { id: "proj-1" } }, + body: { + config: { + defaultBranch: "main", + worker: { agent: "claude-code" }, + orchestrator: { agent: "claude-code" }, + }, + }, + }); + expect(await screen.findByText("Saved.")).toBeInTheDocument(); }); -}); +}); \ No newline at end of file diff --git a/frontend/src/renderer/components/ProjectSettingsForm.tsx b/frontend/src/renderer/components/ProjectSettingsForm.tsx index 9b746b7cb..ea51b7fd4 100644 --- a/frontend/src/renderer/components/ProjectSettingsForm.tsx +++ b/frontend/src/renderer/components/ProjectSettingsForm.tsx @@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import type { components } from "../../api/schema"; import { apiClient, apiErrorMessage } from "../lib/api-client"; +import { DEFAULT_PROJECT_AGENT } from "../lib/agent-options"; import { workspaceQueryKey } from "../hooks/useWorkspaceQuery"; import { RequiredAgentField } from "./CreateProjectAgentSheet"; import { DashboardSubhead } from "./DashboardSubhead"; @@ -69,15 +70,13 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje const [form, setForm] = useState({ defaultBranch: config.defaultBranch ?? project.defaultBranch ?? "", sessionPrefix: config.sessionPrefix ?? "", - workerAgent: config.worker?.agent ?? "", - orchestratorAgent: config.orchestrator?.agent ?? "", + workerAgent: config.worker?.agent || DEFAULT_PROJECT_AGENT, + orchestratorAgent: config.orchestrator?.agent || DEFAULT_PROJECT_AGENT, model: config.agentConfig?.model ?? "", permissions: config.agentConfig?.permissions ?? "", reviewerHarness: config.reviewers?.[0]?.harness ?? "", }); const [savedAt, setSavedAt] = useState(null); - const [validationError, setValidationError] = useState(null); - const missingRequiredAgent = form.workerAgent === "" || form.orchestratorAgent === ""; const mutation = useMutation({ mutationFn: async () => { @@ -104,7 +103,6 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje }, onSuccess: () => { setSavedAt(Date.now()); - setValidationError(null); void queryClient.invalidateQueries({ queryKey: ["project", projectId] }); onSaved(); }, @@ -116,11 +114,6 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje onSubmit={(event) => { event.preventDefault(); setSavedAt(null); - if (missingRequiredAgent) { - setValidationError("Worker and orchestrator agents are required."); - return; - } - setValidationError(null); mutation.mutate(); }} > @@ -171,7 +164,6 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje value={form.workerAgent} placeholder="Select worker agent" label="Default worker agent" - invalid={validationError !== null && form.workerAgent === ""} onChange={(v) => setForm((f) => ({ ...f, workerAgent: v }))} /> setForm((f) => ({ ...f, orchestratorAgent: v }))} /> - {missingRequiredAgent && ( -

Worker and orchestrator agents are required.

- )} {mutation.isPending ? "Saving…" : "Save changes"} - {validationError && {validationError}} {mutation.isError && ( {mutation.error instanceof Error ? mutation.error.message : "Save failed"} @@ -313,4 +300,4 @@ function CenteredNote({ children }: { children: React.ReactNode }) { // rather than an empty {} the daemon would persist. function blankToUndefined(obj: T): T | undefined { return Object.values(obj).some((v) => v !== undefined) ? obj : undefined; -} +} \ No newline at end of file diff --git a/frontend/src/renderer/lib/agent-options.ts b/frontend/src/renderer/lib/agent-options.ts index d26c9a4c0..7cdc56eee 100644 --- a/frontend/src/renderer/lib/agent-options.ts +++ b/frontend/src/renderer/lib/agent-options.ts @@ -23,3 +23,7 @@ export const AGENT_OPTIONS = [ "pi", "autohand", ] as const; + +// The agent new projects use by default, and the fallback for worker/orchestrator +// role fields that have no explicit configuration. Users can change it per project. +export const DEFAULT_PROJECT_AGENT: (typeof AGENT_OPTIONS)[number] = "claude-code"; \ No newline at end of file