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.
This commit is contained in:
VenkataSakethDakuri 2026-07-01 21:56:00 +05:30 committed by GitHub
parent f74ebf379f
commit c267420632
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 33 additions and 32 deletions

View File

@ -1,7 +1,7 @@
import * as Dialog from "@radix-ui/react-dialog"; import * as Dialog from "@radix-ui/react-dialog";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { memo, useEffect, useState } from "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 { Button } from "./ui/button";
import { Label } from "./ui/label"; import { Label } from "./ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
@ -28,14 +28,14 @@ export function CreateProjectAgentSheet({
open, open,
path, path,
}: CreateProjectAgentSheetProps) { }: CreateProjectAgentSheetProps) {
const [workerAgent, setWorkerAgent] = useState(""); const [workerAgent, setWorkerAgent] = useState<string>(DEFAULT_PROJECT_AGENT);
const [orchestratorAgent, setOrchestratorAgent] = useState(""); const [orchestratorAgent, setOrchestratorAgent] = useState<string>(DEFAULT_PROJECT_AGENT);
const canSubmit = workerAgent !== "" && orchestratorAgent !== "" && !isCreating; const canSubmit = workerAgent !== "" && orchestratorAgent !== "" && !isCreating;
useEffect(() => { useEffect(() => {
if (!open) { if (!open) {
setWorkerAgent(""); setWorkerAgent(DEFAULT_PROJECT_AGENT);
setOrchestratorAgent(""); setOrchestratorAgent(DEFAULT_PROJECT_AGENT);
} }
}, [open, path]); }, [open, path]);

View File

@ -170,7 +170,7 @@ describe("ProjectSettingsForm", () => {
expect(screen.queryByText("Saved.")).not.toBeInTheDocument(); 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({ getMock.mockResolvedValue({
data: { data: {
status: "ok", status: "ok",
@ -189,15 +189,25 @@ describe("ProjectSettingsForm", () => {
renderSettings(); renderSettings();
expect(await screen.findByText("Worker and orchestrator agents are required.")).toBeInTheDocument(); const workerAgent = await screen.findByRole("combobox", { name: "Default worker agent" });
expect(screen.getByRole("combobox", { name: "Default worker agent" })).toHaveTextContent("Select worker agent"); const orchestratorAgent = screen.getByRole("combobox", { name: "Default orchestrator agent" });
expect(screen.getByRole("combobox", { name: "Default orchestrator agent" })).toHaveTextContent( expect(workerAgent).toHaveTextContent("claude-code");
"Select orchestrator agent", 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" })); await userEvent.click(screen.getByRole("button", { name: "Save changes" }));
expect(await screen.findAllByText("Worker and orchestrator agents are required.")).toHaveLength(2); await waitFor(() => expect(putMock).toHaveBeenCalledTimes(1));
expect(putMock).not.toHaveBeenCalled(); 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();
}); });
}); });

View File

@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useState } from "react"; import { useState } from "react";
import type { components } from "../../api/schema"; import type { components } from "../../api/schema";
import { apiClient, apiErrorMessage } from "../lib/api-client"; import { apiClient, apiErrorMessage } from "../lib/api-client";
import { DEFAULT_PROJECT_AGENT } from "../lib/agent-options";
import { workspaceQueryKey } from "../hooks/useWorkspaceQuery"; import { workspaceQueryKey } from "../hooks/useWorkspaceQuery";
import { RequiredAgentField } from "./CreateProjectAgentSheet"; import { RequiredAgentField } from "./CreateProjectAgentSheet";
import { DashboardSubhead } from "./DashboardSubhead"; import { DashboardSubhead } from "./DashboardSubhead";
@ -69,15 +70,13 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje
const [form, setForm] = useState({ const [form, setForm] = useState({
defaultBranch: config.defaultBranch ?? project.defaultBranch ?? "", defaultBranch: config.defaultBranch ?? project.defaultBranch ?? "",
sessionPrefix: config.sessionPrefix ?? "", sessionPrefix: config.sessionPrefix ?? "",
workerAgent: config.worker?.agent ?? "", workerAgent: config.worker?.agent || DEFAULT_PROJECT_AGENT,
orchestratorAgent: config.orchestrator?.agent ?? "", orchestratorAgent: config.orchestrator?.agent || DEFAULT_PROJECT_AGENT,
model: config.agentConfig?.model ?? "", model: config.agentConfig?.model ?? "",
permissions: config.agentConfig?.permissions ?? "", permissions: config.agentConfig?.permissions ?? "",
reviewerHarness: config.reviewers?.[0]?.harness ?? "", reviewerHarness: config.reviewers?.[0]?.harness ?? "",
}); });
const [savedAt, setSavedAt] = useState<number | null>(null); const [savedAt, setSavedAt] = useState<number | null>(null);
const [validationError, setValidationError] = useState<string | null>(null);
const missingRequiredAgent = form.workerAgent === "" || form.orchestratorAgent === "";
const mutation = useMutation({ const mutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
@ -104,7 +103,6 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje
}, },
onSuccess: () => { onSuccess: () => {
setSavedAt(Date.now()); setSavedAt(Date.now());
setValidationError(null);
void queryClient.invalidateQueries({ queryKey: ["project", projectId] }); void queryClient.invalidateQueries({ queryKey: ["project", projectId] });
onSaved(); onSaved();
}, },
@ -116,11 +114,6 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje
onSubmit={(event) => { onSubmit={(event) => {
event.preventDefault(); event.preventDefault();
setSavedAt(null); setSavedAt(null);
if (missingRequiredAgent) {
setValidationError("Worker and orchestrator agents are required.");
return;
}
setValidationError(null);
mutation.mutate(); mutation.mutate();
}} }}
> >
@ -171,7 +164,6 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje
value={form.workerAgent} value={form.workerAgent}
placeholder="Select worker agent" placeholder="Select worker agent"
label="Default worker agent" label="Default worker agent"
invalid={validationError !== null && form.workerAgent === ""}
onChange={(v) => setForm((f) => ({ ...f, workerAgent: v }))} onChange={(v) => setForm((f) => ({ ...f, workerAgent: v }))}
/> />
<RequiredAgentField <RequiredAgentField
@ -179,12 +171,8 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje
value={form.orchestratorAgent} value={form.orchestratorAgent}
placeholder="Select orchestrator agent" placeholder="Select orchestrator agent"
label="Default orchestrator agent" label="Default orchestrator agent"
invalid={validationError !== null && form.orchestratorAgent === ""}
onChange={(v) => setForm((f) => ({ ...f, orchestratorAgent: v }))} onChange={(v) => setForm((f) => ({ ...f, orchestratorAgent: v }))}
/> />
{missingRequiredAgent && (
<p className="text-[12px] leading-5 text-error">Worker and orchestrator agents are required.</p>
)}
<Field label="Model override" htmlFor="model"> <Field label="Model override" htmlFor="model">
<input <input
id="model" id="model"
@ -223,7 +211,6 @@ function SettingsBody({ project, projectId, onSaved }: { project: Project; proje
<Button type="submit" variant="primary" disabled={mutation.isPending}> <Button type="submit" variant="primary" disabled={mutation.isPending}>
{mutation.isPending ? "Saving…" : "Save changes"} {mutation.isPending ? "Saving…" : "Save changes"}
</Button> </Button>
{validationError && <span className="text-[12px] text-error">{validationError}</span>}
{mutation.isError && ( {mutation.isError && (
<span className="text-[12px] text-error"> <span className="text-[12px] text-error">
{mutation.error instanceof Error ? mutation.error.message : "Save failed"} {mutation.error instanceof Error ? mutation.error.message : "Save failed"}

View File

@ -23,3 +23,7 @@ export const AGENT_OPTIONS = [
"pi", "pi",
"autohand", "autohand",
] as const; ] 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";