@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.
Files changed (245) 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/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-CW6fWKds.js} +3 -3
  5. package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js.map → EnvVarsSection-CW6fWKds.js.map} +1 -1
  6. package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +1 -0
  7. package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-D7zRhU9K.js.map} +1 -1
  8. package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +1 -0
  9. package/bizar-dash/dist/assets/{MobileSettings-CEQNJNLJ.js.map → MobileSettings-K-sNsn_r.js.map} +1 -1
  10. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
  11. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
  12. package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
  13. package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
  14. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
  15. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
  16. package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
  17. package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
  18. package/bizar-dash/dist/assets/main-C-Md3DaY.css +1 -0
  19. package/bizar-dash/dist/assets/main-CO8fIxQK.js +18 -0
  20. package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +1 -0
  21. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
  22. package/bizar-dash/dist/assets/{markdown-tOLaD6nm.js.map → markdown-w2bDDkAH.js.map} +1 -1
  23. package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +1 -0
  24. package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DXYCA0hC.js.map} +1 -1
  25. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +2 -0
  26. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +1 -0
  27. package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
  28. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
  29. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
  30. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +2 -0
  31. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +1 -0
  32. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
  33. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
  34. package/bizar-dash/dist/index.html +12 -10
  35. package/bizar-dash/dist/mobile.html +7 -4
  36. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  37. package/bizar-dash/src/server/api.mjs +11 -0
  38. package/bizar-dash/src/server/memory-consolidator.mjs +218 -0
  39. package/bizar-dash/src/server/memory-distillation.mjs +296 -0
  40. package/bizar-dash/src/server/routes/distill.mjs +129 -0
  41. package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
  42. package/bizar-dash/src/web/App.tsx +4 -0
  43. package/bizar-dash/src/web/components/Tag.tsx +18 -5
  44. package/bizar-dash/src/web/components/Topbar.tsx +3 -0
  45. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
  46. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
  47. package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
  48. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
  49. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
  50. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
  51. package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
  52. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
  53. package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
  54. package/bizar-dash/src/web/lib/types.ts +25 -0
  55. package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
  56. package/bizar-dash/src/web/styles/main.css +251 -0
  57. package/bizar-dash/src/web/styles/tasks-redesign.css +231 -0
  58. package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
  59. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
  60. package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
  61. package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
  62. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
  63. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
  64. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
  65. package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
  66. package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
  67. package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
  68. package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
  69. package/bizar-dash/src/web/ui/controls/controls.css +454 -0
  70. package/bizar-dash/src/web/ui/controls/index.ts +33 -0
  71. package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
  72. package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
  73. package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
  74. package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
  75. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
  76. package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
  77. package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
  78. package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
  79. package/bizar-dash/src/web/ui/data/data.css +535 -0
  80. package/bizar-dash/src/web/ui/data/index.ts +31 -0
  81. package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
  82. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
  83. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
  84. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
  85. package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
  86. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
  87. package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
  88. package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
  89. package/bizar-dash/src/web/ui/index.ts +56 -0
  90. package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
  91. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
  92. package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
  93. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
  94. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
  95. package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
  96. package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
  97. package/bizar-dash/src/web/ui/layout/index.ts +33 -0
  98. package/bizar-dash/src/web/ui/layout/layout.css +413 -0
  99. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
  100. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
  101. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
  102. package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
  103. package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
  104. package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
  105. package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
  106. package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
  107. package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
  108. package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
  109. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
  110. package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
  111. package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
  112. package/bizar-dash/src/web/ui/styles/globals.css +86 -0
  113. package/bizar-dash/src/web/ui/styles/reset.css +125 -0
  114. package/bizar-dash/src/web/ui/styles/tokens.css +195 -0
  115. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
  116. package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
  117. package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
  118. package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
  119. package/bizar-dash/src/web/views/Agents.tsx +506 -266
  120. package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
  121. package/bizar-dash/src/web/views/Overview.tsx +996 -448
  122. package/bizar-dash/src/web/views/Tasks.tsx +277 -273
  123. package/bizar-dash/tests/communication-log.test.tsx +67 -0
  124. package/bizar-dash/tests/dep-graph.test.tsx +40 -0
  125. package/bizar-dash/tests/event-log.test.tsx +61 -0
  126. package/bizar-dash/tests/goal-input.test.tsx +72 -0
  127. package/bizar-dash/tests/goap-planner.test.ts +68 -0
  128. package/bizar-dash/tests/quality-gates.test.tsx +59 -0
  129. package/bizar-dash/tests/setup.ts +31 -1
  130. package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
  131. package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
  132. package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
  133. package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
  134. package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
  135. package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
  136. package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
  137. package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
  138. package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
  139. package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
  140. package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
  141. package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
  142. package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
  143. package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
  144. package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
  145. package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
  146. package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
  147. package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
  148. package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
  149. package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
  150. package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
  151. package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
  152. package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
  153. package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
  154. package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
  155. package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
  156. package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
  157. package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
  158. package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
  159. package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
  160. package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
  161. package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
  162. package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
  163. package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
  164. package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
  165. package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
  166. package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
  167. package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
  168. package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
  169. package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
  170. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
  171. package/bizar-dash/tests/views/Agents.test.tsx +359 -0
  172. package/bizar-dash/tests/views/Overview.test.tsx +413 -0
  173. package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
  174. package/bizar-dash/vitest.config.ts +3 -1
  175. package/cli/__tests__/cost-gate.test.mjs +301 -0
  176. package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
  177. package/cli/bin.mjs +38 -0
  178. package/cli/commands/claim.mjs +282 -0
  179. package/cli/commands/cost.mjs +279 -0
  180. package/cli/cost-gate.mjs +501 -0
  181. package/cli/feature-list-bridge.mjs +400 -0
  182. package/cli/memory-constants.mjs +17 -0
  183. package/cli/worker-dispatcher.mjs +259 -0
  184. package/cli/worker-dispatcher.test.mjs +204 -0
  185. package/config/trigger-patterns.json +186 -0
  186. package/package.json +2 -2
  187. package/packages/sdk/package.json +1 -1
  188. package/packages/sdk/src/agent-registry.ts +304 -0
  189. package/packages/sdk/src/consensus/byzantine.ts +587 -0
  190. package/packages/sdk/src/consensus/index.ts +147 -0
  191. package/packages/sdk/src/consensus/queen.ts +173 -0
  192. package/packages/sdk/src/consensus/types.ts +199 -0
  193. package/packages/sdk/src/federation/audit.ts +156 -0
  194. package/packages/sdk/src/federation/budget.ts +269 -0
  195. package/packages/sdk/src/federation/envelope.ts +144 -0
  196. package/packages/sdk/src/federation/hmac.ts +160 -0
  197. package/packages/sdk/src/federation/index.ts +443 -0
  198. package/packages/sdk/src/federation/pii.ts +241 -0
  199. package/packages/sdk/src/federation/policy.ts +112 -0
  200. package/packages/sdk/src/federation/trust.ts +146 -0
  201. package/packages/sdk/src/index.ts +67 -0
  202. package/packages/sdk/src/mcp/server.ts +351 -1
  203. package/packages/sdk/src/router/codemod-intent.ts +125 -0
  204. package/packages/sdk/src/router/index.ts +247 -0
  205. package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
  206. package/packages/sdk/src/router/memory-distillation.ts +249 -0
  207. package/packages/sdk/src/router/model-router.ts +235 -0
  208. package/packages/sdk/src/router/q-learning-router.ts +304 -0
  209. package/packages/sdk/src/swarm-topology.ts +279 -0
  210. package/packages/sdk/tests/agent-registry.test.ts +285 -0
  211. package/packages/sdk/tests/consensus.test.ts +376 -0
  212. package/packages/sdk/tests/federation/audit.test.ts +164 -0
  213. package/packages/sdk/tests/federation/budget.test.ts +189 -0
  214. package/packages/sdk/tests/federation/envelope.test.ts +120 -0
  215. package/packages/sdk/tests/federation/hmac.test.ts +194 -0
  216. package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
  217. package/packages/sdk/tests/federation/pii.test.ts +106 -0
  218. package/packages/sdk/tests/federation/policy.test.ts +140 -0
  219. package/packages/sdk/tests/federation/trust.test.ts +124 -0
  220. package/packages/sdk/tests/mcp-tools.test.ts +204 -0
  221. package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
  222. package/packages/sdk/tests/model-router.test.mjs +108 -0
  223. package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
  224. package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
  225. package/packages/sdk/tests/router.test.mjs +91 -0
  226. package/packages/sdk/tests/sdk.test.mjs +26 -2
  227. package/packages/sdk/tests/swarm-topology.test.ts +258 -0
  228. package/scripts/bh-full-e2e.mjs +11 -15
  229. package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
  230. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
  231. package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
  232. package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
  233. package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
  234. package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
  235. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
  236. package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
  237. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
  238. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
  239. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
  240. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
  241. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
  242. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
  243. package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
  244. package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
  245. package/packages/sdk/package-lock.json +0 -32
@@ -0,0 +1,160 @@
1
+ // src/web/components/agents/RoutingDecisions.tsx —
2
+ // F-035 (MetaHarness) — 3-tier model routing transparency panel.
3
+ //
4
+ // Mirrors ruflo's [CODEMOD_AVAILABLE] / [TASK_MODEL_RECOMMENDATION]
5
+ // convention (see goal_ui/src/components/agents/ExecutionMonitor.tsx).
6
+ // Renders the most recent routing decisions emitted on the WS
7
+ // `routing:decision` channel with a tier badge (CODEMOD/TIER1/TIER2/TIER3)
8
+ // plus model + cost + latency columns.
9
+ //
10
+ // Tier color scheme (per spec):
11
+ // CODEMOD → green ($0 deterministic codemod intent)
12
+ // TIER1 → blue (model)
13
+ // TIER2 → yellow (model)
14
+ // TIER3 → red (frontier model)
15
+ //
16
+ // Pure functional React. No class components. Vanilla CSS via the
17
+ // existing main.css (.routing-decisions, .routing-tier-* classes).
18
+
19
+ import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
20
+ import type { RoutingDecision, RoutingTier, WsMessage } from '../../lib/types';
21
+
22
+ export type RoutingDecisionsProps = {
23
+ /** Optional WebSocket subscription target. */
24
+ subscribe?: (handler: (msg: WsMessage) => void) => () => void;
25
+ /** Initial seed for tests / preview. */
26
+ initial?: RoutingDecision[];
27
+ /** Cap on visible rows (default 50). Older rows are pruned first. */
28
+ maxRows?: number;
29
+ /** Tier filter — undefined = all tiers. */
30
+ filterTier?: RoutingTier | 'ALL';
31
+ /** Compact (single-row) layout toggle. */
32
+ compact?: boolean;
33
+ /** Test-only callback when a decision is appended. */
34
+ onAppend?: (d: RoutingDecision) => void;
35
+ };
36
+
37
+ const TIER_ORDER: RoutingTier[] = ['CODEMOD', 'TIER1', 'TIER2', 'TIER3'];
38
+
39
+ function tierClass(tier: RoutingTier): string {
40
+ switch (tier) {
41
+ case 'CODEMOD': return 'routing-tier routing-tier-codemod';
42
+ case 'TIER1': return 'routing-tier routing-tier-1';
43
+ case 'TIER2': return 'routing-tier routing-tier-2';
44
+ case 'TIER3': return 'routing-tier routing-tier-3';
45
+ default: return 'routing-tier';
46
+ }
47
+ }
48
+
49
+ function formatCost(c: number | undefined): string {
50
+ if (c == null || !Number.isFinite(c)) return '—';
51
+ if (c === 0) return '$0';
52
+ if (c < 0.0001) return '<$0.0001';
53
+ if (c < 1) return `$${c.toFixed(4)}`;
54
+ return `$${c.toFixed(2)}`;
55
+ }
56
+
57
+ function formatLatency(ms: number | undefined): string {
58
+ if (ms == null || !Number.isFinite(ms)) return '—';
59
+ if (ms < 1000) return `${Math.round(ms)}ms`;
60
+ return `${(ms / 1000).toFixed(2)}s`;
61
+ }
62
+
63
+ function formatTime(ts: number): string {
64
+ try {
65
+ const d = new Date(ts);
66
+ if (Number.isNaN(d.getTime())) return String(ts);
67
+ return d.toLocaleTimeString();
68
+ } catch {
69
+ return String(ts);
70
+ }
71
+ }
72
+
73
+ export function RoutingDecisions(props: RoutingDecisionsProps) {
74
+ const {
75
+ subscribe,
76
+ initial,
77
+ maxRows = 50,
78
+ filterTier,
79
+ compact = false,
80
+ onAppend,
81
+ } = props;
82
+
83
+ const [decisions, setDecisions] = useState<RoutingDecision[]>(() =>
84
+ Array.isArray(initial) ? initial.slice(0, maxRows) : [],
85
+ );
86
+
87
+ const onAppendRef = useRef(onAppend);
88
+ useEffect(() => { onAppendRef.current = onAppend; }, [onAppend]);
89
+
90
+ const handle = useCallback((msg: WsMessage) => {
91
+ if (msg.type !== 'routing:decision') return;
92
+ const d = msg.decision;
93
+ if (!d || !d.id || !d.tier) return;
94
+ setDecisions((cur) => {
95
+ const next = [d, ...cur.filter((x) => x.id !== d.id)];
96
+ if (next.length > maxRows) next.length = maxRows;
97
+ return next;
98
+ });
99
+ onAppendRef.current?.(d);
100
+ }, [maxRows]);
101
+
102
+ // Wire up the WS subscription if one is provided.
103
+ useEffect(() => {
104
+ if (typeof subscribe !== 'function') return undefined;
105
+ return subscribe(handle);
106
+ }, [subscribe, handle]);
107
+
108
+ const visible = useMemo(() => {
109
+ if (!filterTier || filterTier === 'ALL') return decisions;
110
+ return decisions.filter((d) => d.tier === filterTier);
111
+ }, [decisions, filterTier]);
112
+
113
+ // ── Empty state ──────────────────────────────────────────────────
114
+ if (decisions.length === 0) {
115
+ return (
116
+ <div className="routing-decisions routing-decisions-empty" data-testid="routing-decisions-empty">
117
+ <div className="routing-decisions-empty-title">No routing decisions yet</div>
118
+ <div className="routing-decisions-empty-hint">
119
+ When the SDK emits a <code>routing:decision</code> event, the
120
+ most recent <strong>{maxRows}</strong> will appear here.
121
+ </div>
122
+ </div>
123
+ );
124
+ }
125
+
126
+ return (
127
+ <div className={compact ? 'routing-decisions routing-decisions-compact' : 'routing-decisions'} data-testid="routing-decisions">
128
+ <table className="routing-decisions-table" role="table">
129
+ <thead>
130
+ <tr>
131
+ <th scope="col">Tier</th>
132
+ <th scope="col">Model</th>
133
+ <th scope="col">Task</th>
134
+ <th scope="col">Cost</th>
135
+ <th scope="col">Latency</th>
136
+ <th scope="col">When</th>
137
+ </tr>
138
+ </thead>
139
+ <tbody>
140
+ {visible.map((d) => (
141
+ <tr key={d.id} data-testid={`routing-row-${d.id}`} className="routing-row">
142
+ <td>
143
+ <span className={tierClass(d.tier)} title={TIER_ORDER.includes(d.tier) ? `Tier ${d.tier}` : d.tier}>
144
+ {d.tier}
145
+ </span>
146
+ </td>
147
+ <td className="routing-model" title={d.model}>{d.model}</td>
148
+ <td className="routing-task">{d.task || '—'}</td>
149
+ <td className="routing-cost">{formatCost(d.costUsd)}</td>
150
+ <td className="routing-latency">{formatLatency(d.latencyMs)}</td>
151
+ <td className="routing-time" title={String(d.ts)}>{formatTime(d.ts)}</td>
152
+ </tr>
153
+ ))}
154
+ </tbody>
155
+ </table>
156
+ </div>
157
+ );
158
+ }
159
+
160
+ export default RoutingDecisions;
@@ -0,0 +1,86 @@
1
+ // src/web/components/goals/GoalInput.tsx — plain-English goal input.
2
+ //
3
+ // v6.4.0 — F-036 (Goal Planner UI). Free-form textarea + submit
4
+ // button. The submit fires a POST to /api/goal-planner/plan and
5
+ // returns the resulting Plan to the parent via onSubmit(plan).
6
+ import { useState } from 'react';
7
+ import { Send, Sparkles } from 'lucide-react';
8
+ import { Button } from '../Button';
9
+ import { api } from '../../lib/api';
10
+ import { useToast } from '../Toast';
11
+ import type { Plan } from '../../lib/goapPlanner';
12
+
13
+ interface Props {
14
+ onPlan?: (plan: Plan) => void;
15
+ /** Optional: when true, the input renders in compact (toolbar) mode. */
16
+ compact?: boolean;
17
+ }
18
+
19
+ const MAX_CHARS = 4000;
20
+
21
+ export function GoalInput({ onPlan, compact = false }: Props) {
22
+ const toast = useToast();
23
+ const [goal, setGoal] = useState('');
24
+ const [submitting, setSubmitting] = useState(false);
25
+
26
+ const submit = async () => {
27
+ const trimmed = goal.trim();
28
+ if (!trimmed) {
29
+ toast.error('Enter a goal first.');
30
+ return;
31
+ }
32
+ if (trimmed.length > MAX_CHARS) {
33
+ toast.error(`Goal must be ${MAX_CHARS} chars or fewer.`);
34
+ return;
35
+ }
36
+ setSubmitting(true);
37
+ try {
38
+ const plan = await api.post<Plan>('/goal-planner/plan', { goal: trimmed });
39
+ if (onPlan) onPlan(plan);
40
+ toast.success(`Plan generated: ${plan.steps.length} steps.`);
41
+ } catch (err) {
42
+ const msg = (err as Error)?.message || 'Planner failed';
43
+ toast.error(`Planner failed: ${msg}`);
44
+ } finally {
45
+ setSubmitting(false);
46
+ }
47
+ };
48
+
49
+ return (
50
+ <div className={`goal-input${compact ? ' goal-input-compact' : ''}`}>
51
+ <label className="goal-input-label" htmlFor="goal-input-textarea">
52
+ <Sparkles size={14} aria-hidden />
53
+ Plain-English goal
54
+ </label>
55
+ <textarea
56
+ id="goal-input-textarea"
57
+ className="goal-input-textarea"
58
+ value={goal}
59
+ onChange={(e) => setGoal(e.target.value)}
60
+ placeholder="e.g. Research the latest advancements in quantum computing, then design and build a small demo, test it, and document the result."
61
+ disabled={submitting}
62
+ maxLength={MAX_CHARS}
63
+ rows={compact ? 3 : 5}
64
+ data-testid="goal-input-textarea"
65
+ />
66
+ <div className="goal-input-footer">
67
+ <span className="goal-input-counter" data-testid="goal-input-counter">
68
+ {goal.length} / {MAX_CHARS}
69
+ </span>
70
+ <Button
71
+ variant="primary"
72
+ size="md"
73
+ onClick={submit}
74
+ loading={submitting}
75
+ disabled={submitting || !goal.trim()}
76
+ data-testid="goal-input-submit"
77
+ >
78
+ <Send size={14} aria-hidden />
79
+ {submitting ? 'Planning…' : 'Generate plan'}
80
+ </Button>
81
+ </div>
82
+ </div>
83
+ );
84
+ }
85
+
86
+ export default GoalInput;
@@ -0,0 +1,111 @@
1
+ // src/web/components/goals/PlanVisualization.tsx — collapsible action tree.
2
+ //
3
+ // v6.4.0 — F-036 (Goal Planner UI). Renders the A* plan as a list of
4
+ // collapsible steps. Each step shows action, agent, deps, cost,
5
+ // and a click-to-expand description.
6
+ import { useState } from 'react';
7
+ import { ChevronDown, ChevronRight, Clock, GitBranch } from 'lucide-react';
8
+ import { Card, CardTitle, CardMeta } from '../Card';
9
+ import { Tag } from '../Tag';
10
+ import { cn } from '../../lib/utils';
11
+ import type { Plan, PlannedStep } from '../../lib/goapPlanner';
12
+
13
+ interface Props {
14
+ plan: Plan | null;
15
+ }
16
+
17
+ function formatDuration(ms: number): string {
18
+ if (!Number.isFinite(ms) || ms < 0) return '—';
19
+ const total = Math.floor(ms / 1000);
20
+ const h = Math.floor(total / 3600);
21
+ const m = Math.floor((total % 3600) / 60);
22
+ const s = total % 60;
23
+ if (h > 0) return `${h}h ${m}m`;
24
+ if (m > 0) return `${m}m ${s}s`;
25
+ return `${s}s`;
26
+ }
27
+
28
+ export function PlanVisualization({ plan }: Props) {
29
+ if (!plan) {
30
+ return (
31
+ <Card className="plan-visualization">
32
+ <CardTitle>Execution plan</CardTitle>
33
+ <CardMeta>Submit a goal to generate a plan.</CardMeta>
34
+ </Card>
35
+ );
36
+ }
37
+
38
+ return (
39
+ <Card className="plan-visualization" data-testid="plan-visualization">
40
+ <div className="plan-viz-header">
41
+ <CardTitle>
42
+ <GitBranch size={14} aria-hidden /> Execution plan
43
+ </CardTitle>
44
+ <div className="plan-viz-badges">
45
+ <Tag variant="info">{plan.steps.length} steps</Tag>
46
+ <Tag variant="accent">Cost {plan.totalCost}</Tag>
47
+ <Tag variant="success">
48
+ <Clock size={10} aria-hidden /> {formatDuration(plan.totalDurationMs)}
49
+ </Tag>
50
+ </div>
51
+ </div>
52
+ <CardMeta className="plan-viz-goal">Goal: {plan.goal}</CardMeta>
53
+ <ol className="plan-viz-tree" role="tree">
54
+ {plan.steps.map((step) => (
55
+ <PlanStepRow key={step.id} step={step} />
56
+ ))}
57
+ </ol>
58
+ </Card>
59
+ );
60
+ }
61
+
62
+ function PlanStepRow({ step }: { step: PlannedStep }) {
63
+ const [open, setOpen] = useState(false);
64
+ return (
65
+ <li className="plan-viz-row" role="treeitem" aria-expanded={open}>
66
+ <button
67
+ type="button"
68
+ className="plan-viz-row-head"
69
+ onClick={() => setOpen((v) => !v)}
70
+ data-testid={`plan-step-${step.id}`}
71
+ >
72
+ <span className="plan-viz-chevron">
73
+ {open ? <ChevronDown size={14} aria-hidden /> : <ChevronRight size={14} aria-hidden />}
74
+ </span>
75
+ <span className={cn('plan-viz-action', `is-${step.action}`)}>{step.action}</span>
76
+ <span className="plan-viz-title">{step.title}</span>
77
+ <span className="plan-viz-agent">@{step.agent}</span>
78
+ <span className="plan-viz-cost">{step.estimatedCost}</span>
79
+ </button>
80
+ {open && (
81
+ <div className="plan-viz-row-body" data-testid={`plan-step-${step.id}-body`}>
82
+ <p>{step.description}</p>
83
+ {step.deps.length > 0 && (
84
+ <div className="plan-viz-deps">
85
+ <strong>Deps:</strong>
86
+ <ul>
87
+ {step.deps.map((d) => (
88
+ <li key={d}>
89
+ <code>{d}</code>
90
+ </li>
91
+ ))}
92
+ </ul>
93
+ </div>
94
+ )}
95
+ <div className="plan-viz-effects">
96
+ <strong>Effects:</strong>
97
+ <ul>
98
+ {step.effects.map((e) => (
99
+ <li key={e}>
100
+ <code>{e}</code>
101
+ </li>
102
+ ))}
103
+ </ul>
104
+ </div>
105
+ </div>
106
+ )}
107
+ </li>
108
+ );
109
+ }
110
+
111
+ export default PlanVisualization;