@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.
Files changed (264) hide show
  1. package/.claude/hooks/sessionend-recall.mjs +3 -3
  2. package/.claude/hooks/worker-suggest.mjs +110 -0
  3. package/.claude/settings.json +9 -0
  4. package/README.md +125 -47
  5. package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-weVnLz3Z.js} +3 -3
  6. package/bizar-dash/dist/assets/EnvVarsSection-weVnLz3Z.js.map +1 -0
  7. package/bizar-dash/dist/assets/MobileChat-BRFjer8p.js +1 -0
  8. package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-BRFjer8p.js.map} +1 -1
  9. package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js +1 -0
  10. package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js.map +1 -0
  11. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
  12. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
  13. package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
  14. package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
  15. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
  16. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
  17. package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
  18. package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
  19. package/bizar-dash/dist/assets/main-BE4Uh9im.css +1 -0
  20. package/bizar-dash/dist/assets/main-DqC58FEZ.js +18 -0
  21. package/bizar-dash/dist/assets/main-DqC58FEZ.js.map +1 -0
  22. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
  23. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js.map +1 -0
  24. package/bizar-dash/dist/assets/mobile-DRZsK3ic.js +1 -0
  25. package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DRZsK3ic.js.map} +1 -1
  26. package/bizar-dash/dist/assets/mobile-layout-BWLtwtfQ.css +1 -0
  27. package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js +2 -0
  28. package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js.map +1 -0
  29. package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
  30. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
  31. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
  32. package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.js +2 -0
  33. package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.js.map +1 -0
  34. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
  35. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
  36. package/bizar-dash/dist/index.html +13 -11
  37. package/bizar-dash/dist/mobile.html +8 -5
  38. package/bizar-dash/src/server/api.mjs +11 -0
  39. package/bizar-dash/src/server/memory-consolidator.mjs +225 -0
  40. package/bizar-dash/src/server/memory-distillation.mjs +296 -0
  41. package/bizar-dash/src/server/routes/distill.mjs +129 -0
  42. package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
  43. package/bizar-dash/src/web/App.tsx +4 -0
  44. package/bizar-dash/src/web/components/Tag.tsx +18 -5
  45. package/bizar-dash/src/web/components/Topbar.tsx +3 -0
  46. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
  47. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
  48. package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
  49. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
  50. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
  51. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
  52. package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
  53. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
  54. package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
  55. package/bizar-dash/src/web/lib/types.ts +25 -0
  56. package/bizar-dash/src/web/mobile/MobileApp.tsx +33 -2
  57. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +2 -2
  58. package/bizar-dash/src/web/mobile/MobileEval.tsx +4 -4
  59. package/bizar-dash/src/web/mobile/MobileMemory.tsx +2 -2
  60. package/bizar-dash/src/web/mobile/MobileSettings.tsx +3 -3
  61. package/bizar-dash/src/web/mobile/MobileTasks.tsx +2 -2
  62. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +3 -3
  63. package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
  64. package/bizar-dash/src/web/styles/main.css +251 -0
  65. package/bizar-dash/src/web/styles/mobile.css +41 -0
  66. package/bizar-dash/src/web/styles/tasks-redesign.css +253 -0
  67. package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
  68. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
  69. package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
  70. package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
  71. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
  72. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
  73. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
  74. package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
  75. package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
  76. package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
  77. package/bizar-dash/src/web/ui/controls/Textarea.tsx +56 -0
  78. package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
  79. package/bizar-dash/src/web/ui/controls/controls.css +476 -0
  80. package/bizar-dash/src/web/ui/controls/index.ts +35 -0
  81. package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
  82. package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
  83. package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
  84. package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
  85. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
  86. package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
  87. package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
  88. package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
  89. package/bizar-dash/src/web/ui/data/data.css +535 -0
  90. package/bizar-dash/src/web/ui/data/index.ts +31 -0
  91. package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
  92. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
  93. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
  94. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
  95. package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
  96. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
  97. package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
  98. package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
  99. package/bizar-dash/src/web/ui/index.ts +56 -0
  100. package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
  101. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
  102. package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
  103. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
  104. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
  105. package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
  106. package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
  107. package/bizar-dash/src/web/ui/layout/ViewHeader.tsx +48 -0
  108. package/bizar-dash/src/web/ui/layout/index.ts +34 -0
  109. package/bizar-dash/src/web/ui/layout/layout.css +464 -0
  110. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
  111. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
  112. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
  113. package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
  114. package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
  115. package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
  116. package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
  117. package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
  118. package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
  119. package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
  120. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
  121. package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
  122. package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
  123. package/bizar-dash/src/web/ui/styles/globals.css +86 -0
  124. package/bizar-dash/src/web/ui/styles/reset.css +125 -0
  125. package/bizar-dash/src/web/ui/styles/tokens.css +208 -0
  126. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
  127. package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
  128. package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
  129. package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
  130. package/bizar-dash/src/web/views/Agents.tsx +516 -268
  131. package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
  132. package/bizar-dash/src/web/views/Overview.tsx +985 -448
  133. package/bizar-dash/src/web/views/Tasks.tsx +271 -273
  134. package/bizar-dash/tests/communication-log.test.tsx +67 -0
  135. package/bizar-dash/tests/dep-graph.test.tsx +40 -0
  136. package/bizar-dash/tests/event-log.test.tsx +61 -0
  137. package/bizar-dash/tests/goal-input.test.tsx +72 -0
  138. package/bizar-dash/tests/goap-planner.test.ts +68 -0
  139. package/bizar-dash/tests/quality-gates.test.tsx +59 -0
  140. package/bizar-dash/tests/setup.ts +31 -1
  141. package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
  142. package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
  143. package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
  144. package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
  145. package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
  146. package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
  147. package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
  148. package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
  149. package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
  150. package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
  151. package/bizar-dash/tests/ui/controls/Textarea.test.tsx +56 -0
  152. package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
  153. package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
  154. package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
  155. package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
  156. package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
  157. package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
  158. package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
  159. package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
  160. package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
  161. package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
  162. package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
  163. package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
  164. package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
  165. package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
  166. package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
  167. package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
  168. package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
  169. package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
  170. package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
  171. package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
  172. package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
  173. package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
  174. package/bizar-dash/tests/ui/layout/ViewHeader.test.tsx +47 -0
  175. package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
  176. package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
  177. package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
  178. package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
  179. package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
  180. package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
  181. package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
  182. package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
  183. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
  184. package/bizar-dash/tests/views/Agents.test.tsx +359 -0
  185. package/bizar-dash/tests/views/Overview.test.tsx +413 -0
  186. package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
  187. package/bizar-dash/vitest.config.ts +3 -1
  188. package/cli/__tests__/cost-gate.test.mjs +301 -0
  189. package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
  190. package/cli/bin.mjs +38 -0
  191. package/cli/commands/claim.mjs +282 -0
  192. package/cli/commands/cost.mjs +279 -0
  193. package/cli/cost-gate.mjs +501 -0
  194. package/cli/feature-list-bridge.mjs +400 -0
  195. package/cli/memory-constants.mjs +17 -0
  196. package/cli/provision-claude.mjs +94 -0
  197. package/cli/worker-dispatcher.mjs +259 -0
  198. package/cli/worker-dispatcher.test.mjs +204 -0
  199. package/config/trigger-patterns.json +186 -0
  200. package/package.json +2 -2
  201. package/packages/sdk/package.json +1 -1
  202. package/packages/sdk/src/agent-registry.ts +304 -0
  203. package/packages/sdk/src/consensus/byzantine.ts +587 -0
  204. package/packages/sdk/src/consensus/index.ts +147 -0
  205. package/packages/sdk/src/consensus/queen.ts +173 -0
  206. package/packages/sdk/src/consensus/types.ts +199 -0
  207. package/packages/sdk/src/federation/audit.ts +156 -0
  208. package/packages/sdk/src/federation/budget.ts +269 -0
  209. package/packages/sdk/src/federation/envelope.ts +144 -0
  210. package/packages/sdk/src/federation/hmac.ts +160 -0
  211. package/packages/sdk/src/federation/index.ts +443 -0
  212. package/packages/sdk/src/federation/pii.ts +241 -0
  213. package/packages/sdk/src/federation/policy.ts +112 -0
  214. package/packages/sdk/src/federation/trust.ts +146 -0
  215. package/packages/sdk/src/index.ts +67 -0
  216. package/packages/sdk/src/mcp/server.ts +351 -1
  217. package/packages/sdk/src/router/codemod-intent.ts +125 -0
  218. package/packages/sdk/src/router/index.ts +247 -0
  219. package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
  220. package/packages/sdk/src/router/memory-distillation.ts +249 -0
  221. package/packages/sdk/src/router/model-router.ts +235 -0
  222. package/packages/sdk/src/router/q-learning-router.ts +304 -0
  223. package/packages/sdk/src/swarm-topology.ts +279 -0
  224. package/packages/sdk/tests/agent-registry.test.ts +285 -0
  225. package/packages/sdk/tests/consensus.test.ts +376 -0
  226. package/packages/sdk/tests/federation/audit.test.ts +164 -0
  227. package/packages/sdk/tests/federation/budget.test.ts +189 -0
  228. package/packages/sdk/tests/federation/envelope.test.ts +120 -0
  229. package/packages/sdk/tests/federation/hmac.test.ts +194 -0
  230. package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
  231. package/packages/sdk/tests/federation/pii.test.ts +106 -0
  232. package/packages/sdk/tests/federation/policy.test.ts +140 -0
  233. package/packages/sdk/tests/federation/trust.test.ts +124 -0
  234. package/packages/sdk/tests/mcp-tools.test.ts +204 -0
  235. package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
  236. package/packages/sdk/tests/model-router.test.mjs +108 -0
  237. package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
  238. package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
  239. package/packages/sdk/tests/router.test.mjs +91 -0
  240. package/packages/sdk/tests/sdk.test.mjs +26 -2
  241. package/packages/sdk/tests/swarm-topology.test.ts +258 -0
  242. package/scripts/bh-full-e2e.mjs +11 -15
  243. package/bizar-dash/dist/assets/EnvVarsSection-B58aiJiE.js.map +0 -1
  244. package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
  245. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
  246. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js.map +0 -1
  247. package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
  248. package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
  249. package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
  250. package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
  251. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
  252. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js.map +0 -1
  253. package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
  254. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
  255. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
  256. package/bizar-dash/dist/assets/mobile-layout-CJnZNLy3.css +0 -1
  257. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
  258. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
  259. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
  260. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
  261. package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
  262. package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
  263. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  264. package/packages/sdk/package-lock.json +0 -32
@@ -0,0 +1,359 @@
1
+ // tests/views/Agents.test.tsx — Wave 3 Agents view (split-pane roster + detail panel).
2
+ //
3
+ // Covers the redesign contract: DataTable renders rows from snapshot, clicking
4
+ // a row selects an agent and updates the detail panel, category + search
5
+ // filters narrow the table, and the Edit button fires the handler. The
6
+ // create/delete/invoke modals use the legacy Modal and are exercised here
7
+ // only at the click-button level — their form contents are unchanged from
8
+ // the previous view and live in the legacy test suite.
9
+
10
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
11
+ import { render, screen, waitFor, within } from '@testing-library/react';
12
+ import userEvent from '@testing-library/user-event';
13
+ import React from 'react';
14
+ import { ToastProvider } from '../../src/web/components/Toast';
15
+ import { ModalProvider } from '../../src/web/components/Modal';
16
+ import { Agents } from '../../src/web/views/Agents';
17
+ import type { Agent, Settings, Snapshot } from '../../src/web/lib/types';
18
+
19
+ // ─── API mock ────────────────────────────────────────────────────────────
20
+
21
+ const apiGet = vi.fn();
22
+ const apiPost = vi.fn();
23
+ const apiPut = vi.fn();
24
+ const apiDel = vi.fn();
25
+
26
+ vi.mock('../../src/web/lib/api', () => ({
27
+ api: {
28
+ get: (...args: unknown[]) => apiGet(...args),
29
+ post: (...args: unknown[]) => apiPost(...args),
30
+ put: (...args: unknown[]) => apiPut(...args),
31
+ del: (...args: unknown[]) => apiDel(...args),
32
+ },
33
+ }));
34
+
35
+ // The Agents view does not own a WS subscription (parent App.tsx does), but
36
+ // stubbing Ws makes the file load-safe if a future change introduces one.
37
+ vi.mock('../../src/web/lib/ws', () => ({
38
+ Ws: class {
39
+ on() { return () => undefined; }
40
+ onStatus() { return () => undefined; }
41
+ close() {}
42
+ },
43
+ }));
44
+
45
+ // ─── Test fixtures ───────────────────────────────────────────────────────
46
+
47
+ const odin: Agent = {
48
+ name: 'odin',
49
+ description: 'Top-tier orchestrator',
50
+ model: 'anthropic/claude-3-5-sonnet',
51
+ mode: 'primary',
52
+ file: 'odin.md',
53
+ path: '/agents/odin.md',
54
+ mtime: Date.now() - 3600_000,
55
+ tools: ['bash', 'read', 'edit'],
56
+ tags: ['orchestrator', 'reasoning'],
57
+ category: 'reasoning',
58
+ color: '#8b5cf6',
59
+ status: 'working',
60
+ isStuck: false,
61
+ };
62
+
63
+ const frigg: Agent = {
64
+ name: 'frigg',
65
+ description: 'Quality + memory curator',
66
+ model: 'anthropic/claude-3-5-haiku',
67
+ mode: 'subagent',
68
+ file: 'frigg.md',
69
+ path: '/agents/frigg.md',
70
+ mtime: Date.now() - 7200_000,
71
+ tools: ['read', 'grep'],
72
+ tags: ['curator', 'analysis'],
73
+ category: 'analysis',
74
+ status: 'idle',
75
+ };
76
+
77
+ const tyr: Agent = {
78
+ name: 'tyr',
79
+ description: 'Implementation engine',
80
+ model: 'openai/gpt-4o',
81
+ mode: 'subagent',
82
+ file: 'tyr.md',
83
+ path: '/agents/tyr.md',
84
+ mtime: Date.now() - 1800_000,
85
+ tools: ['bash', 'edit', 'write'],
86
+ tags: ['implementation', 'code'],
87
+ category: 'code',
88
+ color: '#2563eb',
89
+ status: 'working',
90
+ isStuck: true,
91
+ };
92
+
93
+ function makeSnapshot(agents: Agent[] = [odin, frigg, tyr]): Snapshot {
94
+ return {
95
+ agents,
96
+ } as unknown as Snapshot;
97
+ }
98
+
99
+ const dummySettings = {} as Settings;
100
+
101
+ const Harness: React.FC<{ children: React.ReactNode }> = ({ children }) => (
102
+ <ToastProvider>
103
+ <ModalProvider>{children}</ModalProvider>
104
+ </ToastProvider>
105
+ );
106
+
107
+ // ─── Tests ───────────────────────────────────────────────────────────────
108
+
109
+ describe('Agents view (Wave 3 redesign)', () => {
110
+ beforeEach(() => {
111
+ apiGet.mockReset();
112
+ apiPost.mockReset();
113
+ apiPut.mockReset();
114
+ apiDel.mockReset();
115
+ // Default mock for any GET that may sneak through (modal loads).
116
+ apiGet.mockImplementation(async () => ({}));
117
+ apiPost.mockResolvedValue({});
118
+ apiPut.mockResolvedValue({});
119
+ apiDel.mockResolvedValue({});
120
+ // confirm() always true for delete tests
121
+ vi.spyOn(window, 'confirm').mockReturnValue(true);
122
+ });
123
+
124
+ it('renders the DataTable rows from the snapshot', async () => {
125
+ render(
126
+ <Harness>
127
+ <Agents
128
+ snapshot={makeSnapshot()}
129
+ settings={dummySettings}
130
+ activeTab="agents"
131
+ setActiveTab={() => undefined}
132
+ refreshSnapshot={async () => undefined}
133
+ />
134
+ </Harness>,
135
+ );
136
+
137
+ expect(await screen.findByTestId('agents-table')).toBeInTheDocument();
138
+ expect(screen.getByTestId('agents-count').textContent).toContain('(3)');
139
+
140
+ const table = screen.getByTestId('agents-table');
141
+ expect(within(table).getByText('odin')).toBeInTheDocument();
142
+ expect(within(table).getByText('frigg')).toBeInTheDocument();
143
+ expect(within(table).getByText('tyr')).toBeInTheDocument();
144
+ });
145
+
146
+ it('shows the empty state when no agents match', async () => {
147
+ render(
148
+ <Harness>
149
+ <Agents
150
+ snapshot={makeSnapshot([])}
151
+ settings={dummySettings}
152
+ activeTab="agents"
153
+ setActiveTab={() => undefined}
154
+ refreshSnapshot={async () => undefined}
155
+ />
156
+ </Harness>,
157
+ );
158
+
159
+ expect(await screen.findByText(/No agents found/i)).toBeInTheDocument();
160
+ expect(screen.getByTestId('agents-count').textContent).toContain('(0)');
161
+ });
162
+
163
+ it('clicking a row populates the detail panel', async () => {
164
+ const user = userEvent.setup();
165
+ render(
166
+ <Harness>
167
+ <Agents
168
+ snapshot={makeSnapshot()}
169
+ settings={dummySettings}
170
+ activeTab="agents"
171
+ setActiveTab={() => undefined}
172
+ refreshSnapshot={async () => undefined}
173
+ />
174
+ </Harness>,
175
+ );
176
+
177
+ const table = await screen.findByTestId('agents-table');
178
+ await user.click(within(table).getByText('frigg'));
179
+
180
+ // Detail panel shows the agent's metadata — use unique marker
181
+ await waitFor(() => {
182
+ expect(screen.getByText('Quality + memory curator')).toBeInTheDocument();
183
+ });
184
+ // Frigg-specific tag (curator) appears in the detail panel
185
+ expect(screen.getAllByText('curator').length).toBeGreaterThan(0);
186
+ // Edit button is rendered with frigg-specific testid
187
+ expect(screen.getByTestId('agents-detail-edit-frigg')).toBeInTheDocument();
188
+ // Frigg's path shows in the panel description
189
+ expect(screen.getByText('/agents/frigg.md')).toBeInTheDocument();
190
+ });
191
+
192
+ it('category filter narrows the visible agents', async () => {
193
+ const user = userEvent.setup();
194
+ render(
195
+ <Harness>
196
+ <Agents
197
+ snapshot={makeSnapshot()}
198
+ settings={dummySettings}
199
+ activeTab="agents"
200
+ setActiveTab={() => undefined}
201
+ refreshSnapshot={async () => undefined}
202
+ />
203
+ </Harness>,
204
+ );
205
+
206
+ const table = await screen.findByTestId('agents-table');
207
+ expect(within(table).getByText('tyr')).toBeInTheDocument();
208
+
209
+ const filter = screen.getByTestId('agents-category-filter') as HTMLSelectElement;
210
+ await user.selectOptions(filter, 'code');
211
+
212
+ expect(within(table).queryByText('odin')).not.toBeInTheDocument();
213
+ expect(within(table).queryByText('frigg')).not.toBeInTheDocument();
214
+ expect(within(table).getByText('tyr')).toBeInTheDocument();
215
+ expect(screen.getByTestId('agents-count').textContent).toContain('(1)');
216
+ });
217
+
218
+ it('search filters by name / description / tag', async () => {
219
+ const user = userEvent.setup();
220
+ render(
221
+ <Harness>
222
+ <Agents
223
+ snapshot={makeSnapshot()}
224
+ settings={dummySettings}
225
+ activeTab="agents"
226
+ setActiveTab={() => undefined}
227
+ refreshSnapshot={async () => undefined}
228
+ />
229
+ </Harness>,
230
+ );
231
+
232
+ const table = await screen.findByTestId('agents-table');
233
+ const search = screen.getByTestId('agents-search');
234
+
235
+ // Tag match
236
+ await user.type(search, 'curator');
237
+ expect(within(table).queryByText('odin')).not.toBeInTheDocument();
238
+ expect(within(table).queryByText('frigg')).toBeInTheDocument();
239
+ expect(within(table).queryByText('tyr')).not.toBeInTheDocument();
240
+
241
+ // Description match
242
+ await user.clear(search);
243
+ await user.type(search, 'Implementation');
244
+ expect(within(table).getByText('tyr')).toBeInTheDocument();
245
+ expect(within(table).queryByText('frigg')).not.toBeInTheDocument();
246
+
247
+ // Name match (case-insensitive)
248
+ await user.clear(search);
249
+ await user.type(search, 'ODIN');
250
+ expect(within(table).getByText('odin')).toBeInTheDocument();
251
+ expect(within(table).queryByText('tyr')).not.toBeInTheDocument();
252
+ });
253
+
254
+ it('Edit button on the detail panel loads the agent and opens the edit modal', async () => {
255
+ const user = userEvent.setup();
256
+ apiGet.mockImplementation(async (path: string) => {
257
+ if (path === '/agents/odin') {
258
+ return { ...odin, prompt: 'You are Odin.' };
259
+ }
260
+ return {};
261
+ });
262
+
263
+ render(
264
+ <Harness>
265
+ <Agents
266
+ snapshot={makeSnapshot()}
267
+ settings={dummySettings}
268
+ activeTab="agents"
269
+ setActiveTab={() => undefined}
270
+ refreshSnapshot={async () => undefined}
271
+ />
272
+ </Harness>,
273
+ );
274
+
275
+ const table = await screen.findByTestId('agents-table');
276
+ await user.click(within(table).getByText('odin'));
277
+
278
+ const editBtn = await screen.findByTestId('agents-detail-edit-odin');
279
+ await user.click(editBtn);
280
+
281
+ await waitFor(() => {
282
+ expect(apiGet).toHaveBeenCalledWith('/agents/odin');
283
+ });
284
+
285
+ // Edit modal renders with the existing prompt pre-filled.
286
+ await waitFor(() => {
287
+ const promptArea = screen.getByLabelText(/system prompt/i) as HTMLTextAreaElement;
288
+ expect(promptArea.value).toBe('You are Odin.');
289
+ });
290
+ expect(screen.getByText('Edit odin')).toBeInTheDocument();
291
+ });
292
+
293
+ it('pulse fires on StatusDot when agent is stuck', async () => {
294
+ render(
295
+ <Harness>
296
+ <Agents
297
+ snapshot={makeSnapshot()}
298
+ settings={dummySettings}
299
+ activeTab="agents"
300
+ setActiveTab={() => undefined}
301
+ refreshSnapshot={async () => undefined}
302
+ />
303
+ </Harness>,
304
+ );
305
+
306
+ const table = await screen.findByTestId('agents-table');
307
+ // Tyr is stuck — find the row and confirm the pulsing status dot is present.
308
+ const tyrRow = within(table).getByText('tyr').closest('tr');
309
+ expect(tyrRow).not.toBeNull();
310
+ const pulseDot = tyrRow!.querySelector('.bizar-status-dot--pulse');
311
+ expect(pulseDot).not.toBeNull();
312
+ });
313
+
314
+ it('renders the "No agent selected" placeholder when nothing is picked', async () => {
315
+ render(
316
+ <Harness>
317
+ <Agents
318
+ snapshot={makeSnapshot()}
319
+ settings={dummySettings}
320
+ activeTab="agents"
321
+ setActiveTab={() => undefined}
322
+ refreshSnapshot={async () => undefined}
323
+ />
324
+ </Harness>,
325
+ );
326
+
327
+ expect(await screen.findByText(/No agent selected/i)).toBeInTheDocument();
328
+ });
329
+
330
+ it('refresh button reloads /agents and updates count', async () => {
331
+ const user = userEvent.setup();
332
+ apiGet.mockImplementation(async (path: string) => {
333
+ if (path === '/agents') return { agents: [odin] };
334
+ return {};
335
+ });
336
+
337
+ render(
338
+ <Harness>
339
+ <Agents
340
+ snapshot={makeSnapshot()}
341
+ settings={dummySettings}
342
+ activeTab="agents"
343
+ setActiveTab={() => undefined}
344
+ refreshSnapshot={async () => undefined}
345
+ />
346
+ </Harness>,
347
+ );
348
+
349
+ expect(screen.getByTestId('agents-count').textContent).toContain('(3)');
350
+ await user.click(screen.getByTestId('agents-refresh'));
351
+
352
+ await waitFor(() => {
353
+ expect(apiGet).toHaveBeenCalledWith('/agents');
354
+ });
355
+ await waitFor(() => {
356
+ expect(screen.getByTestId('agents-count').textContent).toContain('(1)');
357
+ });
358
+ });
359
+ });