Previously __import__ was a plain closure whose __globals__ contained
this module's real __builtins__, so sandboxed code could escape via
__import__.__globals__['__builtins__']['__import__']('os').
Wrap _safe_import in _SafeBuiltin (same hardening as other builtins) so
its __globals__ attribute is not reachable from user code. Also resolve
_real_import at wrap-time rather than closing over __builtins__ to keep
the closure minimal.
Replace closure wrapper with a callable class (_SafeBuiltin) built in an
isolated exec namespace. Closures leak this module's globals via
wrapper.__globals__ (print.__globals__['io'], print.__globals__['Path']).
The callable class has no __globals__ attribute at instance level, and
the class methods' __globals__ is the isolated namespace instead of this
module.
Also block pathlib and asyncio imports — both modules import os directly,
so Path.cwd.__globals__['os'] and asyncio.subprocess were escape routes.
Remove os from the default namespace and restrict __builtins__ to a
curated safe set (no open/exec/eval/compile). Add an import guard that
blocks os, subprocess, shutil, sys, builtins, and other system-access
modules. Preserve __build_class__ so class definitions still work.
Wrap builtin functions to prevent __self__ leaking the builtins module.
Remove Path and asyncio from pre-loaded namespace to close
Path.cwd.__globals__ and asyncio.sys.modules escape routes.
test_cli_upload and test_cli_coordinate_click create SessionInfo
without actions, hitting the type-narrowing guard. Now provide
ActionHandler in all test cases.
- Ruff format all skill_cli and test files
- Fix type: get_config_value returns str|int|None, callers cast properly
- Fix type: BrowserWrapper.actions is non-optional (always provided)
- Fix type: config comparison uses 'is' not '=='
- Rewrite test_setup_command for new setup.handle(yes=True) API
- Add None guard in test_cli_lifecycle for state file
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.
- 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)
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.
Multiple agents can share one browser via --connect without interfering
with each other. Each agent registers with `browser-use register` to get
a numeric index, then passes it with `--connect <index>` on every command.
- Tab locking: mutating commands (click, type, open) lock the tab to the
agent. Other agents get an error if they try to mutate the same tab.
Read-only commands (state, screenshot) work on any tab.
- Agent registry: agents.json tracks registered agents with timestamps.
Expired agents (5min inactive) get cleaned up automatically.
- Session lock: prevents double BrowserSession creation when two agents
connect simultaneously.
- Focus swap: daemon swaps agent_focus_target_id and cached_selector_map
per-agent before each command, so element indices are isolated.
- Updated the assertion for the User-Agent header in the /json/version endpoint test to improve readability by using a multi-line format.
- This change enhances the clarity of the test's intent while maintaining the existing validation logic.
These modifications contribute to better maintainability of the test code.
- Improved assertions in tests to ensure all user-provided headers are present in the additional_headers.
- Added checks to confirm that the User-Agent header is injected for remote connections, ensuring consistent behavior across tests.
- Updated the test for the /json/version endpoint to validate the presence and format of the User-Agent header.
These changes enhance the reliability of header handling in the CDP client tests.
- Intercept `cloud --help` early so it routes to _print_cloud_usage()
instead of argparse's generic stub
- Remove permissive `or 'cloud'` fallback in epilog test assertion
- Delete no-op regex test that didn't verify actual code
- Tighten default socket path assertion to check 'default.sock'
- Add `browser-use upload <index> <path>` command for uploading files to
file input elements via the CLI
- Extract find_file_input_near_element from nested closures in tools/service.py
to a reusable method on BrowserSession, deduplicating two copies
- Add BrowserWrapper.upload() for the Python REPL
- Resolve file paths to absolute on the client side before sending to daemon
- Update SKILL.md files and README with upload command docs
Unify all CLI-managed files under ~/.browser-use/ (config, sockets, PIDs,
binaries, tunnels) instead of scattering across ~/.config/browser-use/ and
~/.browser-use/run/. Add profile-use Go binary as managed subcommand via
browser-use profile, with auto-download fallback and install.sh integration.
Wire cloudflared and profile-use availability checks into browser-use doctor.
Adds `--connect` to auto-discover running Chrome instances via DevToolsActivePort
files and well-known port probing, eliminating manual CDP URL construction. Fixes
daemon process hanging on `close` when connected to external browsers (--connect,
--cdp-url, cloud) by calling stop() (disconnect) instead of kill() (terminate).
Login/logout with API key persistence, versioned REST calls (v2/v3),
task polling, and OpenAPI-driven help. Stdlib only, no daemon needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the multi-session server (server.py, SessionRegistry, portalocker locking,
PID files, orphan detection) with a minimal daemon (daemon.py) that holds one
BrowserSession in memory. Socket file existence = alive. Auto-exits when browser
dies via CDP watchdog.
-2277 lines, +142 lines across 20 files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>