@phuetz/code-buddy 1.1.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 (291) hide show
  1. package/README.md +160 -165
  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 +69 -14
  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/middleware/index.d.ts +1 -0
  20. package/dist/agent/middleware/index.js +1 -0
  21. package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
  22. package/dist/agent/middleware/visual-validation-middleware.js +67 -0
  23. package/dist/agent/model-benchmark.d.ts +77 -0
  24. package/dist/agent/model-benchmark.js +309 -0
  25. package/dist/agent/model-tier.d.ts +14 -0
  26. package/dist/agent/model-tier.js +71 -0
  27. package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
  28. package/dist/agent/multi-agent/multi-agent-system.js +3 -1
  29. package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
  30. package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
  31. package/dist/agent/multi-agent/session-tools.js +22 -5
  32. package/dist/agent/reasoning/mcts.js +20 -6
  33. package/dist/agent/session-end-flush.d.ts +6 -1
  34. package/dist/agent/session-end-flush.js +20 -4
  35. package/dist/agent/streaming/streaming-handler.d.ts +7 -0
  36. package/dist/agent/streaming/streaming-handler.js +8 -1
  37. package/dist/agent/tool-executor.js +1 -0
  38. package/dist/agent/tool-handler.d.ts +5 -0
  39. package/dist/agent/tool-handler.js +130 -4
  40. package/dist/browser-automation/browser-operator-executor.js +15 -0
  41. package/dist/browser-automation/browser-use-runner.d.ts +96 -0
  42. package/dist/browser-automation/browser-use-runner.js +492 -0
  43. package/dist/browser-automation/camofox-runner.d.ts +107 -0
  44. package/dist/browser-automation/camofox-runner.js +287 -0
  45. package/dist/channels/dingtalk/index.js +3 -0
  46. package/dist/channels/discord/client.d.ts +9 -0
  47. package/dist/channels/discord/client.js +12 -0
  48. package/dist/channels/feishu/index.d.ts +167 -1
  49. package/dist/channels/feishu/index.js +432 -7
  50. package/dist/channels/gateway-lifecycle.d.ts +132 -0
  51. package/dist/channels/gateway-lifecycle.js +219 -0
  52. package/dist/channels/google-chat/index.d.ts +5 -1
  53. package/dist/channels/google-chat/index.js +5 -1
  54. package/dist/channels/imessage/index.d.ts +14 -0
  55. package/dist/channels/imessage/index.js +64 -17
  56. package/dist/channels/index.d.ts +4 -0
  57. package/dist/channels/index.js +4 -0
  58. package/dist/channels/irc/index.d.ts +73 -3
  59. package/dist/channels/irc/index.js +446 -11
  60. package/dist/channels/line/index.js +3 -0
  61. package/dist/channels/mattermost/index.d.ts +51 -4
  62. package/dist/channels/mattermost/index.js +303 -20
  63. package/dist/channels/nextcloud-talk/index.d.ts +89 -8
  64. package/dist/channels/nextcloud-talk/index.js +367 -16
  65. package/dist/channels/nostr/index.d.ts +121 -1
  66. package/dist/channels/nostr/index.js +396 -8
  67. package/dist/channels/ntfy/index.js +3 -0
  68. package/dist/channels/qq/index.js +3 -0
  69. package/dist/channels/slash-parity.d.ts +117 -0
  70. package/dist/channels/slash-parity.js +185 -0
  71. package/dist/channels/synology-chat/index.js +3 -0
  72. package/dist/channels/teams/index.d.ts +3 -0
  73. package/dist/channels/teams/index.js +3 -0
  74. package/dist/channels/telegram/client.d.ts +9 -0
  75. package/dist/channels/telegram/client.js +12 -0
  76. package/dist/channels/twilio-voice/index.js +3 -0
  77. package/dist/channels/webchat/index.d.ts +7 -1
  78. package/dist/channels/webchat/index.js +7 -1
  79. package/dist/channels/wecom/index.js +3 -0
  80. package/dist/channels/weixin/index.js +3 -0
  81. package/dist/channels/zalo/index.js +3 -0
  82. package/dist/cli/config-loader.js +1 -1
  83. package/dist/codebuddy/client.d.ts +30 -1
  84. package/dist/codebuddy/client.js +167 -20
  85. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
  86. package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
  87. package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
  88. package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
  89. package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
  90. package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
  91. package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
  92. package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
  93. package/dist/codebuddy/tool-definitions/index.js +2 -1
  94. package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
  95. package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
  96. package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
  97. package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
  98. package/dist/codebuddy/tools.d.ts +1 -1
  99. package/dist/codebuddy/tools.js +27 -24
  100. package/dist/commands/cli/hermes-commands.js +108 -0
  101. package/dist/commands/cli/native-engine-commands.js +102 -3
  102. package/dist/commands/cli/utility-commands.js +45 -0
  103. package/dist/commands/enhanced-command-handler.js +1 -1
  104. package/dist/commands/goal-cli.d.ts +30 -0
  105. package/dist/commands/goal-cli.js +210 -27
  106. package/dist/commands/handlers/goal-handler.d.ts +9 -2
  107. package/dist/commands/handlers/goal-handler.js +36 -9
  108. package/dist/commands/handlers/infra-handlers.js +36 -0
  109. package/dist/commands/handlers/memory-handlers.js +96 -1
  110. package/dist/commands/headless-slash.d.ts +2 -0
  111. package/dist/commands/headless-slash.js +12 -0
  112. package/dist/commands/llm-provider-resolution.d.ts +5 -3
  113. package/dist/commands/llm-provider-resolution.js +87 -33
  114. package/dist/commands/ollama.d.ts +25 -0
  115. package/dist/commands/ollama.js +100 -0
  116. package/dist/commands/provider.d.ts +5 -0
  117. package/dist/commands/provider.js +106 -55
  118. package/dist/commands/spec-next.js +2 -1
  119. package/dist/commands/spec-plan.js +5 -16
  120. package/dist/commands/tunnel.d.ts +2 -0
  121. package/dist/commands/tunnel.js +48 -0
  122. package/dist/config/config-resolver.d.ts +2 -1
  123. package/dist/config/config-resolver.js +54 -41
  124. package/dist/config/constants.d.ts +28 -0
  125. package/dist/config/constants.js +7 -0
  126. package/dist/config/env-schema.js +756 -0
  127. package/dist/config/model-tools.js +4 -4
  128. package/dist/config/toml-config.d.ts +16 -0
  129. package/dist/config/toml-config.js +3 -0
  130. package/dist/context/context-manager-v2.js +1 -0
  131. package/dist/daemon/autonomous-daemon.d.ts +1 -1
  132. package/dist/daemon/autonomous-daemon.js +3 -3
  133. package/dist/daemon/autonomous-loop.js +11 -2
  134. package/dist/daemon/colab-goal.js +9 -1
  135. package/dist/daemon/cron-agent-bridge.d.ts +12 -3
  136. package/dist/daemon/cron-agent-bridge.js +25 -9
  137. package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
  138. package/dist/desktop/codebuddy-engine-adapter.js +257 -80
  139. package/dist/desktop/engine-adapter.d.ts +14 -0
  140. package/dist/desktop-automation/automation-manager.js +16 -0
  141. package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
  142. package/dist/desktop-automation/omniparser-runner.js +115 -0
  143. package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
  144. package/dist/desktop-automation/smart-snapshot.js +72 -5
  145. package/dist/doctor/index.js +40 -23
  146. package/dist/events/types.d.ts +11 -0
  147. package/dist/fleet/colab-store.js +14 -2
  148. package/dist/fleet/peer-session-bridge.js +50 -12
  149. package/dist/fleet/peer-tool-bridge.js +14 -0
  150. package/dist/goals/goal-decomposer.d.ts +27 -0
  151. package/dist/goals/goal-decomposer.js +293 -0
  152. package/dist/goals/goal-judge-client.d.ts +15 -0
  153. package/dist/goals/goal-judge-client.js +45 -0
  154. package/dist/goals/goal-judge.d.ts +2 -0
  155. package/dist/goals/goal-judge.js +35 -6
  156. package/dist/goals/goal-loop.d.ts +14 -0
  157. package/dist/goals/goal-loop.js +47 -2
  158. package/dist/goals/goal-manager.d.ts +6 -0
  159. package/dist/goals/goal-manager.js +67 -14
  160. package/dist/goals/goal-state.d.ts +13 -0
  161. package/dist/goals/goal-state.js +83 -13
  162. package/dist/goals/goal-store.d.ts +3 -0
  163. package/dist/goals/goal-store.js +31 -15
  164. package/dist/hooks/user-hooks.js +17 -3
  165. package/dist/index.js +464 -25
  166. package/dist/input/text-to-speech.d.ts +2 -6
  167. package/dist/input/text-to-speech.js +2 -27
  168. package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
  169. package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
  170. package/dist/integrations/tailscale.d.ts +13 -0
  171. package/dist/integrations/tailscale.js +87 -34
  172. package/dist/knowledge/workspace-indexer.js +53 -9
  173. package/dist/mcp/client.js +1 -0
  174. package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
  175. package/dist/mcp/mcp-desktop-tools.js +158 -0
  176. package/dist/mcp/mcp-server.js +2 -0
  177. package/dist/memory/enhanced-memory.d.ts +10 -0
  178. package/dist/memory/enhanced-memory.js +33 -2
  179. package/dist/memory/index.d.ts +3 -1
  180. package/dist/memory/index.js +4 -1
  181. package/dist/memory/memory-auto-proposer.d.ts +23 -0
  182. package/dist/memory/memory-auto-proposer.js +308 -0
  183. package/dist/memory/memory-candidate-queue.d.ts +92 -0
  184. package/dist/memory/memory-candidate-queue.js +261 -0
  185. package/dist/memory/persistent-memory.d.ts +45 -2
  186. package/dist/memory/persistent-memory.js +236 -40
  187. package/dist/ml/bayesian-qualifier.d.ts +1 -1
  188. package/dist/ml/bayesian-qualifier.js +35 -2
  189. package/dist/openclaw/gateway-bridge.js +5 -0
  190. package/dist/plugins/marketplace.d.ts +1 -0
  191. package/dist/plugins/marketplace.js +7 -0
  192. package/dist/prompts/execution-discipline.d.ts +14 -0
  193. package/dist/prompts/execution-discipline.js +29 -0
  194. package/dist/prompts/variation-injector.js +9 -2
  195. package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
  196. package/dist/protocols/acp/acp-agentic-runner.js +115 -18
  197. package/dist/protocols/acp/acp-session-store.d.ts +23 -0
  198. package/dist/protocols/acp/acp-session-store.js +77 -0
  199. package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
  200. package/dist/protocols/acp/acp-stdio-server.js +71 -16
  201. package/dist/providers/active-llm-registry.d.ts +37 -0
  202. package/dist/providers/active-llm-registry.js +186 -0
  203. package/dist/providers/auxiliary-provider.d.ts +25 -0
  204. package/dist/providers/auxiliary-provider.js +192 -0
  205. package/dist/providers/codex-oauth.d.ts +1 -1
  206. package/dist/providers/codex-oauth.js +27 -4
  207. package/dist/providers/index.d.ts +3 -0
  208. package/dist/providers/index.js +4 -0
  209. package/dist/providers/provider-catalog.d.ts +70 -0
  210. package/dist/providers/provider-catalog.js +738 -0
  211. package/dist/providers/provider-fallback.d.ts +35 -0
  212. package/dist/providers/provider-fallback.js +171 -0
  213. package/dist/providers/xai-oauth.d.ts +128 -0
  214. package/dist/providers/xai-oauth.js +735 -0
  215. package/dist/scheduler/cron-scheduler.d.ts +15 -3
  216. package/dist/scheduler/cron-scheduler.js +53 -7
  217. package/dist/scheduler/script-runner.d.ts +2 -0
  218. package/dist/scheduler/script-runner.js +19 -4
  219. package/dist/scheduler/watchdog-handlers.js +7 -20
  220. package/dist/search/usearch-index.js +7 -2
  221. package/dist/security/tool-policy/tool-groups.js +2 -0
  222. package/dist/server/index.js +17 -2
  223. package/dist/server/routes/mobile.d.ts +12 -4
  224. package/dist/server/routes/mobile.js +116 -1
  225. package/dist/server/tls-config.d.ts +35 -0
  226. package/dist/server/tls-config.js +142 -0
  227. package/dist/server/tunnel-manager.d.ts +20 -0
  228. package/dist/server/tunnel-manager.js +58 -0
  229. package/dist/server/websocket/fleet-bridge.js +5 -0
  230. package/dist/services/prompt-builder.d.ts +1 -0
  231. package/dist/services/prompt-builder.js +49 -8
  232. package/dist/shared/engine-types.d.ts +15 -1
  233. package/dist/sidecar/sidecar-bridge.d.ts +1 -0
  234. package/dist/sidecar/sidecar-bridge.js +21 -10
  235. package/dist/spec/spec-store.js +8 -1
  236. package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
  237. package/dist/talk-mode/providers/audioreader-tts.js +8 -8
  238. package/dist/templates/project-scaffolding.js +3 -2
  239. package/dist/themes/theme-schema.d.ts +10 -10
  240. package/dist/tools/application-profiles.js +38 -0
  241. package/dist/tools/bash/bash-tool.d.ts +1 -0
  242. package/dist/tools/bash/bash-tool.js +16 -2
  243. package/dist/tools/bash/command-validator.js +3 -0
  244. package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
  245. package/dist/tools/bash/rtk-rewrite.js +100 -0
  246. package/dist/tools/bash/streaming-executor.js +14 -1
  247. package/dist/tools/computer-control-tool.d.ts +16 -1
  248. package/dist/tools/computer-control-tool.js +317 -5
  249. package/dist/tools/document-generator.d.ts +14 -0
  250. package/dist/tools/document-generator.js +79 -0
  251. package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
  252. package/dist/tools/execute-code-rpc-invoker.js +46 -5
  253. package/dist/tools/metadata.js +28 -0
  254. package/dist/tools/office-macro-tool.d.ts +10 -0
  255. package/dist/tools/office-macro-tool.js +93 -0
  256. package/dist/tools/registry/index.d.ts +2 -1
  257. package/dist/tools/registry/index.js +2 -1
  258. package/dist/tools/registry/memory-tools.d.ts +20 -0
  259. package/dist/tools/registry/memory-tools.js +225 -3
  260. package/dist/tools/registry/multimodal-tools.js +2 -2
  261. package/dist/tools/registry/vision-tools.d.ts +33 -0
  262. package/dist/tools/registry/vision-tools.js +308 -6
  263. package/dist/tools/registry/windows-tools.d.ts +2 -0
  264. package/dist/tools/registry/windows-tools.js +47 -0
  265. package/dist/tools/route-peer-tool.js +14 -0
  266. package/dist/tools/screenshot-tool.js +14 -2
  267. package/dist/tools/text-to-speech-tool.d.ts +1 -1
  268. package/dist/tools/text-to-speech-tool.js +2 -12
  269. package/dist/tools/tool-selector.js +3 -59
  270. package/dist/utils/config-validation/schema.d.ts +40 -7
  271. package/dist/utils/config-validation/schema.js +130 -1
  272. package/dist/utils/cost-tracker.js +21 -1
  273. package/dist/utils/disk-guard.d.ts +180 -0
  274. package/dist/utils/disk-guard.js +404 -0
  275. package/dist/utils/interactive-setup.js +35 -2
  276. package/dist/utils/model-utils.js +11 -2
  277. package/dist/utils/output-sanitizer.js +11 -0
  278. package/dist/utils/provider-detector.d.ts +8 -7
  279. package/dist/utils/provider-detector.js +19 -77
  280. package/dist/utils/settings-manager.d.ts +7 -0
  281. package/dist/utils/token-display.d.ts +3 -3
  282. package/dist/utils/token-display.js +22 -4
  283. package/dist/wizard/onboarding.d.ts +39 -0
  284. package/dist/wizard/onboarding.js +433 -21
  285. package/dist/wizard/provider-onboarding.d.ts +8 -23
  286. package/dist/wizard/provider-onboarding.js +39 -158
  287. package/package.json +4 -2
  288. package/dist/plugins/provider-onboarding.d.ts +0 -23
  289. package/dist/plugins/provider-onboarding.js +0 -116
  290. package/dist/utils/kokoro-tts.d.ts +0 -8
  291. package/dist/utils/kokoro-tts.js +0 -67
@@ -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
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Camoufox Runner — Playwright-server protocol (NOT Chrome CDP)
3
+ *
4
+ * Camoufox is an anti-detect **Firefox**. It does NOT speak the Chrome
5
+ * DevTools Protocol and exposes no `/json/version` endpoint. The upstream
6
+ * supported automation surface is a **Playwright server**: the Python
7
+ * `camoufox` package launches Playwright's `firefox` `launchServer`, which
8
+ * prints a Playwright-server WebSocket endpoint (`ws://<host>:<port>/<guid>`).
9
+ * The caller connects to it with `playwright.firefox.connect(wsEndpoint)` —
10
+ * NOT `chromium.connectOverCDP()` (that is Chromium-only and invalid here).
11
+ *
12
+ * This runner spawns the server, parses the printed wsEndpoint, and returns
13
+ * it. The caller owns the `firefox.connect()` step (see
14
+ * `src/agent/hermes-browser-backends.ts → runCamofoxSmoke`).
15
+ *
16
+ * ── Live-validated on this host (camoufox 0.4.11, 2026-06-14) ──────────────
17
+ * The repo's Node Playwright 1.58.2 `firefox.connect()` SUCCESSFULLY connects
18
+ * to a server launched by the venv's Playwright 1.58.0 driver — same-minor
19
+ * patch skew passes Playwright's version guard, so a real end-to-end page
20
+ * load (title + heading round-trip) works. If a future ecosystem bump widens
21
+ * the gap to a different minor (e.g. server 1.49 vs client 1.58), `connect`
22
+ * throws a version-guard error; the caller surfaces that honestly rather than
23
+ * faking success.
24
+ *
25
+ * ── 0.4.11 CLI bug worked around here ──────────────────────────────────────
26
+ * `python -m camoufox server` (and `camoufox.server.launch_server()` with no
27
+ * args) is BROKEN in 0.4.11: `launch_options()` emits `proxy: null`, which the
28
+ * bundled `launchServer.js` rejects with
29
+ * `proxy: expected object, got null → Failed to launch browser`. We drive the
30
+ * same public `launch_server` plumbing from a tiny inline Python launcher that
31
+ * strips null-valued options before piping them to `launchServer.js`, which is
32
+ * the supported path minus the upstream null bug.
33
+ *
34
+ * Falls back gracefully when Camoufox is not installed — every public function
35
+ * returns a typed result rather than throwing.
36
+ */
37
+ export interface CamofoxRunnerOptions {
38
+ /** Run without a visible window (default: true). */
39
+ headless?: boolean;
40
+ /**
41
+ * Milliseconds to wait for the server to print its wsEndpoint
42
+ * (default: 30 000 — the first launch downloads/initialises the profile).
43
+ */
44
+ timeout?: number;
45
+ /**
46
+ * Path to a Python interpreter that has the `camoufox` package installed.
47
+ * Defaults to `$CODEBUDDY_CAMOFOX_PYTHON`, then `python3`.
48
+ * This is the supported entry point — Camoufox's server is Python-driven.
49
+ */
50
+ pythonPath?: string;
51
+ /**
52
+ * Optional explicit path to the Camoufox **browser binary** to hand to the
53
+ * server (`executable_path`). Defaults to `$CODEBUDDY_CAMOFOX_BINARY`, else
54
+ * the package's own auto-resolved install. Rarely needed.
55
+ */
56
+ binaryPath?: string;
57
+ }
58
+ export interface CamofoxRunnerResult {
59
+ ok: boolean;
60
+ /**
61
+ * Playwright-server WebSocket endpoint, e.g.
62
+ * `ws://localhost:44477/51422436867b21da7464b5b253fd2fdd`.
63
+ * Connect with `playwright.firefox.connect(wsEndpoint)` — NOT connectOverCDP.
64
+ */
65
+ wsEndpoint?: string;
66
+ error?: string;
67
+ /**
68
+ * PID of the launched Python process group leader. Pass to `closeCamofox`
69
+ * to reap the whole python → node → firefox tree.
70
+ */
71
+ pid?: number;
72
+ }
73
+ /**
74
+ * Extract the Playwright-server WS endpoint from accumulated server stdout.
75
+ * Upstream prints: `Websocket endpoint:\x1b[93m ws://host:port/guid \x1b[0m`.
76
+ *
77
+ * Only returns an endpoint once the line is **complete** — i.e. the buffer
78
+ * contains a delimiter *after* the `ws://…` token (the trailing ANSI reset is
79
+ * stripped, so we look for whitespace / newline / end-of-input following it).
80
+ * This avoids resolving a truncated URL when the pipe splits mid-line.
81
+ */
82
+ export declare function parseWsEndpoint(text: string): string | null;
83
+ /**
84
+ * Launch a Camoufox Playwright server and return its wsEndpoint.
85
+ *
86
+ * ```ts
87
+ * const result = await launchCamofox({ headless: true });
88
+ * if (result.ok) {
89
+ * const playwright = await import('playwright');
90
+ * // Firefox-based — use firefox.connect(), NOT chromium.connectOverCDP().
91
+ * const browser = await playwright.firefox.connect(result.wsEndpoint!);
92
+ * // ... drive the browser ...
93
+ * await browser.close();
94
+ * await closeCamofox(result.pid!);
95
+ * }
96
+ * ```
97
+ */
98
+ export declare function launchCamofox(options?: CamofoxRunnerOptions): Promise<CamofoxRunnerResult>;
99
+ /**
100
+ * Gracefully terminate a previously launched Camoufox server tree.
101
+ *
102
+ * The launcher is spawned `detached`, so its PID leads a process group that
103
+ * contains the node Playwright server and every Firefox process. We signal the
104
+ * whole group (`-pid`) — signalling only the python leader would orphan the
105
+ * node server and the browser.
106
+ */
107
+ export declare function closeCamofox(pid: number): Promise<void>;