import React, { useMemo, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { ArchiveRestore, Archive, Copy, LayoutGrid, List, MoreHorizontal, Plus, RotateCcw, Send, Settings2, Trash2, } from 'lucide-react'; import { Alert, Badge, Button, ConfirmModal, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState, SearchInput, SegmentedToggle, toast, } from '@/components/ds'; import { cn } from '@/lib/utils'; import { useAgents } from '@/lib/agents/useAgents'; import { searchAgents } from '@/lib/agents/registry'; import { agentIconFor } from '@/components/agents/icons'; import OwliverAvatar from '@/components/krow/OwliverAvatar'; import { AdminPage } from '@/components/admin/PageShell'; /** * Agents — the workspace management list. * * Modern, clean, and high-density agent directory with streamlined * status filter tabs, no redundant versioning labels, and full-width layout. */ const STATUS_TONE = { published: 'success', draft: 'neutral', archived: 'warning' }; function AgentGlyph({ agent, className = 'h-10 w-10' }) { const Icon = agentIconFor(agent.icon); if (!Icon) return ; return ( ); } /** Card representation for Grid view */ function AgentCard({ agent, shipped, overridden, onOpen, onAction }) { return (
{/* Top row: Glyph + Title/Badges + Menu */}
{agent.status} {shipped && overridden && ( Customized )} {!shipped && Custom}
{agent.description && (

{agent.description}

)}
Configure onAction('duplicate', agent)} className="cursor-pointer"> Duplicate {agent.status !== 'published' && ( onAction('publish', agent)} className="cursor-pointer"> Publish )} {agent.status === 'published' && ( onAction('republish', agent)} className="cursor-pointer"> Publish update )} {agent.status !== 'archived' ? ( onAction('archive', agent)} className="cursor-pointer"> Archive ) : ( onAction('restore', agent)} className="cursor-pointer"> Restore as draft )} {(overridden || !shipped) && ( <> onAction('remove', agent)} className="cursor-pointer text-destructive focus:text-destructive"> {shipped ? <> Revert to shipped : <> Delete} )}
{/* When to use context */} {agent.trigger && (
Trigger: {agent.trigger}
)}
{/* Card bottom */}
Skills {agent.skills.length} Pages {agent.pages.length} {agent.subagents.length > 0 && ( Subagents {agent.subagents.length} )} Knowledge {agent.knowledge.length} Reasoning {agent.reasoning}
{agent.pages.map((page) => ( {page} ))}
); } /** Directory Table Row for List view */ function AgentTableRow({ agent, shipped, overridden, onOpen, onAction }) { return ( {/* Agent Identity */}
{agent.description && (

{agent.description}

)}
{/* Status */}
{agent.status} {shipped && overridden && ( Customized )} {!shipped && Custom}
{/* Surfaces / Pages */}
{agent.pages.slice(0, 3).map((page) => ( {page} ))} {agent.pages.length > 3 && ( +{agent.pages.length - 3} )}
{/* Capabilities */}
{agent.skills.length} skills {agent.reasoning}
{/* Actions */}
Configure onAction('duplicate', agent)} className="cursor-pointer"> Duplicate {agent.status !== 'published' && ( onAction('publish', agent)} className="cursor-pointer"> Publish )} {agent.status === 'published' && ( onAction('republish', agent)} className="cursor-pointer"> Publish update )} {agent.status !== 'archived' ? ( onAction('archive', agent)} className="cursor-pointer"> Archive ) : ( onAction('restore', agent)} className="cursor-pointer"> Restore as draft )} {(overridden || !shipped) && ( <> onAction('remove', agent)} className="cursor-pointer text-destructive focus:text-destructive"> {shipped ? <> Revert to shipped : <> Delete} )}
); } export default function AdminWorkspaceAgents() { const navigate = useNavigate(); const { agents, diagnostics, saving, isShipped, isOverridden, publish, archive, restore, duplicate, remove, } = useAgents(); const [query, setQuery] = useState(''); const [status, setStatus] = useState('all'); const [view, setView] = useState(() => { try { return localStorage.getItem('krow_agents_view') || 'list'; } catch { return 'list'; } }); const [confirming, setConfirming] = useState(null); const handleViewChange = (newView) => { setView(newView); try { localStorage.setItem('krow_agents_view', newView); } catch { /* ignore */ } }; const visible = useMemo(() => { const found = searchAgents(agents, query); return status === 'all' ? found : found.filter((a) => a.status === status); }, [agents, query, status]); const publishedCount = agents.filter((a) => a.status === 'published').length; const draftCount = agents.filter((a) => a.status === 'draft').length; const archivedCount = agents.filter((a) => a.status === 'archived').length; /** Runs a lifecycle action and reports honestly when it is refused. */ const run = async (action, agent) => { const fn = { publish, republish: publish, archive, restore, duplicate, remove }[action]; const result = await fn(agent.id); if (result?.conflict) toast.error(result.conflict.message); else if (result?.error) toast.error(result.error); else if (action === 'duplicate' && result?.id) navigate(`/admin/workspace/agents/${result.id}`); }; const onAction = (action, agent) => { /* Destructive and irreversible-looking actions confirm; the rest run. */ if (action === 'remove' || action === 'archive') { setConfirming({ action, agent }); return; } run(action, agent); }; return (
navigate('/admin/workspace/agents/new')}> Create agent } >
{diagnostics.length > 0 && (
    {diagnostics.slice(0, 5).map((d) => (
  • {d.message}
  • ))}
)} {/* Unified horizontal management toolbar with clean filter pills */}
{/* Modern status filter tabs */}
{draftCount > 0 && ( )} {archivedCount > 0 && ( )}
Skills managed in Workspace → Skills
{visible.length === 0 ? ( ) : view === 'list' ? ( /* Clean Modern Directory Table View */
{visible.map((agent) => ( navigate(`/admin/workspace/agents/${agent.id}`)} onAction={onAction} /> ))}
Agent Status Surfaces Covered Capabilities Actions
) : ( /* Modern Grid View */
{visible.map((agent) => ( navigate(`/admin/workspace/agents/${agent.id}`)} onAction={onAction} /> ))}
)}
!open && setConfirming(null)} title={ confirming?.action === 'archive' ? `Archive ${confirming?.agent.name}?` : isShipped(confirming?.agent?.id) ? 'Revert to the shipped definition?' : `Delete ${confirming?.agent.name}?` } description={ confirming?.action === 'archive' ? 'It stops answering and disappears from the switcher. Its definition is kept, and it can be restored as a draft.' : isShipped(confirming?.agent?.id) ? 'Your changes to this agent are discarded and the shipped definition takes over again.' : 'This definition is removed. It cannot be recovered.' } confirmLabel={confirming?.action === 'archive' ? 'Archive' : 'Confirm'} busy={saving} onConfirm={async () => { const pending = confirming; setConfirming(null); if (pending) await run(pending.action, pending.agent); }} />
); }