import { navigateWithAgentChatViewTransition, sendToAgentChat, } from "@agent-native/core/client/agent-chat"; import { useActionMutation, useActionQuery, } from "@agent-native/core/client/hooks"; import { parseCustomAgentProfile } from "@agent-native/core/resources/metadata"; import { IconAdjustmentsHorizontal, IconChevronDown, IconDotsVertical, IconEdit, IconFileImport, IconFolder, IconLayoutGrid, IconMessageCircle, IconPlugConnected, IconPlus, IconTrash, IconUpload, } from "@tabler/icons-react"; import { useEffect, useRef, useState, type ChangeEvent, type ReactNode, } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; import { buildSimpleAgentContent, slugifyAgentName, } from "../lib/simple-agent-profile.js"; import { ActionQueryError } from "./action-query-error"; import { AppIcon } from "./app-icon"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "./ui/alert-dialog"; import { Badge } from "./ui/badge"; import { Button } from "./ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "./ui/collapsible"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "./ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./ui/dropdown-menu"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./ui/select"; import { Skeleton } from "./ui/skeleton"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"; import { Textarea } from "./ui/textarea"; interface WorkspaceAgentResource { id: string; name: string; description: string | null; path: string; content: string; scope: "all" | "selected"; updatedAt: number; } interface AgentPackFileResource extends WorkspaceAgentResource { kind: "agent-file" | "skill"; } interface AgentPackResponse { profile: WorkspaceAgentResource; root: string; files: AgentPackFileResource[]; } interface AgentPackFileInput { path: string; content: string; } const AGENT_ICON_KEYS = [ "brain", "users", "filetext", "chartbar", "route", "listcheck", "code", "messagecircle", ] as const; function agentIconKey(resource: Pick) { let hash = 0; for (const character of `${resource.id}:${resource.name}`) { hash = (hash * 31 + character.charCodeAt(0)) | 0; } return AGENT_ICON_KEYS[Math.abs(hash) % AGENT_ICON_KEYS.length]; } export function isPendingWorkspaceResourceApproval(result: unknown) { if (!result || typeof result !== "object") return false; const mutation = result as { status?: unknown; changeType?: unknown }; return ( mutation.status === "pending" && typeof mutation.changeType === "string" && mutation.changeType.startsWith("workspace-resource.") ); } export function handleAgentPackMutationSuccess( result: unknown, options: { appliedMessage: string; approvalMessage: string; onApplied: () => void; notify?: (message: string) => void; }, ) { const notify = options.notify || ((message) => toast.success(message)); if (isPendingWorkspaceResourceApproval(result)) { notify(options.approvalMessage); return; } notify(options.appliedMessage); options.onApplied(); } interface AgentEditorProps { resource?: WorkspaceAgentResource; trigger?: ReactNode; onSaved?: () => void; open?: boolean; onOpenChange?: (open: boolean) => void; } function profileFields(resource?: WorkspaceAgentResource) { if (!resource) { return { name: "", description: "", instructions: "# Role\n\nDescribe how this agent should work.\n", model: "inherit", tools: "inherit", scope: "all" as const, }; } const profile = parseCustomAgentProfile(resource.content, resource.path); return { name: profile?.name || resource.name, description: profile?.description || resource.description || "", instructions: profile?.instructions || resource.content, model: profile?.model || "inherit", tools: profile?.tools || "inherit", scope: resource.scope, }; } function AgentEditorDialog({ resource, trigger, onSaved, open: controlledOpen, onOpenChange: controlledOnOpenChange, }: AgentEditorProps) { const isEditing = Boolean(resource); const [uncontrolledOpen, setUncontrolledOpen] = useState(false); const open = controlledOpen ?? uncontrolledOpen; const setOpen = controlledOnOpenChange ?? setUncontrolledOpen; const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [instructions, setInstructions] = useState(""); const [model, setModel] = useState("inherit"); const [tools, setTools] = useState("inherit"); const [scope, setScope] = useState<"all" | "selected">("all"); const [advancedOpen, setAdvancedOpen] = useState(false); useEffect(() => { if (!open) return; const fields = profileFields(resource); setName(fields.name); setDescription(fields.description); setInstructions(fields.instructions); setModel(fields.model); setTools(fields.tools); setScope(fields.scope); setAdvancedOpen(fields.model !== "inherit" || fields.tools !== "inherit"); }, [open, resource]); const create = useActionMutation("create-workspace-resource", { onSuccess: () => { toast.success("Agent created"); setOpen(false); onSaved?.(); }, onError: (error) => toast.error(error.message), }); const update = useActionMutation("update-workspace-resource", { onSuccess: () => { toast.success("Agent updated"); setOpen(false); onSaved?.(); }, onError: (error) => toast.error(error.message), }); const pending = create.isPending || update.isPending; const canSave = Boolean(name.trim() && instructions.trim() && !pending); function save() { const content = buildSimpleAgentContent({ name, description, model, tools, instructions, }); if (resource) { update.mutate({ id: resource.id, name: name.trim(), description: description.trim(), content, scope, }); return; } create.mutate({ kind: "agent", name: name.trim(), description: description.trim() || undefined, path: `agents/${slugifyAgentName(name)}.md`, content, scope, }); } return ( {trigger ? ( {trigger} ) : controlledOpen === undefined ? ( ) : null} {isEditing ? "Manage agent" : "Create agent"} {isEditing ? "Update this reusable agent profile." : "Create a reusable agent profile."}
setName(event.target.value)} placeholder="User Research" />
setDescription(event.target.value)} placeholder="Synthesizes user research into clear insights" />
setModel(event.target.value)} placeholder="inherit" />
setTools(event.target.value)} placeholder="inherit" />