@phuetz/code-buddy 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/README.md +160 -164
  2. package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
  3. package/dist/agent/codebuddy-agent.d.ts +1 -1
  4. package/dist/agent/codebuddy-agent.js +101 -13
  5. package/dist/agent/execution/agent-executor.js +122 -12
  6. package/dist/agent/execution/tool-dependency-graph.js +7 -0
  7. package/dist/agent/execution/tool-hooks.d.ts +6 -0
  8. package/dist/agent/execution/tool-hooks.js +13 -5
  9. package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
  10. package/dist/agent/execution/tool-selection-strategy.js +67 -10
  11. package/dist/agent/extended-thinking.d.ts +8 -0
  12. package/dist/agent/extended-thinking.js +27 -0
  13. package/dist/agent/hermes-browser-backends.js +151 -11
  14. package/dist/agent/hermes-claw-migrate.d.ts +59 -0
  15. package/dist/agent/hermes-claw-migrate.js +427 -26
  16. package/dist/agent/hermes-parity-manifest.js +78 -71
  17. package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
  18. package/dist/agent/hermes-runtime-lifecycle.js +468 -0
  19. package/dist/agent/lesson-auto-proposer.js +10 -0
  20. package/dist/agent/middleware/index.d.ts +1 -0
  21. package/dist/agent/middleware/index.js +1 -0
  22. package/dist/agent/middleware/session-duration.d.ts +36 -0
  23. package/dist/agent/middleware/session-duration.js +78 -0
  24. package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
  25. package/dist/agent/middleware/visual-validation-middleware.js +67 -0
  26. package/dist/agent/model-benchmark.d.ts +77 -0
  27. package/dist/agent/model-benchmark.js +309 -0
  28. package/dist/agent/model-tier.d.ts +14 -0
  29. package/dist/agent/model-tier.js +71 -0
  30. package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
  31. package/dist/agent/multi-agent/multi-agent-system.js +3 -1
  32. package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
  33. package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
  34. package/dist/agent/multi-agent/session-tools.js +22 -5
  35. package/dist/agent/reasoning/mcts.js +20 -6
  36. package/dist/agent/session-end-flush.d.ts +71 -0
  37. package/dist/agent/session-end-flush.js +233 -0
  38. package/dist/agent/streaming/streaming-handler.d.ts +7 -0
  39. package/dist/agent/streaming/streaming-handler.js +8 -1
  40. package/dist/agent/tool-executor.js +1 -0
  41. package/dist/agent/tool-handler.d.ts +5 -0
  42. package/dist/agent/tool-handler.js +130 -4
  43. package/dist/browser-automation/browser-operator-executor.js +15 -0
  44. package/dist/browser-automation/browser-use-runner.d.ts +96 -0
  45. package/dist/browser-automation/browser-use-runner.js +492 -0
  46. package/dist/browser-automation/camofox-runner.d.ts +107 -0
  47. package/dist/browser-automation/camofox-runner.js +287 -0
  48. package/dist/channels/dingtalk/index.js +3 -0
  49. package/dist/channels/discord/client.d.ts +9 -0
  50. package/dist/channels/discord/client.js +12 -0
  51. package/dist/channels/feishu/index.d.ts +167 -1
  52. package/dist/channels/feishu/index.js +432 -7
  53. package/dist/channels/gateway-lifecycle.d.ts +132 -0
  54. package/dist/channels/gateway-lifecycle.js +219 -0
  55. package/dist/channels/google-chat/index.d.ts +5 -1
  56. package/dist/channels/google-chat/index.js +5 -1
  57. package/dist/channels/imessage/index.d.ts +14 -0
  58. package/dist/channels/imessage/index.js +64 -17
  59. package/dist/channels/index.d.ts +4 -0
  60. package/dist/channels/index.js +4 -0
  61. package/dist/channels/irc/index.d.ts +73 -3
  62. package/dist/channels/irc/index.js +446 -11
  63. package/dist/channels/line/index.js +3 -0
  64. package/dist/channels/mattermost/index.d.ts +51 -4
  65. package/dist/channels/mattermost/index.js +303 -20
  66. package/dist/channels/nextcloud-talk/index.d.ts +89 -8
  67. package/dist/channels/nextcloud-talk/index.js +367 -16
  68. package/dist/channels/nostr/index.d.ts +121 -1
  69. package/dist/channels/nostr/index.js +396 -8
  70. package/dist/channels/ntfy/index.js +3 -0
  71. package/dist/channels/qq/index.js +3 -0
  72. package/dist/channels/slash-parity.d.ts +117 -0
  73. package/dist/channels/slash-parity.js +185 -0
  74. package/dist/channels/synology-chat/index.js +3 -0
  75. package/dist/channels/teams/index.d.ts +3 -0
  76. package/dist/channels/teams/index.js +3 -0
  77. package/dist/channels/telegram/client.d.ts +9 -0
  78. package/dist/channels/telegram/client.js +12 -0
  79. package/dist/channels/twilio-voice/index.js +3 -0
  80. package/dist/channels/webchat/index.d.ts +7 -1
  81. package/dist/channels/webchat/index.js +7 -1
  82. package/dist/channels/wecom/index.js +3 -0
  83. package/dist/channels/weixin/index.js +3 -0
  84. package/dist/channels/zalo/index.js +3 -0
  85. package/dist/cli/config-loader.js +1 -1
  86. package/dist/codebuddy/client.d.ts +30 -1
  87. package/dist/codebuddy/client.js +167 -20
  88. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
  89. package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
  90. package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
  91. package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
  92. package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
  93. package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
  94. package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
  95. package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
  96. package/dist/codebuddy/tool-definitions/index.js +2 -1
  97. package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
  98. package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
  99. package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
  100. package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
  101. package/dist/codebuddy/tools.d.ts +1 -1
  102. package/dist/codebuddy/tools.js +27 -24
  103. package/dist/commands/cli/hermes-commands.js +108 -0
  104. package/dist/commands/cli/native-engine-commands.js +107 -3
  105. package/dist/commands/cli/utility-commands.js +45 -0
  106. package/dist/commands/enhanced-command-handler.js +5 -0
  107. package/dist/commands/goal-cli.d.ts +71 -0
  108. package/dist/commands/goal-cli.js +280 -0
  109. package/dist/commands/handlers/goal-handler.d.ts +34 -0
  110. package/dist/commands/handlers/goal-handler.js +155 -0
  111. package/dist/commands/handlers/index.d.ts +1 -0
  112. package/dist/commands/handlers/index.js +2 -0
  113. package/dist/commands/handlers/infra-handlers.js +36 -0
  114. package/dist/commands/handlers/memory-handlers.js +96 -1
  115. package/dist/commands/headless-slash.d.ts +2 -0
  116. package/dist/commands/headless-slash.js +12 -0
  117. package/dist/commands/llm-provider-resolution.d.ts +5 -3
  118. package/dist/commands/llm-provider-resolution.js +87 -33
  119. package/dist/commands/ollama.d.ts +25 -0
  120. package/dist/commands/ollama.js +100 -0
  121. package/dist/commands/provider.d.ts +5 -0
  122. package/dist/commands/provider.js +106 -55
  123. package/dist/commands/slash/builtin-commands.js +20 -0
  124. package/dist/commands/spec-next.js +2 -1
  125. package/dist/commands/spec-plan.js +5 -16
  126. package/dist/commands/tunnel.d.ts +2 -0
  127. package/dist/commands/tunnel.js +48 -0
  128. package/dist/config/config-resolver.d.ts +2 -1
  129. package/dist/config/config-resolver.js +54 -41
  130. package/dist/config/constants.d.ts +28 -0
  131. package/dist/config/constants.js +7 -0
  132. package/dist/config/env-schema.js +770 -0
  133. package/dist/config/feature-flags.js +7 -0
  134. package/dist/config/model-tools.js +4 -4
  135. package/dist/config/toml-config.d.ts +16 -0
  136. package/dist/config/toml-config.js +3 -0
  137. package/dist/context/context-manager-v2.d.ts +39 -0
  138. package/dist/context/context-manager-v2.js +91 -0
  139. package/dist/daemon/agent-task-executor.js +12 -1
  140. package/dist/daemon/autonomous-daemon.d.ts +1 -1
  141. package/dist/daemon/autonomous-daemon.js +5 -3
  142. package/dist/daemon/autonomous-loop.d.ts +21 -1
  143. package/dist/daemon/autonomous-loop.js +69 -0
  144. package/dist/daemon/colab-goal.d.ts +38 -0
  145. package/dist/daemon/colab-goal.js +81 -0
  146. package/dist/daemon/cron-agent-bridge.d.ts +12 -3
  147. package/dist/daemon/cron-agent-bridge.js +25 -9
  148. package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
  149. package/dist/desktop/codebuddy-engine-adapter.js +257 -80
  150. package/dist/desktop/engine-adapter.d.ts +14 -0
  151. package/dist/desktop-automation/automation-manager.js +16 -0
  152. package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
  153. package/dist/desktop-automation/omniparser-runner.js +115 -0
  154. package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
  155. package/dist/desktop-automation/smart-snapshot.js +72 -5
  156. package/dist/doctor/index.js +40 -23
  157. package/dist/events/types.d.ts +11 -0
  158. package/dist/fleet/colab-store.d.ts +21 -0
  159. package/dist/fleet/colab-store.js +28 -0
  160. package/dist/fleet/peer-session-bridge.d.ts +1 -1
  161. package/dist/fleet/peer-session-bridge.js +243 -2
  162. package/dist/fleet/peer-session-store.d.ts +3 -0
  163. package/dist/fleet/peer-tool-bridge.js +14 -0
  164. package/dist/fleet/privacy-lint.d.ts +8 -0
  165. package/dist/fleet/privacy-lint.js +22 -0
  166. package/dist/goals/goal-decomposer.d.ts +27 -0
  167. package/dist/goals/goal-decomposer.js +293 -0
  168. package/dist/goals/goal-judge-client.d.ts +15 -0
  169. package/dist/goals/goal-judge-client.js +45 -0
  170. package/dist/goals/goal-judge.d.ts +38 -0
  171. package/dist/goals/goal-judge.js +158 -0
  172. package/dist/goals/goal-loop.d.ts +37 -0
  173. package/dist/goals/goal-loop.js +101 -0
  174. package/dist/goals/goal-manager.d.ts +77 -0
  175. package/dist/goals/goal-manager.js +289 -0
  176. package/dist/goals/goal-state.d.ts +99 -0
  177. package/dist/goals/goal-state.js +315 -0
  178. package/dist/goals/goal-store.d.ts +28 -0
  179. package/dist/goals/goal-store.js +87 -0
  180. package/dist/goals/index.d.ts +5 -0
  181. package/dist/goals/index.js +6 -0
  182. package/dist/hooks/use-input-handler.js +36 -1
  183. package/dist/hooks/user-hooks.js +17 -3
  184. package/dist/index.js +506 -25
  185. package/dist/input/text-to-speech.d.ts +2 -6
  186. package/dist/input/text-to-speech.js +2 -27
  187. package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
  188. package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
  189. package/dist/integrations/tailscale.d.ts +13 -0
  190. package/dist/integrations/tailscale.js +87 -34
  191. package/dist/knowledge/workspace-indexer.js +53 -9
  192. package/dist/mcp/client.js +1 -0
  193. package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
  194. package/dist/mcp/mcp-desktop-tools.js +158 -0
  195. package/dist/mcp/mcp-server.js +2 -0
  196. package/dist/memory/enhanced-memory.d.ts +10 -0
  197. package/dist/memory/enhanced-memory.js +33 -2
  198. package/dist/memory/index.d.ts +3 -1
  199. package/dist/memory/index.js +4 -1
  200. package/dist/memory/memory-auto-proposer.d.ts +23 -0
  201. package/dist/memory/memory-auto-proposer.js +308 -0
  202. package/dist/memory/memory-candidate-queue.d.ts +92 -0
  203. package/dist/memory/memory-candidate-queue.js +261 -0
  204. package/dist/memory/persistent-memory.d.ts +45 -2
  205. package/dist/memory/persistent-memory.js +236 -40
  206. package/dist/ml/bayesian-qualifier.d.ts +1 -1
  207. package/dist/ml/bayesian-qualifier.js +35 -2
  208. package/dist/observability/run-store.d.ts +1 -1
  209. package/dist/openclaw/gateway-bridge.js +5 -0
  210. package/dist/plugins/marketplace.d.ts +1 -0
  211. package/dist/plugins/marketplace.js +7 -0
  212. package/dist/prompts/execution-discipline.d.ts +14 -0
  213. package/dist/prompts/execution-discipline.js +29 -0
  214. package/dist/prompts/variation-injector.js +9 -2
  215. package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
  216. package/dist/protocols/acp/acp-agentic-runner.js +115 -18
  217. package/dist/protocols/acp/acp-session-store.d.ts +23 -0
  218. package/dist/protocols/acp/acp-session-store.js +77 -0
  219. package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
  220. package/dist/protocols/acp/acp-stdio-server.js +71 -16
  221. package/dist/providers/active-llm-registry.d.ts +37 -0
  222. package/dist/providers/active-llm-registry.js +186 -0
  223. package/dist/providers/auxiliary-provider.d.ts +25 -0
  224. package/dist/providers/auxiliary-provider.js +192 -0
  225. package/dist/providers/codex-oauth.d.ts +1 -1
  226. package/dist/providers/codex-oauth.js +27 -4
  227. package/dist/providers/index.d.ts +3 -0
  228. package/dist/providers/index.js +4 -0
  229. package/dist/providers/provider-catalog.d.ts +70 -0
  230. package/dist/providers/provider-catalog.js +738 -0
  231. package/dist/providers/provider-fallback.d.ts +35 -0
  232. package/dist/providers/provider-fallback.js +171 -0
  233. package/dist/providers/xai-oauth.d.ts +128 -0
  234. package/dist/providers/xai-oauth.js +735 -0
  235. package/dist/scheduler/cron-scheduler.d.ts +15 -3
  236. package/dist/scheduler/cron-scheduler.js +53 -7
  237. package/dist/scheduler/script-runner.d.ts +2 -0
  238. package/dist/scheduler/script-runner.js +19 -4
  239. package/dist/scheduler/watchdog-handlers.js +7 -20
  240. package/dist/search/usearch-index.js +7 -2
  241. package/dist/security/tool-policy/tool-groups.js +2 -0
  242. package/dist/server/index.js +17 -2
  243. package/dist/server/routes/mobile.d.ts +12 -4
  244. package/dist/server/routes/mobile.js +116 -1
  245. package/dist/server/tls-config.d.ts +35 -0
  246. package/dist/server/tls-config.js +142 -0
  247. package/dist/server/tunnel-manager.d.ts +20 -0
  248. package/dist/server/tunnel-manager.js +58 -0
  249. package/dist/server/websocket/fleet-bridge.d.ts +13 -1
  250. package/dist/server/websocket/fleet-bridge.js +16 -0
  251. package/dist/services/prompt-builder.d.ts +1 -0
  252. package/dist/services/prompt-builder.js +49 -8
  253. package/dist/shared/engine-types.d.ts +15 -1
  254. package/dist/sidecar/sidecar-bridge.d.ts +1 -0
  255. package/dist/sidecar/sidecar-bridge.js +21 -10
  256. package/dist/spec/spec-store.js +8 -1
  257. package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
  258. package/dist/talk-mode/providers/audioreader-tts.js +8 -8
  259. package/dist/templates/project-scaffolding.js +3 -2
  260. package/dist/themes/theme-schema.d.ts +10 -10
  261. package/dist/tools/application-profiles.js +38 -0
  262. package/dist/tools/bash/bash-tool.d.ts +1 -0
  263. package/dist/tools/bash/bash-tool.js +16 -2
  264. package/dist/tools/bash/command-validator.js +3 -0
  265. package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
  266. package/dist/tools/bash/rtk-rewrite.js +100 -0
  267. package/dist/tools/bash/streaming-executor.js +14 -1
  268. package/dist/tools/computer-control-tool.d.ts +16 -1
  269. package/dist/tools/computer-control-tool.js +317 -5
  270. package/dist/tools/document-generator.d.ts +14 -0
  271. package/dist/tools/document-generator.js +79 -0
  272. package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
  273. package/dist/tools/execute-code-rpc-invoker.js +46 -5
  274. package/dist/tools/metadata.js +28 -0
  275. package/dist/tools/office-macro-tool.d.ts +10 -0
  276. package/dist/tools/office-macro-tool.js +93 -0
  277. package/dist/tools/registry/index.d.ts +2 -1
  278. package/dist/tools/registry/index.js +2 -1
  279. package/dist/tools/registry/memory-tools.d.ts +20 -0
  280. package/dist/tools/registry/memory-tools.js +225 -3
  281. package/dist/tools/registry/multimodal-tools.js +2 -2
  282. package/dist/tools/registry/vision-tools.d.ts +33 -0
  283. package/dist/tools/registry/vision-tools.js +308 -6
  284. package/dist/tools/registry/windows-tools.d.ts +2 -0
  285. package/dist/tools/registry/windows-tools.js +47 -0
  286. package/dist/tools/route-peer-tool.js +14 -0
  287. package/dist/tools/screenshot-tool.js +14 -2
  288. package/dist/tools/text-to-speech-tool.d.ts +1 -1
  289. package/dist/tools/text-to-speech-tool.js +2 -12
  290. package/dist/tools/tool-selector.js +3 -59
  291. package/dist/utils/config-validation/schema.d.ts +40 -7
  292. package/dist/utils/config-validation/schema.js +130 -1
  293. package/dist/utils/cost-tracker.js +21 -1
  294. package/dist/utils/disk-guard.d.ts +180 -0
  295. package/dist/utils/disk-guard.js +404 -0
  296. package/dist/utils/interactive-setup.js +35 -2
  297. package/dist/utils/model-utils.js +11 -2
  298. package/dist/utils/output-sanitizer.js +11 -0
  299. package/dist/utils/provider-detector.d.ts +8 -7
  300. package/dist/utils/provider-detector.js +19 -77
  301. package/dist/utils/settings-manager.d.ts +7 -0
  302. package/dist/utils/token-display.d.ts +3 -3
  303. package/dist/utils/token-display.js +22 -4
  304. package/dist/wizard/onboarding.d.ts +39 -0
  305. package/dist/wizard/onboarding.js +433 -21
  306. package/dist/wizard/provider-onboarding.d.ts +8 -23
  307. package/dist/wizard/provider-onboarding.js +39 -158
  308. package/package.json +4 -2
  309. package/dist/plugins/provider-onboarding.d.ts +0 -23
  310. package/dist/plugins/provider-onboarding.js +0 -116
  311. package/dist/utils/kokoro-tts.d.ts +0 -8
  312. package/dist/utils/kokoro-tts.js +0 -67
@@ -189,12 +189,27 @@ export class BrowserOperatorExecutor {
189
189
  'access denied',
190
190
  'security checkpoint',
191
191
  ];
192
+ const sensitivePlatforms = [
193
+ 'yandex.com',
194
+ 'yandex.ru',
195
+ 'login.microsoftonline.com',
196
+ 'zoom.us',
197
+ ];
192
198
  for (const indicator of antiBotIndicators) {
193
199
  if (lower.includes(indicator)) {
194
200
  raiseCheckpoint(`Security checkpoint detected: "${indicator}"`);
195
201
  break;
196
202
  }
197
203
  }
204
+ if (!checkpointDetected) {
205
+ const currentUrl = String(this.page.url?.() ?? '').toLowerCase();
206
+ for (const platform of sensitivePlatforms) {
207
+ if (currentUrl.includes(platform)) {
208
+ raiseCheckpoint(`OSINT Policy Violation: Target-specific exploit scripts are strictly declined for "${platform}"`);
209
+ break;
210
+ }
211
+ }
212
+ }
198
213
  }
199
214
  catch {
200
215
  // Ignore polling errors while pages are navigating or closing.
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Browser Use Runner
3
+ *
4
+ * browser-use is an open-source agentic browser-automation library. This runner
5
+ * routes a task through one of three paths, in precedence order:
6
+ *
7
+ * 1. The Browser Use **managed API** (via `BROWSER_USE_API_KEY`), or
8
+ * 2. The **Nous Tool Gateway** (`CODEBUDDY_NOUS_TOOL_GATEWAY_URL`), or
9
+ * 3. A **local** browser-use install driven against a local Ollama model
10
+ * (no paid account required). Enabled with `CODEBUDDY_BROWSER_USE_LOCAL=1`
11
+ * or auto-detected when the `browser_use` Python package is importable and
12
+ * neither managed path is configured.
13
+ *
14
+ * The managed paths (1 and 2) speak HTTP via `fetch`. The local path (3) spawns
15
+ * a Python subprocess that runs `Agent(task=..., llm=ChatOllama(...)).run()` and
16
+ * prints a sentinel-delimited JSON result line, which we parse and normalise
17
+ * back into the same `BrowserUseActionResult` shape.
18
+ *
19
+ * Returns structured results with optional screenshot data. Falls back
20
+ * gracefully (typed error result, never throws) when nothing is configured.
21
+ */
22
+ export interface BrowserUseRunnerOptions {
23
+ /** Browser Use API key (falls back to `BROWSER_USE_API_KEY` env). */
24
+ apiKey?: string;
25
+ /** Nous Tool Gateway URL (falls back to `CODEBUDDY_NOUS_TOOL_GATEWAY_URL` env). */
26
+ gatewayUrl?: string;
27
+ /** Request/run timeout in milliseconds (default: 60 000 managed, 180 000 local). */
28
+ timeout?: number;
29
+ /**
30
+ * Force/disable the local browser-use path explicitly. When undefined it is
31
+ * read from `CODEBUDDY_BROWSER_USE_LOCAL` and otherwise auto-detected.
32
+ */
33
+ local?: boolean;
34
+ /**
35
+ * Python interpreter that has the `browser_use` package installed.
36
+ * Defaults to `$CODEBUDDY_BROWSER_USE_PYTHON`, then `python3`.
37
+ */
38
+ pythonPath?: string;
39
+ /**
40
+ * Ollama model the local browser-use agent should drive. Defaults to
41
+ * `$CODEBUDDY_BROWSER_USE_MODEL`, then `qwen2.5:7b-instruct` (a tool-calling
42
+ * capable model — browser-use relies on structured tool calls).
43
+ */
44
+ model?: string;
45
+ /**
46
+ * Ollama host the local agent should connect to. Defaults to `$OLLAMA_HOST`,
47
+ * then `http://localhost:11434`.
48
+ */
49
+ ollamaHost?: string;
50
+ /**
51
+ * Path to a Chrome/Chromium binary for browser-use to launch
52
+ * (`BrowserProfile.executable_path`). Defaults to `$CODEBUDDY_BROWSER_USE_CHROME`.
53
+ * On sandboxed Linux/CI hosts the snap-confined chromium often fails the
54
+ * 30 s browser-start watchdog; pointing at a plain Chromium binary (e.g. the
55
+ * Playwright cache) plus `chromiumSandbox: false` is what makes launch work.
56
+ */
57
+ chromePath?: string;
58
+ /**
59
+ * Disable Chromium's sandbox and add `--no-sandbox` (needed inside most
60
+ * containers / sandboxed CI). Defaults from `$CODEBUDDY_BROWSER_USE_NO_SANDBOX`
61
+ * (truthy), else true on Linux where the sandbox commonly blocks launch.
62
+ */
63
+ noSandbox?: boolean;
64
+ /**
65
+ * Whether browser-use should send screenshots to the model. browser-use sends
66
+ * them by default, which 400s on text-only Ollama models
67
+ * (`Multimodal data provided, but model does not support multimodal requests`).
68
+ * Defaults to `false` so the default text model works; set true with a vision
69
+ * model. Read from `$CODEBUDDY_BROWSER_USE_VISION` when undefined.
70
+ */
71
+ useVision?: boolean;
72
+ /** Max agent steps for the local run (default: 6). */
73
+ maxSteps?: number;
74
+ }
75
+ export interface BrowserUseActionResult {
76
+ ok: boolean;
77
+ /** Extracted page content or action result text. */
78
+ content?: string;
79
+ /** Base64-encoded screenshot, when the service provides one. */
80
+ screenshot?: string;
81
+ error?: string;
82
+ }
83
+ /** Pull the last sentinel-prefixed JSON object out of accumulated stdout. */
84
+ export declare function parseLocalResult(stdout: string): BrowserUseActionResult | null;
85
+ /**
86
+ * Execute a browser action through the Browser Use service.
87
+ *
88
+ * ```ts
89
+ * const result = await executeBrowserUseAction(
90
+ * 'Extract the main heading',
91
+ * 'https://example.com',
92
+ * );
93
+ * if (result.ok) console.log(result.content);
94
+ * ```
95
+ */
96
+ export declare function executeBrowserUseAction(action: string, url: string, options?: BrowserUseRunnerOptions): Promise<BrowserUseActionResult>;
@@ -0,0 +1,492 @@
1
+ /**
2
+ * Browser Use Runner
3
+ *
4
+ * browser-use is an open-source agentic browser-automation library. This runner
5
+ * routes a task through one of three paths, in precedence order:
6
+ *
7
+ * 1. The Browser Use **managed API** (via `BROWSER_USE_API_KEY`), or
8
+ * 2. The **Nous Tool Gateway** (`CODEBUDDY_NOUS_TOOL_GATEWAY_URL`), or
9
+ * 3. A **local** browser-use install driven against a local Ollama model
10
+ * (no paid account required). Enabled with `CODEBUDDY_BROWSER_USE_LOCAL=1`
11
+ * or auto-detected when the `browser_use` Python package is importable and
12
+ * neither managed path is configured.
13
+ *
14
+ * The managed paths (1 and 2) speak HTTP via `fetch`. The local path (3) spawns
15
+ * a Python subprocess that runs `Agent(task=..., llm=ChatOllama(...)).run()` and
16
+ * prints a sentinel-delimited JSON result line, which we parse and normalise
17
+ * back into the same `BrowserUseActionResult` shape.
18
+ *
19
+ * Returns structured results with optional screenshot data. Falls back
20
+ * gracefully (typed error result, never throws) when nothing is configured.
21
+ */
22
+ import { spawn, spawnSync } from 'child_process';
23
+ import { logger } from '../utils/logger.js';
24
+ // ---------------------------------------------------------------------------
25
+ // Internals
26
+ // ---------------------------------------------------------------------------
27
+ const DEFAULT_TIMEOUT_MS = 60_000;
28
+ /** Local agentic navigation is multi-step against a local model — give it room. */
29
+ const DEFAULT_LOCAL_TIMEOUT_MS = 180_000;
30
+ const BROWSER_USE_API_URL = 'https://api.browser-use.com/api/v1/run-task';
31
+ const DEFAULT_LOCAL_MODEL = 'qwen2.5:7b-instruct';
32
+ const DEFAULT_OLLAMA_HOST = 'http://localhost:11434';
33
+ /** Sentinel the Python entrypoint wraps its JSON result with, so we can pull it
34
+ * out of browser-use's very noisy step-by-step stdout (analogous to camofox's
35
+ * wsEndpoint parsing). */
36
+ const RESULT_SENTINEL = '__CB_BU_RESULT__';
37
+ /**
38
+ * Determine which managed endpoint to use, preferring the Browser Use API when
39
+ * an API key is available, then falling back to the Nous Tool Gateway. Returns
40
+ * null when neither managed path is configured (caller then considers local).
41
+ */
42
+ function resolveEndpoint(options = {}) {
43
+ const apiKey = options.apiKey ?? process.env.BROWSER_USE_API_KEY?.trim();
44
+ const gatewayUrl = options.gatewayUrl ?? process.env.CODEBUDDY_NOUS_TOOL_GATEWAY_URL?.trim();
45
+ if (apiKey) {
46
+ return {
47
+ kind: 'browser-use-api',
48
+ url: BROWSER_USE_API_URL,
49
+ headers: {
50
+ 'Authorization': `Bearer ${apiKey}`,
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ };
54
+ }
55
+ if (gatewayUrl) {
56
+ // The Nous Tool Gateway expects a POST to /browser-use with a JSON body.
57
+ const base = gatewayUrl.replace(/\/+$/, '');
58
+ return {
59
+ kind: 'nous-gateway',
60
+ url: `${base}/browser-use`,
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ },
64
+ };
65
+ }
66
+ return null;
67
+ }
68
+ /**
69
+ * Build the request body matching the Browser Use API schema.
70
+ * The Nous Tool Gateway accepts the same shape.
71
+ */
72
+ function buildRequestBody(action, url) {
73
+ return {
74
+ task: action,
75
+ url,
76
+ };
77
+ }
78
+ /**
79
+ * Normalise the JSON response into our unified result type.
80
+ * Both endpoints return slightly different shapes but converge on a few
81
+ * common fields.
82
+ */
83
+ function normaliseResponse(
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ json) {
86
+ return {
87
+ content: typeof json.result === 'string' ? json.result
88
+ : typeof json.content === 'string' ? json.content
89
+ : typeof json.output === 'string' ? json.output
90
+ : typeof json.text === 'string' ? json.text
91
+ : JSON.stringify(json),
92
+ screenshot: typeof json.screenshot === 'string' ? json.screenshot : undefined,
93
+ };
94
+ }
95
+ // ---------------------------------------------------------------------------
96
+ // Local path
97
+ // ---------------------------------------------------------------------------
98
+ /**
99
+ * Resolve whether the local browser-use path should be used. Precedence:
100
+ * - explicit `options.local`
101
+ * - `CODEBUDDY_BROWSER_USE_LOCAL` truthy (`1`/`true`/`yes`)
102
+ * - auto-detect: `browser_use` is importable by the chosen interpreter.
103
+ *
104
+ * The auto-detect probe (a fast `python3 -c "import browser_use"`) only runs on
105
+ * the no-managed-config path, so it never adds latency when an API key/gateway
106
+ * is present.
107
+ */
108
+ function shouldUseLocal(options) {
109
+ if (typeof options.local === 'boolean')
110
+ return options.local;
111
+ const flag = process.env.CODEBUDDY_BROWSER_USE_LOCAL?.trim().toLowerCase();
112
+ if (flag === '0' || flag === 'false' || flag === 'no')
113
+ return false;
114
+ if (flag === '1' || flag === 'true' || flag === 'yes')
115
+ return true;
116
+ return detectLocalBrowserUse(resolvePython(options));
117
+ }
118
+ function resolvePython(options) {
119
+ return options.pythonPath ?? (process.env.CODEBUDDY_BROWSER_USE_PYTHON?.trim() || 'python3');
120
+ }
121
+ function isTruthyEnv(value) {
122
+ const v = value?.trim().toLowerCase();
123
+ if (v === '1' || v === 'true' || v === 'yes')
124
+ return true;
125
+ if (v === '0' || v === 'false' || v === 'no')
126
+ return false;
127
+ return undefined;
128
+ }
129
+ /**
130
+ * The Chromium sandbox commonly blocks browser-use's launch inside containers /
131
+ * sandboxed CI (the 30 s browser-start watchdog fires). Default to disabling it
132
+ * on Linux, where this is the common failure; honour explicit option/env first.
133
+ */
134
+ function resolveNoSandbox(options) {
135
+ if (typeof options.noSandbox === 'boolean')
136
+ return options.noSandbox;
137
+ const env = isTruthyEnv(process.env.CODEBUDDY_BROWSER_USE_NO_SANDBOX);
138
+ if (typeof env === 'boolean')
139
+ return env;
140
+ return process.platform === 'linux';
141
+ }
142
+ /**
143
+ * browser-use sends screenshots to the model by default, which 400s on text-only
144
+ * Ollama models. Default vision OFF so the default text model works; explicit
145
+ * option/env overrides (set true when driving a vision-capable model).
146
+ */
147
+ function resolveUseVision(options) {
148
+ if (typeof options.useVision === 'boolean')
149
+ return options.useVision;
150
+ return isTruthyEnv(process.env.CODEBUDDY_BROWSER_USE_VISION) ?? false;
151
+ }
152
+ /** Probe: is the `browser_use` package importable by `python`? */
153
+ function detectLocalBrowserUse(python) {
154
+ try {
155
+ const result = spawnSync(python, ['-c', 'import browser_use'], {
156
+ encoding: 'utf8',
157
+ stdio: ['ignore', 'ignore', 'ignore'],
158
+ timeout: 5000,
159
+ windowsHide: true,
160
+ });
161
+ return !result.error && result.status === 0;
162
+ }
163
+ catch {
164
+ return false;
165
+ }
166
+ }
167
+ /**
168
+ * Inline Python entrypoint that runs a browser-use Agent against a local Ollama
169
+ * model and prints a single `__CB_BU_RESULT__{json}` line on stdout.
170
+ *
171
+ * The browser launch is configured from CB_BU_* env (executable path,
172
+ * no-sandbox, headless) — this is the config live-validated on a sandboxed
173
+ * Linux host (browser-use 0.13.1): a plain Chromium binary + chromium_sandbox
174
+ * disabled is what gets past the 30 s browser-start watchdog. `use_vision` is
175
+ * defaulted off so text-only Ollama models don't 400 on screenshot payloads.
176
+ *
177
+ * Kept resilient to browser-use API drift: the `ChatOllama` import location and
178
+ * the final-result accessor (`.final_result()` / `.extracted_content()` / str)
179
+ * vary across versions, so we try each in turn. The BrowserProfile / use_vision
180
+ * wiring is best-effort — if an older/newer browser-use rejects a kwarg we fall
181
+ * back to a bare `Agent(task, llm)`. Any failure is emitted as
182
+ * `{"ok": false, "error": ...}` with the same sentinel, so the TS side always
183
+ * gets a structured result.
184
+ */
185
+ function buildLocalScript() {
186
+ return [
187
+ 'import asyncio, json, os, sys',
188
+ 'SENTINEL = "' + RESULT_SENTINEL + '"',
189
+ '',
190
+ 'def emit(obj):',
191
+ ' sys.stdout.write(SENTINEL + json.dumps(obj) + "\\n")',
192
+ ' sys.stdout.flush()',
193
+ '',
194
+ 'def envflag(name):',
195
+ ' return os.environ.get(name, "").strip().lower() in ("1", "true", "yes")',
196
+ '',
197
+ 'def make_llm(model, host):',
198
+ ' # ChatOllama moved across browser-use versions; try known locations.',
199
+ ' errors = []',
200
+ ' for mod, attr in (("browser_use.llm", "ChatOllama"),',
201
+ ' ("langchain_ollama", "ChatOllama"),',
202
+ ' ("langchain_community.chat_models", "ChatOllama")):',
203
+ ' try:',
204
+ ' m = __import__(mod, fromlist=[attr])',
205
+ ' cls = getattr(m, attr)',
206
+ ' except Exception as e:',
207
+ ' errors.append(str(e)); continue',
208
+ ' for kwargs in ({"model": model, "host": host},',
209
+ ' {"model": model, "base_url": host},',
210
+ ' {"model": model}):',
211
+ ' try:',
212
+ ' return cls(**kwargs)',
213
+ ' except Exception as e:',
214
+ ' errors.append(str(e))',
215
+ ' raise RuntimeError("Could not construct ChatOllama: " + "; ".join(errors))',
216
+ '',
217
+ 'def make_profile():',
218
+ ' # Best-effort: build a BrowserProfile that launches a real Chromium',
219
+ ' # without the sandbox (the combo that passes the browser-start watchdog',
220
+ ' # on sandboxed Linux/CI). Returns None if BrowserProfile is unavailable.',
221
+ ' try:',
222
+ ' from browser_use.browser.profile import BrowserProfile',
223
+ ' except Exception:',
224
+ ' return None',
225
+ ' no_sandbox = envflag("CB_BU_NO_SANDBOX")',
226
+ ' kwargs = {"headless": True}',
227
+ ' chrome = os.environ.get("CB_BU_CHROME", "").strip()',
228
+ ' if chrome:',
229
+ ' kwargs["executable_path"] = chrome',
230
+ ' if no_sandbox:',
231
+ ' kwargs["chromium_sandbox"] = False',
232
+ ' kwargs["args"] = ["--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage"]',
233
+ ' try:',
234
+ ' return BrowserProfile(**kwargs)',
235
+ ' except Exception:',
236
+ ' return None',
237
+ '',
238
+ 'def make_agent(task, llm):',
239
+ ' from browser_use import Agent',
240
+ ' use_vision = envflag("CB_BU_VISION")',
241
+ ' profile = make_profile()',
242
+ ' # Try the richest constructor first, then degrade gracefully on TypeError',
243
+ ' # so we stay compatible across browser-use versions.',
244
+ ' attempts = []',
245
+ ' if profile is not None:',
246
+ ' attempts.append({"browser_profile": profile, "use_vision": use_vision})',
247
+ ' attempts.append({"browser_profile": profile})',
248
+ ' attempts.append({"use_vision": use_vision})',
249
+ ' attempts.append({})',
250
+ ' last = None',
251
+ ' for extra in attempts:',
252
+ ' try:',
253
+ ' return Agent(task=task, llm=llm, **extra)',
254
+ ' except TypeError as e:',
255
+ ' last = e',
256
+ ' raise last if last else RuntimeError("Could not construct Agent")',
257
+ '',
258
+ 'def extract(history):',
259
+ ' # browser-use Agent.run() returns a history object; pull final text.',
260
+ ' for accessor in ("final_result", "extracted_content"):',
261
+ ' fn = getattr(history, accessor, None)',
262
+ ' if callable(fn):',
263
+ ' try:',
264
+ ' val = fn()',
265
+ ' if val:',
266
+ ' return val if isinstance(val, str) else json.dumps(val, default=str)',
267
+ ' except Exception:',
268
+ ' pass',
269
+ ' return str(history)',
270
+ '',
271
+ 'async def run_agent(agent):',
272
+ ' try:',
273
+ ' return await agent.run(max_steps=int(os.environ.get("CB_BU_MAX_STEPS", "6")))',
274
+ ' except TypeError:',
275
+ ' return await agent.run()',
276
+ '',
277
+ 'async def main():',
278
+ ' task = os.environ["CB_BU_TASK"]',
279
+ ' url = os.environ.get("CB_BU_URL", "").strip()',
280
+ ' model = os.environ.get("CB_BU_MODEL", "' + DEFAULT_LOCAL_MODEL + '")',
281
+ ' host = os.environ.get("CB_BU_OLLAMA_HOST", "' + DEFAULT_OLLAMA_HOST + '")',
282
+ ' full_task = task if not url else (task + "\\nStart at this URL: " + url)',
283
+ ' llm = make_llm(model, host)',
284
+ ' agent = make_agent(full_task, llm)',
285
+ ' history = await run_agent(agent)',
286
+ ' emit({"ok": True, "content": extract(history)})',
287
+ '',
288
+ 'try:',
289
+ ' asyncio.run(main())',
290
+ 'except Exception as e:',
291
+ ' emit({"ok": False, "error": type(e).__name__ + ": " + str(e)})',
292
+ ].join('\n');
293
+ }
294
+ /** Pull the last sentinel-prefixed JSON object out of accumulated stdout. */
295
+ export function parseLocalResult(stdout) {
296
+ const lines = stdout.split(/\r?\n/);
297
+ for (let i = lines.length - 1; i >= 0; i--) {
298
+ const line = lines[i];
299
+ if (!line)
300
+ continue;
301
+ const idx = line.indexOf(RESULT_SENTINEL);
302
+ if (idx === -1)
303
+ continue;
304
+ const jsonText = line.slice(idx + RESULT_SENTINEL.length).trim();
305
+ try {
306
+ const parsed = JSON.parse(jsonText);
307
+ if (parsed.ok) {
308
+ return {
309
+ ok: true,
310
+ content: typeof parsed.content === 'string' ? parsed.content : JSON.stringify(parsed.content),
311
+ screenshot: typeof parsed.screenshot === 'string' ? parsed.screenshot : undefined,
312
+ };
313
+ }
314
+ return {
315
+ ok: false,
316
+ error: typeof parsed.error === 'string' ? parsed.error : 'Local browser-use run failed.',
317
+ };
318
+ }
319
+ catch {
320
+ // Not valid JSON on this line; keep scanning earlier lines.
321
+ }
322
+ }
323
+ return null;
324
+ }
325
+ /**
326
+ * Run the task locally via a spawned browser-use Python subprocess. Never
327
+ * throws — always resolves to a typed `BrowserUseActionResult`.
328
+ */
329
+ async function runLocalBrowserUse(action, url, options) {
330
+ const python = resolvePython(options);
331
+ const timeoutMs = options.timeout ?? DEFAULT_LOCAL_TIMEOUT_MS;
332
+ const model = options.model ?? (process.env.CODEBUDDY_BROWSER_USE_MODEL?.trim() || DEFAULT_LOCAL_MODEL);
333
+ const ollamaHost = options.ollamaHost ?? (process.env.OLLAMA_HOST?.trim() || DEFAULT_OLLAMA_HOST);
334
+ const chromePath = options.chromePath ?? (process.env.CODEBUDDY_BROWSER_USE_CHROME?.trim() || '');
335
+ const noSandbox = resolveNoSandbox(options);
336
+ const useVision = resolveUseVision(options);
337
+ const maxSteps = options.maxSteps ?? 6;
338
+ const script = buildLocalScript();
339
+ logger.debug(`[browser-use-runner] local → ${python} (model=${model}, host=${ollamaHost}, ` +
340
+ `vision=${useVision}, noSandbox=${noSandbox}, action=${action.slice(0, 80)})`);
341
+ const childEnv = {
342
+ ...process.env,
343
+ CB_BU_TASK: action,
344
+ CB_BU_URL: url,
345
+ CB_BU_MODEL: model,
346
+ CB_BU_OLLAMA_HOST: ollamaHost,
347
+ CB_BU_CHROME: chromePath,
348
+ CB_BU_NO_SANDBOX: noSandbox ? '1' : '0',
349
+ CB_BU_VISION: useVision ? '1' : '0',
350
+ CB_BU_MAX_STEPS: String(maxSteps),
351
+ };
352
+ return await new Promise((resolve) => {
353
+ let proc;
354
+ try {
355
+ proc = spawn(python, ['-c', script], {
356
+ env: childEnv,
357
+ stdio: ['ignore', 'pipe', 'pipe'],
358
+ windowsHide: true,
359
+ });
360
+ }
361
+ catch (err) {
362
+ const message = err instanceof Error ? err.message : String(err);
363
+ logger.warn(`[browser-use-runner] local spawn failed: ${message}`);
364
+ resolve({ ok: false, error: `Failed to spawn ${python} for local browser-use: ${message}` });
365
+ return;
366
+ }
367
+ let settled = false;
368
+ let stdoutBuf = '';
369
+ let stderrBuf = '';
370
+ const finish = (result) => {
371
+ if (settled)
372
+ return;
373
+ settled = true;
374
+ clearTimeout(timer);
375
+ resolve(result);
376
+ };
377
+ const timer = setTimeout(() => {
378
+ try {
379
+ proc.kill('SIGKILL');
380
+ }
381
+ catch { /* best-effort */ }
382
+ logger.warn(`[browser-use-runner] local run timed out after ${timeoutMs}ms.`);
383
+ finish({ ok: false, error: `Local browser-use run timed out after ${timeoutMs}ms.` });
384
+ }, timeoutMs);
385
+ proc.stdout?.on('data', (chunk) => {
386
+ stdoutBuf += chunk.toString();
387
+ });
388
+ proc.stderr?.on('data', (chunk) => {
389
+ stderrBuf += chunk.toString();
390
+ });
391
+ proc.on('error', (err) => {
392
+ const hint = /ENOENT/.test(err.message)
393
+ ? ` Python interpreter '${python}' not found, or the browser_use package is not installed. ` +
394
+ 'Install with: python3 -m venv <venv> && <venv>/bin/pip install browser-use && <venv>/bin/playwright install chromium, ' +
395
+ 'then set CODEBUDDY_BROWSER_USE_PYTHON=<venv>/bin/python.'
396
+ : '';
397
+ logger.warn(`[browser-use-runner] local spawn error: ${err.message}${hint}`);
398
+ finish({ ok: false, error: `Local browser-use spawn error: ${err.message}${hint}` });
399
+ });
400
+ proc.on('close', (code) => {
401
+ const parsed = parseLocalResult(stdoutBuf);
402
+ if (parsed) {
403
+ finish(parsed);
404
+ return;
405
+ }
406
+ const detail = classifyLocalError(stderrBuf || stdoutBuf);
407
+ finish({
408
+ ok: false,
409
+ error: `Local browser-use exited (code ${code ?? 'null'}) without a result. ${detail}`.trim(),
410
+ });
411
+ });
412
+ });
413
+ }
414
+ /** Map common local failures to an actionable message. */
415
+ function classifyLocalError(output) {
416
+ const text = output.trim();
417
+ if (!text)
418
+ return 'No diagnostic output was produced.';
419
+ if (/No module named ['"]?browser_use|ModuleNotFoundError.*browser_use/i.test(text)) {
420
+ return ('The browser_use Python package is not importable by the chosen interpreter. ' +
421
+ 'Install with: python3 -m venv <venv> && <venv>/bin/pip install browser-use && ' +
422
+ '<venv>/bin/playwright install chromium, then set CODEBUDDY_BROWSER_USE_PYTHON=<venv>/bin/python.');
423
+ }
424
+ if (/No module named ['"]?(langchain_ollama|playwright)/i.test(text)) {
425
+ return 'A browser-use dependency is missing (langchain-ollama / playwright). Reinstall browser-use and run `playwright install chromium`.';
426
+ }
427
+ if (/ConnectionError|Connection refused|Failed to connect|11434/i.test(text)) {
428
+ return 'Could not reach the local Ollama server. Start Ollama (default http://localhost:11434) or set OLLAMA_HOST, and pull a tool-calling model (e.g. qwen2.5:7b-instruct).';
429
+ }
430
+ const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? text.slice(0, 200);
431
+ return `Diagnostic: ${firstLine}`;
432
+ }
433
+ // ---------------------------------------------------------------------------
434
+ // Public API
435
+ // ---------------------------------------------------------------------------
436
+ /**
437
+ * Execute a browser action through the Browser Use service.
438
+ *
439
+ * ```ts
440
+ * const result = await executeBrowserUseAction(
441
+ * 'Extract the main heading',
442
+ * 'https://example.com',
443
+ * );
444
+ * if (result.ok) console.log(result.content);
445
+ * ```
446
+ */
447
+ export async function executeBrowserUseAction(action, url, options = {}) {
448
+ const endpoint = resolveEndpoint(options);
449
+ // No managed endpoint — try the local open-source path (no paid account).
450
+ if (!endpoint) {
451
+ if (shouldUseLocal(options)) {
452
+ return runLocalBrowserUse(action, url, options);
453
+ }
454
+ return {
455
+ ok: false,
456
+ error: 'Browser Use is not configured. Set BROWSER_USE_API_KEY or CODEBUDDY_NOUS_TOOL_GATEWAY_URL, ' +
457
+ 'or run locally with CODEBUDDY_BROWSER_USE_LOCAL=1 (requires `pip install browser-use` + a local Ollama model).',
458
+ };
459
+ }
460
+ const timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
461
+ try {
462
+ const controller = new AbortController();
463
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
464
+ logger.debug(`[browser-use-runner] ${endpoint.kind} → POST ${endpoint.url} (action=${action.slice(0, 80)})`);
465
+ const response = await fetch(endpoint.url, {
466
+ method: 'POST',
467
+ headers: endpoint.headers,
468
+ body: JSON.stringify(buildRequestBody(action, url)),
469
+ signal: controller.signal,
470
+ });
471
+ clearTimeout(timer);
472
+ if (!response.ok) {
473
+ const body = await response.text().catch(() => '');
474
+ const message = `${endpoint.kind} returned HTTP ${response.status}: ${body.slice(0, 200)}`;
475
+ logger.warn(`[browser-use-runner] ${message}`);
476
+ return { ok: false, error: message };
477
+ }
478
+ const json = (await response.json());
479
+ const { content, screenshot } = normaliseResponse(json);
480
+ return { ok: true, content, screenshot };
481
+ }
482
+ catch (err) {
483
+ const message = err instanceof Error ? err.message : String(err);
484
+ const isAbort = err instanceof Error && err.name === 'AbortError';
485
+ const errorText = isAbort
486
+ ? `Request to ${endpoint.kind} timed out after ${timeoutMs}ms.`
487
+ : `${endpoint.kind} request failed: ${message}`;
488
+ logger.warn(`[browser-use-runner] ${errorText}`);
489
+ return { ok: false, error: errorText };
490
+ }
491
+ }
492
+ //# sourceMappingURL=browser-use-runner.js.map