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 '@/pages/admin/_shell';
/**
* 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.name}
{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.name}
{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
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 */}
setStatus('all')}
className={cn(
'rounded-lg px-3 py-1 font-medium transition-all',
status === 'all'
? 'bg-surface text-ink-1 shadow-2xs font-semibold'
: 'text-ink-3 hover:text-ink-1'
)}
>
All ({agents.length})
setStatus('published')}
className={cn(
'rounded-lg px-3 py-1 font-medium transition-all',
status === 'published'
? 'bg-surface text-emerald-700 shadow-2xs font-semibold'
: 'text-ink-3 hover:text-ink-1'
)}
>
Published ({publishedCount})
{draftCount > 0 && (
setStatus('draft')}
className={cn(
'rounded-lg px-3 py-1 font-medium transition-all',
status === 'draft'
? 'bg-surface text-ink-1 shadow-2xs font-semibold'
: 'text-ink-3 hover:text-ink-1'
)}
>
Drafts ({draftCount})
)}
{archivedCount > 0 && (
setStatus('archived')}
className={cn(
'rounded-lg px-3 py-1 font-medium transition-all',
status === 'archived'
? 'bg-surface text-amber-700 shadow-2xs font-semibold'
: 'text-ink-3 hover:text-ink-1'
)}
>
Archived ({archivedCount})
)}
Skills managed in
Workspace → Skills
{visible.length === 0 ? (
) : view === 'list' ? (
/* Clean Modern Directory Table View */
Agent
Status
Surfaces Covered
Capabilities
Actions
{visible.map((agent) => (
navigate(`/admin/workspace/agents/${agent.id}`)}
onAction={onAction}
/>
))}
) : (
/* 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);
}}
/>
);
}