import { useState, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate, useSearchParams } from "@/lib/router"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { agentsApi } from "../api/agents"; import { queryKeys } from "../lib/queryKeys"; import { AGENT_ROLES } from "@paperclipai/shared"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Shield, User } from "lucide-react"; import { cn, agentUrl } from "../lib/utils"; import { roleLabels } from "../components/agent-config-primitives"; import { AgentConfigForm, type CreateConfigValues } from "../components/AgentConfigForm"; import { defaultCreateValues } from "../components/agent-config-defaults"; import { getUIAdapter } from "../adapters"; import { AgentIcon } from "../components/AgentIconPicker"; import { DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX, DEFAULT_CODEX_LOCAL_MODEL, } from "@paperclipai/adapter-codex-local"; import { DEFAULT_CURSOR_LOCAL_MODEL } from "@paperclipai/adapter-cursor-local"; import { DEFAULT_GEMINI_LOCAL_MODEL } from "@paperclipai/adapter-gemini-local"; const SUPPORTED_ADVANCED_ADAPTER_TYPES = new Set([ "claude_local", "codex_local", "gemini_local", "opencode_local", "pi_local", "cursor", "openclaw_gateway", ]); function createValuesForAdapterType( adapterType: CreateConfigValues["adapterType"], ): CreateConfigValues { const { adapterType: _discard, ...defaults } = defaultCreateValues; const nextValues: CreateConfigValues = { ...defaults, adapterType }; if (adapterType === "codex_local") { nextValues.model = DEFAULT_CODEX_LOCAL_MODEL; nextValues.dangerouslyBypassSandbox = DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX; } else if (adapterType === "gemini_local") { nextValues.model = DEFAULT_GEMINI_LOCAL_MODEL; } else if (adapterType === "cursor") { nextValues.model = DEFAULT_CURSOR_LOCAL_MODEL; } else if (adapterType === "opencode_local") { nextValues.model = ""; } return nextValues; } export function NewAgent() { const { selectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const queryClient = useQueryClient(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const presetAdapterType = searchParams.get("adapterType"); const [name, setName] = useState(""); const [title, setTitle] = useState(""); const [role, setRole] = useState("general"); const [reportsTo, setReportsTo] = useState(""); const [configValues, setConfigValues] = useState(defaultCreateValues); const [roleOpen, setRoleOpen] = useState(false); const [reportsToOpen, setReportsToOpen] = useState(false); const [formError, setFormError] = useState(null); const { data: agents } = useQuery({ queryKey: queryKeys.agents.list(selectedCompanyId!), queryFn: () => agentsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const { data: adapterModels, error: adapterModelsError, isLoading: adapterModelsLoading, isFetching: adapterModelsFetching, } = useQuery({ queryKey: selectedCompanyId ? queryKeys.agents.adapterModels(selectedCompanyId, configValues.adapterType) : ["agents", "none", "adapter-models", configValues.adapterType], queryFn: () => agentsApi.adapterModels(selectedCompanyId!, configValues.adapterType), enabled: Boolean(selectedCompanyId), }); const isFirstAgent = !agents || agents.length === 0; const effectiveRole = isFirstAgent ? "ceo" : role; useEffect(() => { setBreadcrumbs([ { label: "Agents", href: "/agents" }, { label: "New Agent" }, ]); }, [setBreadcrumbs]); useEffect(() => { if (isFirstAgent) { if (!name) setName("CEO"); if (!title) setTitle("CEO"); } }, [isFirstAgent]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { const requested = presetAdapterType; if (!requested) return; if (!SUPPORTED_ADVANCED_ADAPTER_TYPES.has(requested as CreateConfigValues["adapterType"])) { return; } setConfigValues((prev) => { if (prev.adapterType === requested) return prev; return createValuesForAdapterType(requested as CreateConfigValues["adapterType"]); }); }, [presetAdapterType]); const createAgent = useMutation({ mutationFn: (data: Record) => agentsApi.hire(selectedCompanyId!, data), onSuccess: (result) => { queryClient.invalidateQueries({ queryKey: queryKeys.agents.list(selectedCompanyId!) }); queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!) }); navigate(agentUrl(result.agent)); }, onError: (error) => { setFormError(error instanceof Error ? error.message : "Failed to create agent"); }, }); function buildAdapterConfig() { const adapter = getUIAdapter(configValues.adapterType); return adapter.buildAdapterConfig(configValues); } function handleSubmit() { if (!selectedCompanyId || !name.trim()) return; setFormError(null); if (configValues.adapterType === "opencode_local") { const selectedModel = configValues.model.trim(); if (!selectedModel) { setFormError("OpenCode requires an explicit model in provider/model format."); return; } if (adapterModelsError) { setFormError( adapterModelsError instanceof Error ? adapterModelsError.message : "Failed to load OpenCode models.", ); return; } if (adapterModelsLoading || adapterModelsFetching) { setFormError("OpenCode models are still loading. Please wait and try again."); return; } const discovered = adapterModels ?? []; if (!discovered.some((entry) => entry.id === selectedModel)) { setFormError( discovered.length === 0 ? "No OpenCode models discovered. Run `opencode models` and authenticate providers." : `Configured OpenCode model is unavailable: ${selectedModel}`, ); return; } } createAgent.mutate({ name: name.trim(), role: effectiveRole, ...(title.trim() ? { title: title.trim() } : {}), ...(reportsTo ? { reportsTo } : {}), adapterType: configValues.adapterType, adapterConfig: buildAdapterConfig(), runtimeConfig: { heartbeat: { enabled: configValues.heartbeatEnabled, intervalSec: configValues.intervalSec, wakeOnDemand: true, cooldownSec: 10, maxConcurrentRuns: 1, }, }, budgetMonthlyCents: 0, }); } const currentReportsTo = (agents ?? []).find((a) => a.id === reportsTo); return (

New Agent

Advanced agent configuration

{/* Name */}
setName(e.target.value)} autoFocus />
{/* Title */}
setTitle(e.target.value)} />
{/* Property chips: Role + Reports To */}
{AGENT_ROLES.map((r) => ( ))} {(agents ?? []).map((a) => ( ))}
{/* Shared config form */} setConfigValues((prev) => ({ ...prev, ...patch }))} adapterModels={adapterModels} /> {/* Footer */}
{isFirstAgent && (

This will be the CEO

)} {formError && (

{formError}

)}
); }