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:
parent
f74ebf379f
commit
c267420632
|
|
@ -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]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -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"}
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
Loading…
Reference in New Issue