Commit Graph

9153 Commits

Author SHA1 Message Date
ShawnPana f26bd52af0 fix: enable Page domain before registering dialog handler
Page.enable() must be called on the root CDP client before
javascriptDialogOpening events will fire. Without this, JS dialogs
freeze all CDP commands because the auto-dismiss handler never triggers.
2026-04-01 19:11:52 -07:00
ShawnPana 094b699eb7 fix: add 'new' to tab command error message 2026-04-01 19:01:25 -07:00
ShawnPana 6c0ac3adab fix: only create new profile on 404, guard JSON parsing
Profile validation now fails on auth/server errors instead of silently
creating a new profile. Also wraps profile creation response parsing
in try/except for malformed payloads.
2026-04-01 18:59:51 -07:00
ShawnPana b4c874721a fix: scroll handles left/right directions correctly
window.scrollBy now maps left/right to X axis instead of always
using Y axis. Fixes horizontal scroll regression from CDP-to-JS rewrite.
2026-04-01 18:57:52 -07:00
ShawnPana b92c7486bf fix: remove 9229 from Chrome CDP port probing (Node.js debugger port, not Chrome) 2026-04-01 18:24:41 -07:00
ShawnPana 870776fa9e docs: restructure SKILL.md — headless-first, push advanced to references
Core workflow now starts with bare headless (no setup step). connect and
cloud connect mentioned prominently but not required. Python/CDP and
multi-session pushed to reference files. Added session recovery tip.
219 lines (down from 272).
2026-04-01 18:20:02 -07:00
ShawnPana e09f55eedc chore: remove more dead code (second codex pass)
- Removed utils.py is_daemon_alive() (stale duplicate of main.py version)
- Removed TunnelManager.is_available() (unused, get_status() used instead)
- Removed dead json_output param from setup.handle()
2026-04-01 17:56:39 -07:00
ShawnPana 01995138f1 chore: remove dead code found by codex review
- Deleted commands/utils.py (format_duration never referenced)
- Removed COMMANDS constant from doctor.py (never read)
- Removed list_sessions and get_log_path from utils.py (no callers)
- Removed unreachable event_bus fallbacks from BrowserWrapper
- Fixed dead assignment in doctor _check_browser
2026-04-01 17:43:42 -07:00
ShawnPana ca05f46352 refactor: remove --agent/register/tab-ownership, sessions-as-agents model
Multi-agent isolation is now achieved through separate sessions
(--session NAME), each with its own browser. Removed:
- register command and agents.json
- --agent flag and agent_id plumbing
- TabOwnershipManager and all tab locking logic
- dispatch lock and focus swapping between agents
- tab_ownership.py (deleted)
- test_tab_ownership.py (deleted)

Simplified tab commands: no lock checks, no _tab_list injection,
no _resolved_target_id params. agent_focus_target_id stays for
single-agent tab tracking.

Tested: 3 concurrent subagents on separate cloud sessions,
3 concurrent subagents on separate headless Chromium sessions.
2026-04-01 17:34:46 -07:00
MagMueller e600b70d45 fix: reuse querySelectorAll result to avoid duplicate DOM traversal 2026-04-01 16:55:52 -07:00
ShawnPana 73a926caa6 feat: add connect command + --agent flag, decouple multi-agent from Chrome
- browser-use connect: one-time command to discover and connect to local
  Chrome (like cloud connect but for local)
- --agent INDEX: per-command flag for multi-agent tab isolation, works
  with any browser mode (cloud, profile, cdp-url, headless)
- register is now per-session ({session}.agents.json)
- --connect deprecated with migration message
- SKILL.md updated for new connect/--agent workflow
- Tested: 3 concurrent agents on shared cloud browser session
2026-04-01 16:52:56 -07:00
MagMueller d2aab6b495 fix: address review comments (StopIteration, isClickable semantics) 2026-04-01 16:45:34 -07:00
MagMueller 2048a510d0 chore: remove accidentally committed submodules and generated files 2026-04-01 16:24:24 -07:00
MagMueller 163575d9d7 style: fix lint (unused import, redundant parens) 2026-04-01 16:24:17 -07:00
MagMueller 0db1dae8dc chore: remove local benchmark files from PR
These are developer-local stress test utilities, not production tests.
Keeping the PR focused on the 3 production fixes only.
2026-04-01 16:23:40 -07:00
MagMueller 92a2cc647a Merge remote-tracking branch 'upstream/main' into fix/heavy-page-dom-capture 2026-04-01 16:23:14 -07:00
MagMueller 89d82689d8 perf: fix O(n²) bottlenecks in DOM capture for heavy pages
Three targeted fixes for pages with 5k+ elements:

1. build_snapshot_lookup: convert isClickable list to set before loop
   _parse_rare_boolean_data used `index in list` (O(n) per call).
   Called once per node = O(n²). Now O(1) via set lookup.
   20k elements: 14,160ms → 2,973ms. 100k: 356s → 9s.

2. RectUnionPure: add _MAX_RECTS=5000 safety cap
   Paint order rect union fragments exponentially with overlapping
   layers. Uncapped, 20k elements took 372s. Capped: 4.7s.
   Degrades gracefully (less aggressive filtering, same correctness).

3. Skip JS listener detection on pages with >10k elements
   querySelectorAll('*') + per-element DOM.describeNode took 2.3s at 20k.
   Elements still detected via accessibility tree + heuristics.

Combined effect at 20k elements: ~400s → ~16s (25x faster).
Normal pages (<5k elements) are completely unaffected.
2026-04-01 16:21:33 -07:00
ShawnPana c4d5a0eace docs: add multi-session and config sections to browser-use skill
SKILL.md: added Multiple Browser Sessions section (with pointer to
references/multi-session.md) and Configuration section (config set/get/
list/unset, doctor, setup).

New references/multi-session.md: detailed guide for running multiple
browser sessions simultaneously with --session flag.
2026-04-01 16:04:44 -07:00
MagMueller 6a92780586 perf: fix O(n²) in build_snapshot_lookup — 39x speedup at 100k elements
_parse_rare_boolean_data used `index in list` (O(n) per call) on the
isClickable rare boolean data. Called once per node, this was O(n²)
total — the #1 bottleneck in the entire pipeline.

Fix: convert the list to a set once before the loop. O(1) per lookup.

Before → After:
  5k elements:  1,788ms →   768ms  (2.3x)
  20k elements: 16,819ms → 2,911ms (5.8x)
  100k elements: 356,629ms → 9,224ms (38.7x)

This single fix makes the full pipeline 2-3x faster at every scale:
  5k:  8.0s → ~3.8s
  20k: 29.7s → ~16s
  100k: impossible → ~20s (Chrome-limited, not Python-limited)
2026-04-01 15:53:47 -07:00
ShawnPana c19a9a5b1b feat: add browser-use config command (set, get, list, unset)
CLI config management driven by CONFIG_KEYS schema. Validates keys,
coerces types (int for timeout), shows all values with config list.
2026-04-01 15:49:17 -07:00
ShawnPana e7dfbd23ae feat: install.sh creates config.json on install
Creates ~/.browser-use/config.json with {} if it doesn't exist,
with 0600 permissions. Ensures config file is always present
regardless of install method.
2026-04-01 15:38:28 -07:00
ShawnPana d60dd24cdf feat: rewrite setup as interactive post-install command
Six steps: home dir, config.json, Chromium, profile-use, cloudflared,
validation + config display. Interactive prompts for downloads (steps
3-5), --yes for CI. Idempotent — skips what's already installed.
2026-04-01 15:36:47 -07:00
ShawnPana 2e7dff8aa6 feat: doctor shows config state with all keys from schema
After diagnostics, doctor now displays config section showing all
CONFIG_KEYS values: api_key (masked), profile ID, proxy, timeout.
Uses get_config_display() from config module. Includes docs link.
2026-04-01 15:33:14 -07:00
ShawnPana 7cf543dc91 feat: add CLI config module as single source of truth
New browser_use/skill_cli/config.py with CONFIG_KEYS schema, read/write
helpers, get_config_value (with defaults), and get_config_display (for
doctor). Refactored cloud.py to import from config module instead of
inline helpers. Removed hardcoded proxy code validation — server-side
validation covers 249 country codes.
2026-04-01 15:29:51 -07:00
MagMueller 30419e82dc bench: add threshold curve + JS boundary tests for heavy page strategy 2026-04-01 14:47:39 -07:00
MagMueller c1bfe4f328 bench: add per-method timing benchmark across scales (10k→1M) 2026-04-01 13:19:46 -07:00
MagMueller 715e0bbf02 chore: gitignore generated test HTML files 2026-04-01 12:25:18 -07:00
MagMueller 5ec5c8d43a perf: hoist CDP session lookup + cap paint order rect explosion
Two additional performance fixes for heavy pages:

1. Hoist get_or_create_cdp_session() outside _construct_enhanced_node
   Previously called once PER DOM NODE inside the recursive tree
   construction. On a 100k-element page, this was 100k+ async
   operations. Now resolved once before recursion starts.

2. Add _MAX_RECTS=5000 safety cap to RectUnionPure
   The paint order rect union can fragment exponentially with many
   overlapping translucent layers (each add() splits up to 4 rects).
   Cap prevents memory/CPU explosion on complex pages.

Also: expanded stress test suite to 15 pages (up to 132k elements)
including shadow DOM + iframe combos, overlapping layers, cross-origin
iframes, and a 100k flat element test. All 15 pass.
2026-04-01 12:25:10 -07:00
MagMueller dee1af29cc test: add heavy page DOM capture stress tests
10 progressively heavier test pages (1k to 50k+ elements):
- Flat divs, nested tables, shadow DOM, iframes, deep nesting
- Mega forms, SVG heavy, event listeners, cross-origin iframes
- Ultimate stress test combining everything

Two test modes:
- --dom-only: tests DOM capture without LLM (fast, no API key needed)
- --agent: tests full agent loop with real LLM on subset of pages
2026-04-01 11:44:31 -07:00
MagMueller 683994da8e fix: prevent DOM capture timeout on heavy pages (20k+ elements)
Pages with very large DOMs (e.g. Stimulsoft designer with 20,000+
elements) cause the browser state capture to time out, making the
agent unable to interact with the browser.

Three targeted fixes:

1. Skip JS listener detection on heavy pages (>10k elements)
   The querySelectorAll('*') + getEventListeners() loop followed by
   individual DOM.describeNode CDP calls for each listener element is
   O(n) and can take 10s+ alone on heavy pages.

2. Batch DOM.describeNode calls (chunks of 50)
   Previously all calls fired at once via asyncio.gather, flooding the
   CDP WebSocket and causing timeouts on concurrent operations.

3. Adaptive CDP timeouts based on page complexity
   - >15k elements: 25s initial / 10s retry (was 10s/2s)
   - >5k elements: 15s initial / 5s retry
   - Normal pages: unchanged 10s/2s
2026-04-01 11:25:03 -07:00
ShawnPana c2c9aa8556 feat: cloud signup command, unified base URL, auth isolation
- browser-use cloud signup: challenge-response agent self-registration
- browser-use cloud signup --verify: verify and save API key
- browser-use cloud signup --claim: generate account claim URL
- Base URL convention unified: BROWSER_USE_CLOUD_BASE_URL is host-only
  (e.g. https://api.browser-use.com), CLI appends /api/{version}
- CLI daemon blocked from falling back to library's cloud_auth.json
- cloud connect validates API key before spawning daemon
- No-key error message mentions cloud signup
2026-03-31 21:14:20 -07:00
ShawnPana 9fd3d81f8b fix: default cloud connect proxy to US, validate country codes
_get_cloud_connect_proxy defaults to 'us' when not in config.
Invalid codes (including null, false, 'none') return None (no proxy).
2026-03-31 14:17:42 -07:00
ShawnPana 032e73eec7 feat: config-driven proxy/timeout for cloud connect, enable recording
cloud connect reads cloud_connect_proxy and cloud_connect_timeout from
~/.browser-use/config.json. Recording always enabled via enableRecording
default on CreateBrowserRequest. No CLI flags — edit config for custom
settings, use cloud v2 REST for full control.
2026-03-31 13:44:27 -07:00
ShawnPana 2b9ee57975 fix: CloudBrowserClient respects BROWSER_USE_CLOUD_BASE_URL in CLI
CLIBrowserSession._provision_cloud_browser now reads the env var and
overrides CloudBrowserClient.api_base_url before provisioning. This
keeps the change CLI-only without modifying library code.
2026-03-31 12:51:44 -07:00
ShawnPana 584b1a0854 feat: add BROWSER_USE_CLOUD_BASE_URL env var override
Single env var to override the cloud API base URL for all versions.
Per-version overrides (BROWSER_USE_CLOUD_BASE_URL_V2/V3) still take
precedence for backward compatibility.
2026-03-31 12:38:01 -07:00
ShawnPana b4b2a4c18d feat: zero-config cloud connect with auto-managed profile
cloud connect now works with no flags. On first use, creates a
"Browser Use CLI" profile via the Cloud API and saves the ID to
config.json. Subsequent connects reuse it (validates on each call,
recreates if deleted).

Removed --timeout, --proxy-country, --profile-id flags and their
plumbing through daemon/sessions. Power users who need custom browser
settings use cloud v2 POST /browsers directly.
2026-03-31 12:28:42 -07:00
Saurav Panda e026a51fd3 fix: close httpx client pool on session stop to prevent OOM on Lambda (#4577)
## Summary
- Close `CloudBrowserClient`'s httpx connection pool in
`on_BrowserStopEvent` after `stop_browser()` completes
- Make `CloudBrowserClient.close()` idempotent (safe to call multiple
times)

## Problem
`BrowserSession.on_BrowserStopEvent` calls `stop_browser()` to end the
cloud session but never calls `_cloud_browser_client.close()`. The
underlying `httpx.AsyncClient` connection pool stays alive in memory.

On AWS Lambda with provisioned concurrency, the same container handles
many invocations sequentially. Each invocation creates a new
`BrowserSession` → new `CloudBrowserClient` → new `httpx.AsyncClient`,
but the old ones are never cleaned up. Memory climbs from ~1.3GB to the
3GB ceiling over hours, triggering OOM kills.

**Observed in production**: 21 `Runtime.ExitError` crashes in 6 hours,
`aws.lambda.enhanced.max_memory_used` saturated at 3,009 MB (the Lambda
limit), 3 confirmed `out_of_memory` events.

## Test plan
- [ ] Run `tests/ci` suite to verify no regressions
- [ ] Verify cloud browser sessions still clean up properly
(`stop_browser` + `close`)
- [ ] Verify calling `close()` twice doesn't raise


🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Close the `httpx` client pool when a cloud browser session stops to
prevent memory leaks and OOM on AWS Lambda. Also makes
`CloudBrowserClient.close()` safe to call multiple times.

- **Bug Fixes**
- Always call `_cloud_browser_client.close()` in
`BrowserSession.on_BrowserStopEvent` after `stop_browser()` (in finally)
to free the `httpx.AsyncClient` pool.
- Make `CloudBrowserClient.close()` idempotent by checking
`client.is_closed` before `aclose()`.

<sup>Written for commit 5e644981e8.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-03-31 11:39:04 -07:00
LarsenCundric 5e644981e8 fix: close CloudBrowserClient httpx pool on session stop to prevent memory leak
BrowserSession.on_BrowserStopEvent calls stop_browser() but never calls
_cloud_browser_client.close(), leaving the httpx connection pool alive.
On Lambda provisioned concurrency, these pools accumulate across
invocations — memory climbs from ~1.3GB to the 3GB ceiling over hours,
triggering OOM kills (21 Runtime.ExitError crashes in 6 hours observed
in production).

Changes:
- Call _cloud_browser_client.close() in on_BrowserStopEvent after
  stop_browser completes (in a finally block so it runs even if
  stop_browser fails)
- Make CloudBrowserClient.close() idempotent (check is_closed before
  calling aclose) so it's safe to call multiple times

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 11:19:43 -07:00
ShawnPana 04ca40c3af docs: add CDP/Python reference for browser-use skill
New references/cdp-python.md with tested recipes for raw CDP access
via the Python session: activating tabs, listing targets, running JS,
device emulation, cookies. SKILL.md updated with pointer to the
reference file explaining when to reach for CDP vs CLI commands.
2026-03-31 10:35:03 -07:00
ShawnPana 8d557b3de7 docs: update README tabs section and file layout for recent changes
Replace standalone switch/close-tab with tab subcommands.
Add state.json to file layout.
2026-03-30 20:11:39 -07:00
ShawnPana 526bde4d0f refactor: remove standalone switch and close-tab command aliases
These were duplicates of tab switch and tab close with separate
lock-checking and focus-resolution code paths. Having one way to
do each thing reduces maintenance surface and avoids isolation bugs.
2026-03-30 20:09:50 -07:00
ShawnPana a6744f4ade fix: tab close uses caller's logical focus, not Chrome's global focus
Default close-tab and tab-close paths used session_manager.get_focused_target()
which returns Chrome's globally focused tab. In multi-agent mode this lets
one agent close another's tab. Now uses agent_focus_target_id first, falling
back to global focus only in single-agent mode.

Also: fresh daemon spawn now uses phase-aware state file waiting (15s) instead
of fixed 5s socket polling, consistent with ensure_daemon's probe logic.
2026-03-30 20:04:01 -07:00
ShawnPana d59c0e6984 fix: CLI reconciliation gaps found by codex review
- ensure_daemon now phase-aware: waits for initializing/starting/
  shutting_down with staleness timeouts, errors on unhealthy sessions
- _close_session only cleans files after confirmed PID death
- _handle_sessions won't delete live daemon's files on stale terminal state
- _probe_session uses socket_pid for split-brain PID resolution
- _is_daemon_process works on Windows (wmic)
- Updated + added robustness tests (codex-authored)
2026-03-30 19:49:57 -07:00
ShawnPana 7c7fd37220 test: add daemon lifecycle tests with real subprocess daemons
15 tests covering state file transitions, _probe_session branches (live
daemon, dead PID, no files, corrupt state), close via socket, close
orphaned daemon, close --all with mixed sessions, sessions listing with
phase column, and stale/terminal state cleanup.

Uses real daemon subprocesses with BROWSER_USE_HOME overridden to temp
dirs. No mocking.
2026-03-30 19:09:00 -07:00
ShawnPana f222c98bbf feat: unified session probe and cross-platform lifecycle helpers
Replace scattered PID/socket/process checks with _probe_session() that
reads the state file, reconciles PIDs, checks liveness, and probes the
socket without deleting anything. Callers decide cleanup policy.

Adds _is_pid_alive, _is_daemon_process, _terminate_pid (cross-platform
with SIGKILL escalation), _close_session (shared by close and close-all).

sessions command now shows phase column. close and close-all both handle
orphaned daemons via SIGTERM fallback. close polls for PID disappearance
up to 15s before giving up.
2026-03-30 18:47:34 -07:00
ShawnPana 8ade5748e2 feat: add daemon lifecycle state file and shutdown re-entrancy guard
Daemon now writes <session>.state.json at each lifecycle transition
(initializing → ready → starting → running → shutting_down → stopped/failed).
All shutdown triggers funneled through _request_shutdown() to prevent
double cleanup. Startup rollback on failure cleans up browser resources.

Also fixes cloud browser leak: CLIBrowserSession.stop() now explicitly
stops the remote browser via API instead of just disconnecting the
websocket.

Daemon ping response now includes PID for split-brain resolution.
2026-03-30 18:43:21 -07:00
ShawnPana ac48ebecaf fix: SIGTERM fallback for orphaned daemons in close command
When the daemon's socket is unreachable but the PID file references a
live process, close now sends SIGTERM directly instead of printing
"No active browser session" and leaving the daemon running forever.
2026-03-30 18:39:06 -07:00
ShawnPana e989797541 fix: sweep orphaned sockets in sessions command
Sockets without a corresponding live PID file were never cleaned up
because cleanup was only triggered per-session on next use. Add a
glob pass in _handle_sessions to remove any .sock file that doesn't
match a live session.
2026-03-29 18:05:56 -07:00
ShawnPana 18c2199e22 fix: serialize multi-agent focus swaps with dispatch lock
Concurrent agents could interleave focus swaps on the shared
BrowserSession, corrupting each other's state. Wrap the entire
swap-execute-restore cycle in _dispatch_lock and separate the
tab-ownership path from single-agent mode.
2026-03-28 15:58:29 -07:00
ShawnPana 4be3525d81 fix: use JS scrollBy instead of CDP gesture for CLI scroll command
CDP input gesture simulation doesn't work in --connect mode (external
Chrome). Switch to window.scrollBy via Runtime.evaluate which works
regardless of connection mode.
2026-03-28 15:58:24 -07:00