@polderlabs/bizar 6.3.0 → 7.0.1
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/README.md +125 -47
- package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-weVnLz3Z.js} +3 -3
- package/bizar-dash/dist/assets/EnvVarsSection-weVnLz3Z.js.map +1 -0
- package/bizar-dash/dist/assets/MobileChat-BRFjer8p.js +1 -0
- package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-BRFjer8p.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js +1 -0
- package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js.map +1 -0
- 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-BE4Uh9im.css +1 -0
- package/bizar-dash/dist/assets/main-DqC58FEZ.js +18 -0
- package/bizar-dash/dist/assets/main-DqC58FEZ.js.map +1 -0
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-DRZsK3ic.js +1 -0
- package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DRZsK3ic.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-layout-BWLtwtfQ.css +1 -0
- package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js +2 -0
- package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.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-BAxVOU_a.js +2 -0
- package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.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 +13 -11
- package/bizar-dash/dist/mobile.html +8 -5
- package/bizar-dash/src/server/api.mjs +11 -0
- package/bizar-dash/src/server/memory-consolidator.mjs +225 -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/mobile/MobileApp.tsx +33 -2
- package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +2 -2
- package/bizar-dash/src/web/mobile/MobileEval.tsx +4 -4
- package/bizar-dash/src/web/mobile/MobileMemory.tsx +2 -2
- package/bizar-dash/src/web/mobile/MobileSettings.tsx +3 -3
- package/bizar-dash/src/web/mobile/MobileTasks.tsx +2 -2
- package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +3 -3
- 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/mobile.css +41 -0
- package/bizar-dash/src/web/styles/tasks-redesign.css +253 -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/Textarea.tsx +56 -0
- package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
- package/bizar-dash/src/web/ui/controls/controls.css +476 -0
- package/bizar-dash/src/web/ui/controls/index.ts +35 -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/ViewHeader.tsx +48 -0
- package/bizar-dash/src/web/ui/layout/index.ts +34 -0
- package/bizar-dash/src/web/ui/layout/layout.css +464 -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 +208 -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 +516 -268
- package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
- package/bizar-dash/src/web/views/Overview.tsx +985 -448
- package/bizar-dash/src/web/views/Tasks.tsx +271 -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/Textarea.test.tsx +56 -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/layout/ViewHeader.test.tsx +47 -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/provision-claude.mjs +94 -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/EnvVarsSection-B58aiJiE.js.map +0 -1
- 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/MobileSettings-CEQNJNLJ.js.map +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/markdown-tOLaD6nm.js.map +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/mobile-layout-CJnZNLy3.css +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/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +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;
|
|
@@ -49,36 +62,63 @@ const MODELS = [
|
|
|
49
62
|
'openrouter/minimax/minimax-m2.7',
|
|
50
63
|
];
|
|
51
64
|
|
|
65
|
+
// Default agent swatch — the only place in the dashboard where a
|
|
66
|
+
// literal hex appears. HTML <input type="color"> requires CSS Color
|
|
67
|
+
// Module Level 3 hex format (no `oklch()`, no `var(--token)`). Keep
|
|
68
|
+
// this in sync with --default-agent-swatch in ui/styles/tokens.css;
|
|
69
|
+
// the token is what every CSS-side swatch reads, this is just the
|
|
70
|
+
// native color-input seed value.
|
|
71
|
+
const DEFAULT_AGENT_SWATCH_HEX = '#8b5cf6';
|
|
72
|
+
|
|
52
73
|
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',
|
|
74
|
+
{ id: 'reasoning', label: 'Reasoning', variant: 'accent' as const },
|
|
75
|
+
{ id: 'code', label: 'Code', variant: 'info' as const },
|
|
76
|
+
{ id: 'design', label: 'Design', variant: 'success' as const },
|
|
77
|
+
{ id: 'planning', label: 'Planning', variant: 'warning' as const },
|
|
78
|
+
{ id: 'gitops', label: 'GitOps', variant: 'danger' as const },
|
|
79
|
+
{ id: 'analysis', label: 'Analysis', variant: 'neutral' as const },
|
|
59
80
|
];
|
|
60
81
|
|
|
61
|
-
function
|
|
62
|
-
|
|
82
|
+
function categoryMeta(cat: string | undefined): { label: string; variant: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'accent' } {
|
|
83
|
+
if (!cat) return { label: '—', variant: 'neutral' };
|
|
84
|
+
const found = CATEGORIES.find((c) => c.id === cat);
|
|
85
|
+
return found
|
|
86
|
+
? { label: found.label, variant: found.variant }
|
|
87
|
+
: { label: cat, variant: 'neutral' };
|
|
63
88
|
}
|
|
64
89
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
type StatusKind = 'idle' | 'working' | 'error' | 'stuck';
|
|
91
|
+
|
|
92
|
+
function statusKind(agent: Agent): StatusKind {
|
|
93
|
+
if (agent.isStuck) return 'stuck';
|
|
94
|
+
const s = (agent.status || 'idle') as string;
|
|
95
|
+
if (s === 'working') return 'working';
|
|
96
|
+
if (s === 'error') return 'error';
|
|
97
|
+
return 'idle';
|
|
72
98
|
}
|
|
73
99
|
|
|
74
|
-
function
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
100
|
+
function statusLabel(kind: StatusKind): string {
|
|
101
|
+
if (kind === 'stuck') return 'stuck';
|
|
102
|
+
return kind;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function statusDotVariant(kind: StatusKind): 'neutral' | 'info' | 'danger' {
|
|
106
|
+
if (kind === 'working') return 'info';
|
|
107
|
+
if (kind === 'error' || kind === 'stuck') return 'danger';
|
|
108
|
+
return 'neutral';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function lastSeenTs(agent: Agent): number | undefined {
|
|
112
|
+
// The Agent type exposes `lastSeen` (legacy) and a future `lastSeenAt`.
|
|
113
|
+
// Either is acceptable; missing fields render as "—".
|
|
114
|
+
const a = agent as unknown as { lastSeenAt?: number };
|
|
115
|
+
return a.lastSeenAt ?? agent.lastSeen;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function tagsToString(tags: string[] | undefined, max = 4): string {
|
|
119
|
+
if (!tags || tags.length === 0) return '—';
|
|
120
|
+
const shown = tags.slice(0, max).join(', ');
|
|
121
|
+
return tags.length > max ? `${shown} +${tags.length - max}` : shown;
|
|
82
122
|
}
|
|
83
123
|
|
|
84
124
|
export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
@@ -88,13 +128,22 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
88
128
|
const [loading, setLoading] = useState(!snapshot.agents);
|
|
89
129
|
const [categoryFilter, setCategoryFilter] = useState<string>('');
|
|
90
130
|
const [search, setSearch] = useState('');
|
|
131
|
+
const [selectedName, setSelectedName] = useState<string | null>(null);
|
|
91
132
|
|
|
92
133
|
useEffect(() => {
|
|
93
134
|
setAgents(snapshot.agents || []);
|
|
94
135
|
setLoading(!snapshot.agents);
|
|
95
136
|
}, [snapshot.agents]);
|
|
96
137
|
|
|
97
|
-
|
|
138
|
+
// If the selected agent disappears from the list (deleted elsewhere, WS
|
|
139
|
+
// swap), clear the selection so the detail panel reverts to the placeholder.
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (selectedName && !agents.some((a) => a.name === selectedName)) {
|
|
142
|
+
setSelectedName(null);
|
|
143
|
+
}
|
|
144
|
+
}, [agents, selectedName]);
|
|
145
|
+
|
|
146
|
+
const reload = async (): Promise<void> => {
|
|
98
147
|
try {
|
|
99
148
|
const d = await api.get<{ agents: Agent[] }>('/agents');
|
|
100
149
|
setAgents(d.agents || []);
|
|
@@ -108,7 +157,11 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
108
157
|
const sorted = useMemo(() => {
|
|
109
158
|
let out = [...agents];
|
|
110
159
|
if (categoryFilter) {
|
|
111
|
-
out = out.filter((a) =>
|
|
160
|
+
out = out.filter((a) =>
|
|
161
|
+
categoryFilter === '__none__'
|
|
162
|
+
? !(a.category || '')
|
|
163
|
+
: (a.category || '') === categoryFilter,
|
|
164
|
+
);
|
|
112
165
|
}
|
|
113
166
|
if (search) {
|
|
114
167
|
const q = search.toLowerCase();
|
|
@@ -122,13 +175,16 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
122
175
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
123
176
|
}, [agents, categoryFilter, search]);
|
|
124
177
|
|
|
125
|
-
const
|
|
126
|
-
const set = new Set<string>();
|
|
127
|
-
for (const a of agents) for (const t of a.tags || []) set.add(t);
|
|
128
|
-
return Array.from(set).sort();
|
|
129
|
-
}, [agents]);
|
|
178
|
+
const hasLastSeen = sorted.some((a) => typeof lastSeenTs(a) === 'number');
|
|
130
179
|
|
|
131
|
-
const
|
|
180
|
+
const selected = useMemo(
|
|
181
|
+
() => sorted.find((a) => a.name === selectedName) || null,
|
|
182
|
+
[sorted, selectedName],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// ─── Handlers (modal flows kept on the legacy Modal) ───────────────────
|
|
186
|
+
|
|
187
|
+
const onCreate = (): void => {
|
|
132
188
|
let nameEl: HTMLInputElement | null = null;
|
|
133
189
|
let descEl: HTMLInputElement | null = null;
|
|
134
190
|
let modelEl: HTMLSelectElement | null = null;
|
|
@@ -181,7 +237,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
181
237
|
</div>
|
|
182
238
|
<div className="task-form-field" style={{ flex: '0 0 80px' }}>
|
|
183
239
|
<label className="field-label" htmlFor="agent-new-color">Color</label>
|
|
184
|
-
<input id="agent-new-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue=
|
|
240
|
+
<input id="agent-new-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue={DEFAULT_AGENT_SWATCH_HEX} />
|
|
185
241
|
</div>
|
|
186
242
|
</div>
|
|
187
243
|
<div className="task-form-row">
|
|
@@ -251,6 +307,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
251
307
|
prompt: promptEl?.value || '',
|
|
252
308
|
});
|
|
253
309
|
setAgents((cur) => [...cur, created]);
|
|
310
|
+
setSelectedName(created.name);
|
|
254
311
|
toast.success('Agent created.');
|
|
255
312
|
modal.close();
|
|
256
313
|
await refreshSnapshot();
|
|
@@ -266,7 +323,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
266
323
|
});
|
|
267
324
|
};
|
|
268
325
|
|
|
269
|
-
const onEdit = async (a: Agent) => {
|
|
326
|
+
const onEdit = async (a: Agent): Promise<void> => {
|
|
270
327
|
let descEl: HTMLInputElement | null = null;
|
|
271
328
|
let modelEl: HTMLSelectElement | null = null;
|
|
272
329
|
let modeEl: HTMLSelectElement | null = null;
|
|
@@ -313,7 +370,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
313
370
|
</div>
|
|
314
371
|
<div className="task-form-field" style={{ flex: '0 0 80px' }}>
|
|
315
372
|
<label className="field-label" htmlFor="agent-edit-color">Color</label>
|
|
316
|
-
<input id="agent-edit-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue={full.color ||
|
|
373
|
+
<input id="agent-edit-color" ref={(el) => (colorEl = el)} className="input" type="color" defaultValue={full.color || DEFAULT_AGENT_SWATCH_HEX} />
|
|
317
374
|
</div>
|
|
318
375
|
</div>
|
|
319
376
|
<div className="task-form-row">
|
|
@@ -345,7 +402,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
345
402
|
<input type="checkbox" value={t} aria-label={t} defaultChecked={full.tools?.includes(t)} />
|
|
346
403
|
<span>{t}</span>
|
|
347
404
|
</label>
|
|
348
|
-
|
|
405
|
+
))}
|
|
349
406
|
</div>
|
|
350
407
|
</fieldset>
|
|
351
408
|
<label className="field-label" htmlFor="agent-edit-prompt">System prompt</label>
|
|
@@ -404,7 +461,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
404
461
|
}
|
|
405
462
|
};
|
|
406
463
|
|
|
407
|
-
const onDelete = async (a: Agent) => {
|
|
464
|
+
const onDelete = async (a: Agent): Promise<void> => {
|
|
408
465
|
if (!confirm(`Delete agent "${a.name}"? This removes ${a.path}.`)) return;
|
|
409
466
|
try {
|
|
410
467
|
await api.del(`/agents/${encodeURIComponent(a.name)}`);
|
|
@@ -415,7 +472,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
415
472
|
}
|
|
416
473
|
};
|
|
417
474
|
|
|
418
|
-
const onInvoke =
|
|
475
|
+
const onInvoke = (a: Agent): void => {
|
|
419
476
|
let promptEl: HTMLTextAreaElement | null = null;
|
|
420
477
|
modal.open({
|
|
421
478
|
title: `Invoke ${a.name}`,
|
|
@@ -461,7 +518,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
461
518
|
});
|
|
462
519
|
};
|
|
463
520
|
|
|
464
|
-
const onRestart = async (a: Agent) => {
|
|
521
|
+
const onRestart = async (a: Agent): Promise<void> => {
|
|
465
522
|
try {
|
|
466
523
|
const updated = await api.post<Agent>(`/agents/${encodeURIComponent(a.name)}/restart`);
|
|
467
524
|
setAgents((cur) => cur.map((x) => (x.name === a.name ? updated : x)));
|
|
@@ -471,7 +528,7 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
471
528
|
}
|
|
472
529
|
};
|
|
473
530
|
|
|
474
|
-
const onSetStatus = async (a: Agent, status: 'idle' | 'working' | 'error') => {
|
|
531
|
+
const onSetStatus = async (a: Agent, status: 'idle' | 'working' | 'error'): Promise<void> => {
|
|
475
532
|
try {
|
|
476
533
|
const updated = await api.post<Agent>(`/agents/${encodeURIComponent(a.name)}/status`, { status });
|
|
477
534
|
setAgents((cur) => cur.map((x) => (x.name === a.name ? updated : x)));
|
|
@@ -480,237 +537,428 @@ export function Agents({ snapshot, refreshSnapshot }: Props) {
|
|
|
480
537
|
}
|
|
481
538
|
};
|
|
482
539
|
|
|
540
|
+
// ─── Table column definitions ──────────────────────────────────────────
|
|
541
|
+
|
|
542
|
+
type Col = DataTableColumn<Agent>;
|
|
543
|
+
|
|
544
|
+
const columns: Col[] = [
|
|
545
|
+
{
|
|
546
|
+
key: 'name',
|
|
547
|
+
header: 'Name',
|
|
548
|
+
sortable: true,
|
|
549
|
+
accessor: (a) => a.name,
|
|
550
|
+
render: (a) => (
|
|
551
|
+
<span className="agents-name-cell">
|
|
552
|
+
<span
|
|
553
|
+
className={cx(
|
|
554
|
+
'agents-name-cell__swatch',
|
|
555
|
+
!a.color && 'agents-name-cell__swatch--empty',
|
|
556
|
+
)}
|
|
557
|
+
style={a.color ? { background: a.color } : undefined}
|
|
558
|
+
aria-hidden="true"
|
|
559
|
+
/>
|
|
560
|
+
{a.name}
|
|
561
|
+
</span>
|
|
562
|
+
),
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
key: 'status',
|
|
566
|
+
header: 'Status',
|
|
567
|
+
sortable: true,
|
|
568
|
+
accessor: (a) => statusKind(a),
|
|
569
|
+
render: (a) => {
|
|
570
|
+
const kind = statusKind(a);
|
|
571
|
+
return (
|
|
572
|
+
<span className="agents-status-cell">
|
|
573
|
+
<StatusDot
|
|
574
|
+
variant={statusDotVariant(kind)}
|
|
575
|
+
size="sm"
|
|
576
|
+
pulse={kind === 'stuck'}
|
|
577
|
+
label={statusLabel(kind)}
|
|
578
|
+
/>
|
|
579
|
+
<span
|
|
580
|
+
className={cx(
|
|
581
|
+
'agents-status-cell__label',
|
|
582
|
+
kind === 'stuck' && 'agents-status-cell__label--stuck',
|
|
583
|
+
)}
|
|
584
|
+
>
|
|
585
|
+
{statusLabel(kind)}
|
|
586
|
+
</span>
|
|
587
|
+
</span>
|
|
588
|
+
);
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
key: 'category',
|
|
593
|
+
header: 'Category',
|
|
594
|
+
sortable: true,
|
|
595
|
+
accessor: (a) => a.category || '',
|
|
596
|
+
render: (a) => {
|
|
597
|
+
const meta = categoryMeta(a.category);
|
|
598
|
+
if (!a.category) return <span className="muted">—</span>;
|
|
599
|
+
return (
|
|
600
|
+
<span className="agents-category-cell">
|
|
601
|
+
<Badge variant={meta.variant} size="sm">{meta.label}</Badge>
|
|
602
|
+
</span>
|
|
603
|
+
);
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
key: 'tags',
|
|
608
|
+
header: 'Tags',
|
|
609
|
+
accessor: (a) => (a.tags || []).join(','),
|
|
610
|
+
render: (a) => (
|
|
611
|
+
<span className="agents-tags-cell" title={(a.tags || []).join(', ')}>
|
|
612
|
+
{tagsToString(a.tags)}
|
|
613
|
+
</span>
|
|
614
|
+
),
|
|
615
|
+
},
|
|
616
|
+
];
|
|
617
|
+
|
|
618
|
+
if (hasLastSeen) {
|
|
619
|
+
columns.push({
|
|
620
|
+
key: 'lastSeen',
|
|
621
|
+
header: 'Last seen',
|
|
622
|
+
sortable: true,
|
|
623
|
+
accessor: (a) => lastSeenTs(a) || 0,
|
|
624
|
+
render: (a) => {
|
|
625
|
+
const ts = lastSeenTs(a);
|
|
626
|
+
return <span className="muted tabular-nums">{ts ? formatRelative(ts) : '—'}</span>;
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// ─── Detail panel ─────────────────────────────────────────────────────
|
|
632
|
+
|
|
633
|
+
function renderDetailBody(): React.JSX.Element {
|
|
634
|
+
if (!selected) {
|
|
635
|
+
return (
|
|
636
|
+
<div className="agents-detail__placeholder">
|
|
637
|
+
<span className="agents-detail__placeholder-icon" aria-hidden="true">
|
|
638
|
+
<Bot size={28} />
|
|
639
|
+
</span>
|
|
640
|
+
<span className="agents-detail__placeholder-title">No agent selected</span>
|
|
641
|
+
<span>Pick a row from the table to see its metadata, actions, and prompt.</span>
|
|
642
|
+
</div>
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const a = selected;
|
|
647
|
+
const kind = statusKind(a);
|
|
648
|
+
const catMeta = categoryMeta(a.category);
|
|
649
|
+
const canRestart = a.isStuck || a.status === 'working' || a.status === 'error';
|
|
650
|
+
|
|
651
|
+
const metaItems: KeyValueItem[] = [
|
|
652
|
+
{
|
|
653
|
+
key: 'status',
|
|
654
|
+
label: 'Status',
|
|
655
|
+
value: (
|
|
656
|
+
<span className="agents-status-cell">
|
|
657
|
+
<StatusDot
|
|
658
|
+
variant={statusDotVariant(kind)}
|
|
659
|
+
size="sm"
|
|
660
|
+
pulse={kind === 'stuck'}
|
|
661
|
+
label={statusLabel(kind)}
|
|
662
|
+
/>
|
|
663
|
+
<span className="agents-status-cell__label">{statusLabel(kind)}</span>
|
|
664
|
+
</span>
|
|
665
|
+
),
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
key: 'category',
|
|
669
|
+
label: 'Category',
|
|
670
|
+
value: a.category
|
|
671
|
+
? <Badge variant={catMeta.variant} size="sm">{catMeta.label}</Badge>
|
|
672
|
+
: <span className="muted">—</span>,
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
key: 'tags',
|
|
676
|
+
label: 'Tags',
|
|
677
|
+
value: a.tags && a.tags.length > 0 ? (
|
|
678
|
+
<Inline gap={1} className="agents-detail__tags">
|
|
679
|
+
{a.tags.map((t) => (
|
|
680
|
+
<Badge key={t} variant="neutral" size="sm">{t}</Badge>
|
|
681
|
+
))}
|
|
682
|
+
</Inline>
|
|
683
|
+
) : <span className="muted">—</span>,
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
key: 'tools',
|
|
687
|
+
label: 'Tools',
|
|
688
|
+
value: a.tools && a.tools.length > 0 ? (
|
|
689
|
+
<Inline gap={1} className="agents-detail__tools">
|
|
690
|
+
{a.tools.map((t) => (
|
|
691
|
+
<Badge key={t} variant="info" size="sm">{t}</Badge>
|
|
692
|
+
))}
|
|
693
|
+
</Inline>
|
|
694
|
+
) : <span className="muted">—</span>,
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
key: 'model',
|
|
698
|
+
label: 'Model',
|
|
699
|
+
mono: true,
|
|
700
|
+
copyable: true,
|
|
701
|
+
value: a.model || '—',
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
key: 'mode',
|
|
705
|
+
label: 'Mode',
|
|
706
|
+
value: a.mode || 'subagent',
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
key: 'color',
|
|
710
|
+
label: 'Color',
|
|
711
|
+
value: a.color ? (
|
|
712
|
+
<Inline gap={2} align="center">
|
|
713
|
+
<span
|
|
714
|
+
className="agents-detail__name-swatch"
|
|
715
|
+
style={{ background: a.color }}
|
|
716
|
+
aria-hidden="true"
|
|
717
|
+
/>
|
|
718
|
+
<code className="mono">{a.color}</code>
|
|
719
|
+
</Inline>
|
|
720
|
+
) : <span className="muted">—</span>,
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
key: 'description',
|
|
724
|
+
label: 'Description',
|
|
725
|
+
value: a.description
|
|
726
|
+
? <div className="agents-detail__description">{truncate(a.description, 600)}</div>
|
|
727
|
+
: <span className="muted">—</span>,
|
|
728
|
+
},
|
|
729
|
+
];
|
|
730
|
+
|
|
731
|
+
if (typeof lastSeenTs(a) === 'number') {
|
|
732
|
+
metaItems.push({
|
|
733
|
+
key: 'lastSeen',
|
|
734
|
+
label: 'Last seen',
|
|
735
|
+
mono: true,
|
|
736
|
+
value: formatRelative(lastSeenTs(a) as number),
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (a.lastError?.message) {
|
|
741
|
+
metaItems.push({
|
|
742
|
+
key: 'lastError',
|
|
743
|
+
label: 'Last error',
|
|
744
|
+
value: (
|
|
745
|
+
<div className="agents-detail__error">{a.lastError.message}</div>
|
|
746
|
+
),
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return (
|
|
751
|
+
<Stack gap={4} className="agents-detail__meta">
|
|
752
|
+
<KeyValueList items={metaItems} orientation="horizontal" />
|
|
753
|
+
|
|
754
|
+
<Box className="agents-detail__prompt">
|
|
755
|
+
<span className="agents-detail__prompt-label">System prompt</span>
|
|
756
|
+
{a.prompt ? (
|
|
757
|
+
<pre className="agents-detail__prompt-body">{a.prompt}</pre>
|
|
758
|
+
) : (
|
|
759
|
+
<span className="agents-detail__prompt-empty">No prompt configured.</span>
|
|
760
|
+
)}
|
|
761
|
+
</Box>
|
|
762
|
+
|
|
763
|
+
<Inline gap={2} wrap className="agents-detail__actions">
|
|
764
|
+
<UIButton
|
|
765
|
+
variant="secondary"
|
|
766
|
+
size="sm"
|
|
767
|
+
icon={<Pencil size={12} />}
|
|
768
|
+
onClick={() => onEdit(a)}
|
|
769
|
+
aria-label={`Edit ${a.name}`}
|
|
770
|
+
data-testid={`agents-detail-edit-${a.name}`}
|
|
771
|
+
>
|
|
772
|
+
Edit
|
|
773
|
+
</UIButton>
|
|
774
|
+
<UIButton
|
|
775
|
+
variant="secondary"
|
|
776
|
+
size="sm"
|
|
777
|
+
icon={<Play size={12} />}
|
|
778
|
+
onClick={() => onInvoke(a)}
|
|
779
|
+
aria-label={`Invoke ${a.name}`}
|
|
780
|
+
data-testid={`agents-detail-invoke-${a.name}`}
|
|
781
|
+
>
|
|
782
|
+
Invoke
|
|
783
|
+
</UIButton>
|
|
784
|
+
{canRestart && (
|
|
785
|
+
<UIButton
|
|
786
|
+
variant="ghost"
|
|
787
|
+
size="sm"
|
|
788
|
+
icon={<RotateCw size={12} />}
|
|
789
|
+
onClick={() => onRestart(a)}
|
|
790
|
+
aria-label={`Restart ${a.name}`}
|
|
791
|
+
data-testid={`agents-detail-restart-${a.name}`}
|
|
792
|
+
>
|
|
793
|
+
Restart
|
|
794
|
+
</UIButton>
|
|
795
|
+
)}
|
|
796
|
+
<UIButton
|
|
797
|
+
variant="danger"
|
|
798
|
+
size="sm"
|
|
799
|
+
icon={<Trash2 size={12} />}
|
|
800
|
+
onClick={() => onDelete(a)}
|
|
801
|
+
aria-label={`Delete ${a.name}`}
|
|
802
|
+
data-testid={`agents-detail-delete-${a.name}`}
|
|
803
|
+
>
|
|
804
|
+
Delete
|
|
805
|
+
</UIButton>
|
|
806
|
+
</Inline>
|
|
807
|
+
|
|
808
|
+
{a.isStuck && (
|
|
809
|
+
<Inline gap={2} align="center" className="agents-detail__error">
|
|
810
|
+
<CircleAlert size={12} />
|
|
811
|
+
<span>
|
|
812
|
+
Agent is stuck — restart to reset state.
|
|
813
|
+
</span>
|
|
814
|
+
</Inline>
|
|
815
|
+
)}
|
|
816
|
+
</Stack>
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ─── Render ────────────────────────────────────────────────────────────
|
|
821
|
+
|
|
822
|
+
const categoryOptions = [
|
|
823
|
+
{ value: '', label: 'All categories' },
|
|
824
|
+
...CATEGORIES.map((c) => ({ value: c.id, label: c.label })),
|
|
825
|
+
{ value: '__none__', label: '(no category)' },
|
|
826
|
+
];
|
|
827
|
+
|
|
483
828
|
return (
|
|
484
|
-
<div className="
|
|
485
|
-
<header className="
|
|
486
|
-
<div className="
|
|
487
|
-
<h2 className="
|
|
488
|
-
<Bot size={18} />
|
|
829
|
+
<div className="agents-redesign" data-testid="agents-view">
|
|
830
|
+
<header className="agents-redesign__header">
|
|
831
|
+
<div className="agents-redesign__header-text">
|
|
832
|
+
<h2 className="agents-redesign__title">
|
|
833
|
+
<Bot size={18} aria-hidden="true" />
|
|
834
|
+
<span>Agents</span>
|
|
835
|
+
<span className="agents-redesign__title-count" data-testid="agents-count">
|
|
836
|
+
({sorted.length})
|
|
837
|
+
</span>
|
|
489
838
|
</h2>
|
|
490
|
-
<p className="
|
|
491
|
-
The Norse pantheon —
|
|
839
|
+
<p className="agents-redesign__subtitle">
|
|
840
|
+
The Norse pantheon — pick a row to inspect, <kbd>Edit</kbd> to modify, <kbd>Invoke</kbd> to dispatch.
|
|
492
841
|
</p>
|
|
493
842
|
</div>
|
|
494
|
-
<div className="
|
|
495
|
-
<
|
|
496
|
-
<label htmlFor="agents-search" className="
|
|
843
|
+
<div className="agents-redesign__controls">
|
|
844
|
+
<span className="agents-redesign__control" style={{ minWidth: 180 }}>
|
|
845
|
+
<label htmlFor="agents-search" className="vh">Search agents</label>
|
|
497
846
|
<input
|
|
498
847
|
id="agents-search"
|
|
499
848
|
className="input"
|
|
500
|
-
type="
|
|
501
|
-
placeholder="Search…"
|
|
849
|
+
type="search"
|
|
850
|
+
placeholder="Search name, description, tag…"
|
|
502
851
|
value={search}
|
|
503
852
|
onChange={(e) => setSearch(e.target.value)}
|
|
504
853
|
aria-label="Search agents"
|
|
854
|
+
data-testid="agents-search"
|
|
505
855
|
/>
|
|
506
|
-
</
|
|
507
|
-
<
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
856
|
+
</span>
|
|
857
|
+
<span className="agents-redesign__control" style={{ minWidth: 180 }}>
|
|
858
|
+
<label htmlFor="agents-category-filter" className="vh">Filter by category</label>
|
|
859
|
+
<select
|
|
860
|
+
id="agents-category-filter"
|
|
861
|
+
className="select"
|
|
862
|
+
value={categoryFilter}
|
|
863
|
+
onChange={(e) => setCategoryFilter(e.target.value)}
|
|
864
|
+
data-testid="agents-category-filter"
|
|
865
|
+
>
|
|
866
|
+
{categoryOptions.map((opt) => (
|
|
867
|
+
<option key={opt.value || '__all__'} value={opt.value}>{opt.label}</option>
|
|
868
|
+
))}
|
|
869
|
+
</select>
|
|
870
|
+
</span>
|
|
871
|
+
<UIButton
|
|
872
|
+
variant="ghost"
|
|
873
|
+
size="sm"
|
|
874
|
+
icon={<RefreshCw size={12} />}
|
|
875
|
+
onClick={reload}
|
|
876
|
+
aria-label="Refresh"
|
|
877
|
+
data-testid="agents-refresh"
|
|
513
878
|
>
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
</
|
|
879
|
+
Refresh
|
|
880
|
+
</UIButton>
|
|
881
|
+
<UIButton
|
|
882
|
+
variant="primary"
|
|
883
|
+
size="sm"
|
|
884
|
+
icon={<Plus size={12} />}
|
|
885
|
+
onClick={onCreate}
|
|
886
|
+
aria-label="New agent"
|
|
887
|
+
data-testid="agents-new"
|
|
888
|
+
>
|
|
889
|
+
New agent
|
|
890
|
+
</UIButton>
|
|
526
891
|
</div>
|
|
527
892
|
</header>
|
|
528
893
|
|
|
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)}
|
|
894
|
+
<div className="agents-redesign__body">
|
|
895
|
+
<Box className="agents-roster">
|
|
896
|
+
{loading ? (
|
|
897
|
+
<LoadingState label="Loading agents…" rows={3} />
|
|
898
|
+
) : sorted.length === 0 ? (
|
|
899
|
+
<EmptyState
|
|
900
|
+
icon={<Bot size={32} />}
|
|
901
|
+
title="No agents found"
|
|
902
|
+
description={
|
|
903
|
+
agents.length === 0
|
|
904
|
+
? 'Run bizar in the terminal to install Bizar.'
|
|
905
|
+
: 'No agents match the current filters — try clearing the search or category.'
|
|
906
|
+
}
|
|
907
|
+
action={
|
|
908
|
+
agents.length === 0
|
|
909
|
+
? undefined
|
|
910
|
+
: {
|
|
911
|
+
label: 'Clear filters',
|
|
912
|
+
onClick: () => {
|
|
913
|
+
setSearch('');
|
|
914
|
+
setCategoryFilter('');
|
|
915
|
+
},
|
|
916
|
+
}
|
|
917
|
+
}
|
|
557
918
|
/>
|
|
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>
|
|
919
|
+
) : (
|
|
920
|
+
<div className="agents-roster__table" data-testid="agents-table">
|
|
921
|
+
<DataTable<Agent>
|
|
922
|
+
columns={columns}
|
|
923
|
+
data={sorted}
|
|
924
|
+
rowKey={(a) => a.name}
|
|
925
|
+
compact
|
|
926
|
+
onRowClick={(a) => setSelectedName(a.name)}
|
|
927
|
+
/>
|
|
647
928
|
</div>
|
|
648
929
|
)}
|
|
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' }}
|
|
930
|
+
</Box>
|
|
931
|
+
|
|
932
|
+
<Panel
|
|
933
|
+
title={
|
|
934
|
+
selected ? (
|
|
935
|
+
<span className="agents-detail__name">
|
|
936
|
+
<span
|
|
937
|
+
className={cx(
|
|
938
|
+
'agents-detail__name-swatch',
|
|
939
|
+
!selected.color && 'agents-detail__name-swatch--empty',
|
|
940
|
+
)}
|
|
941
|
+
style={selected.color ? { background: selected.color } : undefined}
|
|
942
|
+
aria-hidden="true"
|
|
943
|
+
/>
|
|
944
|
+
<span>{selected.name}</span>
|
|
945
|
+
</span>
|
|
946
|
+
) : (
|
|
947
|
+
'Agent detail'
|
|
948
|
+
)
|
|
949
|
+
}
|
|
950
|
+
description={
|
|
951
|
+
selected
|
|
952
|
+
? selected.path
|
|
953
|
+
: 'Select an agent from the roster to see its metadata.'
|
|
954
|
+
}
|
|
955
|
+
padding={4}
|
|
956
|
+
variant="outlined"
|
|
957
|
+
className="agents-detail"
|
|
678
958
|
>
|
|
679
|
-
{
|
|
680
|
-
</
|
|
959
|
+
{renderDetailBody()}
|
|
960
|
+
</Panel>
|
|
681
961
|
</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>
|
|
962
|
+
</div>
|
|
715
963
|
);
|
|
716
|
-
}
|
|
964
|
+
}
|