@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,304 @@
1
+ /**
2
+ * agent-registry.ts — in-process agent lifecycle registry (BizarAgentRegistry).
3
+ *
4
+ * F-032 — ported from ruflo `v3/@claude-flow/swarm/src/coordination/agent-registry.ts`
5
+ * (lines 33, 127, 159, 268, 286). Bizar does not ship a full agent runtime;
6
+ * the registry is a stateful, in-memory record of agent metadata that the MCP
7
+ * tools can read/write. The Claude Code Agent SDK is the actual orchestrator;
8
+ * this registry is a thin coordination surface for `agent_spawn`,
9
+ * `agent_list`, and `agent_terminate`.
10
+ *
11
+ * No subprocess, no event bus. Heartbeat is a no-op stub that updates a
12
+ * timestamp so downstream observers can poll liveness.
13
+ *
14
+ * Persistence is opt-in: pass `{ persistPath }` to the constructor (or
15
+ * to the `bizarAgentRegistry` singleton via its mutator) and the registry
16
+ * writes a JSON snapshot to disk after every mutation. This keeps the
17
+ * in-process test path pure while letting the MCP singleton survive a
18
+ * Claude Code session restart.
19
+ */
20
+
21
+ import { randomUUID } from "node:crypto";
22
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
23
+ import { dirname } from "node:path";
24
+
25
+ export type AgentStatus = "active" | "terminated";
26
+
27
+ /**
28
+ * Bizar's typed-agent allowlist. Mirrors the six typed agents in
29
+ * `config/agents/` (coder / tester / reviewer / system-architect /
30
+ * planner / researcher) plus three ruflo-derived typed routes
31
+ * (performance-engineer / security-auditor / memory-specialist).
32
+ * `registerAgent` rejects anything outside this set so the MCP tools
33
+ * surface a typed vocabulary callers can rely on.
34
+ */
35
+ export const AGENT_TYPES = [
36
+ "coder",
37
+ "tester",
38
+ "reviewer",
39
+ "system-architect",
40
+ "planner",
41
+ "researcher",
42
+ "performance-engineer",
43
+ "security-auditor",
44
+ "memory-specialist",
45
+ ] as const;
46
+
47
+ export type AllowedAgentType = (typeof AGENT_TYPES)[number];
48
+
49
+ export function isAllowedAgentType(t: unknown): t is AllowedAgentType {
50
+ return typeof t === "string" && (AGENT_TYPES as readonly string[]).includes(t);
51
+ }
52
+
53
+ export interface AgentRecord {
54
+ agentId: string;
55
+ type: AllowedAgentType;
56
+ name?: string;
57
+ status: AgentStatus;
58
+ priority?: "low" | "normal" | "high" | "critical";
59
+ metadata?: Record<string, unknown>;
60
+ taskCount: number;
61
+ createdAt: string;
62
+ lastHeartbeatAt: string;
63
+ terminatedAt?: string;
64
+ terminationReason?: string;
65
+ gracefulTermination?: boolean;
66
+ }
67
+
68
+ export interface RegisterAgentInput {
69
+ type: string;
70
+ name?: string;
71
+ priority?: "low" | "normal" | "high" | "critical";
72
+ metadata?: Record<string, unknown>;
73
+ }
74
+
75
+ export interface RegisterAgentResult {
76
+ agentId: string;
77
+ status: AgentStatus;
78
+ }
79
+
80
+ export interface ListAgentsInput {
81
+ status?: AgentStatus | "all";
82
+ type?: string;
83
+ limit?: number;
84
+ offset?: number;
85
+ }
86
+
87
+ export interface ListAgentsResult {
88
+ agents: AgentRecord[];
89
+ total: number;
90
+ }
91
+
92
+ export interface TerminateAgentInput {
93
+ agentId: string;
94
+ reason?: string;
95
+ graceful?: boolean;
96
+ }
97
+
98
+ export interface TerminateAgentResult {
99
+ agentId: string;
100
+ status: AgentStatus;
101
+ terminatedAt: string;
102
+ }
103
+
104
+ export interface BizarAgentRegistryOpts {
105
+ /**
106
+ * When set, the registry loads from this file on construction (if it
107
+ * exists) and writes a JSON snapshot after every mutation. The parent
108
+ * directory is created on demand. Path resolution is the caller's
109
+ * responsibility — the registry treats the value as final.
110
+ */
111
+ persistPath?: string;
112
+ }
113
+
114
+ interface PersistShape {
115
+ version: 1;
116
+ agents: AgentRecord[];
117
+ }
118
+
119
+ export class BizarAgentRegistry {
120
+ private readonly agents: Map<string, AgentRecord> = new Map();
121
+ private readonly seenIds: Set<string> = new Set();
122
+ private persistPath?: string;
123
+
124
+ constructor(opts: BizarAgentRegistryOpts = {}) {
125
+ this.persistPath = opts.persistPath;
126
+ if (this.persistPath) this.loadFromDisk();
127
+ }
128
+
129
+ /**
130
+ * Configure (or clear) the persistence path after construction.
131
+ * The next mutation will write a snapshot to the new location.
132
+ */
133
+ setPersistPath(path: string | undefined): void {
134
+ this.persistPath = path;
135
+ if (path) this.loadFromDisk();
136
+ }
137
+
138
+ /**
139
+ * Generate a unique agent id of the form `agent-<uuid>`. Uses
140
+ * `crypto.randomUUID()` so the IDs are stable across processes
141
+ * and there is no retry loop on collision (UUID v4 has a 122-bit
142
+ * random payload).
143
+ */
144
+ private nextAgentId(): string {
145
+ const id = `agent-${randomUUID()}`;
146
+ this.seenIds.add(id);
147
+ return id;
148
+ }
149
+
150
+ registerAgent(input: RegisterAgentInput): RegisterAgentResult {
151
+ if (!input || typeof input.type !== "string" || input.type.trim().length === 0) {
152
+ throw new Error("registerAgent: `type` is required and must be a non-empty string");
153
+ }
154
+ if (!isAllowedAgentType(input.type)) {
155
+ throw new Error(
156
+ `registerAgent: \`type\` must be one of ${AGENT_TYPES.join(", ")}; got: ${input.type}`,
157
+ );
158
+ }
159
+ const agentId = this.nextAgentId();
160
+ const now = new Date().toISOString();
161
+ const record: AgentRecord = {
162
+ agentId,
163
+ type: input.type,
164
+ name: input.name,
165
+ status: "active",
166
+ priority: input.priority,
167
+ metadata: input.metadata,
168
+ taskCount: 0,
169
+ createdAt: now,
170
+ lastHeartbeatAt: now,
171
+ };
172
+ this.agents.set(agentId, record);
173
+ this.persist();
174
+ return { agentId, status: record.status };
175
+ }
176
+
177
+ listAgents(input: ListAgentsInput = {}): ListAgentsResult {
178
+ const status = input.status ?? "all";
179
+ const type = input.type;
180
+ let filtered = Array.from(this.agents.values()).filter((a) => {
181
+ if (status !== "all" && a.status !== status) return false;
182
+ if (type && a.type !== type) return false;
183
+ return true;
184
+ });
185
+ const total = filtered.length;
186
+ const offset = Math.max(0, input.offset ?? 0);
187
+ const limit = input.limit !== undefined ? Math.max(0, Math.min(1000, input.limit)) : 100;
188
+ filtered = filtered.slice(offset, offset + limit);
189
+ return { agents: filtered, total };
190
+ }
191
+
192
+ terminateAgent(input: TerminateAgentInput): TerminateAgentResult {
193
+ if (!input || typeof input.agentId !== "string") {
194
+ throw new Error("terminateAgent: `agentId` is required");
195
+ }
196
+ const record = this.agents.get(input.agentId);
197
+ if (!record) {
198
+ throw new Error(`terminateAgent: agent not found: ${input.agentId}`);
199
+ }
200
+ const terminatedAt = new Date().toISOString();
201
+ record.status = "terminated";
202
+ record.terminatedAt = terminatedAt;
203
+ record.terminationReason = input.reason;
204
+ record.gracefulTermination = input.graceful ?? true;
205
+ record.lastHeartbeatAt = terminatedAt;
206
+ this.persist();
207
+ return { agentId: record.agentId, status: record.status, terminatedAt };
208
+ }
209
+
210
+ getAgent(agentId: string): AgentRecord | undefined {
211
+ return this.agents.get(agentId);
212
+ }
213
+
214
+ /**
215
+ * No-op heartbeat stub. Updates `lastHeartbeatAt` so callers can poll
216
+ * liveness. Returns true if the agent was found.
217
+ */
218
+ heartbeat(agentId: string): boolean {
219
+ const record = this.agents.get(agentId);
220
+ if (!record) return false;
221
+ if (record.status === "terminated") return false;
222
+ record.lastHeartbeatAt = new Date().toISOString();
223
+ this.persist();
224
+ return true;
225
+ }
226
+
227
+ /**
228
+ * Bump the per-agent task counter (used by orchestration callers).
229
+ */
230
+ incrementTaskCount(agentId: string, by = 1): number {
231
+ const record = this.agents.get(agentId);
232
+ if (!record) return 0;
233
+ record.taskCount += by;
234
+ this.persist();
235
+ return record.taskCount;
236
+ }
237
+
238
+ /**
239
+ * Test/dev helper: drop everything. Not exposed via MCP.
240
+ */
241
+ reset(): void {
242
+ this.agents.clear();
243
+ this.seenIds.clear();
244
+ // Persistence: a reset should also wipe the file when configured,
245
+ // so the next loadFromDisk starts clean.
246
+ if (this.persistPath && existsSync(this.persistPath)) {
247
+ try { writeFileSync(this.persistPath, JSON.stringify({ version: 1, agents: [] }, null, 2), "utf-8"); }
248
+ catch { /* best-effort */ }
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Total agents currently tracked (any status).
254
+ */
255
+ size(): number {
256
+ return this.agents.size;
257
+ }
258
+
259
+ // ── Persistence ───────────────────────────────────────────────────────
260
+
261
+ private loadFromDisk(): void {
262
+ if (!this.persistPath || !existsSync(this.persistPath)) return;
263
+ try {
264
+ const raw = readFileSync(this.persistPath, "utf-8");
265
+ const data = JSON.parse(raw) as Partial<PersistShape>;
266
+ if (Array.isArray(data.agents)) {
267
+ for (const a of data.agents) {
268
+ if (!a || typeof a.agentId !== "string") continue;
269
+ this.agents.set(a.agentId, a);
270
+ this.seenIds.add(a.agentId);
271
+ }
272
+ }
273
+ } catch {
274
+ // Corrupt or partial file — start fresh. A stale snapshot is
275
+ // never worse than refusing to boot.
276
+ }
277
+ }
278
+
279
+ private persist(): void {
280
+ if (!this.persistPath) return;
281
+ try {
282
+ mkdirSync(dirname(this.persistPath), { recursive: true });
283
+ const data: PersistShape = {
284
+ version: 1,
285
+ agents: Array.from(this.agents.values()),
286
+ };
287
+ writeFileSync(this.persistPath, JSON.stringify(data, null, 2), "utf-8");
288
+ } catch {
289
+ // Persistence is best-effort. The registry remains correct
290
+ // in memory even if the disk write fails.
291
+ }
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Process-wide singleton used by MCP tools. Persistence defaults to
297
+ * `.harness/agents.json` in the current working directory so the
298
+ * registry survives a Claude Code session restart on the same machine.
299
+ * Tests should construct their own `new BizarAgentRegistry()` to get
300
+ * an isolated, non-persistent instance.
301
+ */
302
+ export const bizarAgentRegistry = new BizarAgentRegistry({
303
+ persistPath: ".harness/agents.json",
304
+ });