import { AGENT_RUNTIMES, type AgentRuntime, type AgentTemplate, fetchTemplate, fetchTemplateIndex, findInvalidSkillRef, RUNTIME_LABELS, type TemplateIndex, } from "@vtit-agent-coding/shared"; import { useEffect, useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { AgentIdenticon } from "../components/AgentIdenticon"; import { SkillMultiSelect } from "../components/SkillMultiSelect"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select"; import { Textarea } from "../components/ui/textarea"; import { useAgents, useCreateAgent } from "../hooks/useAgents"; import { useMachines } from "../hooks/useMachines"; import { agentColor } from "../lib/agentIdentity"; const RUNTIME_MODELS_MAP: Record = { claude: ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"], antigravity: ["gemini-2.5-pro", "gemini-2.5-flash", "claude-3-7-sonnet"], codex: ["gpt-4o", "codex-v1", "o3-mini"], copilot: ["copilot-chat", "gpt-4o"], custom: ["default"], }; type Step = "choose" | "recruit" | "form"; export function AgentNewPage() { const navigate = useNavigate(); const { agents } = useAgents(); const { machines } = useMachines(); const createAgent = useCreateAgent(); const [step, setStep] = useState("choose"); const [selectedTemplate, setSelectedTemplate] = useState(null); const [selectedMachineId, setSelectedMachineId] = useState(""); const [name, setName] = useState(""); const [username, setUsername] = useState(""); const [bio, setBio] = useState(""); const [soul, setSoul] = useState(""); const [role, setRole] = useState(""); const [handoffTo, setHandoffTo] = useState([]); const [runtime, setRuntime] = useState("claude"); const [model, setModel] = useState(""); const [skills, setSkills] = useState([]); const [error, setError] = useState(null); const selectedMachine = useMemo(() => { return machines.find((m: any) => m.id === selectedMachineId) || machines[0] || null; }, [machines, selectedMachineId]); const availableRuntimes = useMemo(() => { if (!selectedMachine?.runtimes?.length) return AGENT_RUNTIMES; const names = selectedMachine.runtimes.map((r: any) => (typeof r === "string" ? r : r.name)).filter(Boolean); return names.length > 0 ? names : AGENT_RUNTIMES; }, [selectedMachine]); const suggestedModels = useMemo(() => { return RUNTIME_MODELS_MAP[runtime] || []; }, [runtime]); const existingRoles = [...new Set(agents.map((a) => a.role).filter(Boolean))]; function applyTemplate(t: AgentTemplate) { setSelectedTemplate(t); setName(t.name); const defaultUsername = t.username || t.name .toLowerCase() .replace(/[^a-z0-9]/g, "-") .replace(/-+/g, "-"); setUsername(defaultUsername); setBio(t.bio || ""); setSoul(t.soul || ""); setRole(t.role || ""); setHandoffTo(t.handoff_to || []); setRuntime(t.runtime || "claude"); setModel(t.model || ""); setSkills(t.skills || []); setStep("form"); } function startCustom() { setSelectedTemplate(null); setName(""); setUsername(""); setBio(""); setSoul(""); setRole(""); setHandoffTo([]); setRuntime("claude"); setModel(""); setSkills([]); setStep("form"); } async function handleCreate() { const finalUsername = (username.trim() || name.trim() || role.trim() || "agent") .toLowerCase() .replace(/[^a-z0-9_-]/g, "-") .replace(/-+/g, "-"); setError(null); const invalidSkill = findInvalidSkillRef(skills); if (invalidSkill) { setError(`Invalid skill "${invalidSkill}". Use source/repo[#ref]@skill-name format.`); return; } try { await createAgent.mutateAsync({ name: name.trim() || undefined, username: finalUsername, bio: bio.trim() || undefined, soul: soul.trim() || undefined, role: role.trim() || undefined, handoff_to: handoffTo.length ? handoffTo : undefined, runtime, model: model.trim() || undefined, skills: skills.length ? skills : undefined, }); navigate("/agents"); } catch (err: any) { setError(err.message); } } return (
{step === "choose" && setStep("recruit")} onCustom={startCustom} />} {step === "recruit" && setStep("choose")} />} {step === "form" && ( setStep(selectedTemplate ? "recruit" : "choose")} onCreate={handleCreate} /> )}
); } /* ── Step 1: Choose path ── */ function ChooseStep({ onRecruit, onCustom }: { onRecruit: () => void; onCustom: () => void }) { return (

New agent

Choose how to create your agent

); } /* ── Step 2: Recruit — pick a template ── */ function RecruitStep({ onSelect, onBack }: { onSelect: (t: AgentTemplate) => void; onBack: () => void }) { const [index, setIndex] = useState([]); const [loading, setLoading] = useState(true); const [loadingSlug, setLoadingSlug] = useState(null); useEffect(() => { fetchTemplateIndex() .then(setIndex) .finally(() => setLoading(false)); }, []); async function handleSelect(slug: string) { setLoadingSlug(slug); try { const t = await fetchTemplate(slug); if (!t.role) t.role = slug; onSelect(t); } finally { setLoadingSlug(null); } } return (

Recruit an agent

Select a role template to get started

{loading ? (
{[0, 1, 2, 3, 4, 5].map((i) => (
))}
) : (
{index.map((entry) => ( ))}
)}
); } /* ── Step 3: Form with live preview ── */ interface FormStepProps { template: AgentTemplate | null; existingRoles: string[]; machines: any[]; selectedMachineId: string; setSelectedMachineId: (v: string) => void; availableRuntimes: string[]; suggestedModels: string[]; name: string; setName: (v: string) => void; username: string; setUsername: (v: string) => void; bio: string; setBio: (v: string) => void; soul: string; setSoul: (v: string) => void; role: string; setRole: (v: string) => void; handoffTo: string[]; setHandoffTo: (v: string[]) => void; runtime: AgentRuntime; setRuntime: (v: AgentRuntime) => void; model: string; setModel: (v: string) => void; skills: string[]; setSkills: (v: string[]) => void; creating: boolean; error: string | null; onBack: () => void; onCreate: () => void; } function FormStep(props: FormStepProps) { const { template, existingRoles, machines, selectedMachineId, setSelectedMachineId, availableRuntimes, suggestedModels, name, setName, username, setUsername, bio, setBio, soul, setSoul, role, setRole, handoffTo, setHandoffTo, runtime, setRuntime, model, setModel, skills, setSkills, creating, error, onBack, onCreate, } = props; const previewKey = name.trim() || "preview"; const previewColor = agentColor(previewKey); return (

{template ? `Recruit ${template.name}` : "Create agent"}

{/* Left: form */}
{/* Identity */}
Identity
setName(e.target.value)} placeholder="e.g. Bolt" className="font-mono" />
setUsername(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""))} placeholder="e.g. bolt" className="font-mono" />
setRole(e.target.value)} placeholder="e.g. backend-developer" className="font-mono" />
setBio(e.target.value)} placeholder="Short description" />