@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,129 @@
1
+ /**
2
+ * src/server/routes/distill.mjs
3
+ *
4
+ * v6.4.0 — F-033 / ADR-174.
5
+ *
6
+ * REST surface for the ReasoningBank distillation pipeline.
7
+ *
8
+ * POST /api/distill
9
+ * Triggers a single RETRIEVE → JUDGE → DISTILL → CONSOLIDATE run
10
+ * over the current vault. Optional JSON body:
11
+ * { "since": "<iso8601>" }
12
+ * Returns the consolidation result (same shape as the SDK
13
+ * `memory_distill` MCP tool plus the in-file path).
14
+ *
15
+ * GET /api/distill/patterns
16
+ * Reads the most recent `.bizar/distilled-patterns.json` and
17
+ * returns its `patterns` array (filtered by `?tier=` and
18
+ * `?promoted=` query params). 404 if the file does not yet
19
+ * exist (a distillation run hasn't happened).
20
+ *
21
+ * GET /api/distill/status
22
+ * Returns a small status object:
23
+ * { file: "<path or null>", patternCount, promotedCount, byTier, generatedAt }
24
+ *
25
+ * Mounted at /api/distill/*. The route itself is `Router()`-style so
26
+ * it composes with the rest of the v3 dashboard API.
27
+ */
28
+
29
+ import { Router } from 'express';
30
+ import { existsSync, readFileSync } from 'node:fs';
31
+ import { dirname, join } from 'node:path';
32
+
33
+ const SERVER_ROOT = join(dirname(new URL(import.meta.url).pathname), '..');
34
+ const memoryConsolidator = await import(`${SERVER_ROOT}/memory-consolidator.mjs`).then((m) => m);
35
+
36
+ const { wrap } = await import('./_shared.mjs').then((m) => m);
37
+
38
+ /**
39
+ * Build the distill router. `projectRoot` is the Bizar repo root — it
40
+ * defaults to `process.cwd()` so that `bizar dashboard --dev` "just
41
+ * works" when launched from inside a Bizar project.
42
+ */
43
+ export function createDistillRouter({ projectRoot } = {}) {
44
+ const root = projectRoot ?? process.cwd();
45
+ const router = Router();
46
+
47
+ // ---------------------------------------------------------------------
48
+ // POST /api/distill — run the pipeline.
49
+ // ---------------------------------------------------------------------
50
+ router.post('/distill', wrap(async (req, res) => {
51
+ const since = typeof req.body?.since === 'string' ? req.body.since : undefined;
52
+ const result = memoryConsolidator.runConsolidation({ projectRoot: root, since });
53
+
54
+ // Side-effect: write the machine-readable log so the GET route
55
+ // can serve it without re-running the pipeline.
56
+ const filePath = memoryConsolidator.writeDistilledPatternsFile(result, {
57
+ projectRoot: root,
58
+ });
59
+
60
+ res.json({
61
+ distilled: result.distilled,
62
+ promoted: result.promoted,
63
+ patterns: result.patterns,
64
+ byTier: result.byTier,
65
+ written: result.written,
66
+ file: filePath,
67
+ });
68
+ }));
69
+
70
+ // ---------------------------------------------------------------------
71
+ // GET /api/distill/patterns — return the patterns.
72
+ // ---------------------------------------------------------------------
73
+ router.get('/distill/patterns', wrap(async (req, res) => {
74
+ const fp = join(root, memoryConsolidator.DISTILLED_PATTERNS_FILE);
75
+ if (!existsSync(fp)) {
76
+ res.status(404).json({ error: 'not_distilled_yet', file: fp });
77
+ return;
78
+ }
79
+ const raw = readFileSync(fp, 'utf-8');
80
+ let payload;
81
+ try { payload = JSON.parse(raw); }
82
+ catch (e) { res.status(500).json({ error: 'corrupt_distill_file', message: String(e) }); return; }
83
+
84
+ let patterns = Array.isArray(payload.patterns) ? payload.patterns : [];
85
+
86
+ const tier = typeof req.query.tier === 'string' ? req.query.tier : null;
87
+ if (tier) patterns = patterns.filter((p) => p.provenance_tier === tier);
88
+
89
+ const promoted = req.query.promoted === 'true'
90
+ ? true
91
+ : req.query.promoted === 'false'
92
+ ? false
93
+ : null;
94
+ if (promoted !== null) patterns = patterns.filter((p) => Boolean(p.promoted) === promoted);
95
+
96
+ res.json({
97
+ generatedAt: payload.generatedAt,
98
+ byTier: payload.byTier,
99
+ patterns,
100
+ count: patterns.length,
101
+ });
102
+ }));
103
+
104
+ // ---------------------------------------------------------------------
105
+ // GET /api/distill/status — small status object.
106
+ // ---------------------------------------------------------------------
107
+ router.get('/distill/status', wrap(async (_req, res) => {
108
+ const fp = join(root, memoryConsolidator.DISTILLED_PATTERNS_FILE);
109
+ if (!existsSync(fp)) {
110
+ res.json({ file: null, patternCount: 0, promotedCount: 0, byTier: null, generatedAt: null });
111
+ return;
112
+ }
113
+ try {
114
+ const payload = JSON.parse(readFileSync(fp, 'utf-8'));
115
+ const patterns = Array.isArray(payload.patterns) ? payload.patterns : [];
116
+ res.json({
117
+ file: fp,
118
+ patternCount: patterns.length,
119
+ promotedCount: patterns.filter((p) => Boolean(p.promoted)).length,
120
+ byTier: payload.byTier,
121
+ generatedAt: payload.generatedAt,
122
+ });
123
+ } catch (e) {
124
+ res.status(500).json({ error: 'corrupt_distill_file', message: String(e) });
125
+ }
126
+ }));
127
+
128
+ return router;
129
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * src/server/routes/goal-planner.mjs
3
+ *
4
+ * v6.4.0 — F-036 (Goal Planner UI).
5
+ *
6
+ * Single endpoint:
7
+ * POST /api/goal-planner/plan { goal } -> Plan
8
+ *
9
+ * The planner logic is duplicated here in pure JS so the route
10
+ * runs without a Vite build step. The canonical implementation
11
+ * lives in `bizar-dash/src/web/lib/goapPlanner.ts` and produces
12
+ * the same shape.
13
+ */
14
+ import { Router } from 'express';
15
+ import { wrap } from './_shared.mjs';
16
+
17
+ const VERB_TO_ACTION = {
18
+ research: { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Gather context, examples, and prior art.' },
19
+ investigate: { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Probe the unknown regions of the problem.' },
20
+ explore: { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Map the solution space.' },
21
+ find: { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Locate references and prior work.' },
22
+ analyze: { name: 'analyze', cost: 1, agent: 'Odin', effect: 'goal_analyzed', preconditions: ['context_gathered'], description: 'Decompose the goal into measurable sub-objectives.' },
23
+ plan: { name: 'plan', cost: 1, agent: 'Odin', effect: 'plan_drafted', preconditions: ['goal_analyzed'], description: 'Sequence the work into milestones.' },
24
+ design: { name: 'design', cost: 2, agent: 'Tyr', effect: 'design_drafted', preconditions: ['plan_drafted'], description: 'Sketch the architecture and contracts.' },
25
+ architect: { name: 'design', cost: 2, agent: 'Tyr', effect: 'design_drafted', preconditions: ['plan_drafted'], description: 'Lay out the system structure.' },
26
+ build: { name: 'build', cost: 3, agent: 'Thor', effect: 'artifact_built', preconditions: ['design_drafted'], description: 'Construct the artifact end-to-end.' },
27
+ implement: { name: 'implement', cost: 3, agent: 'Thor', effect: 'artifact_built', preconditions: ['design_drafted'], description: 'Wire up the code path.' },
28
+ create: { name: 'implement', cost: 3, agent: 'Thor', effect: 'artifact_built', preconditions: ['design_drafted'], description: 'Stand up the new module.' },
29
+ add: { name: 'implement', cost: 2, agent: 'Thor', effect: 'artifact_built', preconditions: ['design_drafted'], description: 'Layer the new capability onto an existing surface.' },
30
+ write: { name: 'implement', cost: 2, agent: 'Thor', effect: 'artifact_built', description: 'Author the requested artifact.' },
31
+ fix: { name: 'implement', cost: 2, agent: 'Thor', effect: 'artifact_built', description: 'Repair the reported defect.' },
32
+ refactor: { name: 'implement', cost: 2, agent: 'Thor', effect: 'artifact_built', description: 'Improve the structure without changing behavior.' },
33
+ test: { name: 'test', cost: 2, agent: 'Forseti', effect: 'tests_passing', preconditions: ['artifact_built'], description: 'Exercise the artifact against acceptance criteria.' },
34
+ verify: { name: 'test', cost: 1, agent: 'Forseti', effect: 'tests_passing', preconditions: ['artifact_built'], description: 'Validate against the original goal statement.' },
35
+ validate: { name: 'test', cost: 1, agent: 'Forseti', effect: 'tests_passing', preconditions: ['artifact_built'], description: 'Cross-check the result against acceptance criteria.' },
36
+ review: { name: 'review', cost: 1, agent: 'Forseti', effect: 'review_passed', preconditions: ['artifact_built'], description: 'Audit the change for quality and regressions.' },
37
+ document: { name: 'document', cost: 1, agent: 'Bragi', effect: 'docs_written', preconditions: ['artifact_built'], description: 'Capture the new knowledge in docs.' },
38
+ deploy: { name: 'deploy', cost: 2, agent: 'Heimdall', effect: 'deployed', preconditions: ['tests_passing', 'review_passed'], description: 'Promote the artifact to production.' },
39
+ ship: { name: 'deploy', cost: 2, agent: 'Heimdall', effect: 'deployed', preconditions: ['tests_passing', 'review_passed'], description: 'Release the change to users.' },
40
+ release: { name: 'deploy', cost: 2, agent: 'Heimdall', effect: 'deployed', preconditions: ['tests_passing', 'review_passed'], description: 'Tag and publish a new version.' },
41
+ publish: { name: 'deploy', cost: 1, agent: 'Heimdall', effect: 'deployed', preconditions: ['tests_passing'], description: 'Make the artifact externally available.' },
42
+ commit: { name: 'commit', cost: 1, agent: 'Hermod', effect: 'committed', description: 'Land the change in version control.' },
43
+ save: { name: 'commit', cost: 1, agent: 'Hermod', effect: 'committed', description: 'Persist the work.' },
44
+ };
45
+
46
+ const DURATION_MS = {
47
+ research: 30000, analyze: 20000, plan: 25000, design: 60000,
48
+ build: 120000, implement: 90000, test: 60000, review: 30000,
49
+ document: 30000, deploy: 60000, commit: 10000,
50
+ };
51
+
52
+ const FALLBACK = { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Investigate the goal.' };
53
+
54
+ function splitClauses(text) {
55
+ // Split on terminal punctuation, conjunctions (then/after/before/and
56
+ // then), AND commas / the word "and" so that "Design, build, test,
57
+ // and deploy" produces 4 clauses.
58
+ const parts = text.split(/[.;!?\n]|\bthen\b|\bafter that\b|\bafter\b|\bbefore\b|\band then\b|\s*,\s*|\band\b/gi);
59
+ const out = [];
60
+ for (const part of parts) {
61
+ const t = part.trim();
62
+ if (!t) continue;
63
+ if (!/^[a-zA-Z]+\b/.test(t) && t.includes(',')) {
64
+ for (const sub of t.split(',')) {
65
+ const s = sub.trim();
66
+ if (s) out.push(s);
67
+ }
68
+ } else {
69
+ out.push(t);
70
+ }
71
+ }
72
+ return out;
73
+ }
74
+
75
+ function stripPolite(body) {
76
+ let out = body;
77
+ for (let i = 0; i < 4; i++) {
78
+ const before = out;
79
+ out = out.replace(/^(?:please\s+|kindly\s+)/i, '');
80
+ out = out.replace(/^(?:we should|let's|lets|i want to|i need to|i'd like to|i would like to|can you|could you|would you|help me|help us|make sure to|please)\s+/i, '');
81
+ if (out === before) break;
82
+ }
83
+ return out;
84
+ }
85
+
86
+ function extractVerb(clause) {
87
+ const body = stripPolite(clause.trim());
88
+ const first = (body.split(/\s+/)[0] || '').toLowerCase().replace(/[^a-z]/g, '');
89
+ return first;
90
+ }
91
+
92
+ function clauseSubject(clause) {
93
+ const body = stripPolite(clause.trim());
94
+ const stripped = body.replace(/^[a-zA-Z]+\s+/, '').trim();
95
+ return stripped.length > 0 ? stripped : clause;
96
+ }
97
+
98
+ function aStar(actions, goals) {
99
+ const goalSet = new Set(goals);
100
+ if (goalSet.size === 0) return actions.slice(0, 1);
101
+ const heur = (s) => {
102
+ let u = 0;
103
+ for (const g of goalSet) if (!s.has(g)) u++;
104
+ return u;
105
+ };
106
+ const keyOf = (s) => Array.from(s).sort().join('|') || 'empty';
107
+ const start = { sat: new Set(), path: [], cost: 0 };
108
+ const open = [start];
109
+ const closed = new Set();
110
+ let best = start;
111
+ while (open.length) {
112
+ open.sort((a, b) => (a.cost + heur(a.sat)) - (b.cost + heur(b.sat)));
113
+ const node = open.shift();
114
+ if (heur(node.sat) === 0) return node.path;
115
+ if (heur(node.sat) < heur(best.sat)) best = node;
116
+ const k = keyOf(node.sat);
117
+ if (closed.has(k)) continue;
118
+ closed.add(k);
119
+ for (const a of actions) {
120
+ const pre = a.preconditions || [];
121
+ if (!pre.every((p) => node.sat.has(p))) continue;
122
+ const next = new Set(node.sat);
123
+ next.add(a.effect);
124
+ open.push({ sat: next, path: [...node.path, a], cost: node.cost + a.cost });
125
+ }
126
+ }
127
+ return best.path;
128
+ }
129
+
130
+ function inlinePlan(goalText) {
131
+ const id = `plan_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
132
+ const clauses = splitClauses(goalText);
133
+ const effective = clauses.length > 0 ? clauses : [(goalText || 'Investigate the goal').trim()];
134
+ const rawTemplates = effective.map((c) => VERB_TO_ACTION[extractVerb(c)] || FALLBACK);
135
+ const subjects = effective.map(clauseSubject);
136
+ const seen = new Set();
137
+ const dedup = [];
138
+ for (let i = 0; i < rawTemplates.length; i++) {
139
+ const t = rawTemplates[i];
140
+ const s = subjects[i] || `step ${i + 1}`;
141
+ const sig = `${t.name}::${s.toLowerCase().slice(0, 40)}`;
142
+ if (seen.has(sig)) continue;
143
+ seen.add(sig);
144
+ dedup.push({ template: t, subject: s });
145
+ }
146
+ const trimmed = dedup.slice(0, 8);
147
+ const goalEffects = trimmed.map((d) => d.template.effect);
148
+ const sequence = aStar(trimmed.map((d) => d.template), goalEffects);
149
+ const idByEffect = new Map();
150
+ const steps = [];
151
+ let n = 0;
152
+ for (const tpl of sequence) {
153
+ const match = trimmed.find((s) => s.template === tpl);
154
+ if (!match) continue;
155
+ n += 1;
156
+ const sid = `s${n}`;
157
+ const deps = [];
158
+ for (const p of tpl.preconditions || []) {
159
+ const pid = idByEffect.get(p);
160
+ if (pid) deps.push(pid);
161
+ }
162
+ const title = match.subject.charAt(0).toUpperCase() + match.subject.slice(1);
163
+ steps.push({
164
+ id: sid,
165
+ action: tpl.name,
166
+ title: title.length > 0 ? title : tpl.name,
167
+ agent: tpl.agent,
168
+ description: tpl.description,
169
+ effects: [tpl.effect],
170
+ deps,
171
+ status: 'pending',
172
+ estimatedCost: tpl.cost,
173
+ estimatedDurationMs: DURATION_MS[tpl.name] || 30000,
174
+ });
175
+ idByEffect.set(tpl.effect, sid);
176
+ }
177
+ if (steps.length === 0 && trimmed.length > 0) {
178
+ let idx = 0;
179
+ for (const { template, subject } of trimmed) {
180
+ idx += 1;
181
+ const sid = `s${idx}`;
182
+ const deps = [];
183
+ for (const p of template.preconditions || []) {
184
+ const pid = idByEffect.get(p);
185
+ if (pid) deps.push(pid);
186
+ }
187
+ const title = subject.charAt(0).toUpperCase() + subject.slice(1);
188
+ steps.push({
189
+ id: sid,
190
+ action: template.name,
191
+ title: title.length > 0 ? title : template.name,
192
+ agent: template.agent,
193
+ description: template.description,
194
+ effects: [template.effect],
195
+ deps,
196
+ status: 'pending',
197
+ estimatedCost: template.cost,
198
+ estimatedDurationMs: DURATION_MS[template.name] || 30000,
199
+ });
200
+ idByEffect.set(template.effect, sid);
201
+ }
202
+ }
203
+ const totalCost = steps.reduce((sum, s) => sum + s.estimatedCost, 0);
204
+ const totalDurationMs = steps.reduce((sum, s) => sum + s.estimatedDurationMs, 0);
205
+ return { id, goal: goalText, steps, totalCost, totalDurationMs, goalEffects };
206
+ }
207
+
208
+ function readGoal(body) {
209
+ const raw = body && body.goal;
210
+ if (typeof raw !== 'string' || !raw.trim()) {
211
+ const err = new Error('goal (non-empty string) is required');
212
+ err.status = 400;
213
+ err.code = 'bad_request';
214
+ throw err;
215
+ }
216
+ if (raw.length > 4000) {
217
+ const err = new Error('goal must be 4000 chars or fewer');
218
+ err.status = 400;
219
+ err.code = 'bad_request';
220
+ throw err;
221
+ }
222
+ return raw.trim();
223
+ }
224
+
225
+ export function createGoalPlannerRouter({ broadcast } = {}) {
226
+ const router = Router();
227
+ router.post('/goal-planner/plan', wrap(async (req, res) => {
228
+ const goal = readGoal(req.body);
229
+ const plan = inlinePlan(goal);
230
+ if (broadcast) {
231
+ try { broadcast({ type: 'goal:planned', planId: plan.id, goal }); } catch { /* best-effort */ }
232
+ }
233
+ res.json(plan);
234
+ }));
235
+ return router;
236
+ }
@@ -42,6 +42,8 @@ import { Doctor } from './views/Doctor';
42
42
  import { Harness } from './views/Harness';
43
43
  import { Eval } from './views/Eval';
44
44
  import { EvalReport } from './views/EvalReport';
45
+ // v6.4.0 — F-036 Goal Planner UI.
46
+ import { GoalPlanner } from './views/GoalPlanner';
45
47
  import { SettingsNav } from './components/SettingsNav';
46
48
  import { Spinner } from './components/Spinner';
47
49
  import { Button } from './components/Button';
@@ -108,6 +110,8 @@ const VIEW_MAP: Record<string, (p: ViewProps) => React.ReactNode> = {
108
110
  // Eval view's "Open Eval Report" button (full report + diff).
109
111
  eval: Eval,
110
112
  evalReport: EvalReport,
113
+ // v6.4.0 — F-036 Goal Planner UI. Plain-English goal → A* plan.
114
+ 'goal-planner': GoalPlanner,
111
115
  };
112
116
 
113
117
  const VERSION = 'v6.0.0';
@@ -1,17 +1,30 @@
1
1
  // src/components/Tag.tsx — pill for tags.
2
2
 
3
- import type { ReactNode } from 'react';
3
+ import type { HTMLAttributes, ReactNode } from 'react';
4
4
  import { cn } from '../lib/utils';
5
5
 
6
- export type TagProps = {
6
+ export type TagVariant =
7
+ | "neutral"
8
+ | "info"
9
+ | "accent"
10
+ | "primary"
11
+ | "success"
12
+ | "warning"
13
+ | "error";
14
+
15
+ export type TagProps = HTMLAttributes<HTMLSpanElement> & {
7
16
  children: ReactNode;
8
17
  className?: string;
18
+ variant?: TagVariant;
9
19
  onRemove?: () => void;
10
20
  };
11
21
 
12
- export function Tag({ children, className, onRemove }: TagProps) {
22
+ export function Tag({ children, className, variant, onRemove, ...rest }: TagProps) {
13
23
  return (
14
- <span className={cn('tag', className)}>
24
+ <span
25
+ className={cn('tag', variant ? `tag-${variant}` : undefined, className)}
26
+ {...rest}
27
+ >
15
28
  {children}
16
29
  {onRemove && (
17
30
  <button
@@ -25,4 +38,4 @@ export function Tag({ children, className, onRemove }: TagProps) {
25
38
  )}
26
39
  </span>
27
40
  );
28
- }
41
+ }
@@ -26,6 +26,7 @@ import {
26
26
  Brain,
27
27
  Stethoscope,
28
28
  ClipboardCheck,
29
+ Target,
29
30
  type LucideIcon,
30
31
  } from 'lucide-react';
31
32
  import { cn } from '../lib/utils';
@@ -66,6 +67,8 @@ export const TABS: TabDef[] = [
66
67
  { id: 'doctor', label: 'Doctor', icon: Stethoscope },
67
68
  // v6.0.0 — Harness engineering dashboard.
68
69
  { id: 'harness', label: 'Harness', icon: Shield },
70
+ // v6.4.0 — F-036 Goal Planner UI. Plain-English → A* plan.
71
+ { id: 'goal-planner', label: 'Goals', icon: Target },
69
72
  { id: 'settings', label: 'Settings', icon: Sliders },
70
73
  ];
71
74
 
@@ -0,0 +1,126 @@
1
+ // src/web/components/agents/CommunicationLog.tsx — inter-agent message bus trace.
2
+ //
3
+ // v6.4.0 — F-036 (Goal Planner UI). Subscribes to ws.ts for any
4
+ // event whose type starts with `agent:` and renders the last 200
5
+ // messages with color coding by type. Bounded virtual-list window
6
+ // so a runaway stream can't blow up the page.
7
+ import { useEffect, useState } from 'react';
8
+ import { MessageSquare, ArrowRight } from 'lucide-react';
9
+ import { Ws } from '../../lib/ws';
10
+ import { Card, CardTitle, CardMeta } from '../Card';
11
+ import { Tag } from '../Tag';
12
+
13
+ const MAX_ROWS = 200;
14
+
15
+ type Entry = {
16
+ id: string;
17
+ ts: number;
18
+ from: string;
19
+ to: string;
20
+ message: string;
21
+ kind: 'info' | 'request' | 'success' | 'warning' | 'error' | 'status' | 'restarted' | 'stuck' | 'message';
22
+ raw: unknown;
23
+ };
24
+
25
+ function classify(t: string): Entry['kind'] {
26
+ if (t === 'agent:status') return 'status';
27
+ if (t === 'agent:restarted') return 'restarted';
28
+ if (t === 'agent:stuck') return 'stuck';
29
+ if (t === 'agent:message') return 'message';
30
+ return 'info';
31
+ }
32
+
33
+ function deriveEntry(t: string, payload: Record<string, unknown> | undefined, ts: number): Entry {
34
+ const kind = classify(t);
35
+ const id = `${ts}-${Math.random().toString(36).slice(2, 7)}`;
36
+ if (t === 'agent:message') {
37
+ const m = (payload?.message || payload) as Record<string, unknown>;
38
+ return {
39
+ id,
40
+ ts,
41
+ from: String(m?.from || 'unknown'),
42
+ to: String(m?.to || 'unknown'),
43
+ message: String(m?.text || m?.message || JSON.stringify(m).slice(0, 160)),
44
+ kind,
45
+ raw: payload,
46
+ };
47
+ }
48
+ const agent = (payload?.agent || payload) as Record<string, unknown>;
49
+ const name = String(agent?.name || 'agent');
50
+ return {
51
+ id,
52
+ ts,
53
+ from: name,
54
+ to: kind === 'stuck' ? 'orchestrator' : 'broadcast',
55
+ message: `${t} ${agent?.status ? `→ ${agent.status}` : ''}`.trim(),
56
+ kind,
57
+ raw: payload,
58
+ };
59
+ }
60
+
61
+ interface Props {
62
+ /** Override the max row count (default 200). */
63
+ maxRows?: number;
64
+ }
65
+
66
+ export function CommunicationLog({ maxRows = MAX_ROWS }: Props) {
67
+ const [rows, setRows] = useState<Entry[]>([]);
68
+
69
+ useEffect(() => {
70
+ const ws = new Ws();
71
+ const off = ws.on((msg) => {
72
+ const t = (msg as { type?: string }).type;
73
+ if (!t || !t.startsWith('agent:')) return;
74
+ const m = msg as { type: string; ts?: number } & Record<string, unknown>;
75
+ const ts = Number(m.ts) || Date.now();
76
+ setRows((prev) => {
77
+ const next = [...prev, deriveEntry(t, m as Record<string, unknown>, ts)];
78
+ return next.length > maxRows ? next.slice(next.length - maxRows) : next;
79
+ });
80
+ });
81
+ return () => {
82
+ off();
83
+ ws.close();
84
+ };
85
+ }, [maxRows]);
86
+
87
+ return (
88
+ <Card className="comm-log" data-testid="communication-log">
89
+ <div className="comm-log-header">
90
+ <CardTitle>
91
+ <MessageSquare size={14} aria-hidden /> Communication log
92
+ </CardTitle>
93
+ <Tag variant="neutral" data-testid="communication-log-count">{rows.length}</Tag>
94
+ </div>
95
+ <CardMeta>Inter-agent message bus trace · bounded to {maxRows} rows</CardMeta>
96
+ <ul className="comm-log-list" data-testid="communication-log-list">
97
+ {rows.length === 0 && (
98
+ <li className="comm-log-empty" data-testid="communication-log-empty">
99
+ No agent messages yet. Stream will appear here live.
100
+ </li>
101
+ )}
102
+ {rows.slice().reverse().map((row) => (
103
+ <li
104
+ key={row.id}
105
+ className={`comm-log-row is-${row.kind}`}
106
+ data-testid="communication-log-row"
107
+ data-kind={row.kind}
108
+ >
109
+ <div className="comm-log-row-meta">
110
+ <span className="comm-log-from">{row.from}</span>
111
+ <ArrowRight size={12} aria-hidden />
112
+ <span className="comm-log-to">{row.to}</span>
113
+ <Tag variant="neutral" className={`comm-log-kind is-${row.kind}`}>{row.kind}</Tag>
114
+ <time className="comm-log-time" dateTime={new Date(row.ts).toISOString()}>
115
+ {new Date(row.ts).toLocaleTimeString()}
116
+ </time>
117
+ </div>
118
+ <div className="comm-log-message">{row.message}</div>
119
+ </li>
120
+ ))}
121
+ </ul>
122
+ </Card>
123
+ );
124
+ }
125
+
126
+ export default CommunicationLog;