@polderlabs/bizar 6.3.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/sessionend-recall.mjs +3 -3
- package/.claude/hooks/worker-suggest.mjs +110 -0
- package/.claude/settings.json +9 -0
- package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-CW6fWKds.js} +3 -3
- package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js.map → EnvVarsSection-CW6fWKds.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +1 -0
- package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-D7zRhU9K.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +1 -0
- package/bizar-dash/dist/assets/{MobileSettings-CEQNJNLJ.js.map → MobileSettings-K-sNsn_r.js.map} +1 -1
- package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
- package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
- package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
- package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
- package/bizar-dash/dist/assets/main-C-Md3DaY.css +1 -0
- package/bizar-dash/dist/assets/main-CO8fIxQK.js +18 -0
- package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +1 -0
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
- package/bizar-dash/dist/assets/{markdown-tOLaD6nm.js.map → markdown-w2bDDkAH.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +1 -0
- package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DXYCA0hC.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +2 -0
- package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +1 -0
- package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
- package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +2 -0
- package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +1 -0
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
- package/bizar-dash/dist/index.html +12 -10
- package/bizar-dash/dist/mobile.html +7 -4
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/api.mjs +11 -0
- package/bizar-dash/src/server/memory-consolidator.mjs +218 -0
- package/bizar-dash/src/server/memory-distillation.mjs +296 -0
- package/bizar-dash/src/server/routes/distill.mjs +129 -0
- package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
- package/bizar-dash/src/web/App.tsx +4 -0
- package/bizar-dash/src/web/components/Tag.tsx +18 -5
- package/bizar-dash/src/web/components/Topbar.tsx +3 -0
- package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
- package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
- package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
- package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
- package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
- package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
- package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
- package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
- package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
- package/bizar-dash/src/web/lib/types.ts +25 -0
- package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
- package/bizar-dash/src/web/styles/main.css +251 -0
- package/bizar-dash/src/web/styles/tasks-redesign.css +231 -0
- package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
- package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
- package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
- package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
- package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
- package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
- package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
- package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
- package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
- package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
- package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
- package/bizar-dash/src/web/ui/controls/controls.css +454 -0
- package/bizar-dash/src/web/ui/controls/index.ts +33 -0
- package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
- package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
- package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
- package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
- package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
- package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
- package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
- package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
- package/bizar-dash/src/web/ui/data/data.css +535 -0
- package/bizar-dash/src/web/ui/data/index.ts +31 -0
- package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
- package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
- package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
- package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
- package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
- package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
- package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
- package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
- package/bizar-dash/src/web/ui/index.ts +56 -0
- package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
- package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
- package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
- package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
- package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
- package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
- package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
- package/bizar-dash/src/web/ui/layout/index.ts +33 -0
- package/bizar-dash/src/web/ui/layout/layout.css +413 -0
- package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
- package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
- package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
- package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
- package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
- package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
- package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
- package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
- package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
- package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
- package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
- package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
- package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
- package/bizar-dash/src/web/ui/styles/globals.css +86 -0
- package/bizar-dash/src/web/ui/styles/reset.css +125 -0
- package/bizar-dash/src/web/ui/styles/tokens.css +195 -0
- package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
- package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
- package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
- package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
- package/bizar-dash/src/web/views/Agents.tsx +506 -266
- package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
- package/bizar-dash/src/web/views/Overview.tsx +996 -448
- package/bizar-dash/src/web/views/Tasks.tsx +277 -273
- package/bizar-dash/tests/communication-log.test.tsx +67 -0
- package/bizar-dash/tests/dep-graph.test.tsx +40 -0
- package/bizar-dash/tests/event-log.test.tsx +61 -0
- package/bizar-dash/tests/goal-input.test.tsx +72 -0
- package/bizar-dash/tests/goap-planner.test.ts +68 -0
- package/bizar-dash/tests/quality-gates.test.tsx +59 -0
- package/bizar-dash/tests/setup.ts +31 -1
- package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
- package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
- package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
- package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
- package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
- package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
- package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
- package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
- package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
- package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
- package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
- package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
- package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
- package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
- package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
- package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
- package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
- package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
- package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
- package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
- package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
- package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
- package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
- package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
- package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
- package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
- package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
- package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
- package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
- package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
- package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
- package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
- package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
- package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
- package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
- package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
- package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
- package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
- package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
- package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
- package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
- package/bizar-dash/tests/views/Agents.test.tsx +359 -0
- package/bizar-dash/tests/views/Overview.test.tsx +413 -0
- package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
- package/bizar-dash/vitest.config.ts +3 -1
- package/cli/__tests__/cost-gate.test.mjs +301 -0
- package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
- package/cli/bin.mjs +38 -0
- package/cli/commands/claim.mjs +282 -0
- package/cli/commands/cost.mjs +279 -0
- package/cli/cost-gate.mjs +501 -0
- package/cli/feature-list-bridge.mjs +400 -0
- package/cli/memory-constants.mjs +17 -0
- package/cli/worker-dispatcher.mjs +259 -0
- package/cli/worker-dispatcher.test.mjs +204 -0
- package/config/trigger-patterns.json +186 -0
- package/package.json +2 -2
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/agent-registry.ts +304 -0
- package/packages/sdk/src/consensus/byzantine.ts +587 -0
- package/packages/sdk/src/consensus/index.ts +147 -0
- package/packages/sdk/src/consensus/queen.ts +173 -0
- package/packages/sdk/src/consensus/types.ts +199 -0
- package/packages/sdk/src/federation/audit.ts +156 -0
- package/packages/sdk/src/federation/budget.ts +269 -0
- package/packages/sdk/src/federation/envelope.ts +144 -0
- package/packages/sdk/src/federation/hmac.ts +160 -0
- package/packages/sdk/src/federation/index.ts +443 -0
- package/packages/sdk/src/federation/pii.ts +241 -0
- package/packages/sdk/src/federation/policy.ts +112 -0
- package/packages/sdk/src/federation/trust.ts +146 -0
- package/packages/sdk/src/index.ts +67 -0
- package/packages/sdk/src/mcp/server.ts +351 -1
- package/packages/sdk/src/router/codemod-intent.ts +125 -0
- package/packages/sdk/src/router/index.ts +247 -0
- package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
- package/packages/sdk/src/router/memory-distillation.ts +249 -0
- package/packages/sdk/src/router/model-router.ts +235 -0
- package/packages/sdk/src/router/q-learning-router.ts +304 -0
- package/packages/sdk/src/swarm-topology.ts +279 -0
- package/packages/sdk/tests/agent-registry.test.ts +285 -0
- package/packages/sdk/tests/consensus.test.ts +376 -0
- package/packages/sdk/tests/federation/audit.test.ts +164 -0
- package/packages/sdk/tests/federation/budget.test.ts +189 -0
- package/packages/sdk/tests/federation/envelope.test.ts +120 -0
- package/packages/sdk/tests/federation/hmac.test.ts +194 -0
- package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
- package/packages/sdk/tests/federation/pii.test.ts +106 -0
- package/packages/sdk/tests/federation/policy.test.ts +140 -0
- package/packages/sdk/tests/federation/trust.test.ts +124 -0
- package/packages/sdk/tests/mcp-tools.test.ts +204 -0
- package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
- package/packages/sdk/tests/model-router.test.mjs +108 -0
- package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
- package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
- package/packages/sdk/tests/router.test.mjs +91 -0
- package/packages/sdk/tests/sdk.test.mjs +26 -2
- package/packages/sdk/tests/swarm-topology.test.ts +258 -0
- package/scripts/bh-full-e2e.mjs +11 -15
- package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
- package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
- package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
- package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
- package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
- package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
- package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
- package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
- package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
- package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
- package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
- package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
- package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
- package/packages/sdk/package-lock.json +0 -32
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
// src/views/Agents.tsx —
|
|
1
|
+
// src/views/Agents.tsx — Agent roster view (Wave 3 redesign).
|
|
2
|
+
//
|
|
3
|
+
// Wave 3 migrates the legacy card-grid agent roster to the Bizar design
|
|
4
|
+
// system: a split-pane layout with a sortable DataTable on the left and a
|
|
5
|
+
// detail Panel on the right that surfaces the selected agent's metadata,
|
|
6
|
+
// actions (Edit / Delete / Restart) and system prompt. Modals still use the
|
|
7
|
+
// legacy Modal component (the design-system Dialog will land in a later
|
|
8
|
+
// wave), and the public function signature, export name, API paths, and
|
|
9
|
+
// WS event subscriptions remain unchanged so callers and the server are
|
|
10
|
+
// not disturbed by this UI-only refactor.
|
|
2
11
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
12
|
import {
|
|
4
13
|
Bot,
|
|
@@ -8,29 +17,33 @@ import {
|
|
|
8
17
|
Trash2,
|
|
9
18
|
Play,
|
|
10
19
|
Save,
|
|
11
|
-
X,
|
|
12
20
|
RotateCw,
|
|
13
|
-
|
|
14
|
-
Tag as TagIcon,
|
|
15
|
-
Folder,
|
|
16
|
-
CheckCircle2,
|
|
17
|
-
Clock,
|
|
18
|
-
AlertTriangle,
|
|
19
|
-
Circle,
|
|
20
|
-
ChevronDown,
|
|
21
|
-
ChevronRight,
|
|
21
|
+
CircleAlert,
|
|
22
22
|
} from 'lucide-react';
|
|
23
23
|
import { Button } from '../components/Button';
|
|
24
|
-
import { Card, CardTitle } from '../components/Card';
|
|
25
|
-
import { EmptyState } from '../components/EmptyState';
|
|
26
|
-
import { Spinner } from '../components/Spinner';
|
|
27
|
-
import { StatusBadge } from '../components/StatusBadge';
|
|
28
24
|
import { useModal } from '../components/Modal';
|
|
29
25
|
import { useToast } from '../components/Toast';
|
|
30
26
|
import { api } from '../lib/api';
|
|
31
|
-
import {
|
|
27
|
+
import { formatRelative, truncate } from '../lib/utils';
|
|
32
28
|
import type { Agent, Settings, Snapshot } from '../lib/types';
|
|
33
29
|
|
|
30
|
+
// Design-system consumers — Wave 3 first real view to use them.
|
|
31
|
+
import { Stack, Inline, Box } from '../ui/primitives';
|
|
32
|
+
import {
|
|
33
|
+
DataTable,
|
|
34
|
+
KeyValueList,
|
|
35
|
+
EmptyState,
|
|
36
|
+
LoadingState,
|
|
37
|
+
type DataTableColumn,
|
|
38
|
+
type KeyValueItem,
|
|
39
|
+
} from '../ui/data';
|
|
40
|
+
import { Button as UIButton } from '../ui/controls';
|
|
41
|
+
import { Badge, StatusDot } from '../ui/feedback';
|
|
42
|
+
import { Panel } from '../ui/layout';
|
|
43
|
+
import { cx } from '../ui/utils/cx';
|
|
44
|
+
|
|
45
|
+
import '../styles/agents-redesign.css';
|
|
46
|
+
|
|
34
47
|
type Props = {
|
|
35
48
|
snapshot: Snapshot;
|
|
36
49
|
settings: Settings;
|
|
@@ -50,35 +63,54 @@ const MODELS = [
|
|
|
50
63
|
];
|
|
51
64
|
|
|
52
65
|
const CATEGORIES = [
|
|
53
|
-
{ id: 'reasoning', label: 'Reasoning',
|
|
54
|
-
{ id: 'code', label: 'Code',
|
|
55
|
-
{ id: 'design', label: 'Design',
|
|
56
|
-
{ id: 'planning', label: 'Planning',
|
|
57
|
-
{ id: 'gitops', label: 'GitOps',
|
|
58
|
-
{ id: 'analysis', label: 'Analysis',
|
|
66
|
+
{ id: 'reasoning', label: 'Reasoning', variant: 'accent' as const },
|
|
67
|
+
{ id: 'code', label: 'Code', variant: 'info' as const },
|
|
68
|
+
{ id: 'design', label: 'Design', variant: 'success' as const },
|
|
69
|
+
{ id: 'planning', label: 'Planning', variant: 'warning' as const },
|
|
70
|
+
{ id: 'gitops', label: 'GitOps', variant: 'danger' as const },
|
|
71
|
+
{ id: 'analysis', label: 'Analysis', variant: 'neutral' as const },
|
|
59
72
|
];
|
|
60
73
|
|
|
61
|
-
function
|
|
62
|
-
|
|
74
|
+
function categoryMeta(cat: string | undefined): { label: string; variant: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'accent' } {
|
|
75
|
+
if (!cat) return { label: '—', variant: 'neutral' };
|
|
76
|
+
const found = CATEGORIES.find((c) => c.id === cat);
|
|
77
|
+
return found
|
|
78
|
+
? { label: found.label, variant: found.variant }
|
|
79
|
+
: { label: cat, variant: 'neutral' };
|
|
63
80
|
}
|
|
64
81
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
type StatusKind = 'idle' | 'working' | 'error' | 'stuck';
|
|
83
|
+
|
|
84
|
+
function statusKind(agent: Agent): StatusKind {
|
|
85
|
+
if (agent.isStuck) return 'stuck';
|
|
86
|
+
const s = (agent.status || 'idle') as string;
|
|
87
|
+
if (s === 'working') return 'working';
|
|
88
|
+
if (s === 'error') return 'error';
|
|
89
|
+
return 'idle';
|
|
72
90
|
}
|
|
73
91
|
|
|
74
|
-
function
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
92
|
+
function statusLabel(kind: StatusKind): string {
|
|
93
|
+
if (kind === 'stuck') return 'stuck';
|
|
94
|
+
return kind;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function statusDotVariant(kind: StatusKind): 'neutral' | 'info' | 'danger' {
|
|
98
|
+
if (kind === 'working') return 'info';
|
|
99
|
+
if (kind === 'error' || kind === 'stuck') return 'danger';
|
|
100
|
+
return 'neutral';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function lastSeenTs(agent: Agent): number | undefined {
|
|
104
|
+
// The Agent type exposes `lastSeen` (legacy) and a future `lastSeenAt`.
|
|
105
|
+
// Either is acceptable; missing fields render as "—".
|
|
106
|
+
const a = agent as unknown as { lastSeenAt?: number };
|
|
107
|
+
return a.lastSeenAt ?? agent.lastSeen;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function tagsToString(tags: string[] | undefined, max = 4): string {
|
|
111
|
+
if (!tags || tags.length === 0) return '—';
|
|
112
|
+
const shown = tags.slice(0, max).join(', ');
|
|
113
|
+
return tags.length > max ? `${shown} +${tags.length - max}` : shown;
|
|
82
114
|
}
|
|
83
115
|
|
|
84
116
|
export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
@@ -88,13 +120,22 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
88
120
|
const [loading, setLoading] = useState(!snapshot.agents);
|
|
89
121
|
const [categoryFilter, setCategoryFilter] = useState<string>('');
|
|
90
122
|
const [search, setSearch] = useState('');
|
|
123
|
+
const [selectedName, setSelectedName] = useState<string | null>(null);
|
|
91
124
|
|
|
92
125
|
useEffect(() => {
|
|
93
126
|
setAgents(snapshot.agents || []);
|
|
94
127
|
setLoading(!snapshot.agents);
|
|
95
128
|
}, [snapshot.agents]);
|
|
96
129
|
|
|
97
|
-
|
|
130
|
+
// If the selected agent disappears from the list (deleted elsewhere, WS
|
|
131
|
+
// swap), clear the selection so the detail panel reverts to the placeholder.
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (selectedName && !agents.some((a) => a.name === selectedName)) {
|
|
134
|
+
setSelectedName(null);
|
|
135
|
+
}
|
|
136
|
+
}, [agents, selectedName]);
|
|
137
|
+
|
|
138
|
+
const reload = async (): Promise<void> => {
|
|
98
139
|
try {
|
|
99
140
|
const d = await api.get<{ agents: Agent[] }>('/agents');
|
|
100
141
|
setAgents(d.agents || []);
|
|
@@ -108,7 +149,11 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
108
149
|
const sorted = useMemo(() => {
|
|
109
150
|
let out = [...agents];
|
|
110
151
|
if (categoryFilter) {
|
|
111
|
-
out = out.filter((a) =>
|
|
152
|
+
out = out.filter((a) =>
|
|
153
|
+
categoryFilter === '__none__'
|
|
154
|
+
? !(a.category || '')
|
|
155
|
+
: (a.category || '') === categoryFilter,
|
|
156
|
+
);
|
|
112
157
|
}
|
|
113
158
|
if (search) {
|
|
114
159
|
const q = search.toLowerCase();
|
|
@@ -122,13 +167,16 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
122
167
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
123
168
|
}, [agents, categoryFilter, search]);
|
|
124
169
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
170
|
+
const hasLastSeen = sorted.some((a) => typeof lastSeenTs(a) === 'number');
|
|
171
|
+
|
|
172
|
+
const selected = useMemo(
|
|
173
|
+
() => sorted.find((a) => a.name === selectedName) || null,
|
|
174
|
+
[sorted, selectedName],
|
|
175
|
+
);
|
|
130
176
|
|
|
131
|
-
|
|
177
|
+
// ─── Handlers (modal flows kept on the legacy Modal) ───────────────────
|
|
178
|
+
|
|
179
|
+
const onCreate = (): void => {
|
|
132
180
|
let nameEl: HTMLInputElement | null = null;
|
|
133
181
|
let descEl: HTMLInputElement | null = null;
|
|
134
182
|
let modelEl: HTMLSelectElement | null = null;
|
|
@@ -251,6 +299,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
251
299
|
prompt: promptEl?.value || '',
|
|
252
300
|
});
|
|
253
301
|
setAgents((cur) => [...cur, created]);
|
|
302
|
+
setSelectedName(created.name);
|
|
254
303
|
toast.success('Agent created.');
|
|
255
304
|
modal.close();
|
|
256
305
|
await refreshSnapshot();
|
|
@@ -266,7 +315,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
266
315
|
});
|
|
267
316
|
};
|
|
268
317
|
|
|
269
|
-
const onEdit = async (a: Agent) => {
|
|
318
|
+
const onEdit = async (a: Agent): Promise<void> => {
|
|
270
319
|
let descEl: HTMLInputElement | null = null;
|
|
271
320
|
let modelEl: HTMLSelectElement | null = null;
|
|
272
321
|
let modeEl: HTMLSelectElement | null = null;
|
|
@@ -345,7 +394,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
345
394
|
<input type="checkbox" value={t} aria-label={t} defaultChecked={full.tools?.includes(t)} />
|
|
346
395
|
<span>{t}</span>
|
|
347
396
|
</label>
|
|
348
|
-
|
|
397
|
+
))}
|
|
349
398
|
</div>
|
|
350
399
|
</fieldset>
|
|
351
400
|
<label className="field-label" htmlFor="agent-edit-prompt">System prompt</label>
|
|
@@ -404,7 +453,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
404
453
|
}
|
|
405
454
|
};
|
|
406
455
|
|
|
407
|
-
const onDelete = async (a: Agent) => {
|
|
456
|
+
const onDelete = async (a: Agent): Promise<void> => {
|
|
408
457
|
if (!confirm(`Delete agent "${a.name}"? This removes ${a.path}.`)) return;
|
|
409
458
|
try {
|
|
410
459
|
await api.del(`/agents/${encodeURIComponent(a.name)}`);
|
|
@@ -415,7 +464,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
415
464
|
}
|
|
416
465
|
};
|
|
417
466
|
|
|
418
|
-
const onInvoke =
|
|
467
|
+
const onInvoke = (a: Agent): void => {
|
|
419
468
|
let promptEl: HTMLTextAreaElement | null = null;
|
|
420
469
|
modal.open({
|
|
421
470
|
title: `Invoke ${a.name}`,
|
|
@@ -461,7 +510,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
461
510
|
});
|
|
462
511
|
};
|
|
463
512
|
|
|
464
|
-
const onRestart = async (a: Agent) => {
|
|
513
|
+
const onRestart = async (a: Agent): Promise<void> => {
|
|
465
514
|
try {
|
|
466
515
|
const updated = await api.post<Agent>(`/agents/${encodeURIComponent(a.name)}/restart`);
|
|
467
516
|
setAgents((cur) => cur.map((x) => (x.name === a.name ? updated : x)));
|
|
@@ -471,7 +520,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
471
520
|
}
|
|
472
521
|
};
|
|
473
522
|
|
|
474
|
-
const onSetStatus = async (a: Agent, status: 'idle' | 'working' | 'error') => {
|
|
523
|
+
const onSetStatus = async (a: Agent, status: 'idle' | 'working' | 'error'): Promise<void> => {
|
|
475
524
|
try {
|
|
476
525
|
const updated = await api.post<Agent>(`/agents/${encodeURIComponent(a.name)}/status`, { status });
|
|
477
526
|
setAgents((cur) => cur.map((x) => (x.name === a.name ? updated : x)));
|
|
@@ -480,237 +529,428 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
480
529
|
}
|
|
481
530
|
};
|
|
482
531
|
|
|
532
|
+
// ─── Table column definitions ──────────────────────────────────────────
|
|
533
|
+
|
|
534
|
+
type Col = DataTableColumn<Agent>;
|
|
535
|
+
|
|
536
|
+
const columns: Col[] = [
|
|
537
|
+
{
|
|
538
|
+
key: 'name',
|
|
539
|
+
header: 'Name',
|
|
540
|
+
sortable: true,
|
|
541
|
+
accessor: (a) => a.name,
|
|
542
|
+
render: (a) => (
|
|
543
|
+
<span className="agents-name-cell">
|
|
544
|
+
<span
|
|
545
|
+
className={cx(
|
|
546
|
+
'agents-name-cell__swatch',
|
|
547
|
+
!a.color && 'agents-name-cell__swatch--empty',
|
|
548
|
+
)}
|
|
549
|
+
style={a.color ? { background: a.color } : undefined}
|
|
550
|
+
aria-hidden="true"
|
|
551
|
+
/>
|
|
552
|
+
{a.name}
|
|
553
|
+
</span>
|
|
554
|
+
),
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
key: 'status',
|
|
558
|
+
header: 'Status',
|
|
559
|
+
sortable: true,
|
|
560
|
+
accessor: (a) => statusKind(a),
|
|
561
|
+
render: (a) => {
|
|
562
|
+
const kind = statusKind(a);
|
|
563
|
+
return (
|
|
564
|
+
<span className="agents-status-cell">
|
|
565
|
+
<StatusDot
|
|
566
|
+
variant={statusDotVariant(kind)}
|
|
567
|
+
size="sm"
|
|
568
|
+
pulse={kind === 'stuck'}
|
|
569
|
+
label={statusLabel(kind)}
|
|
570
|
+
/>
|
|
571
|
+
<span
|
|
572
|
+
className={cx(
|
|
573
|
+
'agents-status-cell__label',
|
|
574
|
+
kind === 'stuck' && 'agents-status-cell__label--stuck',
|
|
575
|
+
)}
|
|
576
|
+
>
|
|
577
|
+
{statusLabel(kind)}
|
|
578
|
+
</span>
|
|
579
|
+
</span>
|
|
580
|
+
);
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
key: 'category',
|
|
585
|
+
header: 'Category',
|
|
586
|
+
sortable: true,
|
|
587
|
+
accessor: (a) => a.category || '',
|
|
588
|
+
render: (a) => {
|
|
589
|
+
const meta = categoryMeta(a.category);
|
|
590
|
+
if (!a.category) return <span className="muted">—</span>;
|
|
591
|
+
return (
|
|
592
|
+
<span className="agents-category-cell">
|
|
593
|
+
<Badge variant={meta.variant} size="sm">{meta.label}</Badge>
|
|
594
|
+
</span>
|
|
595
|
+
);
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
key: 'tags',
|
|
600
|
+
header: 'Tags',
|
|
601
|
+
accessor: (a) => (a.tags || []).join(','),
|
|
602
|
+
render: (a) => (
|
|
603
|
+
<span className="agents-tags-cell" title={(a.tags || []).join(', ')}>
|
|
604
|
+
{tagsToString(a.tags)}
|
|
605
|
+
</span>
|
|
606
|
+
),
|
|
607
|
+
},
|
|
608
|
+
];
|
|
609
|
+
|
|
610
|
+
if (hasLastSeen) {
|
|
611
|
+
columns.push({
|
|
612
|
+
key: 'lastSeen',
|
|
613
|
+
header: 'Last seen',
|
|
614
|
+
sortable: true,
|
|
615
|
+
accessor: (a) => lastSeenTs(a) || 0,
|
|
616
|
+
render: (a) => {
|
|
617
|
+
const ts = lastSeenTs(a);
|
|
618
|
+
return <span className="muted tabular-nums">{ts ? formatRelative(ts) : '—'}</span>;
|
|
619
|
+
},
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// ─── Detail panel ─────────────────────────────────────────────────────
|
|
624
|
+
|
|
625
|
+
function renderDetailBody(): React.JSX.Element {
|
|
626
|
+
if (!selected) {
|
|
627
|
+
return (
|
|
628
|
+
<div className="agents-detail__placeholder">
|
|
629
|
+
<span className="agents-detail__placeholder-icon" aria-hidden="true">
|
|
630
|
+
<Bot size={28} />
|
|
631
|
+
</span>
|
|
632
|
+
<span className="agents-detail__placeholder-title">No agent selected</span>
|
|
633
|
+
<span>Pick a row from the table to see its metadata, actions, and prompt.</span>
|
|
634
|
+
</div>
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const a = selected;
|
|
639
|
+
const kind = statusKind(a);
|
|
640
|
+
const catMeta = categoryMeta(a.category);
|
|
641
|
+
const canRestart = a.isStuck || a.status === 'working' || a.status === 'error';
|
|
642
|
+
|
|
643
|
+
const metaItems: KeyValueItem[] = [
|
|
644
|
+
{
|
|
645
|
+
key: 'status',
|
|
646
|
+
label: 'Status',
|
|
647
|
+
value: (
|
|
648
|
+
<span className="agents-status-cell">
|
|
649
|
+
<StatusDot
|
|
650
|
+
variant={statusDotVariant(kind)}
|
|
651
|
+
size="sm"
|
|
652
|
+
pulse={kind === 'stuck'}
|
|
653
|
+
label={statusLabel(kind)}
|
|
654
|
+
/>
|
|
655
|
+
<span className="agents-status-cell__label">{statusLabel(kind)}</span>
|
|
656
|
+
</span>
|
|
657
|
+
),
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
key: 'category',
|
|
661
|
+
label: 'Category',
|
|
662
|
+
value: a.category
|
|
663
|
+
? <Badge variant={catMeta.variant} size="sm">{catMeta.label}</Badge>
|
|
664
|
+
: <span className="muted">—</span>,
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
key: 'tags',
|
|
668
|
+
label: 'Tags',
|
|
669
|
+
value: a.tags && a.tags.length > 0 ? (
|
|
670
|
+
<Inline gap={1} className="agents-detail__tags">
|
|
671
|
+
{a.tags.map((t) => (
|
|
672
|
+
<Badge key={t} variant="neutral" size="sm">{t}</Badge>
|
|
673
|
+
))}
|
|
674
|
+
</Inline>
|
|
675
|
+
) : <span className="muted">—</span>,
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
key: 'tools',
|
|
679
|
+
label: 'Tools',
|
|
680
|
+
value: a.tools && a.tools.length > 0 ? (
|
|
681
|
+
<Inline gap={1} className="agents-detail__tools">
|
|
682
|
+
{a.tools.map((t) => (
|
|
683
|
+
<Badge key={t} variant="info" size="sm">{t}</Badge>
|
|
684
|
+
))}
|
|
685
|
+
</Inline>
|
|
686
|
+
) : <span className="muted">—</span>,
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
key: 'model',
|
|
690
|
+
label: 'Model',
|
|
691
|
+
mono: true,
|
|
692
|
+
copyable: true,
|
|
693
|
+
value: a.model || '—',
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
key: 'mode',
|
|
697
|
+
label: 'Mode',
|
|
698
|
+
value: a.mode || 'subagent',
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
key: 'color',
|
|
702
|
+
label: 'Color',
|
|
703
|
+
value: a.color ? (
|
|
704
|
+
<Inline gap={2} align="center">
|
|
705
|
+
<span
|
|
706
|
+
className="agents-detail__name-swatch"
|
|
707
|
+
style={{ background: a.color }}
|
|
708
|
+
aria-hidden="true"
|
|
709
|
+
/>
|
|
710
|
+
<code className="mono">{a.color}</code>
|
|
711
|
+
</Inline>
|
|
712
|
+
) : <span className="muted">—</span>,
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
key: 'description',
|
|
716
|
+
label: 'Description',
|
|
717
|
+
value: a.description
|
|
718
|
+
? <div className="agents-detail__description">{truncate(a.description, 600)}</div>
|
|
719
|
+
: <span className="muted">—</span>,
|
|
720
|
+
},
|
|
721
|
+
];
|
|
722
|
+
|
|
723
|
+
if (typeof lastSeenTs(a) === 'number') {
|
|
724
|
+
metaItems.push({
|
|
725
|
+
key: 'lastSeen',
|
|
726
|
+
label: 'Last seen',
|
|
727
|
+
mono: true,
|
|
728
|
+
value: formatRelative(lastSeenTs(a) as number),
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (a.lastError?.message) {
|
|
733
|
+
metaItems.push({
|
|
734
|
+
key: 'lastError',
|
|
735
|
+
label: 'Last error',
|
|
736
|
+
value: (
|
|
737
|
+
<div className="agents-detail__error">{a.lastError.message}</div>
|
|
738
|
+
),
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return (
|
|
743
|
+
<Stack gap={4} className="agents-detail__meta">
|
|
744
|
+
<KeyValueList items={metaItems} orientation="horizontal" />
|
|
745
|
+
|
|
746
|
+
<Box className="agents-detail__prompt">
|
|
747
|
+
<span className="agents-detail__prompt-label">System prompt</span>
|
|
748
|
+
{a.prompt ? (
|
|
749
|
+
<pre className="agents-detail__prompt-body">{a.prompt}</pre>
|
|
750
|
+
) : (
|
|
751
|
+
<span className="agents-detail__prompt-empty">No prompt configured.</span>
|
|
752
|
+
)}
|
|
753
|
+
</Box>
|
|
754
|
+
|
|
755
|
+
<Inline gap={2} wrap className="agents-detail__actions">
|
|
756
|
+
<UIButton
|
|
757
|
+
variant="secondary"
|
|
758
|
+
size="sm"
|
|
759
|
+
icon={<Pencil size={12} />}
|
|
760
|
+
onClick={() => onEdit(a)}
|
|
761
|
+
aria-label={`Edit ${a.name}`}
|
|
762
|
+
data-testid={`agents-detail-edit-${a.name}`}
|
|
763
|
+
>
|
|
764
|
+
Edit
|
|
765
|
+
</UIButton>
|
|
766
|
+
<UIButton
|
|
767
|
+
variant="secondary"
|
|
768
|
+
size="sm"
|
|
769
|
+
icon={<Play size={12} />}
|
|
770
|
+
onClick={() => onInvoke(a)}
|
|
771
|
+
aria-label={`Invoke ${a.name}`}
|
|
772
|
+
data-testid={`agents-detail-invoke-${a.name}`}
|
|
773
|
+
>
|
|
774
|
+
Invoke
|
|
775
|
+
</UIButton>
|
|
776
|
+
{canRestart && (
|
|
777
|
+
<UIButton
|
|
778
|
+
variant="ghost"
|
|
779
|
+
size="sm"
|
|
780
|
+
icon={<RotateCw size={12} />}
|
|
781
|
+
onClick={() => onRestart(a)}
|
|
782
|
+
aria-label={`Restart ${a.name}`}
|
|
783
|
+
data-testid={`agents-detail-restart-${a.name}`}
|
|
784
|
+
>
|
|
785
|
+
Restart
|
|
786
|
+
</UIButton>
|
|
787
|
+
)}
|
|
788
|
+
<UIButton
|
|
789
|
+
variant="danger"
|
|
790
|
+
size="sm"
|
|
791
|
+
icon={<Trash2 size={12} />}
|
|
792
|
+
onClick={() => onDelete(a)}
|
|
793
|
+
aria-label={`Delete ${a.name}`}
|
|
794
|
+
data-testid={`agents-detail-delete-${a.name}`}
|
|
795
|
+
>
|
|
796
|
+
Delete
|
|
797
|
+
</UIButton>
|
|
798
|
+
</Inline>
|
|
799
|
+
|
|
800
|
+
{a.isStuck && (
|
|
801
|
+
<Inline gap={2} align="center" className="agents-detail__error">
|
|
802
|
+
<CircleAlert size={12} />
|
|
803
|
+
<span>
|
|
804
|
+
Agent is stuck — restart to reset state.
|
|
805
|
+
</span>
|
|
806
|
+
</Inline>
|
|
807
|
+
)}
|
|
808
|
+
</Stack>
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// ─── Render ────────────────────────────────────────────────────────────
|
|
813
|
+
|
|
814
|
+
const categoryOptions = [
|
|
815
|
+
{ value: '', label: 'All categories' },
|
|
816
|
+
...CATEGORIES.map((c) => ({ value: c.id, label: c.label })),
|
|
817
|
+
{ value: '__none__', label: '(no category)' },
|
|
818
|
+
];
|
|
819
|
+
|
|
483
820
|
return (
|
|
484
|
-
<div className="
|
|
485
|
-
<header className="
|
|
486
|
-
<div className="
|
|
487
|
-
<h2 className="
|
|
488
|
-
<Bot size={18} />
|
|
821
|
+
<div className="agents-redesign" data-testid="agents-view">
|
|
822
|
+
<header className="agents-redesign__header">
|
|
823
|
+
<div className="agents-redesign__header-text">
|
|
824
|
+
<h2 className="agents-redesign__title">
|
|
825
|
+
<Bot size={18} aria-hidden="true" />
|
|
826
|
+
<span>Agents</span>
|
|
827
|
+
<span className="agents-redesign__title-count" data-testid="agents-count">
|
|
828
|
+
({sorted.length})
|
|
829
|
+
</span>
|
|
489
830
|
</h2>
|
|
490
|
-
<p className="
|
|
491
|
-
The Norse pantheon —
|
|
831
|
+
<p className="agents-redesign__subtitle">
|
|
832
|
+
The Norse pantheon — pick a row to inspect, <kbd>Edit</kbd> to modify, <kbd>Invoke</kbd> to dispatch.
|
|
492
833
|
</p>
|
|
493
834
|
</div>
|
|
494
|
-
<div className="
|
|
495
|
-
<
|
|
496
|
-
<label htmlFor="agents-search" className="
|
|
835
|
+
<div className="agents-redesign__controls">
|
|
836
|
+
<span className="agents-redesign__control" style={{ minWidth: 180 }}>
|
|
837
|
+
<label htmlFor="agents-search" className="vh">Search agents</label>
|
|
497
838
|
<input
|
|
498
839
|
id="agents-search"
|
|
499
840
|
className="input"
|
|
500
|
-
type="
|
|
501
|
-
placeholder="Search…"
|
|
841
|
+
type="search"
|
|
842
|
+
placeholder="Search name, description, tag…"
|
|
502
843
|
value={search}
|
|
503
844
|
onChange={(e) => setSearch(e.target.value)}
|
|
504
845
|
aria-label="Search agents"
|
|
846
|
+
data-testid="agents-search"
|
|
505
847
|
/>
|
|
506
|
-
</
|
|
507
|
-
<
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
848
|
+
</span>
|
|
849
|
+
<span className="agents-redesign__control" style={{ minWidth: 180 }}>
|
|
850
|
+
<label htmlFor="agents-category-filter" className="vh">Filter by category</label>
|
|
851
|
+
<select
|
|
852
|
+
id="agents-category-filter"
|
|
853
|
+
className="select"
|
|
854
|
+
value={categoryFilter}
|
|
855
|
+
onChange={(e) => setCategoryFilter(e.target.value)}
|
|
856
|
+
data-testid="agents-category-filter"
|
|
857
|
+
>
|
|
858
|
+
{categoryOptions.map((opt) => (
|
|
859
|
+
<option key={opt.value || '__all__'} value={opt.value}>{opt.label}</option>
|
|
860
|
+
))}
|
|
861
|
+
</select>
|
|
862
|
+
</span>
|
|
863
|
+
<UIButton
|
|
864
|
+
variant="ghost"
|
|
865
|
+
size="sm"
|
|
866
|
+
icon={<RefreshCw size={12} />}
|
|
867
|
+
onClick={reload}
|
|
868
|
+
aria-label="Refresh"
|
|
869
|
+
data-testid="agents-refresh"
|
|
513
870
|
>
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
</
|
|
871
|
+
Refresh
|
|
872
|
+
</UIButton>
|
|
873
|
+
<UIButton
|
|
874
|
+
variant="primary"
|
|
875
|
+
size="sm"
|
|
876
|
+
icon={<Plus size={12} />}
|
|
877
|
+
onClick={onCreate}
|
|
878
|
+
aria-label="New agent"
|
|
879
|
+
data-testid="agents-new"
|
|
880
|
+
>
|
|
881
|
+
New agent
|
|
882
|
+
</UIButton>
|
|
526
883
|
</div>
|
|
527
884
|
</header>
|
|
528
885
|
|
|
529
|
-
|
|
530
|
-
<
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
onEdit={() => onEdit(a)}
|
|
554
|
-
onDelete={() => onDelete(a)}
|
|
555
|
-
onRestart={() => onRestart(a)}
|
|
556
|
-
onSetStatus={(s) => onSetStatus(a, s)}
|
|
886
|
+
<div className="agents-redesign__body">
|
|
887
|
+
<Box className="agents-roster">
|
|
888
|
+
{loading ? (
|
|
889
|
+
<LoadingState label="Loading agents…" rows={3} />
|
|
890
|
+
) : sorted.length === 0 ? (
|
|
891
|
+
<EmptyState
|
|
892
|
+
icon={<Bot size={32} />}
|
|
893
|
+
title="No agents found"
|
|
894
|
+
description={
|
|
895
|
+
agents.length === 0
|
|
896
|
+
? 'Run bizar in the terminal to install Bizar.'
|
|
897
|
+
: 'No agents match the current filters — try clearing the search or category.'
|
|
898
|
+
}
|
|
899
|
+
action={
|
|
900
|
+
agents.length === 0
|
|
901
|
+
? undefined
|
|
902
|
+
: {
|
|
903
|
+
label: 'Clear filters',
|
|
904
|
+
onClick: () => {
|
|
905
|
+
setSearch('');
|
|
906
|
+
setCategoryFilter('');
|
|
907
|
+
},
|
|
908
|
+
}
|
|
909
|
+
}
|
|
557
910
|
/>
|
|
558
|
-
)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
onInvoke,
|
|
568
|
-
onEdit,
|
|
569
|
-
onDelete,
|
|
570
|
-
onRestart,
|
|
571
|
-
onSetStatus,
|
|
572
|
-
}: {
|
|
573
|
-
agent: Agent;
|
|
574
|
-
onInvoke: () => void;
|
|
575
|
-
onEdit: () => void;
|
|
576
|
-
onDelete: () => void;
|
|
577
|
-
onRestart: () => void;
|
|
578
|
-
onSetStatus: (s: 'idle' | 'working' | 'error') => void;
|
|
579
|
-
}) {
|
|
580
|
-
const [expanded, setExpanded] = useState(false);
|
|
581
|
-
const catColor = categoryColor(agent.category);
|
|
582
|
-
const isWorking = (agent.status === 'working' || !!agent.currentTaskId) && !agent.isStuck;
|
|
583
|
-
return (
|
|
584
|
-
<Card
|
|
585
|
-
variant="elevated"
|
|
586
|
-
interactive
|
|
587
|
-
className={cn(
|
|
588
|
-
'agent-card',
|
|
589
|
-
isWorking && 'is-working',
|
|
590
|
-
agent.isStuck && 'is-stuck',
|
|
591
|
-
)}
|
|
592
|
-
>
|
|
593
|
-
<div className="agent-card-head">
|
|
594
|
-
<div className="agent-card-name">
|
|
595
|
-
<StatusDot status={agent.status} isStuck={agent.isStuck} />
|
|
596
|
-
{agent.name}
|
|
597
|
-
</div>
|
|
598
|
-
<div className="agent-card-badges">
|
|
599
|
-
{agent.category && (
|
|
600
|
-
<span
|
|
601
|
-
className="agent-card-category"
|
|
602
|
-
style={{ background: `color-mix(in srgb, ${catColor} 18%, transparent)`, color: catColor }}
|
|
603
|
-
>
|
|
604
|
-
{agent.category}
|
|
605
|
-
</span>
|
|
606
|
-
)}
|
|
607
|
-
<StatusPill agent={agent} />
|
|
608
|
-
</div>
|
|
609
|
-
</div>
|
|
610
|
-
<p className="agent-card-desc">{truncate(agent.description, 200)}</p>
|
|
611
|
-
<div className="agent-card-meta">
|
|
612
|
-
<span className="mono" title={agent.model || ''}>
|
|
613
|
-
{agent.model || '—'}
|
|
614
|
-
</span>
|
|
615
|
-
<span className="tabular-nums muted">{formatRelative(agent.mtime)}</span>
|
|
616
|
-
</div>
|
|
617
|
-
{agent.tags && agent.tags.length > 0 && (
|
|
618
|
-
<div className="agent-card-tags">
|
|
619
|
-
{agent.tags.map((t) => (
|
|
620
|
-
<span key={t} className="agent-card-tag">{t}</span>
|
|
621
|
-
))}
|
|
622
|
-
</div>
|
|
623
|
-
)}
|
|
624
|
-
{(isWorking || agent.lastTask) && (
|
|
625
|
-
<div className="agent-card-activity">
|
|
626
|
-
{agent.currentTaskId && (
|
|
627
|
-
<div className="agent-card-row">
|
|
628
|
-
<Activity size={12} />
|
|
629
|
-
<span className="muted">Working on</span>
|
|
630
|
-
<code className="mono">{agent.currentTaskId}</code>
|
|
631
|
-
</div>
|
|
632
|
-
)}
|
|
633
|
-
{agent.lastTask && !agent.currentTaskId && (
|
|
634
|
-
<div className="agent-card-row">
|
|
635
|
-
<CheckCircle2 size={12} />
|
|
636
|
-
<span className="muted">Last</span>
|
|
637
|
-
<code className="mono">{agent.lastTask.id}</code>
|
|
638
|
-
<span className="muted tabular-nums">{formatRelative(agent.lastTask.finishedAt)}</span>
|
|
639
|
-
</div>
|
|
640
|
-
)}
|
|
641
|
-
{agent.tasksTotal != null && agent.tasksTotal > 0 && (
|
|
642
|
-
<div className="agent-card-row">
|
|
643
|
-
<Circle size={12} />
|
|
644
|
-
<span className="muted">Success rate</span>
|
|
645
|
-
<span className="tabular-nums">{Math.round((agent.successRate || 0) * 100)}%</span>
|
|
646
|
-
<span className="muted tabular-nums">({agent.tasksSucceeded}/{agent.tasksTotal})</span>
|
|
911
|
+
) : (
|
|
912
|
+
<div className="agents-roster__table" data-testid="agents-table">
|
|
913
|
+
<DataTable<Agent>
|
|
914
|
+
columns={columns}
|
|
915
|
+
data={sorted}
|
|
916
|
+
rowKey={(a) => a.name}
|
|
917
|
+
compact
|
|
918
|
+
onRowClick={(a) => setSelectedName(a.name)}
|
|
919
|
+
/>
|
|
647
920
|
</div>
|
|
648
921
|
)}
|
|
649
|
-
</
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
style={{ marginLeft: 'auto' }}
|
|
922
|
+
</Box>
|
|
923
|
+
|
|
924
|
+
<Panel
|
|
925
|
+
title={
|
|
926
|
+
selected ? (
|
|
927
|
+
<span className="agents-detail__name">
|
|
928
|
+
<span
|
|
929
|
+
className={cx(
|
|
930
|
+
'agents-detail__name-swatch',
|
|
931
|
+
!selected.color && 'agents-detail__name-swatch--empty',
|
|
932
|
+
)}
|
|
933
|
+
style={selected.color ? { background: selected.color } : undefined}
|
|
934
|
+
aria-hidden="true"
|
|
935
|
+
/>
|
|
936
|
+
<span>{selected.name}</span>
|
|
937
|
+
</span>
|
|
938
|
+
) : (
|
|
939
|
+
'Agent detail'
|
|
940
|
+
)
|
|
941
|
+
}
|
|
942
|
+
description={
|
|
943
|
+
selected
|
|
944
|
+
? selected.path
|
|
945
|
+
: 'Select an agent from the roster to see its metadata.'
|
|
946
|
+
}
|
|
947
|
+
padding={4}
|
|
948
|
+
variant="outlined"
|
|
949
|
+
className="agents-detail"
|
|
678
950
|
>
|
|
679
|
-
{
|
|
680
|
-
</
|
|
951
|
+
{renderDetailBody()}
|
|
952
|
+
</Panel>
|
|
681
953
|
</div>
|
|
682
|
-
|
|
683
|
-
<div className="agent-card-expanded">
|
|
684
|
-
<div className="agent-card-status-actions">
|
|
685
|
-
<span className="muted text-sm">Set status:</span>
|
|
686
|
-
<Button
|
|
687
|
-
variant={agent.status === 'idle' ? 'primary' : 'ghost'}
|
|
688
|
-
size="sm"
|
|
689
|
-
onClick={() => onSetStatus('idle')}
|
|
690
|
-
>
|
|
691
|
-
Idle
|
|
692
|
-
</Button>
|
|
693
|
-
<Button
|
|
694
|
-
variant={agent.status === 'working' ? 'primary' : 'ghost'}
|
|
695
|
-
size="sm"
|
|
696
|
-
onClick={() => onSetStatus('working')}
|
|
697
|
-
>
|
|
698
|
-
Working
|
|
699
|
-
</Button>
|
|
700
|
-
<Button
|
|
701
|
-
variant={agent.status === 'error' ? 'primary' : 'ghost'}
|
|
702
|
-
size="sm"
|
|
703
|
-
onClick={() => onSetStatus('error')}
|
|
704
|
-
>
|
|
705
|
-
Error
|
|
706
|
-
</Button>
|
|
707
|
-
</div>
|
|
708
|
-
<div className="agent-card-meta">
|
|
709
|
-
<Folder size={11} />
|
|
710
|
-
<code className="mono agent-card-path">{agent.path}</code>
|
|
711
|
-
</div>
|
|
712
|
-
</div>
|
|
713
|
-
)}
|
|
714
|
-
</Card>
|
|
954
|
+
</div>
|
|
715
955
|
);
|
|
716
|
-
}
|
|
956
|
+
}
|