@johpaz/hive-agents 0.0.38 → 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 (214) hide show
  1. package/README.md +18 -18
  2. package/dist/hive.js +3428 -2603
  3. package/dist/tool-worker.js +2085 -1820
  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-DGNLDXjR.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-CnMO1FN8.js → CanvasPage-Cvs5ctza.js} +7 -7
  10. package/dist/ui/assets/ChannelsPage-C5m_L7P9.js +8 -0
  11. package/dist/ui/{dist/assets/DashboardPage-VyXXp3U1.js → assets/DashboardPage-CztbRQdm.js} +2 -2
  12. package/dist/ui/assets/{LoginPage-DPj2s2Qq.js → LoginPage-OMsrx5oj.js} +1 -1
  13. package/dist/ui/assets/LogsPage-CcYYwjgF.js +1 -0
  14. package/dist/ui/{dist/assets/MeetingPage-2ky_hKiG.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-B-hDZUM2.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/{dist/assets/WebChatPage-BuGT2AL0.js → assets/WebChatPage-c-7S9jnT.js} +2 -2
  21. package/dist/ui/assets/accordion-DAbcVQCn.js +1 -0
  22. package/dist/ui/{dist/assets/alert-Bq6awLlW.js → assets/alert-D_2Y3qjL.js} +1 -1
  23. package/dist/ui/{dist/assets/alert-dialog-DQvltYmf.js → assets/alert-dialog-CpMxaNcu.js} +1 -1
  24. package/dist/ui/assets/{badge-DXUDdTed.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-bI9jImCS.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-Cg8zdT52.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-CrH0Jj3v.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/assets/{slider-CsiUDxc3.js → 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-DGNLDXjR.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-CnMO1FN8.js → CanvasPage-Cvs5ctza.js} +7 -7
  54. package/dist/ui/dist/assets/ChannelsPage-C5m_L7P9.js +8 -0
  55. package/dist/ui/{assets/DashboardPage-VyXXp3U1.js → dist/assets/DashboardPage-CztbRQdm.js} +2 -2
  56. package/dist/ui/dist/assets/{LoginPage-DPj2s2Qq.js → LoginPage-OMsrx5oj.js} +1 -1
  57. package/dist/ui/dist/assets/LogsPage-CcYYwjgF.js +1 -0
  58. package/dist/ui/{assets/MeetingPage-2ky_hKiG.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-B-hDZUM2.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/{assets/WebChatPage-BuGT2AL0.js → dist/assets/WebChatPage-c-7S9jnT.js} +2 -2
  65. package/dist/ui/dist/assets/accordion-DAbcVQCn.js +1 -0
  66. package/dist/ui/{assets/alert-Bq6awLlW.js → dist/assets/alert-D_2Y3qjL.js} +1 -1
  67. package/dist/ui/{assets/alert-dialog-DQvltYmf.js → dist/assets/alert-dialog-CpMxaNcu.js} +1 -1
  68. package/dist/ui/dist/assets/{badge-DXUDdTed.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-bI9jImCS.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-Cg8zdT52.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-CrH0Jj3v.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/dist/assets/{slider-CsiUDxc3.js → 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/llm-client.ts +4 -0
  105. package/packages/core/src/agent/llm-providers/anthropic.ts +23 -8
  106. package/packages/core/src/agent/llm-providers/interface.ts +5 -1
  107. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  108. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  109. package/packages/core/src/channels/whatsapp.ts +13 -1
  110. package/packages/core/src/config/loader.ts +7 -7
  111. package/packages/core/src/gateway/helpers/path.ts +2 -1
  112. package/packages/core/src/gateway/initializer.ts +4 -4
  113. package/packages/core/src/gateway/llm-local/downloader.ts +130 -11
  114. package/packages/core/src/gateway/llm-local/index.ts +2 -0
  115. package/packages/core/src/gateway/llm-local/models.ts +4 -3
  116. package/packages/core/src/gateway/router.ts +7 -5
  117. package/packages/core/src/gateway/routes/http-client.ts +16 -0
  118. package/packages/core/src/gateway/routes/llm-local.ts +51 -5
  119. package/packages/core/src/gateway/routes/providers.ts +43 -2
  120. package/packages/core/src/gateway/server.ts +11 -2
  121. package/packages/core/src/gateway/tts/src/install.ts +17 -9
  122. package/packages/core/src/storage/crypto.ts +152 -20
  123. package/packages/core/src/storage/migrate.ts +51 -18
  124. package/packages/core/src/storage/seed.ts +38 -2
  125. package/packages/core/src/tool-runtime/index.ts +22 -1
  126. package/packages/core/src/tools/api/api-request.ts +174 -0
  127. package/packages/core/src/tools/api/index.ts +16 -0
  128. package/packages/core/src/tools/index.ts +12 -0
  129. package/packages/core/src/tools/web/browser-click.ts +2 -2
  130. package/packages/core/src/tools/web/browser-extract.ts +22 -6
  131. package/packages/core/src/tools/web/browser-navigate.ts +34 -18
  132. package/packages/core/src/tools/web/browser-screenshot.ts +40 -8
  133. package/packages/core/src/tools/web/browser-script.ts +2 -2
  134. package/packages/core/src/tools/web/browser-service.ts +295 -341
  135. package/packages/core/src/tools/web/browser-type.ts +5 -10
  136. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  137. package/packages/core/src/tools/web/index.ts +1 -1
  138. package/packages/core/src/utils/logger.ts +2 -1
  139. package/packages/mcp/src/manager.ts +2 -1
  140. package/packages/skills/src/bundled/api/api_client/SKILL.md +132 -0
  141. package/packages/skills/src/bundled-data.generated.ts +1274 -1217
  142. package/packages/skills/src/loader.ts +2 -1
  143. package/dist/ui/assets/AgentCreateForm-0oFbN3gj.js +0 -1
  144. package/dist/ui/assets/AgentDetailPage-BJ4L2fNJ.js +0 -1
  145. package/dist/ui/assets/AgentNewPage-B3n0LUck.js +0 -1
  146. package/dist/ui/assets/ChannelsPage-fbF8K4MR.js +0 -8
  147. package/dist/ui/assets/LogsPage-B2lY9maY.js +0 -1
  148. package/dist/ui/assets/ProvidersPage-CEyUM2tD.js +0 -1
  149. package/dist/ui/assets/SettingsPage-eO0i3g8p.js +0 -9
  150. package/dist/ui/assets/SetupPage-ByYqTELb.js +0 -1
  151. package/dist/ui/assets/accordion-C5d5Rm5z.js +0 -1
  152. package/dist/ui/assets/globe-DeCQTCDJ.js +0 -1
  153. package/dist/ui/assets/index-B2fCYtTS.css +0 -2
  154. package/dist/ui/assets/index-CQ7fn00w.js +0 -116
  155. package/dist/ui/assets/progress-BherYzY6.js +0 -1
  156. package/dist/ui/assets/scroll-area-DkeyX32e.js +0 -1
  157. package/dist/ui/assets/send-B0H5SEIE.js +0 -1
  158. package/dist/ui/assets/switch-BDwN8RYV.js +0 -1
  159. package/dist/ui/assets/table-CSc8ubon.js +0 -1
  160. package/dist/ui/assets/textarea-CXgXWKrT.js +0 -1
  161. package/dist/ui/assets/useProviders-CnlC_qCS.js +0 -1
  162. package/dist/ui/dist/assets/AgentCreateForm-0oFbN3gj.js +0 -1
  163. package/dist/ui/dist/assets/AgentDetailPage-BJ4L2fNJ.js +0 -1
  164. package/dist/ui/dist/assets/AgentNewPage-B3n0LUck.js +0 -1
  165. package/dist/ui/dist/assets/ChannelsPage-fbF8K4MR.js +0 -8
  166. package/dist/ui/dist/assets/LogsPage-B2lY9maY.js +0 -1
  167. package/dist/ui/dist/assets/ProvidersPage-CEyUM2tD.js +0 -1
  168. package/dist/ui/dist/assets/SettingsPage-eO0i3g8p.js +0 -9
  169. package/dist/ui/dist/assets/SetupPage-ByYqTELb.js +0 -1
  170. package/dist/ui/dist/assets/accordion-C5d5Rm5z.js +0 -1
  171. package/dist/ui/dist/assets/globe-DeCQTCDJ.js +0 -1
  172. package/dist/ui/dist/assets/index-B2fCYtTS.css +0 -2
  173. package/dist/ui/dist/assets/index-CQ7fn00w.js +0 -116
  174. package/dist/ui/dist/assets/progress-BherYzY6.js +0 -1
  175. package/dist/ui/dist/assets/scroll-area-DkeyX32e.js +0 -1
  176. package/dist/ui/dist/assets/send-B0H5SEIE.js +0 -1
  177. package/dist/ui/dist/assets/switch-BDwN8RYV.js +0 -1
  178. package/dist/ui/dist/assets/table-CSc8ubon.js +0 -1
  179. package/dist/ui/dist/assets/textarea-CXgXWKrT.js +0 -1
  180. package/dist/ui/dist/assets/useProviders-CnlC_qCS.js +0 -1
  181. /package/dist/ui/assets/{card-CNf6BS2e.js → card-CXAm46at.js} +0 -0
  182. /package/dist/ui/assets/{cpu-Cdgc_B1K.js → cpu-DSpPVLAz.js} +0 -0
  183. /package/dist/ui/assets/{download-C3ifGMjJ.js → download-D9ZyUZZR.js} +0 -0
  184. /package/dist/ui/assets/{external-link-BvxYeTP1.js → external-link-CHPbUorN.js} +0 -0
  185. /package/dist/ui/assets/{eye-DqNTU_GD.js → eye-epHJZ_nQ.js} +0 -0
  186. /package/dist/ui/assets/{file-text-BT_9S9SM.js → file-text-BEjEmgby.js} +0 -0
  187. /package/dist/ui/assets/{folder-open-BhH8y9ac.js → folder-open-iQMHVEqS.js} +0 -0
  188. /package/dist/ui/assets/{format-GVHeOyWI.js → format-oFACFaca.js} +0 -0
  189. /package/dist/ui/assets/{gateway-url-COCbW0IR.js → gateway-url-iG-C6Agn.js} +0 -0
  190. /package/dist/ui/assets/{gauge-D_TMa4i9.js → gauge-D0_GMEcq.js} +0 -0
  191. /package/dist/ui/assets/{settings-Ds4SqD8s.js → settings-CcMGI1iU.js} +0 -0
  192. /package/dist/ui/assets/{sparkles-yUEb-7oH.js → sparkles-D6fx8JC5.js} +0 -0
  193. /package/dist/ui/assets/{trash-2-CNjMkoq6.js → trash-2-BHRa5ft9.js} +0 -0
  194. /package/dist/ui/assets/{triangle-alert-C9Y8Ub4X.js → triangle-alert-D4nwAVbc.js} +0 -0
  195. /package/dist/ui/assets/{vendor-router-C9pIYwbJ.js → vendor-router-pCP7sjma.js} +0 -0
  196. /package/dist/ui/assets/{volume-2-CeSXNDv4.js → volume-2-B6tkRy2u.js} +0 -0
  197. /package/dist/ui/assets/{zap-hlXjpSeA.js → zap-QO7iWMRg.js} +0 -0
  198. /package/dist/ui/dist/assets/{card-CNf6BS2e.js → card-CXAm46at.js} +0 -0
  199. /package/dist/ui/dist/assets/{cpu-Cdgc_B1K.js → cpu-DSpPVLAz.js} +0 -0
  200. /package/dist/ui/dist/assets/{download-C3ifGMjJ.js → download-D9ZyUZZR.js} +0 -0
  201. /package/dist/ui/dist/assets/{external-link-BvxYeTP1.js → external-link-CHPbUorN.js} +0 -0
  202. /package/dist/ui/dist/assets/{eye-DqNTU_GD.js → eye-epHJZ_nQ.js} +0 -0
  203. /package/dist/ui/dist/assets/{file-text-BT_9S9SM.js → file-text-BEjEmgby.js} +0 -0
  204. /package/dist/ui/dist/assets/{folder-open-BhH8y9ac.js → folder-open-iQMHVEqS.js} +0 -0
  205. /package/dist/ui/dist/assets/{format-GVHeOyWI.js → format-oFACFaca.js} +0 -0
  206. /package/dist/ui/dist/assets/{gateway-url-COCbW0IR.js → gateway-url-iG-C6Agn.js} +0 -0
  207. /package/dist/ui/dist/assets/{gauge-D_TMa4i9.js → gauge-D0_GMEcq.js} +0 -0
  208. /package/dist/ui/dist/assets/{settings-Ds4SqD8s.js → settings-CcMGI1iU.js} +0 -0
  209. /package/dist/ui/dist/assets/{sparkles-yUEb-7oH.js → sparkles-D6fx8JC5.js} +0 -0
  210. /package/dist/ui/dist/assets/{trash-2-CNjMkoq6.js → trash-2-BHRa5ft9.js} +0 -0
  211. /package/dist/ui/dist/assets/{triangle-alert-C9Y8Ub4X.js → triangle-alert-D4nwAVbc.js} +0 -0
  212. /package/dist/ui/dist/assets/{vendor-router-C9pIYwbJ.js → vendor-router-pCP7sjma.js} +0 -0
  213. /package/dist/ui/dist/assets/{volume-2-CeSXNDv4.js → volume-2-B6tkRy2u.js} +0 -0
  214. /package/dist/ui/dist/assets/{zap-hlXjpSeA.js → zap-QO7iWMRg.js} +0 -0
@@ -1,234 +1,166 @@
1
1
  /**
2
- * BrowserService — lanza Chrome/Brave VISIBLE y lo controla via CDP (WebSocket).
2
+ * BrowserService — Browser automation via agent-browser CLI (Rust).
3
3
  *
4
4
  * Flujo:
5
- * 1. Detecta el browser instalado (nativo o Flatpak).
6
- * 2. Lo lanza con Bun.spawn + --remote-debugging-port=9222.
7
- * 3. CDPClient conecta via WebSocket al DevTools endpoint.
8
- * 4. Todas las herramientas de browser usan CDPClient como si fuera Puppeteer/Playwright.
5
+ * 1. Detecta si agent-browser está instalado (lazy install en primer uso).
6
+ * 2. Ejecuta comandos via CLI con --json para output estructurado.
7
+ * 3. El daemon de agent-browser maneja Chrome internamente via CDP.
8
+ * 4. Las herramientas de browser usan AgentBrowserView (API compatible con CDPClient).
9
9
  */
10
10
 
11
11
  import { logger } from "../../utils/logger.ts";
12
12
  import type { Config } from "../../config/loader.ts";
13
- import { existsSync, writeFileSync, chmodSync } from "fs";
14
- import { tmpdir } from "os";
13
+ import { existsSync, mkdirSync, readFileSync, rmSync } from "fs";
14
+ import { homedir, tmpdir } from "os";
15
+ import { dirname, join, resolve } from "path";
15
16
 
16
17
  const log = logger.child("browser-service");
17
18
 
18
- // ─── Detección del browser ────────────────────────────────────────────────────
19
-
20
- const FLATPAK_BROWSERS = [
21
- "com.google.Chrome",
22
- "com.brave.Browser",
23
- "org.chromium.Chromium",
24
- "com.microsoft.Edge",
25
- ];
26
-
27
- const NATIVE_PATHS: Record<string, string[]> = {
28
- linux: [
29
- "/usr/bin/google-chrome",
30
- "/usr/bin/google-chrome-stable",
31
- "/usr/bin/brave-browser",
32
- "/usr/bin/brave",
33
- "/usr/bin/chromium-browser",
34
- "/usr/bin/chromium",
35
- "/usr/bin/microsoft-edge",
36
- "/snap/bin/chromium",
37
- ],
38
- darwin: [
39
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
40
- "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
41
- "/Applications/Chromium.app/Contents/MacOS/Chromium",
42
- "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
43
- `${process.env.HOME}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
44
- ],
45
- win32: [
46
- `${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`,
47
- `${process.env.PROGRAMFILES}\\Google\\Chrome\\Application\\chrome.exe`,
48
- `${process.env["PROGRAMFILES(X86)"]}\\Google\\Chrome\\Application\\chrome.exe`,
49
- `${process.env.LOCALAPPDATA}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
50
- `${process.env.PROGRAMFILES}\\Microsoft\\Edge\\Application\\msedge.exe`,
51
- ],
52
- };
53
-
54
- export type LaunchSpec =
55
- | { kind: "native"; path: string }
56
- | { kind: "flatpak"; appId: string };
57
-
58
- export function detectBrowser(): LaunchSpec | undefined {
59
- if (process.env.BUN_CHROME_PATH && existsSync(process.env.BUN_CHROME_PATH)) {
60
- return { kind: "native", path: process.env.BUN_CHROME_PATH };
61
- }
62
- const platform = process.platform as string;
63
- const natives = (NATIVE_PATHS[platform] ?? NATIVE_PATHS.linux).filter(Boolean);
64
- const found = natives.find(p => existsSync(p));
65
- if (found) return { kind: "native", path: found };
66
-
67
- if (platform === "linux" && existsSync("/usr/bin/flatpak")) {
68
- for (const appId of FLATPAK_BROWSERS) {
69
- const r = Bun.spawnSync(["flatpak", "info", appId], { stdout: "pipe", stderr: "pipe" });
70
- if (r.exitCode === 0) return { kind: "flatpak", appId };
71
- }
19
+ // ─── Instalación lazy de agent-browser ────────────────────────────────────────
20
+
21
+ const HIVE_DIR = join(homedir(), ".hive");
22
+ const AGENT_BROWSER_DIR = join(HIVE_DIR, "agent-browser");
23
+ const AGENT_BROWSER_PKG_JSON = join(AGENT_BROWSER_DIR, "package.json");
24
+ const DEFAULT_SESSION_NAME = "hive";
25
+
26
+ /** Check if agent-browser is installed in the cache dir by running --version */
27
+ async function isAgentBrowserInstalled(): Promise<boolean> {
28
+ if (!existsSync(AGENT_BROWSER_PKG_JSON)) return false;
29
+ try {
30
+ const proc = Bun.spawn(["bun", "run", "agent-browser", "--version"], {
31
+ cwd: AGENT_BROWSER_DIR,
32
+ stdout: "pipe",
33
+ stderr: "pipe",
34
+ });
35
+ const exitCode = await proc.exited;
36
+ return exitCode === 0;
37
+ } catch {
38
+ return false;
72
39
  }
73
40
  }
74
41
 
75
- // ─── CDP Client ───────────────────────────────────────────────────────────────
42
+ async function installAgentBrowser(): Promise<void> {
43
+ mkdirSync(AGENT_BROWSER_DIR, { recursive: true });
76
44
 
77
- const CDP_PORT = 9222;
78
- const allInstances = new Set<CDPClient>();
45
+ // Create minimal package.json
46
+ const pkg = { name: "hive-agent-browser", version: "1.0.0", dependencies: {} };
47
+ await Bun.write(AGENT_BROWSER_PKG_JSON, JSON.stringify(pkg, null, 2));
79
48
 
80
- export class CDPClient {
81
- private ws: WebSocket | null = null;
82
- private proc: ReturnType<typeof Bun.spawn> | null = null;
83
- private cmdId = 0;
84
- private pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
85
- private _url = "";
86
- private _focusedSelector: string | null = null;
49
+ log.info("📦 Instalando agent-browser (primera vez, ~75MB)...");
50
+ const proc = Bun.spawn(["bun", "add", "agent-browser@latest"], {
51
+ cwd: AGENT_BROWSER_DIR,
52
+ stdout: "pipe",
53
+ stderr: "pipe",
54
+ });
87
55
 
88
- get url(): string { return this._url; }
89
- get title(): string { return ""; }
90
- get loading(): boolean { return false; }
91
- get isConnected(): boolean { return this.ws !== null && this.ws.readyState === WebSocket.OPEN; }
92
-
93
- // ── Launch ──────────────────────────────────────────────────────────────────
94
-
95
- async launch(spec: LaunchSpec): Promise<void> {
96
- const commonArgs = [
97
- `--remote-debugging-port=${CDP_PORT}`,
98
- "--no-first-run",
99
- "--no-default-browser-check",
100
- "--disable-popup-blocking",
101
- `--user-data-dir=${tmpdir()}/hive-browser-profile`,
102
- "about:blank",
103
- ];
104
-
105
- if (spec.kind === "native") {
106
- this.proc = Bun.spawn([spec.path, ...commonArgs], {
107
- stdout: "ignore",
108
- stderr: "ignore",
109
- });
110
- log.info(`Lanzando browser nativo: ${spec.path} (PID ${this.proc.pid})`);
111
- } else {
112
- this.proc = Bun.spawn(["flatpak", "run", spec.appId, ...commonArgs], {
113
- stdout: "ignore",
114
- stderr: "ignore",
115
- });
116
- log.info(`Lanzando Flatpak ${spec.appId} (PID ${this.proc.pid})`);
117
- }
56
+ const exitCode = await proc.exited;
57
+ const stderr = await new Response(proc.stderr).text();
118
58
 
119
- await this._waitForCDP();
120
- await this._connect();
121
- allInstances.add(this);
59
+ if (exitCode !== 0) {
60
+ throw new Error(`bun add agent-browser failed: ${stderr}`);
122
61
  }
123
62
 
124
- // ── CDP WebSocket ───────────────────────────────────────────────────────────
63
+ log.info("✅ agent-browser instalado.");
64
+ }
125
65
 
126
- private async _waitForCDP(timeout = 15000): Promise<void> {
127
- const deadline = Date.now() + timeout;
128
- while (Date.now() < deadline) {
129
- try {
130
- const r = await fetch(`http://localhost:${CDP_PORT}/json/version`);
131
- if (r.ok) return;
132
- } catch { /* not ready yet */ }
133
- await new Promise<void>(r => setTimeout(r, 300));
134
- }
135
- throw new Error(`CDP no respondió en ${timeout}ms en puerto ${CDP_PORT}`);
136
- }
137
-
138
- private async _connect(): Promise<void> {
139
- const r = await fetch(`http://localhost:${CDP_PORT}/json`);
140
- const targets = await r.json() as Array<{ type: string; webSocketDebuggerUrl: string }>;
141
- const target = targets.find(t => t.type === "page") ?? targets[0];
142
- if (!target?.webSocketDebuggerUrl) throw new Error("No hay target CDP disponible");
143
-
144
- await new Promise<void>((resolve, reject) => {
145
- const ws = new WebSocket(target.webSocketDebuggerUrl);
146
- ws.onopen = () => {
147
- this.ws = ws;
148
- ws.onmessage = (ev: MessageEvent) => {
149
- const msg = JSON.parse(ev.data as string) as {
150
- id?: number;
151
- result?: unknown;
152
- error?: { message: string };
153
- };
154
- if (msg.id !== undefined) {
155
- const p = this.pending.get(msg.id);
156
- if (p) {
157
- this.pending.delete(msg.id);
158
- if (msg.error) p.reject(new Error(msg.error.message));
159
- else p.resolve(msg.result ?? {});
160
- }
161
- }
162
- };
163
- resolve();
164
- };
165
- ws.onerror = () => reject(new Error("WebSocket CDP falló al conectar"));
166
- ws.onclose = () => {
167
- // Rechazar todos los pendientes
168
- for (const p of this.pending.values()) p.reject(new Error("CDP WebSocket cerrado"));
169
- this.pending.clear();
170
- };
171
- });
66
+ /** Run agent-browser CLI from the cache directory — cross-platform via bun run */
67
+ async function runAgentBrowser(
68
+ args: string[]
69
+ ): Promise<{ success: boolean; data?: any; error?: string }> {
70
+ const proc = Bun.spawn(["bun", "run", "agent-browser", ...args], {
71
+ cwd: AGENT_BROWSER_DIR,
72
+ stdout: "pipe",
73
+ stderr: "pipe",
74
+ });
172
75
 
173
- await this.cdp("Page.enable");
174
- await this.cdp("Runtime.enable");
76
+ const stdout = await new Response(proc.stdout).text();
77
+ const stderr = await new Response(proc.stderr).text();
78
+ const exitCode = await proc.exited;
79
+
80
+ if (exitCode !== 0 && !stdout.trim()) {
81
+ throw new Error(stderr || `agent-browser ${args[0]} failed`);
175
82
  }
176
83
 
177
- // ── CDP raw command ─────────────────────────────────────────────────────────
84
+ try {
85
+ const result = JSON.parse(stdout.trim().split("\n").pop() || "{}");
86
+ return result;
87
+ } catch {
88
+ return { success: true, data: { raw: stdout.trim() } };
89
+ }
90
+ }
178
91
 
179
- async cdp<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {
180
- if (!this.ws) throw new Error("CDP no conectado");
181
- const id = ++this.cmdId;
182
- return new Promise<T>((resolve, reject) => {
183
- this.pending.set(id, {
184
- resolve: v => resolve(v as T),
185
- reject,
92
+ async function ensureChromeInstalled(): Promise<void> {
93
+ log.info("🔍 Verificando Chrome para agent-browser...");
94
+ const res = await runAgentBrowser(["open", "about:blank", "--session", DEFAULT_SESSION_NAME, "--json"]);
95
+
96
+ if (!res.success) {
97
+ const err = res.error || "";
98
+ // Chrome not installed — trigger install
99
+ if (err.includes("not found") || err.includes("install")) {
100
+ log.info("📥 Descargando Chrome (agent-browser install)...");
101
+ const installProc = Bun.spawn(["bun", "run", "agent-browser", "install"], {
102
+ cwd: AGENT_BROWSER_DIR,
103
+ stdout: "pipe",
104
+ stderr: "pipe",
186
105
  });
187
- this.ws!.send(JSON.stringify({ id, method, params: params ?? {} }));
188
- });
106
+ const installExit = await installProc.exited;
107
+ if (installExit !== 0) {
108
+ const installErr = await new Response(installProc.stderr).text();
109
+ throw new Error(`agent-browser install failed: ${installErr}`);
110
+ }
111
+ log.info("✅ Chrome descargado.");
112
+ return;
113
+ }
114
+ throw new Error(`agent-browser chrome check failed: ${err}`);
189
115
  }
116
+ }
190
117
 
191
- // ── navigate ────────────────────────────────────────────────────────────────
118
+ // ─── AgentBrowserView (API compatible con CDPClient) ──────────────────────────
192
119
 
193
- async navigate(url: string): Promise<void> {
194
- this._focusedSelector = null;
195
- await this.cdp("Page.navigate", { url });
196
- // Esperar hasta document.readyState === 'complete'
197
- const deadline = Date.now() + 30000;
198
- while (Date.now() < deadline) {
199
- await new Promise<void>(r => setTimeout(r, 150));
200
- try {
201
- const res = await this.cdp<{ result: { value: string } }>("Runtime.evaluate", {
202
- expression: "document.readyState",
203
- returnByValue: true,
204
- });
205
- if (res.result?.value === "complete") break;
206
- } catch { /* continuar */ }
207
- }
208
- // Actualizar URL real (puede haber redirect)
209
- try {
210
- const res = await this.cdp<{ result: { value: string } }>("Runtime.evaluate", {
211
- expression: "location.href",
212
- returnByValue: true,
213
- });
214
- this._url = res.result?.value || url;
215
- } catch {
216
- this._url = url;
217
- }
120
+ export class AgentBrowserView {
121
+ private sessionName: string;
122
+ private _url = "";
123
+
124
+ get url(): string { return this._url; }
125
+ get title(): string { return ""; }
126
+ get loading(): boolean { return false; }
127
+ get isConnected(): boolean { return true; }
128
+
129
+ constructor(sessionName: string = DEFAULT_SESSION_NAME) {
130
+ this.sessionName = sessionName;
218
131
  }
219
132
 
220
- // ── evaluate ────────────────────────────────────────────────────────────────
133
+ protected async run(args: string[]): Promise<{ success: boolean; data?: any; error?: string }> {
134
+ return runAgentBrowser(["--session", this.sessionName, "--json", ...args]);
135
+ }
221
136
 
222
- async evaluate<T = unknown>(script: string): Promise<T> {
223
- const res = await this.cdp<{ result: { value: T } }>("Runtime.evaluate", {
224
- expression: `(async () => { return (${script}) })()`,
225
- returnByValue: true,
226
- awaitPromise: true,
227
- });
228
- return res.result?.value as T;
137
+ async navigate(url: string): Promise<void> {
138
+ // Ensure protocol
139
+ const target = /^https?:\/\//.test(url) ? url : `https://${url}`;
140
+ const res = await this.run(["open", target]);
141
+ if (!res.success) throw new Error(res.error || "navigate failed");
142
+ this._url = res.data?.url || target;
143
+ // Small delay to let JS settle (same as old implementation)
144
+ await new Promise(r => setTimeout(r, 500));
229
145
  }
230
146
 
231
- // ── screenshot ──────────────────────────────────────────────────────────────
147
+ async evaluate<T = unknown>(script: string): Promise<T> {
148
+ let wrapped = script;
149
+ const trimmed = script.trim();
150
+
151
+ // If script contains top-level await, wrap in async IIFE to make it valid JS
152
+ if (/\bawait\b/.test(script) && !trimmed.startsWith("(async") && !trimmed.startsWith("async function")) {
153
+ if (trimmed.startsWith("return")) {
154
+ wrapped = `(async () => { ${script} })()`;
155
+ } else {
156
+ wrapped = `(async () => { return ${script}; })()`;
157
+ }
158
+ }
159
+
160
+ const res = await this.run(["eval", wrapped]);
161
+ if (!res.success) throw new Error(res.error || "eval failed");
162
+ return res.data?.result as T;
163
+ }
232
164
 
233
165
  async screenshot(options?: {
234
166
  encoding?: "blob" | "buffer" | "base64" | "shmem";
@@ -236,156 +168,177 @@ export class CDPClient {
236
168
  quality?: number;
237
169
  clip?: { x: number; y: number; width: number; height: number; scale: number };
238
170
  }): Promise<string> {
239
- const params: Record<string, unknown> = {
240
- format: options?.format ?? "png",
241
- };
242
- if (options?.quality) params.quality = options.quality;
243
- if (options?.clip) params.clip = options.clip;
171
+ // Build args
172
+ const args: string[] = ["screenshot"];
244
173
 
245
- const res = await this.cdp<{ data: string }>("Page.captureScreenshot", params);
246
- return res.data;
247
- }
174
+ if (options?.format === "jpeg") {
175
+ args.push("--screenshot-format", "jpeg");
176
+ }
177
+ if (options?.quality) {
178
+ args.push("--screenshot-quality", String(options.quality));
179
+ }
248
180
 
249
- // ── click ───────────────────────────────────────────────────────────────────
181
+ // If clip/selector is provided, agent-browser screenshot accepts a positional selector
182
+ // For element screenshots, we can pass a selector as first positional arg
183
+ // But we don't have selector in options here — the old CDPClient didn't use it either
184
+ // screenshotElement helper handles element-specific screenshots
250
185
 
251
- async click(selector: string, _options?: Record<string, unknown>): Promise<void> {
252
- // 1. Verificar que el elemento existe y obtener coordenadas para visual feedback
253
- const box = await this.evaluate<{ x: number; y: number; width: number; height: number } | null>(`
254
- (() => {
255
- const el = document.querySelector(${JSON.stringify(selector)});
256
- if (!el) return null;
257
- el.scrollIntoView({ behavior: "instant", block: "center" });
258
- const r = el.getBoundingClientRect();
259
- return { x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2), width: r.width, height: r.height };
260
- })()
261
- `);
262
- if (!box) throw new Error(`Selector no encontrado: ${selector}`);
186
+ const res = await this.run(args);
187
+ if (!res.success) throw new Error(res.error || "screenshot failed");
263
188
 
264
- // 2. Mover el cursor CDP al elemento (visual feedback en el browser visible)
265
- await this.cdp("Input.dispatchMouseEvent", {
266
- type: "mouseMoved", x: box.x, y: box.y, button: "none",
267
- });
189
+ const path = res.data?.path as string;
190
+ if (!path) throw new Error("screenshot did not return a path");
191
+
192
+ const data = readFileSync(path);
193
+ const base64 = Buffer.from(data).toString("base64");
268
194
 
269
- // 3. element.click() para trigger fiable de onclick/event listeners
270
- await this.evaluate(`document.querySelector(${JSON.stringify(selector)}).click()`);
271
- this._focusedSelector = selector;
195
+ // Cleanup temp file
196
+ try { rmSync(path); } catch { /* ignore */ }
197
+
198
+ return base64;
272
199
  }
273
200
 
274
- // ── type ────────────────────────────────────────────────────────────────────
201
+ async click(selector: string, _options?: Record<string, unknown>): Promise<void> {
202
+ const res = await this.run(["click", selector]);
203
+ if (!res.success) throw new Error(res.error || `click failed: ${selector}`);
204
+ }
275
205
 
276
206
  async type(text: string): Promise<void> {
277
- // Si sabemos qué elemento fue clickeado, escribimos directamente en él.
278
- // Esto es más fiable que Input.insertText o dispatchKeyEvent char, que
279
- // dependen de que CDP tenga el focus sincronizado correctamente.
280
- if (this._focusedSelector) {
281
- const sel = this._focusedSelector;
282
- await this.evaluate(`
283
- (() => {
284
- const el = document.querySelector(${JSON.stringify(sel)});
285
- if (!el) return;
286
- const s = el.selectionStart ?? el.value?.length ?? 0;
287
- const e = el.selectionEnd ?? el.value?.length ?? 0;
288
- const before = (el.value ?? "").substring(0, s);
289
- const after = (el.value ?? "").substring(e);
290
- el.value = before + ${JSON.stringify(text)} + after;
291
- el.selectionStart = el.selectionEnd = before.length + ${JSON.stringify(text)}.length;
292
- el.dispatchEvent(new Event('input', { bubbles: true }));
293
- el.dispatchEvent(new Event('change', { bubbles: true }));
294
- })()
295
- `);
296
- } else {
297
- // Fallback: char events al elemento activo del browser
298
- for (const char of text) {
299
- await this.cdp("Input.dispatchKeyEvent", { type: "char", text: char });
300
- }
301
- }
207
+ // Fallback: keyboard inserttext (requires focused element)
208
+ const res = await this.run(["keyboard", "inserttext", text]);
209
+ if (!res.success) throw new Error(res.error || "type failed");
302
210
  }
303
211
 
304
- // ── press ───────────────────────────────────────────────────────────────────
212
+ async typeIn(selector: string, text: string): Promise<void> {
213
+ const res = await this.run(["type", selector, text]);
214
+ if (!res.success) throw new Error(res.error || `type failed: ${selector}`);
215
+ }
305
216
 
306
- async press(key: string, options?: { modifiers?: string[] }): Promise<void> {
307
- const modifierBits = (options?.modifiers ?? []).reduce((acc, m) => {
308
- if (m === "Alt") return acc | 1;
309
- if (m === "Control" || m === "Meta") return acc | 2;
310
- if (m === "Shift") return acc | 8;
311
- return acc;
312
- }, 0);
313
-
314
- await this.cdp("Input.dispatchKeyEvent", { type: "keyDown", key, modifiers: modifierBits });
315
- // El evento 'char' es necesario para que el navegador procese teclas como Enter
316
- // y dispare comportamientos del DOM (submit de formularios, saltos de línea, etc.)
317
- await this.cdp("Input.dispatchKeyEvent", {
318
- type: "char",
319
- key: key === "Return" || key === "Enter" ? "\r" : key.length === 1 ? key : "",
320
- modifiers: modifierBits,
321
- });
322
- await this.cdp("Input.dispatchKeyEvent", { type: "keyUp", key, modifiers: modifierBits });
217
+ async fill(selector: string, text: string): Promise<void> {
218
+ const res = await this.run(["fill", selector, text]);
219
+ if (!res.success) throw new Error(res.error || `fill failed: ${selector}`);
323
220
  }
324
221
 
325
- // ── scroll ──────────────────────────────────────────────────────────────────
222
+ async press(key: string, options?: { modifiers?: string[] }): Promise<void> {
223
+ const modifiers = options?.modifiers ?? [];
224
+ const combo = modifiers.length > 0
225
+ ? `${modifiers.join("+")}+${key}`
226
+ : key;
227
+ const res = await this.run(["press", combo]);
228
+ if (!res.success) throw new Error(res.error || `press failed: ${combo}`);
229
+ }
326
230
 
327
231
  async scroll(dx: number, dy: number): Promise<void> {
328
- await this.evaluate(`window.scrollBy(${dx}, ${dy})`);
232
+ const dir = dy > 0 ? "down" : dy < 0 ? "up" : dx > 0 ? "right" : "left";
233
+ const px = Math.abs(dy || dx);
234
+ const res = await this.run(["scroll", dir, String(px)]);
235
+ if (!res.success) throw new Error(res.error || "scroll failed");
329
236
  }
330
237
 
331
- async scrollTo(selector: string, options?: { behavior?: "smooth" | "instant" }): Promise<void> {
332
- const behavior = options?.behavior ?? "smooth";
333
- await this.evaluate(`document.querySelector(${JSON.stringify(selector)})?.scrollIntoView({ behavior: ${JSON.stringify(behavior)}, block: "center" })`);
238
+ async scrollTo(selector: string, _options?: { behavior?: "smooth" | "instant" }): Promise<void> {
239
+ // agent-browser has scrollintoview (behavior not supported via CLI)
240
+ const res = await this.run(["scrollintoview", selector]);
241
+ if (!res.success) throw new Error(res.error || `scrollTo failed: ${selector}`);
334
242
  }
335
243
 
336
- // ── navigation helpers ──────────────────────────────────────────────────────
337
-
338
244
  async back(): Promise<void> {
339
- await this.evaluate("history.back()");
245
+ const res = await this.run(["back"]);
246
+ if (!res.success) throw new Error(res.error || "back failed");
340
247
  await new Promise<void>(r => setTimeout(r, 800));
341
248
  }
342
249
 
343
250
  async forward(): Promise<void> {
344
- await this.evaluate("history.forward()");
251
+ const res = await this.run(["forward"]);
252
+ if (!res.success) throw new Error(res.error || "forward failed");
345
253
  await new Promise<void>(r => setTimeout(r, 800));
346
254
  }
347
255
 
348
256
  async reload(): Promise<void> {
349
- await this.cdp("Page.reload");
257
+ const res = await this.run(["reload"]);
258
+ if (!res.success) throw new Error(res.error || "reload failed");
350
259
  await new Promise<void>(r => setTimeout(r, 1000));
351
260
  }
352
261
 
353
262
  async resize(width: number, height: number): Promise<void> {
354
- await this.cdp("Emulation.setDeviceMetricsOverride", {
355
- width, height, deviceScaleFactor: 1, mobile: false,
356
- });
263
+ const res = await this.run(["set", "viewport", String(width), String(height)]);
264
+ if (!res.success) throw new Error(res.error || "resize failed");
265
+ }
266
+
267
+ /** Capture accessibility tree snapshot (compact, AI-optimized). ~200-600 chars vs ~3000+ innerText. */
268
+ async snapshot(options?: { compact?: boolean; depth?: number; interactiveOnly?: boolean }): Promise<string> {
269
+ const args = ["snapshot"];
270
+ if (options?.compact !== false) args.push("-c");
271
+ if (options?.depth) args.push("-d", String(options.depth));
272
+ if (options?.interactiveOnly) args.push("-i");
273
+
274
+ const res = await this.run(args);
275
+ if (!res.success) throw new Error(res.error || "snapshot failed");
276
+ return res.data?.snapshot as string || "";
357
277
  }
358
278
 
359
- // ── close ───────────────────────────────────────────────────────────────────
279
+ async cdp<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {
280
+ const script = `
281
+ (() => {
282
+ // agent-browser does not expose raw CDP directly via CLI for all methods.
283
+ // For common methods we can emulate; for others we return a notice.
284
+ const method = ${JSON.stringify(method)};
285
+ const params = ${JSON.stringify(params ?? {})};
286
+ return { method, params, note: "CDP passthrough not fully supported by agent-browser CLI" };
287
+ })()
288
+ `;
289
+ const res = await this.run(["eval", script]);
290
+ if (!res.success) throw new Error(res.error || `cdp failed: ${method}`);
291
+ return res.data?.result as T;
292
+ }
360
293
 
361
294
  close(): void {
362
- try { this.ws?.close(); } catch { /* ignore */ }
363
- try { this.proc?.kill(); } catch { /* ignore */ }
364
- this.ws = null;
365
- this.proc = null;
366
- this._url = "";
367
- allInstances.delete(this);
295
+ // Close the session
296
+ this.run(["close"]).catch(() => { /* ignore */ });
297
+ }
298
+ }
299
+
300
+ // ─── Backwards compatibility exports ──────────────────────────────────────────
301
+
302
+ /** @deprecated Use AgentBrowserView instead */
303
+ export class CDPClient extends AgentBrowserView {
304
+ private _launched = false;
305
+
306
+ async launch(_spec?: unknown, _options?: unknown): Promise<void> {
307
+ if (this._launched) return;
308
+ // Verify agent-browser is working by opening about:blank
309
+ const res = await this.run(["open", "about:blank"]);
310
+ if (!res.success) throw new Error(res.error || "Failed to launch agent-browser");
311
+ this._launched = true;
368
312
  }
369
313
 
370
314
  static closeAll(): void {
371
- for (const inst of allInstances) inst.close();
372
- allInstances.clear();
315
+ // agent-browser sessions are managed by the daemon; no explicit cleanup needed
373
316
  }
374
317
  }
375
318
 
319
+ /** @deprecated No longer used — agent-browser handles browser detection internally */
320
+ export function detectBrowser(_options?: unknown): undefined {
321
+ return undefined;
322
+ }
323
+
324
+ /** @deprecated No longer used */
325
+ export type LaunchSpec = { kind: "remote"; cdpUrl: string };
326
+
376
327
  // ─── BrowserService (singleton) ───────────────────────────────────────────────
377
328
 
378
- export type BrowserView = CDPClient;
329
+ export type BrowserView = AgentBrowserView;
379
330
 
380
- let _client: CDPClient | null = null;
381
- let _spec: LaunchSpec | undefined = undefined;
331
+ let _client: AgentBrowserView | null = null;
382
332
  let _available = false;
383
333
  let _launching = false;
384
334
 
385
335
  export class BrowserService {
386
336
  private static instance: BrowserService | null = null;
337
+ private readonly config: Config;
387
338
 
388
- private constructor(_config: Config) {}
339
+ private constructor(config: Config) {
340
+ this.config = config;
341
+ }
389
342
 
390
343
  static getInstance(config: Config): BrowserService {
391
344
  if (!BrowserService.instance) {
@@ -395,40 +348,52 @@ export class BrowserService {
395
348
  }
396
349
 
397
350
  /**
398
- * Probe-only: detect if a browser is installed and mark tools as available.
399
- * Does NOT launch the browser — that happens lazily on first tool use.
351
+ * Probe / lazy install agent-browser.
400
352
  */
401
353
  async start(): Promise<boolean> {
402
- _spec = detectBrowser();
403
- if (!_spec) {
404
- log.warn("Ningún browser Chromium encontrado.");
405
- log.warn(" Linux nativo: sudo dnf install chromium");
406
- log.warn(" Flatpak: flatpak install flathub com.google.Chrome");
407
- log.warn(" Manual: export BUN_CHROME_PATH=/ruta/a/chrome");
354
+ const b = this.config.tools?.browser;
355
+ if (b?.enabled === false) {
356
+ _available = false;
357
+ return false;
358
+ }
359
+
360
+ const installed = await isAgentBrowserInstalled();
361
+
362
+ if (!installed) {
363
+ try {
364
+ await installAgentBrowser();
365
+ } catch (err) {
366
+ log.warn(`No se pudo instalar agent-browser: ${(err as Error).message}`);
367
+ log.warn(" Instalar manualmente: bun add -g agent-browser");
368
+ _available = false;
369
+ return false;
370
+ }
371
+ }
372
+
373
+ try {
374
+ await ensureChromeInstalled();
375
+ } catch (err) {
376
+ log.warn(`Chrome no pudo prepararse: ${(err as Error).message}`);
408
377
  _available = false;
409
378
  return false;
410
379
  }
380
+
411
381
  _available = true;
412
- log.info(`✅ Browser detectado (${_spec.kind === "native" ? _spec.path : _spec.appId}) — se abrirá al primer uso`);
382
+ log.info(" agent-browser listo — se abrirá al primer uso");
413
383
  return true;
414
384
  }
415
385
 
416
- /**
417
- * Lazy launch: called by getView() on first tool use.
418
- */
419
386
  private async _ensureLaunched(): Promise<boolean> {
420
387
  if (_client) return true;
421
- if (!_spec) return false;
422
388
  if (_launching) {
423
- // Wait up to 10s for concurrent launch to finish
424
389
  const deadline = Date.now() + 10000;
425
390
  while (_launching && Date.now() < deadline) await new Promise(r => setTimeout(r, 100));
426
391
  return !!_client;
427
392
  }
428
393
  _launching = true;
429
394
  try {
430
- _client = new CDPClient();
431
- await _client.launch(_spec);
395
+ const sessionName = this.config.tools?.browser?.sessionName ?? DEFAULT_SESSION_NAME;
396
+ _client = new AgentBrowserView(sessionName);
432
397
  log.info("✅ Browser abierto — el usuario verá las acciones del agente");
433
398
  return true;
434
399
  } catch (err) {
@@ -441,25 +406,17 @@ export class BrowserService {
441
406
  }
442
407
  }
443
408
 
444
- async getView(): Promise<CDPClient | null> {
409
+ async getView(): Promise<AgentBrowserView | null> {
445
410
  if (!_available) return null;
446
-
447
- // Health-check: if Chrome was closed by the user or crashed, relaunch on next call
448
- if (_client && !_client.isConnected) {
449
- log.warn("Browser connection lost — relaunching on next tool call");
450
- _client = null;
451
- }
452
-
453
411
  await this._ensureLaunched();
454
412
  return _client;
455
413
  }
456
414
 
457
- /** Sync version — returns existing client only (no launch). Use getView() in tools. */
458
- getViewSync(): CDPClient | null {
415
+ getViewSync(): AgentBrowserView | null {
459
416
  return _client;
460
417
  }
461
418
 
462
- async getPage(): Promise<CDPClient | null> {
419
+ async getPage(): Promise<AgentBrowserView | null> {
463
420
  return this.getView();
464
421
  }
465
422
 
@@ -505,7 +462,7 @@ export function getBrowserService(): BrowserService | null {
505
462
  // ─── Helpers (misma API que antes) ───────────────────────────────────────────
506
463
 
507
464
  export async function waitForSelector(
508
- view: CDPClient,
465
+ view: AgentBrowserView,
509
466
  selector: string,
510
467
  timeout = 30000
511
468
  ): Promise<void> {
@@ -519,7 +476,7 @@ export async function waitForSelector(
519
476
  }
520
477
 
521
478
  export async function waitForCondition(
522
- view: CDPClient,
479
+ view: AgentBrowserView,
523
480
  expression: string,
524
481
  timeout = 30000
525
482
  ): Promise<void> {
@@ -533,22 +490,19 @@ export async function waitForCondition(
533
490
  }
534
491
 
535
492
  export async function screenshotElement(
536
- view: CDPClient,
493
+ view: AgentBrowserView,
537
494
  selector: string
538
495
  ): Promise<string> {
539
- const box = await view.evaluate<{ x: number; y: number; width: number; height: number } | null>(`
540
- (() => {
541
- const el = document.querySelector(${JSON.stringify(selector)});
542
- if (!el) return null;
543
- const r = el.getBoundingClientRect();
544
- return { x: r.left, y: r.top, width: r.width, height: r.height };
545
- })()
546
- `);
547
-
548
- if (!box) throw new Error(`Elemento no encontrado: ${selector}`);
549
-
550
- return view.screenshot({
551
- format: "png",
552
- clip: { x: box.x, y: box.y, width: box.width, height: box.height, scale: 1 },
553
- });
496
+ const res = await (view as any).run(["screenshot", selector]);
497
+ if (!res.success) throw new Error(res.error || `screenshot failed: ${selector}`);
498
+
499
+ const path = res.data?.path as string;
500
+ if (!path) throw new Error("screenshot did not return a path");
501
+
502
+ const data = readFileSync(path);
503
+ const base64 = Buffer.from(data).toString("base64");
504
+
505
+ try { rmSync(path); } catch { /* ignore */ }
506
+
507
+ return base64;
554
508
  }