import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; import { useDialog } from "../context/DialogContext"; import { useCompany } from "../context/CompanyContext"; import { agentsApi } from "../api/agents"; import { queryKeys } from "../lib/queryKeys"; import { AGENT_ROLES, AGENT_ADAPTER_TYPES } from "@paperclip/shared"; import { Dialog, DialogContent, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Maximize2, Minimize2, Bot, User, Shield, ChevronDown, ChevronRight, } from "lucide-react"; import { cn } from "../lib/utils"; const roleLabels: Record = { ceo: "CEO", cto: "CTO", cmo: "CMO", cfo: "CFO", engineer: "Engineer", designer: "Designer", pm: "PM", qa: "QA", devops: "DevOps", researcher: "Researcher", general: "General", }; const adapterLabels: Record = { claude_local: "Claude (local)", codex_local: "Codex (local)", process: "Process", http: "HTTP", }; export function NewAgentDialog() { const { newAgentOpen, closeNewAgent } = useDialog(); const { selectedCompanyId, selectedCompany } = useCompany(); const queryClient = useQueryClient(); const navigate = useNavigate(); const [expanded, setExpanded] = useState(false); // Identity const [name, setName] = useState(""); const [title, setTitle] = useState(""); const [role, setRole] = useState("general"); const [reportsTo, setReportsTo] = useState(""); const [capabilities, setCapabilities] = useState(""); // Adapter const [adapterType, setAdapterType] = useState("claude_local"); const [cwd, setCwd] = useState(""); const [promptTemplate, setPromptTemplate] = useState(""); const [bootstrapPrompt, setBootstrapPrompt] = useState(""); const [model, setModel] = useState(""); // claude_local specific const [maxTurnsPerRun, setMaxTurnsPerRun] = useState(80); const [dangerouslySkipPermissions, setDangerouslySkipPermissions] = useState(true); // codex_local specific const [search, setSearch] = useState(false); const [dangerouslyBypassSandbox, setDangerouslyBypassSandbox] = useState(true); // process specific const [command, setCommand] = useState(""); const [args, setArgs] = useState(""); // http specific const [url, setUrl] = useState(""); // Heartbeat const [heartbeatEnabled, setHeartbeatEnabled] = useState(true); const [intervalSec, setIntervalSec] = useState(300); const [wakeOnAssignment, setWakeOnAssignment] = useState(true); const [wakeOnOnDemand, setWakeOnOnDemand] = useState(true); const [wakeOnAutomation, setWakeOnAutomation] = useState(true); const [cooldownSec, setCooldownSec] = useState(10); // Runtime const [contextMode, setContextMode] = useState("thin"); const [budgetMonthlyCents, setBudgetMonthlyCents] = useState(0); const [timeoutSec, setTimeoutSec] = useState(900); const [graceSec, setGraceSec] = useState(15); // Sections const [adapterOpen, setAdapterOpen] = useState(true); const [heartbeatOpen, setHeartbeatOpen] = useState(false); const [runtimeOpen, setRuntimeOpen] = useState(false); // Popover states const [roleOpen, setRoleOpen] = useState(false); const [reportsToOpen, setReportsToOpen] = useState(false); const [adapterTypeOpen, setAdapterTypeOpen] = useState(false); const { data: agents } = useQuery({ queryKey: queryKeys.agents.list(selectedCompanyId!), queryFn: () => agentsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId && newAgentOpen, }); const isFirstAgent = !agents || agents.length === 0; const effectiveRole = isFirstAgent ? "ceo" : role; const createAgent = useMutation({ mutationFn: (data: Record) => agentsApi.create(selectedCompanyId!, data), onSuccess: (agent) => { queryClient.invalidateQueries({ queryKey: queryKeys.agents.list(selectedCompanyId!) }); reset(); closeNewAgent(); navigate(`/agents/${agent.id}`); }, }); function reset() { setName(""); setTitle(""); setRole("general"); setReportsTo(""); setCapabilities(""); setAdapterType("claude_local"); setCwd(""); setPromptTemplate(""); setBootstrapPrompt(""); setModel(""); setMaxTurnsPerRun(80); setDangerouslySkipPermissions(true); setSearch(false); setDangerouslyBypassSandbox(true); setCommand(""); setArgs(""); setUrl(""); setHeartbeatEnabled(true); setIntervalSec(300); setWakeOnAssignment(true); setWakeOnOnDemand(true); setWakeOnAutomation(true); setCooldownSec(10); setContextMode("thin"); setBudgetMonthlyCents(0); setTimeoutSec(900); setGraceSec(15); setExpanded(false); setAdapterOpen(true); setHeartbeatOpen(false); setRuntimeOpen(false); } function buildAdapterConfig() { const config: Record = {}; if (cwd) config.cwd = cwd; if (promptTemplate) config.promptTemplate = promptTemplate; if (bootstrapPrompt) config.bootstrapPromptTemplate = bootstrapPrompt; if (model) config.model = model; config.timeoutSec = timeoutSec; config.graceSec = graceSec; if (adapterType === "claude_local") { config.maxTurnsPerRun = maxTurnsPerRun; config.dangerouslySkipPermissions = dangerouslySkipPermissions; } else if (adapterType === "codex_local") { config.search = search; config.dangerouslyBypassApprovalsAndSandbox = dangerouslyBypassSandbox; } else if (adapterType === "process") { if (command) config.command = command; if (args) config.args = args.split(",").map((a) => a.trim()).filter(Boolean); } else if (adapterType === "http") { if (url) config.url = url; } return config; } function handleSubmit() { if (!selectedCompanyId || !name.trim()) return; createAgent.mutate({ name: name.trim(), role: effectiveRole, ...(title.trim() ? { title: title.trim() } : {}), ...(reportsTo ? { reportsTo } : {}), ...(capabilities.trim() ? { capabilities: capabilities.trim() } : {}), adapterType, adapterConfig: buildAdapterConfig(), runtimeConfig: { heartbeat: { enabled: heartbeatEnabled, intervalSec, wakeOnAssignment, wakeOnOnDemand, wakeOnAutomation, cooldownSec, }, }, contextMode, budgetMonthlyCents, }); } function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); handleSubmit(); } } const currentAgent = (agents ?? []).find((a) => a.id === reportsTo); return ( { if (!open) { reset(); closeNewAgent(); } }} > {/* Header */}
{selectedCompany && ( {selectedCompany.name.slice(0, 3).toUpperCase()} )} New agent
{/* Name */}
setName(e.target.value)} autoFocus />
{/* Title */}
setTitle(e.target.value)} />
{/* Property chips */}
{/* Role */} {AGENT_ROLES.map((r) => ( ))} {/* Reports To */} {(agents ?? []).map((a) => ( ))} {/* Adapter type */} {AGENT_ADAPTER_TYPES.map((t) => ( ))}
{/* Capabilities */}
setCapabilities(e.target.value)} />
{/* Adapter Config Section */} setAdapterOpen(!adapterOpen)} >
setCwd(e.target.value)} />