{"slug":"create-agent","category":"agents","job":"start","added":"2026-09-15T08:55Z","title":"Create Agent","description":"A two-step wizard card for naming an agent, picking its icon, and selecting which event sources activate it.","url":"https://ai-patterns.app/patterns/agents/create-agent","markdown":"https://ai-patterns.app/patterns/agents/create-agent.md","json":"https://ai-patterns.app/api/patterns/agents/create-agent","seenIn":[{"team":"Notion","source":"https://www.notion.com/help/custom-agents"}],"spec":"# Create Agent\n\n## Summary\nA 2-step wizard card for creating a named, icon-identified agent and selecting which external event sources activate it. Step 1 captures identity (name and icon); Step 2 captures trigger sources. On completion it emits a typed payload that maps directly onto the Agent Triggers panel.\n\n## When to use\n- First-time agent creation where no agent exists yet.\n- Onboarding flows that walk users through setting up their first automation.\n- An agent catalog screen with an \"Add agent\" entry point that opens an inline or sheet-level wizard.\n\n## When not to use\n- Editing an existing agent's trigger sources — use the Agent Triggers pattern directly, which has per-trigger toggles and a \"Finish setup\" inline action.\n- When only one trigger source is possible. Presenting a multi-select of one option adds unnecessary friction; skip to a single confirmation step instead.\n- When agent creation is complex enough to require a full-page form (multiple steps beyond identity and triggers, required integrations, permissions review, etc.).\n\n## Anatomy\n1. **Header preview row** — always visible across both steps. Shows the currently selected icon in a rounded avatar, the live agent name (or \"New agent\" placeholder), and two step-progress dots.\n2. **Step 1 — Identity** — a title/subtitle label pair, a text input for the agent name, a row of five icon-picker buttons (Bot, Sparkles, Zap, Brain, Rocket), and a full-width \"Choose triggers →\" CTA.\n3. **Step 2 — Triggers** — a title/subtitle label pair, a `<ul>` of trigger source cards (each with a bordered icon square, a label and description, and a trailing checkmark circle), and a two-button footer with \"Back\" (outline) and \"Create agent\" (filled).\n\n## Behavior\n- The header preview row updates in real time: the name reflects every keystroke; the icon swaps immediately on selection. This gives users continuous feedback on how the finished agent will appear elsewhere in the product.\n- Icon selection is immediate with no confirm step — clicking a picker button applies it at once.\n- Trigger source cards are multi-select toggle buttons. The user may select zero, one, or all sources; there is no minimum enforcement in the component (enforce constraints in the consuming view if needed).\n- Navigating from Step 1 to Step 2 slides content in from the right; navigating back slides from the left. AnimatePresence with `mode=\"wait\"` ensures the outgoing step fully exits before the incoming step enters.\n- \"Create agent\" is the terminal action. It fires `onCreateAgent` with a payload of `{ name, iconId, triggerSources }` and hands control entirely to the parent. The component itself has no post-creation state.\n- The \"Back\" button does not reset Step 1 state — name and icon choices are preserved across forward/back navigation.\n\n## Content guidelines\n- Agent names should be short proper nouns or noun phrases that describe the agent's role, not its mechanism: \"Pattern Bot\", \"Deploy Guard\", \"Design Review\" — not \"github-trigger-agent-v2\".\n- The placeholder text \"e.g. Pattern Bot\" demonstrates the expected format; replace it with a placeholder appropriate to the product domain.\n- Trigger source labels name the platform (GitHub, Slack, Figma). Descriptions name the event type that fires the agent, not the agent's response: \"Issues, pull requests, and labels\" not \"Monitors your repo and responds to issues\".\n- The \"Create agent\" button label should always say exactly that — avoid \"Save\", \"Done\", or \"Finish\", which imply the agent already exists.\n\n## Accessibility\n- The name input has a visible `<p>` label (\"Name your agent\") immediately above it and an `aria-label` attribute for programmatic association.\n- Each icon-picker button has an `aria-label` naming the icon (e.g. \"Sparkles\") and `aria-pressed` reflecting selection state.\n- Each trigger source card is a `<button>` with `aria-pressed` reflecting whether it is selected.\n- The step-progress dots in the header are `aria-hidden` — they are a decorative visual indicator, not a navigation control.\n- Step transitions use motion that respects `prefers-reduced-motion` via Motion's default behavior; the `duration: 0.15` transitions are brief enough that they cause minimal disruption even without the media-query guard.\n\n## Related patterns\n- **Agent Triggers** — Use it as well right after creation, since the Create Agent payload maps straight onto its title, agent and triggers props.\n- **Setup Checklist** — Use it instead when onboarding needs several independent tasks done over time rather than one two-step wizard.\n- **Invite Members** — Use it as a visual reference when both appear in one product, since they share the header preview, multi-step form and final action.\n","code":"\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport {\n  Antenna,\n  AtSign,\n  Bot,\n  Brain,\n  Check,\n  ChevronRight,\n  Code2,\n  Compass,\n  Cpu,\n  Database,\n  Eye,\n  FlaskConical,\n  GitBranch,\n  Globe,\n  Layers,\n  Lightbulb,\n  Plus,\n  Rocket,\n  Search,\n  Server,\n  Shield,\n  ShieldAlert,\n  ShieldOff,\n  Sparkles,\n  Star,\n  Terminal,\n  Webhook,\n  Wrench,\n  X,\n  Zap,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\n// ── Trigger sources ──────────────────────────────────────────────────────────\n\ninterface TriggerField {\n  id: string;\n  label: string;\n  type: \"text\" | \"select\";\n  placeholder?: string;\n  options?: { value: string; label: string }[];\n}\n\nexport const TRIGGER_SOURCES = [\n  {\n    id: \"github\" as const,\n    label: \"GitHub\",\n    icon: GitBranch,\n    description: \"Issues, pull requests, and labels\",\n    fields: [\n      {\n        id: \"repo\",\n        label: \"Repository\",\n        type: \"text\" as const,\n        placeholder: \"e.g. carlosmarch/ai-patterns\",\n      },\n      {\n        id: \"event\",\n        label: \"Event\",\n        type: \"select\" as const,\n        options: [\n          { value: \"issue-labeled\", label: \"Issue labeled\" },\n          { value: \"pr-opened\", label: \"PR opened\" },\n          { value: \"pr-merged\", label: \"PR merged\" },\n        ],\n      },\n    ] satisfies TriggerField[],\n  },\n  {\n    id: \"slack\" as const,\n    label: \"Slack\",\n    icon: AtSign,\n    description: \"Mentions, messages, and reactions\",\n    fields: [\n      {\n        id: \"channel\",\n        label: \"Channel\",\n        type: \"text\" as const,\n        placeholder: \"e.g. #design-requests\",\n      },\n      {\n        id: \"event\",\n        label: \"Trigger on\",\n        type: \"select\" as const,\n        options: [\n          { value: \"mention\", label: \"Agent mentioned\" },\n          { value: \"keyword\", label: \"Keyword match\" },\n        ],\n      },\n    ] satisfies TriggerField[],\n  },\n  {\n    id: \"figma\" as const,\n    label: \"Figma\",\n    icon: Layers,\n    description: \"Frame status changes and comments\",\n    fields: [\n      {\n        id: \"scope\",\n        label: \"Files\",\n        type: \"select\" as const,\n        options: [\n          { value: \"any\", label: \"Any Figma file\" },\n          { value: \"specific\", label: \"Specific file URL\" },\n        ],\n      },\n      {\n        id: \"event\",\n        label: \"Event\",\n        type: \"select\" as const,\n        options: [\n          { value: \"ready-for-dev\", label: \"Frame ready for dev\" },\n          { value: \"comment\", label: \"New comment\" },\n        ],\n      },\n    ] satisfies TriggerField[],\n  },\n] as const;\n\nexport type TriggerSourceId = (typeof TRIGGER_SOURCES)[number][\"id\"];\n\n// ── Agent icons ───────────────────────────────────────────────────────────────\n\nexport const AGENT_ICONS = [\n  { id: \"bot\" as const, icon: Bot, label: \"Bot\" },\n  { id: \"sparkles\" as const, icon: Sparkles, label: \"Sparkles\" },\n  { id: \"zap\" as const, icon: Zap, label: \"Zap\" },\n  { id: \"brain\" as const, icon: Brain, label: \"Brain\" },\n  { id: \"rocket\" as const, icon: Rocket, label: \"Rocket\" },\n] as const;\n\nexport const EXTRA_AGENT_ICONS = [\n  { id: \"cpu\" as const, icon: Cpu, label: \"CPU\" },\n  { id: \"globe\" as const, icon: Globe, label: \"Globe\" },\n  { id: \"terminal\" as const, icon: Terminal, label: \"Terminal\" },\n  { id: \"code2\" as const, icon: Code2, label: \"Code\" },\n  { id: \"wrench\" as const, icon: Wrench, label: \"Wrench\" },\n  { id: \"search\" as const, icon: Search, label: \"Search\" },\n  { id: \"eye\" as const, icon: Eye, label: \"Eye\" },\n  { id: \"star\" as const, icon: Star, label: \"Star\" },\n  { id: \"database\" as const, icon: Database, label: \"Database\" },\n  { id: \"server\" as const, icon: Server, label: \"Server\" },\n  { id: \"lightbulb\" as const, icon: Lightbulb, label: \"Lightbulb\" },\n  { id: \"compass\" as const, icon: Compass, label: \"Compass\" },\n  { id: \"flask\" as const, icon: FlaskConical, label: \"Flask\" },\n  { id: \"antenna\" as const, icon: Antenna, label: \"Antenna\" },\n] as const;\n\nexport const ALL_AGENT_ICONS = [...AGENT_ICONS, ...EXTRA_AGENT_ICONS];\n\nexport type AgentIconId = (typeof ALL_AGENT_ICONS)[number][\"id\"];\n\n// ── Guardrails ────────────────────────────────────────────────────────────────\n\nexport interface Guardrail {\n  id: string;\n  label: string;\n  description: string;\n  icon: React.ComponentType<{ className?: string }>;\n  enabled: boolean;\n}\n\nexport const DEFAULT_GUARDRAILS: Guardrail[] = [\n  {\n    id: \"approval-required\",\n    label: \"Require approval\",\n    description: \"Human must approve before any write action\",\n    icon: ShieldAlert,\n    enabled: true,\n  },\n  {\n    id: \"read-only\",\n    label: \"Read-only by default\",\n    description: \"Cannot push code or send messages directly\",\n    icon: ShieldOff,\n    enabled: true,\n  },\n  {\n    id: \"rate-limit\",\n    label: \"Limit to 1 action per trigger\",\n    description: \"Prevents runaway loops on rapid events\",\n    icon: Shield,\n    enabled: false,\n  },\n];\n\n// ── Payload ───────────────────────────────────────────────────────────────────\n\nexport interface SelectedTrigger {\n  sourceId: TriggerSourceId;\n  config: Record<string, string>;\n}\n\nexport interface CustomTrigger {\n  id: string;\n  name: string;\n  event: string;\n}\n\nexport interface CreateAgentPayload {\n  name: string;\n  instructions: string;\n  iconId: AgentIconId;\n  guardrails: { id: string; enabled: boolean }[];\n  triggers: SelectedTrigger[];\n  customTriggers: CustomTrigger[];\n}\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\nexport interface CreateAgentProps {\n  onCreateAgent?: (payload: CreateAgentPayload) => void;\n  className?: string;\n}\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function CreateAgent({ onCreateAgent, className }: CreateAgentProps) {\n  const [step, setStep] = React.useState<0 | 1>(0);\n  const [direction, setDirection] = React.useState<1 | -1>(1);\n  const [name, setName] = React.useState(\"\");\n  const [instructions, setInstructions] = React.useState(\"\");\n  const [iconId, setIconId] = React.useState<AgentIconId>(\"bot\");\n  const [selectedSources, setSelectedSources] = React.useState<TriggerSourceId[]>([]);\n  const [triggerConfigs, setTriggerConfigs] = React.useState<\n    Partial<Record<TriggerSourceId, Record<string, string>>>\n  >({});\n  const [customTriggers, setCustomTriggers] = React.useState<CustomTrigger[]>([]);\n  const [showMoreIcons, setShowMoreIcons] = React.useState(false);\n  const [guardrailStates, setGuardrailStates] = React.useState<Record<string, boolean>>(\n    Object.fromEntries(DEFAULT_GUARDRAILS.map((g) => [g.id, g.enabled]))\n  );\n\n  const selectedIcon = ALL_AGENT_ICONS.find((i) => i.id === iconId)!;\n  const SelectedIconComp = selectedIcon.icon;\n\n  function goToStep1() {\n    setDirection(1);\n    setStep(1);\n  }\n\n  function goToStep0() {\n    setDirection(-1);\n    setStep(0);\n  }\n\n  function toggleSource(id: TriggerSourceId) {\n    setSelectedSources((prev) =>\n      prev.includes(id) ? prev.filter((s) => s !== id) : [...prev, id]\n    );\n  }\n\n  function setTriggerField(sourceId: TriggerSourceId, fieldId: string, value: string) {\n    setTriggerConfigs((prev) => ({\n      ...prev,\n      [sourceId]: { ...prev[sourceId], [fieldId]: value },\n    }));\n  }\n\n  function addCustomTrigger() {\n    setCustomTriggers((prev) => [\n      ...prev,\n      { id: `custom-${Date.now()}`, name: \"\", event: \"\" },\n    ]);\n  }\n\n  function updateCustomTrigger(id: string, field: keyof Omit<CustomTrigger, \"id\">, value: string) {\n    setCustomTriggers((prev) =>\n      prev.map((t) => (t.id === id ? { ...t, [field]: value } : t))\n    );\n  }\n\n  function removeCustomTrigger(id: string) {\n    setCustomTriggers((prev) => prev.filter((t) => t.id !== id));\n  }\n\n  function toggleGuardrail(id: string) {\n    setGuardrailStates((prev) => ({ ...prev, [id]: !prev[id] }));\n  }\n\n  function buildPayload(): CreateAgentPayload {\n    return {\n      name: name.trim() || \"Unnamed Agent\",\n      instructions: instructions.trim(),\n      iconId,\n      guardrails: DEFAULT_GUARDRAILS.map((g) => ({ id: g.id, enabled: guardrailStates[g.id] ?? g.enabled })),\n      triggers: selectedSources.map((sourceId) => ({\n        sourceId,\n        config: triggerConfigs[sourceId] ?? {},\n      })),\n      customTriggers,\n    };\n  }\n\n  const variants = {\n    enter: (dir: number) => ({ x: dir * 16, opacity: 0 }),\n    center: { x: 0, opacity: 1 },\n    exit: (dir: number) => ({ x: dir * -16, opacity: 0 }),\n  };\n\n  return (\n    <div suppressHydrationWarning className={cn(\"w-full max-w-xs overflow-hidden rounded-2xl border bg-card\", className)}>\n      {/* Header preview */}\n      <div className=\"flex items-center justify-between gap-3 border-b px-4 py-3\">\n        <div className=\"flex min-w-0 items-center gap-2\">\n          <span className=\"flex size-7 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground\">\n            <SelectedIconComp className=\"size-3.5\" />\n          </span>\n          <p className=\"truncate text-sm font-semibold text-foreground\">\n            {name.trim() || \"New agent\"}\n          </p>\n        </div>\n        <div className=\"flex shrink-0 items-center gap-1\" aria-hidden>\n          {[0, 1].map((i) => (\n            <span\n              key={i}\n              className={cn(\n                \"block h-1.5 rounded-full transition-all duration-200\",\n                step === i ? \"w-4 bg-foreground\" : \"w-1.5 bg-muted-foreground/30\"\n              )}\n            />\n          ))}\n        </div>\n      </div>\n\n      {/* Steps */}\n      <div className=\"relative overflow-hidden\">\n        <AnimatePresence initial={false} custom={direction} mode=\"wait\">\n          {step === 0 ? (\n            <motion.div\n              key=\"step-identity\"\n              custom={direction}\n              variants={variants}\n              initial=\"enter\"\n              animate=\"center\"\n              exit=\"exit\"\n              transition={{ duration: 0.15, ease: \"easeInOut\" }}\n              className=\"px-4 pb-4 pt-3\"\n            >\n              <p className=\"text-sm font-semibold\">Name your agent</p>\n              <p className=\"mt-0.5 text-xs text-muted-foreground\">\n                Give it a name, instructions, and an icon.\n              </p>\n\n              <input\n                type=\"text\"\n                value={name}\n                onChange={(e) => setName(e.target.value)}\n                placeholder=\"e.g. AI-Patterns\"\n                aria-label=\"Agent name\"\n                className=\"mt-3 w-full rounded-lg border bg-background px-3 py-2 text-sm outline-none ring-ring placeholder:text-muted-foreground/50 focus-visible:ring-2\"\n              />\n\n              <textarea\n                value={instructions}\n                onChange={(e) => setInstructions(e.target.value)}\n                placeholder=\"e.g. When a GitHub issue is labeled pattern-request in carlosmarch/ai-patterns, triage it and suggest a matching component.\"\n                aria-label=\"Agent instructions\"\n                rows={3}\n                className=\"mt-2 w-full resize-none rounded-lg border bg-background px-3 py-2 text-sm outline-none ring-ring placeholder:text-muted-foreground/50 focus-visible:ring-2\"\n              />\n\n              <ul className=\"mt-3 divide-y rounded-xl border\">\n                {DEFAULT_GUARDRAILS.map(({ id, label, description, icon: Icon }) => {\n                  const enabled = guardrailStates[id] ?? false;\n                  return (\n                    <li key={id} className=\"flex items-center gap-3 px-3 py-2.5\">\n                      <span className={cn(\"flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground\")}>\n                        <Icon className=\"size-3.5\" />\n                      </span>\n                      <span className=\"min-w-0 flex-1\">\n                        <span className=\"block text-xs font-medium\">{label}</span>\n                        <span className=\"block text-[11px] text-muted-foreground\">{description}</span>\n                      </span>\n                      <button\n                        type=\"button\"\n                        role=\"switch\"\n                        aria-checked={enabled}\n                        aria-label={label}\n                        onClick={() => toggleGuardrail(id)}\n                        className={cn(\n                          \"relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n                          enabled ? \"bg-foreground\" : \"bg-muted\"\n                        )}\n                      >\n                        <motion.span\n                          aria-hidden\n                          className=\"pointer-events-none inline-block h-4 w-4 rounded-full bg-background shadow-sm\"\n                          animate={{ x: enabled ? 16 : 0 }}\n                          transition={{ type: \"spring\", stiffness: 500, damping: 30 }}\n                        />\n                      </button>\n                    </li>\n                  );\n                })}\n              </ul>\n\n              <div className=\"mt-3 space-y-1.5\">\n                <div className=\"flex items-center gap-1.5\">\n                  {AGENT_ICONS.map(({ id, icon: Icon, label }) => (\n                    <button\n                      key={id}\n                      type=\"button\"\n                      aria-label={label}\n                      aria-pressed={iconId === id}\n                      onClick={() => setIconId(id)}\n                      className={cn(\n                        \"flex size-8 items-center justify-center rounded-lg transition-colors\",\n                        iconId === id\n                          ? \"bg-foreground text-background\"\n                          : \"bg-muted text-muted-foreground hover:text-foreground\"\n                      )}\n                    >\n                      <Icon className=\"size-4\" />\n                    </button>\n                  ))}\n                  <button\n                    type=\"button\"\n                    aria-label={showMoreIcons ? \"Show fewer icons\" : \"Show more icons\"}\n                    aria-expanded={showMoreIcons}\n                    onClick={() => setShowMoreIcons((v) => !v)}\n                    className={cn(\n                      \"flex size-8 items-center justify-center rounded-lg transition-colors\",\n                      showMoreIcons\n                        ? \"bg-muted text-foreground\"\n                        : \"bg-muted text-muted-foreground hover:text-foreground\"\n                    )}\n                  >\n                    <Plus className={cn(\"size-4 transition-transform duration-150\", showMoreIcons && \"rotate-45\")} />\n                  </button>\n                </div>\n\n                <AnimatePresence initial={false}>\n                  {showMoreIcons && (\n                    <motion.div\n                      initial={{ height: 0, opacity: 0 }}\n                      animate={{ height: \"auto\", opacity: 1 }}\n                      exit={{ height: 0, opacity: 0 }}\n                      transition={{ duration: 0.15, ease: \"easeInOut\" }}\n                      className=\"overflow-hidden\"\n                    >\n                      <div className=\"flex flex-wrap gap-1.5 pt-0.5\">\n                        {EXTRA_AGENT_ICONS.map(({ id, icon: Icon, label }) => (\n                          <button\n                            key={id}\n                            type=\"button\"\n                            aria-label={label}\n                            aria-pressed={iconId === id}\n                            onClick={() => { setIconId(id); setShowMoreIcons(false); }}\n                            className={cn(\n                              \"flex size-8 items-center justify-center rounded-lg transition-colors\",\n                              iconId === id\n                                ? \"bg-foreground text-background\"\n                                : \"bg-muted text-muted-foreground hover:text-foreground\"\n                            )}\n                          >\n                            <Icon className=\"size-4\" />\n                          </button>\n                        ))}\n                      </div>\n                    </motion.div>\n                  )}\n                </AnimatePresence>\n              </div>\n\n              <button\n                type=\"button\"\n                onClick={goToStep1}\n                className=\"mt-4 flex w-full items-center justify-center gap-1.5 rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background transition-opacity hover:opacity-90\"\n              >\n                Choose triggers\n                <ChevronRight className=\"size-3.5\" />\n              </button>\n\n              <button\n                type=\"button\"\n                onClick={() => onCreateAgent?.(buildPayload())}\n                className=\"mt-2 w-full text-center text-xs text-muted-foreground transition-colors hover:text-foreground\"\n              >\n                Skip triggers\n              </button>\n            </motion.div>\n          ) : (\n            <motion.div\n              key=\"step-triggers\"\n              custom={direction}\n              variants={variants}\n              initial=\"enter\"\n              animate=\"center\"\n              exit=\"exit\"\n              transition={{ duration: 0.15, ease: \"easeInOut\" }}\n              className=\"px-4 pb-4 pt-3\"\n            >\n              <p className=\"text-sm font-semibold\">Choose triggers</p>\n              <p className=\"mt-0.5 text-xs text-muted-foreground\">\n                Select the sources that activate this agent.\n              </p>\n\n              <ul className=\"mt-3 flex flex-col gap-2\">\n                {TRIGGER_SOURCES.map(({ id, label, icon: Icon, description, fields }) => {\n                  const isSelected = selectedSources.includes(id);\n                  const config = triggerConfigs[id] ?? {};\n\n                  return (\n                    <li key={id} className={cn(\"rounded-xl border transition-colors\", isSelected ? \"border-foreground/20 bg-muted/40\" : \"\")}>\n                      {/* Toggle row */}\n                      <button\n                        type=\"button\"\n                        aria-pressed={isSelected}\n                        onClick={() => toggleSource(id)}\n                        className=\"flex w-full items-center gap-3 px-3 py-2.5 text-left\"\n                      >\n                        <span className=\"flex size-7 shrink-0 items-center justify-center rounded-md border bg-background text-muted-foreground\">\n                          <Icon className=\"size-3.5\" />\n                        </span>\n                        <span className=\"min-w-0 flex-1\">\n                          <span className=\"block text-xs font-medium\">{label}</span>\n                          <span className=\"block text-[11px] text-muted-foreground\">{description}</span>\n                        </span>\n                        <span\n                          className={cn(\n                            \"flex size-4 shrink-0 items-center justify-center rounded-full border transition-colors\",\n                            isSelected\n                              ? \"border-foreground bg-foreground text-background\"\n                              : \"border-muted-foreground/30\"\n                          )}\n                        >\n                          {isSelected && <Check className=\"size-2.5\" strokeWidth={3} />}\n                        </span>\n                      </button>\n\n                      {/* Expanded config */}\n                      <AnimatePresence initial={false}>\n                        {isSelected && (\n                          <motion.div\n                            initial={{ height: 0, opacity: 0 }}\n                            animate={{ height: \"auto\", opacity: 1 }}\n                            exit={{ height: 0, opacity: 0 }}\n                            transition={{ duration: 0.15, ease: \"easeInOut\" }}\n                            className=\"overflow-hidden\"\n                          >\n                            <div className=\"flex flex-col gap-2 border-t px-3 pb-3 pt-2.5\">\n                              {fields.map((field) => (\n                                <div key={field.id} className=\"flex flex-col gap-1\">\n                                  <label className=\"text-[11px] font-medium text-muted-foreground\">\n                                    {field.label}\n                                  </label>\n                                  {field.type === \"text\" ? (\n                                    <input\n                                      type=\"text\"\n                                      value={config[field.id] ?? \"\"}\n                                      onChange={(e) => setTriggerField(id, field.id, e.target.value)}\n                                      placeholder={field.placeholder}\n                                      className=\"w-full rounded-md border bg-background px-2.5 py-1.5 text-xs outline-none ring-ring placeholder:text-muted-foreground/40 focus-visible:ring-2\"\n                                    />\n                                  ) : (\n                                    <select\n                                      value={config[field.id] ?? field.options?.[0]?.value ?? \"\"}\n                                      onChange={(e) => setTriggerField(id, field.id, e.target.value)}\n                                      className=\"w-full rounded-md border bg-background px-2.5 py-1.5 text-xs outline-none ring-ring focus-visible:ring-2\"\n                                    >\n                                      {field.options?.map((opt) => (\n                                        <option key={opt.value} value={opt.value}>\n                                          {opt.label}\n                                        </option>\n                                      ))}\n                                    </select>\n                                  )}\n                                </div>\n                              ))}\n                            </div>\n                          </motion.div>\n                        )}\n                      </AnimatePresence>\n                    </li>\n                  );\n                })}\n              </ul>\n\n              {/* Custom triggers */}\n              <AnimatePresence initial={false}>\n                {customTriggers.map((ct) => (\n                  <motion.div\n                    key={ct.id}\n                    initial={{ height: 0, opacity: 0 }}\n                    animate={{ height: \"auto\", opacity: 1 }}\n                    exit={{ height: 0, opacity: 0 }}\n                    transition={{ duration: 0.15, ease: \"easeInOut\" }}\n                    className=\"overflow-hidden\"\n                  >\n                    <div className=\"mt-2 rounded-xl border border-foreground/20 bg-muted/40\">\n                      <div className=\"flex items-center gap-2 px-3 py-2.5\">\n                        <span className=\"flex size-7 shrink-0 items-center justify-center rounded-md border bg-background text-muted-foreground\">\n                          <Webhook className=\"size-3.5\" />\n                        </span>\n                        <span className=\"flex-1 text-xs font-medium text-muted-foreground\">\n                          Custom trigger\n                        </span>\n                        <button\n                          type=\"button\"\n                          aria-label=\"Remove custom trigger\"\n                          onClick={() => removeCustomTrigger(ct.id)}\n                          className=\"flex size-5 items-center justify-center rounded text-muted-foreground/50 transition-colors hover:text-foreground\"\n                        >\n                          <X className=\"size-3.5\" />\n                        </button>\n                      </div>\n                      <div className=\"flex flex-col gap-2 border-t px-3 pb-3 pt-2.5\">\n                        <div className=\"flex flex-col gap-1\">\n                          <label className=\"text-[11px] font-medium text-muted-foreground\">\n                            Name\n                          </label>\n                          <input\n                            type=\"text\"\n                            value={ct.name}\n                            onChange={(e) => updateCustomTrigger(ct.id, \"name\", e.target.value)}\n                            placeholder=\"e.g. Webhook received\"\n                            className=\"w-full rounded-md border bg-background px-2.5 py-1.5 text-xs outline-none ring-ring placeholder:text-muted-foreground/40 focus-visible:ring-2\"\n                          />\n                        </div>\n                        <div className=\"flex flex-col gap-1\">\n                          <label className=\"text-[11px] font-medium text-muted-foreground\">\n                            Event source\n                          </label>\n                          <input\n                            type=\"text\"\n                            value={ct.event}\n                            onChange={(e) => updateCustomTrigger(ct.id, \"event\", e.target.value)}\n                            placeholder=\"e.g. POST /webhooks/my-agent\"\n                            className=\"w-full rounded-md border bg-background px-2.5 py-1.5 text-xs outline-none ring-ring placeholder:text-muted-foreground/40 focus-visible:ring-2\"\n                          />\n                        </div>\n                      </div>\n                    </div>\n                  </motion.div>\n                ))}\n              </AnimatePresence>\n\n              <button\n                type=\"button\"\n                onClick={addCustomTrigger}\n                className=\"mt-2 flex w-full items-center gap-1.5 rounded-lg border border-dashed px-3 py-2 text-xs text-muted-foreground transition-colors hover:border-foreground/30 hover:text-foreground\"\n              >\n                <Plus className=\"size-3.5\" />\n                Add custom trigger\n              </button>\n\n              <div className=\"mt-3 flex gap-2\">\n                <button\n                  type=\"button\"\n                  onClick={goToStep0}\n                  className=\"flex-1 rounded-lg border px-4 py-2 text-sm font-medium transition-colors hover:bg-muted\"\n                >\n                  Back\n                </button>\n                <button\n                  type=\"button\"\n                  onClick={() => onCreateAgent?.(buildPayload())}\n                  className=\"flex-1 rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background transition-opacity hover:opacity-90\"\n                >\n                  Create agent\n                </button>\n              </div>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </div>\n    </div>\n  );\n}\n","dependencies":["lucide-react","motion"],"localImports":["@/lib/utils"]}