@johpaz/hive-agents 0.0.37 → 0.0.39

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 (223) hide show
  1. package/README.md +18 -18
  2. package/dist/hive.js +3529 -2702
  3. package/dist/tool-worker.js +2110 -1856
  4. package/dist/ui/assets/AgentCreateForm-BTCzFbca.js +1 -0
  5. package/dist/ui/assets/AgentDetailPage-o27TRSVw.js +1 -0
  6. package/dist/ui/assets/AgentNewPage-400cCpYt.js +1 -0
  7. package/dist/ui/{dist/assets/AgentsPage-YvSgWRiw.js → assets/AgentsPage-C-GSRk-N.js} +5 -5
  8. package/dist/ui/assets/ApiClientPage-BOTpz6oP.js +3 -0
  9. package/dist/ui/assets/{CanvasPage-DtMwGvxf.js → CanvasPage-Cvs5ctza.js} +7 -7
  10. package/dist/ui/assets/ChannelsPage-C5m_L7P9.js +8 -0
  11. package/dist/ui/{dist/assets/DashboardPage-ghl1ZguH.js → assets/DashboardPage-CztbRQdm.js} +2 -2
  12. package/dist/ui/assets/{LoginPage-CAmSI9Vy.js → LoginPage-OMsrx5oj.js} +1 -1
  13. package/dist/ui/assets/LogsPage-CcYYwjgF.js +1 -0
  14. package/dist/ui/{dist/assets/MeetingPage-WjjGOqqU.js → assets/MeetingPage-CrKVAfe6.js} +1 -1
  15. package/dist/ui/assets/{NotFound-BMeQSGcG.js → NotFound-GbAJDgoD.js} +1 -1
  16. package/dist/ui/assets/ProvidersPage-uqPcZUSV.js +1 -0
  17. package/dist/ui/{dist/assets/RecoverPage-DpW3l-yv.js → assets/RecoverPage-CwB2ByCU.js} +1 -1
  18. package/dist/ui/assets/SettingsPage-DKLlye0z.js +9 -0
  19. package/dist/ui/assets/SetupPage-DOVh1ldK.js +1 -0
  20. package/dist/ui/assets/WebChatPage-c-7S9jnT.js +16 -0
  21. package/dist/ui/assets/accordion-DAbcVQCn.js +1 -0
  22. package/dist/ui/{dist/assets/alert-C-NE-P3s.js → assets/alert-D_2Y3qjL.js} +1 -1
  23. package/dist/ui/{dist/assets/alert-dialog-C5mzbHdP.js → assets/alert-dialog-CpMxaNcu.js} +1 -1
  24. package/dist/ui/assets/{badge-ChpACfWO.js → badge-CxTPR6_t.js} +1 -1
  25. package/dist/ui/assets/bell-8BqRYmzf.js +1 -0
  26. package/dist/ui/assets/circle-x-Bv6WrUJo.js +1 -0
  27. package/dist/ui/assets/copy-dU94ZGsi.js +1 -0
  28. package/dist/ui/{dist/assets/dialog-QnZ0ad8O.js → assets/dialog-DfS3idb3.js} +1 -1
  29. package/dist/ui/{dist/assets/dropdown-menu-BK-CO3Od.js → assets/dropdown-menu-BdCbAW1z.js} +1 -1
  30. package/dist/ui/assets/{es-NQNoaWDx.js → es-Cz5h9_84.js} +1 -1
  31. package/dist/ui/assets/index-CmGm_r89.js +116 -0
  32. package/dist/ui/assets/index-T7HgphSn.css +2 -0
  33. package/dist/ui/{dist/assets/label-D2H1IR_J.js → assets/label-byJkqOYq.js} +1 -1
  34. package/dist/ui/assets/progress-Dtz-Mzys.js +1 -0
  35. package/dist/ui/assets/scroll-area-BXtLsE9E.js +1 -0
  36. package/dist/ui/assets/search-BGmPJ-6L.js +1 -0
  37. package/dist/ui/assets/select-Cl16QYa_.js +1 -0
  38. package/dist/ui/assets/send-BuQcUO-R.js +1 -0
  39. package/dist/ui/assets/shield-C-05qB-2.js +1 -0
  40. package/dist/ui/{dist/assets/slider-CsiUDxc3.js → assets/slider-D2I0qven.js} +1 -1
  41. package/dist/ui/assets/switch-h2SfQX4B.js +1 -0
  42. package/dist/ui/assets/table-CVkIRJKK.js +1 -0
  43. package/dist/ui/assets/tabs-C619jxbO.js +1 -0
  44. package/dist/ui/assets/textarea-wvA-FDjO.js +1 -0
  45. package/dist/ui/assets/useProviders-eEri6BAc.js +1 -0
  46. package/dist/ui/{dist/assets/vendor-radix-cw1bQaVC.js → assets/vendor-radix-D6rA7xKY.js} +4 -4
  47. package/dist/ui/assets/{vendor-react-D4s9E-zj.js → vendor-react-BU5iQU4f.js} +1 -1
  48. package/dist/ui/dist/assets/AgentCreateForm-BTCzFbca.js +1 -0
  49. package/dist/ui/dist/assets/AgentDetailPage-o27TRSVw.js +1 -0
  50. package/dist/ui/dist/assets/AgentNewPage-400cCpYt.js +1 -0
  51. package/dist/ui/{assets/AgentsPage-YvSgWRiw.js → dist/assets/AgentsPage-C-GSRk-N.js} +5 -5
  52. package/dist/ui/dist/assets/ApiClientPage-BOTpz6oP.js +3 -0
  53. package/dist/ui/dist/assets/{CanvasPage-DtMwGvxf.js → CanvasPage-Cvs5ctza.js} +7 -7
  54. package/dist/ui/dist/assets/ChannelsPage-C5m_L7P9.js +8 -0
  55. package/dist/ui/{assets/DashboardPage-ghl1ZguH.js → dist/assets/DashboardPage-CztbRQdm.js} +2 -2
  56. package/dist/ui/dist/assets/{LoginPage-CAmSI9Vy.js → LoginPage-OMsrx5oj.js} +1 -1
  57. package/dist/ui/dist/assets/LogsPage-CcYYwjgF.js +1 -0
  58. package/dist/ui/{assets/MeetingPage-WjjGOqqU.js → dist/assets/MeetingPage-CrKVAfe6.js} +1 -1
  59. package/dist/ui/dist/assets/{NotFound-BMeQSGcG.js → NotFound-GbAJDgoD.js} +1 -1
  60. package/dist/ui/dist/assets/ProvidersPage-uqPcZUSV.js +1 -0
  61. package/dist/ui/{assets/RecoverPage-DpW3l-yv.js → dist/assets/RecoverPage-CwB2ByCU.js} +1 -1
  62. package/dist/ui/dist/assets/SettingsPage-DKLlye0z.js +9 -0
  63. package/dist/ui/dist/assets/SetupPage-DOVh1ldK.js +1 -0
  64. package/dist/ui/dist/assets/WebChatPage-c-7S9jnT.js +16 -0
  65. package/dist/ui/dist/assets/accordion-DAbcVQCn.js +1 -0
  66. package/dist/ui/{assets/alert-C-NE-P3s.js → dist/assets/alert-D_2Y3qjL.js} +1 -1
  67. package/dist/ui/{assets/alert-dialog-C5mzbHdP.js → dist/assets/alert-dialog-CpMxaNcu.js} +1 -1
  68. package/dist/ui/dist/assets/{badge-ChpACfWO.js → badge-CxTPR6_t.js} +1 -1
  69. package/dist/ui/dist/assets/bell-8BqRYmzf.js +1 -0
  70. package/dist/ui/dist/assets/circle-x-Bv6WrUJo.js +1 -0
  71. package/dist/ui/dist/assets/copy-dU94ZGsi.js +1 -0
  72. package/dist/ui/{assets/dialog-QnZ0ad8O.js → dist/assets/dialog-DfS3idb3.js} +1 -1
  73. package/dist/ui/{assets/dropdown-menu-BK-CO3Od.js → dist/assets/dropdown-menu-BdCbAW1z.js} +1 -1
  74. package/dist/ui/dist/assets/{es-NQNoaWDx.js → es-Cz5h9_84.js} +1 -1
  75. package/dist/ui/dist/assets/index-CmGm_r89.js +116 -0
  76. package/dist/ui/dist/assets/index-T7HgphSn.css +2 -0
  77. package/dist/ui/{assets/label-D2H1IR_J.js → dist/assets/label-byJkqOYq.js} +1 -1
  78. package/dist/ui/dist/assets/progress-Dtz-Mzys.js +1 -0
  79. package/dist/ui/dist/assets/scroll-area-BXtLsE9E.js +1 -0
  80. package/dist/ui/dist/assets/search-BGmPJ-6L.js +1 -0
  81. package/dist/ui/dist/assets/select-Cl16QYa_.js +1 -0
  82. package/dist/ui/dist/assets/send-BuQcUO-R.js +1 -0
  83. package/dist/ui/dist/assets/shield-C-05qB-2.js +1 -0
  84. package/dist/ui/{assets/slider-CsiUDxc3.js → dist/assets/slider-D2I0qven.js} +1 -1
  85. package/dist/ui/dist/assets/switch-h2SfQX4B.js +1 -0
  86. package/dist/ui/dist/assets/table-CVkIRJKK.js +1 -0
  87. package/dist/ui/dist/assets/tabs-C619jxbO.js +1 -0
  88. package/dist/ui/dist/assets/textarea-wvA-FDjO.js +1 -0
  89. package/dist/ui/dist/assets/useProviders-eEri6BAc.js +1 -0
  90. package/dist/ui/{assets/vendor-radix-cw1bQaVC.js → dist/assets/vendor-radix-D6rA7xKY.js} +4 -4
  91. package/dist/ui/dist/assets/{vendor-react-D4s9E-zj.js → vendor-react-BU5iQU4f.js} +1 -1
  92. package/dist/ui/dist/index.html +6 -6
  93. package/dist/ui/index.html +6 -6
  94. package/package.json +1 -1
  95. package/packages/cli/src/adapters/binary.ts +8 -4
  96. package/packages/cli/src/adapters/bun-global.ts +5 -1
  97. package/packages/cli/src/adapters/config.ts +4 -3
  98. package/packages/cli/src/adapters/docker.ts +2 -1
  99. package/packages/cli/src/commands/gateway.ts +123 -9
  100. package/packages/cli/src/commands/logs.ts +2 -1
  101. package/packages/cli/src/commands/onboard.ts +1 -1
  102. package/packages/cli/src/commands/sessions.ts +2 -1
  103. package/packages/cli/src/commands/skills.ts +2 -1
  104. package/packages/core/src/agent/agent-loop.ts +4 -14
  105. package/packages/core/src/agent/context-compiler.ts +1 -1
  106. package/packages/core/src/agent/conversation-store.ts +4 -5
  107. package/packages/core/src/agent/llm-client.ts +4 -0
  108. package/packages/core/src/agent/llm-providers/anthropic.ts +23 -8
  109. package/packages/core/src/agent/llm-providers/interface.ts +5 -1
  110. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  111. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  112. package/packages/core/src/agent/providers/index.ts +3 -4
  113. package/packages/core/src/agent/tool-selector.ts +3 -4
  114. package/packages/core/src/channels/whatsapp.ts +13 -1
  115. package/packages/core/src/config/loader.ts +7 -7
  116. package/packages/core/src/gateway/helpers/path.ts +2 -1
  117. package/packages/core/src/gateway/initializer.ts +4 -4
  118. package/packages/core/src/gateway/llm-local/downloader.ts +130 -11
  119. package/packages/core/src/gateway/llm-local/index.ts +2 -0
  120. package/packages/core/src/gateway/llm-local/models.ts +4 -3
  121. package/packages/core/src/gateway/resolver.ts +5 -1
  122. package/packages/core/src/gateway/router.ts +7 -5
  123. package/packages/core/src/gateway/routes/chat.ts +16 -16
  124. package/packages/core/src/gateway/routes/http-client.ts +16 -0
  125. package/packages/core/src/gateway/routes/llm-local.ts +51 -5
  126. package/packages/core/src/gateway/routes/providers.ts +43 -2
  127. package/packages/core/src/gateway/server.ts +55 -47
  128. package/packages/core/src/gateway/tts/src/install.ts +17 -9
  129. package/packages/core/src/storage/crypto.ts +152 -20
  130. package/packages/core/src/storage/migrate.ts +51 -18
  131. package/packages/core/src/storage/seed.ts +77 -35
  132. package/packages/core/src/tool-runtime/index.ts +42 -1
  133. package/packages/core/src/tools/api/api-request.ts +174 -0
  134. package/packages/core/src/tools/api/index.ts +16 -0
  135. package/packages/core/src/tools/index.ts +12 -0
  136. package/packages/core/src/tools/web/browser-click.ts +2 -2
  137. package/packages/core/src/tools/web/browser-extract.ts +22 -6
  138. package/packages/core/src/tools/web/browser-navigate.ts +34 -18
  139. package/packages/core/src/tools/web/browser-screenshot.ts +40 -8
  140. package/packages/core/src/tools/web/browser-script.ts +2 -2
  141. package/packages/core/src/tools/web/browser-service.ts +295 -341
  142. package/packages/core/src/tools/web/browser-type.ts +5 -10
  143. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  144. package/packages/core/src/tools/web/index.ts +1 -1
  145. package/packages/core/src/utils/logger.ts +2 -1
  146. package/packages/mcp/src/manager.ts +2 -1
  147. package/packages/skills/src/bundled/api/api_client/SKILL.md +132 -0
  148. package/packages/skills/src/bundled-data.generated.ts +1274 -1217
  149. package/packages/skills/src/loader.ts +2 -1
  150. package/dist/ui/assets/AgentCreateForm-tJZv9FZC.js +0 -1
  151. package/dist/ui/assets/AgentDetailPage-Du-mRcAX.js +0 -1
  152. package/dist/ui/assets/AgentNewPage-DIFYd_Ys.js +0 -1
  153. package/dist/ui/assets/ChannelsPage-BdBXWHjj.js +0 -8
  154. package/dist/ui/assets/LogsPage-DAPBHkwK.js +0 -1
  155. package/dist/ui/assets/ProvidersPage-Ct6HsAi1.js +0 -1
  156. package/dist/ui/assets/SettingsPage-DBJ7_E6C.js +0 -9
  157. package/dist/ui/assets/SetupPage-DKmLVUaj.js +0 -1
  158. package/dist/ui/assets/WebChatPage-CVRcKept.js +0 -16
  159. package/dist/ui/assets/accordion-C5d5Rm5z.js +0 -1
  160. package/dist/ui/assets/globe-DeCQTCDJ.js +0 -1
  161. package/dist/ui/assets/index-B2fCYtTS.css +0 -2
  162. package/dist/ui/assets/index-DMCjjdqf.js +0 -116
  163. package/dist/ui/assets/progress-BherYzY6.js +0 -1
  164. package/dist/ui/assets/scroll-area-DkeyX32e.js +0 -1
  165. package/dist/ui/assets/send-B0H5SEIE.js +0 -1
  166. package/dist/ui/assets/switch-BDwN8RYV.js +0 -1
  167. package/dist/ui/assets/table-CSc8ubon.js +0 -1
  168. package/dist/ui/assets/textarea-CXgXWKrT.js +0 -1
  169. package/dist/ui/assets/useProviders-C6_QHsEi.js +0 -1
  170. package/dist/ui/dist/assets/AgentCreateForm-tJZv9FZC.js +0 -1
  171. package/dist/ui/dist/assets/AgentDetailPage-Du-mRcAX.js +0 -1
  172. package/dist/ui/dist/assets/AgentNewPage-DIFYd_Ys.js +0 -1
  173. package/dist/ui/dist/assets/ChannelsPage-BdBXWHjj.js +0 -8
  174. package/dist/ui/dist/assets/LogsPage-DAPBHkwK.js +0 -1
  175. package/dist/ui/dist/assets/ProvidersPage-Ct6HsAi1.js +0 -1
  176. package/dist/ui/dist/assets/SettingsPage-DBJ7_E6C.js +0 -9
  177. package/dist/ui/dist/assets/SetupPage-DKmLVUaj.js +0 -1
  178. package/dist/ui/dist/assets/WebChatPage-CVRcKept.js +0 -16
  179. package/dist/ui/dist/assets/accordion-C5d5Rm5z.js +0 -1
  180. package/dist/ui/dist/assets/globe-DeCQTCDJ.js +0 -1
  181. package/dist/ui/dist/assets/index-B2fCYtTS.css +0 -2
  182. package/dist/ui/dist/assets/index-DMCjjdqf.js +0 -116
  183. package/dist/ui/dist/assets/progress-BherYzY6.js +0 -1
  184. package/dist/ui/dist/assets/scroll-area-DkeyX32e.js +0 -1
  185. package/dist/ui/dist/assets/send-B0H5SEIE.js +0 -1
  186. package/dist/ui/dist/assets/switch-BDwN8RYV.js +0 -1
  187. package/dist/ui/dist/assets/table-CSc8ubon.js +0 -1
  188. package/dist/ui/dist/assets/textarea-CXgXWKrT.js +0 -1
  189. package/dist/ui/dist/assets/useProviders-C6_QHsEi.js +0 -1
  190. /package/dist/ui/assets/{card-CNf6BS2e.js → card-CXAm46at.js} +0 -0
  191. /package/dist/ui/assets/{cpu-Cdgc_B1K.js → cpu-DSpPVLAz.js} +0 -0
  192. /package/dist/ui/assets/{download-C3ifGMjJ.js → download-D9ZyUZZR.js} +0 -0
  193. /package/dist/ui/assets/{external-link-BvxYeTP1.js → external-link-CHPbUorN.js} +0 -0
  194. /package/dist/ui/assets/{eye-DqNTU_GD.js → eye-epHJZ_nQ.js} +0 -0
  195. /package/dist/ui/assets/{file-text-BT_9S9SM.js → file-text-BEjEmgby.js} +0 -0
  196. /package/dist/ui/assets/{folder-open-BhH8y9ac.js → folder-open-iQMHVEqS.js} +0 -0
  197. /package/dist/ui/assets/{format-GVHeOyWI.js → format-oFACFaca.js} +0 -0
  198. /package/dist/ui/assets/{gateway-url-COCbW0IR.js → gateway-url-iG-C6Agn.js} +0 -0
  199. /package/dist/ui/assets/{gauge-D_TMa4i9.js → gauge-D0_GMEcq.js} +0 -0
  200. /package/dist/ui/assets/{settings-Ds4SqD8s.js → settings-CcMGI1iU.js} +0 -0
  201. /package/dist/ui/assets/{sparkles-yUEb-7oH.js → sparkles-D6fx8JC5.js} +0 -0
  202. /package/dist/ui/assets/{trash-2-CNjMkoq6.js → trash-2-BHRa5ft9.js} +0 -0
  203. /package/dist/ui/assets/{triangle-alert-C9Y8Ub4X.js → triangle-alert-D4nwAVbc.js} +0 -0
  204. /package/dist/ui/assets/{vendor-router-C9pIYwbJ.js → vendor-router-pCP7sjma.js} +0 -0
  205. /package/dist/ui/assets/{volume-2-CeSXNDv4.js → volume-2-B6tkRy2u.js} +0 -0
  206. /package/dist/ui/assets/{zap-hlXjpSeA.js → zap-QO7iWMRg.js} +0 -0
  207. /package/dist/ui/dist/assets/{card-CNf6BS2e.js → card-CXAm46at.js} +0 -0
  208. /package/dist/ui/dist/assets/{cpu-Cdgc_B1K.js → cpu-DSpPVLAz.js} +0 -0
  209. /package/dist/ui/dist/assets/{download-C3ifGMjJ.js → download-D9ZyUZZR.js} +0 -0
  210. /package/dist/ui/dist/assets/{external-link-BvxYeTP1.js → external-link-CHPbUorN.js} +0 -0
  211. /package/dist/ui/dist/assets/{eye-DqNTU_GD.js → eye-epHJZ_nQ.js} +0 -0
  212. /package/dist/ui/dist/assets/{file-text-BT_9S9SM.js → file-text-BEjEmgby.js} +0 -0
  213. /package/dist/ui/dist/assets/{folder-open-BhH8y9ac.js → folder-open-iQMHVEqS.js} +0 -0
  214. /package/dist/ui/dist/assets/{format-GVHeOyWI.js → format-oFACFaca.js} +0 -0
  215. /package/dist/ui/dist/assets/{gateway-url-COCbW0IR.js → gateway-url-iG-C6Agn.js} +0 -0
  216. /package/dist/ui/dist/assets/{gauge-D_TMa4i9.js → gauge-D0_GMEcq.js} +0 -0
  217. /package/dist/ui/dist/assets/{settings-Ds4SqD8s.js → settings-CcMGI1iU.js} +0 -0
  218. /package/dist/ui/dist/assets/{sparkles-yUEb-7oH.js → sparkles-D6fx8JC5.js} +0 -0
  219. /package/dist/ui/dist/assets/{trash-2-CNjMkoq6.js → trash-2-BHRa5ft9.js} +0 -0
  220. /package/dist/ui/dist/assets/{triangle-alert-C9Y8Ub4X.js → triangle-alert-D4nwAVbc.js} +0 -0
  221. /package/dist/ui/dist/assets/{vendor-router-C9pIYwbJ.js → vendor-router-pCP7sjma.js} +0 -0
  222. /package/dist/ui/dist/assets/{volume-2-CeSXNDv4.js → volume-2-B6tkRy2u.js} +0 -0
  223. /package/dist/ui/dist/assets/{zap-hlXjpSeA.js → zap-QO7iWMRg.js} +0 -0
@@ -29,7 +29,6 @@
29
29
  *
30
30
  * 6. Tool categorization: Tools are categorized by semantic domain:
31
31
  * - scheduling (cron tools)
32
- * - projects (project/task management)
33
32
  * - filesystem (file operations)
34
33
  * - web (search/fetch)
35
34
  * - browser (browser automation)
@@ -527,10 +526,10 @@ function enrichToolDescription(tool: ToolDescriptor): string {
527
526
  *
528
527
  * Canonical format: `{safeServer}__{safeTool}` (double underscore as separator)
529
528
  */
530
- export function mcpToolFullName(_serverName: string, toolName: string): string {
529
+ export function mcpToolFullName(serverName: string, toolName: string): string {
531
530
  const safe = (s: string) => s.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_\-]/g, '_')
532
- const sanitized = safe(toolName)
533
- const trimmed = sanitized.length > 64 ? sanitized.substring(0, 64) : sanitized
531
+ const full = `${safe(serverName)}__${safe(toolName)}`
532
+ const trimmed = full.length > 64 ? full.substring(0, 64) : full
534
533
  return /^[a-zA-Z_]/.test(trimmed) ? trimmed : `_${trimmed}`.substring(0, 64)
535
534
  }
536
535
 
@@ -11,6 +11,7 @@ import type { ChannelConfig, IncomingMessage, OutboundMessage } from "./base.ts"
11
11
  import { BaseChannel } from "./base.ts";
12
12
  import { existsSync, mkdirSync, rmSync } from "node:fs";
13
13
  import * as path from "node:path";
14
+ import { homedir } from "node:os";
14
15
  import { logger } from "../utils/logger.ts";
15
16
  import { getDb } from "../storage/sqlite.ts";
16
17
  // @ts-ignore — no type definitions for qrcode-terminal
@@ -69,7 +70,7 @@ export class WhatsAppChannel extends BaseChannel {
69
70
  }
70
71
 
71
72
  private getAuthPath(agentId: string, accountId: string): string {
72
- const baseDir = process.env.HOME ?? "";
73
+ const baseDir = homedir();
73
74
  const authDir = path.join(baseDir, ".hive", "agents", agentId, "whatsapp", accountId);
74
75
 
75
76
  if (!existsSync(authDir)) {
@@ -117,11 +118,22 @@ export class WhatsAppChannel extends BaseChannel {
117
118
  this.connectionState.waVersion = version.join(".");
118
119
  this.log.info(`Using WhatsApp Web v${version.join(".")}`);
119
120
 
121
+ const baileysLogger = {
122
+ level: "silent",
123
+ child: () => baileysLogger,
124
+ trace: () => {},
125
+ debug: () => {},
126
+ info: (msg: unknown) => { if (typeof msg === "object" && msg !== null) this.log.debug((msg as any).msg ?? JSON.stringify(msg)); },
127
+ warn: (msg: unknown) => { if (typeof msg === "object" && msg !== null) this.log.warn((msg as any).msg ?? JSON.stringify(msg)); },
128
+ error: (msg: unknown) => { if (typeof msg === "object" && msg !== null) this.log.error((msg as any).msg ?? JSON.stringify(msg)); },
129
+ };
130
+
120
131
  this.socket = makeWASocket({
121
132
  version,
122
133
  auth: state,
123
134
  printQRInTerminal: false,
124
135
  syncFullHistory: false,
136
+ logger: baileysLogger as any,
125
137
  getMessage: async () => ({ conversation: "" }),
126
138
  });
127
139
 
@@ -1,7 +1,7 @@
1
1
  import * as z from "zod";
2
2
  import { mkdirSync, existsSync, readFileSync } from "node:fs";
3
3
  import * as path from "node:path";
4
- import { availableParallelism } from "node:os";
4
+ import { availableParallelism, homedir } from "node:os";
5
5
 
6
6
  const LogLevelSchema = z.enum(["debug", "info", "warn", "error"]);
7
7
  const DMPolicySchema = z.enum(["open", "pairing", "allowlist"]);
@@ -33,7 +33,7 @@ export function getHiveDir(): string {
33
33
  // Priority 1: HIVE_HOME explicitly set
34
34
  if (process.env.HIVE_HOME) {
35
35
  const hiveDir = process.env.HIVE_HOME.startsWith("~")
36
- ? path.join(process.env.HOME || "", process.env.HIVE_HOME.slice(1))
36
+ ? path.join(homedir(), process.env.HIVE_HOME.slice(1))
37
37
  : process.env.HIVE_HOME;
38
38
  loadEnv(hiveDir);
39
39
  return hiveDir;
@@ -49,7 +49,7 @@ export function getHiveDir(): string {
49
49
  }
50
50
 
51
51
  // Priority 3: Default ~/.hive
52
- const defaultDir = path.join(process.env.HOME || "", ".hive");
52
+ const defaultDir = path.join(homedir(), ".hive");
53
53
  loadEnv(defaultDir);
54
54
  return defaultDir;
55
55
  }
@@ -60,7 +60,7 @@ const expandPath = (p: string): string => {
60
60
  return p.replace("~/.hive", hiveDir);
61
61
  }
62
62
  if (p.startsWith("~")) {
63
- return path.join(process.env.HOME || "", p.slice(1));
63
+ return path.join(homedir(), p.slice(1));
64
64
  }
65
65
  return p;
66
66
  };
@@ -117,9 +117,9 @@ const WebConfigSchema = z.object({
117
117
 
118
118
  const BrowserConfigSchema = z.object({
119
119
  enabled: z.boolean().optional(),
120
- cdpUrl: z.string().optional(),
121
120
  headless: z.boolean().optional(),
122
121
  timeoutMs: z.number().optional(),
122
+ sessionName: z.string().optional(),
123
123
  });
124
124
 
125
125
  const CanvasConfigSchema = z.object({
@@ -437,7 +437,7 @@ function buildDefaultConfig(): Config {
437
437
  allowlist: [],
438
438
  denylist: ["rm -rf /", "sudo", "chmod 777", "> /dev/", "mkfs"],
439
439
  timeoutSeconds: 30,
440
- workDir: path.join(process.env.HOME || "", "exec"), // Points to home for exec by default
440
+ workDir: path.join(homedir(), "exec"), // Points to home for exec by default
441
441
  },
442
442
  web: {
443
443
  allowlist: [],
@@ -446,9 +446,9 @@ function buildDefaultConfig(): Config {
446
446
  },
447
447
  browser: {
448
448
  enabled: true,
449
- cdpUrl: "ws://127.0.0.1:9222",
450
449
  headless: true,
451
450
  timeoutMs: 30000,
451
+ sessionName: "hive",
452
452
  },
453
453
  canvas: {
454
454
  enabled: true,
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { homedir } from "node:os";
2
3
 
3
4
  /**
4
5
  * Expands a path that starts with ~ to the user's home directory.
@@ -7,7 +8,7 @@ import * as path from "node:path";
7
8
  */
8
9
  export function expandPath(p: string): string {
9
10
  if (p.startsWith("~")) {
10
- return path.join(process.env.HOME ?? "", p.slice(1));
11
+ return path.join(homedir(), p.slice(1));
11
12
  }
12
13
  return p;
13
14
  }
@@ -250,11 +250,11 @@ export async function initializeGateway(
250
250
  const agent = createAgentService();
251
251
  await agent.initialize();
252
252
 
253
- // 5b. Initialize Browser Service (Chrome via Bun.WebView nativo)
253
+ // 5b. Initialize Browser Service (agent-browser CLI)
254
254
  let browserAvailable = false;
255
255
 
256
256
  try {
257
- log.info("Detecting browser (lazy launch — will open on first agent use)...");
257
+ log.info("Initializing browser automation (agent-browser)...");
258
258
 
259
259
  const browserService = initializeBrowserService(config);
260
260
  browserAvailable = await browserService.start();
@@ -262,8 +262,8 @@ export async function initializeGateway(
262
262
  if (browserAvailable) {
263
263
  activateBrowserTools();
264
264
  } else {
265
- log.warn("⚠️ No se encontró Chrome/Chromium - browser tools desactivadas");
266
- log.warn(" Linux: sudo dnf install chromium | macOS: brew install --cask google-chrome");
265
+ log.warn("⚠️ agent-browser no disponible - browser tools desactivadas");
266
+ log.warn(" Se instalará automáticamente en primer uso o manual: bun add -g agent-browser");
267
267
  }
268
268
  } catch (error) {
269
269
  log.warn(`Browser Service initialization skipped: ${(error as Error).message}`);
@@ -18,22 +18,116 @@ export const MODELS_DIR = join(LLM_ROOT, "models")
18
18
  if (!existsSync(BIN_DIR)) mkdirSync(BIN_DIR, { recursive: true })
19
19
  if (!existsSync(MODELS_DIR)) mkdirSync(MODELS_DIR, { recursive: true })
20
20
 
21
- /** URLs de modelos en HuggingFace (Gemma 4 según GUIA-SERVIDOR.md) */
21
+ /** URLs de modelos en HuggingFace (Gemma 4 y Qwen 3.5 según Unsloth) */
22
22
  export const HF_MODEL_URLS = {
23
+ // mmproj legacy (E4B)
23
24
  mmproj: "https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/resolve/main/mmproj-BF16.gguf",
25
+ // mmproj Gemma 4
26
+ mmproj_gemma4_12b: "https://huggingface.co/unsloth/gemma-4-12b-it-GGUF/resolve/main/mmproj-BF16.gguf",
27
+ mmproj_gemma4_26b: "https://huggingface.co/unsloth/gemma-4-26B-A4B-it-GGUF/resolve/main/mmproj-BF16.gguf",
28
+ mmproj_gemma4_31b: "https://huggingface.co/unsloth/gemma-4-31B-it-GGUF/resolve/main/mmproj-BF16.gguf",
29
+ // mmproj Qwen 3.5
30
+ mmproj_qwen3_5_2b: "https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/resolve/main/mmproj-F16.gguf",
31
+ mmproj_qwen3_5_4b: "https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/mmproj-F16.gguf",
32
+ mmproj_qwen3_5_9b: "https://huggingface.co/unsloth/Qwen3.5-9B-GGUF/resolve/main/mmproj-F16.gguf",
33
+ mmproj_qwen3_5_27b: "https://huggingface.co/unsloth/Qwen3.5-27B-GGUF/resolve/main/mmproj-F16.gguf",
34
+ mmproj_qwen3_5_35b: "https://huggingface.co/unsloth/Qwen3.5-35B-A3B-GGUF/resolve/main/mmproj-F16.gguf",
35
+ // modelos Gemma 4
24
36
  e2b_Q4_K_XL: "https://huggingface.co/unsloth/gemma-4-E2B-it-GGUF/resolve/main/gemma-4-E2B-it-UD-Q4_K_XL.gguf",
25
- e4b_Q4_K_XL: "https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/resolve/main/gemma-4-E4B-it-UD-Q4_K_XL.gguf"
37
+ e4b_Q4_K_XL: "https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/resolve/main/gemma-4-E4B-it-UD-Q4_K_XL.gguf",
38
+ gemma4_12b_Q4_K_XL: "https://huggingface.co/unsloth/gemma-4-12b-it-GGUF/resolve/main/gemma-4-12b-it-UD-Q4_K_XL.gguf",
39
+ gemma4_26b_Q4_K_M: "https://huggingface.co/unsloth/gemma-4-26B-A4B-it-GGUF/resolve/main/gemma-4-26B-A4B-it-UD-Q4_K_M.gguf",
40
+ gemma4_31b_Q4_K_XL: "https://huggingface.co/unsloth/gemma-4-31B-it-GGUF/resolve/main/gemma-4-31B-it-UD-Q4_K_XL.gguf",
41
+ // modelos Qwen 3.5
42
+ qwen3_5_2b_Q4_K_XL: "https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/resolve/main/Qwen3.5-2B-UD-Q4_K_XL.gguf",
43
+ qwen3_5_4b_Q4_K_XL: "https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/Qwen3.5-4B-UD-Q4_K_XL.gguf",
44
+ qwen3_5_9b_Q4_K_XL: "https://huggingface.co/unsloth/Qwen3.5-9B-GGUF/resolve/main/Qwen3.5-9B-UD-Q4_K_XL.gguf",
45
+ qwen3_5_27b_Q4_K_XL: "https://huggingface.co/unsloth/Qwen3.5-27B-GGUF/resolve/main/Qwen3.5-27B-UD-Q4_K_XL.gguf",
46
+ qwen3_5_35b_Q4_K_XL: "https://huggingface.co/unsloth/Qwen3.5-35B-A3B-GGUF/resolve/main/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf",
26
47
  }
27
48
 
28
- export type ModelId = "mmproj" | "e2b_Q4_K_XL" | "e4b_Q4_K_XL"
49
+ export type ModelId =
50
+ | "mmproj"
51
+ | "mmproj_gemma4_12b"
52
+ | "mmproj_gemma4_26b"
53
+ | "mmproj_gemma4_31b"
54
+ | "mmproj_qwen3_5_2b"
55
+ | "mmproj_qwen3_5_4b"
56
+ | "mmproj_qwen3_5_9b"
57
+ | "mmproj_qwen3_5_27b"
58
+ | "mmproj_qwen3_5_35b"
59
+ | "e2b_Q4_K_XL"
60
+ | "e4b_Q4_K_XL"
61
+ | "gemma4_12b_Q4_K_XL"
62
+ | "gemma4_26b_Q4_K_M"
63
+ | "gemma4_31b_Q4_K_XL"
64
+ | "qwen3_5_2b_Q4_K_XL"
65
+ | "qwen3_5_4b_Q4_K_XL"
66
+ | "qwen3_5_9b_Q4_K_XL"
67
+ | "qwen3_5_27b_Q4_K_XL"
68
+ | "qwen3_5_35b_Q4_K_XL"
29
69
 
30
70
  /** Modelos que requieren proyector de visión (mmproj) */
31
- export const VISION_MODELS: ModelId[] = ["e4b_Q4_K_XL"]
71
+ export const VISION_MODELS: ModelId[] = [
72
+ "e4b_Q4_K_XL",
73
+ "gemma4_12b_Q4_K_XL",
74
+ "gemma4_26b_Q4_K_M",
75
+ "gemma4_31b_Q4_K_XL",
76
+ "qwen3_5_2b_Q4_K_XL",
77
+ "qwen3_5_4b_Q4_K_XL",
78
+ "qwen3_5_9b_Q4_K_XL",
79
+ "qwen3_5_27b_Q4_K_XL",
80
+ "qwen3_5_35b_Q4_K_XL",
81
+ ]
82
+
83
+ /** Mapeo de cada modelo a su mmproj correspondiente */
84
+ export const MODEL_MMPROJ_MAP: Record<ModelId, ModelId | undefined> = {
85
+ mmproj: undefined,
86
+ mmproj_gemma4_12b: undefined,
87
+ mmproj_gemma4_26b: undefined,
88
+ mmproj_gemma4_31b: undefined,
89
+ mmproj_qwen3_5_2b: undefined,
90
+ mmproj_qwen3_5_4b: undefined,
91
+ mmproj_qwen3_5_9b: undefined,
92
+ mmproj_qwen3_5_27b: undefined,
93
+ mmproj_qwen3_5_35b: undefined,
94
+ e2b_Q4_K_XL: undefined,
95
+ e4b_Q4_K_XL: "mmproj",
96
+ gemma4_12b_Q4_K_XL: "mmproj_gemma4_12b",
97
+ gemma4_26b_Q4_K_M: "mmproj_gemma4_26b",
98
+ gemma4_31b_Q4_K_XL: "mmproj_gemma4_31b",
99
+ qwen3_5_2b_Q4_K_XL: "mmproj_qwen3_5_2b",
100
+ qwen3_5_4b_Q4_K_XL: "mmproj_qwen3_5_4b",
101
+ qwen3_5_9b_Q4_K_XL: "mmproj_qwen3_5_9b",
102
+ qwen3_5_27b_Q4_K_XL: "mmproj_qwen3_5_27b",
103
+ qwen3_5_35b_Q4_K_XL: "mmproj_qwen3_5_35b",
104
+ }
105
+
106
+ /** Devuelve el ID del mmproj asociado a un modelo, o undefined si no requiere visión */
107
+ export function getModelMMProjId(modelId: ModelId): ModelId | undefined {
108
+ return MODEL_MMPROJ_MAP[modelId]
109
+ }
32
110
 
33
111
  export const MODEL_FILES: Record<ModelId, string> = {
34
112
  mmproj: "mmproj-BF16.gguf",
113
+ mmproj_gemma4_12b: "mmproj-gemma4-12b-BF16.gguf",
114
+ mmproj_gemma4_26b: "mmproj-gemma4-26b-BF16.gguf",
115
+ mmproj_gemma4_31b: "mmproj-gemma4-31b-BF16.gguf",
116
+ mmproj_qwen3_5_2b: "mmproj-qwen3.5-2b-F16.gguf",
117
+ mmproj_qwen3_5_4b: "mmproj-qwen3.5-4b-F16.gguf",
118
+ mmproj_qwen3_5_9b: "mmproj-qwen3.5-9b-F16.gguf",
119
+ mmproj_qwen3_5_27b: "mmproj-qwen3.5-27b-F16.gguf",
120
+ mmproj_qwen3_5_35b: "mmproj-qwen3.5-35b-F16.gguf",
35
121
  e2b_Q4_K_XL: "gemma-4-E2B-it-UD-Q4_K_XL.gguf",
36
122
  e4b_Q4_K_XL: "gemma-4-E4B-it-UD-Q4_K_XL.gguf",
123
+ gemma4_12b_Q4_K_XL: "gemma-4-12b-it-UD-Q4_K_XL.gguf",
124
+ gemma4_26b_Q4_K_M: "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf",
125
+ gemma4_31b_Q4_K_XL: "gemma-4-31B-it-UD-Q4_K_XL.gguf",
126
+ qwen3_5_2b_Q4_K_XL: "Qwen3.5-2B-UD-Q4_K_XL.gguf",
127
+ qwen3_5_4b_Q4_K_XL: "Qwen3.5-4B-UD-Q4_K_XL.gguf",
128
+ qwen3_5_9b_Q4_K_XL: "Qwen3.5-9B-UD-Q4_K_XL.gguf",
129
+ qwen3_5_27b_Q4_K_XL: "Qwen3.5-27B-UD-Q4_K_XL.gguf",
130
+ qwen3_5_35b_Q4_K_XL: "Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf",
37
131
  }
38
132
 
39
133
  export function getModelPath(modelId: ModelId): string {
@@ -65,13 +159,14 @@ export async function downloadFile(
65
159
  const { done, value } = await reader.read()
66
160
  if (done) break
67
161
 
68
- writer.write(value)
162
+ await writer.write(value)
69
163
  downloaded += value.byteLength
70
164
  if (onProgress) onProgress(downloaded, total)
71
165
  }
166
+ await writer.flush()
72
167
  await writer.end()
73
168
  } catch (err) {
74
- await writer.end()
169
+ try { await writer.end() } catch { /* ignore */ }
75
170
  throw err
76
171
  }
77
172
  }
@@ -191,9 +286,10 @@ export async function downloadModel(
191
286
  await downloadFile(url, dest, onProgress)
192
287
  console.log(`[hive-local] ✓ Modelo ${modelId} descargado`)
193
288
 
194
- // Advertir si el modelo requiere visión y no tenemos mmproj (NO descargar automáticamente)
195
- if (VISION_MODELS.includes(modelId) && !isModelDownloaded("mmproj")) {
196
- console.warn(`[hive-local] ⚠️ El modelo ${modelId} requiere el proyector de visión (mmproj). Descárgalo manualmente desde la UI.`)
289
+ // Advertir si el modelo requiere visión y no tenemos su mmproj (NO descargar automáticamente)
290
+ const mmprojId = getModelMMProjId(modelId)
291
+ if (mmprojId && !isModelDownloaded(mmprojId)) {
292
+ console.warn(`[hive-local] ⚠️ El modelo ${modelId} requiere el proyector de visión (${mmprojId}). Descárgalo manualmente desde la UI.`)
197
293
  }
198
294
 
199
295
  return dest
@@ -206,11 +302,34 @@ export async function installMMProj(): Promise<string> {
206
302
 
207
303
  /** Lista modelos disponibles localmente (solo modelos de texto/visión finales) */
208
304
  export function listLocalModels(): { id: ModelId; name: string; size: string; downloaded: boolean }[] {
209
- const models: ModelId[] = ["e2b_Q4_K_XL", "e4b_Q4_K_XL"]
305
+ const models: ModelId[] = [
306
+ "e2b_Q4_K_XL",
307
+ "e4b_Q4_K_XL",
308
+ "gemma4_12b_Q4_K_XL",
309
+ "gemma4_26b_Q4_K_M",
310
+ "gemma4_31b_Q4_K_XL",
311
+ "qwen3_5_2b_Q4_K_XL",
312
+ "qwen3_5_4b_Q4_K_XL",
313
+ "qwen3_5_9b_Q4_K_XL",
314
+ "qwen3_5_27b_Q4_K_XL",
315
+ "qwen3_5_35b_Q4_K_XL",
316
+ ]
317
+ const sizeMap: Record<string, string> = {
318
+ e2b_Q4_K_XL: "~3 GB",
319
+ e4b_Q4_K_XL: "~5 GB",
320
+ gemma4_12b_Q4_K_XL: "~7 GB",
321
+ gemma4_26b_Q4_K_M: "~10 GB",
322
+ gemma4_31b_Q4_K_XL: "~14 GB",
323
+ qwen3_5_2b_Q4_K_XL: "~1.2 GB",
324
+ qwen3_5_4b_Q4_K_XL: "~2.5 GB",
325
+ qwen3_5_9b_Q4_K_XL: "~5.4 GB",
326
+ qwen3_5_27b_Q4_K_XL: "~16 GB",
327
+ qwen3_5_35b_Q4_K_XL: "~21 GB",
328
+ }
210
329
  return models.map((id) => ({
211
330
  id,
212
331
  name: MODEL_FILES[id],
213
- size: id.includes("Q4_K_XL") ? "~5-8 GB" : "~2-3 GB",
332
+ size: sizeMap[id] || "~?",
214
333
  downloaded: isModelDownloaded(id),
215
334
  }))
216
335
  }
@@ -15,9 +15,11 @@ export {
15
15
  listLocalModels,
16
16
  isModelDownloaded,
17
17
  getModelPath,
18
+ getModelMMProjId,
18
19
  BIN_DIR,
19
20
  MODELS_DIR,
20
21
  HF_MODEL_URLS,
22
+ MODEL_MMPROJ_MAP,
21
23
  } from "./downloader"
22
24
  export type { ModelId } from "./downloader"
23
25
 
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
  import { existsSync } from "fs"
8
- import { MODELS_DIR, getModelPath, type ModelId } from "./downloader"
8
+ import { MODELS_DIR, getModelPath, getModelMMProjId, type ModelId } from "./downloader"
9
9
 
10
10
  export interface ModelConfig {
11
11
  modelPath: string
@@ -43,8 +43,9 @@ export function getModelConfig(modelId: ModelId, port: number = 8081): ModelConf
43
43
  throw new Error(`Modelo no descargado: ${modelId}. Ejecuta downloadModel("${modelId}") primero.`)
44
44
  }
45
45
 
46
- const mmprojPath = getModelPath("mmproj")
47
- const hasMMProj = existsSync(mmprojPath)
46
+ const mmprojId = getModelMMProjId(modelId)
47
+ const mmprojPath = mmprojId ? getModelPath(mmprojId) : undefined
48
+ const hasMMProj = mmprojPath ? existsSync(mmprojPath) : false
48
49
 
49
50
  return {
50
51
  modelPath,
@@ -2,6 +2,7 @@ import { getDb } from "../storage/sqlite"
2
2
 
3
3
  export interface ResolveContextResult {
4
4
  userId: string
5
+ threadId: string
5
6
  agentId: string
6
7
  isNewUser: boolean
7
8
  }
@@ -51,8 +52,11 @@ export function resolveContext(options: ResolveContextOptions): ResolveContextRe
51
52
  .get()
52
53
 
53
54
  const agentId = coordinatorAgent?.id || "bee"
55
+ // One canonical conversation thread is shared across channels. Transport
56
+ // session IDs route replies; conversations.thread_id owns agent context.
57
+ const threadId = userId
54
58
 
55
- return { userId, agentId, isNewUser }
59
+ return { userId, threadId, agentId, isNewUser }
56
60
  }
57
61
 
58
62
  export function getDefaultAgentId(): string {
@@ -1,4 +1,6 @@
1
1
  import type { Config, Binding } from "../config/loader.ts";
2
+ import { homedir } from "node:os";
3
+ import * as path from "node:path";
2
4
 
3
5
  export interface RoutingContext {
4
6
  channel: string;
@@ -113,12 +115,12 @@ export class Router {
113
115
  const agent = agents.find((a) => a.id === agentId);
114
116
 
115
117
  if (agent?.workspace) {
116
- return agent.workspace.replace(/^~/, process.env.HOME ?? "");
118
+ return agent.workspace.replace(/^~/, homedir());
117
119
  }
118
120
 
119
- const baseDir = this.config.agent?.baseDir?.replace(/^~/, process.env.HOME ?? "")
120
- ?? `${process.env.HOME}/.hive/agents`;
121
-
122
- return `${baseDir}/${agentId}/workspace`;
121
+ const baseDir = this.config.agent?.baseDir?.replace(/^~/, homedir())
122
+ ?? path.join(homedir(), ".hive", "agents");
123
+
124
+ return path.join(baseDir, agentId, "workspace");
123
125
  }
124
126
  }
@@ -4,7 +4,7 @@
4
4
  * POST /api/chat
5
5
  * {
6
6
  * "message": "Mensaje para el coordinador",
7
- * "thread_id": "ID de sesión (opcional, se genera si no existe)",
7
+ * "thread_id": "conversations.thread_id (opcional, usa el thread canónico del usuario si no existe)",
8
8
  * "channel": "canal (opcional, default: webchat)"
9
9
  * }
10
10
  */
@@ -12,12 +12,11 @@
12
12
  import { getDb } from "../../storage/sqlite";
13
13
  import { resolveUserId, resolveAgentId } from "../../storage/onboarding";
14
14
  import { laneQueue } from "../lane-queue";
15
- import { getRecentMessages } from "../../agent/conversation-store";
16
15
  import { AgentRunner } from "../../agent/providers";
17
16
  import { logger } from "../../utils/logger";
18
- import { getUserDate, getUserTime } from "../../utils/date";
19
17
 
20
18
  const log = logger.child("api:chat");
19
+ export const DEFAULT_CHAT_HISTORY_LIMIT = 40;
21
20
 
22
21
  export interface ChatRequest {
23
22
  message: string;
@@ -34,6 +33,11 @@ export interface ChatResponse {
34
33
  error?: string;
35
34
  }
36
35
 
36
+ export function resolveChatThreadId(finalUserId: string, requestedThreadId?: string): string {
37
+ const trimmedThreadId = requestedThreadId?.trim();
38
+ return trimmedThreadId || finalUserId || "default";
39
+ }
40
+
37
41
  export async function handleChat(
38
42
  req: Request,
39
43
  addCorsHeaders: (res: Response, req: Request) => Response
@@ -60,8 +64,8 @@ export async function handleChat(
60
64
  // Resolve agent ID (coordinator by default)
61
65
  const finalAgentId = agentId || resolveAgentId(null) || "main";
62
66
 
63
- // Generate or use provided thread_id
64
- const threadId = thread_id || `${finalUserId}-${Date.now()}`;
67
+ // conversations.thread_id is the context key; never generate a per-request thread.
68
+ const threadId = resolveChatThreadId(finalUserId, thread_id);
65
69
 
66
70
  log.info(`[chat] Processing message from user=${finalUserId} agent=${finalAgentId} thread=${threadId}`);
67
71
 
@@ -86,15 +90,10 @@ export async function handleChat(
86
90
  // Format message with timestamp
87
91
  const messageContent = `[Timestamp: ${exactTime} (${userTimezone})]\n${message}`;
88
92
 
89
- // Get recent conversation history
90
- const history = getRecentMessages(threadId, 15);
91
- const messages = [
92
- ...history.map((row) => ({
93
- role: row.role as "user" | "assistant" | "system",
94
- content: row.content,
95
- })),
96
- { role: "user" as const, content: messageContent }
97
- ];
93
+ // AgentLoop persists this user message, then compileContext loads the last
94
+ // 15 messages from conversations by threadId. Prepending history here is
95
+ // ineffective because AgentLoop.stream only consumes the latest user input.
96
+ const messages = [{ role: "user" as const, content: messageContent }];
98
97
 
99
98
  // Get provider config from DB
100
99
  const agent = db.query<any, [string]>(
@@ -124,6 +123,7 @@ export async function handleChat(
124
123
  maxSteps: 15,
125
124
  threadId,
126
125
  userId: finalUserId,
126
+ agentId: finalAgentId,
127
127
  channel,
128
128
  onStep: async (step) => {
129
129
  if (step.type === "text" && step.message) {
@@ -200,12 +200,12 @@ export async function handleChat(
200
200
  export async function handleGetChatHistory(req: Request, addCorsHeaders: (r: Response, req: Request) => Response): Promise<Response> {
201
201
  const url = new URL(req.url)
202
202
  const threadId = url.searchParams.get("sessionId") || url.searchParams.get("threadId") || "default"
203
- const limit = parseInt(url.searchParams.get("limit") || "15")
203
+ const limit = parseInt(url.searchParams.get("limit") || String(DEFAULT_CHAT_HISTORY_LIMIT))
204
204
 
205
205
  const messages = getDb().query(`
206
206
  SELECT id, thread_id, channel, role, content, tool_calls_json, tool_call_id, reasoning_content, token_count, created_at, updated_at FROM conversations
207
207
  WHERE thread_id = ? AND role IN ('user', 'assistant')
208
- ORDER BY created_at DESC
208
+ ORDER BY id DESC
209
209
  LIMIT ?
210
210
  `).all(threadId, limit) as Record<string, unknown>[]
211
211
 
@@ -0,0 +1,16 @@
1
+ import { apiRequestTool } from "../../tools/api/api-request.ts";
2
+
3
+ export async function handleHttpRequest(req: Request, addCorsHeaders: (r: Response, req: Request) => Response): Promise<Response> {
4
+ try {
5
+ const body = await req.json().catch(() => ({}));
6
+
7
+ const result = await apiRequestTool.execute(body, { configurable: {} });
8
+
9
+ return addCorsHeaders(Response.json(result), req);
10
+ } catch (error) {
11
+ return addCorsHeaders(Response.json({
12
+ ok: false,
13
+ error: `Request failed: ${(error as Error).message}`,
14
+ }), req);
15
+ }
16
+ }
@@ -19,6 +19,15 @@ let installing = false
19
19
  let installLogs: string[] = []
20
20
  let downloadingModelId: string | null = null
21
21
 
22
+ interface DownloadProgress {
23
+ modelId: string
24
+ downloaded: number
25
+ total: number
26
+ percent: number
27
+ }
28
+
29
+ let currentDownloadProgress: DownloadProgress | null = null
30
+
22
31
  async function getInstalledStatus() {
23
32
  const { LLAMA_CPP_DEFAULT_VER } = await import("../llm-local/detector")
24
33
  const gpu = await detectGPU()
@@ -183,7 +192,12 @@ export async function handleDownloadLLMModel(
183
192
  ;(async () => {
184
193
  try {
185
194
  await downloadModel(modelId, (d, t) => {
186
- // Opcional: Podríamos emitir eventos por WS si quisiéramos progreso real en UI
195
+ currentDownloadProgress = {
196
+ modelId,
197
+ downloaded: d,
198
+ total: t,
199
+ percent: t > 0 ? Math.round((d / t) * 100) : 0,
200
+ }
187
201
  })
188
202
  installLogs.push(`✓ Modelo ${modelId} descargado exitosamente`)
189
203
  } catch (err) {
@@ -191,6 +205,7 @@ export async function handleDownloadLLMModel(
191
205
  installLogs.push(`[error] Error descargando ${modelId}: ${msg}`)
192
206
  } finally {
193
207
  downloadingModelId = null
208
+ currentDownloadProgress = null
194
209
  }
195
210
  })()
196
211
 
@@ -207,7 +222,15 @@ export async function handleStartLocalLLM(
207
222
  } catch { /* ignore if no body */ }
208
223
 
209
224
  const mode = body.mode || "TEXT"
210
- const modelId = body.modelId || getRecommendedModel(mode.toLowerCase() as any)
225
+ // If no modelId specified, prefer the recommended model; fall back to the
226
+ // first downloaded model so the user doesn't get an error when they only
227
+ // downloaded a non-default model.
228
+ const recommended = getRecommendedModel(mode.toLowerCase() as any)
229
+ const allModels = listLocalModels()
230
+ const downloadedModels = allModels.filter(m => m.downloaded)
231
+ const resolvedModelId: string =
232
+ body.modelId ||
233
+ (downloadedModels.find(m => m.id === recommended) ? recommended : (downloadedModels[0]?.id ?? recommended))
211
234
 
212
235
  const status = await getInstalledStatus()
213
236
  if (!status.installed && !status.binaryExists) {
@@ -217,11 +240,18 @@ export async function handleStartLocalLLM(
217
240
  )
218
241
  }
219
242
 
243
+ if (downloadedModels.length === 0) {
244
+ return addCors(
245
+ Response.json({ started: false, reason: "No hay modelos descargados. Descarga un modelo primero desde la sección de modelos." }, { status: 400 }),
246
+ req
247
+ )
248
+ }
249
+
220
250
  try {
221
- installLogs.push(`[llm-server] Iniciando servidor en modo ${mode} con modelo ${modelId}...`)
222
- await llamaManager.start(mode, modelId as any)
251
+ installLogs.push(`[llm-server] Iniciando servidor en modo ${mode} con modelo ${resolvedModelId}...`)
252
+ await llamaManager.start(mode, resolvedModelId as any)
223
253
  installLogs.push(`[llm-server] Servidor ${mode} iniciado correctamente.`)
224
- return addCors(Response.json({ started: true, mode }), req)
254
+ return addCors(Response.json({ started: true, mode, modelId: resolvedModelId }), req)
225
255
  } catch (err) {
226
256
  const msg = err instanceof Error ? err.message : String(err)
227
257
  installLogs.push(`[error] Servidor LLM falló al iniciar: ${msg}`)
@@ -251,6 +281,22 @@ export async function handleStopLocalLLM(
251
281
  * Verifica el estado del LLM local al iniciar el gateway.
252
282
  * NO auto-inicia el servidor ni descarga nada — el usuario debe hacerlo manualmente.
253
283
  */
284
+ export async function handleGetDownloadProgress(
285
+ req: Request,
286
+ addCors: (r: Response, req: Request) => Response
287
+ ): Promise<Response> {
288
+ if (!currentDownloadProgress) {
289
+ return addCors(Response.json({ active: false }), req)
290
+ }
291
+ return addCors(
292
+ Response.json({
293
+ active: true,
294
+ ...currentDownloadProgress,
295
+ }),
296
+ req
297
+ )
298
+ }
299
+
254
300
  export async function initializeLocalLLM() {
255
301
  try {
256
302
  const status = await getInstalledStatus()