@phuetz/code-buddy 1.1.0 → 1.3.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 +211 -163
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +12 -12
- package/dist/agent/base-agent.d.ts +4 -0
- package/dist/agent/base-agent.js +6 -0
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- 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/facades/infrastructure-facade.d.ts +9 -2
- package/dist/agent/facades/infrastructure-facade.js +15 -6
- 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/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -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/self-improvement/authored-artifact-gate.d.ts +18 -0
- package/dist/agent/self-improvement/authored-artifact-gate.js +42 -0
- package/dist/agent/self-improvement/authored-tool-runtime.d.ts +27 -0
- package/dist/agent/self-improvement/authored-tool-runtime.js +57 -0
- package/dist/agent/self-improvement/authored-tool-store.d.ts +24 -0
- package/dist/agent/self-improvement/authored-tool-store.js +57 -0
- package/dist/agent/self-improvement/llm-tool-proposer.d.ts +41 -0
- package/dist/agent/self-improvement/llm-tool-proposer.js +136 -0
- package/dist/agent/self-improvement/sandbox-scorer.d.ts +17 -0
- package/dist/agent/self-improvement/sandbox-scorer.js +43 -0
- package/dist/agent/self-improvement/self-knowledge.d.ts +8 -0
- package/dist/agent/self-improvement/self-knowledge.js +24 -0
- package/dist/agent/self-improvement/skill-benchmark.d.ts +9 -0
- package/dist/agent/self-improvement/skill-benchmark.js +22 -0
- package/dist/agent/self-improvement/skill-consolidator.d.ts +71 -0
- package/dist/agent/self-improvement/skill-consolidator.js +137 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +42 -0
- package/dist/agent/self-improvement/skill-engine.js +87 -0
- package/dist/agent/self-improvement/skill-gate.d.ts +19 -0
- package/dist/agent/self-improvement/skill-gate.js +62 -0
- package/dist/agent/self-improvement/skill-mutator.d.ts +74 -0
- package/dist/agent/self-improvement/skill-mutator.js +223 -0
- package/dist/agent/self-improvement/skill-proposer.d.ts +40 -0
- package/dist/agent/self-improvement/skill-proposer.js +82 -0
- package/dist/agent/self-improvement/skill-types.d.ts +41 -0
- package/dist/agent/self-improvement/skill-types.js +13 -0
- package/dist/agent/self-improvement/tool-benchmark.d.ts +10 -0
- package/dist/agent/self-improvement/tool-benchmark.js +37 -0
- package/dist/agent/self-improvement/tool-engine.d.ts +54 -0
- package/dist/agent/self-improvement/tool-engine.js +101 -0
- package/dist/agent/self-improvement/tool-gate.d.ts +20 -0
- package/dist/agent/self-improvement/tool-gate.js +78 -0
- package/dist/agent/self-improvement/tool-proposer.d.ts +31 -0
- package/dist/agent/self-improvement/tool-proposer.js +34 -0
- package/dist/agent/self-improvement/tool-skill-mutator.d.ts +40 -0
- package/dist/agent/self-improvement/tool-skill-mutator.js +79 -0
- package/dist/agent/self-improvement/tool-types.d.ts +48 -0
- package/dist/agent/self-improvement/tool-types.js +9 -0
- package/dist/agent/self-improvement/types.d.ts +3 -1
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- 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 +133 -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 +7 -1
- 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 +8 -1
- package/dist/codebuddy/tools.js +67 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/improve-command.js +123 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +2 -2
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/handlers/missing-handlers.d.ts +1 -1
- package/dist/commands/handlers/missing-handlers.js +26 -3
- 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/skills-cli/index.js +123 -0
- package/dist/commands/slash/builtin-commands.js +1 -1
- 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/companion/percepts.js +11 -1
- 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 +756 -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/bootstrap-loader.js +6 -23
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/context/import-directive-parser.d.ts +4 -0
- package/dist/context/import-directive-parser.js +51 -6
- package/dist/context/instruction-excludes.d.ts +30 -1
- package/dist/context/instruction-excludes.js +71 -1
- package/dist/context/jit-context.d.ts +8 -10
- package/dist/context/jit-context.js +28 -106
- package/dist/context/project-context.d.ts +90 -0
- package/dist/context/project-context.js +295 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.d.ts +31 -1
- package/dist/daemon/autonomous-loop.js +91 -4
- package/dist/daemon/colab-goal.js +9 -1
- 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.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -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 +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/harness/contract.d.ts +28 -28
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/identity/identity-manager.js +3 -2
- package/dist/index.js +481 -26
- 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-resources.js +2 -3
- 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/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/sensory/dreaming.d.ts +45 -0
- package/dist/sensory/dreaming.js +114 -0
- package/dist/sensory/heartbeat-scheduler.d.ts +38 -0
- package/dist/sensory/heartbeat-scheduler.js +72 -0
- package/dist/sensory/reactions.d.ts +24 -0
- package/dist/sensory/reactions.js +31 -0
- package/dist/sensory/screen-reaction.d.ts +23 -0
- package/dist/sensory/screen-reaction.js +59 -0
- package/dist/sensory/sensory-bridge.d.ts +23 -0
- package/dist/sensory/sensory-bridge.js +85 -0
- package/dist/sensory/sensory-memory.d.ts +20 -0
- package/dist/sensory/sensory-memory.js +39 -0
- package/dist/sensory/speech-reaction.d.ts +21 -0
- package/dist/sensory/speech-reaction.js +83 -0
- package/dist/sensory/vision-reaction.d.ts +31 -0
- package/dist/sensory/vision-reaction.js +74 -0
- package/dist/server/index.js +106 -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.js +5 -0
- package/dist/services/prompt-builder.d.ts +11 -0
- package/dist/services/prompt-builder.js +124 -17
- 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/skills/parser.js +3 -0
- package/dist/skills/skill-importer.d.ts +58 -0
- package/dist/skills/skill-importer.js +261 -0
- package/dist/skills/skill-sources.d.ts +20 -0
- package/dist/skills/skill-sources.js +102 -0
- package/dist/skills/types.d.ts +6 -0
- 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/register-tool-handler.d.ts +25 -0
- package/dist/tools/register-tool-handler.js +100 -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/registry.d.ts +6 -0
- package/dist/tools/registry.js +8 -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/init-project.d.ts +7 -0
- package/dist/utils/init-project.js +37 -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 +19 -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
|
@@ -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>;
|