@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.
- package/README.md +160 -164
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +101 -13
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/lesson-auto-proposer.js +10 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/session-duration.d.ts +36 -0
- package/dist/agent/middleware/session-duration.js +78 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/session-end-flush.d.ts +71 -0
- package/dist/agent/session-end-flush.js +233 -0
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +130 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +1 -1
- package/dist/codebuddy/tools.js +27 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/native-engine-commands.js +107 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +5 -0
- package/dist/commands/goal-cli.d.ts +71 -0
- package/dist/commands/goal-cli.js +280 -0
- package/dist/commands/handlers/goal-handler.d.ts +34 -0
- package/dist/commands/handlers/goal-handler.js +155 -0
- package/dist/commands/handlers/index.d.ts +1 -0
- package/dist/commands/handlers/index.js +2 -0
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/slash/builtin-commands.js +20 -0
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +770 -0
- package/dist/config/feature-flags.js +7 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/context-manager-v2.d.ts +39 -0
- package/dist/context/context-manager-v2.js +91 -0
- package/dist/daemon/agent-task-executor.js +12 -1
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +5 -3
- package/dist/daemon/autonomous-loop.d.ts +21 -1
- package/dist/daemon/autonomous-loop.js +69 -0
- package/dist/daemon/colab-goal.d.ts +38 -0
- package/dist/daemon/colab-goal.js +81 -0
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.d.ts +21 -0
- package/dist/fleet/colab-store.js +28 -0
- package/dist/fleet/peer-session-bridge.d.ts +1 -1
- package/dist/fleet/peer-session-bridge.js +243 -2
- package/dist/fleet/peer-session-store.d.ts +3 -0
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/fleet/privacy-lint.d.ts +8 -0
- package/dist/fleet/privacy-lint.js +22 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +38 -0
- package/dist/goals/goal-judge.js +158 -0
- package/dist/goals/goal-loop.d.ts +37 -0
- package/dist/goals/goal-loop.js +101 -0
- package/dist/goals/goal-manager.d.ts +77 -0
- package/dist/goals/goal-manager.js +289 -0
- package/dist/goals/goal-state.d.ts +99 -0
- package/dist/goals/goal-state.js +315 -0
- package/dist/goals/goal-store.d.ts +28 -0
- package/dist/goals/goal-store.js +87 -0
- package/dist/goals/index.d.ts +5 -0
- package/dist/goals/index.js +6 -0
- package/dist/hooks/use-input-handler.js +36 -1
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +506 -25
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/observability/run-store.d.ts +1 -1
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/server/index.js +17 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.d.ts +13 -1
- package/dist/server/websocket/fleet-bridge.js +16 -0
- package/dist/services/prompt-builder.d.ts +1 -0
- package/dist/services/prompt-builder.js +49 -8
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +7 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- 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
|