@johpaz/hive 1.7.2 → 1.7.3

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 (427) hide show
  1. package/README.md +178 -36
  2. package/dist/hive.js +315124 -0
  3. package/package.json +11 -3
  4. package/packages/core/src/index.ts +0 -1
  5. package/.dockerignore +0 -9
  6. package/CONTRIBUTING.md +0 -44
  7. package/Dockerfile +0 -67
  8. package/docker-compose.yml +0 -19
  9. package/packages/cli/package.json +0 -28
  10. package/packages/cli/src/commands/agent-run.ts +0 -168
  11. package/packages/cli/src/commands/agents.ts +0 -398
  12. package/packages/cli/src/commands/chat.ts +0 -142
  13. package/packages/cli/src/commands/config.ts +0 -50
  14. package/packages/cli/src/commands/cron.ts +0 -161
  15. package/packages/cli/src/commands/dev.ts +0 -95
  16. package/packages/cli/src/commands/doctor.ts +0 -133
  17. package/packages/cli/src/commands/gateway.ts +0 -422
  18. package/packages/cli/src/commands/logs.ts +0 -57
  19. package/packages/cli/src/commands/mcp.ts +0 -175
  20. package/packages/cli/src/commands/message.ts +0 -77
  21. package/packages/cli/src/commands/onboard.ts +0 -1696
  22. package/packages/cli/src/commands/security.ts +0 -144
  23. package/packages/cli/src/commands/service.ts +0 -50
  24. package/packages/cli/src/commands/sessions.ts +0 -116
  25. package/packages/cli/src/commands/skills.ts +0 -187
  26. package/packages/cli/src/commands/update.ts +0 -25
  27. package/packages/cli/src/index.ts +0 -190
  28. package/packages/cli/src/utils/token.ts +0 -6
  29. package/packages/code-bridge/README.md +0 -78
  30. package/packages/code-bridge/package.json +0 -18
  31. package/packages/code-bridge/src/index.ts +0 -95
  32. package/packages/code-bridge/src/process-manager.ts +0 -212
  33. package/packages/code-bridge/src/schemas.ts +0 -133
  34. package/packages/core/package.json +0 -55
  35. package/packages/core/src/agent/agent-loop.ts +0 -520
  36. package/packages/core/src/agent/compaction.ts +0 -183
  37. package/packages/core/src/agent/context-compiler.ts +0 -544
  38. package/packages/core/src/agent/context-guard.ts +0 -91
  39. package/packages/core/src/agent/conversation-store.ts +0 -193
  40. package/packages/core/src/agent/curator.ts +0 -158
  41. package/packages/core/src/agent/hooks.ts +0 -166
  42. package/packages/core/src/agent/llm-client.ts +0 -503
  43. package/packages/core/src/agent/native-tools.ts +0 -31
  44. package/packages/core/src/agent/playbook-selector.ts +0 -143
  45. package/packages/core/src/agent/prompt-builder.ts +0 -167
  46. package/packages/core/src/agent/providers/index.ts +0 -186
  47. package/packages/core/src/agent/providers.ts +0 -1
  48. package/packages/core/src/agent/reflector.ts +0 -200
  49. package/packages/core/src/agent/service.ts +0 -266
  50. package/packages/core/src/agent/skill-selector.ts +0 -413
  51. package/packages/core/src/agent/stuck-loop.ts +0 -133
  52. package/packages/core/src/agent/tool-selector.ts +0 -623
  53. package/packages/core/src/agent/tracer.ts +0 -102
  54. package/packages/core/src/canvas/canvas-manager.ts +0 -319
  55. package/packages/core/src/canvas/canvas-tools.ts +0 -420
  56. package/packages/core/src/canvas/emitter.ts +0 -119
  57. package/packages/core/src/canvas/index.ts +0 -2
  58. package/packages/core/src/channels/base.ts +0 -140
  59. package/packages/core/src/channels/discord.ts +0 -260
  60. package/packages/core/src/channels/index.ts +0 -7
  61. package/packages/core/src/channels/manager.ts +0 -383
  62. package/packages/core/src/channels/slack.ts +0 -287
  63. package/packages/core/src/channels/telegram.ts +0 -552
  64. package/packages/core/src/channels/webchat.ts +0 -139
  65. package/packages/core/src/channels/whatsapp.ts +0 -375
  66. package/packages/core/src/config/index.ts +0 -12
  67. package/packages/core/src/config/loader.ts +0 -529
  68. package/packages/core/src/events/agent-bus.ts +0 -460
  69. package/packages/core/src/events/event-bus.ts +0 -169
  70. package/packages/core/src/gateway/helpers/cors.ts +0 -32
  71. package/packages/core/src/gateway/helpers/index.ts +0 -4
  72. package/packages/core/src/gateway/helpers/narration.ts +0 -60
  73. package/packages/core/src/gateway/helpers/path.ts +0 -13
  74. package/packages/core/src/gateway/helpers/redact.ts +0 -61
  75. package/packages/core/src/gateway/index.ts +0 -5
  76. package/packages/core/src/gateway/initializer.ts +0 -332
  77. package/packages/core/src/gateway/lane-queue.ts +0 -169
  78. package/packages/core/src/gateway/resolver.ts +0 -108
  79. package/packages/core/src/gateway/router.ts +0 -124
  80. package/packages/core/src/gateway/routes/agents.ts +0 -187
  81. package/packages/core/src/gateway/routes/channels.ts +0 -203
  82. package/packages/core/src/gateway/routes/chat.ts +0 -241
  83. package/packages/core/src/gateway/routes/config.ts +0 -12
  84. package/packages/core/src/gateway/routes/cron.ts +0 -42
  85. package/packages/core/src/gateway/routes/ethics.ts +0 -46
  86. package/packages/core/src/gateway/routes/mcp.ts +0 -346
  87. package/packages/core/src/gateway/routes/models.ts +0 -93
  88. package/packages/core/src/gateway/routes/projects.ts +0 -179
  89. package/packages/core/src/gateway/routes/providers.ts +0 -192
  90. package/packages/core/src/gateway/routes/setup.ts +0 -267
  91. package/packages/core/src/gateway/routes/skills.ts +0 -70
  92. package/packages/core/src/gateway/routes/system.ts +0 -165
  93. package/packages/core/src/gateway/routes/tasks.ts +0 -44
  94. package/packages/core/src/gateway/routes/tools.ts +0 -35
  95. package/packages/core/src/gateway/routes/users.ts +0 -118
  96. package/packages/core/src/gateway/routes/voice.ts +0 -73
  97. package/packages/core/src/gateway/routes/workspace.ts +0 -281
  98. package/packages/core/src/gateway/server.ts +0 -1978
  99. package/packages/core/src/gateway/session.ts +0 -95
  100. package/packages/core/src/gateway/slash-commands.ts +0 -193
  101. package/packages/core/src/heartbeat/index.ts +0 -157
  102. package/packages/core/src/mcp/hot-reload.ts +0 -213
  103. package/packages/core/src/mcp/singleton.ts +0 -21
  104. package/packages/core/src/memory/index.ts +0 -1
  105. package/packages/core/src/memory/notes.ts +0 -68
  106. package/packages/core/src/plugins/api.ts +0 -128
  107. package/packages/core/src/plugins/index.ts +0 -2
  108. package/packages/core/src/plugins/loader.ts +0 -365
  109. package/packages/core/src/resilience/circuit-breaker.ts +0 -225
  110. package/packages/core/src/security/google-chat.ts +0 -269
  111. package/packages/core/src/security/index.ts +0 -192
  112. package/packages/core/src/security/pairing.ts +0 -250
  113. package/packages/core/src/security/rate-limit.ts +0 -270
  114. package/packages/core/src/security/signal.ts +0 -321
  115. package/packages/core/src/state/store.ts +0 -312
  116. package/packages/core/src/storage/crypto.ts +0 -101
  117. package/packages/core/src/storage/onboarding.ts +0 -1609
  118. package/packages/core/src/storage/schema.ts +0 -567
  119. package/packages/core/src/storage/seed.ts +0 -608
  120. package/packages/core/src/storage/sqlite.ts +0 -363
  121. package/packages/core/src/storage/usage.ts +0 -270
  122. package/packages/core/src/tools/agents/index.ts +0 -607
  123. package/packages/core/src/tools/bridge-events.ts +0 -26
  124. package/packages/core/src/tools/canvas/index.ts +0 -281
  125. package/packages/core/src/tools/cli/index.ts +0 -142
  126. package/packages/core/src/tools/codebridge/index.ts +0 -179
  127. package/packages/core/src/tools/core/index.ts +0 -257
  128. package/packages/core/src/tools/cron/index.ts +0 -373
  129. package/packages/core/src/tools/filesystem/fs-delete.ts +0 -78
  130. package/packages/core/src/tools/filesystem/fs-edit.ts +0 -106
  131. package/packages/core/src/tools/filesystem/fs-exists.ts +0 -63
  132. package/packages/core/src/tools/filesystem/fs-glob.ts +0 -108
  133. package/packages/core/src/tools/filesystem/fs-list.ts +0 -129
  134. package/packages/core/src/tools/filesystem/fs-read.ts +0 -72
  135. package/packages/core/src/tools/filesystem/fs-write.ts +0 -67
  136. package/packages/core/src/tools/filesystem/index.ts +0 -34
  137. package/packages/core/src/tools/filesystem/workspace-guard.ts +0 -62
  138. package/packages/core/src/tools/index.ts +0 -197
  139. package/packages/core/src/tools/projects/index.ts +0 -37
  140. package/packages/core/src/tools/projects/project-create.ts +0 -94
  141. package/packages/core/src/tools/projects/project-done.ts +0 -66
  142. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  143. package/packages/core/src/tools/projects/project-list.ts +0 -96
  144. package/packages/core/src/tools/projects/project-update.ts +0 -72
  145. package/packages/core/src/tools/projects/task-create.ts +0 -68
  146. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  147. package/packages/core/src/tools/projects/task-update.ts +0 -93
  148. package/packages/core/src/tools/search-knowledge/search-knowledge.ts +0 -155
  149. package/packages/core/src/tools/types.ts +0 -39
  150. package/packages/core/src/tools/voice/index.ts +0 -104
  151. package/packages/core/src/tools/web/browser-click.ts +0 -54
  152. package/packages/core/src/tools/web/browser-navigate.ts +0 -84
  153. package/packages/core/src/tools/web/browser-screenshot.ts +0 -54
  154. package/packages/core/src/tools/web/browser-type.ts +0 -60
  155. package/packages/core/src/tools/web/index.ts +0 -31
  156. package/packages/core/src/tools/web/web-fetch.ts +0 -78
  157. package/packages/core/src/tools/web/web-search.ts +0 -123
  158. package/packages/core/src/utils/benchmark.ts +0 -80
  159. package/packages/core/src/utils/crypto.ts +0 -73
  160. package/packages/core/src/utils/date.ts +0 -42
  161. package/packages/core/src/utils/index.ts +0 -5
  162. package/packages/core/src/utils/logger.ts +0 -389
  163. package/packages/core/src/utils/retry.ts +0 -70
  164. package/packages/core/src/utils/toon.ts +0 -356
  165. package/packages/core/src/voice/index.ts +0 -583
  166. package/packages/hive-ui/README.md +0 -52
  167. package/packages/hive-ui/components.json +0 -20
  168. package/packages/hive-ui/index.html +0 -30
  169. package/packages/hive-ui/package.json +0 -90
  170. package/packages/hive-ui/public/favicon.ico +0 -0
  171. package/packages/hive-ui/public/placeholder.svg +0 -1
  172. package/packages/hive-ui/src/App.tsx +0 -115
  173. package/packages/hive-ui/src/components/CronJobsPanel.tsx +0 -200
  174. package/packages/hive-ui/src/components/NavLink.tsx +0 -34
  175. package/packages/hive-ui/src/components/NotesPanel.tsx +0 -79
  176. package/packages/hive-ui/src/components/SystemMonitor.tsx +0 -270
  177. package/packages/hive-ui/src/components/UsageStatsPanel.tsx +0 -334
  178. package/packages/hive-ui/src/components/WelcomeDialog.tsx +0 -279
  179. package/packages/hive-ui/src/components/ui/accordion.tsx +0 -52
  180. package/packages/hive-ui/src/components/ui/alert-dialog.tsx +0 -104
  181. package/packages/hive-ui/src/components/ui/alert.tsx +0 -45
  182. package/packages/hive-ui/src/components/ui/aspect-ratio.tsx +0 -5
  183. package/packages/hive-ui/src/components/ui/avatar.tsx +0 -38
  184. package/packages/hive-ui/src/components/ui/badge.tsx +0 -29
  185. package/packages/hive-ui/src/components/ui/bee-loader.tsx +0 -68
  186. package/packages/hive-ui/src/components/ui/breadcrumb.tsx +0 -90
  187. package/packages/hive-ui/src/components/ui/button.tsx +0 -47
  188. package/packages/hive-ui/src/components/ui/calendar.tsx +0 -54
  189. package/packages/hive-ui/src/components/ui/card.tsx +0 -45
  190. package/packages/hive-ui/src/components/ui/carousel.tsx +0 -224
  191. package/packages/hive-ui/src/components/ui/chart.tsx +0 -303
  192. package/packages/hive-ui/src/components/ui/checkbox.tsx +0 -26
  193. package/packages/hive-ui/src/components/ui/collapsible.tsx +0 -9
  194. package/packages/hive-ui/src/components/ui/command.tsx +0 -133
  195. package/packages/hive-ui/src/components/ui/context-menu.tsx +0 -178
  196. package/packages/hive-ui/src/components/ui/dialog.tsx +0 -95
  197. package/packages/hive-ui/src/components/ui/drawer.tsx +0 -87
  198. package/packages/hive-ui/src/components/ui/dropdown-menu.tsx +0 -179
  199. package/packages/hive-ui/src/components/ui/form.tsx +0 -129
  200. package/packages/hive-ui/src/components/ui/hover-card.tsx +0 -27
  201. package/packages/hive-ui/src/components/ui/input-otp.tsx +0 -61
  202. package/packages/hive-ui/src/components/ui/input.tsx +0 -22
  203. package/packages/hive-ui/src/components/ui/label.tsx +0 -17
  204. package/packages/hive-ui/src/components/ui/menubar.tsx +0 -207
  205. package/packages/hive-ui/src/components/ui/navigation-menu.tsx +0 -120
  206. package/packages/hive-ui/src/components/ui/pagination.tsx +0 -80
  207. package/packages/hive-ui/src/components/ui/popover.tsx +0 -29
  208. package/packages/hive-ui/src/components/ui/progress.tsx +0 -23
  209. package/packages/hive-ui/src/components/ui/radio-group.tsx +0 -36
  210. package/packages/hive-ui/src/components/ui/resizable.tsx +0 -37
  211. package/packages/hive-ui/src/components/ui/scroll-area.tsx +0 -38
  212. package/packages/hive-ui/src/components/ui/select.tsx +0 -143
  213. package/packages/hive-ui/src/components/ui/separator.tsx +0 -20
  214. package/packages/hive-ui/src/components/ui/sheet.tsx +0 -107
  215. package/packages/hive-ui/src/components/ui/sidebar.tsx +0 -636
  216. package/packages/hive-ui/src/components/ui/skeleton.tsx +0 -7
  217. package/packages/hive-ui/src/components/ui/slider.tsx +0 -23
  218. package/packages/hive-ui/src/components/ui/sonner.tsx +0 -27
  219. package/packages/hive-ui/src/components/ui/switch.tsx +0 -27
  220. package/packages/hive-ui/src/components/ui/table.tsx +0 -72
  221. package/packages/hive-ui/src/components/ui/tabs.tsx +0 -53
  222. package/packages/hive-ui/src/components/ui/textarea.tsx +0 -21
  223. package/packages/hive-ui/src/components/ui/toast.tsx +0 -111
  224. package/packages/hive-ui/src/components/ui/toaster.tsx +0 -24
  225. package/packages/hive-ui/src/components/ui/toggle-group.tsx +0 -49
  226. package/packages/hive-ui/src/components/ui/toggle.tsx +0 -37
  227. package/packages/hive-ui/src/components/ui/tooltip.tsx +0 -28
  228. package/packages/hive-ui/src/components/ui/use-toast.ts +0 -3
  229. package/packages/hive-ui/src/hooks/use-mobile.tsx +0 -19
  230. package/packages/hive-ui/src/hooks/use-toast.ts +0 -186
  231. package/packages/hive-ui/src/hooks/useAgentConfig.ts +0 -25
  232. package/packages/hive-ui/src/hooks/useAgents.ts +0 -38
  233. package/packages/hive-ui/src/hooks/useBridge.ts +0 -38
  234. package/packages/hive-ui/src/hooks/useCanvas.ts +0 -24
  235. package/packages/hive-ui/src/hooks/useChannels.ts +0 -2
  236. package/packages/hive-ui/src/hooks/useEthics.ts +0 -51
  237. package/packages/hive-ui/src/hooks/useProviders.ts +0 -14
  238. package/packages/hive-ui/src/hooks/useTheme.ts +0 -29
  239. package/packages/hive-ui/src/hooks/useUserConfig.ts +0 -17
  240. package/packages/hive-ui/src/hooks/useWebSocket.ts +0 -12
  241. package/packages/hive-ui/src/index.css +0 -620
  242. package/packages/hive-ui/src/lib/api.ts +0 -100
  243. package/packages/hive-ui/src/lib/constants.ts +0 -6
  244. package/packages/hive-ui/src/lib/models.ts +0 -64
  245. package/packages/hive-ui/src/lib/swal.ts +0 -30
  246. package/packages/hive-ui/src/lib/utils.ts +0 -6
  247. package/packages/hive-ui/src/lib/websocket.ts +0 -7
  248. package/packages/hive-ui/src/main.tsx +0 -5
  249. package/packages/hive-ui/src/modules/agent-config/details/AgentDetailsEditor.tsx +0 -524
  250. package/packages/hive-ui/src/modules/agent-config/ethics/EthicsConflictDetector.tsx +0 -18
  251. package/packages/hive-ui/src/modules/agent-config/ethics/EthicsEditor.tsx +0 -19
  252. package/packages/hive-ui/src/modules/agent-config/ethics/EthicsRulesList.tsx +0 -36
  253. package/packages/hive-ui/src/modules/agent-config/ethics/EthicsTemplateGallery.tsx +0 -361
  254. package/packages/hive-ui/src/modules/agent-config/ethics/index.ts +0 -4
  255. package/packages/hive-ui/src/modules/agent-config/index.ts +0 -6
  256. package/packages/hive-ui/src/modules/agent-config/mcp/MCPServerAdd.tsx +0 -322
  257. package/packages/hive-ui/src/modules/agent-config/mcp/MCPServerCard.tsx +0 -93
  258. package/packages/hive-ui/src/modules/agent-config/mcp/MCPServerConfig.tsx +0 -427
  259. package/packages/hive-ui/src/modules/agent-config/mcp/MCPServerList.tsx +0 -85
  260. package/packages/hive-ui/src/modules/agent-config/mcp/MCPToolExplorer.tsx +0 -79
  261. package/packages/hive-ui/src/modules/agent-config/mcp/index.ts +0 -5
  262. package/packages/hive-ui/src/modules/agent-config/shared/ConfigEditorLayout.tsx +0 -30
  263. package/packages/hive-ui/src/modules/agent-config/shared/ConfigExporter.tsx +0 -26
  264. package/packages/hive-ui/src/modules/agent-config/shared/ConfigImporter.tsx +0 -25
  265. package/packages/hive-ui/src/modules/agent-config/shared/DiffViewer.tsx +0 -31
  266. package/packages/hive-ui/src/modules/agent-config/shared/MarkdownEditor.tsx +0 -32
  267. package/packages/hive-ui/src/modules/agent-config/shared/SaveStatusIndicator.tsx +0 -23
  268. package/packages/hive-ui/src/modules/agent-config/shared/ValidationPanel.tsx +0 -36
  269. package/packages/hive-ui/src/modules/agent-config/shared/index.ts +0 -7
  270. package/packages/hive-ui/src/modules/agent-config/skills/SkillCard.tsx +0 -81
  271. package/packages/hive-ui/src/modules/agent-config/skills/SkillConfigEditor.tsx +0 -22
  272. package/packages/hive-ui/src/modules/agent-config/skills/SkillCreator.tsx +0 -60
  273. package/packages/hive-ui/src/modules/agent-config/skills/SkillInstaller.tsx +0 -23
  274. package/packages/hive-ui/src/modules/agent-config/skills/SkillList.tsx +0 -72
  275. package/packages/hive-ui/src/modules/agent-config/skills/SkillsTab.tsx +0 -202
  276. package/packages/hive-ui/src/modules/agent-config/skills/index.ts +0 -5
  277. package/packages/hive-ui/src/modules/agent-config/tools/ToolCard.tsx +0 -27
  278. package/packages/hive-ui/src/modules/agent-config/tools/ToolConfigPanel.tsx +0 -22
  279. package/packages/hive-ui/src/modules/agent-config/tools/ToolManager.tsx +0 -266
  280. package/packages/hive-ui/src/modules/agent-config/tools/ToolPermissions.tsx +0 -287
  281. package/packages/hive-ui/src/modules/agent-config/tools/ToolRegistry.tsx +0 -84
  282. package/packages/hive-ui/src/modules/agent-config/tools/ToolUsageStats.tsx +0 -52
  283. package/packages/hive-ui/src/modules/agent-config/tools/index.ts +0 -4
  284. package/packages/hive-ui/src/modules/agent-config/user/ActiveAgentsList.tsx +0 -109
  285. package/packages/hive-ui/src/modules/agent-config/user/GlobalConfigOverview.tsx +0 -119
  286. package/packages/hive-ui/src/modules/agent-config/user/UserMemoryManager.tsx +0 -54
  287. package/packages/hive-ui/src/modules/agent-config/user/UserPreferencesForm.tsx +0 -163
  288. package/packages/hive-ui/src/modules/agent-config/user/UserProfileEditor.tsx +0 -261
  289. package/packages/hive-ui/src/modules/agent-config/user/index.ts +0 -3
  290. package/packages/hive-ui/src/modules/agents/AgentActivityLog.tsx +0 -25
  291. package/packages/hive-ui/src/modules/agents/AgentCard.tsx +0 -305
  292. package/packages/hive-ui/src/modules/agents/AgentCreateForm.tsx +0 -446
  293. package/packages/hive-ui/src/modules/agents/AgentDetail.tsx +0 -28
  294. package/packages/hive-ui/src/modules/agents/AgentInternalCard.tsx +0 -162
  295. package/packages/hive-ui/src/modules/agents/AgentList.tsx +0 -29
  296. package/packages/hive-ui/src/modules/agents/AgentStatusBadge.tsx +0 -34
  297. package/packages/hive-ui/src/modules/agents/ModelSelector.tsx +0 -151
  298. package/packages/hive-ui/src/modules/bridge/BridgeLogViewer.tsx +0 -61
  299. package/packages/hive-ui/src/modules/bridge/BridgeProcessList.tsx +0 -77
  300. package/packages/hive-ui/src/modules/bridge/BridgeStatus.tsx +0 -23
  301. package/packages/hive-ui/src/modules/bridge/BridgeTerminal.tsx +0 -7
  302. package/packages/hive-ui/src/modules/canvas/CanvasButton.tsx +0 -3
  303. package/packages/hive-ui/src/modules/canvas/CanvasChart.tsx +0 -3
  304. package/packages/hive-ui/src/modules/canvas/CanvasComponentMap.tsx +0 -605
  305. package/packages/hive-ui/src/modules/canvas/CanvasContainer.tsx +0 -360
  306. package/packages/hive-ui/src/modules/canvas/CanvasForm.tsx +0 -3
  307. package/packages/hive-ui/src/modules/canvas/CanvasMarkdown.tsx +0 -3
  308. package/packages/hive-ui/src/modules/canvas/CanvasTable.tsx +0 -3
  309. package/packages/hive-ui/src/modules/canvas/ComponentRenderer.tsx +0 -30
  310. package/packages/hive-ui/src/modules/canvas/DynamicRenderer.tsx +0 -3
  311. package/packages/hive-ui/src/modules/channels/available/AvailableChannelsGrid.tsx +0 -89
  312. package/packages/hive-ui/src/modules/channels/available/ChannelAuthForm.tsx +0 -33
  313. package/packages/hive-ui/src/modules/channels/available/ChannelSetupWizard.tsx +0 -48
  314. package/packages/hive-ui/src/modules/channels/available/ChannelTestConnection.tsx +0 -37
  315. package/packages/hive-ui/src/modules/channels/available/ChannelTypeCard.tsx +0 -30
  316. package/packages/hive-ui/src/modules/channels/available/ChannelWebhookConfig.tsx +0 -30
  317. package/packages/hive-ui/src/modules/channels/available/index.ts +0 -6
  318. package/packages/hive-ui/src/modules/channels/connected/ChannelCard.tsx +0 -95
  319. package/packages/hive-ui/src/modules/channels/connected/ChannelConfigPanel.tsx +0 -260
  320. package/packages/hive-ui/src/modules/channels/connected/ChannelDisconnectButton.tsx +0 -21
  321. package/packages/hive-ui/src/modules/channels/connected/ChannelLogsViewer.tsx +0 -42
  322. package/packages/hive-ui/src/modules/channels/connected/ChannelQRCode.tsx +0 -32
  323. package/packages/hive-ui/src/modules/channels/connected/ChannelReconnectButton.tsx +0 -16
  324. package/packages/hive-ui/src/modules/channels/connected/ChannelStatusBadge.tsx +0 -26
  325. package/packages/hive-ui/src/modules/channels/connected/ConnectedChannelsList.tsx +0 -40
  326. package/packages/hive-ui/src/modules/channels/connected/index.ts +0 -8
  327. package/packages/hive-ui/src/modules/channels/shared/ChannelCard.tsx +0 -84
  328. package/packages/hive-ui/src/modules/channels/shared/ChannelConfigDialog.tsx +0 -279
  329. package/packages/hive-ui/src/modules/channels/shared/ChannelIcon.tsx +0 -40
  330. package/packages/hive-ui/src/modules/channels/shared/ChannelStats.tsx +0 -37
  331. package/packages/hive-ui/src/modules/channels/shared/ChannelTypeBadge.tsx +0 -23
  332. package/packages/hive-ui/src/modules/channels/shared/ConnectionHealthIndicator.tsx +0 -20
  333. package/packages/hive-ui/src/modules/channels/shared/MessagePreview.tsx +0 -19
  334. package/packages/hive-ui/src/modules/channels/shared/index.ts +0 -5
  335. package/packages/hive-ui/src/modules/chat/ChatContainer.tsx +0 -268
  336. package/packages/hive-ui/src/modules/chat/ChatHistory.tsx +0 -101
  337. package/packages/hive-ui/src/modules/chat/ChatInput.tsx +0 -108
  338. package/packages/hive-ui/src/modules/chat/ChatMessage.tsx +0 -137
  339. package/packages/hive-ui/src/modules/chat/ThinkingIndicator.tsx +0 -10
  340. package/packages/hive-ui/src/modules/layout/AppLayout.tsx +0 -45
  341. package/packages/hive-ui/src/modules/layout/ConnectionStatus.tsx +0 -19
  342. package/packages/hive-ui/src/modules/layout/Header.tsx +0 -20
  343. package/packages/hive-ui/src/modules/layout/HiveSidebar.tsx +0 -173
  344. package/packages/hive-ui/src/modules/layout/ThemeToggle.tsx +0 -18
  345. package/packages/hive-ui/src/modules/providers/ProviderCard.tsx +0 -319
  346. package/packages/hive-ui/src/modules/providers/ProviderConfigForm.tsx +0 -146
  347. package/packages/hive-ui/src/modules/providers/ProviderFailoverConfig.tsx +0 -110
  348. package/packages/hive-ui/src/modules/providers/ProviderList.tsx +0 -33
  349. package/packages/hive-ui/src/modules/providers/ProviderStatusIndicator.tsx +0 -23
  350. package/packages/hive-ui/src/modules/providers/configs/ProviderAPIKeyManager.tsx +0 -39
  351. package/packages/hive-ui/src/modules/providers/configs/ProviderEndpointConfig.tsx +0 -27
  352. package/packages/hive-ui/src/modules/providers/configs/ProviderRateLimits.tsx +0 -37
  353. package/packages/hive-ui/src/modules/providers/configs/ProviderRetryPolicy.tsx +0 -46
  354. package/packages/hive-ui/src/modules/providers/configs/index.ts +0 -4
  355. package/packages/hive-ui/src/modules/providers/index.ts +0 -5
  356. package/packages/hive-ui/src/modules/providers/models/ModelBenchmarkBadge.tsx +0 -21
  357. package/packages/hive-ui/src/modules/providers/models/ModelCapabilities.tsx +0 -44
  358. package/packages/hive-ui/src/modules/providers/models/ModelCard.tsx +0 -36
  359. package/packages/hive-ui/src/modules/providers/models/ModelComparisonTable.tsx +0 -47
  360. package/packages/hive-ui/src/modules/providers/models/ModelList.tsx +0 -51
  361. package/packages/hive-ui/src/modules/providers/models/ModelPricingInfo.tsx +0 -17
  362. package/packages/hive-ui/src/modules/providers/models/ModelSelector.tsx +0 -32
  363. package/packages/hive-ui/src/modules/providers/models/index.ts +0 -7
  364. package/packages/hive-ui/src/pages/AgentDetailPage.tsx +0 -74
  365. package/packages/hive-ui/src/pages/AgentNewPage.tsx +0 -5
  366. package/packages/hive-ui/src/pages/AgentsPage.tsx +0 -147
  367. package/packages/hive-ui/src/pages/BridgePage.tsx +0 -83
  368. package/packages/hive-ui/src/pages/CanvasPage.tsx +0 -32
  369. package/packages/hive-ui/src/pages/ChannelsPage.tsx +0 -176
  370. package/packages/hive-ui/src/pages/DashboardPage.tsx +0 -321
  371. package/packages/hive-ui/src/pages/Index.tsx +0 -14
  372. package/packages/hive-ui/src/pages/LogsPage.tsx +0 -252
  373. package/packages/hive-ui/src/pages/NotFound.tsx +0 -24
  374. package/packages/hive-ui/src/pages/ProjectsPage.tsx +0 -241
  375. package/packages/hive-ui/src/pages/ProvidersPage.tsx +0 -111
  376. package/packages/hive-ui/src/pages/SettingsPage.tsx +0 -147
  377. package/packages/hive-ui/src/pages/SetupPage.tsx +0 -1177
  378. package/packages/hive-ui/src/pages/WebChatPage.tsx +0 -15
  379. package/packages/hive-ui/src/stores/agentConfigStore.ts +0 -32
  380. package/packages/hive-ui/src/stores/agentStore.ts +0 -5
  381. package/packages/hive-ui/src/stores/bridgeStore.ts +0 -237
  382. package/packages/hive-ui/src/stores/canvasStore.ts +0 -250
  383. package/packages/hive-ui/src/stores/channelStore.ts +0 -5
  384. package/packages/hive-ui/src/stores/chatStore.ts +0 -42
  385. package/packages/hive-ui/src/stores/ethicsStore.ts +0 -141
  386. package/packages/hive-ui/src/stores/mcpStore.ts +0 -5
  387. package/packages/hive-ui/src/stores/modelStore.ts +0 -2
  388. package/packages/hive-ui/src/stores/projectsStore.ts +0 -141
  389. package/packages/hive-ui/src/stores/providerStore.ts +0 -2
  390. package/packages/hive-ui/src/stores/skillStore.ts +0 -5
  391. package/packages/hive-ui/src/stores/toolStore.ts +0 -5
  392. package/packages/hive-ui/src/stores/useGlobalConfigStore.ts +0 -937
  393. package/packages/hive-ui/src/stores/useLoaderStore.ts +0 -21
  394. package/packages/hive-ui/src/stores/useNotesAndCronsStore.ts +0 -144
  395. package/packages/hive-ui/src/stores/useWebSocketStore.ts +0 -152
  396. package/packages/hive-ui/src/stores/useWelcomeStore.ts +0 -37
  397. package/packages/hive-ui/src/stores/userConfigStore.ts +0 -23
  398. package/packages/hive-ui/src/stores/userStore.ts +0 -82
  399. package/packages/hive-ui/src/test/setup.ts +0 -15
  400. package/packages/hive-ui/src/types/agent-config.ts +0 -33
  401. package/packages/hive-ui/src/types/agent.ts +0 -65
  402. package/packages/hive-ui/src/types/bridge.ts +0 -27
  403. package/packages/hive-ui/src/types/canvas.ts +0 -76
  404. package/packages/hive-ui/src/types/channels.ts +0 -109
  405. package/packages/hive-ui/src/types/chat.ts +0 -25
  406. package/packages/hive-ui/src/types/connections.ts +0 -17
  407. package/packages/hive-ui/src/types/ethics.ts +0 -41
  408. package/packages/hive-ui/src/types/index.ts +0 -15
  409. package/packages/hive-ui/src/types/mcp.ts +0 -36
  410. package/packages/hive-ui/src/types/notes-crons.ts +0 -31
  411. package/packages/hive-ui/src/types/providers.ts +0 -145
  412. package/packages/hive-ui/src/types/skill.ts +0 -12
  413. package/packages/hive-ui/src/types/tool.ts +0 -44
  414. package/packages/hive-ui/src/types/user.ts +0 -26
  415. package/packages/hive-ui/src/types/websocket.ts +0 -14
  416. package/packages/hive-ui/src/vite-env.d.ts +0 -1
  417. package/packages/mcp/package.json +0 -26
  418. package/packages/mcp/src/config.ts +0 -13
  419. package/packages/mcp/src/index.ts +0 -1
  420. package/packages/mcp/src/logger.ts +0 -42
  421. package/packages/mcp/src/manager.ts +0 -439
  422. package/packages/mcp/src/transports/index.ts +0 -67
  423. package/packages/mcp/src/transports/sse.ts +0 -241
  424. package/packages/mcp/src/transports/websocket.ts +0 -159
  425. package/packages/skills/package.json +0 -21
  426. package/packages/skills/src/index.ts +0 -1
  427. package/packages/skills/src/loader.ts +0 -346
@@ -1,1978 +0,0 @@
1
- import type { Config } from "../config/loader";
2
- import { loadConfig, getHiveDir } from "../config/loader";
3
- import { logger, onLogEntry } from "../utils/logger";
4
- import { sessionManager, parseSessionId } from "./session";
5
- import { laneQueue } from "./lane-queue";
6
- import {
7
- type InboundMessage,
8
- type OutboundMessage,
9
- isSlashCommand,
10
- executeSlashCommand,
11
- } from "./slash-commands";
12
- import { ChannelManager } from "../channels/manager";
13
- import { AgentService } from "../agent/service";
14
- import { AgentRunner } from "../agent/providers/index";
15
- import type { IncomingMessage } from "../channels/base";
16
- import { mkdirSync, rmSync, unlinkSync, watch, existsSync } from "node:fs";
17
- import * as path from "node:path";
18
- import { cpus as osCpus } from "node:os";
19
- import { getDb, getDbPathLazy, initializeDatabase } from "../storage/sqlite";
20
- import { getRecentMessages } from "../agent/conversation-store";
21
- import { canvasManager } from "../canvas/canvas-manager.ts";
22
- import { subscribeCanvas, unsubscribeCanvas, emitCanvas, getCanvasSnapshot } from "../canvas/emitter";
23
- import { subscribeBridge, unsubscribeBridge } from "../tools/bridge-events";
24
- import { randomUUID } from "crypto";
25
- import { decryptConfig } from "../storage/crypto.ts";
26
- import { resolveContext } from "./resolver";
27
- import { getUsageStats } from "../storage/usage";
28
- import { voiceService } from "../voice/index";
29
- import { Benchmark } from "../utils/benchmark";
30
- import { initializeGateway, type GatewayInitializationResult } from "./initializer";
31
- import { handleSetupStatus, handleVerifyProvider, handleCompleteSetup } from "./routes/setup";
32
- import { resolveUserId } from "../storage/onboarding";
33
- import { handleGetAgents, handleCreateAgent, handleUpdateAgent } from "./routes/agents";
34
- import { handleGetProviders, handleCreateProvider, handleToggleProvider, handleUpdateProvider, handleSyncProviderModels } from "./routes/providers";
35
- import { handleGetUsers, handleCreateUser, handleUpdateUserSettings, handleGetUserChannels, handleLinkUserChannel } from "./routes/users";
36
- import { handleGetSkills, handleActivateSkill, handleDeleteSkill, handleCreateSkill } from "./routes/skills";
37
- import { handleGetEthics, handleActivateEthics, handleDeleteEthics } from "./routes/ethics";
38
- import { handleGetTools, handleActivateTool } from "./routes/tools";
39
- import { handleGetProjects, handleGetActiveProject, handleCreateProject, handleUpdateProject, handleGetProjectHistory, handleGetProjectDetail, handleGetProjectTasks } from "./routes/projects";
40
- import { handleGetTasks, handleUpdateTask } from "./routes/tasks";
41
- import { handleGetCronJobs, handleGetCronChannels, handleUpdateCronJob } from "./routes/cron";
42
- import { handleGetChannels, handleGetChannelConfig, handleActivateChannel, handleDeactivateChannel, handleCreateChannel, handleGetChannelAccount, handleUpdateChannelAccount, handleDeleteChannelAccount, handleChannelAction, handleUpdateChannelSettings, handleToggleChannel } from "./routes/channels";
43
- import { handleGetMcpServers, handleCreateMcpServer, handleUpdateMcpServer, handleDeleteMcpServer, handleToggleMcpServer, handleGetMCPServerTools, handleToggleMCPTool, handleDeleteMCPTool } from "./routes/mcp";
44
- import { handleGetModels, handleCreateModel, handleToggleModel, handleGetModelsConfig, handleUpdateModelsConfig } from "./routes/models";
45
- import { handleGetVoiceProviders, handleGetConfiguredVoiceProviders, handleTestVoice, handleGetChannelVoice, handleUpdateChannelVoice } from "./routes/voice";
46
- import { handleGetActivityStats, handleGetSystemStats, handleGetUsageStats, handleSystemReload, handleApiReload } from "./routes/system";
47
- import { handleGetChatHistory, handleGetCanvas, handleGetNotes, handleUpdateNote } from "./routes/chat";
48
- import { handleChat as handlePostChat } from "./routes/chat";
49
- import { handleGetConfig } from "./routes/config";
50
- import { handleGetWorkspace, handleUpdateWorkspace, handleValidateWorkspace, handleCreateWorkspace, handleOpenWorkspace } from "./routes/workspace";
51
- import { getNarration, expandPath, addCorsHeaders, redactConfig, CORS_ORIGINS } from "./helpers";
52
- import { initCronScheduler, resolveBestChannel } from "../tools/cron";
53
-
54
- const logSubscribers = new Set<string>();
55
-
56
- // Helpers imported from ./helpers/index.ts
57
- // - getNarration, TOOL_NARRATIONS
58
- // - expandPath
59
- // - addCorsHeaders, CORS_ORIGINS
60
- // - redactConfig, redactValue
61
-
62
- interface WebSocketData {
63
- sessionId: string;
64
- authenticatedAt: number;
65
- }
66
-
67
- export async function startGateway(config: Config): Promise<void> {
68
- const host = config.gateway?.host ?? "127.0.0.1";
69
- const port = config.gateway?.port ?? 18790;
70
- const pidFile = expandPath(config.gateway?.pidFile ?? "~/.hive/gateway.pid");
71
-
72
- // FIX 2 — startTime para calcular uptime en /status y /api/agents
73
- const startTime = Date.now();
74
-
75
- // CPU delta sampling — process.cpuUsage() is cumulative; we diff between calls
76
- const numCores = osCpus().length || 1;
77
- let lastCpuSample = process.cpuUsage();
78
- let lastCpuSampleTime = Date.now();
79
- const log = logger.child("gateway");
80
- const mcpLog = logger.child("mcp:api");
81
-
82
- log.info(`Starting gateway on ${host}:${port}`);
83
-
84
- // ── Inicialización modular con manejo de errores ──────────────────────────
85
- let agent: AgentService;
86
- let runner: AgentRunner;
87
- let channelManager: ChannelManager;
88
- let dbProvider: string;
89
- let dbModel: string;
90
- const agentList = config.agents?.list ?? [];
91
- const defaultAgent = agentList.find((a) => a.default) ?? agentList[0];
92
- const workspacePath = expandPath(defaultAgent?.workspace ?? "~/.hive/workspace");
93
-
94
- // ── Bind port immediately so parent health-check doesn't timeout ──────────
95
- // The full handler is loaded via server.reload() once initialization finishes
96
- let server = Bun.serve<WebSocketData>({
97
- port,
98
- hostname: host,
99
- fetch: (_req) => Response.json({ status: "starting" }),
100
- websocket: { open() { }, message() { }, close() { } },
101
- });
102
- log.info(`Port ${port} bound (initializing gateway...)`);
103
-
104
- // Setup mode: no DB file OR DB exists but has no users (interrupted first run)
105
- let gatewaySetupMode = !existsSync(getDbPathLazy());
106
- if (!gatewaySetupMode) {
107
- try {
108
- initializeDatabase();
109
- const count = (getDb().query("SELECT COUNT(*) as count FROM users").get() as { count: number }).count;
110
- if (count === 0) gatewaySetupMode = true;
111
- } catch {
112
- gatewaySetupMode = true;
113
- }
114
- }
115
-
116
- try {
117
- // Usar el inicializador modular para todos los componentes críticos
118
- const init = await initializeGateway(config, pidFile);
119
-
120
- agent = init.agent;
121
- runner = init.runner;
122
- channelManager = init.channelManager;
123
- dbProvider = init.provider;
124
- dbModel = init.model;
125
-
126
- if (gatewaySetupMode) {
127
- log.info("🎉 Setup mode: gateway running — open http://localhost:" + port + "/setup to configure");
128
- } else {
129
- log.info("✅ Gateway initialization completed successfully");
130
- // ── Initialize Cron Scheduler ──────────────────────────────────────────
131
- initCronScheduler((sessionId, task, jobId, context) => {
132
- agent.emit("cron", sessionId, task, jobId, context);
133
- });
134
- }
135
- } catch (error) {
136
- log.error(`❌ Gateway initialization failed: ${(error as Error).message}`);
137
- log.error("Stack trace:", (error as Error).stack);
138
- process.exit(1);
139
- }
140
-
141
- // Check for insecure binding
142
- if (host === "0.0.0.0" && config.security?.warnOnInsecureConfig !== false) {
143
- log.warn("Gateway binding to 0.0.0.0 exposes server to all network interfaces!");
144
- }
145
-
146
- // ── CRON Handler setup ─────────────────────────────────────────────────────
147
- function prepareTools(agentInstance: AgentService, sessionId: string) {
148
- // Tools are now handled by the native agent-loop internally
149
- return undefined;
150
- }
151
-
152
- if (!gatewaySetupMode) agent.on("cron", async (sessionId: string, task: string, jobId?: string, context?: { fecha_usuario: string; hora_usuario: string }) => {
153
- log.info(`[CRON] Triggered task '${task}' for session ${sessionId}, jobId: ${jobId || 'unknown'} [${context?.fecha_usuario} ${context?.hora_usuario}]`);
154
-
155
- let notifyChannel = "webchat";
156
- let notifySessionId = sessionId;
157
- let taskMessage = task; // Default to task name if no taskMessage found
158
- let projectId: string | null = null;
159
-
160
- if (jobId) {
161
- try {
162
- const db = getDb();
163
- const cronJob = db.query<any, [string]>(
164
- "SELECT user_id, project_id, notify_channel_id, task_config FROM cron_jobs WHERE id = ?"
165
- ).get(jobId) as any;
166
-
167
- if (cronJob) {
168
- projectId = cronJob.project_id || null;
169
-
170
- // Extract taskMessage from task_config if available
171
- const taskConfig = cronJob.task_config ? JSON.parse(cronJob.task_config) : {};
172
- taskMessage = taskConfig.message || task;
173
-
174
- // If linked to a project, activate it
175
- if (projectId) {
176
- const project = db.query<any, [string]>(
177
- "SELECT id, name, status FROM projects WHERE id = ?"
178
- ).get(projectId);
179
- if (project) {
180
- log.info(`[CRON] Activating linked project: ${project.name} (${projectId}) [status: ${project.status} → active]`);
181
- db.query(
182
- "UPDATE projects SET status = 'active', updated_at = unixepoch() WHERE id = ?"
183
- ).run(projectId);
184
- }
185
- }
186
-
187
- // Apply priority chain: explicit job channel → registered identity → webchat
188
- notifyChannel = resolveBestChannel(cronJob.user_id, cronJob.notify_channel_id);
189
-
190
- if (notifyChannel !== "webchat") {
191
- // External channel: need the channel-specific session ID from user_identities
192
- const identity = db.query<{ channel_user_id: string }, [string, string]>(
193
- "SELECT channel_user_id FROM user_identities WHERE user_id = ? AND channel = ? LIMIT 1"
194
- ).get(cronJob.user_id, notifyChannel);
195
- if (identity?.channel_user_id) {
196
- notifySessionId = identity.channel_user_id;
197
- } else {
198
- log.warn(`[CRON] No identity found for channel '${notifyChannel}', falling back to webchat`);
199
- notifyChannel = "webchat";
200
- }
201
- }
202
-
203
- if (notifyChannel === "webchat") {
204
- // Use any active WebChat session; fall back to user_id (works if client connects with that ID)
205
- const webchatChannel = channelManager.getChannel("webchat") as any;
206
- const activeWsSession = webchatChannel?.getAnyActiveSession?.() ?? cronJob.user_id;
207
- notifySessionId = activeWsSession;
208
- log.info(`[CRON] WebChat session resolved: ${notifySessionId}`);
209
- }
210
- }
211
- } catch (e) {
212
- log.warn(`[CRON] Could not fetch notify channel for job ${jobId}: ${(e as Error).message}`);
213
- }
214
- }
215
-
216
- // Use the resolved session for both internal processing and notifications
217
- const activeSessionId = notifySessionId;
218
-
219
- try {
220
- await channelManager.send(notifyChannel, activeSessionId, {
221
- content: `⏰ Ejecutando tarea: ${task}`,
222
- type: "progress"
223
- });
224
- } catch (notifyErr) {
225
- log.warn(`[CRON] Could not send start notification: ${(notifyErr as Error).message}`);
226
- }
227
-
228
- laneQueue.enqueue(activeSessionId, async (_t, signal) => {
229
- if (signal.aborted) return;
230
- try {
231
- // Add cron message as 'user' role so it appears in the chat history
232
- const cronMessage = `[CRON] Scheduled task triggered: ${task}.${projectId ? `\n[project_id: ${projectId}]` : ''}
233
- [fecha_usuario: ${context?.fecha_usuario || ""}]
234
- [hora_usuario: ${context?.hora_usuario || ""}]
235
- Instruction: ${taskMessage}
236
- Please execute it now.`;
237
-
238
- const history = getRecentMessages(activeSessionId, 50);
239
- const messages = [
240
- ...history.map((row) => ({
241
- role: row.role as "user" | "assistant" | "system",
242
- content: row.content,
243
- })),
244
- { role: "user" as const, content: cronMessage }
245
- ];
246
-
247
- const provider = dbProvider;
248
-
249
- log.info(`[CRON] Generating response for session ${activeSessionId}...`);
250
- const response = await runner.generate({
251
- provider: provider as any,
252
- messages,
253
- rawUserMessage: cronMessage,
254
- maxTokens: 4096,
255
- tools: prepareTools(agent, activeSessionId),
256
- maxSteps: 15,
257
- threadId: activeSessionId,
258
- channel: "cron",
259
- onStep: async (step) => {
260
- if (step.type === "text" && step.message) {
261
- const trimmedMessage = (typeof step.message === "string" ? step.message : "").trim();
262
- if (trimmedMessage) {
263
- await channelManager.send(notifyChannel, notifySessionId, {
264
- content: trimmedMessage,
265
- type: "progress"
266
- });
267
- }
268
- }
269
- if (step.type === "tool_result" && step.message) {
270
- try {
271
- const result = JSON.parse(step.message);
272
- if (result._sendToUser || result.status) {
273
- const userMessage = result.message || result.status || step.message;
274
- await channelManager.send(notifyChannel, notifySessionId, {
275
- content: `📊 ${userMessage}`,
276
- type: "progress"
277
- });
278
- }
279
- } catch { }
280
- }
281
- },
282
- });
283
-
284
- const responseContent = response.content?.trim() || "Task completed.";
285
-
286
- // For cron tasks, always send the final response to the notification channel
287
- await channelManager.send(notifyChannel, notifySessionId, { content: responseContent });
288
-
289
- // Also send to WebSocket if the original session is connected
290
- const session = sessionManager.get(sessionId);
291
- if (session?.ws) {
292
- session.ws.send(JSON.stringify({ type: "message", sessionId: sessionId, content: responseContent } as OutboundMessage));
293
- }
294
- } catch (error) {
295
- log.error(`[CRON] Error for session ${sessionId}: ${(error as Error).message}`);
296
- await channelManager.send(notifyChannel, notifySessionId, {
297
- content: `❌ Error en tarea programada: ${(error as Error).message}`,
298
- });
299
- }
300
- });
301
- });
302
-
303
- // Set up hot reload watchers
304
- const watchers: Array<() => void> = [];
305
-
306
- // Note: Context store, Ethics, Agent Loop, LLM runner, and Channel Manager
307
- // are now initialized by initializeGateway() above
308
-
309
- // Handle messages from channels (Telegram, Discord, WhatsApp, Slack)
310
- if (!gatewaySetupMode) channelManager.onMessage(async (message: IncomingMessage) => {
311
- log.info(`📥 Message from ${message.channel}:${message.accountId}`);
312
- log.info(` Session: ${message.sessionId}`);
313
-
314
- const voiceConfig = voiceService.getChannelVoiceConfig(message.channel);
315
- let messageContent = message.content;
316
-
317
- let preferAudioResponse = false;
318
- let inputType: "text" | "audio_transcribed" = "text";
319
- let sttProviderUsed: string | null = null;
320
-
321
- if (voiceConfig.voiceEnabled && message.audio) {
322
- log.info(`🎙️ Voice enabled, processing audio...`);
323
-
324
- if (!voiceConfig.sttProvider) {
325
- log.warn(`⚠️ STT provider not configured for channel ${message.channel}`);
326
- await channelManager.send(message.channel, message.sessionId, {
327
- content: `🎙️ Para usar notas de voz, necesitas configurar el proveedor STT en la configuración del canal. Ve a Configuración > Canales > [Tu canal] y configura "Prov. STT" (ej: groq-whisper o openai)`,
328
- });
329
- return;
330
- }
331
-
332
- try {
333
- const audioInput = voiceService.normalizeAudioFromChannel(message.channel, message.audio);
334
- sttProviderUsed = voiceConfig.sttProvider || "groq-whisper";
335
- messageContent = await voiceService.transcribe(audioInput, sttProviderUsed);
336
- log.info(`📝 Transcribed: ${messageContent.substring(0, 100)}...`);
337
-
338
- inputType = "audio_transcribed";
339
- // If user sent audio and TTS is available, always respond in audio
340
- preferAudioResponse = !!voiceConfig.ttsProvider;
341
-
342
- await channelManager.send(message.channel, message.sessionId, {
343
- content: `🎙️ Transcripción: ${messageContent}`,
344
- type: "message"
345
- });
346
- } catch (error) {
347
- log.error(`❌ Transcription failed: ${(error as Error).message}`);
348
- await channelManager.send(message.channel, message.sessionId, {
349
- content: `Error al transcribir audio: ${(error as Error).message}`,
350
- });
351
- return;
352
- }
353
- }
354
-
355
- log.info(` Content: ${messageContent.substring(0, 150)}${messageContent.length > 150 ? "..." : ""}`);
356
-
357
- const { userId } = resolveContext({
358
- channel: message.channel,
359
- channelUserId: message.sessionId,
360
- });
361
-
362
- const telegramMeta = message.metadata?.telegram as { messageId?: number } | undefined;
363
- const messageId = telegramMeta?.messageId?.toString();
364
- await Promise.all([
365
- channelManager.markAsRead(message.channel, message.sessionId, messageId),
366
- channelManager.startTyping(message.channel, message.sessionId),
367
- ]);
368
-
369
- // unifiedSessionId = userId del onboarding → historial y thread LangGraph unificados
370
- const unifiedSessionId = userId;
371
- // routingSessionId = peerId del canal → para enviar respuestas de vuelta al canal correcto
372
- const routingSessionId = message.sessionId;
373
-
374
- const userMetadata = inputType === "audio_transcribed"
375
- ? { input_type: "audio_transcribed", stt_provider: sttProviderUsed, channel: message.channel }
376
- : { input_type: "text", channel: message.channel };
377
-
378
- // Obtener la zona horaria del usuario para el timestamp exacto
379
- const userRow = getDb()
380
- .query<any, [string]>("SELECT * FROM users WHERE id = ?")
381
- .get(userId);
382
- const userTimezone = userRow?.timezone || "UTC";
383
- const now = new Date();
384
- let exactTime = "";
385
- try {
386
- exactTime = now.toLocaleString("en-US", {
387
- timeZone: userTimezone,
388
- dateStyle: "full",
389
- timeStyle: "long",
390
- });
391
- } catch (e) {
392
- exactTime = now.toISOString();
393
- }
394
- const messageContentWithTime = `[Timestamp: ${exactTime} (${userTimezone})]\n${messageContent}`;
395
-
396
- const messages = [{ role: "user" as const, content: messageContentWithTime }];
397
-
398
- try {
399
- log.info(`🤖 Routing to agent loop...`);
400
-
401
- const response = await runner.generate({
402
- provider: dbProvider as any,
403
- messages,
404
- rawUserMessage: messageContent,
405
- maxTokens: 4096,
406
- tools: prepareTools(agent, unifiedSessionId),
407
- maxSteps: 15,
408
- threadId: unifiedSessionId,
409
- userId,
410
- channel: message.channel,
411
- onStep: async (step) => {
412
- // "text" = el agente narra lo que está pensando/haciendo antes de un tool_call
413
- if (step.type === "text" && step.message) {
414
- const trimmedMessage = (typeof step.message === "string" ? step.message : "").trim();
415
- if (trimmedMessage) {
416
- log.debug(`[NARRATION] ${trimmedMessage.substring(0, 100)}`);
417
- await channelManager.send(message.channel, routingSessionId, {
418
- content: trimmedMessage,
419
- type: "progress",
420
- });
421
- }
422
- return;
423
- }
424
-
425
- // "tool_call" = el agente va a ejecutar una herramienta → narrar al usuario
426
- if (step.type === "tool_call" && step.toolName) {
427
- const narration = getNarration(step.toolName);
428
- log.debug(`[TOOL] ${step.toolName} → "${narration}"`);
429
- await channelManager.send(message.channel, routingSessionId, {
430
- content: narration,
431
- type: "progress",
432
- });
433
- return;
434
- }
435
-
436
- // "tool_result" = resultado de la herramienta
437
- // Solo enviamos al usuario si el resultado lo pide explícitamente
438
- if (step.type === "tool_result" && step.message) {
439
- try {
440
- const result = JSON.parse(step.message);
441
- if (result._sendToUser) {
442
- const userMessage = result.message || result.status || step.message;
443
- await channelManager.send(message.channel, routingSessionId, {
444
- content: userMessage,
445
- type: "progress",
446
- });
447
- }
448
- } catch {
449
- // No es JSON estructurado — no enviamos resultados crudos al usuario
450
- }
451
- return;
452
- }
453
- },
454
- });
455
-
456
- const responseContent = response.content?.trim() || "";
457
- if (!responseContent) {
458
- log.warn(`📤 LLM response: empty — skipping send`);
459
- return;
460
- }
461
- log.info(`📤 LLM response: ${responseContent.substring(0, 100)}${responseContent.length > 100 ? "..." : ""}`);
462
-
463
- const shouldSpeak = preferAudioResponse;
464
- let responseType: "text" | "audio" = "text";
465
- let ttsProviderUsed: string | null = null;
466
- let ttsMimeType: string | null = null;
467
-
468
- if (responseContent) {
469
- if (shouldSpeak) {
470
- if (!voiceConfig.ttsProvider) {
471
- log.warn(`⚠️ TTS provider not configured, user requested audio`);
472
- await channelManager.send(message.channel, routingSessionId, {
473
- content: `${responseContent}\n\n🔊 Para recibir respuestas en audio, configura el proveedor TTS en Configuración > Canales > [Tu canal] (ej: elevenlabs, openai-tts)`
474
- });
475
- } else {
476
- try {
477
- log.info(`🔊 TTS enabled, synthesizing audio...`);
478
- const audioOutput = await voiceService.speak(responseContent, voiceConfig.ttsProvider, voiceConfig.ttsVoiceId || undefined);
479
- ttsProviderUsed = voiceConfig.ttsProvider;
480
- ttsMimeType = audioOutput.mimeType;
481
- responseType = "audio";
482
-
483
- const channel = channelManager.getChannel(message.channel);
484
- if (channel?.sendAudio) {
485
- await channel.sendAudio(routingSessionId, audioOutput.data as Buffer, audioOutput.mimeType);
486
- log.info(`✅ Audio sent to ${routingSessionId}`);
487
- } else {
488
- await channelManager.send(message.channel, routingSessionId, { content: responseContent });
489
- }
490
- } catch (error) {
491
- log.error(`❌ TTS failed: ${(error as Error).message}), sending text instead`);
492
- await channelManager.send(message.channel, routingSessionId, { content: responseContent });
493
- }
494
- }
495
- } else {
496
- await channelManager.send(message.channel, routingSessionId, { content: responseContent });
497
- }
498
- }
499
-
500
- const assistantMetadata = {
501
- response_type: responseType,
502
- tts_provider: ttsProviderUsed,
503
- mime_type: ttsMimeType,
504
- channel: message.channel
505
- };
506
-
507
- await channelManager.stopTyping(message.channel, routingSessionId);
508
- log.info(`✅ Response sent to ${routingSessionId} via ${message.channel}`);
509
- } catch (error) {
510
- await channelManager.stopTyping(message.channel, routingSessionId);
511
- log.error(`❌ Error: ${(error as Error).message} `);
512
- await channelManager.send(message.channel, routingSessionId, {
513
- content: `Error: ${(error as Error).message} `,
514
- });
515
- }
516
- });
517
-
518
- // ── Auth helper ──────────────────────────────────────────────────────────
519
- // En modo desarrollo (HIVE_DEV=true), no requerimos autenticación
520
- const isDev = process.env.HIVE_DEV === "true" || process.env.NODE_ENV === "development";
521
-
522
- function checkAuth(req: Request, url: URL): boolean {
523
- // En modo desarrollo, permitir todo
524
- if (isDev) return true;
525
-
526
- // Read live from env so the token set during setup/complete takes effect immediately
527
- const activeToken = process.env.HIVE_AUTH_TOKEN;
528
- if (!activeToken) return true;
529
- const authHeader = req.headers.get("authorization");
530
- const provided = authHeader?.replace(/^Bearer\s+/i, "") ?? url.searchParams.get("token");
531
- return provided === activeToken;
532
- }
533
-
534
- // Reload with full handler now that initialization is complete
535
- server.reload({
536
- async fetch(req, server) {
537
- const start = Date.now();
538
- const url = new URL(req.url);
539
- const method = req.method;
540
-
541
- const logRequest = (status: number, duration: number) => {
542
- // Skip health checks from spamming logs unless debug
543
- if (url.pathname === "/health" || url.pathname === "/health/") {
544
- log.debug(`${method} ${url.pathname} - ${status} (${duration}ms)`);
545
- } else {
546
- log.info(`${method} ${url.pathname} - ${status} (${duration}ms)`);
547
- }
548
- };
549
-
550
- const handleRequest = async (): Promise<Response | undefined> => {
551
-
552
- // ── CORS preflight ────────────────────────────────────────────────────
553
- if (req.method === "OPTIONS") {
554
- const origin = req.headers.get("Origin");
555
- if (origin && (origin.includes("localhost") || origin.includes("127.0.0.1") || CORS_ORIGINS.some(o => origin.includes(o.replace("http://", ""))))) {
556
- return new Response(null, {
557
- status: 204,
558
- headers: {
559
- "Access-Control-Allow-Origin": origin,
560
- "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
561
- "Access-Control-Allow-Headers": "Content-Type, Authorization, Accept, X-Requested-With",
562
- "Access-Control-Allow-Credentials": "true",
563
- "Access-Control-Max-Age": "86400",
564
- },
565
- });
566
- }
567
- return new Response(null, { status: 204 });
568
- }
569
-
570
- // ── WebSocket upgrade ────────────────────────────────────────────────
571
- if (url.pathname === "/ws" || url.pathname === "/ws/") {
572
- // En modo desarrollo, no requerir autenticación para WebSocket
573
- if (!isDev && !checkAuth(req, url)) {
574
- return new Response("Unauthorized", { status: 401 });
575
- }
576
- const sessionId = url.searchParams.get("session") || resolveUserId({}) || "default";
577
- if (!sessionId) {
578
- return new Response("Missing session or user ID", { status: 400 });
579
- }
580
- const success = server.upgrade(req, {
581
- data: { sessionId, authenticatedAt: Date.now() },
582
- });
583
- if (success) return undefined;
584
- return new Response("WebSocket upgrade failed", { status: 400 });
585
- }
586
-
587
- // ── Bridge Events WebSocket upgrade ────────────────────────────────────
588
- if (url.pathname === "/bridge-events" || url.pathname === "/bridge-events/") {
589
- const sessionId = `bridge:${url.searchParams.get("sessionId") ?? (resolveUserId({}) ?? "default")}`;
590
- const success = server.upgrade(req, { data: { sessionId, authenticatedAt: Date.now() } });
591
- if (success) return undefined;
592
- return new Response("Bridge events WebSocket upgrade failed", { status: 400 });
593
- }
594
-
595
- // ── Canvas WebSocket upgrade ────────────────────────────────────────────
596
- if (url.pathname === "/canvas" || url.pathname === "/canvas/") {
597
- // En modo desarrollo, no requerir autenticación para Canvas WebSocket
598
- let sessionId = url.searchParams.get("sessionId") ?? url.searchParams.get("session");
599
- const defaultUserId = resolveUserId({});
600
- if (!sessionId && defaultUserId) {
601
- sessionId = `canvas:${defaultUserId}`;
602
- }
603
- if (!sessionId) {
604
- return new Response("Missing session or user ID for canvas", { status: 400 });
605
- }
606
- const success = server.upgrade(req, {
607
- data: { sessionId: sessionId.startsWith("canvas:") ? sessionId : `canvas:${sessionId}`, authenticatedAt: Date.now() },
608
- });
609
- if (success) return undefined;
610
- return new Response("Canvas WebSocket upgrade failed", { status: 400 });
611
- }
612
-
613
- // ── Health (must be before UI routing so it works in dev mode too) ───
614
- if (url.pathname === "/health" || url.pathname === "/health/") {
615
- return addCorsHeaders(Response.json({ status: "ok", pid: process.pid }), req);
616
- }
617
-
618
- // ── Dashboard / UI ────────────────────────────────────────────────────
619
- // In development: UI is served by Vite on port 5173, Gateway only handles /api and /ws
620
- // In production: serve static files from packages/hive-ui/dist
621
-
622
- // Check if this is an API or WebSocket request
623
- const isApiRequest = url.pathname.startsWith("/api");
624
- const isWsRequest = url.pathname.startsWith("/ws");
625
- const isUiRequest = url.pathname === "/ui" || url.pathname === "/ui/" || url.pathname.startsWith("/ui/") || url.pathname.startsWith("/ui?");
626
- const isSetupRequest = url.pathname === "/setup" || url.pathname === "/setup/" || url.pathname.startsWith("/setup/") || url.pathname.startsWith("/setup?");
627
-
628
- // In development mode, skip UI handling - Vite handles it directly
629
- // Only serve static files from dist if they exist (production mode)
630
- if (!isApiRequest && !isWsRequest) {
631
- // In development: tell user to use Vite directly
632
- if (isDev) {
633
- return new Response(
634
- "UI not available through Gateway in development.\n\n" +
635
- "Use Vite directly: http://localhost:5173\n",
636
- { status: 404, headers: { "Content-Type": "text/plain" } }
637
- );
638
- }
639
-
640
- // In production: serve from dist folder
641
- // Priority: HIVE_UI_DIR env > ~/.hive/ui > process.cwd()/packages/hive-ui/dist
642
- const uiDirFromEnv = process.env.HIVE_UI_DIR;
643
- const uiDirFromHive = path.join(getHiveDir(), "ui");
644
- const uiDirFromCwd = path.join(process.cwd(), "packages/hive-ui/dist");
645
- const uiDir = uiDirFromEnv
646
- || (existsSync(path.join(uiDirFromHive, "index.html")) ? uiDirFromHive : uiDirFromCwd);
647
- let subPath = url.pathname;
648
-
649
- // Normalize path for /ui routes
650
- if (subPath === "/ui" || subPath === "/ui/") {
651
- subPath = "/index.html";
652
- } else if (subPath.startsWith("/ui/")) {
653
- subPath = subPath.replace(/^\/ui/, "");
654
- if (!subPath) subPath = "/index.html";
655
- } else if (subPath === "/") {
656
- subPath = "/index.html";
657
- }
658
-
659
- // Normalize path for /setup routes
660
- if (subPath === "/setup" || subPath === "/setup/") {
661
- subPath = "/index.html";
662
- } else if (subPath.startsWith("/setup/")) {
663
- subPath = subPath.replace(/^\/setup/, "");
664
- if (!subPath) subPath = "/index.html";
665
- }
666
-
667
- const filePath = path.join(uiDir, subPath);
668
- const uiFile = Bun.file(filePath);
669
- if (await uiFile.exists()) {
670
- return new Response(uiFile);
671
- }
672
-
673
- // If it's a UI route and no dist, show message
674
- if (isUiRequest || isSetupRequest) {
675
- return new Response(
676
- "UI not found.\n\n" +
677
- "Options:\n" +
678
- " 1. Place the UI in ~/.hive/ui/ (copy hive-ui/dist contents there)\n" +
679
- " 2. Set HIVE_UI_DIR=/path/to/ui\n" +
680
- " 3. Build from source: cd packages/hive-ui && bun run build\n",
681
- { status: 404, headers: { "Content-Type": "text/plain" } }
682
- );
683
- }
684
- }
685
-
686
- // Handle /dashboard redirect for backwards compatibility
687
- if (url.pathname.startsWith("/dashboard")) {
688
- const tokenParam = url.searchParams.get("token") ? `? token = ${url.searchParams.get("token")} ` : "";
689
- return Response.redirect(`/ ui${tokenParam} `, 301);
690
- }
691
-
692
- // ── Rutas que requieren autenticación ────────────────────────────────
693
- if (!checkAuth(req, url)) {
694
- log.warn(`[AUTH] Unauthorized request to ${url.pathname} from ${req.headers.get("origin")} `);
695
- return new Response("Unauthorized", { status: 401 });
696
- }
697
-
698
- // ── Setup API ────────────────────────────────────────────────────────
699
- // GET /api/setup/status
700
- if (url.pathname === "/api/setup/status" || url.pathname === "/api/setup/status/") {
701
- return addCorsHeaders(await handleSetupStatus(), req)
702
- }
703
-
704
- // POST /api/setup/verify-provider
705
- if (url.pathname === "/api/setup/verify-provider" && req.method === "POST") {
706
- return addCorsHeaders(await handleVerifyProvider(req), req)
707
- }
708
-
709
- // POST /api/setup/complete
710
- if (url.pathname === "/api/setup/complete" && req.method === "POST") {
711
- return await handleCompleteSetup(req, config, addCorsHeaders)
712
- }
713
-
714
- // ── Status ───────────────────────────────────────────────────────────
715
- if (url.pathname === "/status" || url.pathname === "/status/") {
716
- return addCorsHeaders(new Response(
717
- JSON.stringify({
718
- status: "ok",
719
- version: "0.1.7",
720
- uptime: Math.floor((Date.now() - startTime) / 1000),
721
- gateway: { host, port },
722
- sessions: sessionManager.list().map((s) => ({
723
- id: s.id,
724
- createdAt: s.createdAt,
725
- messageCount: s.messageCount,
726
- })),
727
- channels: channelManager?.listChannels() ?? [],
728
- queue: { activeSessions: 0 },
729
- }),
730
- { headers: { "Content-Type": "application/json", "Cache-Control": "max-age=5" } }
731
- ), req);
732
- }
733
-
734
- // ── Activity Stats ─────────────────────────────────────────────────
735
- if (url.pathname === "/api/activity-stats" || url.pathname === "/api/activity-stats/") {
736
- return await handleGetActivityStats(req, addCorsHeaders)
737
- }
738
-
739
- // ── System Stats ───────────────────────────────────────────────────
740
- if (url.pathname === "/api/system-stats" || url.pathname === "/api/system-stats/") {
741
- return await handleGetSystemStats(req, addCorsHeaders, startTime)
742
- }
743
-
744
- // ── Usage Stats ─────────────────────────────────────────────────────
745
- if (url.pathname === "/api/usage-stats" || url.pathname === "/api/usage-stats/") {
746
- return await handleGetUsageStats(req, addCorsHeaders)
747
- }
748
-
749
- // ── System Reload ─────────────────────────────────────────────────
750
- if (url.pathname === "/api/system/reload" || url.pathname === "/api/system/reload/") {
751
- return await handleSystemReload(req, addCorsHeaders)
752
- }
753
-
754
- // ── Config ─────────────────────────────────────────────────────────
755
- if (url.pathname === "/api/config") {
756
- if (req.method === "GET") {
757
- return await handleGetConfig(req, addCorsHeaders, config);
758
- }
759
- }
760
-
761
- // ── Projects API ─────────────────────────────────────────────────────
762
- if ((url.pathname === "/api/projects" || url.pathname === "/api/projects/") && req.method === "GET") {
763
- return await handleGetProjects(req, addCorsHeaders)
764
- }
765
-
766
- if (url.pathname === "/api/projects/active" && req.method === "GET") {
767
- return await handleGetActiveProject(req, addCorsHeaders)
768
- }
769
-
770
- if (url.pathname === "/api/projects/history" && req.method === "GET") {
771
- return await handleGetProjectHistory(req, addCorsHeaders)
772
- }
773
-
774
- const projectDetailMatch = url.pathname.match(/^\/api\/projects\/([^/]+)$/)
775
- if (projectDetailMatch && req.method === "GET") {
776
- const projectId = projectDetailMatch[1]
777
- return await handleGetProjectDetail(req, addCorsHeaders, projectId)
778
- }
779
-
780
- if (projectDetailMatch && (req.method === "PATCH" || req.method === "PUT")) {
781
- return await handleUpdateProject(req, addCorsHeaders)
782
- }
783
-
784
- const projectTasksMatch = url.pathname.match(/^\/api\/projects\/([^/]+)\/tasks$/)
785
- if (projectTasksMatch && req.method === "GET") {
786
- const projectId = projectTasksMatch[1]
787
- return await handleGetProjectTasks(req, addCorsHeaders, projectId)
788
- }
789
-
790
- // POST /api/projects — crear nuevo proyecto
791
- if (url.pathname === "/api/projects" && req.method === "POST") {
792
- return await handleCreateProject(req, addCorsHeaders)
793
- }
794
-
795
- // ── Tasks API ─────────────────────────────────────────────────────
796
- if ((url.pathname === "/api/tasks" || url.pathname === "/api/tasks/") && req.method === "GET") {
797
- return await handleGetTasks(req, addCorsHeaders)
798
- }
799
-
800
- const taskDetailMatch = url.pathname.match(/^\/api\/tasks\/(\d+)$/)
801
- if (taskDetailMatch && req.method === "PATCH") {
802
- return await handleUpdateTask(req, addCorsHeaders)
803
- }
804
-
805
-
806
-
807
- // ── Channels API ─────────────────────────────────────────────────────
808
- if ((url.pathname === "/api/channels" || url.pathname === "/api/channels/") && req.method === "POST") {
809
- const body = await req.json().catch(() => ({}));
810
- const { name, accountId, config: channelConfigData } = body;
811
- if (!name || !accountId || !channelConfigData) {
812
- return addCorsHeaders(new Response("Missing name, accountId or config", { status: 400 }), req);
813
- }
814
- // Save config to YAML is handled by caller
815
- config.channels = config.channels || {};
816
- config.channels[name] = config.channels[name] || { enabled: true, accounts: {} };
817
- const channelEntry = config.channels[name] as any;
818
- channelEntry.accounts = channelEntry.accounts || {};
819
- channelEntry.accounts[accountId] = channelConfigData;
820
- return await handleCreateChannel(req, addCorsHeaders, channelManager);
821
- }
822
-
823
- const channelDetailMatch = url.pathname.match(/^\/api\/channels\/([^/]+)\/([^/]+)$/);
824
- if (channelDetailMatch) {
825
- const name = channelDetailMatch[1];
826
- const accountId = channelDetailMatch[2];
827
-
828
- if (req.method === "GET") {
829
- return await handleGetChannelAccount(req, addCorsHeaders, name, accountId);
830
- }
831
- if (req.method === "PUT") {
832
- const body = await req.json().catch(() => ({}));
833
- if (!body.config) return new Response("Missing config", { status: 400 });
834
- // Save config to YAML is handled by caller
835
- config.channels = config.channels || {};
836
- config.channels[name] = config.channels[name] || { enabled: true, accounts: {} };
837
- const channelEntry = config.channels[name] as any;
838
- channelEntry.accounts = channelEntry.accounts || {};
839
- channelEntry.accounts[accountId] = body.config;
840
- return await handleUpdateChannelAccount(req, addCorsHeaders, name, accountId, channelManager);
841
- }
842
- if (req.method === "DELETE") {
843
- // Config update handled by caller
844
- if (config.channels?.[name]) {
845
- const channelEntry = config.channels[name] as any;
846
- if (channelEntry.accounts) {
847
- delete channelEntry.accounts[accountId];
848
- if (Object.keys(channelEntry.accounts).length === 0) {
849
- delete config.channels[name];
850
- }
851
- }
852
- }
853
- return await handleDeleteChannelAccount(req, addCorsHeaders, name, accountId, config, channelManager);
854
- }
855
- }
856
-
857
- const channelActionMatch = url.pathname.match(
858
- /^\/api\/channels\/([^/]+)\/([^/]+)\/(start|stop)$/
859
- );
860
- if (channelActionMatch) {
861
- const [, name, accountId, action] = channelActionMatch;
862
- if (req.method === "POST") {
863
- return await handleChannelAction(req, addCorsHeaders, name, accountId, action as "start" | "stop", channelManager);
864
- }
865
- }
866
-
867
- // ── Skills API ───────────────────────────────────────────────────────
868
- if ((url.pathname === "/api/skills" || url.pathname === "/api/skills/") && req.method === "POST") {
869
- return await handleCreateSkill(req, addCorsHeaders);
870
- }
871
-
872
- // ── Model Config API ─────────────────────────────────────────────────
873
- if (url.pathname === "/api/config/models") {
874
- if (req.method === "GET") {
875
- return await handleGetModelsConfig(req, addCorsHeaders, config);
876
- }
877
- if (req.method === "POST") {
878
- return await handleUpdateModelsConfig(req, addCorsHeaders, config, agent);
879
- }
880
- }
881
-
882
- // ── MCP API ──────────────────────────────────────────────────────────
883
- // Note: Full MCP route handlers are in routes/mcp.ts
884
- if (url.pathname === "/api/mcp/servers" && req.method === "GET") {
885
- const mcpManager = agent.getMCPManager()
886
- if (!mcpManager) {
887
- log.warn(`[GET /api/mcp/servers] MCP Manager is null!`)
888
- } else {
889
- log.info(`[GET /api/mcp/servers] MCP Manager found`)
890
- }
891
- return await handleGetMcpServers(req, addCorsHeaders, mcpManager)
892
- }
893
-
894
- if (url.pathname === "/api/mcp/servers" && req.method === "POST") {
895
- const response = await handleCreateMcpServer(req, addCorsHeaders)
896
-
897
- // Hot reload will auto-connect the server within 2 seconds
898
- // No manual connection needed
899
-
900
- return response
901
- }
902
-
903
- if (url.pathname === "/api/mcp/servers" && req.method === "PUT") {
904
- return await handleUpdateMcpServer(req, addCorsHeaders)
905
- }
906
-
907
- if (url.pathname === "/api/mcp/servers" && req.method === "DELETE") {
908
- return await handleDeleteMcpServer(req, addCorsHeaders)
909
- }
910
-
911
- if (url.pathname.match(/^\/api\/mcp\/servers\/[^/]+\/toggle$/)) {
912
- const mcpId = url.pathname.split("/")[4];
913
- if (req.method === "POST") {
914
- return await handleToggleMcpServer(req, addCorsHeaders, mcpId)
915
- }
916
- }
917
-
918
- // ── Workspace API ────────────────────────────────────────────────────
919
- // Validate workspace path
920
- if (url.pathname === "/api/workspace/validate" && req.method === "POST") {
921
- return await handleValidateWorkspace(req, addCorsHeaders);
922
- }
923
-
924
- // Create workspace directory
925
- if (url.pathname === "/api/workspace/create" && req.method === "POST") {
926
- return await handleCreateWorkspace(req, addCorsHeaders);
927
- }
928
-
929
- // Open workspace in file explorer
930
- if (url.pathname === "/api/workspace/open" && req.method === "GET") {
931
- return await handleOpenWorkspace(req, addCorsHeaders);
932
- }
933
-
934
- // Get/Update workspace files (soul, user, ethics)
935
- for (const wsType of ["soul", "user", "ethics"] as const) {
936
- if (url.pathname === `/api/workspace/${wsType}`) {
937
- if (req.method === "GET") {
938
- return await handleGetWorkspace(req, addCorsHeaders, workspacePath, wsType);
939
- }
940
- if (req.method === "POST") {
941
- const reloadFn = async (type: string) => {
942
- if (type === "soul") agent.reloadSoul();
943
- if (type === "user") agent.reloadUser();
944
- if (type === "ethics") await agent.reloadEthics();
945
- };
946
- return await handleUpdateWorkspace(req, addCorsHeaders, workspacePath, wsType, reloadFn);
947
- }
948
- }
949
- }
950
-
951
- // ── Reload API ───────────────────────────────────────────────────────
952
- if (url.pathname === "/api/reload" && req.method === "POST") {
953
- return await handleApiReload(req, addCorsHeaders, agent);
954
- }
955
-
956
- // ── User Channel Linking API ────────────────────────────────────────────
957
- if (url.pathname === "/api/user/channels" && req.method === "POST") {
958
- return await handleLinkUserChannel(req, addCorsHeaders, config, log);
959
- }
960
-
961
- if (url.pathname === "/api/user/channels" && req.method === "GET") {
962
- return await handleGetUserChannels(req, addCorsHeaders, config);
963
- }
964
-
965
- // ── Agents API ─────────────────────────────────────────────────────
966
- if (url.pathname === "/api/agents" && req.method === "GET") {
967
- return await handleGetAgents(req, addCorsHeaders)
968
- }
969
-
970
- if (url.pathname === "/api/agents" && req.method === "POST") {
971
- return await handleCreateAgent(req, addCorsHeaders)
972
- }
973
-
974
- if (url.pathname.startsWith("/api/agents/") && (req.method === "PATCH" || req.method === "PUT")) {
975
- return await handleUpdateAgent(req, addCorsHeaders)
976
- }
977
-
978
- // ── Providers API ───────────────────────────────────────────────────
979
- if (url.pathname === "/api/providers" && req.method === "GET") {
980
- return await handleGetProviders(req, addCorsHeaders)
981
- }
982
-
983
- if (url.pathname === "/api/providers" && req.method === "POST") {
984
- return await handleCreateProvider(req, addCorsHeaders)
985
- }
986
-
987
- if (url.pathname.match(/^\/api\/providers\/[^/]+\/toggle$/) && req.method === "POST") {
988
- return await handleToggleProvider(req, addCorsHeaders)
989
- }
990
-
991
- const providerIdMatch = url.pathname.match(/^\/api\/providers\/([^/]+)$/)
992
- if (providerIdMatch && (req.method === "PUT" || req.method === "PATCH")) {
993
- return await handleUpdateProvider(req, addCorsHeaders)
994
- }
995
-
996
- // ── Models API ───────────────────────────────────────────────────
997
- // GET /api/models?provider_id=xxx - Get models filtered by provider
998
- if (url.pathname === "/api/models" && req.method === "GET") {
999
- return await handleGetModels(req, addCorsHeaders)
1000
- }
1001
-
1002
- // POST /api/providers/:id/sync-models — sincroniza modelos desde la API local del provider
1003
- const syncModelsMatch = url.pathname.match(/^\/api\/providers\/([^/]+)\/sync-models$/)
1004
- if (syncModelsMatch && req.method === "POST") {
1005
- const providerId = syncModelsMatch[1]
1006
- return await handleSyncProviderModels(req, addCorsHeaders, providerId)
1007
- }
1008
-
1009
- // POST /api/models - Create a new model
1010
- if (url.pathname === "/api/models" && req.method === "POST") {
1011
- return await handleCreateModel(req, addCorsHeaders)
1012
- }
1013
-
1014
- if (url.pathname.match(/^\/api\/models\/[^/]+\/toggle$/) && req.method === "POST") {
1015
- return await handleToggleModel(req, addCorsHeaders)
1016
- }
1017
-
1018
- // ── Channels API ───────────────────────────────────────────────────
1019
- if (url.pathname === "/api/channels" && req.method === "GET") {
1020
- return await handleGetChannels(req, addCorsHeaders)
1021
- }
1022
-
1023
- // ── Skills API ─────────────────────────────────────────────────────
1024
- if (url.pathname === "/api/skills" && req.method === "GET") {
1025
- return await handleGetSkills(req, addCorsHeaders)
1026
- }
1027
-
1028
- if (url.pathname === "/api/skills" && req.method === "POST") {
1029
- const body = await req.json().catch(() => ({}))
1030
- const { name, category, tools, triggers, body: bodyContent } = body
1031
- if (!name) return addCorsHeaders(new Response("Missing name", { status: 400 }), req)
1032
- const id = randomUUID()
1033
- getDb().query(`INSERT INTO skills(id, name, category, tools, triggers, body, version, active) VALUES(?, ?, ?, ?, ?, ?, 1, 1)`).run(id, name, category || "", tools || "", triggers || "", bodyContent || "")
1034
- return addCorsHeaders(Response.json({ success: true, id }), req)
1035
- }
1036
-
1037
- if (url.pathname.match(/^\/api\/skills\/[^/]+\/toggle$/) && req.method === "POST") {
1038
- return await handleActivateSkill(req, addCorsHeaders)
1039
- }
1040
-
1041
- // ── Tools API ────────────────────────────────────────────────────────
1042
- if (url.pathname === "/api/tools" && req.method === "GET") {
1043
- return await handleGetTools(req, addCorsHeaders)
1044
- }
1045
-
1046
- if (url.pathname.match(/^\/api\/tools\/[^/]+\/toggle$/) && req.method === "POST") {
1047
- return await handleActivateTool(req, addCorsHeaders)
1048
- }
1049
-
1050
- // ── Ethics API ──────────────────────────────────────────────────────
1051
- if (url.pathname === "/api/ethics" && req.method === "GET") {
1052
- return await handleGetEthics(req, addCorsHeaders)
1053
- }
1054
-
1055
- if (url.pathname === "/api/ethics" && req.method === "POST") {
1056
- const body = await req.json().catch(() => ({}))
1057
- const { name, description, content, is_default } = body
1058
- if (!name || !content) return addCorsHeaders(Response.json({ success: false, error: "Missing name or content" }, { status: 400 }), req)
1059
- const id = randomUUID()
1060
- getDb().query(`INSERT INTO ethics(id, name, description, content, is_default, enabled, active) VALUES(?, ?, ?, ?, ?, 1, 1)`).run(id, name, description || "", content, is_default ? 1 : 0)
1061
- return addCorsHeaders(Response.json({ success: true, id }), req)
1062
- }
1063
-
1064
- if (url.pathname.match(/^\/api\/ethics\/[^/]+$/) && req.method === "PUT") {
1065
- return await handleActivateEthics(req, addCorsHeaders)
1066
- }
1067
-
1068
- if (url.pathname.match(/^\/api\/ethics\/[^/]+$/) && req.method === "DELETE") {
1069
- return await handleDeleteEthics(req, addCorsHeaders)
1070
- }
1071
-
1072
- // ── Users API ───────────────────────────────────────────────────────
1073
- if (url.pathname === "/api/users" && req.method === "GET") {
1074
- return await handleGetUsers(req, addCorsHeaders)
1075
- }
1076
-
1077
- if (url.pathname === "/api/users" && req.method === "POST") {
1078
- return await handleCreateUser(req, addCorsHeaders)
1079
- }
1080
-
1081
- if (url.pathname === "/api/user/settings" && req.method === "PATCH") {
1082
- return await handleUpdateUserSettings(req, addCorsHeaders)
1083
- }
1084
-
1085
- // ── MCP Servers API ──────────────────────────────────────────────────
1086
- if (url.pathname === "/api/mcp/servers" && req.method === "GET") {
1087
- return await handleGetMcpServers(req, addCorsHeaders, agent.getMCPManager())
1088
- }
1089
-
1090
- // GET /api/mcp/servers/:id/tools - Get tools for a specific MCP server
1091
- if (url.pathname.match(/^\/api\/mcp\/servers\/([^/]+)\/tools$/) && req.method === "GET") {
1092
- const serverId = url.pathname.split("/")[4];
1093
- return await handleGetMCPServerTools(req, addCorsHeaders, serverId)
1094
- }
1095
-
1096
- // POST /api/mcp/tools/:id/toggle - Toggle MCP tool active state
1097
- if (url.pathname.match(/^\/api\/mcp\/tools\/([^/]+)\/toggle$/) && req.method === "POST") {
1098
- const toolId = url.pathname.split("/")[4];
1099
- return await handleToggleMCPTool(req, addCorsHeaders, toolId)
1100
- }
1101
-
1102
- // DELETE /api/mcp/tools/:id - Delete MCP tool
1103
- if (url.pathname.match(/^\/api\/mcp\/tools\/([^/]+)$/) && req.method === "DELETE") {
1104
- const toolId = url.pathname.split("/")[4];
1105
- return await handleDeleteMCPTool(req, addCorsHeaders, toolId)
1106
- }
1107
-
1108
- if (url.pathname.match(/^\/api\/mcp\/servers\/([^/]+)\/toggle$/)) {
1109
- const mcpName = url.pathname.split("/")[4];
1110
- if (req.method === "POST") {
1111
- const body = await req.json().catch(() => ({}))
1112
- // Support both { active: boolean } and { action: "connect"|"disconnect" }
1113
- let active = body.active
1114
- if (active === undefined && body.action !== undefined) {
1115
- active = body.action === "connect"
1116
- }
1117
- if (active === undefined) {
1118
- return addCorsHeaders(Response.json({ success: false, error: "Missing active field" }, { status: 400 }), req)
1119
- }
1120
-
1121
- log.info(`[MCP] Toggle connection for ${mcpName}, active=${active}`)
1122
-
1123
- // Update DB
1124
- getDb().query(`UPDATE mcp_servers SET active = ?, enabled = ? WHERE id = ?`).run(active ? 1 : 0, active ? 1 : 0, mcpName)
1125
-
1126
- // Connect/Disconnect MCP server in real-time (no restart needed)
1127
- try {
1128
- const mcp = agent.getMCPManager();
1129
- if (mcp) {
1130
- log.info(`[MCP] Manager found, connecting ${mcpName}...`)
1131
- if (active) {
1132
- const server = getDb().query(`SELECT * FROM mcp_servers WHERE id = ?`).get(mcpName) as Record<string, any> | undefined;
1133
- if (server) {
1134
- log.info(`[MCP] Server config: transport=${server.transport}, url=${server.url}`)
1135
-
1136
- // Build MCP server config
1137
- const mcpServerConfig: any = {
1138
- transport: server.transport as string,
1139
- command: server.command as string | null,
1140
- args: server.args ? JSON.parse(server.args as string) : [],
1141
- url: server.url as string | null,
1142
- enabled: true,
1143
- }
1144
-
1145
- // Decrypt headers if present
1146
- if (server.headers_encrypted && server.headers_iv) {
1147
- try {
1148
- mcpServerConfig.headers = decryptConfig(server.headers_encrypted, server.headers_iv);
1149
- } catch (e) {
1150
- log.warn(`Failed to decrypt headers for ${mcpName}`);
1151
- }
1152
- }
1153
-
1154
- // Get current MCP config and add/update this server
1155
- const currentConfig = (mcp as any).config || { servers: {} }
1156
- const newServersConfig = { ...currentConfig.servers }
1157
- newServersConfig[mcpName] = mcpServerConfig
1158
-
1159
- // Update MCP Manager config (this will register and auto-connect the server)
1160
- await mcp.updateConfig({
1161
- ...currentConfig,
1162
- servers: newServersConfig,
1163
- });
1164
-
1165
- log.info(`[MCP] Server registered in MCP Manager`)
1166
-
1167
- // Get tools after connection
1168
- const tools = mcp.getServerTools(mcpName) || [];
1169
- log.info(`[MCP] Connected! Tools: ${tools.length}`)
1170
- getDb().query(`UPDATE mcp_servers SET status = ?, tools_count = ? WHERE id = ?`).run("connected", tools.length, mcpName);
1171
- } else {
1172
- log.error(`[MCP] Server not found in DB: ${mcpName}`)
1173
- }
1174
- } else {
1175
- await mcp.disconnectServer(mcpName);
1176
- getDb().query(`UPDATE mcp_servers SET status = ? WHERE id = ?`).run("disconnected", mcpName);
1177
- }
1178
- } else {
1179
- log.error(`[MCP] No MCP Manager found`)
1180
- }
1181
- } catch (error) {
1182
- log.error(`[MCP] Failed to connect ${mcpName}: ${(error as Error).message}`);
1183
- }
1184
-
1185
- return addCorsHeaders(Response.json({ success: true, active, message: active ? "Servidor MCP conectado" : "Servidor MCP desconectado" }), req)
1186
- }
1187
- }
1188
-
1189
- // Support /api/mcp/servers/{name} with POST for connect (frontend uses this)
1190
- if (url.pathname.match(/^\/api\/mcp\/servers\/([^/]+)$/)) {
1191
- const mcpName = url.pathname.split("/")[4];
1192
- if (req.method === "POST") {
1193
- const body = await req.json().catch(() => ({}))
1194
- // Support both { active: boolean } and { action: "connect"|"disconnect" }
1195
- let active = body.active
1196
- if (active === undefined && body.action !== undefined) {
1197
- active = body.action === "connect"
1198
- }
1199
- if (active === undefined) {
1200
- return addCorsHeaders(Response.json({ success: false, error: "Missing active field" }, { status: 400 }), req)
1201
- }
1202
-
1203
- // Update DB
1204
- getDb().query(`UPDATE mcp_servers SET active = ?, enabled = ? WHERE id = ?`).run(active ? 1 : 0, active ? 1 : 0, mcpName)
1205
-
1206
- // Connect/Disconnect MCP server in real-time (no restart needed)
1207
- try {
1208
- const mcp = agent.getMCPManager();
1209
- if (mcp) {
1210
- if (active) {
1211
- const server = getDb().query(`SELECT * FROM mcp_servers WHERE id = ?`).get(mcpName) as Record<string, any> | undefined;
1212
- if (server) {
1213
- log.info(`[MCP] Server config: transport=${server.transport}, url=${server.url}`)
1214
-
1215
- // Build MCP server config
1216
- const mcpServerConfig: any = {
1217
- transport: server.transport as string,
1218
- command: server.command as string | null,
1219
- args: server.args ? JSON.parse(server.args as string) : [],
1220
- url: server.url as string | null,
1221
- enabled: true,
1222
- }
1223
-
1224
- // Decrypt headers if present
1225
- if (server.headers_encrypted && server.headers_iv) {
1226
- try {
1227
- mcpServerConfig.headers = decryptConfig(server.headers_encrypted, server.headers_iv);
1228
- } catch (e) {
1229
- log.warn(`Failed to decrypt headers for ${mcpName}`);
1230
- }
1231
- }
1232
-
1233
- // Get current MCP config and add/update this server
1234
- const currentConfig = (mcp as any).config || { servers: {} }
1235
- const newServersConfig = { ...currentConfig.servers }
1236
- newServersConfig[mcpName] = mcpServerConfig
1237
-
1238
- // Update MCP Manager config (this will register and auto-connect the server)
1239
- await mcp.updateConfig({
1240
- ...currentConfig,
1241
- servers: newServersConfig,
1242
- });
1243
-
1244
- log.info(`[MCP] Server registered in MCP Manager`)
1245
-
1246
- // Get tools after connection
1247
- const tools = mcp.getServerTools(mcpName) || [];
1248
- log.info(`[MCP] Connected! Tools: ${tools.length}`)
1249
-
1250
- // Update DB with status and tools
1251
- getDb().query(`UPDATE mcp_servers SET status = ?, tools_count = ? WHERE id = ?`).run("connected", tools.length, mcpName);
1252
- } else {
1253
- log.error(`[MCP] Server not found in DB: ${mcpName}`)
1254
- }
1255
- } else {
1256
- await mcp.disconnectServer(mcpName);
1257
- getDb().query(`UPDATE mcp_servers SET status = ? WHERE id = ?`).run("disconnected", mcpName);
1258
- }
1259
- }
1260
- } catch (error) {
1261
- log.error(`[MCP] Failed to connect ${mcpName}: ${(error as Error).message}`);
1262
- }
1263
-
1264
- return addCorsHeaders(Response.json({ success: true, active, message: active ? "Servidor MCP conectado" : "Servidor MCP desconectado" }), req)
1265
- }
1266
- }
1267
-
1268
- // ── Channels API ───────────────────────────────────────────────────
1269
- if (url.pathname === "/api/channels" && req.method === "GET") {
1270
- const channels = getDb().query("SELECT id, type, id as account_id, enabled, active, status FROM channels").all();
1271
- return addCorsHeaders(Response.json({ channels }), req);
1272
- }
1273
-
1274
- // PUT /api/channels/:id - Update channel settings
1275
- const channelIdMatch = url.pathname.match(/^\/api\/channels\/([^/]+)$/);
1276
- if (channelIdMatch && req.method === "PUT") {
1277
- const channelId = channelIdMatch[1];
1278
- return await handleUpdateChannelSettings(req, addCorsHeaders, channelId);
1279
- }
1280
-
1281
- if (url.pathname.match(/^\/api\/channels\/[^/]+\/toggle$/)) {
1282
- const channelId = url.pathname.split("/")[3];
1283
- if (req.method === "POST") {
1284
- return await handleToggleChannel(req, addCorsHeaders, channelId);
1285
- }
1286
- }
1287
-
1288
- // ── Voice API ───────────────────────────────────────────────────────
1289
- if (url.pathname === "/api/voice/providers" && req.method === "GET") {
1290
- return await handleGetVoiceProviders(req, addCorsHeaders)
1291
- }
1292
-
1293
- if (url.pathname === "/api/voice/configured-providers" && req.method === "GET") {
1294
- return await handleGetConfiguredVoiceProviders(req, addCorsHeaders)
1295
- }
1296
-
1297
- if (url.pathname === "/api/voice/test" && req.method === "POST") {
1298
- return await handleTestVoice(req, addCorsHeaders)
1299
- }
1300
-
1301
- // GET /api/channels/:id/voice - Get voice config for a channel
1302
- const channelVoiceMatch = url.pathname.match(/^\/api\/channels\/([^/]+)\/voice$/)
1303
- if (channelVoiceMatch && req.method === "GET") {
1304
- const channelId = channelVoiceMatch[1]
1305
- return await handleGetChannelVoice(req, addCorsHeaders, channelId)
1306
- }
1307
-
1308
- // PATCH /api/channels/:id/voice - Update voice config for a channel
1309
- if (channelVoiceMatch && req.method === "PATCH") {
1310
- const channelId = channelVoiceMatch[1]
1311
- return await handleUpdateChannelVoice(req, addCorsHeaders, channelId)
1312
- }
1313
-
1314
- // ── Chat / Canvas / Notes API ───────────────────────────────────────
1315
- if (url.pathname === "/api/chat/history" && req.method === "GET") {
1316
- return await handleGetChatHistory(req, addCorsHeaders)
1317
- }
1318
-
1319
- if (url.pathname === "/api/chat" && req.method === "POST") {
1320
- return await handlePostChat(req, addCorsHeaders)
1321
- }
1322
-
1323
- if (url.pathname === "/api/canvas" && req.method === "GET") {
1324
- return await handleGetCanvas(req, addCorsHeaders)
1325
- }
1326
-
1327
- if (url.pathname === "/api/notes" && req.method === "GET") {
1328
- return await handleGetNotes(req, addCorsHeaders)
1329
- }
1330
-
1331
- // ── Cron Jobs API ───────────────────────────────────────────────────
1332
- if (url.pathname === "/api/cron-jobs" && req.method === "GET") {
1333
- return await handleGetCronJobs(req, addCorsHeaders)
1334
- }
1335
-
1336
- if (url.pathname === "/api/cron-jobs/channels" && req.method === "GET") {
1337
- return await handleGetCronChannels(req, addCorsHeaders)
1338
- }
1339
-
1340
- if (url.pathname.match(/^\/api\/cron-jobs\/[^/]+\/toggle$/) && req.method === "PATCH") {
1341
- return await handleUpdateCronJob(req, addCorsHeaders)
1342
- }
1343
-
1344
- return addCorsHeaders(new Response("Not Found", { status: 404 }), req)
1345
- };
1346
-
1347
- try {
1348
- const response = await handleRequest();
1349
- const duration = Date.now() - start;
1350
- if (response) {
1351
- logRequest(response.status, duration);
1352
- } else {
1353
- // Bun upgrade returns undefined on success
1354
- log.info(`${method} ${url.pathname} - 101 Switching Protocols(${duration}ms)`);
1355
- }
1356
- return response;
1357
- } catch (error) {
1358
- const duration = Date.now() - start;
1359
- log.error(`${method} ${url.pathname} - Internal Error(${duration}ms): ${(error as Error).message} `);
1360
- return addCorsHeaders(Response.json({ success: false, error: (error as Error).message, message: "Error interno del servidor" }, { status: 500 }), req);
1361
- }
1362
- },
1363
-
1364
- websocket: {
1365
- open(ws) {
1366
- const data = ws.data;
1367
- const isCanvas = data.sessionId.startsWith("canvas:");
1368
- const isBridge = data.sessionId.startsWith("bridge:");
1369
-
1370
- if (isBridge) {
1371
- log.info(`Bridge events client connected: ${data.sessionId}`);
1372
- subscribeBridge(ws as any);
1373
- ws.send(JSON.stringify({ type: "bridge:connected", sessionId: data.sessionId }));
1374
- return;
1375
- }
1376
-
1377
- if (isCanvas) {
1378
- log.info(`Canvas session connected: ${data.sessionId} `);
1379
- canvasManager.registerSession(data.sessionId, ws as any);
1380
- subscribeCanvas(ws as any);
1381
- ws.send(JSON.stringify({ type: "canvas:connected", sessionId: data.sessionId }));
1382
- // Send initial snapshot so canvas shows current state
1383
- ws.send(JSON.stringify({ type: "canvas:snapshot", data: getCanvasSnapshot() }));
1384
- return;
1385
- }
1386
-
1387
- log.debug(`WebSocket connected: ${data.sessionId} `);
1388
-
1389
- sessionManager.create(data.sessionId, ws);
1390
-
1391
- const channel = channelManager?.getChannel("webchat") as any;
1392
- if (channel?.registerConnection) channel.registerConnection(ws);
1393
-
1394
- // Send status message
1395
- ws.send(JSON.stringify({
1396
- type: "status",
1397
- sessionId: data.sessionId,
1398
- status: { state: "connected", model: `${dbProvider}/${dbModel}` },
1399
- } as OutboundMessage));
1400
-
1401
- // Send welcome message with real user data
1402
- try {
1403
- const db = getDb();
1404
- const user = db.query("SELECT id, name, language FROM users LIMIT 1").get() as { id: string; name: string; language: string } | undefined;
1405
- const agent = db.query("SELECT id, name, provider_id, model_id FROM agents WHERE role = 'coordinator' LIMIT 1").get() as { id: string; name: string; provider_id: string; model_id: string } | undefined;
1406
-
1407
- // Get channels
1408
- const channels = db.query("SELECT id FROM channels WHERE active = 1").all() as Array<{ id: string }>;
1409
-
1410
- // Get voice config from webchat channel
1411
- const voiceConfig = db.query("SELECT voice_enabled, stt_provider, tts_provider FROM channels WHERE id = 'webchat'").get() as { voice_enabled: number; stt_provider: string; tts_provider: string } | undefined;
1412
-
1413
- // Get code bridge
1414
- const codeBridge = db.query("SELECT id FROM code_bridge WHERE enabled = 1").all() as Array<{ id: string }>;
1415
-
1416
- ws.send(JSON.stringify({
1417
- type: "welcome",
1418
- sessionId: user?.id || data.sessionId,
1419
- user: user ? { id: user.id, name: user.name, language: user.language } : null,
1420
- agent: agent ? { id: agent.id, name: agent.name, provider: agent.provider_id, model: agent.model_id } : null,
1421
- channels: channels.map(c => c.id),
1422
- voice: voiceConfig ? {
1423
- enabled: voiceConfig.voice_enabled === 1,
1424
- sttProvider: voiceConfig.stt_provider,
1425
- ttsProvider: voiceConfig.tts_provider
1426
- } : { enabled: false, sttProvider: null, ttsProvider: null },
1427
- codeBridge: codeBridge.map(cb => cb.id)
1428
- } as OutboundMessage));
1429
- } catch (err) {
1430
- log.error("Error sending welcome message:", err);
1431
- }
1432
- },
1433
-
1434
- async message(ws, message) {
1435
- const data = ws.data;
1436
-
1437
- // Bridge events clients are read-only; ignore any messages they send
1438
- if (data.sessionId.startsWith("bridge:")) return;
1439
-
1440
- let msg: InboundMessage;
1441
- try {
1442
- msg = JSON.parse(message.toString()) as InboundMessage;
1443
- } catch {
1444
- ws.send(JSON.stringify({
1445
- type: "error",
1446
- sessionId: data.sessionId,
1447
- error: "Invalid JSON message",
1448
- } as OutboundMessage));
1449
- return;
1450
- }
1451
-
1452
- msg.sessionId = msg.sessionId ?? data.sessionId;
1453
- sessionManager.touch(msg.sessionId);
1454
-
1455
- if (msg.type === "ping") {
1456
- ws.send(JSON.stringify({ type: "pong", sessionId: msg.sessionId } as OutboundMessage));
1457
- return;
1458
- }
1459
-
1460
- // Canvas subscribe
1461
- if (msg.type === "canvas_subscribe") {
1462
- subscribeCanvas(ws);
1463
- ws.send(JSON.stringify({
1464
- type: "canvas:snapshot",
1465
- data: getCanvasSnapshot(),
1466
- }));
1467
- return;
1468
- }
1469
-
1470
- // Canvas unsubscribe
1471
- if (msg.type === "canvas_unsubscribe") {
1472
- unsubscribeCanvas(ws);
1473
- return;
1474
- }
1475
-
1476
- // Canvas session - handle interactions
1477
- if (data.sessionId.startsWith("canvas:")) {
1478
- canvasManager.handleMessage(data.sessionId, message);
1479
- return;
1480
- }
1481
-
1482
- if (msg.type === "command" || (msg.content && isSlashCommand(msg.content))) {
1483
- const result = await executeSlashCommand(msg.sessionId, msg.content ?? `/${msg.command}`, ws);
1484
- if (result) {
1485
- ws.send(JSON.stringify(result));
1486
- return;
1487
- }
1488
- }
1489
-
1490
- // Logs subscription
1491
- if (msg.type === "logs_subscribe") {
1492
- logSubscribers.add(data.sessionId);
1493
- log.debug(`Session ${data.sessionId} subscribed to logs`);
1494
- return;
1495
- }
1496
-
1497
- if (msg.type === "logs_unsubscribe") {
1498
- logSubscribers.delete(data.sessionId);
1499
- log.debug(`Session ${data.sessionId} unsubscribed from logs`);
1500
- return;
1501
- }
1502
-
1503
- // Handle audio messages from WebChat
1504
- let webchatPreferAudio = false;
1505
- if (msg.type === "audio" && msg.audio) {
1506
- log.info(`WebChat audio from session ${msg.sessionId}`);
1507
-
1508
- const voiceConfig = voiceService.getChannelVoiceConfig("webchat");
1509
-
1510
- if (!voiceConfig.voiceEnabled) {
1511
- ws.send(JSON.stringify({
1512
- type: "error",
1513
- sessionId: msg.sessionId,
1514
- error: "Voice input not enabled for this channel"
1515
- } as OutboundMessage));
1516
- return;
1517
- }
1518
-
1519
- if (!voiceConfig.sttProvider) {
1520
- ws.send(JSON.stringify({
1521
- type: "message",
1522
- sessionId: msg.sessionId,
1523
- content: "🎙️ Para usar notas de voz, configura el proveedor STT en Configuración > Canales > WebChat (ej: groq-whisper)"
1524
- } as OutboundMessage));
1525
- return;
1526
- }
1527
-
1528
- ws.send(JSON.stringify({
1529
- type: "typing",
1530
- isTyping: true,
1531
- sessionId: msg.sessionId,
1532
- } as OutboundMessage));
1533
-
1534
- try {
1535
- const audioInput = { type: "base64" as const, data: msg.audio, mimeType: "audio/webm" };
1536
- const sttProvider = voiceConfig.sttProvider || "groq-whisper";
1537
- const messageContent = await voiceService.transcribe(audioInput, sttProvider);
1538
-
1539
- log.info(`📝 Transcribed: ${messageContent.substring(0, 100)}...`);
1540
-
1541
- webchatPreferAudio = true;
1542
-
1543
- ws.send(JSON.stringify({
1544
- type: "message",
1545
- sessionId: msg.sessionId,
1546
- content: `🎙️ Transcripción: ${messageContent}`
1547
- } as OutboundMessage));
1548
-
1549
- ws.send(JSON.stringify({
1550
- type: "typing",
1551
- isTyping: false,
1552
- sessionId: msg.sessionId,
1553
- } as OutboundMessage));
1554
-
1555
- laneQueue.enqueue(msg.sessionId, async (_task, signal) => {
1556
- if (signal.aborted) {
1557
- ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: msg.sessionId } as OutboundMessage));
1558
- ws.send(JSON.stringify({ type: "error", sessionId: msg.sessionId, error: "Task cancelled" } as OutboundMessage));
1559
- return;
1560
- }
1561
-
1562
- try {
1563
- const unifiedSessionId = msg.sessionId;
1564
- const messages = [{ role: "user" as const, content: messageContent }];
1565
- log.info(`Generating response for session ${unifiedSessionId}...`);
1566
-
1567
- const { userId } = resolveContext({
1568
- channel: "webchat",
1569
- channelUserId: msg.sessionId,
1570
- });
1571
-
1572
- // Streaming: send tokens as they arrive
1573
- let streamedContent = "";
1574
- let messageId = crypto.randomUUID();
1575
-
1576
- const response = await runner.generate({
1577
- provider: dbProvider as any,
1578
- messages,
1579
- maxTokens: 4096,
1580
- tools: prepareTools(agent, unifiedSessionId),
1581
- maxSteps: 15,
1582
- threadId: unifiedSessionId,
1583
- userId,
1584
- onToken: async (token: string) => {
1585
- if (signal.aborted) return;
1586
- streamedContent += token;
1587
- // Send chunk to client
1588
- ws.send(JSON.stringify({
1589
- type: "message",
1590
- id: messageId,
1591
- sessionId: unifiedSessionId,
1592
- content: token,
1593
- isChunk: true,
1594
- isStep: false,
1595
- } as OutboundMessage));
1596
- },
1597
- onStep: async (step) => {
1598
- if (signal.aborted) return;
1599
- if (step.type === "tool_result" && step.message) {
1600
- try {
1601
- const result = JSON.parse(step.message);
1602
- if (result._sendToUser || result.status) {
1603
- const userMessage = result.message || result.status || step.message;
1604
- ws.send(JSON.stringify({
1605
- type: "message",
1606
- sessionId: unifiedSessionId,
1607
- content: `📊 ${userMessage}`,
1608
- isStep: true,
1609
- } as unknown as OutboundMessage));
1610
- return;
1611
- }
1612
- } catch { }
1613
- }
1614
- log.debug(`[TOOL] ${step.type}: ${step.toolName || ""}`);
1615
- },
1616
- });
1617
-
1618
- // Use streamed content from onToken, fallback to response.content
1619
- const content = streamedContent || response.content?.trim() || "";
1620
- log.info(`Response sent to session ${unifiedSessionId} (${content.length} chars)`);
1621
-
1622
- const voiceCfg = voiceService.getChannelVoiceConfig("webchat");
1623
- const shouldSpeak = webchatPreferAudio;
1624
- let responseType: "text" | "audio" = "text";
1625
- let ttsProviderUsed: string | null = null;
1626
- let ttsMimeType: string | null = null;
1627
-
1628
- ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: unifiedSessionId } as OutboundMessage));
1629
-
1630
- // Don't send text message if already streamed (content came via onToken)
1631
- const alreadyStreamed = streamedContent.length > 0;
1632
-
1633
- if (content && !alreadyStreamed) {
1634
- if (shouldSpeak) {
1635
- if (!voiceCfg.ttsProvider) {
1636
- ws.send(JSON.stringify({
1637
- type: "message",
1638
- sessionId: unifiedSessionId,
1639
- content: `${content}\n\n🔊 Para recibir respuestas en audio, configura el proveedor TTS en Configuración > Canales > WebChat (ej: elevenlabs)`,
1640
- isStep: false,
1641
- } as OutboundMessage));
1642
- } else {
1643
- try {
1644
- log.info(`🔊 TTS enabled, synthesizing audio for WebChat...`);
1645
- const audioOutput = await voiceService.speak(content, voiceCfg.ttsProvider, voiceCfg.ttsVoiceId || undefined);
1646
- ttsProviderUsed = voiceCfg.ttsProvider;
1647
- ttsMimeType = audioOutput.mimeType;
1648
- responseType = "audio";
1649
- const base64Audio = (audioOutput.data as Buffer).toString("base64");
1650
- ws.send(JSON.stringify({
1651
- type: "audio",
1652
- sessionId: unifiedSessionId,
1653
- audio: base64Audio,
1654
- content,
1655
- mimeType: audioOutput.mimeType,
1656
- } as OutboundMessage));
1657
- } catch (ttsError) {
1658
- log.error(`TTS failed: ${(ttsError as Error).message}), sending text instead`);
1659
- ws.send(JSON.stringify({ type: "message", sessionId: unifiedSessionId, content, isStep: false } as OutboundMessage));
1660
- }
1661
- }
1662
- } else {
1663
- ws.send(JSON.stringify({ type: "message", sessionId: unifiedSessionId, content, isStep: false } as OutboundMessage));
1664
- }
1665
- }
1666
- } catch (error) {
1667
- ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: msg.sessionId } as OutboundMessage));
1668
- ws.send(JSON.stringify({
1669
- type: "error",
1670
- sessionId: msg.sessionId,
1671
- error: (error as Error).message,
1672
- } as OutboundMessage));
1673
- log.error(`Error for session ${msg.sessionId}: ${(error as Error).message}`);
1674
- }
1675
- });
1676
- } catch (error) {
1677
- ws.send(JSON.stringify({
1678
- type: "typing",
1679
- isTyping: false,
1680
- sessionId: msg.sessionId,
1681
- } as OutboundMessage));
1682
- ws.send(JSON.stringify({
1683
- type: "error",
1684
- sessionId: msg.sessionId,
1685
- error: `Transcription failed: ${(error as Error).message}`
1686
- } as OutboundMessage));
1687
- }
1688
- return;
1689
- }
1690
-
1691
- if (msg.type === "message" && msg.content) {
1692
- log.info(`WebChat message from session ${msg.sessionId}: ${msg.content.substring(0, 100)}`);
1693
-
1694
- // FIX 6 — typing indicator inmediato ANTES de encolar
1695
- // El usuario ve "escribiendo..." de inmediato, no después del queue
1696
- ws.send(JSON.stringify({
1697
- type: "typing",
1698
- isTyping: true,
1699
- sessionId: msg.sessionId,
1700
- } as OutboundMessage));
1701
-
1702
- laneQueue.enqueue(msg.sessionId, async (_task, signal) => {
1703
- if (signal.aborted) {
1704
- ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: msg.sessionId } as OutboundMessage));
1705
- ws.send(JSON.stringify({ type: "error", sessionId: msg.sessionId, error: "Task cancelled" } as OutboundMessage));
1706
- return;
1707
- }
1708
-
1709
- try {
1710
- const unifiedSessionId = msg.sessionId;
1711
- const messages = [{ role: "user" as const, content: msg.content }];
1712
- log.info(`Generating response for session ${unifiedSessionId}...`);
1713
-
1714
- const { userId } = resolveContext({
1715
- channel: "webchat",
1716
- channelUserId: msg.sessionId,
1717
- });
1718
-
1719
- // Streaming: send tokens as they arrive
1720
- let streamedContent = "";
1721
- let messageId = crypto.randomUUID();
1722
-
1723
- const response = await runner.generate({
1724
- provider: dbProvider as any,
1725
- messages,
1726
- maxTokens: 4096,
1727
- tools: prepareTools(agent, unifiedSessionId),
1728
- maxSteps: 15,
1729
- threadId: unifiedSessionId,
1730
- userId,
1731
- onToken: async (token: string) => {
1732
- if (signal.aborted) return;
1733
- streamedContent += token;
1734
- // Send chunk to client
1735
- ws.send(JSON.stringify({
1736
- type: "message",
1737
- id: messageId,
1738
- sessionId: unifiedSessionId,
1739
- content: token,
1740
- isChunk: true,
1741
- isStep: false,
1742
- } as OutboundMessage));
1743
- },
1744
- onStep: async (step) => {
1745
- if (signal.aborted) return;
1746
-
1747
- // Para tool_result, verificar si es un mensaje de progreso
1748
- if (step.type === "tool_result" && step.message) {
1749
- try {
1750
- const result = JSON.parse(step.message);
1751
- if (result._sendToUser || result.status) {
1752
- const userMessage = result.message || result.status || step.message;
1753
- ws.send(JSON.stringify({
1754
- type: "message",
1755
- sessionId: unifiedSessionId,
1756
- content: `📊 ${userMessage}`,
1757
- isStep: true,
1758
- } as unknown as OutboundMessage));
1759
- return;
1760
- }
1761
- } catch {
1762
- // No es JSON de progreso
1763
- }
1764
- }
1765
-
1766
- log.debug(`[TOOL] ${step.type}: ${step.toolName || ""}`);
1767
- },
1768
- });
1769
-
1770
- // Use streamed content from onToken, fallback to response.content
1771
- const content = streamedContent || response.content?.trim() || "";
1772
- log.info(`Response sent to session ${unifiedSessionId} (${content.length} chars)`);
1773
-
1774
- const voiceConfig = voiceService.getChannelVoiceConfig("webchat");
1775
- const shouldSpeak = webchatPreferAudio;
1776
- let responseType: "text" | "audio" = "text";
1777
- let ttsProviderUsed: string | null = null;
1778
- let ttsMimeType: string | null = null;
1779
-
1780
- ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: unifiedSessionId } as OutboundMessage));
1781
-
1782
- // Don't send text message if already streamed (content came via onToken)
1783
- const alreadyStreamed = streamedContent.length > 0;
1784
-
1785
- if (content && !alreadyStreamed) {
1786
- if (shouldSpeak) {
1787
- if (!voiceConfig.ttsProvider) {
1788
- ws.send(JSON.stringify({
1789
- type: "message",
1790
- sessionId: unifiedSessionId,
1791
- content: `${content}\n\n🔊 Para recibir respuestas en audio, configura el proveedor TTS en Configuración > Canales > WebChat (ej: elevenlabs)`,
1792
- isStep: false
1793
- } as OutboundMessage));
1794
- } else {
1795
- try {
1796
- log.info(`🔊 TTS enabled, synthesizing audio for WebChat...`);
1797
- const audioOutput = await voiceService.speak(content, voiceConfig.ttsProvider, voiceConfig.ttsVoiceId || undefined);
1798
- ttsProviderUsed = voiceConfig.ttsProvider;
1799
- ttsMimeType = audioOutput.mimeType;
1800
- responseType = "audio";
1801
-
1802
- const base64Audio = (audioOutput.data as Buffer).toString("base64");
1803
-
1804
- ws.send(JSON.stringify({
1805
- type: "audio",
1806
- sessionId: unifiedSessionId,
1807
- audio: base64Audio,
1808
- content,
1809
- mimeType: audioOutput.mimeType,
1810
- } as OutboundMessage));
1811
- } catch (ttsError) {
1812
- log.error(`TTS failed: ${(ttsError as Error).message}), sending text instead`);
1813
- ws.send(JSON.stringify({ type: "message", sessionId: unifiedSessionId, content, isStep: false } as OutboundMessage));
1814
- }
1815
- }
1816
- } else {
1817
- ws.send(JSON.stringify({ type: "message", sessionId: unifiedSessionId, content, isStep: false } as OutboundMessage));
1818
- }
1819
- }
1820
- } catch (error) {
1821
- const unifiedSessionId = msg.sessionId;
1822
- // Detener typing aunque falle — nunca dejar el spinner infinito
1823
- ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: unifiedSessionId } as OutboundMessage));
1824
- ws.send(JSON.stringify({
1825
- type: "error",
1826
- sessionId: unifiedSessionId,
1827
- error: (error as Error).message,
1828
- } as OutboundMessage));
1829
- log.error(`Error for session ${unifiedSessionId}: ${(error as Error).message}`);
1830
- }
1831
- });
1832
-
1833
- return;
1834
- }
1835
-
1836
- ws.send(JSON.stringify({
1837
- type: "error",
1838
- sessionId: msg.sessionId,
1839
- error: "Unknown message type",
1840
- } as OutboundMessage));
1841
- },
1842
-
1843
- close(ws) {
1844
- const data = ws.data;
1845
- const isCanvas = data.sessionId.startsWith("canvas:");
1846
- const isBridge = data.sessionId.startsWith("bridge:");
1847
-
1848
- if (isBridge) {
1849
- unsubscribeBridge(ws as any);
1850
- return;
1851
- }
1852
-
1853
- if (isCanvas) {
1854
- canvasManager.unregisterSession(data.sessionId);
1855
- unsubscribeCanvas(ws as any);
1856
- return;
1857
- }
1858
-
1859
- log.debug(`WebSocket disconnected: ${data.sessionId}`);
1860
- logSubscribers.delete(data.sessionId);
1861
- sessionManager.delete(data.sessionId);
1862
- laneQueue.cancel(data.sessionId);
1863
-
1864
- const channel = channelManager?.getChannel("webchat") as any;
1865
- if (channel?.unregisterConnection) channel.unregisterConnection(data.sessionId);
1866
- },
1867
- },
1868
- });
1869
-
1870
- onLogEntry((entry) => {
1871
- if (logSubscribers.size === 0) return;
1872
-
1873
- const payload = JSON.stringify({
1874
- type: "log",
1875
- sessionId: entry.meta?.sessionId || "system",
1876
- logEntry: entry,
1877
- });
1878
-
1879
- for (const sessionId of logSubscribers) {
1880
- const session = sessionManager.get(sessionId);
1881
- if (session?.ws && session.ws.readyState === 1) {
1882
- try {
1883
- session.ws.send(payload);
1884
- } catch {
1885
- logSubscribers.delete(sessionId);
1886
- }
1887
- } else {
1888
- logSubscribers.delete(sessionId);
1889
- }
1890
- }
1891
- });
1892
-
1893
- log.info(`Gateway started successfully`);
1894
-
1895
- // Check if running as child process in dev mode (parent handles browser open)
1896
- const isGatewayChild = process.env.HIVE_GATEWAY_CHILD === "1";
1897
-
1898
- // Print URLs based on mode
1899
- if (isDev) {
1900
- // In development: UI is served by Vite on port 5173
1901
- log.info(`[gateway] API: http://${host}:${port}`);
1902
- log.info(`[gateway] WebSocket: ws://${host}:${port}/ws`);
1903
- log.info(`[gateway] Canvas: ws://${host}:${port}/canvas`);
1904
- log.info(`[gateway] Modo: desarrollo`);
1905
- if (!isGatewayChild) {
1906
- log.info(`🐝 Administra tu Hive aquí: http://localhost:5173`);
1907
- }
1908
- } else {
1909
- // In production: Gateway serves UI from dist/
1910
- // Check if this is first-run setup mode
1911
- const isSetupMode = !existsSync(getDbPathLazy());
1912
- const baseUrl = `http://${host}:${port}`;
1913
- const uiUrl = isSetupMode ? `${baseUrl}/setup` : `${baseUrl}/ui`;
1914
-
1915
- log.info(`[gateway] UI: ${uiUrl}`);
1916
- log.info(`[gateway] API: http://${host}:${port}`);
1917
- log.info(`[gateway] WebSocket: ws://${host}:${port}/ws`);
1918
- log.info(`[gateway] Canvas: ws://${host}:${port}/canvas`);
1919
-
1920
- log.info(isSetupMode ? `🎉 Primer arranque — abriendo wizard de configuración...` : `🐝 Administra tu Hive aquí: ${uiUrl}`);
1921
-
1922
- // Always open browser on startup (setup and normal mode).
1923
- // Set NO_BROWSER=1 to skip in headless/server environments.
1924
- if (!process.env.NO_BROWSER) {
1925
- try {
1926
- const platform = process.platform;
1927
- let shellCmd: string;
1928
- if (platform === "win32") {
1929
- shellCmd = `start "" "${uiUrl}"`;
1930
- } else if (platform === "darwin") {
1931
- shellCmd = `open "${uiUrl}"`;
1932
- } else {
1933
- // Linux: gio open first (GNOME/Wayland native), then xdg-open fallbacks
1934
- shellCmd = `gio open "${uiUrl}" 2>/dev/null || xdg-open "${uiUrl}" 2>/dev/null || sensible-browser "${uiUrl}" 2>/dev/null || x-www-browser "${uiUrl}" 2>/dev/null || true`;
1935
- }
1936
- const shell = platform === "win32" ? "cmd" : "/bin/sh";
1937
- const shellArg = platform === "win32" ? "/c" : "-c";
1938
- // Use Bun.spawn (native Bun API) for reliable detached subprocess
1939
- const proc = Bun.spawn([shell, shellArg, shellCmd], {
1940
- stdout: "ignore",
1941
- stderr: "ignore",
1942
- stdin: "ignore",
1943
- });
1944
- proc.unref();
1945
- } catch (err) {
1946
- log.warn(`Could not open browser: ${(err as Error).message}`);
1947
- }
1948
- }
1949
- }
1950
- if (!gatewaySetupMode) log.info(`Channels: ${channelManager.listChannels().map((c) => c.name).join(", ") || "none"}`);
1951
-
1952
- // FIX 7 — SIGTERM desconecta MCP limpiamente antes de cerrar
1953
- process.on("SIGTERM", async () => {
1954
- log.info("Received SIGTERM, shutting down gracefully...");
1955
- watchers.forEach((close) => close());
1956
- const mcp = agent?.getMCPManager();
1957
- if (mcp) {
1958
- log.info("Disconnecting MCP servers...");
1959
- await mcp.disconnectAll().catch(() => { });
1960
- }
1961
- if (channelManager) await channelManager.stopAll();
1962
- server.stop();
1963
- try { unlinkSync(pidFile); } catch { }
1964
- process.exit(0);
1965
- });
1966
-
1967
- process.on("SIGHUP", async () => {
1968
- log.info("Received SIGHUP, reloading configuration...");
1969
- try {
1970
- const newConfig = await loadConfig();
1971
- await agent.updateConfig(newConfig);
1972
- await agent.reload();
1973
- log.info("Configuration reloaded successfully");
1974
- } catch (error) {
1975
- log.error(`Failed to reload configuration: ${(error as Error).message}`);
1976
- }
1977
- });
1978
- }